"use client"; 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 { PreviewInteractionOverlay } from "./preview-interaction-overlay"; import { BookmarkNoteOverlay } from "./bookmark-note-overlay"; import { ContextMenu, ContextMenuTrigger } from "@/components/ui/context-menu"; import { usePreviewStore } from "@/stores/preview-store"; import { PreviewContextMenu } from "./context-menu"; import { PreviewToolbar } from "./toolbar"; function usePreviewSize() { const editor = useEditor(); const activeProject = editor.project.getActive(); return { width: activeProject?.settings.canvasSize.width, height: activeProject?.settings.canvasSize.height, }; } export function PreviewPanel() { const containerRef = useRef(null); const { isFullscreen, toggleFullscreen } = useFullscreen({ containerRef }); return (
); } function RenderTreeController() { const editor = useEditor(); const tracks = editor.timeline.getTracks(); const mediaAssets = editor.media.getAssets(); const activeProject = editor.project.getActive(); const { width, height } = usePreviewSize(); useDeepCompareEffect(() => { if (!activeProject) return; const duration = editor.timeline.getTotalDuration(); const renderTree = buildScene({ tracks, mediaAssets, duration, canvasSize: { width, height }, background: activeProject.settings.background, isPreview: true, }); editor.renderer.setRenderTree({ renderTree }); }, [tracks, mediaAssets, activeProject?.settings.background, width, height]); return null; } function PreviewCanvas({ onToggleFullscreen, containerRef, }: { onToggleFullscreen: () => void; containerRef: React.RefObject; }) { const canvasRef = useRef(null); const outerContainerRef = useRef(null); const canvasBoundsRef = useRef(null); const lastFrameRef = useRef(-1); const lastSceneRef = useRef(null); const renderingRef = useRef(false); const { width: nativeWidth, height: nativeHeight } = usePreviewSize(); const containerSize = useContainerSize({ containerRef: outerContainerRef }); const editor = useEditor(); const activeProject = editor.project.getActive(); const { overlays } = usePreviewStore(); const renderer = useMemo(() => { return new CanvasRenderer({ width: nativeWidth, height: nativeHeight, fps: 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(); const render = useCallback(() => { if (canvasRef.current && renderTree && !renderingRef.current) { const time = editor.playback.getCurrentTime(); const lastFrameTime = getLastFrameTime({ duration: renderTree.duration, fps: renderer.fps, }); const renderTime = Math.min(time, lastFrameTime); const frame = Math.floor(renderTime * renderer.fps); if ( frame !== lastFrameRef.current || renderTree !== lastSceneRef.current ) { renderingRef.current = true; lastSceneRef.current = renderTree; lastFrameRef.current = frame; renderer .renderToCanvas({ node: renderTree, time: renderTime, targetCanvas: canvasRef.current, }) .then(() => { renderingRef.current = false; }); } } }, [renderer, renderTree, editor.playback]); useRafLoop(render); return (
{overlays.bookmarks && }
); }