feat(studio): drag-drop assetfile/folder and asset import anywhere in the studio (#155)

## Summary

- Add `/api/projects/:id/upload` endpoint for multipart file uploads with automatic dedup naming
- Wire `onImportFiles` from Assets tab "Import media" button through to the upload API
- Add global drag-drop overlay — drop media files **anywhere** in the studio, not just the Assets panel
- Files that already exist get `(2)`, `(3)` suffixes instead of overwriting
- Support uploading into subdirectories via `?dir=` param — dropping on a folder imports there
- Make folders draggable in the file tree + support drop-to-root
- Add `bodyLimit` middleware for early rejection of oversized payloads
- Surface skipped/failed uploads via toast notification instead of console-only

Addresses feedback: _"Wish I could upload/drag-drop assets directly in the Studio (music, images, video) like a CapCut media panel"_

## Test plan

- [x] Open studio, drag an image/video/audio file onto any part of the UI
- [x] Verify the drop overlay appears with "Drop files to import" message
- [x] Drop the file — verify it appears in the Assets tab and file tree
- [x] Drop a file with the same name — verify it gets a `(2)` suffix
- [x] Click "Import media" button in Assets tab — verify file picker works
- [x] Import multiple files at once via drag-drop
- [x] Drop a file onto a nested folder in the file tree — verify it lands in that folder
- [x] Drag a folder in the file tree and drop it on another folder or root — verify it moves
- [x] Drop a file >500MB — verify toast notification appears
- [x] Verify drag overlay doesn't get stuck when dragging over nested UI elements
This commit is contained in:
Miguel Ángel
2026-04-01 00:21:34 +02:00
committed by GitHub
parent 7265d0adfd
commit 5f3488e996
4 changed files with 204 additions and 5 deletions
@@ -22,7 +22,7 @@ interface LeftSidebarProps {
assets: string[];
activeComposition: string | null;
onSelectComposition: (comp: string) => void;
onImportFiles?: (files: FileList) => void;
onImportFiles?: (files: FileList, dir?: string) => void;
fileTree?: string[];
editingFile?: { path: string; content: string | null } | null;
onSelectFile?: (path: string) => void;
@@ -156,6 +156,7 @@ export const LeftSidebar = memo(function LeftSidebar({
onRenameFile={onRenameFile}
onDuplicateFile={onDuplicateFile}
onMoveFile={onMoveFile}
onImportFiles={onImportFiles}
/>
</div>
)}