From 83a60926d87c6f2155c3a33cbf43233de9d489f2 Mon Sep 17 00:00:00 2001 From: Marcus Schiesser Date: Thu, 19 Mar 2026 17:51:51 +0800 Subject: [PATCH] fix: center stacked screen and webcam layout --- src/components/video-editor/SettingsPanel.tsx | 66 +++-- src/components/video-editor/VideoPlayback.tsx | 67 ++--- src/components/video-editor/types.ts | 2 +- .../video-editor/videoPlayback/layoutUtils.ts | 60 +++-- src/lib/compositeLayout.test.ts | 78 ++++++ src/lib/compositeLayout.ts | 250 ++++++++++++++++++ src/lib/exporter/frameRenderer.ts | 100 +++---- src/lib/exporter/gifExporter.ts | 3 +- src/lib/exporter/videoExporter.ts | 3 +- src/lib/webcamOverlay.test.ts | 63 ----- src/lib/webcamOverlay.ts | 196 -------------- 11 files changed, 482 insertions(+), 406 deletions(-) create mode 100644 src/lib/compositeLayout.test.ts create mode 100644 src/lib/compositeLayout.ts delete mode 100644 src/lib/webcamOverlay.test.ts delete mode 100644 src/lib/webcamOverlay.ts diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 500356b..3d546d0 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -36,10 +36,10 @@ import { Slider } from "@/components/ui/slider"; import { Switch } from "@/components/ui/switch"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { getAssetPath } from "@/lib/assetPath"; +import { WEBCAM_LAYOUT_PRESETS } from "@/lib/compositeLayout"; import type { ExportFormat, ExportQuality, GifFrameRate, GifSizePreset } from "@/lib/exporter"; import { GIF_FRAME_RATES, GIF_SIZE_PRESETS } from "@/lib/exporter"; import { cn } from "@/lib/utils"; -import { WEBCAM_LAYOUT_PRESETS } from "@/lib/webcamOverlay"; import { type AspectRatio } from "@/utils/aspectRatioUtils"; import { getTestId } from "@/utils/getTestId"; import { AnnotationSettingsPanel } from "./AnnotationSettingsPanel"; @@ -582,7 +582,47 @@ export function SettingsPanel({ )} - + + {hasWebcam && ( + + +
+ + Layout +
+
+ +
+
Preset
+ +
+
+
+ )} +
@@ -601,28 +641,6 @@ export function SettingsPanel({ />
- {hasWebcam && ( -
-
Webcam Layout
- -
- )}
diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index f576ae3..24cd187 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -20,11 +20,11 @@ import { } from "react"; import { getAssetPath } from "@/lib/assetPath"; import { - computeWebcamOverlayLayout, getWebcamLayoutCssBoxShadow, + type Size, + type StyledRenderRect, type WebcamLayoutPreset, - type WebcamOverlayLayout, -} from "@/lib/webcamOverlay"; +} from "@/lib/compositeLayout"; import { type AspectRatio, formatAspectRatioForCSS, @@ -141,7 +141,6 @@ const VideoPlayback = forwardRef( const videoRef = useRef(null); const webcamVideoRef = useRef(null); const containerRef = useRef(null); - const stageRef = useRef(null); const appRef = useRef(null); const videoSpriteRef = useRef(null); const videoContainerRef = useRef(null); @@ -151,15 +150,8 @@ const VideoPlayback = forwardRef( const [videoReady, setVideoReady] = useState(false); const overlayRef = useRef(null); const focusIndicatorRef = useRef(null); - const [webcamLayout, setWebcamLayout] = useState(null); - const [webcamDimensions, setWebcamDimensions] = useState<{ - width: number; - height: number; - } | null>(null); - const [screenVideoDimensions, setScreenVideoDimensions] = useState<{ - width: number; - height: number; - } | null>(null); + const [webcamLayout, setWebcamLayout] = useState(null); + const [webcamDimensions, setWebcamDimensions] = useState(null); const currentTimeRef = useRef(0); const zoomRegionsRef = useRef([]); const selectedZoomIdRef = useRef(null); @@ -269,6 +261,8 @@ const VideoPlayback = forwardRef( lockedVideoDimensions: lockedVideoDimensionsRef.current, borderRadius, padding, + webcamDimensions, + webcamLayoutPreset, }); if (result) { @@ -278,6 +272,7 @@ const VideoPlayback = forwardRef( baseOffsetRef.current = result.baseOffset; baseMaskRef.current = result.maskRect; cropBoundsRef.current = result.cropBounds; + setWebcamLayout(result.webcamRect); // Reset camera container to identity cameraContainer.scale.set(1); @@ -290,7 +285,14 @@ const VideoPlayback = forwardRef( updateOverlayForRegion(activeRegion); } - }, [updateOverlayForRegion, cropRegion, borderRadius, padding]); + }, [ + updateOverlayForRegion, + cropRegion, + borderRadius, + padding, + webcamDimensions, + webcamLayoutPreset, + ]); useEffect(() => { layoutVideoContentRef.current = layoutVideoContent; @@ -620,9 +622,6 @@ const VideoPlayback = forwardRef( cancelAnimationFrame(videoReadyRafRef.current); videoReadyRafRef.current = null; } - if (video.videoWidth > 0 && video.videoHeight > 0) { - setScreenVideoDimensions({ width: video.videoWidth, height: video.videoHeight }); - } }, [videoPath]); useEffect(() => { @@ -952,37 +951,6 @@ const VideoPlayback = forwardRef( }; }, [webcamVideoPath]); - useEffect(() => { - const stage = stageRef.current; - if (!stage || !webcamDimensions || !screenVideoDimensions) { - setWebcamLayout(null); - return; - } - - const updateLayout = () => { - const layout = computeWebcamOverlayLayout({ - stageWidth: stage.clientWidth, - stageHeight: stage.clientHeight, - videoWidth: webcamDimensions.width, - videoHeight: webcamDimensions.height, - layoutPreset: webcamLayoutPreset, - screenVideoWidth: screenVideoDimensions?.width, - screenVideoHeight: screenVideoDimensions?.height, - }); - setWebcamLayout(layout); - }; - - updateLayout(); - - if (typeof ResizeObserver === "undefined") { - return; - } - - const observer = new ResizeObserver(updateLayout); - observer.observe(stage); - return () => observer.disconnect(); - }, [screenVideoDimensions, webcamDimensions, webcamLayoutPreset]); - useEffect(() => { const webcamVideo = webcamVideoRef.current; if (!webcamVideo || !webcamVideoPath) { @@ -1096,7 +1064,6 @@ const VideoPlayback = forwardRef( return (
( : "none", }} /> - {webcamVideoPath && screenVideoDimensions && ( + {webcamVideoPath && (