diff options
Diffstat (limited to 'apps/files/src/views')
-rw-r--r-- | apps/files/src/views/FilesList.vue | 19 | ||||
-rw-r--r-- | apps/files/src/views/Navigation.cy.ts | 97 | ||||
-rw-r--r-- | apps/files/src/views/Navigation.vue | 65 |
3 files changed, 98 insertions, 83 deletions
diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue index 493437225d6..fabf1cae6b9 100644 --- a/apps/files/src/views/FilesList.vue +++ b/apps/files/src/views/FilesList.vue @@ -107,7 +107,7 @@ </template> <script lang="ts"> -import type { View, ContentsWithRoot } from '@nextcloud/files' +import type { ContentsWithRoot } from '@nextcloud/files' import type { Upload } from '@nextcloud/upload' import type { CancelablePromise } from 'cancelable-promise' import type { ComponentPublicInstance } from 'vue' @@ -137,6 +137,7 @@ import AccountPlusIcon from 'vue-material-design-icons/AccountPlus.vue' import ViewGridIcon from 'vue-material-design-icons/ViewGrid.vue' import { action as sidebarAction } from '../actions/sidebarAction.ts' +import { useNavigation } from '../composables/useNavigation.ts' import { useFilesStore } from '../store/files.ts' import { usePathsStore } from '../store/paths.ts' import { useSelectionStore } from '../store/selection.ts' @@ -186,10 +187,13 @@ export default defineComponent({ const uploaderStore = useUploaderStore() const userConfigStore = useUserConfigStore() const viewConfigStore = useViewConfigStore() + const { currentView } = useNavigation() const enableGridView = (loadState('core', 'config', [])['enable_non-accessible_features'] ?? true) return { + currentView, + filesStore, pathsStore, selectionStore, @@ -228,10 +232,6 @@ export default defineComponent({ return this.userConfigStore.userConfig }, - currentView(): View { - return this.$navigation.active || this.$navigation.views.find((view) => view.id === (this.$route.params?.view ?? 'files'))! - }, - pageHeading(): string { return this.currentView?.name ?? t('files', 'Files') }, @@ -475,7 +475,7 @@ export default defineComponent({ subscribe('files:node:deleted', this.onNodeDeleted) subscribe('files:node:updated', this.onUpdatedNode) subscribe('nextcloud:unified-search.search', this.onSearch) - subscribe('nextcloud:unified-search.reset', this.onSearch) + subscribe('nextcloud:unified-search.reset', this.resetSearch) // reload on settings change this.unsubscribeStoreCallback = this.userConfigStore.$subscribe(() => this.fetchContent(), { deep: true }) @@ -485,7 +485,7 @@ export default defineComponent({ unsubscribe('files:node:deleted', this.onNodeDeleted) unsubscribe('files:node:updated', this.onUpdatedNode) unsubscribe('nextcloud:unified-search.search', this.onSearch) - unsubscribe('nextcloud:unified-search.reset', this.onSearch) + unsubscribe('nextcloud:unified-search.reset', this.resetSearch) this.unsubscribeStoreCallback() }, @@ -656,6 +656,9 @@ export default defineComponent({ * Reset the search query */ resetSearch() { + // Reset debounced calls to not set the query again + this.onSearch.clear() + // Reset filter query this.filterText = '' }, @@ -668,7 +671,7 @@ export default defineComponent({ if (window?.OCA?.Files?.Sidebar?.setActiveTab) { window.OCA.Files.Sidebar.setActiveTab('sharing') } - sidebarAction.exec(this.currentFolder, this.currentView, this.currentFolder.path) + sidebarAction.exec(this.currentFolder, this.currentView!, this.currentFolder.path) }, toggleGridView() { this.userConfigStore.update('grid_view', !this.userConfig.grid_view) diff --git a/apps/files/src/views/Navigation.cy.ts b/apps/files/src/views/Navigation.cy.ts index a555a04a910..46360a2357a 100644 --- a/apps/files/src/views/Navigation.cy.ts +++ b/apps/files/src/views/Navigation.cy.ts @@ -2,22 +2,38 @@ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -import FolderSvg from '@mdi/svg/svg/folder.svg' -import ShareSvg from '@mdi/svg/svg/share-variant.svg' +import type { Navigation } from '@nextcloud/files' +import FolderSvg from '@mdi/svg/svg/folder.svg?raw' import { createTestingPinia } from '@pinia/testing' import NavigationView from './Navigation.vue' -import router from '../router/router' import { useViewConfigStore } from '../store/viewConfig' import { Folder, View, getNavigation } from '@nextcloud/files' import Vue from 'vue' +import router from '../router/router' + +const resetNavigation = () => { + const nav = getNavigation() + ;[...nav.views].forEach(({ id }) => nav.remove(id)) + nav.setActive(null) +} + +const createView = (id: string, name: string, parent?: string) => new View({ + id, + name, + getContents: async () => ({ folder: {} as Folder, contents: [] }), + icon: FolderSvg, + order: 1, + parent, +}) describe('Navigation renders', () => { - delete window._nc_navigation - const Navigation = getNavigation() + let Navigation: Navigation before(() => { + delete window._nc_navigation + Navigation = getNavigation() Vue.prototype.$navigation = Navigation cy.mockInitialState('files', 'storageStats', { @@ -44,29 +60,31 @@ describe('Navigation renders', () => { }) describe('Navigation API', () => { - delete window._nc_navigation - const Navigation = getNavigation() + let Navigation: Navigation + + before(async () => { + delete window._nc_navigation + Navigation = getNavigation() - before(() => { Vue.prototype.$navigation = Navigation + await router.replace({ name: 'filelist', params: { view: 'files' } }) }) + beforeEach(() => resetNavigation()) + it('Check API entries rendering', () => { - Navigation.register(new View({ - id: 'files', - name: 'Files', - getContents: async () => ({ folder: {} as Folder, contents: [] }), - icon: FolderSvg, - order: 1, - })) + Navigation.register(createView('files', 'Files')) + console.warn(Navigation.views) cy.mount(NavigationView, { + router, global: { - plugins: [createTestingPinia({ - createSpy: cy.spy, - })], + plugins: [ + createTestingPinia({ + createSpy: cy.spy, + }), + ], }, - router, }) cy.get('[data-cy-files-navigation]').should('be.visible') @@ -76,21 +94,16 @@ describe('Navigation API', () => { }) it('Adds a new entry and render', () => { - Navigation.register(new View({ - id: 'sharing', - name: 'Sharing', - getContents: async () => ({ folder: {} as Folder, contents: [] }), - icon: ShareSvg, - order: 2, - })) + Navigation.register(createView('files', 'Files')) + Navigation.register(createView('sharing', 'Sharing')) cy.mount(NavigationView, { + router, global: { plugins: [createTestingPinia({ createSpy: cy.spy, })], }, - router, }) cy.get('[data-cy-files-navigation]').should('be.visible') @@ -100,22 +113,17 @@ describe('Navigation API', () => { }) it('Adds a new children, render and open menu', () => { - Navigation.register(new View({ - id: 'sharingin', - name: 'Shared with me', - getContents: async () => ({ folder: {} as Folder, contents: [] }), - parent: 'sharing', - icon: ShareSvg, - order: 1, - })) + Navigation.register(createView('files', 'Files')) + Navigation.register(createView('sharing', 'Sharing')) + Navigation.register(createView('sharingin', 'Shared with me', 'sharing')) cy.mount(NavigationView, { + router, global: { plugins: [createTestingPinia({ createSpy: cy.spy, })], }, - router, }) cy.wrap(useViewConfigStore()).as('viewConfigStore') @@ -143,23 +151,18 @@ describe('Navigation API', () => { }) it('Throws when adding a duplicate entry', () => { - expect(() => { - Navigation.register(new View({ - id: 'files', - name: 'Files', - getContents: async () => ({ folder: {} as Folder, contents: [] }), - icon: FolderSvg, - order: 1, - })) - }).to.throw('View id files is already registered') + Navigation.register(createView('files', 'Files')) + expect(() => Navigation.register(createView('files', 'Files'))) + .to.throw('View id files is already registered') }) }) describe('Quota rendering', () => { - delete window._nc_navigation - const Navigation = getNavigation() + let Navigation: Navigation before(() => { + delete window._nc_navigation + Navigation = getNavigation() Vue.prototype.$navigation = Navigation }) diff --git a/apps/files/src/views/Navigation.vue b/apps/files/src/views/Navigation.vue index 71e9bf38068..b69c6d5f7f2 100644 --- a/apps/files/src/views/Navigation.vue +++ b/apps/files/src/views/Navigation.vue @@ -45,7 +45,7 @@ :name="t('files', 'Files settings')" data-cy-files-navigation-settings-button @click.prevent.stop="openSettings"> - <Cog slot="icon" :size="20" /> + <IconCog slot="icon" :size="20" /> </NcAppNavigationItem> </ul> </template> @@ -61,22 +61,26 @@ import type { View } from '@nextcloud/files' import { emit } from '@nextcloud/event-bus' -import { translate } from '@nextcloud/l10n' -import Cog from 'vue-material-design-icons/Cog.vue' +import { translate as t } from '@nextcloud/l10n' +import { defineComponent } from 'vue' + +import IconCog 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 NavigationQuota from '../components/NavigationQuota.vue' +import SettingsModal from './Settings.vue' +import { useNavigation } from '../composables/useNavigation' import { useViewConfigStore } from '../store/viewConfig.ts' import logger from '../logger.js' -import NavigationQuota from '../components/NavigationQuota.vue' -import SettingsModal from './Settings.vue' -export default { +export default defineComponent({ name: 'Navigation', components: { - Cog, + IconCog, + NavigationQuota, NcAppNavigation, NcAppNavigationItem, @@ -86,7 +90,12 @@ export default { setup() { const viewConfigStore = useViewConfigStore() + const { currentView, views } = useNavigation() + return { + currentView, + views, + viewConfigStore, } }, @@ -98,18 +107,13 @@ export default { }, computed: { + /** + * The current view ID from the route params + */ currentViewId() { return this.$route?.params?.view || 'files' }, - currentView(): View { - return this.views.find(view => view.id === this.currentViewId)! - }, - - views(): View[] { - return this.$navigation.views - }, - parentViews(): View[] { return this.views // filter child views @@ -137,24 +141,27 @@ export default { }, watch: { - currentView(view, oldView) { - if (view.id !== oldView?.id) { - this.$navigation.setActive(view) - logger.debug(`Navigation changed from ${oldView.id} to ${view.id}`, { from: oldView, to: view }) - + 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 the new view as active this.showView(view) + logger.debug(`Navigation changed from ${oldView} to ${newView}`, { to: view }) } }, }, beforeMount() { - if (this.currentView) { - logger.debug('Navigation mounted. Showing requested view', { view: this.currentView }) - this.showView(this.currentView) - } + // 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: { + t, + /** * Only use exact route matching on routes with child views * Because if a view does not have children (like the files view) then multiple routes might be matched for it @@ -165,9 +172,13 @@ export default { return this.childViews[view.id]?.length > 0 }, + /** + * 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?.() + window.OCA?.Files?.Sidebar?.close?.() this.$navigation.setActive(view) emit('files:navigation:changed', view) }, @@ -221,10 +232,8 @@ export default { onSettingsClose() { this.settingsOpened = false }, - - t: translate, }, -} +}) </script> <style scoped lang="scss"> |