diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-02-10 16:28:21 +0100 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-02-10 16:28:21 +0100 |
| commit | fca99d6126c31fbb18ed9f1034cee6f940b040e8 (patch) | |
| tree | 11500ff0fcc4a933650e3e91f1ea00fd315c4d0c /apps/web/src/components/editor/panels/preview | |
| parent | 33a8705f0d967891402c61dd3192e6bad24921ef (diff) | |
| download | opencut-fca99d6126c31fbb18ed9f1034cee6f940b040e8.tar.gz | |
refactor
Diffstat (limited to 'apps/web/src/components/editor/panels/preview')
| -rw-r--r-- | apps/web/src/components/editor/panels/preview/index.tsx | 141 |
1 files changed, 130 insertions, 11 deletions
diff --git a/apps/web/src/components/editor/panels/preview/index.tsx b/apps/web/src/components/editor/panels/preview/index.tsx index c579b15b..902547bd 100644 --- a/apps/web/src/components/editor/panels/preview/index.tsx +++ b/apps/web/src/components/editor/panels/preview/index.tsx @@ -4,11 +4,23 @@ import { useCallback, useMemo, useRef } from "react"; import useDeepCompareEffect from "use-deep-compare-effect"; import { useEditor } from "@/hooks/use-editor"; import { useRafLoop } from "@/hooks/use-raf-loop"; +import { useContainerSize } from "@/hooks/use-container-size"; +import { useFullscreen } from "@/hooks/use-fullscreen"; import { CanvasRenderer } from "@/services/renderer/canvas-renderer"; import type { RootNode } from "@/services/renderer/nodes/root-node"; import { buildScene } from "@/services/renderer/scene-builder"; -import { getLastFrameTime } from "@/lib/time"; +import { formatTimeCode, getLastFrameTime } from "@/lib/time"; import { PreviewInteractionOverlay } from "./preview-interaction-overlay"; +import { EditableTimecode } from "@/components/editable-timecode"; +import { invokeAction } from "@/lib/actions"; +import { Button } from "@/components/ui/button"; +import { + FullScreenIcon, + PauseIcon, + PlayIcon, +} from "@hugeicons/core-free-icons"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { cn } from "@/utils/ui"; function usePreviewSize() { const editor = useEditor(); @@ -47,32 +59,134 @@ function RenderTreeController() { } export function PreviewPanel() { + const containerRef = useRef<HTMLDivElement>(null); + const { isFullscreen, toggleFullscreen } = useFullscreen({ containerRef }); + return ( - <div className="bg-panel relative flex h-full min-h-0 w-full min-w-0 flex-col rounded-sm"> - <div className="flex min-h-0 min-w-0 flex-1 items-center justify-center p-2"> + <div + ref={containerRef} + className={cn( + "panel bg-background relative flex h-full min-h-0 w-full min-w-0 flex-col rounded-sm border", + isFullscreen && "bg-background", + )} + > + <div className="flex min-h-0 min-w-0 flex-1 items-center justify-center p-2 pb-0"> <PreviewCanvas /> <RenderTreeController /> </div> + <PreviewToolbar + isFullscreen={isFullscreen} + onToggleFullscreen={toggleFullscreen} + /> + </div> + ); +} + +function PreviewToolbar({ + isFullscreen, + onToggleFullscreen, +}: { + isFullscreen: boolean; + onToggleFullscreen: () => void; +}) { + const editor = useEditor(); + const isPlaying = editor.playback.getIsPlaying(); + const currentTime = editor.playback.getCurrentTime(); + const totalDuration = editor.timeline.getTotalDuration(); + const fps = editor.project.getActive().settings.fps; + + return ( + <div className="grid grid-cols-[1fr_auto_1fr] items-center pb-3 pt-5 px-5"> + <div className="flex items-center mt-1"> + <EditableTimecode + time={currentTime} + duration={totalDuration} + format="HH:MM:SS:FF" + fps={fps} + onTimeChange={({ time }) => editor.playback.seek({ time })} + className="text-center" + /> + <span className="text-muted-foreground px-2 font-mono text-xs">/</span> + <span className="text-muted-foreground font-mono text-xs"> + {formatTimeCode({ + timeInSeconds: totalDuration, + format: "HH:MM:SS:FF", + fps, + })} + </span> + </div> + + <Button + variant="text" + size="icon" + type="button" + onClick={() => invokeAction("toggle-play")} + > + <HugeiconsIcon icon={isPlaying ? PauseIcon : PlayIcon} /> + </Button> + + <div className="justify-self-end"> + <Button + variant="text" + size="icon" + type="button" + onClick={onToggleFullscreen} + title={isFullscreen ? "Exit fullscreen" : "Enter fullscreen"} + > + <HugeiconsIcon icon={FullScreenIcon} /> + </Button> + </div> </div> ); } function PreviewCanvas() { const canvasRef = useRef<HTMLCanvasElement>(null); + const containerRef = useRef<HTMLDivElement>(null); const lastFrameRef = useRef(-1); const lastSceneRef = useRef<RootNode | null>(null); const renderingRef = useRef(false); - const { width, height } = usePreviewSize(); + const { width: nativeWidth, height: nativeHeight } = usePreviewSize(); + const containerSize = useContainerSize({ containerRef }); const editor = useEditor(); const activeProject = editor.project.getActive(); const renderer = useMemo(() => { return new CanvasRenderer({ - width, - height, + width: nativeWidth, + height: nativeHeight, fps: activeProject.settings.fps, }); - }, [width, height, activeProject.settings.fps]); + }, [nativeWidth, nativeHeight, activeProject.settings.fps]); + + const displaySize = useMemo(() => { + if ( + !nativeWidth || + !nativeHeight || + containerSize.width === 0 || + containerSize.height === 0 + ) { + return { width: nativeWidth ?? 0, height: nativeHeight ?? 0 }; + } + + const paddingBuffer = 4; + const availableWidth = containerSize.width - paddingBuffer; + const availableHeight = containerSize.height - paddingBuffer; + + const aspectRatio = nativeWidth / nativeHeight; + const containerAspect = availableWidth / availableHeight; + + const displayWidth = + containerAspect > aspectRatio + ? availableHeight * aspectRatio + : availableWidth; + const displayHeight = + containerAspect > aspectRatio + ? availableHeight + : availableWidth / aspectRatio; + + return { width: displayWidth, height: displayHeight }; + }, [nativeWidth, nativeHeight, containerSize.width, containerSize.height]); const renderTree = editor.renderer.getRenderTree(); @@ -109,13 +223,18 @@ function PreviewCanvas() { useRafLoop(render); return ( - <div className="relative"> + <div + ref={containerRef} + className="relative flex h-full w-full items-center justify-center" + > <canvas ref={canvasRef} - width={width} - height={height} - className="block max-h-full max-w-full border" + width={nativeWidth} + height={nativeHeight} + className="block border" style={{ + width: displaySize.width, + height: displaySize.height, background: activeProject.settings.background.type === "blur" ? "transparent" |
