diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-06-14 14:53:03 +0200 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-06-14 14:53:54 +0200 |
commit | f96b27d436d6906dfbbfee90bae9f8c6ada4c520 (patch) | |
tree | 59b9a3e320ff9a1b3aeeb725d0539c9b86ae2aa5 | |
parent | 7a97e22db2d1cb2f2b1a3cd6a4d191edb9fc5065 (diff) | |
download | nextcloud-server-refactor/provide-file-actions-through-composable.tar.gz nextcloud-server-refactor/provide-file-actions-through-composable.zip |
refactor(files): Provide `currentView` and default file action through composablerefactor/provide-file-actions-through-composable
This allows us to remove a dirty `this.$parent.$refs...` chain.
Also the logic will be bundled at one place instead of multiple.
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
-rw-r--r-- | apps/files/src/components/FileEntry/FileEntryActions.vue | 52 | ||||
-rw-r--r-- | apps/files/src/components/FileEntry/FileEntryName.vue | 32 | ||||
-rw-r--r-- | apps/files/src/composables/useCurrentView.ts | 28 | ||||
-rw-r--r-- | apps/files/src/composables/useFileActions.ts | 52 |
4 files changed, 120 insertions, 44 deletions
diff --git a/apps/files/src/components/FileEntry/FileEntryActions.vue b/apps/files/src/components/FileEntry/FileEntryActions.vue index 21d5cd9e796..1ae22c98878 100644 --- a/apps/files/src/components/FileEntry/FileEntryActions.vue +++ b/apps/files/src/components/FileEntry/FileEntryActions.vue @@ -81,6 +81,7 @@ import type { PropType } from 'vue' import { DefaultType, FileAction, Node, NodeStatus, View, getFileActions } from '@nextcloud/files' import { showError, showSuccess } from '@nextcloud/dialogs' import { translate as t } from '@nextcloud/l10n' +import { defineComponent, toRef } from 'vue' import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' import NcActions from '@nextcloud/vue/dist/Components/NcActions.js' @@ -88,9 +89,10 @@ 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 ArrowLeftIcon from 'vue-material-design-icons/ArrowLeft.vue' -import Vue, { defineComponent } from 'vue' - import CustomElementRender from '../CustomElementRender.vue' + +import { useCurrentView } from '../../composables/useCurrentView.ts' +import { useFileActions } from '../../composables/useFileActions.ts' import logger from '../../logger.js' // The registered actions list @@ -132,6 +134,20 @@ export default defineComponent({ }, }, + setup(props) { + const { currentView } = useCurrentView() + const { + defaultAction, + enabledActions, + } = useFileActions(toRef(props, 'source'), currentView) + + return { + currentView, + defaultAction, + enabledActions, + } + }, + data() { return { openedSubmenu: null as FileAction | null, @@ -143,24 +159,11 @@ export default defineComponent({ // Remove any trailing slash but leave root slash return (this.$route?.query?.dir?.toString() || '/').replace(/^(.+)\/$/, '$1') }, - currentView(): View { - return this.$navigation.active as View - }, + isLoading() { return this.source.status === NodeStatus.LOADING }, - // Sorted actions that are enabled for this node - enabledActions() { - if (this.source.attributes.failed) { - return [] - } - - return actions - .filter(action => !action.enabled || action.enabled([this.source], this.currentView)) - .sort((a, b) => (a.order || 0) - (b.order || 0)) - }, - // Enabled action that are displayed inline enabledInlineActions() { if (this.filesListWidth < 768 || this.gridMode) { @@ -177,11 +180,6 @@ export default defineComponent({ return this.enabledActions.filter(action => typeof action.renderInline === 'function') }, - // Default actions - enabledDefaultActions() { - return this.enabledActions.filter(action => !!action?.default) - }, - // Actions shown in the menu enabledMenuActions() { // If we're in a submenu, only render the inline @@ -269,7 +267,7 @@ export default defineComponent({ try { // Set the loading marker this.$emit('update:loading', action.id) - Vue.set(this.source, 'status', NodeStatus.LOADING) + this.$set(this.source, 'status', NodeStatus.LOADING) const success = await action.exec(this.source, this.currentView, this.currentDir) @@ -289,7 +287,7 @@ export default defineComponent({ } finally { // Reset the loading marker this.$emit('update:loading', '') - Vue.set(this.source, 'status', undefined) + this.$set(this.source, 'status', undefined) // If that was a submenu, we just go back after the action if (isSubmenu) { @@ -298,12 +296,8 @@ export default defineComponent({ } }, execDefaultAction(event) { - if (this.enabledDefaultActions.length > 0) { - event.preventDefault() - event.stopPropagation() - // Execute the first default action if any - this.enabledDefaultActions[0].exec(this.source, this.currentView, this.currentDir) - } + // If there is one execute it + this.defaultAction?.exec(this.source, this.currentView, this.currentDir) }, isMenu(id: string) { diff --git a/apps/files/src/components/FileEntry/FileEntryName.vue b/apps/files/src/components/FileEntry/FileEntryName.vue index 3e671c0c141..b32922b16d7 100644 --- a/apps/files/src/components/FileEntry/FileEntryName.vue +++ b/apps/files/src/components/FileEntry/FileEntryName.vue @@ -37,7 +37,7 @@ </template> <script lang="ts"> -import type { Node, View } from '@nextcloud/files' +import type { Node } from '@nextcloud/files' import type { PropType } from 'vue' import { showError, showSuccess } from '@nextcloud/dialogs' @@ -45,13 +45,15 @@ import { emit } from '@nextcloud/event-bus' import { FileType, NodeStatus, Permission } from '@nextcloud/files' import { loadState } from '@nextcloud/initial-state' import { translate as t } from '@nextcloud/l10n' +import { defineComponent, toRef } from 'vue' import axios from '@nextcloud/axios' -import { isAxiosError } from 'axios' -import Vue, { defineComponent } from 'vue' +import Axios from 'axios' import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' import { useRenamingStore } from '../../store/renaming.ts' +import { useCurrentView } from '../../composables/useCurrentView' +import { useFileActions } from '../../composables/useFileActions' import logger from '../../logger.js' const forbiddenCharacters = loadState<string[]>('files', 'forbiddenCharacters', []) @@ -90,18 +92,20 @@ export default defineComponent({ }, }, - setup() { + setup(props) { const renamingStore = useRenamingStore() + const { currentView } = useCurrentView() + const { defaultAction } = useFileActions(toRef(props, 'source'), currentView) + return { + currentView, + defaultAction, + renamingStore, } }, computed: { - currentView(): View { - return this.$navigation.active as View - }, - isRenaming() { return this.renamingStore.renamingNode === this.source }, @@ -135,10 +139,8 @@ export default defineComponent({ } } - const enabledDefaultActions = this.$parent?.$refs?.actions?.enabledDefaultActions - if (enabledDefaultActions?.length > 0) { - const action = enabledDefaultActions[0] - const displayName = action.displayName([this.source], this.currentView) + if (this.defaultAction !== null) { + const displayName = this.defaultAction.displayName([this.source], this.currentView) return { is: 'a', params: { @@ -283,7 +285,7 @@ export default defineComponent({ } // Set loading state - Vue.set(this.source, 'status', NodeStatus.LOADING) + this.$set(this.source, 'status', NodeStatus.LOADING) // Update node this.source.rename(newName) @@ -314,7 +316,7 @@ export default defineComponent({ this.source.rename(oldName) this.$refs.renameInput?.focus() - if (isAxiosError(error)) { + if (Axios.isAxiosError(error)) { // TODO: 409 means current folder does not exist, redirect ? if (error?.response?.status === 404) { showError(t('files', 'Could not rename "{oldName}", it does not exist any more', { oldName })) @@ -328,7 +330,7 @@ export default defineComponent({ // Unknown error showError(t('files', 'Could not rename "{oldName}"', { oldName })) } finally { - Vue.set(this.source, 'status', undefined) + this.$set(this.source, 'status', undefined) } }, diff --git a/apps/files/src/composables/useCurrentView.ts b/apps/files/src/composables/useCurrentView.ts new file mode 100644 index 00000000000..7421258be44 --- /dev/null +++ b/apps/files/src/composables/useCurrentView.ts @@ -0,0 +1,28 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { View } from '@nextcloud/files' +import { getNavigation } from '@nextcloud/files' +import { onMounted, onUnmounted, readonly, shallowRef } from 'vue' + +/** + * Composable to get the current active view + */ +export function useCurrentView() { + const navigation = getNavigation() + const currentView = shallowRef<View>() + + const onUpdateView = ({ detail }) => { currentView.value = detail } + + onMounted(() => navigation.addEventListener('updateActive', onUpdateView)) + onUnmounted(() => navigation.removeEventListener('updateActive', onUpdateView)) + + return { + /** + * The currently active files view + */ + currentView: readonly(currentView), + } +} diff --git a/apps/files/src/composables/useFileActions.ts b/apps/files/src/composables/useFileActions.ts new file mode 100644 index 00000000000..53b8eae68ec --- /dev/null +++ b/apps/files/src/composables/useFileActions.ts @@ -0,0 +1,52 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { FileAction, Node, View } from '@nextcloud/files' +import type { MaybeRef } from '@vueuse/core' + +import { NodeStatus, getFileActions } from '@nextcloud/files' +import { computed, unref } from 'vue' + +export function useFileActions(node: MaybeRef<Node>, view: MaybeRef<View>) { + const actions = getFileActions() + + const enabledActions = computed<FileAction[]>(() => { + if (unref(node).status === NodeStatus.FAILED) { + return [] + } + + return actions + .filter((action: FileAction) => action.enabled === undefined || action.enabled([unref(node)], unref(view))) + .sort((a: FileAction, b: FileAction) => (a.order || 0) - (b.order || 0)) + }) + + const enabledDefaultActions = computed<FileAction[]>(() => { + return enabledActions.value + .filter((action: FileAction) => !!action.default) + }) + + const defaultAction = computed<FileAction | null>(() => enabledDefaultActions[0] ?? null) + + return { + /** + * All registered actions + */ + actions, + /** + * The default action to use on the current node and view + */ + defaultAction, + /** + * All currently enabled actions for the current node and view + * This list is sorted by the action order + */ + enabledActions, + /** + * All currently enabled default actions for the current node and view + * This list is sorted by the action order + */ + enabledDefaultActions, + } +} |