123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- <!--
- - @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev>
- -
- - @author Gary Kim <gary@garykim.dev>
- -
- - @license GNU AGPL version 3 or any later version
- -
- - This program is free software: you can redistribute it and/or modify
- - it under the terms of the GNU Affero General Public License as
- - published by the Free Software Foundation, either version 3 of the
- - License, or (at your option) any later version.
- -
- - This program is distributed in the hope that it will be useful,
- - but WITHOUT ANY WARRANTY; without even the implied warranty of
- - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- - GNU Affero General Public License for more details.
- -
- - You should have received a copy of the GNU Affero General Public License
- - along with this program. If not, see <http://www.gnu.org/licenses/>.
- -
- -->
- <template>
- <NcAppNavigation data-cy-files-navigation>
- <template #list>
- <NcAppNavigationItem v-for="view in parentViews"
- :key="view.id"
- :allow-collapse="true"
- :data-cy-files-navigation-item="view.id"
- :icon="view.iconClass"
- :open="view.expanded"
- :pinned="view.sticky"
- :title="view.name"
- :to="generateToNavigation(view)"
- @update:open="onToggleExpand(view)">
- <!-- Sanitized icon as svg if provided -->
- <NcIconSvgWrapper v-if="view.icon" slot="icon" :svg="view.icon" />
-
- <!-- Child views if any -->
- <NcAppNavigationItem v-for="child in childViews[view.id]"
- :key="child.id"
- :data-cy-files-navigation-item="child.id"
- :exact="true"
- :icon="child.iconClass"
- :title="child.name"
- :to="generateToNavigation(child)">
- <!-- Sanitized icon as svg if provided -->
- <NcIconSvgWrapper v-if="view.icon" slot="icon" :svg="view.icon" />
- </NcAppNavigationItem>
- </NcAppNavigationItem>
- </template>
-
- <!-- Non-scrollable navigation bottom elements -->
- <template #footer>
- <ul class="app-navigation-entry__settings">
- <!-- User storage usage statistics -->
- <NavigationQuota />
-
- <!-- Files settings modal toggle-->
- <NcAppNavigationItem :aria-label="t('files', 'Open the files app settings')"
- :title="t('files', 'Files settings')"
- data-cy-files-navigation-settings-button
- @click.prevent.stop="openSettings">
- <Cog slot="icon" :size="20" />
- </NcAppNavigationItem>
- </ul>
- </template>
-
- <!-- Settings modal-->
- <SettingsModal :open="settingsOpened"
- data-cy-files-navigation-settings
- @close="onSettingsClose" />
- </NcAppNavigation>
- </template>
-
- <script>
- import { emit, subscribe } from '@nextcloud/event-bus'
- import { generateUrl } from '@nextcloud/router'
- import { translate } from '@nextcloud/l10n'
-
- import axios from '@nextcloud/axios'
- import Cog from 'vue-material-design-icons/Cog.vue'
- import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js'
- import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
- import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
-
- import logger from '../logger.js'
- import Navigation from '../services/Navigation.ts'
- import NavigationQuota from '../components/NavigationQuota.vue'
- import SettingsModal from './Settings.vue'
- import { setPageHeading } from '../../../../core/src/OCP/accessibility.js'
-
- export default {
- name: 'Navigation',
-
- components: {
- Cog,
- NavigationQuota,
- NcAppNavigation,
- NcAppNavigationItem,
- NcIconSvgWrapper,
- SettingsModal,
- },
-
- props: {
- // eslint-disable-next-line vue/prop-name-casing
- Navigation: {
- type: Navigation,
- required: true,
- },
- },
-
- data() {
- return {
- settingsOpened: false,
- }
- },
-
- computed: {
- currentViewId() {
- return this.$route?.params?.view || 'files'
- },
-
- /** @return {Navigation} */
- currentView() {
- return this.views.find(view => view.id === this.currentViewId)
- },
-
- /** @return {Navigation[]} */
- views() {
- return this.Navigation.views
- },
-
- /** @return {Navigation[]} */
- parentViews() {
- return this.views
- // filter child views
- .filter(view => !view.parent)
- // sort views by order
- .sort((a, b) => {
- return a.order - b.order
- })
- },
-
- /** @return {Navigation[]} */
- childViews() {
- return this.views
- // filter parent views
- .filter(view => !!view.parent)
- // create a map of parents and their children
- .reduce((list, view) => {
- list[view.parent] = [...(list[view.parent] || []), view]
- // Sort children by order
- list[view.parent].sort((a, b) => {
- return a.order - b.order
- })
- return list
- }, {})
- },
- },
-
- watch: {
- currentView(view, oldView) {
- // If undefined, it means we're initializing the view
- // This is handled by the legacy-view:initialized event
- if (view?.id === oldView?.id) {
- return
- }
-
- this.Navigation.setActive(view.id)
- logger.debug('Navigation changed', { id: view.id, view })
-
- // debugger
- this.showView(view, oldView)
- },
- },
-
- beforeMount() {
- if (this.currentView) {
- logger.debug('Navigation mounted. Showing requested view', { view: this.currentView })
- this.showView(this.currentView)
- }
-
- subscribe('files:legacy-navigation:changed', this.onLegacyNavigationChanged)
-
- // TODO: remove this once the legacy navigation is gone
- subscribe('files:legacy-view:initialized', () => {
- logger.debug('Legacy view initialized', { ...this.currentView })
- this.showView(this.currentView)
- })
- },
-
- methods: {
- /**
- * @param {Navigation} view the new active view
- * @param {Navigation} oldView the old active view
- */
- showView(view, oldView) {
- // Closing any opened sidebar
- window?.OCA?.Files?.Sidebar?.close?.()
-
- if (view?.legacy) {
- const newAppContent = document.querySelector('#app-content #app-content-' + this.currentView.id + '.viewcontainer')
- document.querySelectorAll('#app-content .viewcontainer').forEach(el => {
- el.classList.add('hidden')
- })
- newAppContent.classList.remove('hidden')
-
- // Triggering legacy navigation events
- const { dir = '/' } = OC.Util.History.parseUrlQuery()
- const params = { itemId: view.id, dir }
-
- logger.debug('Triggering legacy navigation event', params)
- window.jQuery(newAppContent).trigger(new window.jQuery.Event('show', params))
- window.jQuery(newAppContent).trigger(new window.jQuery.Event('urlChanged', params))
- }
-
- this.Navigation.setActive(view)
- setPageHeading(view.name)
- emit('files:navigation:changed', view)
- },
-
- /**
- * Coming from the legacy files app.
- * TODO: remove when all views are migrated.
- *
- * @param {Navigation} view the new active view
- */
- onLegacyNavigationChanged({ id } = { id: 'files' }) {
- const view = this.Navigation.views.find(view => view.id === id)
- if (view && view.legacy && view.id !== this.currentView.id) {
- // Force update the current route as the request comes
- // from the legacy files app router
- this.$router.replace({ ...this.$route, params: { view: view.id } })
- this.Navigation.setActive(view)
- this.showView(view)
- }
- },
-
- /**
- * Expand/collapse a a view with children and permanently
- * save this setting in the server.
- *
- * @param {Navigation} view the view to toggle
- */
- onToggleExpand(view) {
- // Invert state
- view.expanded = !view.expanded
- axios.post(generateUrl(`/apps/files/api/v1/toggleShowFolder/${view.id}`), { show: view.expanded })
- },
-
- /**
- * Generate the route to a view
- *
- * @param {Navigation} view the view to toggle
- */
- generateToNavigation(view) {
- if (view.params) {
- const { dir, fileid } = view.params
- return { name: 'filelist', params: view.params, query: { dir, fileid } }
- }
- return { name: 'filelist', params: { view: view.id } }
- },
-
- /**
- * Open the settings modal
- */
- openSettings() {
- this.settingsOpened = true
- },
-
- /**
- * Close the settings modal
- */
- onSettingsClose() {
- this.settingsOpened = false
- },
-
- t: translate,
- },
- }
- </script>
-
- <style scoped lang="scss">
- // TODO: remove when https://github.com/nextcloud/nextcloud-vue/pull/3539 is in
- .app-navigation::v-deep .app-navigation-entry-icon {
- background-repeat: no-repeat;
- background-position: center;
- }
-
- .app-navigation > 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;
- }
- </style>
|