diff options
author | Christopher Ng <chrng8@gmail.com> | 2023-02-09 17:54:59 -0800 |
---|---|---|
committer | Christopher Ng <chrng8@gmail.com> | 2023-02-09 17:54:59 -0800 |
commit | c77998209f779dfccd86afeeafd43a7bbd886ff2 (patch) | |
tree | ff1b5fc33b1d5115f0f62eb4ea02848cdf468bc6 /core/src | |
parent | e47d56ac36d0f1d3e47392a7d9688decf847e1bc (diff) | |
download | nextcloud-server-c77998209f779dfccd86afeeafd43a7bbd886ff2.tar.gz nextcloud-server-c77998209f779dfccd86afeeafd43a7bbd886ff2.zip |
Port user menu to Vue
Signed-off-by: Christopher Ng <chrng8@gmail.com>
Diffstat (limited to 'core/src')
-rw-r--r-- | core/src/components/UserMenu.js | 45 | ||||
-rw-r--r-- | core/src/components/UserMenu/UserMenuEntry.vue | 106 | ||||
-rw-r--r-- | core/src/views/UserMenu.vue | 184 |
3 files changed, 301 insertions, 34 deletions
diff --git a/core/src/components/UserMenu.js b/core/src/components/UserMenu.js index f82a303d1fd..e165e784422 100644 --- a/core/src/components/UserMenu.js +++ b/core/src/components/UserMenu.js @@ -2,6 +2,7 @@ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Christopher Ng <chrng8@gmail.com> * * @license AGPL-3.0-or-later * @@ -20,41 +21,17 @@ * */ -import OC from '../OC' +import Vue from 'vue' -import $ from 'jquery' +import UserMenu from '../views/UserMenu.vue' export const setUp = () => { - const $menu = $('#header #settings') - // Using page terminoogy as below - const $excludedPageClasses = [ - 'user-status-menu-item__header', - ] - - // show loading feedback - $menu.delegate('a', 'click', event => { - let $page = $(event.target) - if (!$page.is('a')) { - $page = $page.closest('a') - } - if (event.which === 1 && !event.ctrlKey && !event.metaKey) { - if (!$excludedPageClasses.includes($page.attr('class'))) { - $page.find('img').remove() - $page.find('div').remove() // prevent odd double-clicks - $page.prepend($('<div></div>').addClass('icon-loading-small')) - } - } else { - // Close navigation when opening menu entry in - // a new tab - OC.hideMenus(() => false) - } - }) - - $menu.delegate('a', 'mouseup', event => { - if (event.which === 2) { - // Close navigation when opening app in - // a new tab via middle click - OC.hideMenus(() => false) - } - }) + const mountPoint = document.getElementById('user-menu') + if (mountPoint) { + // eslint-disable-next-line no-new + new Vue({ + el: mountPoint, + render: h => h(UserMenu), + }) + } } diff --git a/core/src/components/UserMenu/UserMenuEntry.vue b/core/src/components/UserMenu/UserMenuEntry.vue new file mode 100644 index 00000000000..43f65fa9fdb --- /dev/null +++ b/core/src/components/UserMenu/UserMenuEntry.vue @@ -0,0 +1,106 @@ +<!-- + - @copyright 2023 Christopher Ng <chrng8@gmail.com> + - + - @author Christopher Ng <chrng8@gmail.com> + - + - @license AGPL-3.0-or-later + - + - 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> + <li :id="id" + class="menu-entry"> + <a v-if="href" + :href="href" + :class="{ active }" + @click.exact="handleClick"> + <NcLoadingIcon v-if="loading" + class="menu-entry__loading-icon" + :size="18" /> + <img v-else :src="cachedIcon" alt="" /> + {{ name }} + </a> + <button v-else> + <img :src="cachedIcon" alt="" /> + {{ name }} + </button> + </li> +</template> + +<script> +import { loadState } from '@nextcloud/initial-state' + +import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' + +const versionHash = loadState('core', 'versionHash', '') + +export default { + name: 'UserMenuEntry', + + components: { + NcLoadingIcon, + }, + + props: { + id: { + type: String, + required: true, + }, + name: { + type: String, + required: true, + }, + href: { + type: String, + required: true, + }, + active: { + type: Boolean, + required: true, + }, + icon: { + type: String, + required: true, + }, + }, + + data() { + return { + loading: false, + } + }, + + computed: { + cachedIcon() { + return `${this.icon}?v=${versionHash}` + }, + }, + + methods: { + handleClick() { + this.loading = true + }, + }, +} +</script> + +<style lang="scss" scoped> +.menu-entry { + &__loading-icon { + margin-right: 8px; + } +} +</style> diff --git a/core/src/views/UserMenu.vue b/core/src/views/UserMenu.vue new file mode 100644 index 00000000000..5112b8a4eae --- /dev/null +++ b/core/src/views/UserMenu.vue @@ -0,0 +1,184 @@ +<!-- + - @copyright 2023 Christopher Ng <chrng8@gmail.com> + - + - @author Christopher Ng <chrng8@gmail.com> + - + - @license AGPL-3.0-or-later + - + - 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> + <NcHeaderMenu id="user-menu" + class="user-menu" + :aria-label="t('core', 'Open settings menu')"> + <template #trigger> + <NcAvatar class="user-menu__avatar" + :disable-menu="true" + :disable-tooltip="true" + :user="userId" /> + </template> + <nav class="user-menu__nav" + :aria-label="t('core', 'Settings menu')"> + <ul> + <UserMenuEntry v-for="entry in settingsNavEntries" + v-bind="entry" + :key="entry.id" /> + </ul> + </nav> + </NcHeaderMenu> +</template> + +<script> +import { emit } from '@nextcloud/event-bus' +import { getCurrentUser } from '@nextcloud/auth' +import { loadState } from '@nextcloud/initial-state' + +import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js' +import NcHeaderMenu from '@nextcloud/vue/dist/Components/NcHeaderMenu.js' + +import UserMenuEntry from '../components/UserMenu/UserMenuEntry.vue' + +const settingsNavEntries = loadState('core', 'settingsNavEntries', []) + +export default { + name: 'UserMenu', + + components: { + NcAvatar, + NcHeaderMenu, + UserMenuEntry, + }, + + data() { + return { + settingsNavEntries, + userId: getCurrentUser()?.uid, + } + }, + + mounted() { + emit('core:user-menu:mounted') + }, +} +</script> + +<style lang="scss" scoped> +.user-menu { + margin-right: 12px; + + &:deep { + .header-menu { + &__trigger { + opacity: 1 !important; + &:focus-visible { + .user-menu__avatar { + border: 2px solid var(--color-primary-text); + } + } + } + + &__carret { + display: none !important; + } + + &__content { + width: fit-content !important; + } + } + } + + &__avatar { + &:active, + &:focus, + &:hover { + border: 2px solid var(--color-primary-text); + } + } + + &__nav { + display: flex; + width: 100%; + + ul { + display: flex; + flex-direction: column; + gap: 2px; + + &:deep { + li { + a, + button { + border-radius: 6px; + display: inline-flex; + align-items: center; + height: var(--header-menu-item-height); + color: var(--color-main-text); + padding: 10px 8px; + box-sizing: border-box; + white-space: nowrap; + position: relative; + width: 100%; + + &:hover { + background-color: var(--color-background-hover); + } + + &:focus-visible { + background-color: var(--color-background-hover) !important; + box-shadow: inset 0 0 0 2px var(--color-primary) !important; + outline: none !important; + } + + &:active, + &.active { + background-color: var(--color-primary-light); + } + + span { + padding-bottom: 0; + color: var(--color-main-text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 110px; + } + + img { + width: 16px; + height: 16px; + margin-right: 10px; + } + + img, + svg { + opacity: .7; + filter: var(--background-invert-if-dark); + } + } + + // Override global button styles + button { + background-color: transparent; + border: none; + font-weight: normal; + margin: 0; + } + } + } + } + } +} +</style> |