diff --git a/apps/web/src/components/editor/media-panel.tsx b/apps/web/src/components/editor/media-panel.tsx index f2b3f65..cdd67a8 100644 --- a/apps/web/src/components/editor/media-panel.tsx +++ b/apps/web/src/components/editor/media-panel.tsx @@ -17,27 +17,28 @@ export function MediaPanel() { const { mediaItems, addMediaItem, removeMediaItem } = useMediaStore(); const fileInputRef = useRef(null); const [isProcessing, setIsProcessing] = useState(false); + const [progress, setProgress] = useState(0); const [searchQuery, setSearchQuery] = useState(""); const [mediaFilter, setMediaFilter] = useState("all"); const processFiles = async (files: FileList | File[]) => { - // If no files, do nothing - if (!files?.length) return; - + if (!files || files.length === 0) return; setIsProcessing(true); + setProgress(0); try { // Process files (extract metadata, generate thumbnails, etc.) - const items = await processMediaFiles(files); + const processedItems = await processMediaFiles(files, (p) => + setProgress(p) + ); // Add each processed media item to the store - items.forEach((item) => { - addMediaItem(item); - }); + processedItems.forEach((item) => addMediaItem(item)); } catch (error) { - // Show error if processing fails - console.error("File processing failed:", error); + // Show error toast if processing fails + console.error("Error processing files:", error); toast.error("Failed to process files"); } finally { setIsProcessing(false); + setProgress(0); } }; @@ -241,15 +242,12 @@ export function MediaPanel() { {isProcessing ? ( <> - Processing... + {progress}% ) : ( <> - + Add diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx index c1b8f3e..1ecbe54 100644 --- a/apps/web/src/components/editor/timeline.tsx +++ b/apps/web/src/components/editor/timeline.tsx @@ -69,6 +69,7 @@ export function Timeline() { } = usePlaybackStore(); const [isDragOver, setIsDragOver] = useState(false); const [isProcessing, setIsProcessing] = useState(false); + const [progress, setProgress] = useState(0); const [zoomLevel, setZoomLevel] = useState(1); const dragCounterRef = useRef(0); const timelineRef = useRef(null); @@ -334,8 +335,12 @@ export function Timeline() { } else if (e.dataTransfer.files?.length > 0) { // Handle file drops by creating new tracks setIsProcessing(true); + setProgress(0); try { - const processedItems = await processMediaFiles(e.dataTransfer.files); + const processedItems = await processMediaFiles( + e.dataTransfer.files, + (p) => setProgress(p) + ); for (const processedItem of processedItems) { addMediaItem(processedItem); const currentMediaItems = useMediaStore.getState().mediaItems; @@ -363,6 +368,7 @@ export function Timeline() { toast.error("Failed to process dropped files"); } finally { setIsProcessing(false); + setProgress(0); } } }; @@ -594,10 +600,11 @@ export function Timeline() {
{/* Time Display */} -
- {currentTime.toFixed(1)}s / {duration.toFixed(1)}s +
+ {currentTime.toFixed(1)}s / {duration.toFixed(1)}s
{/* Test Clip Button - for debugging */} @@ -946,14 +953,12 @@ export function Timeline() { )} {isDragOver && ( -
-
Drop media here to add a new track
+
+
+ {isProcessing + ? `Processing ${progress}%` + : "Drop media here to add to timeline"} +
)}
@@ -1630,18 +1635,18 @@ function TimelineTrackContent({ } if (mediaItem.type === "audio") { - return ( -
-
- + return ( +
+
+ +
-
- ); - } + ); + } // Fallback for videos without thumbnails return ( diff --git a/apps/web/src/lib/media-processing.ts b/apps/web/src/lib/media-processing.ts index d241f41..daf22b3 100644 --- a/apps/web/src/lib/media-processing.ts +++ b/apps/web/src/lib/media-processing.ts @@ -11,11 +11,15 @@ import { export interface ProcessedMediaItem extends Omit {} export async function processMediaFiles( - files: FileList | File[] + files: FileList | File[], + onProgress?: (progress: number) => void ): Promise { const fileArray = Array.from(files); const processedItems: ProcessedMediaItem[] = []; + const total = fileArray.length; + let completed = 0; + for (const file of fileArray) { const fileType = getFileType(file); @@ -57,6 +61,15 @@ export async function processMediaFiles( duration, aspectRatio, }); + + // Yield back to the event loop to keep the UI responsive + await new Promise((resolve) => setTimeout(resolve, 0)); + + completed += 1; + if (onProgress) { + const percent = Math.round((completed / total) * 100); + onProgress(percent); + } } catch (error) { console.error("Error processing file:", file.name, error); toast.error(`Failed to process ${file.name}`);