aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2024-08-02 23:40:10 +0200
committerGitHub <noreply@github.com>2024-08-02 23:40:10 +0200
commita9296ab5c25a8f34c48b407352a9760b9767ca9b (patch)
tree1cdf736878a76a7e1f5720aa54a563d40b1efd37 /apps
parent2c1883f152b883608e61e8f88f0eb1fd2283006b (diff)
parentba14e7b3f219507d3bbb740482f2f8908d7e6307 (diff)
downloadnextcloud-server-a9296ab5c25a8f34c48b407352a9760b9767ca9b.tar.gz
nextcloud-server-a9296ab5c25a8f34c48b407352a9760b9767ca9b.zip
Merge pull request #46971 from nextcloud/backport/46963/stable28
[stable28] fix(files): cancel move-copy action should not be handled as an error
Diffstat (limited to 'apps')
-rw-r--r--apps/files/src/actions/moveOrCopyAction.ts53
1 files changed, 36 insertions, 17 deletions
diff --git a/apps/files/src/actions/moveOrCopyAction.ts b/apps/files/src/actions/moveOrCopyAction.ts
index cd051916f32..74193fa0f41 100644
--- a/apps/files/src/actions/moveOrCopyAction.ts
+++ b/apps/files/src/actions/moveOrCopyAction.ts
@@ -25,14 +25,14 @@ import type { IFilePickerButton } from '@nextcloud/dialogs'
import type { FileStat, ResponseDataDetailed } from 'webdav'
import type { MoveCopyResult } from './moveOrCopyActionUtils'
-// eslint-disable-next-line n/no-extraneous-import
-import { AxiosError } from 'axios'
-import { basename, join } from 'path'
+import { FilePickerClosed, getFilePickerBuilder, showError, showInfo } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
-import { FilePickerClosed, getFilePickerBuilder, showError } from '@nextcloud/dialogs'
import { FileAction, FileType, NodeStatus, davGetClient, davRootPath, davResultToNode, davGetDefaultPropfind, getUniqueName } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { openConflictPicker, hasConflict } from '@nextcloud/upload'
+// eslint-disable-next-line n/no-extraneous-import
+import { AxiosError } from 'axios'
+import { basename, join } from 'path'
import Vue from 'vue'
import CopyIconSvg from '@mdi/svg/svg/folder-multiple.svg?raw'
@@ -106,7 +106,7 @@ export const handleCopyMoveNodeTo = async (node: Node, destination: Folder, meth
if (index === 1) {
return t('files', '(copy)') // TRANSLATORS: Mark a file as a copy of another file
}
- return t('files', '(copy %n)', undefined, index) // TRANSLATORS: Meaning it is the n'th copy of a file
+ return t('files', '(copy %n)', undefined, index) // TRANSLATORS: Meaning it is the nth copy of a file
}
try {
@@ -186,12 +186,17 @@ 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[]} nodes The nodes to move/copy
- * @return {Promise<MoveCopyResult>} The picked destination
+ * @param action The action to open the file picker for
+ * @param dir The directory to start the file picker in
+ * @param nodes The nodes to move/copy
+ * @return The picked destination or false if cancelled by user
*/
-const openFilePickerForAction = async (action: MoveCopyAction, dir = '/', nodes: Node[]): Promise<MoveCopyResult> => {
+async function openFilePickerForAction(
+ action: MoveCopyAction,
+ dir = '/',
+ nodes: Node[],
+): Promise<MoveCopyResult | false> {
+ const { resolve, reject, promise } = Promise.withResolvers<MoveCopyResult | false>()
const fileIDs = nodes.map(node => node.fileid).filter(Boolean)
const filePicker = getFilePickerBuilder(t('files', 'Choose destination'))
.allowDirectories(true)
@@ -202,9 +207,7 @@ const openFilePickerForAction = async (action: MoveCopyAction, dir = '/', nodes:
.setMimeTypeFilter([])
.setMultiSelect(false)
.startAt(dir)
-
- return new Promise((resolve, reject) => {
- filePicker.setButtonFactory((selection: Node[], path: string) => {
+ .setButtonFactory((selection: Node[], path: string) => {
const buttons: IFilePickerButton[] = []
const target = basename(path)
@@ -252,17 +255,19 @@ const openFilePickerForAction = async (action: MoveCopyAction, dir = '/', nodes:
return buttons
})
+ .build()
- const picker = filePicker.build()
- picker.pick().catch((error) => {
+ filePicker.pick()
+ .catch((error: Error) => {
logger.debug(error as Error)
if (error instanceof FilePickerClosed) {
- reject(new Error(t('files', 'Cancelled move or copy operation')))
+ resolve(false)
} else {
reject(new Error(t('files', 'Move or copy operation failed')))
}
})
- })
+
+ return promise
}
export const action = new FileAction({
@@ -295,6 +300,11 @@ export const action = new FileAction({
logger.error(e as Error)
return false
}
+ if (result === false) {
+ showInfo(t('files', 'Cancelled move or copy of "{filename}".', { filename: node.displayname }))
+ return null
+ }
+
try {
await handleCopyMoveNodeTo(node, result.destination, result.action)
return true
@@ -311,6 +321,15 @@ export const action = new FileAction({
async execBatch(nodes: Node[], view: View, dir: string) {
const action = getActionForNodes(nodes)
const result = await openFilePickerForAction(action, dir, nodes)
+ // Handle cancellation silently
+ 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'),
+ )
+ return nodes.map(() => null)
+ }
+
const promises = nodes.map(async node => {
try {
await handleCopyMoveNodeTo(node, result.destination, result.action)