Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions src/modules/upload/Dropzone.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@ import { uploadFiles } from '@/modules/navigation/duck'
import DropzoneTeaser from '@/modules/upload/DropzoneTeaser'
import { useNewItemHighlightContext } from '@/modules/upload/NewItemHighlightProvider'

// DnD helpers for folder upload
const canHandleFolders = evt => {
if (!evt.dataTransfer) return false
const dt = evt.dataTransfer
return dt.items && dt.items.length && dt.items[0].webkitGetAsEntry != null
}

const canDrop = evt => {
const items = evt.dataTransfer.items
for (let i = 0; i < items.length; i += 1) {
Expand Down Expand Up @@ -55,7 +48,9 @@ export const Dropzone = ({
const onDrop = async (files, _, evt) => {
if (!canDrop(evt)) return

const filesToUpload = canHandleFolders(evt) ? evt.dataTransfer.items : files
// react-dropzone sets file.path on each File with the relative path.
// addToUploadQueue uses file.path to detect and handle folder uploads.
const filesToUpload = files
dispatch(
uploadFiles(
filesToUpload,
Expand Down
3 changes: 3 additions & 0 deletions src/modules/upload/DropzoneDnD.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ export const Dropzone = ({
canDrop: item => !disabled && canDropHelper(item),
drop(item) {
if (disabled) return
// react-dnd calls drop() synchronously during the native drop event,
// so DataTransferItemList and webkitGetAsEntry() are still valid here
// (unlike react-dropzone which processes files asynchronously).
const filesToUpload = canHandleFolders(item)
? item.dataTransfer.items
: item.files
Expand Down
27 changes: 22 additions & 5 deletions src/modules/upload/UploadQueue.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,28 @@ export const DumbUploadQueue = translate()(props => {
)
})

const mapStateToProps = state => ({
queue: getUploadQueue(state),
doneCount: getProcessed(state).length,
successCount: getSuccessful(state).length
})
const mapStateToProps = state => {
const rawQueue = getUploadQueue(state)

// Replace file.name with relativePath for display when available
const queue = rawQueue.map(item => {
if (!item.relativePath) return item
return {
...item,
file: {
name: item.relativePath,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might break icon resolution since we pass a path instead of a file name

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm interesting. I'll check that and add a test for that.

type: item.file?.type,
size: item.file?.size
}
}
})
Comment on lines +43 to +57
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mapStateToProps always creates a new queue array via rawQueue.map(...) even when no items have relativePath. This breaks referential equality and can trigger re-renders of the connected UploadQueue on any store update. Consider memoizing this transformation (e.g., reselect) or only mapping when at least one item needs a display override.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback


return {
queue,
doneCount: getProcessed(state).length,
successCount: getSuccessful(state).length
}
}
const mapDispatchToProps = dispatch => ({
purgeQueue: () => dispatch(purgeUploadQueue())
})
Expand Down
Loading
Loading