add top menu (StandardMenu)
This commit is contained in:
+10
-6
@@ -17,15 +17,19 @@
|
|||||||
|
|
||||||
const waitContainer = async (selector) => {
|
const waitContainer = async (selector) => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const container = document.querySelector(selector)
|
const execute = () => {
|
||||||
|
const container = document.querySelector(selector)
|
||||||
|
|
||||||
if (container) {
|
if (container) {
|
||||||
return resolve(selector, container)
|
resolve(container)
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
execute(selector)
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
execute(selector)
|
||||||
waitContainer(selector)
|
|
||||||
}, 50)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-11
@@ -19,12 +19,10 @@ import './scss/menu.scss'
|
|||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import { createElement } from './lib/dom.js'
|
import { createElement, waitContainer } from './lib/dom.js'
|
||||||
|
|
||||||
import MenuContainer from './menus/MenuContainer.vue'
|
import StandardMenu from './menus/StandardMenu'
|
||||||
|
import MenuContainer from './menus/MenuContainer'
|
||||||
// import PageLoader from './components/PageLoader.vue'
|
|
||||||
// window.PageLoader = PageLoader
|
|
||||||
|
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
const body = document.querySelector('body')
|
const body = document.querySelector('body')
|
||||||
@@ -39,9 +37,16 @@ app.use(pinia)
|
|||||||
app.mixin({ methods: { t, n } })
|
app.mixin({ methods: { t, n } })
|
||||||
app.mount(container)
|
app.mount(container)
|
||||||
|
|
||||||
// waitContainer('#header .app-menu').then((selector) => {
|
waitContainer('#header .app-menu').then((container) => {
|
||||||
// const app = createApp(AppMenu)
|
const menu = createElement('div', {
|
||||||
// app.use(pinia)
|
id: 'app-menu-container',
|
||||||
// app.mixin({ methods: { t, n }})
|
})
|
||||||
// app.mount(selector)
|
|
||||||
// })
|
container.parentNode.insertBefore(menu, container.nextSibling)
|
||||||
|
container.remove()
|
||||||
|
|
||||||
|
const app = createApp(StandardMenu)
|
||||||
|
app.use(pinia)
|
||||||
|
app.mixin({ methods: { t, n } })
|
||||||
|
app.mount(menu)
|
||||||
|
})
|
||||||
|
|||||||
@@ -25,28 +25,35 @@
|
|||||||
:aria-label="t('core', 'Applications menu')"
|
:aria-label="t('core', 'Applications menu')"
|
||||||
>
|
>
|
||||||
<ul
|
<ul
|
||||||
v-if="appList.length"
|
v-if="ready"
|
||||||
class="app-menu-main"
|
class="app-menu-main"
|
||||||
:class="{ 'app-menu-main__hidden-label': hiddenLabels === 1, 'app-menu-main__show-hovered': hiddenLabels === 2 }"
|
:class="{
|
||||||
|
'app-menu-main__hidden-label': hiddenLabels === 1,
|
||||||
|
'app-menu-main__show-hovered': hiddenLabels === 2,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
v-for="app in mainAppList(state)"
|
v-for="app in mainAppList"
|
||||||
:key="app.id"
|
:key="app.id"
|
||||||
:data-app-id="app.id"
|
:data-app-id="app.id"
|
||||||
class="app-menu-entry"
|
class="app-menu-entry"
|
||||||
:class="{ 'app-menu-entry__active': app.active, 'app-menu-entry__hidden-label': hiddenLabels === 1, 'app-menu-main__show-hovered': hiddenLabels === 2 }"
|
:class="{
|
||||||
|
'app-menu-entry__active': app.active,
|
||||||
|
'app-menu-entry__hidden-label': hiddenLabels === 1,
|
||||||
|
'app-menu-main__show-hovered': hiddenLabels === 2,
|
||||||
|
}"
|
||||||
:style="makeStyle(app)"
|
:style="makeStyle(app)"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
:href="app.href"
|
:href="app.href"
|
||||||
:class="{ 'has-unread': app.unread > 0 }"
|
:class="{ 'has-unread': app.unread > 0 }"
|
||||||
:aria-label="appLabel(app)"
|
:aria-label="app.name"
|
||||||
:target="targetBlankApps.indexOf(app.id) !== -1 ? '_blank' : undefined"
|
:target="targetBlankApps.indexOf(app.id) !== -1 ? '_blank' : undefined"
|
||||||
:aria-current="app.active ? 'page' : false"
|
:aria-current="app.active ? 'page' : false"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="app.icon"
|
:src="app.icon"
|
||||||
alt=""
|
:alt="app.name"
|
||||||
/>
|
/>
|
||||||
<div class="app-menu-entry--label">
|
<div class="app-menu-entry--label">
|
||||||
{{ app.name }}
|
{{ app.name }}
|
||||||
@@ -64,9 +71,9 @@
|
|||||||
:aria-label="t('core', 'More apps')"
|
:aria-label="t('core', 'More apps')"
|
||||||
>
|
>
|
||||||
<NcActionLink
|
<NcActionLink
|
||||||
v-for="app in popoverAppList(state)"
|
v-for="app in popoverAppList"
|
||||||
:key="app.id"
|
:key="app.id"
|
||||||
:aria-label="appLabel(app)"
|
:aria-label="app.name"
|
||||||
:aria-current="app.active ? 'page' : false"
|
:aria-current="app.active ? 'page' : false"
|
||||||
:href="app.href"
|
:href="app.href"
|
||||||
:style="makeStyle(app)"
|
:style="makeStyle(app)"
|
||||||
@@ -79,7 +86,7 @@
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="app.icon"
|
:src="app.icon"
|
||||||
alt=""
|
:alt="app.name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -94,134 +101,94 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
|
import { ref, onMounted } from 'vue'
|
||||||
import { loadState } from '@nextcloud/initial-state'
|
import { useConfigStore } from '../store/config.js'
|
||||||
import { n, t } from '@nextcloud/l10n'
|
import { useNavStore } from '../store/nav.js'
|
||||||
import { useElementSize } from '@vueuse/core'
|
import { NcActions, NcActionLink } from '@nextcloud/vue'
|
||||||
import { defineComponent, ref } from 'vue'
|
|
||||||
import axios from '@nextcloud/axios'
|
|
||||||
import { generateOcsUrl } from '@nextcloud/router'
|
|
||||||
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
|
|
||||||
import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js'
|
|
||||||
|
|
||||||
export default defineComponent({
|
const navStore = useNavStore()
|
||||||
name: 'AppMenu',
|
const configStore = useConfigStore()
|
||||||
|
const ready = ref(false)
|
||||||
|
const appList = ref([])
|
||||||
|
const targetBlankApps = ref(null)
|
||||||
|
const hiddenLabels = ref(false)
|
||||||
|
const topMenuApps = ref([])
|
||||||
|
const appsOrder = ref([])
|
||||||
|
const mainAppList = ref([])
|
||||||
|
const popoverAppList = ref([])
|
||||||
|
let resizeTimeout = null
|
||||||
|
|
||||||
components: {
|
const setApps = (value) => {
|
||||||
NcActions,
|
value.forEach((app) => {
|
||||||
NcActionLink,
|
Array.from(topMenuApps.value).forEach((id) => {
|
||||||
},
|
if (app.id === id) {
|
||||||
|
app.order = appsOrder.value.findIndex((element) => element === app.id) || null
|
||||||
setup() {
|
appList.value.push(app)
|
||||||
return {
|
}
|
||||||
t,
|
|
||||||
n,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
apps: null,
|
|
||||||
appList: [],
|
|
||||||
observer: null,
|
|
||||||
targetBlankApps: [],
|
|
||||||
hiddenLabels: true,
|
|
||||||
state: 1,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
axios
|
|
||||||
.get(generateOcsUrl('core/navigation', 2) + '/apps?format=json')
|
|
||||||
.then((response) => response.data)
|
|
||||||
.then((data) => {
|
|
||||||
if (data.ocs.meta.statuscode !== 200) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setApps(data.ocs.data)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.targetBlankApps = window.targetBlankApps
|
|
||||||
this.hiddenLabels = window.topMenuAppsMouseOverHiddenLabel
|
|
||||||
let timeout = null
|
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
timeout = window.setTimeout(() => {
|
|
||||||
this.update()
|
|
||||||
}, 300)
|
|
||||||
})
|
})
|
||||||
},
|
})
|
||||||
|
|
||||||
methods: {
|
computeLists()
|
||||||
update() {
|
}
|
||||||
++this.state
|
|
||||||
},
|
|
||||||
|
|
||||||
mainAppList() {
|
const appLimit = () => {
|
||||||
return this.appList.slice(0, this.appLimit())
|
const headerStart = document.querySelector('#header .header-start')
|
||||||
},
|
const headerEnd = document.querySelector('#header .header-end')
|
||||||
|
const body = document.querySelector('body')
|
||||||
|
|
||||||
popoverAppList() {
|
let size = (headerEnd ? headerEnd.offsetWidth : 0) + 70
|
||||||
return this.appList.slice(this.appLimit())
|
|
||||||
},
|
|
||||||
|
|
||||||
appLimit() {
|
if (headerStart) {
|
||||||
const maxApps = Math.floor(this.$root.$el.offsetWidth / 60)
|
Array.from(headerStart.children).forEach((child) => {
|
||||||
|
if (child.id !== 'app-menu-container') {
|
||||||
if (maxApps < this.appList.length) {
|
size += child.offsetWidth
|
||||||
// Ensure there is space for the overflow menu
|
|
||||||
return Math.max(maxApps - 1, 0)
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return maxApps
|
return Math.floor((body.offsetWidth - size) / 70)
|
||||||
},
|
}
|
||||||
|
|
||||||
setNavigationCounter(id, counter) {
|
const makeStyle = (app) => {
|
||||||
const app = this.appList.find(({ app }) => app === id)
|
if (app.order !== null) {
|
||||||
if (app) {
|
return { order: app.order }
|
||||||
this.$set(app, 'unread', counter)
|
}
|
||||||
} else {
|
|
||||||
logger.warn(`Could not find app "${id}" for setting navigation count`)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setApps(apps) {
|
return {}
|
||||||
this.appList = []
|
}
|
||||||
let orders = {}
|
|
||||||
|
|
||||||
window.menuAppsOrder.forEach((app, order) => {
|
const computeLists = () => {
|
||||||
orders[app] = order + 1
|
mainAppList.value = appList.value.slice(0, appLimit())
|
||||||
})
|
popoverAppList.value = appList.value.slice(appLimit()).sort((a, b) => a.order - b.order)
|
||||||
|
}
|
||||||
|
|
||||||
apps.forEach((app) => {
|
onMounted(async () => {
|
||||||
Array.from(window.topMenuApps).forEach((id) => {
|
const config = await configStore.getConfig()
|
||||||
if (app.id === id) {
|
|
||||||
app.order = orders[id] || null
|
|
||||||
this.appList.push(app)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
appLabel(app) {
|
targetBlankApps.value = config['target-blank-apps']
|
||||||
return (
|
hiddenLabels.value = config['top-menu-mouse-over-hidden-label']
|
||||||
app.name +
|
topMenuApps.value = config['top-menu-apps']
|
||||||
(app.active ? ' (' + t('core', 'Currently open') + ')' : '') +
|
appsOrder.value = config['apps-order']
|
||||||
(app.unread > 0 ? ' (' + n('core', '{count} notification', '{count} notifications', app.unread, { count: app.unread }) + ')' : '')
|
ready.value = true
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
makeStyle(app) {
|
setApps(await navStore.getCoreApps())
|
||||||
if (app.order !== null) {
|
|
||||||
return `order: ${app.order}`
|
window.addEventListener('resize', () => {
|
||||||
}
|
window.clearTimeout(resizeTimeout)
|
||||||
},
|
resizeTimeout = window.setTimeout(computeLists, 100)
|
||||||
},
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
compatConfig: {
|
||||||
|
GLOBAL_MOUNT_CONTAINER: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
$header-icon-size: 20px;
|
$header-icon-size: 20px;
|
||||||
|
|
||||||
@@ -15,6 +15,17 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.__debug {
|
||||||
|
background: red;
|
||||||
|
padding: 5px;
|
||||||
|
color: #fff;
|
||||||
|
width: 50vw;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 3000;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
#side-menu {
|
#side-menu {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
+16
-1
@@ -18,11 +18,12 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl, generateOcsUrl } from '@nextcloud/router'
|
||||||
|
|
||||||
export const useNavStore = defineStore('nav', () => {
|
export const useNavStore = defineStore('nav', () => {
|
||||||
const categories = ref(null)
|
const categories = ref(null)
|
||||||
const apps = ref(null)
|
const apps = ref(null)
|
||||||
|
const coreApps = ref(null)
|
||||||
|
|
||||||
async function getApps() {
|
async function getApps() {
|
||||||
if (apps.value !== null) {
|
if (apps.value !== null) {
|
||||||
@@ -41,6 +42,19 @@ export const useNavStore = defineStore('nav', () => {
|
|||||||
return apps.value
|
return apps.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getCoreApps() {
|
||||||
|
if (coreApps.value !== null) {
|
||||||
|
return coreApps.value
|
||||||
|
}
|
||||||
|
|
||||||
|
coreApps.value = await axios
|
||||||
|
.get(generateOcsUrl('core/navigation', 2) + '/apps?format=json')
|
||||||
|
.then((response) => response.data)
|
||||||
|
.then((value) => value.ocs.data)
|
||||||
|
|
||||||
|
return coreApps.value
|
||||||
|
}
|
||||||
|
|
||||||
async function getCategories() {
|
async function getCategories() {
|
||||||
if (categories.value !== null) {
|
if (categories.value !== null) {
|
||||||
return categories.value
|
return categories.value
|
||||||
@@ -53,6 +67,7 @@ export const useNavStore = defineStore('nav', () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
getApps,
|
getApps,
|
||||||
|
getCoreApps,
|
||||||
getCategories,
|
getCategories,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user