diff options
author | Thomas Müller <thomas.mueller@tmit.eu> | 2014-03-28 10:27:15 +0100 |
---|---|---|
committer | Thomas Müller <thomas.mueller@tmit.eu> | 2014-03-28 10:27:15 +0100 |
commit | e3b951f4122f0a5d8531faaf4082bfb356366847 (patch) | |
tree | 87246f2eaba0c89db88496184b72c6b49eb4d77e /core | |
parent | ccc55f40e6be7c9cc5e5484721998b2d60aeedc9 (diff) | |
parent | eeaefd84c3911a166920084947ad1018c744e6a6 (diff) | |
download | nextcloud-server-e3b951f4122f0a5d8531faaf4082bfb356366847.tar.gz nextcloud-server-e3b951f4122f0a5d8531faaf4082bfb356366847.zip |
Merge pull request #7724 from owncloud/mobile
[WIP] Mobile optimization for base layout and Files app
Diffstat (limited to 'core')
-rw-r--r-- | core/css/mobile.css | 85 | ||||
-rw-r--r-- | core/css/styles.css | 1 | ||||
-rw-r--r-- | core/js/js.js | 108 | ||||
-rw-r--r-- | core/js/share.js | 8 | ||||
-rw-r--r-- | core/js/tests/specs/coreSpec.js | 104 | ||||
-rw-r--r-- | core/templates/layout.user.php | 2 |
6 files changed, 302 insertions, 6 deletions
diff --git a/core/css/mobile.css b/core/css/mobile.css index a63aa902d34..c67ac3e5ecf 100644 --- a/core/css/mobile.css +++ b/core/css/mobile.css @@ -1,4 +1,12 @@ -@media only screen and (max-width: 600px) { +@media only screen and (max-width: 768px) { + +/* show caret indicator next to logo to make clear it is tappable */ +#owncloud.menutoggle { + background-image: url('../img/actions/caret.svg'); + background-repeat: no-repeat; + background-position: right 26px; + padding-right: 16px !important; +} /* compress search box on mobile, expand when focused */ .searchbox input[type="search"] { @@ -18,5 +26,80 @@ display: none; } +/* toggle navigation */ +#content-wrapper { + padding-left: 0; +} + +#navigation { + top: 45px; + bottom: initial; + width: 255px; + max-height: 90%; + margin-top: 0; + top: 45px; + background-color: rgba(36, 40, 47, .97); + overflow-x: initial; + border-bottom-right-radius: 7px; + border-bottom: 1px #333 solid; + border-right: 1px #333 solid; + box-shadow: 0 0 7px rgba(29,45,68,.97); + display: none; +} +#navigation, #navigation * { + box-sizing:border-box; -moz-box-sizing:border-box; +} +#navigation li { + display: inline-block; +} +#navigation a { + width: 80px; + height: 80px; + display: inline-block; + text-align: center; + padding: 20px 0; +} +#navigation a span { + display: inline-block; + font-size: 13px; + padding-bottom: 0; + padding-left: 0; + width: 80px; +} +#navigation .icon { + margin: 0 auto; + padding: 0; +} +#navigation li:first-child .icon { + padding-top: 0; +} +/* Apps management as sticky footer */ +#navigation .wrapper { + min-height: initial; + margin: 0; +} +#apps-management, #navigation .push { + height: initial; +} + + + +/* shift to account for missing navigation */ +#body-user #controls, +#body-settings #controls { + padding-left: 0; +} + +/* don’t require a minimum width for controls bar */ +#controls { + min-width: initial !important; +} + +/* position share dropdown */ +#dropdown { + margin-right: 10% !important; + width: 80% !important; +} + } diff --git a/core/css/styles.css b/core/css/styles.css index 89f58828cf9..2c043ab724c 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -248,6 +248,7 @@ input[type="submit"].enabled { -webkit-box-sizing: border-box; box-sizing: border-box; position: fixed; + top:45px; right: 0; left: 0; height: 44px; diff --git a/core/js/js.js b/core/js/js.js index 3d3185f12c1..721a5a2e927 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -482,6 +482,53 @@ var OC={ }).show(); }, 'html'); } + }, + + // for menu toggling + registerMenu: function($toggle, $menuEl) { + $menuEl.addClass('menu'); + $toggle.addClass('menutoggle'); + $toggle.on('click.menu', function(event) { + if ($menuEl.is(OC._currentMenu)) { + $menuEl.hide(); + OC._currentMenu = null; + OC._currentMenuToggle = null; + return false; + } + // another menu was open? + else if (OC._currentMenu) { + // close it + OC._currentMenu.hide(); + } + $menuEl.show(); + OC._currentMenu = $menuEl; + OC._currentMenuToggle = $toggle; + return false + }); + }, + + unregisterMenu: function($toggle, $menuEl) { + // close menu if opened + if ($menuEl.is(OC._currentMenu)) { + $menuEl.hide(); + OC._currentMenu = null; + OC._currentMenuToggle = null; + } + $toggle.off('click.menu').removeClass('menutoggle'); + $menuEl.removeClass('menu'); + }, + + /** + * Wrapper for matchMedia + * + * This is makes it possible for unit tests to + * stub matchMedia (which doesn't work in PhantomJS) + */ + _matchMedia: function(media) { + if (window.matchMedia) { + return window.matchMedia(media); + } + return false; } }; OC.search.customResults={}; @@ -940,6 +987,67 @@ function initCore() { $('a.action').tipsy({gravity:'s', fade:true, live:true}); $('td .modified').tipsy({gravity:'s', fade:true, live:true}); $('input').tipsy({gravity:'w', fade:true}); + + // toggle for menus + $(document).on('mouseup.closemenus', function(event) { + var $el = $(event.target); + if ($el.closest('.menu').length || $el.closest('.menutoggle').length) { + // don't close when clicking on the menu directly or a menu toggle + return false; + } + if (OC._currentMenu) { + OC._currentMenu.hide(); + } + OC._currentMenu = null; + OC._currentMenuToggle = null; + }); + + + /** + * Set up the main menu toggle to react to media query changes. + * If the screen is small enough, the main menu becomes a toggle. + * If the screen is bigger, the main menu is not a toggle any more. + */ + function setupMainMenu() { + // toggle the navigation on mobile + if (!OC._matchMedia) { + return; + } + var mq = OC._matchMedia('(max-width: 768px)'); + var lastMatch = mq.matches; + var $toggle = $('#header #owncloud'); + var $navigation = $('#navigation'); + + function updateMainMenu() { + // mobile mode ? + if (lastMatch && !$toggle.hasClass('menutoggle')) { + // init the menu + OC.registerMenu($toggle, $navigation); + $toggle.data('oldhref', $toggle.attr('href')); + $toggle.attr('href', '#'); + $navigation.hide(); + } + else { + OC.unregisterMenu($toggle, $navigation); + $toggle.attr('href', $toggle.data('oldhref')); + $navigation.show(); + } + } + + updateMainMenu(); + + // TODO: debounce this + $(window).resize(function() { + if (lastMatch !== mq.matches) { + lastMatch = mq.matches; + updateMainMenu(); + } + }); + } + + if (window.matchMedia) { + setupMainMenu(); + } } $(document).ready(initCore); diff --git a/core/js/share.js b/core/js/share.js index 9ee50ff6963..e769edd0a21 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -48,7 +48,7 @@ OC.Share={ var action = $(file).find('.fileactions .action[data-action="Share"]'); var img = action.find('img').attr('src', image); action.addClass('permanent'); - action.html(' '+t('core', 'Shared')).prepend(img); + action.html(' <span>'+t('core', 'Shared')+'</span>').prepend(img); } else { var dir = $('#dir').val(); if (dir.length > 1) { @@ -63,7 +63,7 @@ OC.Share={ if (img.attr('src') != OC.imagePath('core', 'actions/public')) { img.attr('src', image); $(action).addClass('permanent'); - $(action).html(' '+t('core', 'Shared')).prepend(img); + $(action).html(' <span>'+t('core', 'Shared')+'</span>').prepend(img); } }); } @@ -103,10 +103,10 @@ OC.Share={ var img = action.find('img').attr('src', image); if (shares) { action.addClass('permanent'); - action.html(' '+ escapeHTML(t('core', 'Shared'))).prepend(img); + action.html(' <span>'+ escapeHTML(t('core', 'Shared'))+'</span>').prepend(img); } else { action.removeClass('permanent'); - action.html(' '+ escapeHTML(t('core', 'Share'))).prepend(img); + action.html(' <span>'+ escapeHTML(t('core', 'Share'))+'</span>').prepend(img); } } } diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js index 069546387c7..57ea5be8be0 100644 --- a/core/js/tests/specs/coreSpec.js +++ b/core/js/tests/specs/coreSpec.js @@ -279,5 +279,109 @@ describe('Core base tests', function() { expect(OC.generateUrl('apps/files/download{file}', {file: '/Welcome.txt'})).toEqual(OC.webroot + '/index.php/apps/files/download/Welcome.txt'); }); }); + describe('Main menu mobile toggle', function() { + var oldMatchMedia; + var $toggle; + var $navigation; + + beforeEach(function() { + oldMatchMedia = OC._matchMedia; + // a separate method was needed because window.matchMedia + // cannot be stubbed due to a bug in PhantomJS: + // https://github.com/ariya/phantomjs/issues/12069 + OC._matchMedia = sinon.stub(); + $('#testArea').append('<div id="header">' + + '<a id="owncloud" href="#"></a>' + + '</div>' + + '<div id="navigation"></div>'); + $toggle = $('#owncloud'); + $navigation = $('#navigation'); + }); + + afterEach(function() { + OC._matchMedia = oldMatchMedia; + }); + it('Sets up menu toggle in mobile mode', function() { + OC._matchMedia.returns({matches: true}); + window.initCore(); + expect($toggle.hasClass('menutoggle')).toEqual(true); + expect($navigation.hasClass('menu')).toEqual(true); + }); + it('Does not set up menu toggle in desktop mode', function() { + OC._matchMedia.returns({matches: false}); + window.initCore(); + expect($toggle.hasClass('menutoggle')).toEqual(false); + expect($navigation.hasClass('menu')).toEqual(false); + }); + it('Switches on menu toggle when mobile mode changes', function() { + var mq = {matches: false}; + OC._matchMedia.returns(mq); + window.initCore(); + expect($toggle.hasClass('menutoggle')).toEqual(false); + mq.matches = true; + $(window).trigger('resize'); + expect($toggle.hasClass('menutoggle')).toEqual(true); + }); + it('Switches off menu toggle when mobile mode changes', function() { + var mq = {matches: true}; + OC._matchMedia.returns(mq); + window.initCore(); + expect($toggle.hasClass('menutoggle')).toEqual(true); + mq.matches = false; + $(window).trigger('resize'); + expect($toggle.hasClass('menutoggle')).toEqual(false); + }); + it('Clicking menu toggle toggles navigation in mobile mode', function() { + OC._matchMedia.returns({matches: true}); + window.initCore(); + $navigation.hide(); // normally done through media query triggered CSS + expect($navigation.is(':visible')).toEqual(false); + $toggle.click(); + expect($navigation.is(':visible')).toEqual(true); + $toggle.click(); + expect($navigation.is(':visible')).toEqual(false); + }); + it('Clicking menu toggle does not toggle navigation in desktop mode', function() { + OC._matchMedia.returns({matches: false}); + window.initCore(); + expect($navigation.is(':visible')).toEqual(true); + $toggle.click(); + expect($navigation.is(':visible')).toEqual(true); + }); + it('Switching to mobile mode hides navigation', function() { + var mq = {matches: false}; + OC._matchMedia.returns(mq); + window.initCore(); + expect($navigation.is(':visible')).toEqual(true); + mq.matches = true; + $(window).trigger('resize'); + expect($navigation.is(':visible')).toEqual(false); + }); + it('Switching to desktop mode shows navigation', function() { + var mq = {matches: true}; + OC._matchMedia.returns(mq); + window.initCore(); + expect($navigation.is(':visible')).toEqual(false); + mq.matches = false; + $(window).trigger('resize'); + expect($navigation.is(':visible')).toEqual(true); + }); + it('Switch to desktop with opened menu then back to mobile resets toggle', function() { + var mq = {matches: true}; + OC._matchMedia.returns(mq); + window.initCore(); + expect($navigation.is(':visible')).toEqual(false); + $toggle.click(); + expect($navigation.is(':visible')).toEqual(true); + mq.matches = false; + $(window).trigger('resize'); + expect($navigation.is(':visible')).toEqual(true); + mq.matches = true; + $(window).trigger('resize'); + expect($navigation.is(':visible')).toEqual(false); + $toggle.click(); + expect($navigation.is(':visible')).toEqual(true); + }); + }); }); diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index 789b4dcf902..ba5f6ef9b54 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -15,7 +15,7 @@ </title> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> - <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=1.0"> + <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"> <meta name="apple-itunes-app" content="app-id=543672169"> <link rel="shortcut icon" href="<?php print_unescaped(image_path('', 'favicon.png')); ?>" /> <link rel="apple-touch-icon-precomposed" href="<?php print_unescaped(image_path('', 'favicon-touch.png')); ?>" /> |