@@ -25,14 +25,14 @@ import type { IFilePickerButton } from '@nextcloud/dialogs'
2525import type { FileStat , ResponseDataDetailed } from 'webdav'
2626import type { MoveCopyResult } from './moveOrCopyActionUtils'
2727
28- // eslint-disable-next-line n/no-extraneous-import
29- import { AxiosError } from 'axios'
30- import { basename , join } from 'path'
28+ import { FilePickerClosed , getFilePickerBuilder , showError , showInfo } from '@nextcloud/dialogs'
3129import { emit } from '@nextcloud/event-bus'
32- import { FilePickerClosed , getFilePickerBuilder , showError } from '@nextcloud/dialogs'
3330import { FileAction , FileType , NodeStatus , davGetClient , davRootPath , davResultToNode , davGetDefaultPropfind , getUniqueName } from '@nextcloud/files'
3431import { translate as t } from '@nextcloud/l10n'
3532import { openConflictPicker , hasConflict } from '@nextcloud/upload'
33+ // eslint-disable-next-line n/no-extraneous-import
34+ import { AxiosError } from 'axios'
35+ import { basename , join } from 'path'
3636import Vue from 'vue'
3737
3838import CopyIconSvg from '@mdi/svg/svg/folder-multiple.svg?raw'
@@ -106,7 +106,7 @@ export const handleCopyMoveNodeTo = async (node: Node, destination: Folder, meth
106106 if ( index === 1 ) {
107107 return t ( 'files' , '(copy)' ) // TRANSLATORS: Mark a file as a copy of another file
108108 }
109- return t ( 'files' , '(copy %n)' , undefined , index ) // TRANSLATORS: Meaning it is the n'th copy of a file
109+ return t ( 'files' , '(copy %n)' , undefined , index ) // TRANSLATORS: Meaning it is the nth copy of a file
110110 }
111111
112112 try {
@@ -186,12 +186,17 @@ export const handleCopyMoveNodeTo = async (node: Node, destination: Folder, meth
186186
187187/**
188188 * Open a file picker for the given action
189- * @param { MoveCopyAction } action The action to open the file picker for
190- * @param { string } dir The directory to start the file picker in
191- * @param { Node[] } nodes The nodes to move/copy
192- * @return { Promise<MoveCopyResult> } The picked destination
189+ * @param action The action to open the file picker for
190+ * @param dir The directory to start the file picker in
191+ * @param nodes The nodes to move/copy
192+ * @return The picked destination or false if cancelled by user
193193 */
194- const openFilePickerForAction = async ( action : MoveCopyAction , dir = '/' , nodes : Node [ ] ) : Promise < MoveCopyResult > => {
194+ async function openFilePickerForAction (
195+ action : MoveCopyAction ,
196+ dir = '/' ,
197+ nodes : Node [ ] ,
198+ ) : Promise < MoveCopyResult | false > {
199+ const { resolve, reject, promise } = Promise . withResolvers < MoveCopyResult | false > ( )
195200 const fileIDs = nodes . map ( node => node . fileid ) . filter ( Boolean )
196201 const filePicker = getFilePickerBuilder ( t ( 'files' , 'Choose destination' ) )
197202 . allowDirectories ( true )
@@ -202,9 +207,7 @@ const openFilePickerForAction = async (action: MoveCopyAction, dir = '/', nodes:
202207 . setMimeTypeFilter ( [ ] )
203208 . setMultiSelect ( false )
204209 . startAt ( dir )
205-
206- return new Promise ( ( resolve , reject ) => {
207- filePicker . setButtonFactory ( ( selection : Node [ ] , path : string ) => {
210+ . setButtonFactory ( ( selection : Node [ ] , path : string ) => {
208211 const buttons : IFilePickerButton [ ] = [ ]
209212 const target = basename ( path )
210213
@@ -252,17 +255,19 @@ const openFilePickerForAction = async (action: MoveCopyAction, dir = '/', nodes:
252255
253256 return buttons
254257 } )
258+ . build ( )
255259
256- const picker = filePicker . build ( )
257- picker . pick ( ) . catch ( ( error ) => {
260+ filePicker . pick ( )
261+ . catch ( ( error : Error ) => {
258262 logger . debug ( error as Error )
259263 if ( error instanceof FilePickerClosed ) {
260- reject ( new Error ( t ( 'files' , 'Cancelled move or copy operation' ) ) )
264+ resolve ( false )
261265 } else {
262266 reject ( new Error ( t ( 'files' , 'Move or copy operation failed' ) ) )
263267 }
264268 } )
265- } )
269+
270+ return promise
266271}
267272
268273export const action = new FileAction ( {
@@ -295,6 +300,11 @@ export const action = new FileAction({
295300 logger . error ( e as Error )
296301 return false
297302 }
303+ if ( result === false ) {
304+ showInfo ( t ( 'files' , 'Cancelled move or copy of "{filename}".' , { filename : node . displayname } ) )
305+ return null
306+ }
307+
298308 try {
299309 await handleCopyMoveNodeTo ( node , result . destination , result . action )
300310 return true
@@ -311,6 +321,15 @@ export const action = new FileAction({
311321 async execBatch ( nodes : Node [ ] , view : View , dir : string ) {
312322 const action = getActionForNodes ( nodes )
313323 const result = await openFilePickerForAction ( action , dir , nodes )
324+ // Handle cancellation silently
325+ if ( result === false ) {
326+ showInfo ( nodes . length === 1
327+ ? t ( 'files' , 'Cancelled move or copy of "{filename}".' , { filename : nodes [ 0 ] . displayname } )
328+ : t ( 'files' , 'Cancelled move or copy operation' ) ,
329+ )
330+ return nodes . map ( ( ) => null )
331+ }
332+
314333 const promises = nodes . map ( async node => {
315334 try {
316335 await handleCopyMoveNodeTo ( node , result . destination , result . action )
0 commit comments