add mount of menu

fix store usage

add page loader
This commit is contained in:
Simon Vieille
2025-04-06 14:09:06 +02:00
parent d8a622ce0f
commit b287b671be
15 changed files with 175 additions and 81 deletions
+1 -1
View File
@@ -28,7 +28,7 @@
"@nextcloud/browserslist-config": "^3.0.1", "@nextcloud/browserslist-config": "^3.0.1",
"@nextcloud/event-bus": "^3.3.1", "@nextcloud/event-bus": "^3.3.1",
"@nextcloud/initial-state": "^2.2.0", "@nextcloud/initial-state": "^2.2.0",
"@nextcloud/l10n": "^3.1.0", "@nextcloud/l10n": "^3.2.0",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"eslint": "^9.19.0", "eslint": "^9.19.0",
-20
View File
@@ -1,20 +0,0 @@
const createElement = require('./lib/createElement')
const PageLoader = () => {
const pageLoader = createElement('div', { id: 'side-menu-loader' })
const pageLoaderBar = createElement('div', { id: 'side-menu-loader-bar' })
pageLoader.appendChild(pageLoaderBar)
document.querySelector('body').appendChild(pageLoader)
let pageLoaderValue = 0
window.addEventListener('beforeunload', () => {
setInterval(() => {
pageLoaderBar.style.width = pageLoaderValue.toString() + '%'
pageLoaderValue = Math.min(pageLoaderValue + 0.2, 100)
}, 25)
})
}
module.exports = PageLoader
+8 -2
View File
@@ -42,7 +42,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
@close="hideAddForm" @close="hideAddForm"
> >
<div class="modal__content"> <div class="modal__content">
<div v-for="(lang, key) in langs" :key="key"> <div
v-for="(lang, key) in langs"
:key="key"
>
<span class="lang">{{ lang }}</span> <span class="lang">{{ lang }}</span>
<input <input
v-model="newValue[lang]" v-model="newValue[lang]"
@@ -66,7 +69,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
@close="hideEditForm" @close="hideEditForm"
> >
<div class="modal__content"> <div class="modal__content">
<div v-for="(lang, key) in langs" :key="key"> <div
v-for="(lang, key) in langs"
:key="key"
>
<span class="lang">{{ lang }}</span> <span class="lang">{{ lang }}</span>
<input <input
v-model="editValue[lang]" v-model="editValue[lang]"
+1 -1
View File
@@ -26,7 +26,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</template> </template>
<script setup> <script setup>
const emit = defineEmit('input') const emit = defineEmits('input')
const { value } = defineProps({ const { value } = defineProps({
value: { value: {
type: String, type: String,
+24
View File
@@ -0,0 +1,24 @@
<template>
<div id="side-menu-loader">
<div id="side-menu-loader-bar" :style="createStyle(width)"></div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
const width = ref(0)
const createStyle = (size) => {
return {
width: `${size}%`,
}
}
onMounted(() => {
window.addEventListener('beforeunload', () => {
setInterval(() => {
width.value = Math.min(width.value + 0.2, 100)
}, 25)
})
})
</script>
+5 -3
View File
@@ -1,11 +1,13 @@
const waitContainer = async (selector) => { const waitContainer = async (selector) => {
new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => {
const container = document.querySelector(selector) const container = document.querySelector(selector)
if (container) { if (container) {
resolve(selector, container) return resolve(selector, container)
} }
setTimeout(() => {
waitContainer(selector)
}, 50) }, 50)
}) })
} }
+3 -6
View File
@@ -13,14 +13,11 @@ const containsAppsMatchingSearch = (values, search) => {
} }
const isAppMatchingSearch = (item, search) => { const isAppMatchingSearch = (item, search) => {
if (search.value.trim() === '') { if (search.trim() === '') {
return true return true
} }
return item.name.toLowerCase().includes(search.value.trim().toLowerCase()) return item.name.toLowerCase().includes(search.trim().toLowerCase())
} }
export { export { containsAppsMatchingSearch, isAppMatchingSearch }
containsAppsMatchingSearch,
isAppMatchingSearch,
}
+16 -26
View File
@@ -19,39 +19,29 @@ import './scss/menu.scss'
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import { waitContainer } from './lib/dom.js' import { createElement } from './lib/dom.js'
// import AppMenu from './menus/AppMenu.vue' import MenuContainer from './menus/MenuContainer.vue'
import SideMenu from './menus/SideMenu.vue'
import SideMenuBig from './menus/SideMenuBig.vue'
import SideMenuWithCategories from './menus/SideMenuWithCategories.vue'
// import PageLoader from './components/PageLoader.vue' // import PageLoader from './components/PageLoader.vue'
// window.PageLoader = PageLoader // window.PageLoader = PageLoader
const pinia = createPinia() const pinia = createPinia()
const body = document.querySelector('body')
waitContainer('#side-menu').then((selector, container) => { const container = createElement('div', {
const component = (() => { id: 'side-menu-container',
if (container.getAttribute('data-bigmenu')) {
return SideMenuBig
} else if (container.getAttribute('data-sidewithcategories')) {
return SideMenuWithCategories
} else {
return SideMenu
}
})()
const app = createApp(component)
app.use(pinia)
app.mount(selector)
}) })
waitContainer('#header .app-menu').then((selector) => { body.appendChild(container)
const app = createApp(AppMenu)
const app = createApp(MenuContainer)
app.use(pinia) app.use(pinia)
app.mount(selector) app.mixin({ methods: { t, n }})
}) app.mount(container)
// waitContainer('#header .app-menu').then((selector) => {
// const app = createApp(AppMenu)
// app.use(pinia)
// app.mixin({ methods: { t, n }})
// app.mount(selector)
// })
+87
View File
@@ -0,0 +1,87 @@
<template>
<template v-if="display">
<PageLoader v-if="hasPageLoader" />
<TopWideMenu v-if="display === 'big-menu'" />
<SideMenuWithCategories v-else-if="display === 'side-with-categories'" />
<SimpleSideMenu
v-else-if="display === 'simple-side-menu'"
:alawayDisplayed="display === 'always-displayed'"
/>
</template>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useConfigStore } from '../store/config.js'
import { createElement } from '../lib/dom.js'
import { translate as t } from '@nextcloud/l10n'
import SimpleSideMenu from './SimpleSideMenu'
import TopWideMenu from './TopWideMenu'
import SideMenuWithCategories from './SideMenuWithCategories'
import PageLoader from '../components/PageLoader'
const config = ref(null)
const configStore = useConfigStore()
const display = ref(null)
const alawayDisplayed = ref(false)
const hasPageLoader = ref(false)
const createOpener = () => {
const nextcloud = document.querySelector('#nextcloud')
const logo = document.querySelector('.header-left .logo, .header-start .logo')
if (!nextcloud || !logo) {
return
}
if (logo.parentNode !== nextcloud) {
nextcloud.appendChild(logo)
}
const opener = createElement('button', {
'class': 'side-menu-opener',
'arial-label': t('side_menu', 'Toggle the menu'),
'html': `<span>${t('side_menu', 'Toggle the menu')}</span>`
})
if (config.value['opener-position'] === 'before') {
nextcloud.parentNode.insertBefore(opener, nextcloud)
} else {
nextcloud.parentNode.insertBefore(opener, nextcloud.nextSibling)
}
}
const createLoader = () => {
const loader = createElement('div', { id: 'side-menu-loader' })
const bar = createElement('div', { id: 'side-menu-loader-bar' })
loader.appendChild(bar)
document.querySelector('body').appendChild(loader)
let pageLoaderValue = 0
window.addEventListener('beforeunload', () => {
setInterval(() => {
pageLoaderBar.style.width = pageLoaderValue.toString() + '%'
pageLoaderValue = Math.min(pageLoaderValue + 0.2, 100)
}, 25)
})
}
onMounted(async () => {
config.value = await configStore.getConfig()
if (config.value['big-menu']) {
display.value = 'big-menu'
} else if (config.value['side-with-categories']) {
display.value = 'side-with-categories'
} else {
display.value = 'simple-side-menu'
alawayDisplayed.value = config.value['always-displayed']
}
hasPageLoader.value = config.value['loader-enabled']
createOpener()
})
</script>
@@ -17,8 +17,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template> <template>
<div id="side-menu"> <div id="side-menu">
<div <div
class="side-menu-header"
v-if="settings || !openerHover || (!avatar && !alwaysDisplayed && logo) || avatar" v-if="settings || !openerHover || (!avatar && !alwaysDisplayed && logo) || avatar"
class="side-menu-header"
> >
<SettingsButton <SettingsButton
v-if="settings" v-if="settings"
@@ -75,6 +75,8 @@ import SideMenuApp from '../components/SideMenuApp'
import AppSearch from '../components/AppSearch' import AppSearch from '../components/AppSearch'
import Logo from '../components/Logo' import Logo from '../components/Logo'
const navStore = useNavStore()
const configStore = useConfigStore()
const targetBlankApps = ref(null) const targetBlankApps = ref(null)
const forceLightIcon = ref(null) const forceLightIcon = ref(null)
const avatar = ref(null) const avatar = ref(null)
@@ -86,12 +88,7 @@ const alwaysDisplayed = ref(null)
const search = ref('') const search = ref('')
const apps = ref([]) const apps = ref([])
function getFiltredAndSortedApps( function getFiltredAndSortedApps(items, order, topMenuApps, topSideMenuApps) {
items,
order,
topMenuApps,
topSideMenuApps
) {
const data = [] const data = []
items.forEach((item) => { items.forEach((item) => {
@@ -115,8 +112,8 @@ function getFiltredAndSortedApps(
}) })
} }
onMounted(() => { onMounted(async () => {
const config = useConfigStore.getConfig() const config = await configStore.getConfig()
targetBlankApps.value = config['target-blank-apps'] targetBlankApps.value = config['target-blank-apps']
forceLightIcon.value = config['force-light-icon'] forceLightIcon.value = config['force-light-icon']
@@ -128,10 +125,10 @@ onMounted(() => {
alwaysDisplayed.value = config['always-displayed'] alwaysDisplayed.value = config['always-displayed']
apps.value = getFiltredAndSortedApps( apps.value = getFiltredAndSortedApps(
useNavStore.getApps(), await navStore.getApps(),
config['apps-order'], config['apps-order'],
config['top-menu-apps'], config['top-menu-apps'],
config['top-side-menu-apps'], config['top-side-menu-apps']
) )
}) })
</script> </script>
+6 -2
View File
@@ -1,6 +1,6 @@
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 } from '@nextcloud/router'
export const useConfigStore = defineStore('config', () => { export const useConfigStore = defineStore('config', () => {
@@ -11,8 +11,12 @@ export const useConfigStore = defineStore('config', () => {
return config.value return config.value
} }
config.value = await fetch(generateUrl('/apps/side_menu/js/config')).then((response) => response.data) config.value = await axios.get(generateUrl('/apps/side_menu/js/config')).then((response) => response.data)
return config.value return config.value
} }
return {
getConfig,
}
}) })
+10 -4
View File
@@ -1,20 +1,21 @@
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 } 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)
function getApps() { async function getApps() {
if (apps.value !== null) { if (apps.value !== null) {
return apps.value return apps.value
} }
apps.value = [] apps.value = []
const cats = await getCategories()
getCategories().forEach((category) => { cats.forEach((category) => {
Object.values(category.apps).forEach((app) => { Object.values(category.apps).forEach((app) => {
apps.value.push(app) apps.value.push(app)
}) })
@@ -28,8 +29,13 @@ export const useNavStore = defineStore('nav', () => {
return categories.value return categories.value
} }
categories.value = await fetch(generateUrl('/apps/side_menu/nav/items')).then((response) => response.data.items) categories.value = await axios.get(generateUrl('/apps/side_menu/nav/items')).then((response) => response.data.items)
return categories.value return categories.value
} }
return {
getApps,
getCategories,
}
}) })
+1 -1
View File
@@ -187,7 +187,7 @@ const SMcreateElement = (tagName, attributes) => {
sideMenuContainer.appendChild(sideMenu) sideMenuContainer.appendChild(sideMenu)
<?php if ($_['loader-enabled'] === true): ?> <?php if ($_['loader-enabled'] === true): ?>
PageLoader() // PageLoader()
<?php endif; ?> <?php endif; ?>
if (nextcloud) { if (nextcloud) {
+1
View File
@@ -13,6 +13,7 @@ const isDev = buildMode === 'development'
module.exports = { module.exports = {
target: 'web', target: 'web',
mode: buildMode, mode: buildMode,
devtool: "inline-source-map",
entry: { entry: {
menu: path.resolve(path.join('src', 'menu.js')), menu: path.resolve(path.join('src', 'menu.js')),
// admin: path.resolve(path.join('src', 'admin.js')), // admin: path.resolve(path.join('src', 'admin.js')),