aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2024-03-17 02:04:27 +0100
committerFerdinand Thiessen <opensource@fthiessen.de>2024-03-18 13:05:08 +0100
commit3f3955e04bcc749f3e6fdc7454a1cf3cbc83db32 (patch)
tree4575555d12c017cd4988071c0de8924306c164d8
parent3af954fcc876a7c35b2c71b138fc02c37fcf83c1 (diff)
downloadnextcloud-server-3f3955e04bcc749f3e6fdc7454a1cf3cbc83db32.tar.gz
nextcloud-server-3f3955e04bcc749f3e6fdc7454a1cf3cbc83db32.zip
fix(files): Adjust files drop to work with Blink engine (chrom(ium), edge)
The datatransfer items list is cleared on Blink after the first access to an inner prop due to async handling and GC. Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
-rw-r--r--apps/files/src/components/DragAndDropNotice.vue1
-rw-r--r--apps/files/src/services/DropService.ts30
-rw-r--r--cypress/e2e/files/drag-n-drop.cy.ts62
3 files changed, 81 insertions, 12 deletions
diff --git a/apps/files/src/components/DragAndDropNotice.vue b/apps/files/src/components/DragAndDropNotice.vue
index a9c1d8e99ad..f9b830ca755 100644
--- a/apps/files/src/components/DragAndDropNotice.vue
+++ b/apps/files/src/components/DragAndDropNotice.vue
@@ -22,6 +22,7 @@
-->
<template>
<div v-show="dragover"
+ data-cy-files-drag-drop-area
class="files-list__drag-drop-notice"
@drop="onDrop">
<div class="files-list__drag-drop-notice-wrapper">
diff --git a/apps/files/src/services/DropService.ts b/apps/files/src/services/DropService.ts
index d1e8dd9ed5a..b3371f44337 100644
--- a/apps/files/src/services/DropService.ts
+++ b/apps/files/src/services/DropService.ts
@@ -36,21 +36,27 @@ export const handleDrop = async (data: DataTransfer): Promise<Upload[]> => {
// TODO: Maybe handle `getAsFileSystemHandle()` in the future
const uploads = [] as Upload[]
- for (const item of data.items) {
- if (item.kind !== 'file') {
- logger.debug('Skipping dropped item', { kind: item.kind, type: item.type })
- continue
- }
-
- // MDN recommends to try both, as it might be renamed in the future
- const entry = (item as unknown as { getAsEntry?: () => FileSystemEntry|undefined})?.getAsEntry?.() ?? item.webkitGetAsEntry()
-
+ // we need to cache the entries to prevent Blink engine bug that clears the list (`data.items`) after first access props of one of the entries
+ const entries = [...data.items]
+ .filter((item) => {
+ if (item.kind !== 'file') {
+ logger.debug('Skipping dropped item', { kind: item.kind, type: item.type })
+ return false
+ }
+ return true
+ })
+ .map((item) => {
+ // MDN recommends to try both, as it might be renamed in the future
+ return (item as unknown as { getAsEntry?: () => FileSystemEntry|undefined})?.getAsEntry?.() ?? item.webkitGetAsEntry() ?? item
+ })
+
+ for (const entry of entries) {
// Handle browser issues if Filesystem API is not available. Fallback to File API
- if (entry === null) {
+ if (entry instanceof DataTransferItem) {
logger.debug('Could not get FilesystemEntry of item, falling back to file')
- const file = item.getAsFile()
+ const file = entry.getAsFile()
if (file === null) {
- logger.warn('Could not process DataTransferItem', { type: item.type, kind: item.kind })
+ logger.warn('Could not process DataTransferItem', { type: entry.type, kind: entry.kind })
showError(t('files', 'One of the dropped files could not be processed'))
} else {
uploads.push(await handleFileUpload(file))
diff --git a/cypress/e2e/files/drag-n-drop.cy.ts b/cypress/e2e/files/drag-n-drop.cy.ts
new file mode 100644
index 00000000000..9ede2536f67
--- /dev/null
+++ b/cypress/e2e/files/drag-n-drop.cy.ts
@@ -0,0 +1,62 @@
+import { getRowForFile } from './FilesUtils.ts'
+
+describe('files: Drag and Drop', { testIsolation: true }, () => {
+ beforeEach(() => {
+ cy.createRandomUser().then((user) => {
+ cy.login(user)
+ })
+ cy.visit('/apps/files')
+ })
+
+ it('can drop a file', () => {
+ const dataTransfer = new DataTransfer()
+ dataTransfer.items.add(new File([], 'single-file.txt'))
+
+ cy.intercept('PUT', /\/remote.php\/dav\/files\//).as('uploadFile')
+
+ cy.get('[data-cy-files-drag-drop-area]').should('not.be.visible')
+ // Trigger the drop notice
+ cy.get('main.app-content').trigger('dragover', { dataTransfer })
+ cy.get('[data-cy-files-drag-drop-area]').should('be.visible')
+
+ // Upload drop a file
+ cy.get('[data-cy-files-drag-drop-area]').selectFile({
+ fileName: 'single-file.txt',
+ contents: ['hello '.repeat(1024)],
+ }, { action: 'drag-drop' })
+
+ cy.wait('@uploadFile')
+
+ getRowForFile('single-file.txt').should('be.visible')
+ getRowForFile('single-file.txt').find('[data-cy-files-list-row-size]').should('contain', '6 KB')
+ })
+
+ it('can drop multiple files', () => {
+ const dataTransfer = new DataTransfer()
+ dataTransfer.items.add(new File([], 'first.txt'))
+ dataTransfer.items.add(new File([], 'second.txt'))
+
+ cy.intercept('PUT', /\/remote.php\/dav\/files\//).as('uploadFile')
+
+ // Trigger the drop notice
+ cy.get('main.app-content').trigger('dragover', { dataTransfer })
+ cy.get('[data-cy-files-drag-drop-area]').should('be.visible')
+
+ // Upload drop a file
+ cy.get('[data-cy-files-drag-drop-area]').selectFile([
+ {
+ fileName: 'first.txt',
+ contents: ['Hello'],
+ },
+ {
+ fileName: 'second.txt',
+ contents: ['World'],
+ },
+ ], { action: 'drag-drop' })
+
+ cy.wait('@uploadFile')
+
+ getRowForFile('first.txt').should('be.visible')
+ getRowForFile('second.txt').should('be.visible')
+ })
+})