diff options
-rw-r--r-- | core/css/header.scss | 201 | ||||
-rw-r--r-- | core/js/js.js | 15 | ||||
-rw-r--r-- | core/templates/layout.user.php | 123 | ||||
-rw-r--r-- | lib/private/TemplateLayout.php | 2 | ||||
-rw-r--r-- | lib/private/legacy/app.php | 83 | ||||
-rw-r--r-- | settings/js/apps.js | 105 |
6 files changed, 438 insertions, 91 deletions
diff --git a/core/css/header.scss b/core/css/header.scss index 2b73937a3c4..2f0c1522b0b 100644 --- a/core/css/header.scss +++ b/core/css/header.scss @@ -109,7 +109,7 @@ height: 34px; } .header-appname-container { - display: inline-block; + display: none; padding-top: 22px; padding-right: 10px; flex-shrink: 0; @@ -181,29 +181,31 @@ font-size: 16px; font-weight: 300; margin: 0; - margin-top: -27px; + margin-top: -26px; padding: 7px 0 7px 5px; vertical-align: middle; } - - /* do not show menu toggle on public share links as there is no menu */ #body-public #header .icon-caret { display: none; } /* NAVIGATION --------------------------------------------------------------- */ +nav { + margin-top: auto; +} + #navigation { - position: fixed; + position: relative; top: 45px; - left: 10px; + left: -100%; width: 265px; max-height: 85%; margin-top: 0; padding-bottom: 10px; - background-color: rgba(255, 255, 255, 0.97); - box-shadow: 0 1px 10px rgba(150, 150, 150, 0.75); + background-color: rgba(255, 255, 255, .97); + box-shadow: 0 1px 10px rgba(150, 150, 150, .75); border-radius: 3px; border-top-left-radius: 0; border-top-right-radius: 0; @@ -212,7 +214,48 @@ z-index: 2000; &:after { left: 47%; + bottom: 100%; + border: solid transparent; + content: ' '; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-color: rgba(0, 0, 0, 0); + border-bottom-color: rgba(255, 255, 255, .97); + border-width: 9px; + margin-left: -9px; } +} + +/* arrow look */ + +#expanddiv:after { + bottom: 100%; + border: solid transparent; + content: ' '; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-color: rgba(0, 0, 0, 0); + border-bottom-color: rgba(255, 255, 255, .97); + border-width: 10px; + margin-left: -10px; +} + +/* position of dropdown arrow */ + +#navigation:after { + left: 242px; +} + +#expanddiv:after { + right: 15px; +} + +#navigation { + box-sizing: border-box; * { box-sizing: border-box; } @@ -307,6 +350,9 @@ #apps { max-height: calc(100vh - 100px); overflow: auto; + .in-header { + display: none; + } } /* USER MENU -----------------------------------------------------------------*/ @@ -375,7 +421,7 @@ z-index: 2000; display: none; background: rgb(255, 255, 255); - box-shadow: 0 1px 10px rgba(150, 150, 150, 0.75); + box-shadow: 0 1px 10px rgba(150, 150, 150, .75); border-radius: 3px; border-top-left-radius: 0; border-top-right-radius: 0; @@ -405,3 +451,140 @@ } } } + +/* do not show display name when profile picture is present */ + +#header { + .avatardiv.avatardiv-shown + #expandDisplayName { + display: none; + } + #expand { + display: block; + } +} + +#appmenu { + display: inline-block; + width: auto; + clear: both; + height: 44px; + + li { + float: left; + display: inline-block; + vertical-align: top !important; + height: 20px; + padding: 12px; + + a { + opacity: 0.6; + margin: 0; + text-align: center; + vertical-align: top !important; + position: relative; + height: 44px; + } + } + + li:hover a, + li a.active { + opacity: 1; + + } + + li img, + .icon-more-white { + display: inline-block; + width: 20px; + height: 20px; + } + + li span { + display: none; + position: absolute; + overflow: visible; + background-color: rgba(255, 255, 255, .97); + white-space: nowrap; + border: none; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + border-top-left-radius: 0; + border-top-right-radius: 0; + margin-top: 0; + color: rgba(0, 0, 0, .6); + width: auto; + left: 50%; + top: 31px; + transform: translateX(-50%); + padding: 4px 10px; + -webkit-filter: drop-shadow(0 0 5px rgba(150, 150, 150, .75)); + -moz-filter: drop-shadow(0 0 5px rgba(150, 150, 150, .75)); + -ms-filter: drop-shadow(0 0 5px rgba(150, 150, 150, .75)); + -o-filter: drop-shadow(0 0 5px rgba(150, 150, 150, .75)); + filter: drop-shadow(0 0 5px rgba(150, 150, 150, .75)); + } + + li:hover span { + display: inline-block; + } + + + li:hover a:before, + li a.active:before { + content: ' '; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border: 0 solid transparent; + border-bottom-color: white; + border-width: 10px; + transform: translateX(-50%); + left: 50%; + top: 12px; + z-index: 100; + display: block; + } + &.menu-open li:hover a:before, + &.menu-open li a.active:before, + &.menu-open li:hover span { + display: none !important; + } + + /* do not show active indicator when hovering other icons */ + &:hover li:not(:hover) a:before { + display: none; + } + + li.hidden { + display: none; + } + +} + +/* use popover menu on mobile and small screens */ +@media only screen and (max-width: 600px) { + + #header .header-appname-container { + display: inline-block !important; + } + + #appmenu { + display: none; + } + + #apps .in-header { + display: inline-block; + } + + #navigation { + position: fixed; + top: 45px; + left: 10px; + &:after { + left: 214px; + } + } + +}
\ No newline at end of file diff --git a/core/js/js.js b/core/js/js.js index 6fd66c9c9bb..c8907cdfc90 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -1369,6 +1369,10 @@ function initCore() { * If the screen is bigger, the main menu is not a toggle any more. */ function setupMainMenu() { + + // init the more-apps menu + OC.registerMenu($('#more-apps'), $('#navigation')); + // toggle the navigation var $toggle = $('#header .header-appname-container'); var $navigation = $('#navigation'); @@ -1438,13 +1442,20 @@ function initCore() { // move triangle of apps dropdown to align with app name triangle // 2 is the additional offset between the triangles if($('#navigation').length) { - $('#header #nextcloud + .menutoggle').one('click', function(){ + $('#header #nextcloud + .menutoggle').on('click', function(){ + $('#menu-css-helper').remove(); var caretPosition = $('.header-appname + .icon-caret').offset().left - 2; if(caretPosition > 255) { // if the app name is longer than the menu, just put the triangle in the middle return; } else { - $('head').append('<style>#navigation:after { left: '+ caretPosition +'px; }</style>'); + $('head').append('<style id="menu-css-helper">#navigation:after { left: '+ caretPosition +'px; }</style>'); + } + }); + $('#header #appmenu .menutoggle').on('click', function() { + $('#appmenu').toggleClass('menu-open'); + if($('#appmenu').is(':visible')) { + $('#menu-css-helper').remove(); } }); } diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index e9a9b042e07..3cfb88bf423 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -58,9 +58,88 @@ </h1> <div class="icon-caret"></div> </a> + + <div id="appmenu"> + <ul> + <?php $headerIconCount = 8; ?> + <?php foreach($_['headernavigation'] as $entry): ?> + <li data-id="<?php p($entry['id']); ?>"> + <a href="<?php print_unescaped($entry['href']); ?>" tabindex="3" + <?php if( $entry['active'] ): ?> class="active"<?php endif; ?>> + <img src="<?php print_unescaped($entry['icon'] . '?v=' . $_['versionHash']); ?>" class="app-icon" /> + <div class="icon-loading-dark" style="display:none;"></div> + <span> + <?php p($entry['name']); ?> + </span> + </a> + </li> + <?php endforeach; ?> + <li id="more-apps" class="menutoggle<?php if (!(count($_['navigation']) > $headerIconCount || (OC_User::isAdminUser(OC_User::getUser()) && count($_['navigation'])>=$headerIconCount))): ?> hidden<?php endif; ?>"> + <a href="#"> + <div class="icon-more-white"></div> + <span><?php p($l->t('More apps')); ?></span> + </a> + </li> + <?php if(OC_User::isAdminUser(OC_User::getUser())): ?> + <li id="apps-management" <?php if(count($_['navigation'])>$headerIconCount-1): ?>class="hidden"<?php endif; ?>> + <a href="<?php print_unescaped(\OC::$server->getURLGenerator()->linkToRoute('settings.AppSettings.viewApps')); ?>" tabindex="4" + <?php if( $_['appsmanagement_active'] ): ?> class="active"<?php endif; ?>> + <img src="<?php print_unescaped(image_path('settings', 'apps.svg') . '?v=' . $_['versionHash']); ?>" /> + <div class="icon-loading-dark" style="display:none;"></div> + <span><?php p($l->t('Apps')); ?></span> + </a> + </li> + <?php endif; ?> + </ul> + </div> + + <nav role="navigation"><div id="navigation"> + <div id="apps"> + <ul> + <?php foreach($_['navigation'] as $entry): ?> + <?php if($entry['showInHeader']): ?> + <li data-id="<?php p($entry['id']); ?>" class="in-header"> + <?php else: ?> + <li data-id="<?php p($entry['id']); ?>"> + <?php endif; ?> + <a href="<?php print_unescaped($entry['href']); ?>" tabindex="3" + <?php if( $entry['active'] ): ?> class="active"<?php endif; ?>> + <svg width="32" height="32" viewBox="0 0 32 32"> + <defs><filter id="invert"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"></feColorMatrix></filter></defs> + <image x="0" y="0" width="32" height="32" preserveAspectRatio="xMinYMin meet" filter="url(#invert)" xlink:href="<?php print_unescaped($entry['icon'] . '?v=' . $_['versionHash']); ?>" class="app-icon"></image> + </svg> + <div class="icon-loading-dark" style="display:none;"></div> + <span> + <?php p($entry['name']); ?> + </span> + </a> + </li> + <?php endforeach; ?> + <?php + /* show "More apps" link to app administration directly in app navigation, as last entry */ + if(OC_User::isAdminUser(OC_User::getUser())): + ?> + <li id="apps-management"> + <a href="<?php print_unescaped(\OC::$server->getURLGenerator()->linkToRoute('settings.AppSettings.viewApps')); ?>" tabindex="4" + <?php if( $_['appsmanagement_active'] ): ?> class="active"<?php endif; ?>> + <svg width="32" height="32" viewBox="0 0 32 32" class="app-icon"> + <defs><filter id="invert"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"></feColorMatrix></filter></defs> + <image x="0" y="0" width="32" height="32" preserveAspectRatio="xMinYMin meet" filter="url(#invert)" xlink:href="<?php print_unescaped(image_path('settings', 'apps.svg') . '?v=' . $_['versionHash']); ?>"></image> + </svg> + <div class="icon-loading-dark" style="display:none;"></div> + <span> + <?php p($l->t('Apps')); ?> + </span> + </a> + </li> + <?php endif; ?> + + </ul> + </div> + </div></nav> + </div> - <div id="logo-claim" style="display:none;"><?php p($theme->getLogoClaim()); ?></div> <div id="header-right"> <form class="searchbox" action="#" method="post" role="search" novalidate> <label for="searchbox" class="hidden-visually"> @@ -102,52 +181,12 @@ </a> </li> </ul> + </div> </div> </div> </div></header> - <nav role="navigation"><div id="navigation"> - <div id="apps"> - <ul> - <?php foreach($_['navigation'] as $entry): ?> - <li data-id="<?php p($entry['id']); ?>"> - <a href="<?php print_unescaped($entry['href']); ?>" tabindex="3" - <?php if( $entry['active'] ): ?> class="active"<?php endif; ?>> - <svg width="32" height="32" viewBox="0 0 32 32"> - <defs><filter id="invert"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"></feColorMatrix></filter></defs> - <image x="0" y="0" width="32" height="32" preserveAspectRatio="xMinYMin meet" filter="url(#invert)" xlink:href="<?php print_unescaped($entry['icon'] . '?v=' . $_['versionHash']); ?>" class="app-icon"></image> - </svg> - <div class="icon-loading-dark" style="display:none;"></div> - <span> - <?php p($entry['name']); ?> - </span> - </a> - </li> - <?php endforeach; ?> - <?php - /* show "More apps" link to app administration directly in app navigation, as last entry */ - if(OC_User::isAdminUser(OC_User::getUser())): - ?> - <li id="apps-management"> - <a href="<?php print_unescaped(\OC::$server->getURLGenerator()->linkToRoute('settings.AppSettings.viewApps')); ?>" tabindex="4" - <?php if( $_['appsmanagement_active'] ): ?> class="active"<?php endif; ?>> - <svg width="32" height="32" viewBox="0 0 32 32" class="app-icon"> - <defs><filter id="invert"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"></feColorMatrix></filter></defs> - <image x="0" y="0" width="32" height="32" preserveAspectRatio="xMinYMin meet" filter="url(#invert)" xlink:href="<?php print_unescaped(image_path('settings', 'apps.svg') . '?v=' . $_['versionHash']); ?>"></image> - </svg> - <div class="icon-loading-dark" style="display:none;"></div> - <span> - <?php p($l->t('Apps')); ?> - </span> - </a> - </li> - <?php endif; ?> - - </ul> - </div> - </div></nav> - <div id="sudo-login-background" class="hidden"></div> <form id="sudo-login-form" class="hidden"> <?php p($l->t('This action requires you to confirm your password:')); ?><br> diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index ccd53c9cafa..3f8c75adc84 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -76,6 +76,8 @@ class TemplateLayout extends \OC_Template { $this->assign( 'appid', $appId ); $navigation = \OC_App::getNavigation(); $this->assign( 'navigation', $navigation); + $navigation = \OC_App::getHeaderNavigation(); + $this->assign( 'headernavigation', $navigation); $settingsNavigation = \OC_App::getSettingsNavigation(); $this->assign( 'settingsnavigation', $settingsNavigation); foreach($navigation as $entry) { diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php index f89f32f069a..c82d620882d 100644 --- a/lib/private/legacy/app.php +++ b/lib/private/legacy/app.php @@ -529,25 +529,76 @@ class OC_App { // This is private as well. It simply works, so don't ask for more details private static function proceedNavigation($list) { + $headerIconCount = 8; + if(OC_User::isAdminUser(OC_User::getUser())) { + $headerIconCount--; + } + usort($list, function($a, $b) { + if (isset($a['order']) && isset($b['order'])) { + return ($a['order'] < $b['order']) ? -1 : 1; + } else if (isset($a['order']) || isset($b['order'])) { + return isset($a['order']) ? -1 : 1; + } else { + return ($a['name'] < $b['name']) ? -1 : 1; + } + }); + + $activeAppIndex = -1; $activeApp = OC::$server->getNavigationManager()->getActiveEntry(); - foreach ($list as &$navEntry) { + foreach ($list as $index => &$navEntry) { if ($navEntry['id'] == $activeApp) { $navEntry['active'] = true; + $activeAppIndex = $index; } else { $navEntry['active'] = false; } } unset($navEntry); - usort($list, function($a, $b) { - if (isset($a['order']) && isset($b['order'])) { - return ($a['order'] < $b['order']) ? -1 : 1; - } else if (isset($a['order']) || isset($b['order'])) { - return isset($a['order']) ? -1 : 1; + if($activeAppIndex > ($headerIconCount-1)) { + $active = $list[$activeAppIndex]; + $lastInHeader = $list[$headerIconCount-1]; + $list[$headerIconCount-1] = $active; + $list[$activeAppIndex] = $lastInHeader; + } + + foreach ($list as $index => &$navEntry) { + $navEntry['showInHeader'] = false; + if($index < $headerIconCount) { + $navEntry['showInHeader'] = true; + } + } + + + + return $list; + } + + public static function proceedAppNavigation($entries) { + $headerIconCount = 8; + if(OC_User::isAdminUser(OC_User::getUser())) { + $headerIconCount--; + } + $activeAppIndex = -1; + $list = self::proceedNavigation($entries); + + $activeApp = OC::$server->getNavigationManager()->getActiveEntry(); + foreach ($list as $index => &$navEntry) { + if ($navEntry['id'] == $activeApp) { + $navEntry['active'] = true; + $activeAppIndex = $index; } else { - return ($a['name'] < $b['name']) ? -1 : 1; + $navEntry['active'] = false; } - }); + } + // move active item to last position + if($activeAppIndex > ($headerIconCount-1)) { + $active = $list[$activeAppIndex]; + $lastInHeader = $list[$headerIconCount-1]; + $list[$headerIconCount-1] = $active; + $list[$activeAppIndex] = $lastInHeader; + } + $list = array_slice($list, 0, $headerIconCount); return $list; } @@ -742,6 +793,22 @@ class OC_App { } /** + * Returns the navigation inside the header bar + * + * @return array + * + * This function returns an array containing all entries added. The + * entries are sorted by the key 'order' ascending. Additional to the keys + * given for each app the following keys exist: + * - active: boolean, signals if the user is on this navigation entry + */ + public static function getHeaderNavigation() { + $entries = OC::$server->getNavigationManager()->getAll(); + $navigation = self::proceedAppNavigation($entries); + return $navigation; + } + + /** * get the id of loaded app * * @return string diff --git a/settings/js/apps.js b/settings/js/apps.js index b73b4a35b3f..8be18c4e9c0 100644 --- a/settings/js/apps.js +++ b/settings/js/apps.js @@ -451,22 +451,39 @@ OC.Settings.Apps = OC.Settings.Apps || { rebuildNavigation: function() { $.getJSON(OC.filePath('settings', 'ajax', 'navigationdetect.php')).done(function(response){ - if(response.status === 'success'){ - var idsToKeep = {}; - var navEntries=response.nav_entries; + if(response.status === 'success') { + var addedApps = {}; + var navEntries = response.nav_entries; var container = $('#apps ul'); - for(var i=0; i< navEntries.length; i++){ + + // remove disabled apps + for (var i = 0; i < navEntries.length; i++) { var entry = navEntries[i]; - idsToKeep[entry.id] = true; + if(container.children('li[data-id="' + entry.id + '"]').length === 0) { + addedApps[entry.id] = true; + } + } + container.children('li[data-id]').each(function (index, el) { + var id = $(el).data('id'); + // remove all apps that are not in the correct order + if ((navEntries[index] && navEntries[index].id !== $(el).data('id'))) { + $(el).remove(); + $('#appmenu li[data-id='+id+']').remove(); + } + }); - if(container.children('li[data-id="'+entry.id+'"]').length === 0){ - var li=$('<li></li>'); + var previousEntry; + // add enabled apps to #navigation and #appmenu + for (var i = 0; i < navEntries.length; i++) { + var entry = navEntries[i]; + if (container.children('li[data-id="' + entry.id + '"]').length === 0) { + var li = $('<li></li>'); li.attr('data-id', entry.id); var img = '<svg width="32" height="32" viewBox="0 0 32 32">'; img += '<defs><filter id="invert"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" /></filter></defs>'; img += '<image x="0" y="0" width="32" height="32" preserveAspectRatio="xMinYMin meet" filter="url(#invert)" xlink:href="' + entry.icon + '" class="app-icon" /></svg>'; - var a=$('<a></a>').attr('href', entry.href); - var filename=$('<span></span>'); + var a = $('<a></a>').attr('href', entry.href); + var filename = $('<span></span>'); var loading = $('<div class="icon-loading-dark"></div>').css('display', 'none'); filename.text(entry.name); a.prepend(filename); @@ -474,33 +491,61 @@ OC.Settings.Apps = OC.Settings.Apps || { a.prepend(img); li.append(a); - // append the new app as last item in the list - // which is the "add apps" entry with the id - // #apps-management - $('#apps-management').before(li); - - // scroll the app navigation down - // so the newly added app is seen - $('#navigation').animate({ - scrollTop: $('#navigation').height() - }, 'slow'); + $('#navigation li[data-id=' + previousEntry.id + ']').after(li); // draw attention to the newly added app entry // by flashing it twice - $('#header .menutoggle') - .animate({opacity: 0.5}) - .animate({opacity: 1}) - .animate({opacity: 0.5}) - .animate({opacity: 1}) - .animate({opacity: 0.75}); + if(addedApps[entry.id]) { + $('#header .menutoggle') + .animate({opacity: 0.5}) + .animate({opacity: 1}) + .animate({opacity: 0.5}) + .animate({opacity: 1}) + .animate({opacity: 0.75}); + } } - } - container.children('li[data-id]').each(function(index, el) { - if (!idsToKeep[$(el).data('id')]) { - $(el).remove(); + if ($('#appmenu ul').children('li[data-id="' + entry.id + '"]').length === 0) { + // add apps to #appmenu until it is full + if ($('#appmenu li').not('.hidden').length < 8) { + var li = $('<li></li>'); + li.attr('data-id', entry.id); + var img = '<img src="' + entry.icon + '" class="app-icon">'; + var a = $('<a></a>').attr('href', entry.href); + var filename = $('<span></span>'); + var loading = $('<div class="icon-loading-dark"></div>').css('display', 'none'); + filename.text(entry.name); + a.prepend(filename); + a.prepend(loading); + a.prepend(img); + li.append(a); + $('#appmenu li[data-id='+ previousEntry.id+']').after(li); + if(addedApps[entry.id]) { + li.animate({opacity: 0.5}) + .animate({opacity: 1}) + .animate({opacity: 0.5}) + .animate({opacity: 1}); + } + } } - }); + previousEntry = entry; + // do not show apps from #appmenu in #navigation + if(i < 7) { + $('#navigation li').eq(i).addClass('in-header'); + } else { + $('#navigation li').eq(i).removeClass('in-header'); + } + } + + + + if (navEntries.length > 7) { + $('#more-apps').show(); + $('#apps-management').hide(); + } else { + $('#more-apps').hide(); + $('#apps-management').show(); + } } }); }, |