diff options
Diffstat (limited to 'apps/web/src/components/editor/preview-panel.tsx')
| -rw-r--r-- | apps/web/src/components/editor/preview-panel.tsx | 129 |
1 files changed, 85 insertions, 44 deletions
diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 6cb06061..c6f4e159 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -20,6 +20,7 @@ import { Play, Pause, Expand, SkipBack, SkipForward } from "lucide-react"; import { useState, useRef, useEffect, useCallback } from "react"; import { cn } from "@/lib/utils"; import { formatTimeCode } from "@/lib/time"; +import { EditableTimecode } from "@/components/ui/editable-timecode"; import { FONT_CLASS_MAP } from "@/lib/font-config"; import { BackgroundSettings } from "../background-settings"; import { useProjectStore } from "@/stores/project-store"; @@ -233,6 +234,7 @@ export function PreviewPanel() { tracks.forEach((track) => { track.elements.forEach((element) => { + if (element.hidden) return; const elementStart = element.startTime; const elementEnd = element.startTime + @@ -357,20 +359,18 @@ export function PreviewPanel() { style={{ left: `${ 50 + - ( - (dragState.isDragging && dragState.elementId === element.id - ? dragState.currentX - : element.x) / canvasSize.width - ) * + ((dragState.isDragging && dragState.elementId === element.id + ? dragState.currentX + : element.x) / + canvasSize.width) * 100 }%`, top: `${ 50 + - ( - (dragState.isDragging && dragState.elementId === element.id - ? dragState.currentY - : element.y) / canvasSize.height - ) * + ((dragState.isDragging && dragState.elementId === element.id + ? dragState.currentY + : element.y) / + canvasSize.height) * 100 }%`, transform: `translate(-50%, -50%) rotate(${element.rotation}deg) scale(${scaleRatio})`, @@ -408,7 +408,7 @@ export function PreviewPanel() { return ( <div key={element.id} - className="absolute inset-0 bg-gradient-to-br from-blue-500/20 to-purple-500/20 flex items-center justify-center" + className="absolute inset-0 bg-linear-to-br from-blue-500/20 to-purple-500/20 flex items-center justify-center" > <div className="text-center"> <div className="text-2xl mb-2">🎬</div> @@ -564,7 +564,7 @@ function FullscreenToolbar({ toggle: () => void; getTotalDuration: () => number; }) { - const { isPlaying } = usePlaybackStore(); + const { isPlaying, seek } = usePlaybackStore(); const { activeProject } = useProjectStore(); const [isDragging, setIsDragging] = useState(false); @@ -624,9 +624,15 @@ function FullscreenToolbar({ className="flex items-center gap-2 p-1 pt-2 w-full text-white" > <div className="flex items-center gap-1 text-[0.70rem] tabular-nums text-white/90"> - <span className="text-primary"> - {formatTimeCode(currentTime, "HH:MM:SS:FF", activeProject?.fps || 30)} - </span> + <EditableTimecode + time={currentTime} + duration={totalDuration} + format="HH:MM:SS:FF" + fps={activeProject?.fps || 30} + onTimeChange={seek} + disabled={!hasAnyElements} + className="text-white/90 hover:bg-white/10" + /> <span className="opacity-50">/</span> <span> {formatTimeCode( @@ -691,7 +697,7 @@ function FullscreenToolbar({ style={{ width: `${progress}%` }} /> <div - className="absolute top-1/2 w-3 h-3 rounded-full -translate-y-1/2 -translate-x-1/2 shadow-sm bg-white border border-black/20" + className="absolute top-1/2 w-3 h-3 rounded-full -translate-y-1/2 -translate-x-1/2 shadow-xs bg-white border border-black/20" style={{ left: `${progress}%` }} /> </div> @@ -700,11 +706,11 @@ function FullscreenToolbar({ <Button variant="text" size="icon" - className="!size-4 text-white/80 hover:text-white" + className="size-4! text-white/80 hover:text-white" onClick={onToggleExpanded} title="Exit fullscreen (Esc)" > - <Expand className="!size-4" /> + <Expand className="size-4!" /> </Button> </div> ); @@ -738,7 +744,7 @@ function FullscreenPreview({ getTotalDuration: () => number; }) { return ( - <div className="fixed inset-0 z-[9999] flex flex-col"> + <div className="fixed inset-0 z-9999 flex flex-col"> <div className="flex-1 flex items-center justify-center bg-background"> <div className="relative overflow-hidden border border-border m-3" @@ -801,7 +807,7 @@ function PreviewToolbar({ toggle: () => void; getTotalDuration: () => number; }) { - const { isPlaying } = usePlaybackStore(); + const { isPlaying, seek } = usePlaybackStore(); const { setCanvasSize, setCanvasSizeToOriginal } = useEditorStore(); const { activeProject } = useProjectStore(); const { @@ -821,6 +827,18 @@ function PreviewToolbar({ setCanvasSizeToOriginal(aspectRatio); }; + const totalDuration = getTotalDuration(); + + const skipBackward = () => { + const newTime = Math.max(0, currentTime - 1); + setCurrentTime(newTime); + }; + + const skipForward = () => { + const newTime = Math.min(totalDuration, currentTime + 1); + setCurrentTime(newTime); + }; + if (isExpanded) { return ( <FullscreenToolbar @@ -844,17 +862,18 @@ function PreviewToolbar({ <div> <p className={cn( - "text-[0.75rem] text-muted-foreground flex items-center gap-1", + "text-[0.75rem] text-muted-foreground flex items-center gap-1 w-[10rem]", !hasAnyElements && "opacity-50" )} > - <span className="text-primary tabular-nums"> - {formatTimeCode( - currentTime, - "HH:MM:SS:FF", - activeProject?.fps || 30 - )} - </span> + <EditableTimecode + time={currentTime} + duration={getTotalDuration()} + format="HH:MM:SS:FF" + fps={activeProject?.fps || 30} + onTimeChange={seek} + disabled={!hasAnyElements} + /> <span className="opacity-50">/</span> <span className="tabular-nums"> {formatTimeCode( @@ -865,26 +884,48 @@ function PreviewToolbar({ </span> </p> </div> - <Button - variant="text" - size="icon" - onClick={toggle} - disabled={!hasAnyElements} - className="h-auto p-0" - > - {isPlaying ? ( - <Pause className="h-3 w-3" /> - ) : ( - <Play className="h-3 w-3" /> - )} - </Button> + <div className="flex items-center gap-1"> + <Button + variant="text" + size="icon" + onClick={skipBackward} + disabled={!hasAnyElements} + className="h-auto p-0 text-white hover:text-white/80" + title="Skip backward 1s" + > + <SkipBack className="h-3 w-3" /> + </Button> + <Button + variant="text" + size="icon" + onClick={toggle} + disabled={!hasAnyElements} + className="h-auto p-0 text-white hover:text-white/80" + > + {isPlaying ? ( + <Pause className="h-3 w-3" /> + ) : ( + <Play className="h-3 w-3" /> + )} + </Button> + <Button + variant="text" + size="icon" + onClick={skipForward} + disabled={!hasAnyElements} + className="h-auto p-0 text-white hover:text-white/80" + title="Skip forward 1s" + > + <SkipForward className="h-3 w-3" /> + </Button> + </div> <div className="flex items-center gap-3"> <BackgroundSettings /> <DropdownMenu> <DropdownMenuTrigger asChild> <Button size="sm" - className="!bg-panel-accent text-foreground/85 text-[0.70rem] h-4 rounded-none border border-muted-foreground px-0.5 py-0 font-light" + className="bg-panel-accent! text-foreground/85 text-[0.70rem] h-4 rounded-none border border-muted-foreground px-0.5 py-0 font-light" disabled={!hasAnyElements} > {getDisplayName()} @@ -915,11 +956,11 @@ function PreviewToolbar({ <Button variant="text" size="icon" - className="!size-4 text-muted-foreground" + className="size-4! text-muted-foreground" onClick={onToggleExpanded} title="Enter fullscreen" > - <Expand className="!size-4" /> + <Expand className="size-4!" /> </Button> </div> </div> |
