diff options
author | skjnldsv <skjnldsv@protonmail.com> | 2024-12-11 11:30:46 +0100 |
---|---|---|
committer | skjnldsv <skjnldsv@protonmail.com> | 2024-12-11 19:04:40 +0100 |
commit | 09d4a3f84245f9022180f6894edb590c3cf5fd34 (patch) | |
tree | 122b553aa80630ed7b195d144b7313ca8c3cc48d | |
parent | d33525c4b12684b252e346ace5b4b615ebaaf931 (diff) | |
download | nextcloud-server-09d4a3f84245f9022180f6894edb590c3cf5fd34.tar.gz nextcloud-server-09d4a3f84245f9022180f6894edb590c3cf5fd34.zip |
fixup! fix(files): simplify active store handling
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
-rw-r--r-- | apps/files/src/actions/deleteAction.ts | 4 | ||||
-rw-r--r-- | apps/files/src/components/FileEntry.vue | 1 | ||||
-rw-r--r-- | apps/files/src/components/FileEntry/FileEntryActions.vue | 111 | ||||
-rw-r--r-- | apps/files/src/components/FileEntryGrid.vue | 1 | ||||
-rw-r--r-- | apps/files/src/components/FileEntryMixin.ts | 6 | ||||
-rw-r--r-- | apps/files/src/components/FilesListTableHeaderActions.vue | 8 | ||||
-rw-r--r-- | apps/files/src/main.ts | 6 | ||||
-rw-r--r-- | apps/files/src/services/HotKeysService.ts | 41 | ||||
-rw-r--r-- | apps/files/src/store/active.ts | 15 | ||||
-rw-r--r-- | apps/files/src/types.ts | 3 | ||||
-rw-r--r-- | apps/files/src/utils/actionUtils.ts | 74 | ||||
-rw-r--r-- | apps/files/src/views/FilesList.vue | 13 |
12 files changed, 167 insertions, 116 deletions
diff --git a/apps/files/src/actions/deleteAction.ts b/apps/files/src/actions/deleteAction.ts index 3ac1d2e797f..8d8aa4f9deb 100644 --- a/apps/files/src/actions/deleteAction.ts +++ b/apps/files/src/actions/deleteAction.ts @@ -87,8 +87,8 @@ export const action = new FileAction({ // Map each node to a promise that resolves with the result of exec(node) const promises = nodes.map(node => { - // Create a promise that resolves with the result of exec(node) - const promise = new Promise<boolean>(resolve => { + // Create a promise that resolves with the result of exec(node) + const promise = new Promise<boolean>(resolve => { queue.add(async () => { try { await deleteNode(node) diff --git a/apps/files/src/components/FileEntry.vue b/apps/files/src/components/FileEntry.vue index efd6a3c8fc0..7541c0f0631 100644 --- a/apps/files/src/components/FileEntry.vue +++ b/apps/files/src/components/FileEntry.vue @@ -46,7 +46,6 @@ <FileEntryActions v-show="!isRenamingSmallScreen" ref="actions" :class="`files-list__row-actions-${uniqueId}`" - :loading.sync="loading" :opened.sync="openedMenu" :source="source" /> diff --git a/apps/files/src/components/FileEntry/FileEntryActions.vue b/apps/files/src/components/FileEntry/FileEntryActions.vue index 2b494e0486b..0bbac99bf48 100644 --- a/apps/files/src/components/FileEntry/FileEntryActions.vue +++ b/apps/files/src/components/FileEntry/FileEntryActions.vue @@ -39,7 +39,7 @@ :title="action.title?.([source], currentView)" @click="onActionClick(action)"> <template #icon> - <NcLoadingIcon v-if="loading === action.id" :size="18" /> + <NcLoadingIcon v-if="isLoadingAction(action)" :size="18" /> <NcIconSvgWrapper v-else :svg="action.iconSvgInline([source], currentView)" /> </template> {{ mountType === 'shared' && action.id === 'sharing-status' ? '' : actionDisplayName(action) }} @@ -66,7 +66,7 @@ :title="action.title?.([source], currentView)" @click="onActionClick(action)"> <template #icon> - <NcLoadingIcon v-if="loading === action.id" :size="18" /> + <NcLoadingIcon v-if="isLoadingAction(action)" :size="18" /> <NcIconSvgWrapper v-else :svg="action.iconSvgInline([source], currentView)" /> </template> {{ actionDisplayName(action) }} @@ -82,7 +82,6 @@ import type { FileAction, Node } from '@nextcloud/files' import { DefaultType, NodeStatus } from '@nextcloud/files' import { defineComponent, inject } from 'vue' -import { showError, showSuccess } from '@nextcloud/dialogs' import { translate as t } from '@nextcloud/l10n' import { useHotKey } from '@nextcloud/vue/dist/Composables/useHotKey.js' @@ -94,10 +93,7 @@ import NcActionSeparator from '@nextcloud/vue/dist/Components/NcActionSeparator. import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js' import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' -import { ACTION_DELETE } from '../../actions/deleteAction.ts' -import { ACTION_DETAILS } from '../../actions/sidebarAction.ts' -import { ACTION_FAVORITE } from '../../actions/favoriteAction.ts' -import { ACTION_RENAME } from '../../actions/renameAction.ts' +import { executeAction } from '../../utils/actionUtils.ts' import { useActiveStore } from '../../store/active.ts' import { useFileListWidth } from '../../composables/useFileListWidth.ts' import { useNavigation } from '../../composables/useNavigation' @@ -118,10 +114,6 @@ export default defineComponent({ }, props: { - loading: { - type: String, - required: true, - }, opened: { type: Boolean, default: false, @@ -162,7 +154,7 @@ export default defineComponent({ computed: { isActive() { - return this.activeStore.activeNode?.source === this.source.source + return this.activeStore?.activeNode?.source === this.source.source }, isLoading() { @@ -262,26 +254,6 @@ export default defineComponent({ stop: true, prevent: true, }) - - useHotKey('d', this.onKeyDown, { - stop: true, - prevent: true, - }) - - useHotKey('F2', this.onKeyDown, { - stop: true, - prevent: true, - }) - - useHotKey('Delete', this.onKeyDown, { - stop: true, - prevent: true, - }) - - useHotKey('s', this.onKeyDown, { - stop: true, - prevent: true, - }) }, methods: { @@ -301,57 +273,29 @@ export default defineComponent({ } }, - async onActionClick(action, isSubmenu = false) { - // Skip click on loading - if (this.isLoading || this.loading !== '') { - return + isLoadingAction(action: FileAction) { + if (!this.isActive) { + return false } + return this.activeStore?.activeAction?.id === action.id + }, + async onActionClick(action, isSubmenu = false) { // If the action is a submenu, we open it if (this.enabledSubmenuActions[action.id]) { this.openedSubmenu = action return } - let displayName = action.id - try { - displayName = action.displayName([this.source], this.currentView) - } catch (error) { - logger.error('Error while getting action display name', { action, error }) - } + // Make sure we set the node as active + this.activeStore.setActiveNode(this.source) - // store the source in case it gets removed from the virtual scroller - // before the action is done executing. - const source = this.source - try { - // Set the loading marker - this.$emit('update:loading', action.id) - this.$set(source, 'status', NodeStatus.LOADING) + // Execute the action + await executeAction(action) - const success = await action.exec(source, this.currentView, this.currentDir) - - // If the action returns null, we stay silent - if (success === null || success === undefined) { - return - } - - if (success) { - showSuccess(t('files', '"{displayName}" action executed successfully', { displayName })) - return - } - showError(t('files', '"{displayName}" action failed', { displayName })) - } catch (error) { - logger.error('Error while executing action', { action, error }) - showError(t('files', '"{displayName}" action failed', { displayName })) - } finally { - // Reset the loading marker - this.$emit('update:loading', '') - this.$set(source, 'status', undefined) - - // If that was a submenu, we just go back after the action - if (isSubmenu) { - this.openedSubmenu = null - } + // If that was a submenu, we just go back after the action + if (isSubmenu) { + this.openedSubmenu = null } }, @@ -389,27 +333,6 @@ export default defineComponent({ if (event.key === 'a' && !this.openedMenu) { this.openedMenu = true } - - // d opens the sidebar - if (event.key === 'd') { - this.onActionClick(this.enabledFileActions.find(action => action.id === ACTION_DETAILS)!) - } - - // F2 renames the file - if (event.key === 'F2') { - this.onActionClick(this.enabledFileActions.find(action => action.id === ACTION_RENAME)!) - } - - // Delete key deletes the file with confirmation - if (event.key === 'Delete') { - this.onActionClick(this.enabledFileActions.find(action => action.id === ACTION_DELETE)!) - } - - // s toggle favorite - if (event.key === 's') { - this.onActionClick(this.enabledFileActions.find(action => action.id === ACTION_FAVORITE)!) - } - }, }, }) diff --git a/apps/files/src/components/FileEntryGrid.vue b/apps/files/src/components/FileEntryGrid.vue index 0b0344afb99..bf007e2be6c 100644 --- a/apps/files/src/components/FileEntryGrid.vue +++ b/apps/files/src/components/FileEntryGrid.vue @@ -58,7 +58,6 @@ <FileEntryActions ref="actions" :class="`files-list__row-actions-${uniqueId}`" :grid-mode="true" - :loading.sync="loading" :opened.sync="openedMenu" :source="source" /> </tr> diff --git a/apps/files/src/components/FileEntryMixin.ts b/apps/files/src/components/FileEntryMixin.ts index 4fa85dfbef3..2d20881cde0 100644 --- a/apps/files/src/components/FileEntryMixin.ts +++ b/apps/files/src/components/FileEntryMixin.ts @@ -59,7 +59,6 @@ export default defineComponent({ data() { return { - loading: '', dragover: false, gridMode: false, } @@ -75,7 +74,7 @@ export default defineComponent({ }, isLoading() { - return this.source.status === NodeStatus.LOADING || this.loading !== '' + return this.source.status === NodeStatus.LOADING }, /** @@ -261,9 +260,6 @@ export default defineComponent({ methods: { resetState() { - // Reset loading state - this.loading = '' - // Reset the preview state this.$refs?.preview?.reset?.() diff --git a/apps/files/src/components/FilesListTableHeaderActions.vue b/apps/files/src/components/FilesListTableHeaderActions.vue index 9f5724dc80f..16d99f974dd 100644 --- a/apps/files/src/components/FilesListTableHeaderActions.vue +++ b/apps/files/src/components/FilesListTableHeaderActions.vue @@ -148,7 +148,13 @@ export default defineComponent({ }, async onActionClick(action) { - const displayName = action.displayName(this.nodes, this.currentView) + let displayName = action.id + try { + displayName = action.displayName(this.nodes, this.currentView) + } catch (error) { + logger.error('Error while getting action display name', { action, error }) + } + const selectionSources = this.selectedNodes try { // Set loading markers diff --git a/apps/files/src/main.ts b/apps/files/src/main.ts index 99fe99422a8..26fa7cdca95 100644 --- a/apps/files/src/main.ts +++ b/apps/files/src/main.ts @@ -8,11 +8,12 @@ import { PiniaVuePlugin } from 'pinia' import Vue from 'vue' import { pinia } from './store/index.ts' +import { registerHotkeys } from './services/HotKeysService.ts' +import FilesApp from './FilesApp.vue' import router from './router/router' import RouterService from './services/RouterService' import SettingsModel from './models/Setting.js' import SettingsService from './services/Settings.js' -import FilesApp from './FilesApp.vue' __webpack_nonce__ = getCSPNonce() @@ -38,6 +39,9 @@ if (!window.OCP.Files.Router) { // Init Pinia store Vue.use(PiniaVuePlugin) +// Init HotKeys AFTER pinia is set up +registerHotkeys() + // Init Navigation Service // This only works with Vue 2 - with Vue 3 this will not modify the source but return just a observer const Navigation = Vue.observable(getNavigation()) diff --git a/apps/files/src/services/HotKeysService.ts b/apps/files/src/services/HotKeysService.ts new file mode 100644 index 00000000000..381fc582441 --- /dev/null +++ b/apps/files/src/services/HotKeysService.ts @@ -0,0 +1,41 @@ +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { useHotKey } from '@nextcloud/vue/dist/Composables/useHotKey.js' + +import { action as deleteAction } from '../actions/deleteAction.ts' +import { action as renameAction } from '../actions/renameAction.ts' +import { action as favoriteAction } from '../actions/favoriteAction.ts' +import { action as sidebarAction } from '../actions/sidebarAction.ts' +import { executeAction } from '../utils/actionUtils.ts' +import logger from '../logger.ts' + +export const registerHotkeys = function() { + // d opens the sidebar + useHotKey('d', () => executeAction(sidebarAction), { + stop: true, + prevent: true, + }) + + // F2 renames the file + useHotKey('F2', () => executeAction(renameAction), { + stop: true, + prevent: true, + }) + + // s toggle favorite + useHotKey('s', () => executeAction(favoriteAction), { + stop: true, + prevent: true, + }) + + // Delete deletes the file + useHotKey('Delete', () => executeAction(deleteAction), { + stop: true, + prevent: true, + }) + + logger.debug('Hotkeys registered') +} diff --git a/apps/files/src/store/active.ts b/apps/files/src/store/active.ts index f36b9d1ca95..84715e090f3 100644 --- a/apps/files/src/store/active.ts +++ b/apps/files/src/store/active.ts @@ -4,7 +4,7 @@ */ import type { ActiveStore } from '../types.ts' -import type { Node, View } from '@nextcloud/files' +import type { FileAction, Node, View } from '@nextcloud/files' import { defineStore } from 'pinia' import { getNavigation } from '@nextcloud/files' @@ -18,6 +18,7 @@ export const useActiveStore = function(...args) { _initialized: false, activeNode: null, activeView: null, + activeAction: null, } as ActiveStore), actions: { @@ -29,13 +30,18 @@ export const useActiveStore = function(...args) { this.activeNode = node }, - /** - * Clear the active node - */ clearActiveNode() { this.activeNode = null }, + setActiveAction(action: FileAction) { + this.activeAction = action + }, + + clearActiveAction() { + this.activeAction = null + }, + onDeletedNode(node: Node) { if (this.activeNode && this.activeNode.source === node.source) { this.clearActiveNode() @@ -58,6 +64,7 @@ export const useActiveStore = function(...args) { 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) => { diff --git a/apps/files/src/types.ts b/apps/files/src/types.ts index e316414bcd7..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, View } from '@nextcloud/files' +import type { FileAction, Folder, Node, View } from '@nextcloud/files' import type { Upload } from '@nextcloud/upload' // Global definitions @@ -100,6 +100,7 @@ export interface ActiveStore { _initialized: boolean activeNode: Node|null activeView: View|null + activeAction: FileAction|null } export interface TemplateFile { diff --git a/apps/files/src/utils/actionUtils.ts b/apps/files/src/utils/actionUtils.ts new file mode 100644 index 00000000000..87b8b6f44d3 --- /dev/null +++ b/apps/files/src/utils/actionUtils.ts @@ -0,0 +1,74 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import type { FileAction } from '@nextcloud/files' + +import { NodeStatus } from '@nextcloud/files' +import { showError, showSuccess } from '@nextcloud/dialogs' +import { t } from '@nextcloud/l10n' +import Vue from 'vue' + +import { pinia } from '../store' +import { useActiveStore } from '../store/active' +import logger from '../logger' + +/** + * Execute an action on the current active node + * + * @param action The action to execute + */ +export const executeAction = async (action: FileAction) => { + const activeStore = useActiveStore(pinia) + const currentDir = (window.OCP.Files.Router?.query?.dir || '/') as string + const currentNode = activeStore.activeNode + const currentView = activeStore.activeView + + if (!currentNode || !currentView) { + logger.error('No active node or view', { node: currentNode, view: currentView }) + return + } + + if (currentNode.status === NodeStatus.LOADING) { + logger.debug('Node is already loading', { node: currentNode }) + return + } + + if (!action.enabled!([currentNode], currentView)) { + logger.debug('Action is not not available for the current context', { action, node: currentNode, view: currentView }) + return + } + + let displayName = action.id + try { + displayName = action.displayName([currentNode], currentView) + } catch (error) { + logger.error('Error while getting action display name', { action, error }) + } + + try { + // Set the loading marker + Vue.set(currentNode, 'status', NodeStatus.LOADING) + activeStore.setActiveAction(action) + + const success = await action.exec(currentNode, currentView, currentDir) + + // If the action returns null, we stay silent + if (success === null || success === undefined) { + return + } + + if (success) { + showSuccess(t('files', '"{displayName}" action executed successfully', { displayName })) + return + } + showError(t('files', '"{displayName}" action failed', { displayName })) + } catch (error) { + logger.error('Error while executing action', { action, error }) + showError(t('files', '"{displayName}" action failed', { displayName })) + } finally { + // Reset the loading marker + Vue.set(currentNode, 'status', undefined) + activeStore.clearActiveAction() + } +} diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue index 0d218da13ca..6ce385776f2 100644 --- a/apps/files/src/views/FilesList.vue +++ b/apps/files/src/views/FilesList.vue @@ -135,16 +135,17 @@ import type { Route } from 'vue-router' import type { Upload } from '@nextcloud/upload' import type { UserConfig } from '../types.ts' -import { getCapabilities } from '@nextcloud/capabilities' +import { defineComponent } from 'vue' import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus' -import { Node, Permission, sortNodes, getFileListActions } from '@nextcloud/files' -import { translate as t } from '@nextcloud/l10n' +import { getCapabilities } from '@nextcloud/capabilities' import { join, dirname, normalize } from 'path' -import { showError, showWarning } from '@nextcloud/dialogs' +import { loadState } from '@nextcloud/initial-state' +import { Node, Permission, sortNodes, getFileListActions } from '@nextcloud/files' import { ShareType } from '@nextcloud/sharing' +import { showError, showWarning } from '@nextcloud/dialogs' +import { translate as t } from '@nextcloud/l10n' import { UploadPicker, UploadStatus } from '@nextcloud/upload' -import { loadState } from '@nextcloud/initial-state' -import { defineComponent } from 'vue' +import { useHotKey } from '@nextcloud/vue/dist/Composables/useHotKey.js' import AccountPlusIcon from 'vue-material-design-icons/AccountPlus.vue' import IconAlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue' |