From d8f446580c2636e87aa39c00b1565406c56f9343 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 1 Sep 2025 22:15:32 +0200 Subject: feat: remove default background scene and add scene management --- apps/web/src/components/editor/preview-panel.tsx | 22 +- apps/web/src/components/editor/scenes-view.tsx | 262 ++++++++++++++++----- .../editor/timeline/timeline-cache-indicator.tsx | 14 +- .../editor/timeline/timeline-toolbar.tsx | 6 +- 4 files changed, 230 insertions(+), 74 deletions(-) (limited to 'apps/web/src/components/editor') diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 8b1abd81..36f6a2ba 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -14,6 +14,7 @@ import { cn } from "@/lib/utils"; import { formatTimeCode } from "@/lib/time"; import { EditableTimecode } from "@/components/ui/editable-timecode"; import { useFrameCache } from "@/hooks/use-frame-cache"; +import { useSceneStore } from "@/stores/scene-store"; import { DEFAULT_CANVAS_SIZE, DEFAULT_FPS, @@ -43,6 +44,7 @@ export function PreviewPanel() { const { currentTime, toggle, setCurrentTime } = usePlaybackStore(); const { isPlaying, volume, muted } = usePlaybackStore(); const { activeProject } = useProjectStore(); + const { currentScene } = useSceneStore(); const previewRef = useRef(null); const canvasRef = useRef(null); const { getCachedFrame, cacheFrame, invalidateCache, preRenderNearbyFrames } = @@ -219,11 +221,10 @@ export function PreviewPanel() { }; }, [dragState, previewDimensions, canvasSize, updateTextElement]); - // Invalidate cache when timeline changes + // Clear the frame cache when background settings change since they affect rendering useEffect(() => { invalidateCache(); }, [ - tracks, mediaFiles, activeProject?.backgroundColor, activeProject?.backgroundType, @@ -494,7 +495,8 @@ export function PreviewPanel() { currentTime, tracks, mediaFiles, - activeProject + activeProject, + currentScene?.id ); if (cachedFrame) { mainCtx.putImageData(cachedFrame, 0, 0); @@ -532,7 +534,9 @@ export function PreviewPanel() { }); return tempCtx.getImageData(0, 0, displayWidth, displayHeight); - } + }, + currentScene?.id, + 3 ); } else { // Small lookahead while playing @@ -567,6 +571,7 @@ export function PreviewPanel() { return tempCtx.getImageData(0, 0, displayWidth, displayHeight); }, + currentScene?.id, 1 ); } @@ -644,7 +649,14 @@ export function PreviewPanel() { displayWidth, displayHeight ); - cacheFrame(currentTime, imageData, tracks, mediaFiles, activeProject); + cacheFrame( + currentTime, + imageData, + tracks, + mediaFiles, + activeProject, + currentScene?.id + ); // Blit offscreen to visible canvas mainCtx.clearRect(0, 0, displayWidth, displayHeight); diff --git a/apps/web/src/components/editor/scenes-view.tsx b/apps/web/src/components/editor/scenes-view.tsx index f1df32e9..b5432b53 100644 --- a/apps/web/src/components/editor/scenes-view.tsx +++ b/apps/web/src/components/editor/scenes-view.tsx @@ -1,64 +1,198 @@ -"use client"; - -import { - Sheet, - SheetContent, - SheetDescription, - SheetHeader, - SheetTitle, - SheetTrigger, -} from "@/components/ui/sheet"; -import { Button } from "@/components/ui/button"; -import { useSceneStore } from "@/stores/scene-store"; -import { Check } from "lucide-react"; - -export function ScenesView({ children }: { children: React.ReactNode }) { - const { scenes, currentScene, switchToScene } = useSceneStore(); - - const handleSceneSwitch = async (sceneId: string) => { - try { - await switchToScene({ sceneId }); - } catch (error) { - console.error("Failed to switch scene:", error); - } - }; - - return ( - - {children} - - - Scenes - - Switch between scenes in your project - - -
- {scenes.length === 0 ? ( -
- No scenes available -
- ) : ( -
- {scenes.map((scene) => ( - - ))} -
- )} -
-
-
- ); -} +"use client"; + +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet"; +import { Button } from "@/components/ui/button"; +import { useSceneStore } from "@/stores/scene-store"; +import { Check, ListCheck, Trash2 } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { useState } from "react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, + DialogTrigger, +} from "@/components/ui/dialog"; + +export function ScenesView({ children }: { children: React.ReactNode }) { + const { scenes, currentScene, switchToScene, deleteScene } = useSceneStore(); + const [isSelectMode, setIsSelectMode] = useState(false); + const [selectedScenes, setSelectedScenes] = useState>(new Set()); + + const handleSceneSwitch = async (sceneId: string) => { + if (isSelectMode) { + toggleSceneSelection(sceneId); + return; + } + + try { + await switchToScene({ sceneId }); + } catch (error) { + console.error("Failed to switch scene:", error); + } + }; + + const toggleSceneSelection = (sceneId: string) => { + setSelectedScenes((prev) => { + const newSet = new Set(prev); + if (newSet.has(sceneId)) { + newSet.delete(sceneId); + } else { + newSet.add(sceneId); + } + return newSet; + }); + }; + + const handleSelectMode = () => { + setIsSelectMode(!isSelectMode); + setSelectedScenes(new Set()); + }; + + const handleDeleteSelected = async () => { + for (const sceneId of selectedScenes) { + const scene = scenes.find((s) => s.id === sceneId); + if (scene && !scene.isMain) { + try { + await deleteScene({ sceneId }); + } catch (error) { + console.error("Failed to delete scene:", error); + } + } + } + setSelectedScenes(new Set()); + setIsSelectMode(false); + }; + + return ( + + {children} + + + + {isSelectMode ? `Select scenes (${selectedScenes.size})` : "Scenes"} + + + {isSelectMode + ? "Select scenes to delete" + : "Switch between scenes in your project"} + + +
+
+ + {isSelectMode && ( + scenes.find((s) => s.id === id)?.isMain + )} + > + + + )} +
+ {scenes.length === 0 ? ( +
+ No scenes available +
+ ) : ( +
+ {scenes.map((scene) => ( + + ))} +
+ )} +
+
+
+ ); +} + +function DeleteDialog({ + count, + onDelete, + disabled, + children, +}: { + count: number; + onDelete: () => void; + disabled?: boolean; + children: React.ReactNode; +}) { + const [open, setOpen] = useState(false); + + const handleDelete = () => { + onDelete(); + setOpen(false); + }; + + return ( + + {children} + + + Delete Scenes + + Are you sure you want to delete {count} scene + {count === 1 ? "" : "s"}? This action cannot be undone. + + + + + + + + + ); +} diff --git a/apps/web/src/components/editor/timeline/timeline-cache-indicator.tsx b/apps/web/src/components/editor/timeline/timeline-cache-indicator.tsx index ef667d67..7def2985 100644 --- a/apps/web/src/components/editor/timeline/timeline-cache-indicator.tsx +++ b/apps/web/src/components/editor/timeline/timeline-cache-indicator.tsx @@ -5,6 +5,7 @@ import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; import { TimelineTrack } from "@/types/timeline"; import { MediaFile } from "@/types/media"; import { TProject } from "@/types/project"; +import { useSceneStore } from "@/stores/scene-store"; interface CacheSegment { startTime: number; @@ -22,7 +23,8 @@ interface TimelineCacheIndicatorProps { time: number, tracks: TimelineTrack[], mediaFiles: MediaFile[], - activeProject: TProject | null + activeProject: TProject | null, + sceneId?: string ) => "cached" | "not-cached"; } @@ -34,6 +36,8 @@ export function TimelineCacheIndicator({ activeProject, getRenderStatus, }: TimelineCacheIndicatorProps) { + const { currentScene } = useSceneStore(); + // Calculate cache segments by sampling the timeline const calculateCacheSegments = (): CacheSegment[] => { const segments: CacheSegment[] = []; @@ -49,7 +53,13 @@ export function TimelineCacheIndicator({ for (let i = 0; i <= totalSamples; i++) { const time = i / sampleRate; const cached = - getRenderStatus(time, tracks, mediaFiles, activeProject) === "cached"; + getRenderStatus( + time, + tracks, + mediaFiles, + activeProject, + currentScene?.id + ) === "cached"; if (!currentSegment) { // Start first segment diff --git a/apps/web/src/components/editor/timeline/timeline-toolbar.tsx b/apps/web/src/components/editor/timeline/timeline-toolbar.tsx index 7eeb84e4..79b5d5b2 100644 --- a/apps/web/src/components/editor/timeline/timeline-toolbar.tsx +++ b/apps/web/src/components/editor/timeline/timeline-toolbar.tsx @@ -67,7 +67,7 @@ export function TimelineToolbar({ } = useTimelineStore(); const { currentTime, duration, isPlaying, toggle, seek } = usePlaybackStore(); const { toggleBookmark, isBookmarked, activeProject } = useProjectStore(); - const { currentScene } = useSceneStore(); + const { scenes, currentScene } = useSceneStore(); const handleSplitSelected = () => { if (selectedElements.length === 0) return; @@ -359,11 +359,11 @@ export function TimelineToolbar({
- + {currentScene?.name || "No Scene"} - {}}> + {}}> -- cgit v1.3.1