]> source.dussan.org Git - nextcloud-server.git/commitdiff
feat(files): add batch support to copy-move
authorJohn Molakvoæ <skjnldsv@protonmail.com>
Fri, 8 Dec 2023 15:45:22 +0000 (16:45 +0100)
committerJohn Molakvoæ <skjnldsv@protonmail.com>
Thu, 14 Dec 2023 08:49:59 +0000 (09:49 +0100)
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
apps/files/src/actions/moveOrCopyAction.ts
apps/files/src/actions/moveOrCopyActionUtils.ts

index 22afb79c4d6f4d72b4b1b8dd42a6efb155aeb2d4..9a9f8ba805f7bc200dd84c98f0f6ae8642b7c9a9 100644 (file)
@@ -22,6 +22,7 @@
 import '@nextcloud/dialogs/style.css'
 import type { Folder, Node, View } from '@nextcloud/files'
 import type { IFilePickerButton } from '@nextcloud/dialogs'
+import type { MoveCopyResult } from './moveOrCopyActionUtils'
 
 // eslint-disable-next-line n/no-extraneous-import
 import { AxiosError } from 'axios'
@@ -92,7 +93,6 @@ export const handleCopyMoveNodeTo = async (node: Node, destination: Folder, meth
 
        const relativePath = join(destination.path, node.basename)
        const destinationUrl = generateRemoteUrl(`dav/files/${getCurrentUser()?.uid}${relativePath}`)
-       logger.debug(`${method} ${node.basename} to ${destinationUrl}`)
 
        // Set loading state
        Vue.set(node, 'status', NodeStatus.LOADING)
@@ -140,33 +140,37 @@ export const handleCopyMoveNodeTo = async (node: Node, destination: Folder, meth
  * Open a file picker for the given action
  * @param {MoveCopyAction} action The action to open the file picker for
  * @param {string} dir The directory to start the file picker in
- * @param {Node} node The node to move/copy
- * @return {Promise<boolean>} A promise that resolves to true if the action was successful
+ * @param {Node[]} nodes The nodes to move/copy
+ * @return {Promise<MoveCopyResult>} The picked destination
  */
-const openFilePickerForAction = async (action: MoveCopyAction, dir = '/', node: Node): Promise<boolean> => {
+const openFilePickerForAction = async (action: MoveCopyAction, dir = '/', nodes: Node[]): Promise<MoveCopyResult> => {
+       const fileIDs = nodes.map(node => node.fileid).filter(Boolean)
        const filePicker = getFilePickerBuilder(t('files', 'Chose destination'))
                .allowDirectories(true)
                .setFilter((n: Node) => {
                        // We only want to show folders that we can create nodes in
                        return (n.permissions & Permission.CREATE) !== 0
-                               // We don't want to show the current node in the file picker
-                               && node.fileid !== n.fileid
+                               // We don't want to show the current nodes in the file picker
+                               && !fileIDs.includes(n.fileid)
                })
                .setMimeTypeFilter([])
                .setMultiSelect(false)
                .startAt(dir)
 
        return new Promise((resolve, reject) => {
-               filePicker.setButtonFactory((nodes: Node[], path: string) => {
+               filePicker.setButtonFactory((_selection, path: string) => {
                        const buttons: IFilePickerButton[] = []
                        const target = basename(path)
 
-                       if (node.dirname === path) {
+                       const dirnames = nodes.map(node => node.dirname)
+                       const paths = nodes.map(node => node.path)
+
+                       if (dirnames.includes(path)) {
                                // This file/folder is already in that directory
                                return buttons
                        }
 
-                       if (node.path === path) {
+                       if (paths.includes(path)) {
                                // You cannot move a file/folder onto itself
                                return buttons
                        }
@@ -177,12 +181,10 @@ const openFilePickerForAction = async (action: MoveCopyAction, dir = '/', node:
                                        type: 'primary',
                                        icon: CopyIconSvg,
                                        async callback(destination: Node[]) {
-                                               try {
-                                                       await handleCopyMoveNodeTo(node, destination[0], MoveCopyAction.COPY)
-                                                       resolve(true)
-                                               } catch (error) {
-                                                       reject(error)
-                                               }
+                                               resolve({
+                                                       destination: destination[0] as Folder,
+                                                       action: MoveCopyAction.COPY,
+                                               } as MoveCopyResult)
                                        },
                                })
                        }
@@ -193,13 +195,10 @@ const openFilePickerForAction = async (action: MoveCopyAction, dir = '/', node:
                                        type: action === MoveCopyAction.MOVE ? 'primary' : 'secondary',
                                        icon: FolderMoveSvg,
                                        async callback(destination: Node[]) {
-                                               try {
-                                                       await handleCopyMoveNodeTo(node, destination[0], MoveCopyAction.MOVE)
-                                                       resolve(true)
-                                               } catch (error) {
-                                                       console.warn('got error', error)
-                                                       reject(error)
-                                               }
+                                               resolve({
+                                                       destination: destination[0] as Folder,
+                                                       action: MoveCopyAction.MOVE,
+                                               } as MoveCopyResult)
                                        },
                                })
                        }
@@ -237,8 +236,9 @@ export const action = new FileAction({
 
        async exec(node: Node, view: View, dir: string) {
                const action = getActionForNodes([node])
+               const result = await openFilePickerForAction(action, dir, [node])
                try {
-                       await openFilePickerForAction(action, dir, node)
+                       await handleCopyMoveNodeTo(node, result.destination, result.action)
                        return true
                } catch (error) {
                        if (error instanceof Error && !!error.message) {
@@ -250,5 +250,24 @@ export const action = new FileAction({
                }
        },
 
+       async execBatch(nodes: Node[], view: View, dir: string) {
+               const action = getActionForNodes(nodes)
+               const result = await openFilePickerForAction(action, dir, nodes)
+               const promises = nodes.map(async node => {
+                       try {
+                               await handleCopyMoveNodeTo(node, result.destination, result.action)
+                               return true
+                       } catch (error) {
+                               logger.error(`Failed to ${result.action} node`, { node, error })
+                               return false
+                       }
+               })
+
+               // We need to keep the selection on error!
+               // So we do not return null, and for batch action
+               // we let the front handle the error.
+               return await Promise.all(promises)
+       },
+
        order: 15,
 })
index 82aaa02f9ed41cdd4b22c28abea57b73eb2deafc..01614b14d8ac4758ba6cf3d0a5e736169f0a159f 100644 (file)
@@ -22,7 +22,7 @@
 
 import '@nextcloud/dialogs/style.css'
 
-import type { Node } from '@nextcloud/files'
+import type { Folder, Node } from '@nextcloud/files'
 import { Permission } from '@nextcloud/files'
 import PQueue from 'p-queue'
 
@@ -51,6 +51,11 @@ export enum MoveCopyAction {
        MOVE_OR_COPY = 'move-or-copy',
 }
 
+export type MoveCopyResult = {
+       destination: Folder
+       action: MoveCopyAction.COPY | MoveCopyAction.MOVE
+}
+
 export const canMove = (nodes: Node[]) => {
        const minPermission = nodes.reduce((min, node) => Math.min(min, node.permissions), Permission.ALL)
        return (minPermission & Permission.UPDATE) !== 0