summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/panels/preview
diff options
context:
space:
mode:
authorMaze <167211895+mazeincoding@users.noreply.github.com>2026-01-31 00:20:04 +0100
committerGitHub <noreply@github.com>2026-01-31 00:20:04 +0100
commit7bf0984698e43b9aa2dd879f65951318ed410390 (patch)
treeccf2cd590a47d550152afc07129cb55c71a9ec35 /apps/web/src/components/editor/panels/preview
parent0173db99448d1c4c6415dcf15272e17551b48b04 (diff)
downloadopencut-7bf0984698e43b9aa2dd879f65951318ed410390.tar.gz
codebase overhaul (#697)
Diffstat (limited to 'apps/web/src/components/editor/panels/preview')
-rw-r--r--apps/web/src/components/editor/panels/preview/index.tsx124
1 files changed, 124 insertions, 0 deletions
diff --git a/apps/web/src/components/editor/panels/preview/index.tsx b/apps/web/src/components/editor/panels/preview/index.tsx
new file mode 100644
index 00000000..5ed9db00
--- /dev/null
+++ b/apps/web/src/components/editor/panels/preview/index.tsx
@@ -0,0 +1,124 @@
+"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 { 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";
+
+function usePreviewSize() {
+ const editor = useEditor();
+ const activeProject = editor.project.getActive();
+
+ return {
+ width: activeProject?.settings.canvasSize.width,
+ height: activeProject?.settings.canvasSize.height,
+ };
+}
+
+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,
+ });
+
+ editor.renderer.setRenderTree({ renderTree });
+ }, [tracks, mediaAssets, activeProject?.settings.background, width, height]);
+
+ return null;
+}
+
+export function PreviewPanel() {
+ 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">
+ <PreviewCanvas />
+ <RenderTreeController />
+ </div>
+ </div>
+ );
+}
+
+function PreviewCanvas() {
+ const ref = useRef<HTMLCanvasElement>(null);
+ const lastFrameRef = useRef(-1);
+ const lastSceneRef = useRef<RootNode | null>(null);
+ const renderingRef = useRef(false);
+ const { width, height } = usePreviewSize();
+ const editor = useEditor();
+ const activeProject = editor.project.getActive();
+
+ const renderer = useMemo(() => {
+ return new CanvasRenderer({
+ width,
+ height,
+ fps: activeProject.settings.fps,
+ });
+ }, [width, height, activeProject.settings.fps]);
+
+ const renderTree = editor.renderer.getRenderTree();
+
+ const render = useCallback(() => {
+ if (ref.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: ref.current,
+ })
+ .then(() => {
+ renderingRef.current = false;
+ });
+ }
+ }
+ }, [renderer, renderTree, editor.playback]);
+
+ useRafLoop(render);
+
+ return (
+ <canvas
+ ref={ref}
+ width={width}
+ height={height}
+ className="block max-h-full max-w-full border"
+ style={{
+ background:
+ activeProject.settings.background.type === "blur"
+ ? "transparent"
+ : activeProject?.settings.background.color,
+ }}
+ />
+ );
+}