diff options
Diffstat (limited to 'apps/files/src/views/Navigation.vue')
-rw-r--r-- | apps/files/src/views/Navigation.vue | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/apps/files/src/views/Navigation.vue b/apps/files/src/views/Navigation.vue new file mode 100644 index 00000000000..0f3c3647c6e --- /dev/null +++ b/apps/files/src/views/Navigation.vue @@ -0,0 +1,228 @@ +<!-- + - SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> +<template> + <NcAppNavigation data-cy-files-navigation + class="files-navigation" + :aria-label="t('files', 'Files')"> + <template #search> + <FilesNavigationSearch /> + </template> + <template #default> + <NcAppNavigationList class="files-navigation__list" + :aria-label="t('files', 'Views')"> + <FilesNavigationItem :views="viewMap" /> + </NcAppNavigationList> + + <!-- Settings modal--> + <SettingsModal :open.sync="settingsOpened" + data-cy-files-navigation-settings + @close="onSettingsClose" /> + </template> + + <!-- Non-scrollable navigation bottom elements --> + <template #footer> + <ul class="app-navigation-entry__settings"> + <!-- User storage usage statistics --> + <NavigationQuota /> + + <!-- Files settings modal toggle--> + <NcAppNavigationItem :name="t('files', 'Files settings')" + data-cy-files-navigation-settings-button + @click.prevent.stop="openSettings"> + <IconCog slot="icon" :size="20" /> + </NcAppNavigationItem> + </ul> + </template> + </NcAppNavigation> +</template> + +<script lang="ts"> +import type { View } from '@nextcloud/files' +import type { ViewConfig } from '../types.ts' + +import { emit, subscribe } from '@nextcloud/event-bus' +import { getNavigation } from '@nextcloud/files' +import { t, getCanonicalLocale, getLanguage } from '@nextcloud/l10n' +import { defineComponent } from 'vue' + +import IconCog from 'vue-material-design-icons/CogOutline.vue' +import NcAppNavigation from '@nextcloud/vue/components/NcAppNavigation' +import NcAppNavigationItem from '@nextcloud/vue/components/NcAppNavigationItem' +import NcAppNavigationList from '@nextcloud/vue/components/NcAppNavigationList' +import NavigationQuota from '../components/NavigationQuota.vue' +import SettingsModal from './Settings.vue' +import FilesNavigationItem from '../components/FilesNavigationItem.vue' +import FilesNavigationSearch from '../components/FilesNavigationSearch.vue' + +import { useNavigation } from '../composables/useNavigation' +import { useFiltersStore } from '../store/filters.ts' +import { useViewConfigStore } from '../store/viewConfig.ts' +import logger from '../logger.ts' + +const collator = Intl.Collator( + [getLanguage(), getCanonicalLocale()], + { + numeric: true, + usage: 'sort', + }, +) + +export default defineComponent({ + name: 'Navigation', + + components: { + IconCog, + FilesNavigationItem, + FilesNavigationSearch, + + NavigationQuota, + NcAppNavigation, + NcAppNavigationItem, + NcAppNavigationList, + SettingsModal, + }, + + setup() { + const filtersStore = useFiltersStore() + const viewConfigStore = useViewConfigStore() + const { currentView, views } = useNavigation() + + return { + currentView, + t, + views, + + filtersStore, + viewConfigStore, + } + }, + + data() { + return { + settingsOpened: false, + } + }, + + computed: { + /** + * The current view ID from the route params + */ + currentViewId() { + return this.$route?.params?.view || 'files' + }, + + /** + * Map of parent ids to views + */ + viewMap(): Record<string, View[]> { + return this.views + .reduce((map, view) => { + map[view.parent!] = [...(map[view.parent!] || []), view] + map[view.parent!].sort((a, b) => { + if (typeof a.order === 'number' || typeof b.order === 'number') { + return (a.order ?? 0) - (b.order ?? 0) + } + return collator.compare(a.name, b.name) + }) + return map + }, {} as Record<string, View[]>) + }, + }, + + watch: { + currentViewId(newView, oldView) { + if (this.currentViewId !== this.currentView?.id) { + // This is guaranteed to be a view because `currentViewId` falls back to the default 'files' view + const view = this.views.find(({ id }) => id === this.currentViewId)! + // The new view as active + this.showView(view) + logger.debug(`Navigation changed from ${oldView} to ${newView}`, { to: view }) + } + }, + }, + + created() { + subscribe('files:folder-tree:initialized', this.loadExpandedViews) + subscribe('files:folder-tree:expanded', this.loadExpandedViews) + }, + + beforeMount() { + // This is guaranteed to be a view because `currentViewId` falls back to the default 'files' view + const view = this.views.find(({ id }) => id === this.currentViewId)! + this.showView(view) + logger.debug('Navigation mounted. Showing requested view', { view }) + }, + + methods: { + async loadExpandedViews() { + const viewsToLoad: View[] = (Object.entries(this.viewConfigStore.viewConfigs) as Array<[string, ViewConfig]>) + .filter(([, config]) => config.expanded === true) + .map(([viewId]) => this.views.find(view => view.id === viewId)) + // eslint-disable-next-line no-use-before-define + .filter(Boolean as unknown as ((u: unknown) => u is View)) + .filter((view) => view.loadChildViews && !view.loaded) + for (const view of viewsToLoad) { + await view.loadChildViews(view) + } + }, + + /** + * Set the view as active on the navigation and handle internal state + * @param view View to set active + */ + showView(view: View) { + // Closing any opened sidebar + window.OCA?.Files?.Sidebar?.close?.() + getNavigation().setActive(view) + emit('files:navigation:changed', view) + }, + + /** + * Open the settings modal + */ + openSettings() { + this.settingsOpened = true + }, + + /** + * Close the settings modal + */ + onSettingsClose() { + this.settingsOpened = false + }, + }, +}) +</script> + +<style scoped lang="scss"> +.app-navigation { + :deep(.app-navigation-entry.active .button-vue.icon-collapse:not(:hover)) { + color: var(--color-primary-element-text); + } + + > ul.app-navigation__list { + // Use flex gap value for more elegant spacing + padding-bottom: var(--default-grid-baseline, 4px); + } +} + +.app-navigation-entry__settings { + height: auto !important; + overflow: hidden !important; + padding-top: 0 !important; + // Prevent shrinking or growing + flex: 0 0 auto; +} + +.files-navigation { + &__list { + height: 100%; // Fill all available space for sticky views + } + + :deep(.app-navigation__content > ul.app-navigation__list) { + will-change: scroll-position; + } +} +</style> |