aboutsummaryrefslogtreecommitdiffstats
path: root/core/src
diff options
context:
space:
mode:
authorChristopher Ng <chrng8@gmail.com>2023-02-09 17:54:59 -0800
committerChristopher Ng <chrng8@gmail.com>2023-02-09 17:54:59 -0800
commitc77998209f779dfccd86afeeafd43a7bbd886ff2 (patch)
treeff1b5fc33b1d5115f0f62eb4ea02848cdf468bc6 /core/src
parente47d56ac36d0f1d3e47392a7d9688decf847e1bc (diff)
downloadnextcloud-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.js45
-rw-r--r--core/src/components/UserMenu/UserMenuEntry.vue106
-rw-r--r--core/src/views/UserMenu.vue184
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>