feat: add dual frame webcam layout preset
This commit is contained in:
@@ -354,6 +354,7 @@ export function SettingsPanel({
|
||||
const cropSnapshotRef = useRef<CropRegion | null>(null);
|
||||
const [cropAspectLocked, setCropAspectLocked] = useState(false);
|
||||
const [cropAspectRatio, setCropAspectRatio] = useState("");
|
||||
const isPortraitCanvas = isPortraitAspectRatio(aspectRatio);
|
||||
|
||||
const videoWidth = videoElement?.videoWidth || 1920;
|
||||
const videoHeight = videoElement?.videoHeight || 1080;
|
||||
@@ -779,15 +780,17 @@ export function SettingsPanel({
|
||||
<SelectValue placeholder={t("layout.selectPreset")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{WEBCAM_LAYOUT_PRESETS.filter(
|
||||
(preset) =>
|
||||
preset.value === "picture-in-picture" ||
|
||||
isPortraitAspectRatio(aspectRatio),
|
||||
).map((preset) => (
|
||||
{WEBCAM_LAYOUT_PRESETS.filter((preset) => {
|
||||
if (preset.value === "picture-in-picture") return true;
|
||||
if (preset.value === "vertical-stack") return isPortraitCanvas;
|
||||
return !isPortraitCanvas;
|
||||
}).map((preset) => (
|
||||
<SelectItem key={preset.value} value={preset.value} className="text-xs">
|
||||
{preset.value === "picture-in-picture"
|
||||
? t("layout.pictureInPicture")
|
||||
: t("layout.verticalStack")}
|
||||
: preset.value === "vertical-stack"
|
||||
? t("layout.verticalStack")
|
||||
: t("layout.dualFrame")}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
||||
@@ -1884,7 +1884,8 @@ export default function VideoEditor() {
|
||||
pushState({
|
||||
aspectRatio: ar,
|
||||
webcamLayoutPreset:
|
||||
!isPortraitAspectRatio(ar) && webcamLayoutPreset === "vertical-stack"
|
||||
(isPortraitAspectRatio(ar) && webcamLayoutPreset === "dual-frame") ||
|
||||
(!isPortraitAspectRatio(ar) && webcamLayoutPreset === "vertical-stack")
|
||||
? "picture-in-picture"
|
||||
: webcamLayoutPreset,
|
||||
})
|
||||
@@ -1937,7 +1938,7 @@ export default function VideoEditor() {
|
||||
onWebcamLayoutPresetChange={(preset) =>
|
||||
pushState({
|
||||
webcamLayoutPreset: preset,
|
||||
webcamPosition: preset === "vertical-stack" ? null : webcamPosition,
|
||||
webcamPosition: preset === "picture-in-picture" ? webcamPosition : null,
|
||||
})
|
||||
}
|
||||
webcamMaskShape={webcamMaskShape}
|
||||
|
||||
@@ -44,6 +44,7 @@ describe("projectPersistence media compatibility", () => {
|
||||
aspectRatio: "16:9",
|
||||
webcamLayoutPreset: "picture-in-picture",
|
||||
webcamMaskShape: "circle",
|
||||
webcamPosition: null,
|
||||
exportQuality: "good",
|
||||
exportFormat: "mp4",
|
||||
gifFrameRate: 15,
|
||||
@@ -66,6 +67,12 @@ describe("projectPersistence media compatibility", () => {
|
||||
normalizeProjectEditor({ webcamMaskShape: "not-a-real-shape" as never }).webcamMaskShape,
|
||||
).toBe("rectangle");
|
||||
});
|
||||
|
||||
it("accepts the dual frame webcam layout preset", () => {
|
||||
expect(normalizeProjectEditor({ webcamLayoutPreset: "dual-frame" }).webcamLayoutPreset).toBe(
|
||||
"dual-frame",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("creates stable snapshots for identical project state", () => {
|
||||
|
||||
@@ -400,6 +400,7 @@ export function normalizeProjectEditor(editor: Partial<ProjectEditorState>): Pro
|
||||
editor.aspectRatio && validAspectRatios.has(editor.aspectRatio) ? editor.aspectRatio : "16:9",
|
||||
webcamLayoutPreset:
|
||||
editor.webcamLayoutPreset === "vertical-stack" ||
|
||||
editor.webcamLayoutPreset === "dual-frame" ||
|
||||
editor.webcamLayoutPreset === "picture-in-picture"
|
||||
? editor.webcamLayoutPreset
|
||||
: DEFAULT_WEBCAM_LAYOUT_PRESET,
|
||||
|
||||
@@ -140,7 +140,7 @@ export function layoutVideoContent(params: LayoutParams): LayoutResult | null {
|
||||
screenRect.y,
|
||||
screenRect.width,
|
||||
screenRect.height,
|
||||
compositeLayout.screenCover ? 0 : borderRadius,
|
||||
compositeLayout.screenBorderRadius ?? (compositeLayout.screenCover ? 0 : borderRadius),
|
||||
);
|
||||
maskGraphics.fill({ color: 0xffffff });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user