diff --git a/apps/files/src/components/FileEntryMixin.ts b/apps/files/src/components/FileEntryMixin.ts index 8fc1aa5539990..868e8789bf7be 100644 --- a/apps/files/src/components/FileEntryMixin.ts +++ b/apps/files/src/components/FileEntryMixin.ts @@ -7,17 +7,18 @@ import type { IFileAction } from '@nextcloud/files' import type { PropType } from 'vue' import type { FileSource } from '../types.ts' -import { showError } from '@nextcloud/dialogs' +import { openConflictPicker } from '@nextcloud/dialogs' import { FileType, Folder, File as NcFile, Node, NodeStatus, Permission } from '@nextcloud/files' import { t } from '@nextcloud/l10n' import { generateUrl } from '@nextcloud/router' import { isPublicShare } from '@nextcloud/sharing/public' +import { getConflicts, getUploader } from '@nextcloud/upload' import { vOnClickOutside } from '@vueuse/components' import { extname } from 'path' import Vue, { computed, defineComponent } from 'vue' import { action as sidebarAction } from '../actions/sidebarAction.ts' import logger from '../logger.ts' -import { dataTransferToFileTree, onDropExternalFiles, onDropInternalFiles } from '../services/DropService.ts' +import { onDropInternalFiles } from '../services/DropService.ts' import { getDragAndDropPreview } from '../utils/dragUtils.ts' import { hashCode } from '../utils/hashUtils.ts' import { isDownloadable } from '../utils/permissions.ts' @@ -476,42 +477,69 @@ export default defineComponent({ event.preventDefault() event.stopPropagation() - // Caching the selection - const selection = this.draggingFiles - const items = [...event.dataTransfer?.items || []] as DataTransferItem[] - - // We need to process the dataTransfer ASAP before the - // browser clears it. This is why we cache the items too. - const fileTree = await dataTransferToFileTree(items) - - // We might not have the target directory fetched yet - const contents = await this.activeView?.getContents(this.source.path) - const folder = contents?.folder - if (!folder) { - showError(this.t('files', 'Target folder does not exist any more')) - return - } - // If another button is pressed, cancel it. This // allows cancelling the drag with the right click. if (!this.canDrop || event.button) { return } + // Caching the selection + const selection = this.draggingFiles + const items = Array.from(event.dataTransfer?.items || []) + + if (selection.length === 0 && items.some((item) => item.kind === 'file')) { + const uploader = getUploader() + await uploader.batchUpload( + this.source.path, + items.filter((item) => item.kind === 'file') + .map((item) => 'webkitGetAsEntry' in item ? item.webkitGetAsEntry() : item.getAsFile()) + .filter(Boolean) as (FileSystemEntry | File)[], + async (nodes, path) => { + try { + const { contents, folder } = await this.activeView!.getContents(path) + const conflicts = getConflicts(nodes, contents) + if (conflicts.length === 0) { + return nodes + } + + const result = await openConflictPicker( + folder.displayname, + conflicts, + (contents as Node[]).filter((node) => conflicts.some((conflict) => conflict.name === node.basename)), + { + recursive: true, + }, + ) + if (result === null) { + return false + } + return [ + ...nodes.filter((node) => !conflicts.some((conflict) => conflict.name === node.name)), + ...result.selected, + ...result.renamed, + ] + } catch { + return nodes + } + }, + ) + this.dragover = false + return + } + + // We might not have the target directory fetched yet + const cachedContents = this.filesStore.getNodesByPath(this.activeView.id, this.source.path) + const contents = cachedContents.length === 0 + ? (await this.activeView!.getContents(this.source.path)).contents + : cachedContents + const isCopy = event.ctrlKey this.dragover = false - logger.debug('Dropped', { event, folder, selection, fileTree }) - - // Check whether we're uploading files - if (selection.length === 0 && fileTree.contents.length > 0) { - await onDropExternalFiles(fileTree, folder, contents.contents) - return - } + logger.debug('Dropped', { event, folder: this.source, selection, fileTree }) - // Else we're moving/copying files const nodes = selection.map((source) => this.filesStore.getNode(source)) as Node[] - await onDropInternalFiles(nodes, folder, contents.contents, isCopy) + await onDropInternalFiles(nodes, this.source, contents, isCopy) // Reset selection after we dropped the files // if the dropped files are within the selection