Merge pull request #3008 from nextcloud/appmenu-experiment

Show apps in header
This commit is contained in:
Morris Jobke 2017-03-16 13:03:41 -06:00 committed by GitHub
commit cd4ebe2777
6 changed files with 440 additions and 93 deletions

View File

@ -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;
}
}
}

View File

@ -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();
}
});
}

View File

@ -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>

View File

@ -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) {

View File

@ -529,16 +529,10 @@ class OC_App {
// This is private as well. It simply works, so don't ask for more details
private static function proceedNavigation($list) {
$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
foreach ($list as &$navEntry) {
if ($navEntry['id'] == $activeApp) {
$navEntry['active'] = true;
} else {
$navEntry['active'] = false;
}
$headerIconCount = 8;
if(OC_User::isAdminUser(OC_User::getUser())) {
$headerIconCount--;
}
unset($navEntry);
usort($list, function($a, $b) {
if (isset($a['order']) && isset($b['order'])) {
return ($a['order'] < $b['order']) ? -1 : 1;
@ -549,6 +543,63 @@ class OC_App {
}
});
$activeAppIndex = -1;
$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
foreach ($list as $index => &$navEntry) {
if ($navEntry['id'] == $activeApp) {
$navEntry['active'] = true;
$activeAppIndex = $index;
} else {
$navEntry['active'] = false;
}
}
unset($navEntry);
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 {
$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;
}
@ -741,6 +792,22 @@ class OC_App {
return $navigation;
}
/**
* 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
*

View File

@ -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++){
var entry = navEntries[i];
idsToKeep[entry.id] = true;
if(container.children('li[data-id="'+entry.id+'"]').length === 0){
var li=$('<li></li>');
// remove disabled apps
for (var i = 0; i < navEntries.length; i++) {
var entry = navEntries[i];
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();
}
});
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});
}
}
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');
}
}
container.children('li[data-id]').each(function(index, el) {
if (!idsToKeep[$(el).data('id')]) {
$(el).remove();
}
});
if (navEntries.length > 7) {
$('#more-apps').show();
$('#apps-management').hide();
} else {
$('#more-apps').hide();
$('#apps-management').show();
}
}
});
},