feat(studio): drag-drop assetfile/folder and asset import anywhere in the studio#155
feat(studio): drag-drop assetfile/folder and asset import anywhere in the studio#155miguel-heygen merged 6 commits intomainfrom
Conversation
bdee705 to
a1a91bc
Compare
c68f12e to
a6b2023
Compare
444fd65 to
719fa24
Compare
Add /api/projects/:id/upload endpoint for multipart file uploads with automatic dedup naming. Wire onImportFiles callback from Assets tab through LeftSidebar to App. Add global drag-drop overlay so media files can be dropped anywhere in the studio — not just the Assets panel. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The AssetsTab drop handler and the global drop handler both fired, causing two uploads. Now the global handler checks e.defaultPrevented to skip if a child already handled the drop. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
External files dropped onto the file tree or any folder are imported via the upload API, same as the Assets tab and global drop zone. Internal drags (file-to-folder moves) still work as before. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
d8297e1 to
ecb590d
Compare
719fa24 to
6d4d6d3
Compare
- [Security] Add 500MB per-file size limit on uploads to prevent OOM - [Security] Strip path separators from uploaded filenames - [Bug] Fix dotfile collision naming (.gitignore → correct extension split) - [Bug] Only show global drop overlay for external file drags, not tree reorders - [UX] Log upload errors and report skipped files to console - [Safety] Cap collision counter at 10000 iterations Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6d4d6d3 to
0c0adba
Compare
jrusso1020
left a comment
There was a problem hiding this comment.
Nice feature — clean scope, good security hygiene with isSafePath and filename sanitization. A few things to address:
Must fix:
ensureDir(finalPath)is called with a file path — this will create a directory named after the file and thenwriteFileSyncwill throwEISDIR. Likely breaks uploads at runtime.
Should fix:
- Drag overlay gets stuck due to fragile
onDragLeaveguard — use a drag enter counter instead. - No user-visible feedback for skipped/failed uploads (only
console.warn).
Worth considering:
- No body size limit before
formData()parses the entire payload into memory. - Dedup loop silently falls through at n=10000.
- Files always land in project root regardless of current directory context.
- Remove redundant ensureDir call, add bodyLimit middleware for early rejection - Skip file on dedup suffix exhaustion instead of silent fallthrough - Replace fragile currentTarget===target drag guard with counter ref pattern - Surface skipped/failed uploads via toast instead of console-only - Support ?dir= param for uploading into subdirectories Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add draggable + onDragStart to TreeFolder so folders can be moved - Root drop zone now handles internal moves (files/folders to root) - Visual drag-over highlight on root area when dragging items over it Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
jrusso1020
left a comment
There was a problem hiding this comment.
All original feedback addressed thoroughly — nice work.
ensureDirbug → removed, subdirectory support added via?dir=param ✓- Drag overlay → counter pattern ✓
- Dedup loop → skips at boundary ✓
- Body size limit →
bodyLimitmiddleware ✓ - User feedback → toast notifications ✓
- Root-only uploads → folder-aware drops ✓
Two non-blocking nits left (possible missing join import, toast timer cleanup). LGTM.
| writeFileSync(finalPath, buffer); | ||
| uploaded.push(subDir ? join(subDir, finalName) : finalName); | ||
| } | ||
|
|
There was a problem hiding this comment.
Nit: join is used here but I don't see it added to the node:path import in the diff. Can you verify it's imported? TypeScript should catch this at build time, but worth double-checking.
| const showUploadToast = useCallback((msg: string) => { | ||
| setUploadToast(msg); | ||
| setTimeout(() => setUploadToast(null), 4000); | ||
| }, []); |
There was a problem hiding this comment.
Very minor: setTimeout without cleanup — if the component unmounts before the 4s fires, React will warn about setting state on an unmounted component. Not a real issue for the studio shell (it stays mounted), but if you want to be tidy:
const toastTimerRef = useRef<ReturnType<typeof setTimeout>>();
const showUploadToast = useCallback((msg: string) => {
clearTimeout(toastTimerRef.current);
setUploadToast(msg);
toastTimerRef.current = setTimeout(() => setUploadToast(null), 4000);
}, []);This also gives you the bonus of resetting the timer if a second toast fires before the first clears. Non-blocking.

Summary
/api/projects/:id/uploadendpoint for multipart file uploads with automatic dedup namingonImportFilesfrom Assets tab "Import media" button through to the upload API(2),(3)suffixes instead of overwriting?dir=param — dropping on a folder imports therebodyLimitmiddleware for early rejection of oversized payloadsAddresses feedback: "Wish I could upload/drag-drop assets directly in the Studio (music, images, video) like a CapCut media panel"
Test plan
(2)suffix