From bd0c7f22069d1a2e4b2a2d962becaf6b9e942414 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 7 Jul 2025 19:06:36 +0200 Subject: refactor: store media relative to project, add storage for timeline data, and other things --- .../components/editor/media-panel/views/media.tsx | 16 +++++- apps/web/src/components/editor/timeline-track.tsx | 61 +++++++++++++++------- apps/web/src/components/editor/timeline.tsx | 9 +++- 3 files changed, 63 insertions(+), 23 deletions(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/media-panel/views/media.tsx b/apps/web/src/components/editor/media-panel/views/media.tsx index f975496e..f7da684d 100644 --- a/apps/web/src/components/editor/media-panel/views/media.tsx +++ b/apps/web/src/components/editor/media-panel/views/media.tsx @@ -24,9 +24,11 @@ import { SelectValue, } from "@/components/ui/select"; import { DraggableMediaItem } from "@/components/ui/draggable-item"; +import { useProjectStore } from "@/stores/project-store"; export function MediaView() { const { mediaItems, addMediaItem, removeMediaItem } = useMediaStore(); + const { activeProject } = useProjectStore(); const fileInputRef = useRef(null); const [isProcessing, setIsProcessing] = useState(false); const [progress, setProgress] = useState(0); @@ -35,6 +37,11 @@ export function MediaView() { const processFiles = async (files: FileList | File[]) => { if (!files || files.length === 0) return; + if (!activeProject) { + toast.error("No active project"); + return; + } + setIsProcessing(true); setProgress(0); try { @@ -44,7 +51,7 @@ export function MediaView() { ); // Add each processed media item to the store for (const item of processedItems) { - await addMediaItem(item); + await addMediaItem(activeProject.id, item); } } catch (error) { // Show error toast if processing fails @@ -73,6 +80,11 @@ export function MediaView() { // Remove a media item from the store e.stopPropagation(); + if (!activeProject) { + toast.error("No active project"); + return; + } + // Remove elements automatically when delete media const { tracks, removeTrack } = useTimelineStore.getState(); tracks.forEach((track) => { @@ -92,7 +104,7 @@ export function MediaView() { removeTrack(track.id); } }); - await removeMediaItem(id); + await removeMediaItem(activeProject.id, id); }; const formatDuration = (duration: number) => { diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline-track.tsx index eb58e1ee..2751fa5a 100644 --- a/apps/web/src/components/editor/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline-track.tsx @@ -13,11 +13,12 @@ import { ContextMenuSeparator, ContextMenuTrigger, } from "../ui/context-menu"; -import { +import { TimelineTrack, sortTracksByOrder, ensureMainTrack, - getMainTrack + getMainTrack, + canElementGoOnTrack, } from "@/types/timeline"; import { usePlaybackStore } from "@/stores/playback-store"; import type { @@ -572,7 +573,9 @@ export function TimelineTrackContent({ // dropPosition === "on" but track is not text type // Insert above main track if main track exists, otherwise at top if (mainTrack) { - const mainTrackIndex = tracks.findIndex(t => t.id === mainTrack.id); + const mainTrackIndex = tracks.findIndex( + (t) => t.id === mainTrack.id + ); insertIndex = mainTrackIndex; } else { insertIndex = 0; // Top of timeline @@ -648,9 +651,11 @@ export function TimelineTrackContent({ const isVideoOrImage = dragData.type === "video" || dragData.type === "image"; const isAudio = dragData.type === "audio"; - const isCompatible = - (track.type === "media" && isVideoOrImage) || - (track.type === "audio" && isAudio); + const isCompatible = isVideoOrImage + ? canElementGoOnTrack("media", track.type) + : isAudio + ? canElementGoOnTrack("media", track.type) + : false; let targetTrack = tracks.find((t) => t.id === targetTrackId); @@ -662,7 +667,7 @@ export function TimelineTrackContent({ if (isVideoOrImage) { // For video/image, check if we need a main track or additional media track const mainTrack = getMainTrack(tracks); - + if (!mainTrack) { // No main track exists, create it const updatedTracks = ensureMainTrack(tracks); @@ -672,22 +677,32 @@ export function TimelineTrackContent({ targetTrack = newMainTrack; } else { // Main track was created but somehow has elements, create new media track - const mainTrackIndex = updatedTracks.findIndex(t => t.id === newMainTrack?.id); + const mainTrackIndex = updatedTracks.findIndex( + (t) => t.id === newMainTrack?.id + ); targetTrackId = insertTrackAt("media", mainTrackIndex); - const updatedTracksAfterInsert = useTimelineStore.getState().tracks; - const newTargetTrack = updatedTracksAfterInsert.find(t => t.id === targetTrackId); + const updatedTracksAfterInsert = + useTimelineStore.getState().tracks; + const newTargetTrack = updatedTracksAfterInsert.find( + (t) => t.id === targetTrackId + ); if (!newTargetTrack) return; targetTrack = newTargetTrack; } - } else if (mainTrack.elements.length === 0 && dropPosition === "on") { + } else if ( + mainTrack.elements.length === 0 && + dropPosition === "on" + ) { // Main track exists and is empty, use it targetTrackId = mainTrack.id; targetTrack = mainTrack; } else { // Create new media track above main track - const mainTrackIndex = tracks.findIndex(t => t.id === mainTrack.id); + const mainTrackIndex = tracks.findIndex( + (t) => t.id === mainTrack.id + ); let insertIndex: number; - + if (dropPosition === "above") { insertIndex = currentTrackIndex; } else if (dropPosition === "below") { @@ -696,10 +711,12 @@ export function TimelineTrackContent({ // Insert above main track insertIndex = mainTrackIndex; } - + targetTrackId = insertTrackAt("media", insertIndex); const updatedTracks = useTimelineStore.getState().tracks; - const newTargetTrack = updatedTracks.find(t => t.id === targetTrackId); + const newTargetTrack = updatedTracks.find( + (t) => t.id === targetTrackId + ); if (!newTargetTrack) return; targetTrack = newTargetTrack; } @@ -707,7 +724,7 @@ export function TimelineTrackContent({ // Audio tracks go at the bottom const mainTrack = getMainTrack(tracks); let insertIndex: number; - + if (dropPosition === "above") { insertIndex = currentTrackIndex; } else if (dropPosition === "below") { @@ -715,16 +732,20 @@ export function TimelineTrackContent({ } else { // Insert after main track (bottom area) if (mainTrack) { - const mainTrackIndex = tracks.findIndex(t => t.id === mainTrack.id); + const mainTrackIndex = tracks.findIndex( + (t) => t.id === mainTrack.id + ); insertIndex = mainTrackIndex + 1; } else { insertIndex = tracks.length; // Bottom of timeline } } - + targetTrackId = insertTrackAt("audio", insertIndex); const updatedTracks = useTimelineStore.getState().tracks; - const newTargetTrack = updatedTracks.find(t => t.id === targetTrackId); + const newTargetTrack = updatedTracks.find( + (t) => t.id === targetTrackId + ); if (!newTargetTrack) return; targetTrack = newTargetTrack; } @@ -805,7 +826,7 @@ export function TimelineTrackContent({ ? wouldOverlap ? "Cannot drop - would overlap" : "Drop element here" - : "Drop media here"} + : ""} ) : ( <> diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx index 283f6b04..f98ab5ec 100644 --- a/apps/web/src/components/editor/timeline.tsx +++ b/apps/web/src/components/editor/timeline.tsx @@ -31,6 +31,7 @@ import { import { useTimelineStore } from "@/stores/timeline-store"; import { useMediaStore } from "@/stores/media-store"; import { usePlaybackStore } from "@/stores/playback-store"; +import { useProjectStore } from "@/stores/project-store"; import { processMediaFiles } from "@/lib/media-processing"; import { toast } from "sonner"; import { useState, useRef, useEffect, useCallback } from "react"; @@ -65,6 +66,7 @@ export function Timeline() { redo, } = useTimelineStore(); const { mediaItems, addMediaItem } = useMediaStore(); + const { activeProject } = useProjectStore(); const { currentTime, duration, @@ -377,6 +379,11 @@ export function Timeline() { } } else if (e.dataTransfer.files?.length > 0) { // Handle file drops by creating new tracks + if (!activeProject) { + toast.error("No active project"); + return; + } + setIsProcessing(true); setProgress(0); try { @@ -385,7 +392,7 @@ export function Timeline() { (p) => setProgress(p) ); for (const processedItem of processedItems) { - await addMediaItem(processedItem); + await addMediaItem(activeProject.id, processedItem); const currentMediaItems = useMediaStore.getState().mediaItems; const addedItem = currentMediaItems.find( (item) => -- cgit v1.3.1