diff --git a/core/css/header.css b/core/css/header.css index 4dbd41f5ec7..2c1850389dc 100644 Binary files a/core/css/header.css and b/core/css/header.css differ diff --git a/core/css/header.css.map b/core/css/header.css.map index 5970b18a6a3..824350e4a9a 100644 Binary files a/core/css/header.css.map and b/core/css/header.css.map differ diff --git a/core/css/header.scss b/core/css/header.scss index 5f3c4356d32..8ab0b019437 100644 --- a/core/css/header.scss +++ b/core/css/header.scss @@ -16,7 +16,6 @@ /* prevent ugly selection effect on accidental selection */ #header, -#navigation, #expanddiv { -webkit-user-select: none; -moz-user-select: none; @@ -72,7 +71,6 @@ /* Header menu */ $header-menu-entry-height: 44px; - .header-left > nav > .menu, .header-right > div > .menu { background-color: var(--color-main-background); filter: drop-shadow(0 1px 5px var(--color-box-shadow)); @@ -103,7 +101,6 @@ right: 10px; } - #apps > ul, & > div, & > ul { overflow-y: auto; @@ -111,8 +108,7 @@ @include header-menu-height(); } - /* Use by the apps menu and the settings right menu */ - #apps > ul, + /* Use by the settings right menu */ &.settings-menu > ul { li { a { @@ -182,16 +178,6 @@ padding-right: 10px; flex-shrink: 0; } - /* show caret indicator next to logo to make clear it is tappable */ - .icon-caret { - display: inline-block; - width: 12px; - height: 12px; - margin: 0; - margin-top: -21px; - padding: 0; - vertical-align: middle; - } #header-left, .header-left, #header-right, .header-right { @@ -245,27 +231,6 @@ opacity: .75; } -.menutoggle { - .icon-caret { - opacity: .75; - } - &:hover { - .header-appname, .icon-caret { - opacity: 1; - } - } - &:focus { - .header-appname, .icon-caret { - opacity: 1; - } - } - &.active { - .header-appname, .icon-caret { - opacity: 1; - } - } -} - /* TODO: move into minimal css file for public shared template */ /* only used for public share pages now as we have the app icons when logged in */ .header-appname { @@ -291,56 +256,6 @@ text-overflow: ellipsis; } -/* do not show menu toggle on public share links as there is no menu */ -#body-public #header .icon-caret { - display: none; -} - -/* NAVIGATION --------------------------------------------------------------- */ -nav[role='navigation'] { - display: inline-block; - width: variables.$header-height; - height: variables.$header-height; - margin-left: -#{variables.$header-height}; - position: relative; -} - -#header .header-left > nav > #navigation { - position: relative; - left: 25px; /* half the togglemenu */ - transform: translateX(-50%); - width: 160px; -} - -#header .header-left > nav > #navigation, -.ui-datepicker, -.ui-timepicker.ui-widget { - background-color: var(--color-main-background); - filter: drop-shadow(0 1px 10px var(--color-box-shadow)); - &:after { - /* position of dropdown arrow */ - left: 50%; - 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: var(--color-main-background); - border-width: 10px; - margin-left: -10px; /* border width */ - } -} - -#navigation { - box-sizing: border-box; - .in-header { - display: none; - } -} - /* USER MENU -----------------------------------------------------------------*/ #settings { display: inline-block; @@ -420,220 +335,6 @@ nav[role='navigation'] { } } -/* Apps menu */ -#appmenu { - display: inline-flex; - min-width: variables.$header-height; - z-index: 2; - - li { - position: relative; - cursor: pointer; - padding: 0 2px; - display: flex; - justify-content: center; - - a { - position: relative; - display: flex; - margin: 0; - height: calc(variables.$header-height - 6px); - width: variables.$header-height; - align-items: center; - justify-content: center; - opacity: .85; - // Make sure most app names don’t ellipsize - letter-spacing: -0.5px; - font-size: 12px; - margin: 2px; - } - - /* focused app visual feedback */ - &:hover a, - a:focus, - a.active { - opacity: 1; - font-weight: bold; - } - - // Text size back to normal for hover/focus - &:hover a, - a:focus { - font-size: 14px; - } - - &:hover a + span, - a:focus + span, - &:hover span, - &:focus span, - a:focus span, - a.active span { - display: inline-block; - text-overflow: initial; - width: auto; - overflow: hidden; - padding: 0 5px; - z-index: 2; - } - - /* hidden apps menu */ - img, - .icon-more-white { - display: inline-block; - width: 20px; - height: 20px; - } - - .icon-more-white { - background-image: url('../img/actions/more-white.svg?v=1'); - } - - /* App title */ - span { - opacity: 0; - position: absolute; - color: var(--color-primary-text); - bottom: 2px; - width: 100%; - text-align: center; - overflow: hidden; - text-overflow: ellipsis; - transition: all var(--animation-quick) ease; - pointer-events: none; - } - - /* Set up transitions for showing app titles on hover */ - /* App icon */ - svg, - .icon-more-white { - transition: transform var(--animation-quick) ease; - // If the primary is too bright, invert the app icons - filter: var(--primary-invert-if-bright); - } - - /* Triangle */ - a::before { - transition: border var(--animation-quick) ease; - } - } - - /* Show all app titles on hovering app menu area */ - &:hover { - li { - /* Move up app icon */ - svg, - .icon-more, - .icon-more-white, - .icon-loading-small, - .icon-loading-small-dark { - transform: translateY(-7px); - } - - /* Show app title */ - span { - opacity: 1; - bottom: 2px; - z-index: -1; /* fix clickability issue - otherwise we need to move the span into the link */ - } - - /* Prominent app title for current and hovered/focused app */ - &:hover span, - &:focus span, - .active + span { - opacity: 1; - } - - /* Smaller triangle because of limited space */ - a::before { - border-width: 5px; - } - } - } - - /* Also show app title on focusing single entry (showing all on focus is only possible with CSS4 and parent selectors) */ - li a:focus { - /* Move up app icon */ - svg, - .icon-more, - .icon-more-white, - .icon-loading-small, - .icon-loading-small-dark { - transform: translateY(-7px); - } - - /* Show app title */ - & + span, - span { - opacity: 1; - bottom: 2px; - } - - /* Smaller triangle because of limited space */ - &::before { - border-width: 5px; - } - } - - /* show triangle below active app */ - li a::before { - content: ' '; - height: 0; - width: 0; - position: absolute; - pointer-events: none; - border: 0 solid transparent; - border-bottom-color: var(--color-main-background); - border-width: 10px; - transform: translateX(-50%); - left: 50%; - bottom: -5px; - display: none; - } - - /* triangle focus feedback */ - li a.active::before, - li:hover a::before, - li:hover a.active::before, - li a:focus::before { - display: block; - } - li a.active::before { - z-index: 99; - } - li:hover a::before, - li a.active:hover::before, - li a:focus::before { - z-index: 101; - } - - li.hidden { - display: none; - } - - #more-apps { - z-index: 3; - } -} - -.unread-counter { - display: none; -} -#apps .app-icon-notification, -#appmenu .app-icon-notification { - fill: var(--color-error); -} - -#apps svg:not(.has-unread), -#appmenu svg:not(.has-unread) { - .app-icon-notification-mask { - display: none; - } - .app-icon-notification { - display: none; - } -} - - /* Skip navigation links – show only on keyboard focus */ #skip-actions { position: absolute; diff --git a/core/css/server.css b/core/css/server.css index d8d603379ea..dd6cb730927 100644 Binary files a/core/css/server.css and b/core/css/server.css differ diff --git a/core/css/server.css.map b/core/css/server.css.map index 5cb4a0f4074..5a67a8ebf86 100644 Binary files a/core/css/server.css.map and b/core/css/server.css.map differ diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js index 7f5a8b10d89..93d282c5d5e 100644 --- a/core/js/tests/specs/coreSpec.js +++ b/core/js/tests/specs/coreSpec.js @@ -308,48 +308,6 @@ describe('Core base tests', function() { expect(OC.generateUrl('apps/files/download{file}')).toEqual(OC.getRootPath() + '/index.php/apps/files/download%7Bfile%7D'); }); }); - describe('Main menu mobile toggle', function() { - var clock; - var $toggle; - var $navigation; - - beforeEach(function() { - jQuery.fx.off = true; - clock = sinon.useFakeTimers(); - $('#testArea').append('' + - ''); - $toggle = $('#header').find('.menutoggle'); - $navigation = $('#navigation'); - }); - afterEach(function() { - jQuery.fx.off = false; - clock.restore(); - $(document).off('ajaxError'); - }); - it('Sets up menu toggle', function() { - window.initCore(); - expect($navigation.hasClass('menu')).toEqual(true); - }); - it('Clicking menu toggle toggles navigation in', function() { - window.initCore(); - // fore show more apps icon since otherwise it would be hidden since no icons are available - clock.tick(1 * 1000); - $('#more-apps').show(); - - expect($navigation.is(':visible')).toEqual(false); - $toggle.click(); - clock.tick(1 * 1000); - expect($navigation.is(':visible')).toEqual(true); - $toggle.click(); - clock.tick(1 * 1000); - expect($navigation.is(':visible')).toEqual(false); - }); - }); describe('Util', function() { describe('computerFileSize', function() { it('correctly parses file sizes from a human readable formated string', function() { diff --git a/core/src/components/AppMenu.vue b/core/src/components/AppMenu.vue new file mode 100644 index 00000000000..0a337340ccb --- /dev/null +++ b/core/src/components/AppMenu.vue @@ -0,0 +1,289 @@ + + + + + + + diff --git a/core/src/components/MainMenu.js b/core/src/components/MainMenu.js index 603338d05b3..267a3d9a361 100644 --- a/core/src/components/MainMenu.js +++ b/core/src/components/MainMenu.js @@ -22,99 +22,27 @@ * */ -import $ from 'jquery' +import { translate as t, translatePlural as n } from '@nextcloud/l10n' +import Vue from 'vue' -import OC from '../OC' +import AppMenu from './AppMenu.vue' -/** - * 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. - */ export const setUp = () => { + Vue.mixin({ + methods: { + t, + n, + }, + }) + + const AppMenuApp = Vue.extend(AppMenu) + const appMenu = new AppMenuApp({}).$mount('#header-left__appmenu') + Object.assign(OC, { setNavigationCounter(id, counter) { - const appmenuElement = document.getElementById('appmenu').querySelector('[data-id="' + id + '"] svg') - const appsElement = document.getElementById('apps').querySelector('[data-id="' + id + '"] svg') - if (counter === 0) { - appmenuElement.classList.remove('has-unread') - appsElement.classList.remove('has-unread') - appmenuElement.getElementsByTagName('image')[0].style.mask = '' - appsElement.getElementsByTagName('image')[0].style.mask = '' - } else { - appmenuElement.classList.add('has-unread') - appsElement.classList.add('has-unread') - appmenuElement.getElementsByTagName('image')[0].style.mask = 'url(#hole-appmenu-' + id + ')' - appsElement.getElementsByTagName('image')[0].style.mask = 'url(#hole-' + id + ')' - } - document.getElementById('appmenu').querySelector('[data-id="' + id + '"] .unread-counter').textContent = counter - document.getElementById('apps').querySelector('[data-id="' + id + '"] .unread-counter').textContent = counter + appMenu.setNavigationCounter(id, counter) }, }) - // init the more-apps menu - OC.registerMenu($('#more-apps > a'), $('#navigation')) - // toggle the navigation - const $toggle = $('#header .header-appname-container') - const $navigation = $('#navigation') - const $appmenu = $('#appmenu') - - // init the menu - OC.registerMenu($toggle, $navigation) - $toggle.data('oldhref', $toggle.attr('href')) - $toggle.attr('href', '#') - $navigation.hide() - - // show loading feedback on more apps list - $navigation.delegate('a', 'click', event => { - let $app = $(event.target) - if (!$app.is('a')) { - $app = $app.closest('a') - } - if (event.which === 1 && !event.ctrlKey && !event.metaKey && $app.attr('target') !== '_blank') { - $app.find('svg').remove() - $app.find('div').remove() // prevent odd double-clicks - // no need for theming, loader is already inverted on dark mode - // but we need it over the primary colour - $app.prepend($('
').addClass('icon-loading-small')) - } else { - // Close navigation when opening app in - // a new tab - OC.hideMenus(() => false) - } - }) - - $navigation.delegate('a', 'mouseup', event => { - if (event.which === 2) { - // Close navigation when opening app in - // a new tab via middle click - OC.hideMenus(() => false) - } - }) - - // show loading feedback on visible apps list - $appmenu.delegate('li:not(#more-apps) > a', 'click', event => { - let $app = $(event.target) - if (!$app.is('a')) { - $app = $app.closest('a') - } - - if (event.which === 1 && !event.ctrlKey && !event.metaKey && $app.parent('#more-apps').length === 0 && $app.attr('target') !== '_blank') { - $app.find('svg').remove() - $app.find('div').remove() // prevent odd double-clicks - $app.prepend($('
').addClass( - OCA.Theming && OCA.Theming.inverted - ? 'icon-loading-small' - : 'icon-loading-small-dark' - )) - // trigger redirect - // needed for ie, but also works for every browser - window.location = $app.attr('href') - } else { - // Close navigation when opening app in - // a new tab - OC.hideMenus(() => false) - } - }) } diff --git a/core/src/init.js b/core/src/init.js index 507f5bbb35f..ae8db0abf49 100644 --- a/core/src/init.js +++ b/core/src/init.js @@ -39,60 +39,6 @@ import PasswordConfirmation from './OC/password-confirmation' // keep in sync with core/css/variables.scss const breakpointMobileWidth = 1024 -const resizeMenu = () => { - const appList = $('#appmenu li') - const rightHeaderWidth = $('.header-right').outerWidth() - const headerWidth = $('header').outerWidth() - const usePercentualAppMenuLimit = 0.67 - const minAppsDesktop = 12 - let availableWidth = headerWidth - $('#nextcloud').outerWidth() - (rightHeaderWidth > 210 ? rightHeaderWidth : 210) - const isMobile = $(window).width() < breakpointMobileWidth - if (!isMobile) { - availableWidth = availableWidth * usePercentualAppMenuLimit - } - let appCount = Math.floor((availableWidth / $(appList).width())) - if (isMobile && appCount > minAppsDesktop) { - appCount = minAppsDesktop - } - if (!isMobile && appCount < minAppsDesktop) { - appCount = minAppsDesktop - } - - // show at least 2 apps in the popover - if (appList.length - 1 - appCount >= 1) { - appCount-- - } - - $('#more-apps a').removeClass('active') - let lastShownApp - for (let k = 0; k < appList.length - 1; k++) { - const name = $(appList[k]).data('id') - if (k < appCount) { - $(appList[k]).removeClass('hidden') - $('#apps li[data-id=' + name + ']').addClass('in-header') - lastShownApp = appList[k] - } else { - $(appList[k]).addClass('hidden') - $('#apps li[data-id=' + name + ']').removeClass('in-header') - // move active app to last position if it is active - if (appCount > 0 && $(appList[k]).children('a').hasClass('active')) { - $(lastShownApp).addClass('hidden') - $('#apps li[data-id=' + $(lastShownApp).data('id') + ']').removeClass('in-header') - $(appList[k]).removeClass('hidden') - $('#apps li[data-id=' + name + ']').addClass('in-header') - } - } - } - - // show/hide more apps icon - if ($('#apps li:not(.in-header)').length === 0) { - $('#more-apps').hide() - $('#navigation').hide() - } else { - $('#more-apps').show() - } -} - const initLiveTimestamps = () => { // Update live timestamps every 30 seconds setInterval(() => { @@ -179,30 +125,6 @@ export const initCore = () => { setUpUserMenu() setUpContactsMenu() - // 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').on('click', () => { - $('#menu-css-helper').remove() - const 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 - - } else { - $('head').append('') - } - }) - $('#header #appmenu .menutoggle').on('click', () => { - $('#appmenu').toggleClass('menu-open') - if ($('#appmenu').is(':visible')) { - $('#menu-css-helper').remove() - } - }) - } - - $(window).resize(resizeMenu) - setTimeout(resizeMenu, 0) - // just add snapper for logged in users // and if the app doesn't handle the nav slider itself if ($('#app-navigation').length && !$('html').hasClass('lte9') diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index c7eb2fde5ad..05698b3aa67 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -65,70 +65,7 @@ $getUserAvatar = static function (int $size) use ($_): string { - - - - +
diff --git a/dist/core-common.js b/dist/core-common.js index 22a54f452d6..f5aaf9e3637 100644 Binary files a/dist/core-common.js and b/dist/core-common.js differ diff --git a/dist/core-common.js.map b/dist/core-common.js.map index f292c27fa8a..61893e14284 100644 Binary files a/dist/core-common.js.map and b/dist/core-common.js.map differ diff --git a/dist/core-main.js b/dist/core-main.js index 56bde57f5e9..331c699442a 100644 Binary files a/dist/core-main.js and b/dist/core-main.js differ diff --git a/dist/core-main.js.map b/dist/core-main.js.map index 1dbd04076e0..8a82221039a 100644 Binary files a/dist/core-main.js.map and b/dist/core-main.js.map differ diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index 37f459ca52d..d041db2a7c2 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -100,6 +100,7 @@ class TemplateLayout extends \OC_Template { } $this->initialState->provideInitialState('core', 'active-app', $this->navigationManager->getActiveEntry()); + $this->initialState->provideInitialState('core', 'apps', $this->navigationManager->getAll()); $this->initialState->provideInitialState('unified-search', 'limit-default', (int)$this->config->getAppValue('core', 'unified-search.limit-default', (string)SearchQuery::LIMIT_DEFAULT)); $this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)2)); $this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes'); diff --git a/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php index 91ca4725a96..124000f878d 100644 --- a/tests/acceptance/features/bootstrap/FilesAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesAppContext.php @@ -46,7 +46,7 @@ class FilesAppContext implements Context, ActorAwareInterface { * @return Locator */ private static function appMenu() { - return Locator::forThe()->id("appmenu")-> + return Locator::forThe()->css("header nav.app-menu")-> describedAs("App menu in header"); } @@ -54,7 +54,7 @@ class FilesAppContext implements Context, ActorAwareInterface { * @return Locator */ public static function filesItemInAppMenu() { - return Locator::forThe()->xpath("/li[@data-id = 'files']")-> + return Locator::forThe()->xpath("//li[@data-app-id = 'files']")-> descendantOf(self::appMenu())-> describedAs("Files item in app menu in header"); }