diff --git a/apps/files/src/actions/moveOrCopyAction.ts b/apps/files/src/actions/moveOrCopyAction.ts index 4d70995a0e4a0..fa7b5d4345ae9 100644 --- a/apps/files/src/actions/moveOrCopyAction.ts +++ b/apps/files/src/actions/moveOrCopyAction.ts @@ -8,7 +8,7 @@ import type { FileStat, ResponseDataDetailed } from 'webdav' import type { MoveCopyResult } from './moveOrCopyActionUtils' import { isAxiosError } from '@nextcloud/axios' -import { FilePickerClosed, getFilePickerBuilder, showError, showInfo } from '@nextcloud/dialogs' +import { FilePickerClosed, getFilePickerBuilder, showError, showInfo, TOAST_PERMANENT_TIMEOUT } from '@nextcloud/dialogs' import { emit } from '@nextcloud/event-bus' import { FileAction, FileType, NodeStatus, davGetClient, davRootPath, davResultToNode, davGetDefaultPropfind, getUniqueName, Permission } from '@nextcloud/files' import { translate as t } from '@nextcloud/l10n' @@ -40,6 +40,28 @@ const getActionForNodes = (nodes: Node[]): MoveCopyAction => { return MoveCopyAction.COPY } +/** + * Create a loading notification toast + * @param mode The move or copy mode + * @param source Name of the node that is copied / moved + * @param destination Destination path + * @return {() => void} Function to hide the notification + */ +function createLoadingNotification(mode: MoveCopyAction, source: string, destination: string): () => void { + const text = mode === MoveCopyAction.MOVE ? t('files', 'Moving "{source}" to "{destination}" …', { source, destination }) : t('files', 'Copying "{source}" to "{destination}" …', { source, destination }) + + let toast: ReturnType|undefined + toast = showInfo( + ` ${text}`, + { + isHTML: true, + timeout: TOAST_PERMANENT_TIMEOUT, + onRemove: () => { toast?.hideToast(); toast = undefined }, + }, + ) + return () => toast && toast.hideToast() +} + /** * Handle the copy/move of a node to a destination * This can be imported and used by other scripts/components on server @@ -80,6 +102,7 @@ export const handleCopyMoveNodeTo = async (node: Node, destination: Folder, meth // Set loading state Vue.set(node, 'status', NodeStatus.LOADING) + const actionFinished = createLoadingNotification(method, node.basename, destination.path) const queue = getQueue() return await queue.add(async () => { @@ -162,7 +185,8 @@ export const handleCopyMoveNodeTo = async (node: Node, destination: Folder, meth logger.debug(error as Error) throw new Error() } finally { - Vue.set(node, 'status', undefined) + Vue.set(node, 'status', '') + actionFinished() } }) } @@ -309,7 +333,7 @@ export const action = new FileAction({ if (result === false) { showInfo(nodes.length === 1 ? t('files', 'Cancelled move or copy of "{filename}".', { filename: nodes[0].displayname }) - : t('files', 'Cancelled move or copy operation') + : t('files', 'Cancelled move or copy operation'), ) return nodes.map(() => null) } diff --git a/apps/files/src/components/FileEntryMixin.ts b/apps/files/src/components/FileEntryMixin.ts index d1ada122714f0..e325d97b8f854 100644 --- a/apps/files/src/components/FileEntryMixin.ts +++ b/apps/files/src/components/FileEntryMixin.ts @@ -71,8 +71,9 @@ export default defineComponent({ uniqueId() { return hashCode(this.source.source) }, + isLoading() { - return this.source.status === NodeStatus.LOADING + return this.source.status === NodeStatus.LOADING || this.loading !== '' }, /** diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue index 70c9a38607e36..c7574c8a7a7da 100644 --- a/apps/files/src/views/FilesList.vue +++ b/apps/files/src/views/FilesList.vue @@ -662,6 +662,13 @@ export default defineComponent({