diff options
Diffstat (limited to 'apps/web/src')
| -rw-r--r-- | apps/web/src/components/editor/media-panel.tsx | 26 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline.tsx | 53 | ||||
| -rw-r--r-- | apps/web/src/lib/media-processing.ts | 15 |
3 files changed, 55 insertions, 39 deletions
diff --git a/apps/web/src/components/editor/media-panel.tsx b/apps/web/src/components/editor/media-panel.tsx index f2b3f657..cdd67a86 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<HTMLInputElement>(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 ? ( <> <Upload className="h-4 w-4 animate-spin" /> - <span className="hidden md:inline ml-2">Processing...</span> + <span className="hidden md:inline ml-2">{progress}%</span> </> ) : ( <> <Plus className="h-4 w-4" /> - <span - className="hidden sm:inline ml-2" - aria-label="Add file" - > + <span className="hidden sm:inline ml-2" aria-label="Add file"> Add </span> </> diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx index c1b8f3e8..1ecbe545 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<HTMLDivElement>(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() { <div className="w-px h-6 bg-border mx-1" />
{/* Time Display */}
- <div className="text-xs text-muted-foreground font-mono px-2"
- style={{ minWidth: '18ch', textAlign: 'center' }}
- >
- {currentTime.toFixed(1)}s / {duration.toFixed(1)}s
+ <div
+ className="text-xs text-muted-foreground font-mono px-2"
+ style={{ minWidth: "18ch", textAlign: "center" }}
+ >
+ {currentTime.toFixed(1)}s / {duration.toFixed(1)}s
</div>
{/* Test Clip Button - for debugging */}
@@ -946,14 +953,12 @@ export function Timeline() { </>
)}
{isDragOver && (
- <div
- className="absolute left-0 right-0 border-2 border-dashed border-accent flex items-center justify-center text-muted-foreground"
- style={{
- top: `${tracks.length * 60}px`,
- height: "60px",
- }}
- >
- <div>Drop media here to add a new track</div>
+ <div className="absolute inset-0 z-20 flex items-center justify-center pointer-events-none backdrop-blur-lg">
+ <div>
+ {isProcessing
+ ? `Processing ${progress}%`
+ : "Drop media here to add to timeline"}
+ </div>
</div>
)}
</div>
@@ -1630,18 +1635,18 @@ function TimelineTrackContent({ }
if (mediaItem.type === "audio") {
- return (
- <div className="w-full h-full flex items-center gap-2">
- <div className="flex-1 min-w-0">
- <AudioWaveform
- audioUrl={mediaItem.url}
- height={24}
- className="w-full"
- />
+ return (
+ <div className="w-full h-full flex items-center gap-2">
+ <div className="flex-1 min-w-0">
+ <AudioWaveform
+ audioUrl={mediaItem.url}
+ height={24}
+ className="w-full"
+ />
+ </div>
</div>
- </div>
- );
- }
+ );
+ }
// 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 d241f411..daf22b34 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<MediaItem, "id"> {}
export async function processMediaFiles(
- files: FileList | File[]
+ files: FileList | File[],
+ onProgress?: (progress: number) => void
): Promise<ProcessedMediaItem[]> {
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}`);
|
