add top menu (StandardMenu)
This commit is contained in:
parent
e74de11215
commit
502cc4f3f0
5 changed files with 143 additions and 141 deletions
|
|
@ -17,15 +17,19 @@
|
|||
|
||||
const waitContainer = async (selector) => {
|
||||
return new Promise((resolve) => {
|
||||
const container = document.querySelector(selector)
|
||||
const execute = () => {
|
||||
const container = document.querySelector(selector)
|
||||
|
||||
if (container) {
|
||||
return resolve(selector, container)
|
||||
if (container) {
|
||||
resolve(container)
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
execute(selector)
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
waitContainer(selector)
|
||||
}, 50)
|
||||
execute(selector)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
27
src/menu.js
27
src/menu.js
|
|
@ -19,12 +19,10 @@ import './scss/menu.scss'
|
|||
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createElement } from './lib/dom.js'
|
||||
import { createElement, waitContainer } from './lib/dom.js'
|
||||
|
||||
import MenuContainer from './menus/MenuContainer.vue'
|
||||
|
||||
// import PageLoader from './components/PageLoader.vue'
|
||||
// window.PageLoader = PageLoader
|
||||
import StandardMenu from './menus/StandardMenu'
|
||||
import MenuContainer from './menus/MenuContainer'
|
||||
|
||||
const pinia = createPinia()
|
||||
const body = document.querySelector('body')
|
||||
|
|
@ -39,9 +37,16 @@ app.use(pinia)
|
|||
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)
|
||||
// })
|
||||
waitContainer('#header .app-menu').then((container) => {
|
||||
const menu = createElement('div', {
|
||||
id: 'app-menu-container',
|
||||
})
|
||||
|
||||
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')"
|
||||
>
|
||||
<ul
|
||||
v-if="appList.length"
|
||||
v-if="ready"
|
||||
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
|
||||
v-for="app in mainAppList(state)"
|
||||
v-for="app in mainAppList"
|
||||
:key="app.id"
|
||||
:data-app-id="app.id"
|
||||
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)"
|
||||
>
|
||||
<a
|
||||
:href="app.href"
|
||||
:class="{ 'has-unread': app.unread > 0 }"
|
||||
:aria-label="appLabel(app)"
|
||||
:aria-label="app.name"
|
||||
:target="targetBlankApps.indexOf(app.id) !== -1 ? '_blank' : undefined"
|
||||
:aria-current="app.active ? 'page' : false"
|
||||
>
|
||||
<img
|
||||
:src="app.icon"
|
||||
alt=""
|
||||
:alt="app.name"
|
||||
/>
|
||||
<div class="app-menu-entry--label">
|
||||
{{ app.name }}
|
||||
|
|
@ -64,9 +71,9 @@
|
|||
:aria-label="t('core', 'More apps')"
|
||||
>
|
||||
<NcActionLink
|
||||
v-for="app in popoverAppList(state)"
|
||||
v-for="app in popoverAppList"
|
||||
:key="app.id"
|
||||
:aria-label="appLabel(app)"
|
||||
:aria-label="app.name"
|
||||
:aria-current="app.active ? 'page' : false"
|
||||
:href="app.href"
|
||||
:style="makeStyle(app)"
|
||||
|
|
@ -79,7 +86,7 @@
|
|||
>
|
||||
<img
|
||||
:src="app.icon"
|
||||
alt=""
|
||||
:alt="app.name"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -94,134 +101,94 @@
|
|||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { n, t } from '@nextcloud/l10n'
|
||||
import { useElementSize } from '@vueuse/core'
|
||||
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'
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useConfigStore } from '../store/config.js'
|
||||
import { useNavStore } from '../store/nav.js'
|
||||
import { NcActions, NcActionLink } from '@nextcloud/vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AppMenu',
|
||||
const navStore = useNavStore()
|
||||
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: {
|
||||
NcActions,
|
||||
NcActionLink,
|
||||
},
|
||||
|
||||
setup() {
|
||||
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)
|
||||
const setApps = (value) => {
|
||||
value.forEach((app) => {
|
||||
Array.from(topMenuApps.value).forEach((id) => {
|
||||
if (app.id === id) {
|
||||
app.order = appsOrder.value.findIndex((element) => element === app.id) || null
|
||||
appList.value.push(app)
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
++this.state
|
||||
},
|
||||
computeLists()
|
||||
}
|
||||
|
||||
mainAppList() {
|
||||
return this.appList.slice(0, this.appLimit())
|
||||
},
|
||||
const appLimit = () => {
|
||||
const headerStart = document.querySelector('#header .header-start')
|
||||
const headerEnd = document.querySelector('#header .header-end')
|
||||
const body = document.querySelector('body')
|
||||
|
||||
popoverAppList() {
|
||||
return this.appList.slice(this.appLimit())
|
||||
},
|
||||
let size = (headerEnd ? headerEnd.offsetWidth : 0) + 70
|
||||
|
||||
appLimit() {
|
||||
const maxApps = Math.floor(this.$root.$el.offsetWidth / 60)
|
||||
|
||||
if (maxApps < this.appList.length) {
|
||||
// Ensure there is space for the overflow menu
|
||||
return Math.max(maxApps - 1, 0)
|
||||
if (headerStart) {
|
||||
Array.from(headerStart.children).forEach((child) => {
|
||||
if (child.id !== 'app-menu-container') {
|
||||
size += child.offsetWidth
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return maxApps
|
||||
},
|
||||
return Math.floor((body.offsetWidth - size) / 70)
|
||||
}
|
||||
|
||||
setNavigationCounter(id, counter) {
|
||||
const app = this.appList.find(({ app }) => app === id)
|
||||
if (app) {
|
||||
this.$set(app, 'unread', counter)
|
||||
} else {
|
||||
logger.warn(`Could not find app "${id}" for setting navigation count`)
|
||||
}
|
||||
},
|
||||
const makeStyle = (app) => {
|
||||
if (app.order !== null) {
|
||||
return { order: app.order }
|
||||
}
|
||||
|
||||
setApps(apps) {
|
||||
this.appList = []
|
||||
let orders = {}
|
||||
return {}
|
||||
}
|
||||
|
||||
window.menuAppsOrder.forEach((app, order) => {
|
||||
orders[app] = order + 1
|
||||
})
|
||||
const computeLists = () => {
|
||||
mainAppList.value = appList.value.slice(0, appLimit())
|
||||
popoverAppList.value = appList.value.slice(appLimit()).sort((a, b) => a.order - b.order)
|
||||
}
|
||||
|
||||
apps.forEach((app) => {
|
||||
Array.from(window.topMenuApps).forEach((id) => {
|
||||
if (app.id === id) {
|
||||
app.order = orders[id] || null
|
||||
this.appList.push(app)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
onMounted(async () => {
|
||||
const config = await configStore.getConfig()
|
||||
|
||||
appLabel(app) {
|
||||
return (
|
||||
app.name +
|
||||
(app.active ? ' (' + t('core', 'Currently open') + ')' : '') +
|
||||
(app.unread > 0 ? ' (' + n('core', '{count} notification', '{count} notifications', app.unread, { count: app.unread }) + ')' : '')
|
||||
)
|
||||
},
|
||||
targetBlankApps.value = config['target-blank-apps']
|
||||
hiddenLabels.value = config['top-menu-mouse-over-hidden-label']
|
||||
topMenuApps.value = config['top-menu-apps']
|
||||
appsOrder.value = config['apps-order']
|
||||
ready.value = true
|
||||
|
||||
makeStyle(app) {
|
||||
if (app.order !== null) {
|
||||
return `order: ${app.order}`
|
||||
}
|
||||
},
|
||||
},
|
||||
setApps(await navStore.getCoreApps())
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
window.clearTimeout(resizeTimeout)
|
||||
resizeTimeout = window.setTimeout(computeLists, 100)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
compatConfig: {
|
||||
GLOBAL_MOUNT_CONTAINER: false,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$header-icon-size: 20px;
|
||||
|
||||
|
|
@ -15,6 +15,17 @@
|
|||
* 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 {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
|
|
|||
|
|
@ -18,11 +18,12 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { generateUrl, generateOcsUrl } from '@nextcloud/router'
|
||||
|
||||
export const useNavStore = defineStore('nav', () => {
|
||||
const categories = ref(null)
|
||||
const apps = ref(null)
|
||||
const coreApps = ref(null)
|
||||
|
||||
async function getApps() {
|
||||
if (apps.value !== null) {
|
||||
|
|
@ -41,6 +42,19 @@ export const useNavStore = defineStore('nav', () => {
|
|||
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() {
|
||||
if (categories.value !== null) {
|
||||
return categories.value
|
||||
|
|
@ -53,6 +67,7 @@ export const useNavStore = defineStore('nav', () => {
|
|||
|
||||
return {
|
||||
getApps,
|
||||
getCoreApps,
|
||||
getCategories,
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue