diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 3862c26..c2d66ff 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -36,18 +36,10 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useScopedT } from "@/contexts/I18nContext"; import { getAssetPath } from "@/lib/assetPath"; import { WEBCAM_LAYOUT_PRESETS } from "@/lib/compositeLayout"; -import type { - ExportFormat, - ExportQuality, - GifFrameRate, - GifSizePreset, -} from "@/lib/exporter"; +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 { - type AspectRatio, - isPortraitAspectRatio, -} from "@/utils/aspectRatioUtils"; +import { type AspectRatio, isPortraitAspectRatio } from "@/utils/aspectRatioUtils"; import { getTestId } from "@/utils/getTestId"; import { AnnotationSettingsPanel } from "./AnnotationSettingsPanel"; import { CropControl } from "./CropControl"; @@ -64,7 +56,7 @@ import type { ZoomDepth, ZoomFocusMode, } from "./types"; -import { SPEED_OPTIONS, DEFAULT_WEBCAM_SIZE_PRESET } from "./types"; +import { DEFAULT_WEBCAM_SIZE_PRESET, SPEED_OPTIONS } from "./types"; const WALLPAPER_COUNT = 18; const WALLPAPER_RELATIVE = Array.from( @@ -151,10 +143,7 @@ interface SettingsPanelProps { annotationRegions?: AnnotationRegion[]; onAnnotationContentChange?: (id: string, content: string) => void; onAnnotationTypeChange?: (id: string, type: AnnotationType) => void; - onAnnotationStyleChange?: ( - id: string, - style: Partial, - ) => void; + onAnnotationStyleChange?: (id: string, style: Partial) => void; onAnnotationFigureDataChange?: (id: string, figureData: FigureData) => void; onAnnotationDelete?: (id: string) => void; selectedSpeedId?: string | null; @@ -167,7 +156,8 @@ interface SettingsPanelProps { webcamMaskShape?: import("./types").WebcamMaskShape; onWebcamMaskShapeChange?: (shape: import("./types").WebcamMaskShape) => void; webcamSizePreset?: WebcamSizePreset; - onWebcamSizePresetChange?: (preset: WebcamSizePreset) => void; + onWebcamSizePresetChange?: (size: WebcamSizePreset) => void; + onWebcamSizePresetCommit?: () => void; } export default SettingsPanel; @@ -243,6 +233,7 @@ export function SettingsPanel({ onWebcamMaskShapeChange, webcamSizePreset = DEFAULT_WEBCAM_SIZE_PRESET, onWebcamSizePresetChange, + onWebcamSizePresetCommit, }: SettingsPanelProps) { const t = useScopedT("settings"); const [wallpaperPaths, setWallpaperPaths] = useState([]); @@ -253,9 +244,7 @@ export function SettingsPanel({ let mounted = true; (async () => { try { - const resolved = await Promise.all( - WALLPAPER_RELATIVE.map((p) => getAssetPath(p)), - ); + const resolved = await Promise.all(WALLPAPER_RELATIVE.map((p) => getAssetPath(p))); if (mounted) setWallpaperPaths(resolved); } catch (_err) { if (mounted) setWallpaperPaths(WALLPAPER_RELATIVE.map((p) => `/${p}`)); @@ -301,22 +290,13 @@ export function SettingsPanel({ const next = { ...cropRegion }; switch (field) { case "x": - next.x = Math.max( - 0, - Math.min(pixelValue / videoWidth, 1 - next.width), - ); + next.x = Math.max(0, Math.min(pixelValue / videoWidth, 1 - next.width)); break; case "y": - next.y = Math.max( - 0, - Math.min(pixelValue / videoHeight, 1 - next.height), - ); + next.y = Math.max(0, Math.min(pixelValue / videoHeight, 1 - next.height)); break; case "width": { - const newWidth = Math.max( - 0.05, - Math.min(pixelValue / videoWidth, 1 - next.x), - ); + const newWidth = Math.max(0.05, Math.min(pixelValue / videoWidth, 1 - next.x)); if (cropAspectLocked && next.width > 0 && next.height > 0) { const ratio = next.width / next.height; const newHeight = newWidth / ratio; @@ -330,10 +310,7 @@ export function SettingsPanel({ break; } case "height": { - const newHeight = Math.max( - 0.05, - Math.min(pixelValue / videoHeight, 1 - next.y), - ); + const newHeight = Math.max(0.05, Math.min(pixelValue / videoHeight, 1 - next.y)); if (cropAspectLocked && next.width > 0 && next.height > 0) { const ratio = next.width / next.height; const newWidth = newHeight * ratio; @@ -367,13 +344,11 @@ export function SettingsPanel({ const targetRatio = Number(wStr) / Number(hStr); const next = { ...cropRegion }; - const nextHeight = - (next.width * videoWidth) / (targetRatio * videoHeight); + const nextHeight = (next.width * videoWidth) / (targetRatio * videoHeight); if (next.y + nextHeight <= 1 && nextHeight >= 0.05) { next.height = nextHeight; } else { - const nextWidth = - (next.height * videoHeight * targetRatio) / videoWidth; + const nextWidth = (next.height * videoHeight * targetRatio) / videoWidth; if (next.x + nextWidth <= 1 && nextWidth >= 0.05) { next.width = nextWidth; } @@ -455,10 +430,7 @@ export function SettingsPanel({ event.target.value = ""; }; - const handleRemoveCustomImage = ( - imageUrl: string, - event: React.MouseEvent, - ) => { + const handleRemoveCustomImage = (imageUrl: string, event: React.MouseEvent) => { event.stopPropagation(); setCustomImages((prev) => prev.filter((img) => img !== imageUrl)); // If the removed image was selected, clear selection @@ -497,19 +469,12 @@ export function SettingsPanel({ return ( - onAnnotationContentChange(selectedAnnotation.id, content) - } - onTypeChange={(type) => - onAnnotationTypeChange(selectedAnnotation.id, type) - } - onStyleChange={(style) => - onAnnotationStyleChange(selectedAnnotation.id, style) - } + onContentChange={(content) => onAnnotationContentChange(selectedAnnotation.id, content)} + onTypeChange={(type) => onAnnotationTypeChange(selectedAnnotation.id, type)} + onStyleChange={(style) => onAnnotationStyleChange(selectedAnnotation.id, style)} onFigureDataChange={ onAnnotationFigureDataChange - ? (figureData) => - onAnnotationFigureDataChange(selectedAnnotation.id, figureData) + ? (figureData) => onAnnotationFigureDataChange(selectedAnnotation.id, figureData) : undefined } onDelete={() => onAnnotationDelete(selectedAnnotation.id)} @@ -522,17 +487,11 @@ export function SettingsPanel({
- - {t("zoom.level")} - + {t("zoom.level")}
{zoomEnabled && selectedZoomDepth && ( - { - ZOOM_DEPTH_OPTIONS.find( - (o) => o.depth === selectedZoomDepth, - )?.label - } + {ZOOM_DEPTH_OPTIONS.find((o) => o.depth === selectedZoomDepth)?.label} )} @@ -550,9 +509,7 @@ export function SettingsPanel({ className={cn( "h-auto w-full rounded-lg border px-1 py-2 text-center shadow-sm transition-all", "duration-200 ease-out", - zoomEnabled - ? "opacity-100 cursor-pointer" - : "opacity-40 cursor-not-allowed", + zoomEnabled ? "opacity-100 cursor-pointer" : "opacity-40 cursor-not-allowed", isActive ? "border-[#34B27B] bg-[#34B27B] text-white shadow-[#34B27B]/20" : "border-white/5 bg-white/5 text-slate-400 hover:bg-white/10 hover:border-white/10 hover:text-slate-200", @@ -564,9 +521,7 @@ export function SettingsPanel({ })}
{!zoomEnabled && ( -

- {t("zoom.selectRegion")} -

+

{t("zoom.selectRegion")}

)} {zoomEnabled && hasCursorTelemetry && (
@@ -632,13 +587,11 @@ export function SettingsPanel({
- - {t("speed.playbackSpeed")} - + {t("speed.playbackSpeed")} {selectedSpeedId && selectedSpeedValue && ( - {SPEED_OPTIONS.find((o) => o.speed === selectedSpeedValue) - ?.label ?? `${selectedSpeedValue}×`} + {SPEED_OPTIONS.find((o) => o.speed === selectedSpeedValue)?.label ?? + `${selectedSpeedValue}×`} )}
@@ -668,15 +621,11 @@ export function SettingsPanel({ })}
{!selectedSpeedId && ( -

- {t("speed.selectRegion")} -

+

{t("speed.selectRegion")}

)} {selectedSpeedId && ( ))}
)} + {webcamLayoutPreset === "picture-in-picture" && ( +
+
+
+ {t("layout.webcamSize")} +
+
+ {webcamSizePreset}% +
+
+ onWebcamSizePresetChange?.(values[0])} + onValueCommit={() => onWebcamSizePresetCommit?.()} + min={10} + max={50} + step={1} + className="w-full" + /> +
+ )} )} - +
- - {t("effects.title")} - + {t("effects.title")}
@@ -862,9 +815,7 @@ export function SettingsPanel({ {t("effects.motionBlur")}
- {motionBlurAmount === 0 - ? t("effects.off") - : motionBlurAmount.toFixed(2)} + {motionBlurAmount === 0 ? t("effects.off") : motionBlurAmount.toFixed(2)}
{t("effects.roundness")} - - {borderRadius}px - + {borderRadius}px - onBorderRadiusChange?.(values[0]) - } + onValueChange={(values) => onBorderRadiusChange?.(values[0])} onValueCommit={() => onBorderRadiusCommit?.()} min={0} max={16} @@ -925,15 +872,11 @@ export function SettingsPanel({ {t("effects.padding")} - {webcamLayoutPreset === "vertical-stack" - ? "—" - : `${padding}%`} + {webcamLayoutPreset === "vertical-stack" ? "—" : `${padding}%`} onPaddingChange?.(values[0])} onValueCommit={() => onPaddingCommit?.()} min={0} @@ -963,9 +906,7 @@ export function SettingsPanel({
- - {t("background.title")} - + {t("background.title")}
@@ -1030,9 +971,7 @@ export function SettingsPanel({ role="button" > ))} @@ -1392,9 +1299,7 @@ export function SettingsPanel({ {gifOutputDimensions.width} × {gifOutputDimensions.height}px
- - {t("gifSettings.loop")} - + {t("gifSettings.loop")} - {exportFormat === "gif" - ? t("export.gifButton") - : t("export.videoButton")} + {exportFormat === "gif" ? t("export.gifButton") : t("export.videoButton")}
@@ -1445,9 +1348,7 @@ export function SettingsPanel({