diff options
author | Julius Härtl <jus@bitgrid.net> | 2022-08-27 10:57:13 +0200 |
---|---|---|
committer | Julius Härtl <jus@bitgrid.net> | 2022-08-31 10:24:03 +0200 |
commit | 5b4708c5be100c3a4bbb2fd32151ae2a7420df2d (patch) | |
tree | 181dc1020643ecc5434fa77bfac5606be3826ed4 /core/src/components/AppMenu.vue | |
parent | 23bb4f16f9056e7a79116129c7de5b59cf84f8be (diff) | |
download | nextcloud-server-5b4708c5be100c3a4bbb2fd32151ae2a7420df2d.tar.gz nextcloud-server-5b4708c5be100c3a4bbb2fd32151ae2a7420df2d.zip |
Move app menu to vue
Signed-off-by: Julius Härtl <jus@bitgrid.net>
Diffstat (limited to 'core/src/components/AppMenu.vue')
-rw-r--r-- | core/src/components/AppMenu.vue | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/core/src/components/AppMenu.vue b/core/src/components/AppMenu.vue new file mode 100644 index 00000000000..a8ea7250852 --- /dev/null +++ b/core/src/components/AppMenu.vue @@ -0,0 +1,268 @@ +<!-- + - @copyright Copyright (c) 2022 Julius Härtl <jus@bitgrid.net> + - + - @author Julius Härtl <jus@bitgrid.net> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + --> + +<template> + <nav class="app-menu"> + <ul class="app-menu-main"> + <li v-for="app in mainAppList" + :key="app.id" + :data-app-id="app.id" + class="app-menu-entry" + :class="{ 'app-menu-entry__active': app.active }"> + <a :href="app.href" :class="{ 'has-unread': app.unread > 0 }" :aria-label="appLabel(app)"> + <img :src="app.icon" alt=""> + <div class="app-menu-entry--label"> + {{ app.name }} + <span v-if="app.unread > 0" class="hidden-visually unread-counter">{{ app.unread }}</span> + </div> + </a> + </li> + </ul> + <NcActions class="app-menu-more" :aria-label="t('core', 'More apps')"> + <NcActionLink v-for="app in popoverAppList" + :key="app.id" + :aria-label="appLabel(app)" + :href="app.href" + class="app-menu-popover-entry"> + <template #icon> + <div class="app-icon" :class="{ 'has-unread': app.unread > 0 }"> + <img :src="app.icon" alt=""> + </div> + </template> + {{ app.name }} + <span v-if="app.unread > 0" class="hidden-visually unread-counter">{{ app.unread }}</span> + </NcActionLink> + </NcActions> + </nav> +</template> + +<script> +import { loadState } from '@nextcloud/initial-state' +import NcActions from '@nextcloud/vue/dist/Components/NcActions' +import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink' + +export default { + name: 'AppMenu', + components: { + NcActions, NcActionLink, + }, + data() { + return { + apps: loadState('core', 'apps', {}), + appLimit: 0, + } + }, + computed: { + appList() { + return Object.values(this.apps) + }, + mainAppList() { + return this.appList.slice(0, this.appLimit) + }, + popoverAppList() { + return this.appList.slice(this.appLimit) + }, + appLabel() { + return (app) => app.name + + (app.active ? ' (' + t('core', 'Currently open') + ')' : '') + + (app.unread > 0 ? ' (' + n('core', '{count} notification', '{count} notifications', app.unread, { count: app.unread }) + ')' : '') + }, + }, + mounted() { + window.addEventListener('resize', this.resize) + this.resize() + }, + methods: { + setNavigationCounter(id, counter) { + this.$set(this.apps[id], 'unread', counter) + }, + resize() { + const availableWidth = this.$el.offsetWidth + let appCount = Math.floor(availableWidth / 50) - 1 + const popoverAppCount = this.appList.length - appCount + if (popoverAppCount === 1) { + appCount-- + } + if (appCount < 1) { + appCount = 0 + } + this.appLimit = appCount + }, + }, +} +</script> + +<style lang="scss" scoped> +$header-icon-size: 20px; + +.app-menu { + width: 100%; + display: flex; + flex-shrink: 1; + flex-wrap: wrap; +} +.app-menu-main { + display: flex; + flex-wrap: nowrap; + + .app-menu-entry { + width: 50px; + height: 50px; + position: relative; + display: flex; + opacity: .7; + + &.app-menu-entry__active { + opacity: 1; + + &::before { + content: " "; + position: absolute; + pointer-events: none; + border: 8px solid transparent; + border-bottom-color: var(--color-main-background); + transform: translateX(-50%); + left: 50%; + bottom: 0; + display: block; + transition: all 0.1s ease-in-out; + opacity: 1; + } + + .app-menu-entry--label { + font-weight: bold; + } + } + + a { + width: 100%; + height: 100%; + color: var(--color-primary-text); + position: relative; + } + + img { + transition: margin 0.1s ease-in-out; + width: $header-icon-size; + height: $header-icon-size; + padding: calc((100% - $header-icon-size) / 2); + filter: var(--primary-invert-if-bright); + } + + .app-menu-entry--label { + opacity: 0; + position: absolute; + font-size: 12px; + color: var(--color-primary-text); + text-align: center; + bottom: -5px; + left: 50%; + display: block; + min-width: 100%; + transform: translateX(-50%); + transition: all 0.1s ease-in-out; + width: 100%; + text-overflow: ellipsis; + overflow: hidden; + } + + &:hover, + &:focus-within { + opacity: 1; + .app-menu-entry--label { + opacity: 1; + font-weight: bold; + font-size: 14px; + bottom: 0; + width: auto; + overflow: visible; + } + } + + } + + // Show labels + &:hover, + &:focus-within, + .app-menu-entry:hover, + .app-menu-entry:focus { + opacity: 1; + + img { + margin-top: -6px; + } + + .app-menu-entry--label { + opacity: 1; + bottom: 0; + } + + &::before, .app-menu-entry::before { + border-width: 3px; + } + } +} + +::v-deep .app-menu-more .button-vue--vue-tertiary { + color: var(--color-primary-text); + opacity: .7; + margin: 3px; + + &:hover { + opacity: 1; + background-color: transparent !important; + } +} + +.app-menu-popover-entry { + .app-icon { + position: relative; + height: 44px; + + &.has-unread::after { + background-color: var(--color-main-text); + } + + img { + filter: var(--background-invert-if-bright); + width: $header-icon-size; + height: $header-icon-size; + padding: calc((50px - $header-icon-size) / 2); + } + } +} + +.has-unread::after { + content: ""; + width: 8px; + height: 8px; + background-color: var(--color-primary-text); + border-radius: 50%; + position: absolute; + display: block; + top: 10px; + right: 10px; +} + +.unread-counter { + display: none; +} +</style> |