|
|
|
|
@ -4,7 +4,8 @@
|
|
|
|
|
-->
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<nav class="app-menu"
|
|
|
|
|
<nav ref="appMenu"
|
|
|
|
|
class="app-menu"
|
|
|
|
|
:aria-label="t('core', 'Applications menu')">
|
|
|
|
|
<ul class="app-menu__list">
|
|
|
|
|
<AppMenuEntry v-for="app in mainAppList"
|
|
|
|
|
@ -29,7 +30,8 @@ import type { INavigationEntry } from '../types/navigation'
|
|
|
|
|
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
|
|
|
|
|
import { loadState } from '@nextcloud/initial-state'
|
|
|
|
|
import { n, t } from '@nextcloud/l10n'
|
|
|
|
|
import { defineComponent } from 'vue'
|
|
|
|
|
import { useElementSize } from '@vueuse/core'
|
|
|
|
|
import { defineComponent, ref } from 'vue'
|
|
|
|
|
|
|
|
|
|
import AppMenuEntry from './AppMenuEntry.vue'
|
|
|
|
|
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
|
|
|
|
|
@ -46,40 +48,47 @@ export default defineComponent({
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setup() {
|
|
|
|
|
const appMenu = ref()
|
|
|
|
|
const { width: appMenuWidth } = useElementSize(appMenu)
|
|
|
|
|
return {
|
|
|
|
|
t,
|
|
|
|
|
n,
|
|
|
|
|
appMenu,
|
|
|
|
|
appMenuWidth,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
data() {
|
|
|
|
|
const appList = loadState<INavigationEntry[]>('core', 'apps', [])
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
appList,
|
|
|
|
|
appLimit: 0,
|
|
|
|
|
observer: null as ResizeObserver | null,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
computed: {
|
|
|
|
|
appLimit() {
|
|
|
|
|
const maxApps = Math.floor(this.appMenuWidth / 50)
|
|
|
|
|
if (maxApps < this.appList.length) {
|
|
|
|
|
// Ensure there is space for the overflow menu
|
|
|
|
|
return Math.max(maxApps - 1, 0)
|
|
|
|
|
}
|
|
|
|
|
return maxApps
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
mainAppList() {
|
|
|
|
|
return this.appList.slice(0, this.appLimit)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
popoverAppList() {
|
|
|
|
|
return this.appList.slice(this.appLimit)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
mounted() {
|
|
|
|
|
this.observer = new ResizeObserver(this.resize)
|
|
|
|
|
this.observer.observe(this.$el)
|
|
|
|
|
this.resize()
|
|
|
|
|
subscribe('nextcloud:app-menu.refresh', this.setApps)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
beforeDestroy() {
|
|
|
|
|
this.observer!.disconnect()
|
|
|
|
|
unsubscribe('nextcloud:app-menu.refresh', this.setApps)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
@ -96,54 +105,44 @@ export default defineComponent({
|
|
|
|
|
setApps({ apps }: { apps: INavigationEntry[]}) {
|
|
|
|
|
this.appList = apps
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
resize() {
|
|
|
|
|
const availableWidth = (this.$el as HTMLElement).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 scoped lang="scss">
|
|
|
|
|
.app-menu {
|
|
|
|
|
width: 100%;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-shrink: 1;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
flex: 1 1;
|
|
|
|
|
width: 0;
|
|
|
|
|
|
|
|
|
|
&__list {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Adjust the overflow NcActions styles as they are directly rendered on the background
|
|
|
|
|
&__overflow :deep(.button-vue--vue-tertiary) {
|
|
|
|
|
opacity: .7;
|
|
|
|
|
margin: 3px;
|
|
|
|
|
filter: var(--background-image-invert-if-bright);
|
|
|
|
|
&__overflow {
|
|
|
|
|
margin-block: auto;
|
|
|
|
|
|
|
|
|
|
/* Remove all background and align text color if not expanded */
|
|
|
|
|
&:not([aria-expanded="true"]) {
|
|
|
|
|
color: var(--color-background-plain-text);
|
|
|
|
|
// Adjust the overflow NcActions styles as they are directly rendered on the background
|
|
|
|
|
:deep(.button-vue--vue-tertiary) {
|
|
|
|
|
opacity: .7;
|
|
|
|
|
margin: 3px;
|
|
|
|
|
filter: var(--background-image-invert-if-bright);
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
background-color: transparent !important;
|
|
|
|
|
/* Remove all background and align text color if not expanded */
|
|
|
|
|
&:not([aria-expanded="true"]) {
|
|
|
|
|
color: var(--color-background-plain-text);
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
background-color: transparent !important;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&:focus-visible {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
outline: none !important;
|
|
|
|
|
&:focus-visible {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
outline: none !important;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|