aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorskjnldsv <skjnldsv@protonmail.com>2024-12-11 11:30:46 +0100
committerskjnldsv <skjnldsv@protonmail.com>2024-12-11 19:04:40 +0100
commit09d4a3f84245f9022180f6894edb590c3cf5fd34 (patch)
tree122b553aa80630ed7b195d144b7313ca8c3cc48d
parentd33525c4b12684b252e346ace5b4b615ebaaf931 (diff)
downloadnextcloud-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.ts4
-rw-r--r--apps/files/src/components/FileEntry.vue1
-rw-r--r--apps/files/src/components/FileEntry/FileEntryActions.vue111
-rw-r--r--apps/files/src/components/FileEntryGrid.vue1
-rw-r--r--apps/files/src/components/FileEntryMixin.ts6
-rw-r--r--apps/files/src/components/FilesListTableHeaderActions.vue8
-rw-r--r--apps/files/src/main.ts6
-rw-r--r--apps/files/src/services/HotKeysService.ts41
-rw-r--r--apps/files/src/store/active.ts15
-rw-r--r--apps/files/src/types.ts3
-rw-r--r--apps/files/src/utils/actionUtils.ts74
-rw-r--r--apps/files/src/views/FilesList.vue13
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'