diff options
author | John Molakvoæ <skjnldsv@protonmail.com> | 2023-10-12 15:15:20 +0200 |
---|---|---|
committer | John Molakvoæ <skjnldsv@protonmail.com> | 2023-10-17 11:19:02 +0200 |
commit | 64c32f714894d2c884c82922a5349bbe64a55be8 (patch) | |
tree | 0653f9c4a287f7fd5bb1d5faba04bed22d238dc2 /apps/files/src/components/FileEntry/FileEntryActions.vue | |
parent | 0f1f73478a59f59d92c4542aa42dc61973c600de (diff) | |
download | nextcloud-server-64c32f714894d2c884c82922a5349bbe64a55be8.tar.gz nextcloud-server-64c32f714894d2c884c82922a5349bbe64a55be8.zip |
fix(files): split FileEntry Actions
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
Diffstat (limited to 'apps/files/src/components/FileEntry/FileEntryActions.vue')
-rw-r--r-- | apps/files/src/components/FileEntry/FileEntryActions.vue | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/apps/files/src/components/FileEntry/FileEntryActions.vue b/apps/files/src/components/FileEntry/FileEntryActions.vue new file mode 100644 index 00000000000..84d8f4a40f9 --- /dev/null +++ b/apps/files/src/components/FileEntry/FileEntryActions.vue @@ -0,0 +1,238 @@ +<!-- + - @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com> + - + - @author John Molakvoæ <skjnldsv@protonmail.com> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> +<template> + <td class="files-list__row-actions" + data-cy-files-list-row-actions> + <!-- Render actions --> + <CustomElementRender v-for="action in enabledRenderActions" + :key="action.id" + :class="'files-list__row-action-' + action.id" + :current-view="currentView" + :render="action.renderInline" + :source="source" + class="files-list__row-action--inline" /> + + <!-- Menu actions --> + <NcActions v-if="visible" + ref="actionsMenu" + :boundaries-element="getBoundariesElement()" + :container="getBoundariesElement()" + :disabled="isLoading" + :force-name="true" + :force-menu="enabledInlineActions.length === 0 /* forceMenu only if no inline actions */" + :inline="enabledInlineActions.length" + :open.sync="openedMenu"> + <NcActionButton v-for="action in enabledMenuActions" + :key="action.id" + :class="'files-list__row-action-' + action.id" + :close-after-click="true" + :data-cy-files-list-row-action="action.id" + :title="action.title?.([source], currentView)" + @click="onActionClick(action)"> + <template #icon> + <NcLoadingIcon v-if="loading === action.id" :size="18" /> + <NcIconSvgWrapper v-else :svg="action.iconSvgInline([source], currentView)" /> + </template> + {{ actionDisplayName(action) }} + </NcActionButton> + </NcActions> + </td> +</template> + +<script lang="ts"> +import { DefaultType, FileAction, Folder, Node, NodeStatus, View, getFileActions } from '@nextcloud/files' +import { showError, showSuccess } from '@nextcloud/dialogs' +import { translate as t } from '@nextcloud/l10n'; +import Vue, { PropType } from 'vue' + +import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' +import NcActions from '@nextcloud/vue/dist/Components/NcActions.js' +import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js' +import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' + +import logger from '../../logger.js' + +// The registered actions list +const actions = getFileActions() + +export default Vue.extend({ + name: 'FileEntryActions', + + components: { + NcActionButton, + NcActions, + NcIconSvgWrapper, + NcLoadingIcon, + }, + + props: { + filesListWidth: { + type: Number, + required: true, + }, + loading: { + type: String, + required: true, + }, + opened: { + type: Boolean, + default: false, + }, + source: { + type: Object as PropType<Node>, + required: true, + }, + visible: { + type: Boolean, + default: false, + }, + }, + + setup() { + return { + } + }, + + computed: { + 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) { + return [] + } + return this.enabledActions.filter(action => action?.inline?.(this.source, this.currentView)) + }, + + // Enabled action that are displayed inline with a custom render function + enabledRenderActions() { + if (!this.visible) { + return [] + } + 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() { + return [ + // Showing inline first for the NcActions inline prop + ...this.enabledInlineActions, + // Then the rest + ...this.enabledActions.filter(action => action.default !== DefaultType.HIDDEN && typeof action.renderInline !== 'function'), + ].filter((value, index, self) => { + // Then we filter duplicates to prevent inline actions to be shown twice + return index === self.findIndex(action => action.id === value.id) + }) + }, + + openedMenu: { + get() { + return this.opened + }, + set(value) { + this.$emit('update:opened', value) + }, + }, + }, + + methods: { + /** + * Making this a function in case the files-list + * reference changes in the future. That way we're + * sure there is one at the time we call it. + */ + getBoundariesElement() { + return document.querySelector('.app-content > table.files-list') + }, + + actionDisplayName(action: FileAction) { + if (this.filesListWidth < 768 && action.inline && typeof action.title === 'function') { + // if an inline action is rendered in the menu for + // lack of space we use the title first if defined + const title = action.title([this.source], this.currentView) + if (title) return title + } + return action.displayName([this.source], this.currentView) + }, + + async onActionClick(action) { + const displayName = action.displayName([this.source], this.currentView) + try { + // Set the loading marker + this.$emit('update:loading', action.id) + Vue.set(this.source, 'status', NodeStatus.LOADING) + + const success = await action.exec(this.source, this.currentView, this.currentDir) + + // If the action returns null, we stay silent + if (success === null) { + return + } + + if (success) { + showSuccess(t('files', '"{displayName}" action executed successfully', { displayName })) + return + } + showError(t('files', '"{displayName}" action failed', { displayName })) + } catch (e) { + logger.error('Error while executing action', { action, e }) + showError(t('files', '"{displayName}" action failed', { displayName })) + } finally { + // Reset the loading marker + this.$emit('update:loading', '') + Vue.set(this.source, 'status', undefined) + } + }, + 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) + } + }, + + t, + }, +}) +</script> |