From e7001022c75b3a818356378bb53bbfe5129a10fe Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Fri, 13 Dec 2024 12:00:28 +0100 Subject: feat(files): add opendetails param and file list up/down keyboard shortcut Signed-off-by: skjnldsv --- apps/files/src/actions/sidebarAction.spec.ts | 4 +- apps/files/src/actions/sidebarAction.ts | 9 +- apps/files/src/components/FilesListVirtual.vue | 172 +++++++++++++++++------ apps/files/src/composables/useRouteParameters.ts | 8 ++ apps/files/src/store/active.ts | 77 ++++++++++ apps/files/src/store/dragging.ts | 3 +- apps/files/src/types.ts | 10 +- 7 files changed, 234 insertions(+), 49 deletions(-) create mode 100644 apps/files/src/store/active.ts (limited to 'apps/files/src') diff --git a/apps/files/src/actions/sidebarAction.spec.ts b/apps/files/src/actions/sidebarAction.spec.ts index 1f1e81dbeaf..75ed8c97b47 100644 --- a/apps/files/src/actions/sidebarAction.spec.ts +++ b/apps/files/src/actions/sidebarAction.spec.ts @@ -130,7 +130,7 @@ describe('Open sidebar action exec tests', () => { expect(goToRouteMock).toBeCalledWith( null, { view: view.id, fileid: '1' }, - { dir: '/' }, + { dir: '/', opendetails: 'true' }, true, ) }) @@ -159,7 +159,7 @@ describe('Open sidebar action exec tests', () => { expect(goToRouteMock).toBeCalledWith( null, { view: view.id, fileid: '1' }, - { dir: '/' }, + { dir: '/', opendetails: 'true' }, true, ) }) diff --git a/apps/files/src/actions/sidebarAction.ts b/apps/files/src/actions/sidebarAction.ts index a951de1db97..0b8ad91741e 100644 --- a/apps/files/src/actions/sidebarAction.ts +++ b/apps/files/src/actions/sidebarAction.ts @@ -44,6 +44,11 @@ export const action = new FileAction({ async exec(node: Node, view: View, dir: string) { try { + // If the sidebar is already open for the current file, do nothing + if (window.OCA.Files.Sidebar.file === node.path) { + logger.debug('Sidebar already open for this file', { node }) + return null + } // Open sidebar and set active tab to sharing by default window.OCA.Files.Sidebar.setActiveTab('sharing') @@ -51,10 +56,10 @@ export const action = new FileAction({ await window.OCA.Files.Sidebar.open(node.path) // Silently update current fileid - window.OCP.Files.Router.goToRoute( + window.OCP?.Files?.Router?.goToRoute( null, { view: view.id, fileid: String(node.fileid) }, - { ...window.OCP.Files.Router.query, dir }, + { ...window.OCP.Files.Router.query, dir, opendetails: 'true' }, true, ) diff --git a/apps/files/src/components/FilesListVirtual.vue b/apps/files/src/components/FilesListVirtual.vue index 81c4c5ac666..6df059f6143 100644 --- a/apps/files/src/components/FilesListVirtual.vue +++ b/apps/files/src/components/FilesListVirtual.vue @@ -12,7 +12,6 @@ isMtimeAvailable, isSizeAvailable, nodes, - fileListWidth, }" :scroll-to-index="scrollToIndex" :caption="caption"> @@ -58,32 +57,34 @@ diff --git a/apps/files/src/composables/useRouteParameters.ts b/apps/files/src/composables/useRouteParameters.ts index abf14614fb7..dbb8ca7f081 100644 --- a/apps/files/src/composables/useRouteParameters.ts +++ b/apps/files/src/composables/useRouteParameters.ts @@ -37,6 +37,11 @@ export function useRouteParameters() { () => 'openfile' in route.query && (typeof route.query.openfile !== 'string' || route.query.openfile.toLocaleLowerCase() !== 'false'), ) + const openDetails = computed( + // if `opendetails` is set it is considered truthy, but allow to explicitly set it to 'false' + () => 'opendetails' in route.query && (typeof route.query.opendetails !== 'string' || route.query.opendetails.toLocaleLowerCase() !== 'false'), + ) + return { /** Path of currently open directory */ directory, @@ -46,5 +51,8 @@ export function useRouteParameters() { /** Should the active node should be opened (`openFile` route param) */ openFile, + + /** Should the details sidebar be shown (`openDetails` route param) */ + openDetails, } } diff --git a/apps/files/src/store/active.ts b/apps/files/src/store/active.ts new file mode 100644 index 00000000000..2efb823b232 --- /dev/null +++ b/apps/files/src/store/active.ts @@ -0,0 +1,77 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { ActiveStore } from '../types.ts' +import type { FileAction, Node, View } from '@nextcloud/files' + +import { defineStore } from 'pinia' +import { getNavigation } from '@nextcloud/files' +import { subscribe } from '@nextcloud/event-bus' + +import logger from '../logger.ts' +import type { set } from 'lodash' + +export const useActiveStore = function(...args) { + const store = defineStore('active', { + state: () => ({ + _initialized: false, + activeNode: null, + activeView: null, + activeAction: null, + } as ActiveStore), + + actions: { + setActiveNode(node: Node) { + if (!node) { + throw new Error('Use clearActiveNode to clear the active node') + } + logger.debug('Setting active node', { node }) + this.activeNode = node + }, + + clearActiveNode() { + this.activeNode = null + }, + + onDeletedNode(node: Node) { + if (this.activeNode && this.activeNode.source === node.source) { + this.clearActiveNode() + } + }, + + setActiveAction(action: FileAction) { + this.activeAction = action + }, + + clearActiveAction() { + this.activeAction = null + }, + + onChangedView(view: View|null = null) { + logger.debug('Setting active view', { view }) + this.activeView = view + this.clearActiveNode() + }, + }, + }) + + const activeStore = store(...args) + const navigation = getNavigation() + + // Make sure we only register the listeners once + if (!activeStore._initialized) { + subscribe('files:node:deleted', activeStore.onDeletedNode) + + activeStore._initialized = true + activeStore.onChangedView(navigation.active) + + // Or you can react to changes of the current active view + navigation.addEventListener('updateActive', (event) => { + activeStore.onChangedView(event.detail) + }) + } + + return activeStore +} diff --git a/apps/files/src/store/dragging.ts b/apps/files/src/store/dragging.ts index 667c6fe67a7..f5c20095cca 100644 --- a/apps/files/src/store/dragging.ts +++ b/apps/files/src/store/dragging.ts @@ -2,9 +2,10 @@ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ +import type { DragAndDropStore, FileSource } from '../types' + import { defineStore } from 'pinia' import Vue from 'vue' -import type { DragAndDropStore, FileSource } from '../types' export const useDragAndDropStore = defineStore('dragging', { state: () => ({ diff --git a/apps/files/src/types.ts b/apps/files/src/types.ts index 39f0ed0865f..673cb06e182 100644 --- a/apps/files/src/types.ts +++ b/apps/files/src/types.ts @@ -2,7 +2,7 @@ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -import type { Folder, Node } from '@nextcloud/files' +import type { FileAction, Folder, Node, View } from '@nextcloud/files' import type { Upload } from '@nextcloud/upload' // Global definitions @@ -95,6 +95,14 @@ export interface DragAndDropStore { dragging: FileSource[] } +// Active node store +export interface ActiveStore { + _initialized: boolean + activeNode: Node|null + activeView: View|null + activeAction: FileAction|null +} + export interface TemplateFile { app: string label: string -- cgit v1.2.3