Merge pull request #605 from EtienneLescot/codex/editor-defaults-ssot
Centralize editor defaults
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { type AspectRatio } from "@/utils/aspectRatioUtils";
|
||||
import { DEFAULT_SOURCE_DIMENSIONS } from "./editorDefaults";
|
||||
|
||||
interface CropRegion {
|
||||
x: number; // 0-1 normalized
|
||||
@@ -32,8 +33,8 @@ export function CropControl({ videoElement, cropRegion, onCropChange }: CropCont
|
||||
const ctx = canvas.getContext("2d", { alpha: false });
|
||||
if (!ctx) return;
|
||||
|
||||
canvas.width = videoElement.videoWidth || 1920;
|
||||
canvas.height = videoElement.videoHeight || 1080;
|
||||
canvas.width = videoElement.videoWidth || DEFAULT_SOURCE_DIMENSIONS.width;
|
||||
canvas.height = videoElement.videoHeight || DEFAULT_SOURCE_DIMENSIONS.height;
|
||||
|
||||
const draw = () => {
|
||||
if (videoElement.readyState >= 2) {
|
||||
|
||||
@@ -54,6 +54,14 @@ import { AnnotationSettingsPanel } from "./AnnotationSettingsPanel";
|
||||
import { BlurSettingsPanel } from "./BlurSettingsPanel";
|
||||
import { CropControl } from "./CropControl";
|
||||
import { parseCustomPlaybackSpeedInput } from "./customPlaybackSpeed";
|
||||
import {
|
||||
DEFAULT_CURSOR_SETTINGS,
|
||||
DEFAULT_EDITOR_LAYOUT_SETTINGS,
|
||||
DEFAULT_EXPORT_SETTINGS,
|
||||
DEFAULT_GIF_SETTINGS,
|
||||
DEFAULT_SOURCE_DIMENSIONS,
|
||||
DEFAULT_WEBCAM_SETTINGS,
|
||||
} from "./editorDefaults";
|
||||
import { KeyboardShortcutsHelp } from "./KeyboardShortcutsHelp";
|
||||
import type {
|
||||
AnnotationRegion,
|
||||
@@ -71,7 +79,6 @@ import type {
|
||||
ZoomFocusMode,
|
||||
} from "./types";
|
||||
import {
|
||||
DEFAULT_WEBCAM_SIZE_PRESET,
|
||||
MAX_ZOOM_SCALE,
|
||||
MIN_ZOOM_SCALE,
|
||||
ROTATION_3D_PRESET_ORDER,
|
||||
@@ -383,24 +390,24 @@ export function SettingsPanel({
|
||||
borderRadius = 0,
|
||||
onBorderRadiusChange,
|
||||
onBorderRadiusCommit,
|
||||
padding = 50,
|
||||
padding = DEFAULT_EDITOR_LAYOUT_SETTINGS.padding,
|
||||
onPaddingChange,
|
||||
onPaddingCommit,
|
||||
cropRegion,
|
||||
onCropChange,
|
||||
aspectRatio,
|
||||
videoElement,
|
||||
exportQuality = "good",
|
||||
exportQuality = DEFAULT_EXPORT_SETTINGS.quality,
|
||||
onExportQualityChange,
|
||||
exportFormat = "mp4",
|
||||
exportFormat = DEFAULT_EXPORT_SETTINGS.format,
|
||||
onExportFormatChange,
|
||||
gifFrameRate = 15,
|
||||
gifFrameRate = DEFAULT_GIF_SETTINGS.frameRate,
|
||||
onGifFrameRateChange,
|
||||
gifLoop = true,
|
||||
gifLoop = DEFAULT_GIF_SETTINGS.loop,
|
||||
onGifLoopChange,
|
||||
gifSizePreset = "medium",
|
||||
gifSizePreset = DEFAULT_GIF_SETTINGS.sizePreset,
|
||||
onGifSizePresetChange,
|
||||
gifOutputDimensions = { width: 1280, height: 720 },
|
||||
gifOutputDimensions = DEFAULT_GIF_SETTINGS.outputDimensions,
|
||||
onExport,
|
||||
unsavedExport,
|
||||
onSaveUnsavedExport,
|
||||
@@ -422,25 +429,25 @@ export function SettingsPanel({
|
||||
onSpeedChange,
|
||||
onSpeedDelete,
|
||||
hasWebcam = false,
|
||||
webcamLayoutPreset = "picture-in-picture",
|
||||
webcamLayoutPreset = DEFAULT_WEBCAM_SETTINGS.layoutPreset,
|
||||
onWebcamLayoutPresetChange,
|
||||
webcamMaskShape = "rectangle",
|
||||
webcamMaskShape = DEFAULT_WEBCAM_SETTINGS.maskShape,
|
||||
onWebcamMaskShapeChange,
|
||||
webcamSizePreset = DEFAULT_WEBCAM_SIZE_PRESET,
|
||||
webcamSizePreset = DEFAULT_WEBCAM_SETTINGS.sizePreset,
|
||||
onWebcamSizePresetChange,
|
||||
onWebcamSizePresetCommit,
|
||||
onSaveDiagnostic,
|
||||
showCursor = true,
|
||||
showCursor = DEFAULT_CURSOR_SETTINGS.show,
|
||||
onShowCursorChange,
|
||||
cursorSize = 3.0,
|
||||
cursorSize = DEFAULT_CURSOR_SETTINGS.size,
|
||||
onCursorSizeChange,
|
||||
cursorSmoothing = 0.67,
|
||||
cursorSmoothing = DEFAULT_CURSOR_SETTINGS.smoothing,
|
||||
onCursorSmoothingChange,
|
||||
cursorMotionBlur = 0.35,
|
||||
cursorMotionBlur = DEFAULT_CURSOR_SETTINGS.motionBlur,
|
||||
onCursorMotionBlurChange,
|
||||
cursorClickBounce = 2.5,
|
||||
cursorClickBounce = DEFAULT_CURSOR_SETTINGS.clickBounce,
|
||||
onCursorClickBounceChange,
|
||||
cursorClipToBounds = false,
|
||||
cursorClipToBounds = DEFAULT_CURSOR_SETTINGS.clipToBounds,
|
||||
onCursorClipToBoundsChange,
|
||||
hasCursorData = false,
|
||||
showCursorSettings = true,
|
||||
@@ -479,8 +486,8 @@ export function SettingsPanel({
|
||||
const [cropAspectRatio, setCropAspectRatio] = useState("");
|
||||
const isPortraitCanvas = isPortraitAspectRatio(aspectRatio);
|
||||
|
||||
const videoWidth = videoElement?.videoWidth || 1920;
|
||||
const videoHeight = videoElement?.videoHeight || 1080;
|
||||
const videoWidth = videoElement?.videoWidth || DEFAULT_SOURCE_DIMENSIONS.width;
|
||||
const videoHeight = videoElement?.videoHeight || DEFAULT_SOURCE_DIMENSIONS.height;
|
||||
|
||||
const handleCropNumericChange = useCallback(
|
||||
(field: "x" | "y" | "width" | "height", pixelValue: number) => {
|
||||
|
||||
@@ -49,6 +49,12 @@ import {
|
||||
isPortraitAspectRatio,
|
||||
} from "@/utils/aspectRatioUtils";
|
||||
import { ExportDialog } from "./ExportDialog";
|
||||
import {
|
||||
DEFAULT_CURSOR_SETTINGS,
|
||||
DEFAULT_EXPORT_SETTINGS,
|
||||
DEFAULT_GIF_SETTINGS,
|
||||
DEFAULT_SOURCE_DIMENSIONS,
|
||||
} from "./editorDefaults";
|
||||
import PlaybackControls from "./PlaybackControls";
|
||||
import {
|
||||
createProjectData,
|
||||
@@ -71,11 +77,6 @@ import {
|
||||
DEFAULT_ANNOTATION_SIZE,
|
||||
DEFAULT_ANNOTATION_STYLE,
|
||||
DEFAULT_BLUR_DATA,
|
||||
DEFAULT_CURSOR_CLICK_BOUNCE,
|
||||
DEFAULT_CURSOR_CLIP_TO_BOUNDS,
|
||||
DEFAULT_CURSOR_MOTION_BLUR,
|
||||
DEFAULT_CURSOR_SIZE,
|
||||
DEFAULT_CURSOR_SMOOTHING,
|
||||
DEFAULT_FIGURE_DATA,
|
||||
DEFAULT_PLAYBACK_SPEED,
|
||||
DEFAULT_ZOOM_DEPTH,
|
||||
@@ -203,11 +204,15 @@ export default function VideoEditor() {
|
||||
const [exportError, setExportError] = useState<string | null>(null);
|
||||
const [showExportDialog, setShowExportDialog] = useState(false);
|
||||
const [showNewRecordingDialog, setShowNewRecordingDialog] = useState(false);
|
||||
const [exportQuality, setExportQuality] = useState<ExportQuality>("good");
|
||||
const [exportFormat, setExportFormat] = useState<ExportFormat>("mp4");
|
||||
const [gifFrameRate, setGifFrameRate] = useState<GifFrameRate>(15);
|
||||
const [gifLoop, setGifLoop] = useState(true);
|
||||
const [gifSizePreset, setGifSizePreset] = useState<GifSizePreset>("medium");
|
||||
const [exportQuality, setExportQuality] = useState<ExportQuality>(
|
||||
DEFAULT_EXPORT_SETTINGS.quality,
|
||||
);
|
||||
const [exportFormat, setExportFormat] = useState<ExportFormat>(DEFAULT_EXPORT_SETTINGS.format);
|
||||
const [gifFrameRate, setGifFrameRate] = useState<GifFrameRate>(DEFAULT_GIF_SETTINGS.frameRate);
|
||||
const [gifLoop, setGifLoop] = useState(DEFAULT_GIF_SETTINGS.loop);
|
||||
const [gifSizePreset, setGifSizePreset] = useState<GifSizePreset>(
|
||||
DEFAULT_GIF_SETTINGS.sizePreset,
|
||||
);
|
||||
const [exportedFilePath, setExportedFilePath] = useState<string | null>(null);
|
||||
const [lastSavedSnapshot, setLastSavedSnapshot] = useState<string | null>(null);
|
||||
const [unsavedExport, setUnsavedExport] = useState<{
|
||||
@@ -238,12 +243,14 @@ export default function VideoEditor() {
|
||||
}, [cursorRecordingData, cursorTelemetry]);
|
||||
|
||||
// Cursor & motion blur visual settings (non-undoable preferences)
|
||||
const [showCursor, setShowCursor] = useState(true);
|
||||
const [cursorSize, setCursorSize] = useState(DEFAULT_CURSOR_SIZE);
|
||||
const [cursorSmoothing, setCursorSmoothing] = useState(DEFAULT_CURSOR_SMOOTHING);
|
||||
const [cursorMotionBlur, setCursorMotionBlur] = useState(DEFAULT_CURSOR_MOTION_BLUR);
|
||||
const [cursorClickBounce, setCursorClickBounce] = useState(DEFAULT_CURSOR_CLICK_BOUNCE);
|
||||
const [cursorClipToBounds, setCursorClipToBounds] = useState(DEFAULT_CURSOR_CLIP_TO_BOUNDS);
|
||||
const [showCursor, setShowCursor] = useState(DEFAULT_CURSOR_SETTINGS.show);
|
||||
const [cursorSize, setCursorSize] = useState(DEFAULT_CURSOR_SETTINGS.size);
|
||||
const [cursorSmoothing, setCursorSmoothing] = useState(DEFAULT_CURSOR_SETTINGS.smoothing);
|
||||
const [cursorMotionBlur, setCursorMotionBlur] = useState(DEFAULT_CURSOR_SETTINGS.motionBlur);
|
||||
const [cursorClickBounce, setCursorClickBounce] = useState(DEFAULT_CURSOR_SETTINGS.clickBounce);
|
||||
const [cursorClipToBounds, setCursorClipToBounds] = useState(
|
||||
DEFAULT_CURSOR_SETTINGS.clipToBounds,
|
||||
);
|
||||
const [nativePlatform, setNativePlatform] = useState<NativePlatform | null>(null);
|
||||
const [recordingCursorCaptureMode, setRecordingCursorCaptureMode] =
|
||||
useState<CursorCaptureMode | null>(null);
|
||||
@@ -1575,8 +1582,8 @@ export default function VideoEditor() {
|
||||
videoPlaybackRef.current?.pause();
|
||||
}
|
||||
|
||||
const sourceWidth = video.videoWidth || 1920;
|
||||
const sourceHeight = video.videoHeight || 1080;
|
||||
const sourceWidth = video.videoWidth || DEFAULT_SOURCE_DIMENSIONS.width;
|
||||
const sourceHeight = video.videoHeight || DEFAULT_SOURCE_DIMENSIONS.height;
|
||||
const effectiveSourceDimensions = calculateEffectiveSourceDimensions(
|
||||
sourceWidth,
|
||||
sourceHeight,
|
||||
@@ -1590,8 +1597,8 @@ export default function VideoEditor() {
|
||||
// Get preview CONTAINER dimensions for scaling
|
||||
const playbackRef = videoPlaybackRef.current;
|
||||
const containerElement = playbackRef?.containerRef?.current;
|
||||
const previewWidth = containerElement?.clientWidth || 1920;
|
||||
const previewHeight = containerElement?.clientHeight || 1080;
|
||||
const previewWidth = containerElement?.clientWidth || DEFAULT_SOURCE_DIMENSIONS.width;
|
||||
const previewHeight = containerElement?.clientHeight || DEFAULT_SOURCE_DIMENSIONS.height;
|
||||
|
||||
if (settings.format === "gif" && settings.gifConfig) {
|
||||
// GIF Export
|
||||
@@ -1845,8 +1852,8 @@ export default function VideoEditor() {
|
||||
}
|
||||
|
||||
// Build export settings from current state
|
||||
const sourceWidth = video.videoWidth || 1920;
|
||||
const sourceHeight = video.videoHeight || 1080;
|
||||
const sourceWidth = video.videoWidth || DEFAULT_SOURCE_DIMENSIONS.width;
|
||||
const sourceHeight = video.videoHeight || DEFAULT_SOURCE_DIMENSIONS.height;
|
||||
const effectiveSourceDimensions = calculateEffectiveSourceDimensions(
|
||||
sourceWidth,
|
||||
sourceHeight,
|
||||
@@ -2050,8 +2057,10 @@ export default function VideoEditor() {
|
||||
aspectRatio:
|
||||
aspectRatio === "native"
|
||||
? getNativeAspectRatioValue(
|
||||
videoPlaybackRef.current?.video?.videoWidth || 1920,
|
||||
videoPlaybackRef.current?.video?.videoHeight || 1080,
|
||||
videoPlaybackRef.current?.video?.videoWidth ||
|
||||
DEFAULT_SOURCE_DIMENSIONS.width,
|
||||
videoPlaybackRef.current?.video?.videoHeight ||
|
||||
DEFAULT_SOURCE_DIMENSIONS.height,
|
||||
cropRegion,
|
||||
)
|
||||
: getAspectRatioValue(aspectRatio),
|
||||
@@ -2217,21 +2226,27 @@ export default function VideoEditor() {
|
||||
onGifSizePresetChange={setGifSizePreset}
|
||||
gifOutputDimensions={calculateOutputDimensions(
|
||||
calculateEffectiveSourceDimensions(
|
||||
videoPlaybackRef.current?.video?.videoWidth || 1920,
|
||||
videoPlaybackRef.current?.video?.videoHeight || 1080,
|
||||
videoPlaybackRef.current?.video?.videoWidth ||
|
||||
DEFAULT_SOURCE_DIMENSIONS.width,
|
||||
videoPlaybackRef.current?.video?.videoHeight ||
|
||||
DEFAULT_SOURCE_DIMENSIONS.height,
|
||||
cropRegion,
|
||||
).width,
|
||||
calculateEffectiveSourceDimensions(
|
||||
videoPlaybackRef.current?.video?.videoWidth || 1920,
|
||||
videoPlaybackRef.current?.video?.videoHeight || 1080,
|
||||
videoPlaybackRef.current?.video?.videoWidth ||
|
||||
DEFAULT_SOURCE_DIMENSIONS.width,
|
||||
videoPlaybackRef.current?.video?.videoHeight ||
|
||||
DEFAULT_SOURCE_DIMENSIONS.height,
|
||||
cropRegion,
|
||||
).height,
|
||||
gifSizePreset,
|
||||
GIF_SIZE_PRESETS,
|
||||
aspectRatio === "native"
|
||||
? getNativeAspectRatioValue(
|
||||
videoPlaybackRef.current?.video?.videoWidth || 1920,
|
||||
videoPlaybackRef.current?.video?.videoHeight || 1080,
|
||||
videoPlaybackRef.current?.video?.videoWidth ||
|
||||
DEFAULT_SOURCE_DIMENSIONS.width,
|
||||
videoPlaybackRef.current?.video?.videoHeight ||
|
||||
DEFAULT_SOURCE_DIMENSIONS.height,
|
||||
cropRegion,
|
||||
)
|
||||
: getAspectRatioValue(aspectRatio),
|
||||
|
||||
@@ -49,15 +49,16 @@ import {
|
||||
getNativeAspectRatioValue,
|
||||
} from "@/utils/aspectRatioUtils";
|
||||
import { AnnotationOverlay } from "./AnnotationOverlay";
|
||||
import {
|
||||
DEFAULT_CURSOR_SETTINGS,
|
||||
DEFAULT_EDITOR_LAYOUT_SETTINGS,
|
||||
DEFAULT_SOURCE_DIMENSIONS,
|
||||
} from "./editorDefaults";
|
||||
import {
|
||||
type AnnotationRegion,
|
||||
type BlurData,
|
||||
type CursorTelemetryPoint,
|
||||
computeRotation3DContainScale,
|
||||
DEFAULT_CURSOR_CLICK_BOUNCE,
|
||||
DEFAULT_CURSOR_MOTION_BLUR,
|
||||
DEFAULT_CURSOR_SIZE,
|
||||
DEFAULT_CURSOR_SMOOTHING,
|
||||
DEFAULT_ROTATION_3D,
|
||||
isRotation3DIdentity,
|
||||
lerpRotation3D,
|
||||
@@ -244,7 +245,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
showBlur,
|
||||
motionBlurAmount = 0,
|
||||
borderRadius = 0,
|
||||
padding = 50,
|
||||
padding = DEFAULT_EDITOR_LAYOUT_SETTINGS.padding,
|
||||
cropRegion,
|
||||
trimRegions = [],
|
||||
speedRegions = [],
|
||||
@@ -265,11 +266,11 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
cursorTelemetry = [],
|
||||
cursorClickTimestamps = [],
|
||||
showCursor = false,
|
||||
cursorSize = DEFAULT_CURSOR_SIZE,
|
||||
cursorSmoothing = DEFAULT_CURSOR_SMOOTHING,
|
||||
cursorMotionBlur = DEFAULT_CURSOR_MOTION_BLUR,
|
||||
cursorClickBounce = DEFAULT_CURSOR_CLICK_BOUNCE,
|
||||
cursorClipToBounds = false,
|
||||
cursorSize = DEFAULT_CURSOR_SETTINGS.size,
|
||||
cursorSmoothing = DEFAULT_CURSOR_SETTINGS.smoothing,
|
||||
cursorMotionBlur = DEFAULT_CURSOR_SETTINGS.motionBlur,
|
||||
cursorClickBounce = DEFAULT_CURSOR_SETTINGS.clickBounce,
|
||||
cursorClipToBounds = DEFAULT_CURSOR_SETTINGS.clipToBounds,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
@@ -1824,8 +1825,8 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
aspectRatio,
|
||||
aspectRatio === "native"
|
||||
? getNativeAspectRatioValue(
|
||||
lockedVideoDimensionsRef.current?.width || 1920,
|
||||
lockedVideoDimensionsRef.current?.height || 1080,
|
||||
lockedVideoDimensionsRef.current?.width || DEFAULT_SOURCE_DIMENSIONS.width,
|
||||
lockedVideoDimensionsRef.current?.height || DEFAULT_SOURCE_DIMENSIONS.height,
|
||||
cropRegion,
|
||||
)
|
||||
: undefined,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { INITIAL_EDITOR_STATE } from "@/hooks/useEditorHistory";
|
||||
import { DEFAULT_PREFS } from "@/lib/userPreferences";
|
||||
import {
|
||||
DEFAULT_EDITOR_APPEARANCE_SETTINGS,
|
||||
DEFAULT_EDITOR_LAYOUT_SETTINGS,
|
||||
DEFAULT_EXPORT_SETTINGS,
|
||||
DEFAULT_GIF_SETTINGS,
|
||||
DEFAULT_WEBCAM_SETTINGS,
|
||||
} from "./editorDefaults";
|
||||
import { normalizeProjectEditor } from "./projectPersistence";
|
||||
|
||||
describe("editor defaults SSOT", () => {
|
||||
it("keeps history defaults aligned with editor defaults", () => {
|
||||
expect(INITIAL_EDITOR_STATE).toMatchObject({
|
||||
...DEFAULT_EDITOR_APPEARANCE_SETTINGS,
|
||||
padding: DEFAULT_EDITOR_LAYOUT_SETTINGS.padding,
|
||||
aspectRatio: DEFAULT_EDITOR_LAYOUT_SETTINGS.aspectRatio,
|
||||
cropRegion: DEFAULT_EDITOR_LAYOUT_SETTINGS.cropRegion,
|
||||
wallpaper: DEFAULT_EDITOR_LAYOUT_SETTINGS.wallpaper,
|
||||
webcamLayoutPreset: DEFAULT_WEBCAM_SETTINGS.layoutPreset,
|
||||
webcamMaskShape: DEFAULT_WEBCAM_SETTINGS.maskShape,
|
||||
webcamSizePreset: DEFAULT_WEBCAM_SETTINGS.sizePreset,
|
||||
webcamPosition: DEFAULT_WEBCAM_SETTINGS.position,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps user preference defaults aligned with editor and export defaults", () => {
|
||||
expect(DEFAULT_PREFS).toMatchObject({
|
||||
padding: DEFAULT_EDITOR_LAYOUT_SETTINGS.padding,
|
||||
aspectRatio: DEFAULT_EDITOR_LAYOUT_SETTINGS.aspectRatio,
|
||||
exportQuality: DEFAULT_EXPORT_SETTINGS.quality,
|
||||
exportFormat: DEFAULT_EXPORT_SETTINGS.format,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps project fallback normalization aligned with editor defaults", () => {
|
||||
expect(normalizeProjectEditor({})).toMatchObject({
|
||||
...DEFAULT_EDITOR_APPEARANCE_SETTINGS,
|
||||
padding: DEFAULT_EDITOR_LAYOUT_SETTINGS.padding,
|
||||
cropRegion: DEFAULT_EDITOR_LAYOUT_SETTINGS.cropRegion,
|
||||
wallpaper: DEFAULT_EDITOR_LAYOUT_SETTINGS.wallpaper,
|
||||
aspectRatio: DEFAULT_EDITOR_LAYOUT_SETTINGS.aspectRatio,
|
||||
webcamLayoutPreset: DEFAULT_WEBCAM_SETTINGS.layoutPreset,
|
||||
webcamMaskShape: DEFAULT_WEBCAM_SETTINGS.maskShape,
|
||||
webcamSizePreset: DEFAULT_WEBCAM_SETTINGS.sizePreset,
|
||||
webcamPosition: DEFAULT_WEBCAM_SETTINGS.position,
|
||||
exportQuality: DEFAULT_EXPORT_SETTINGS.quality,
|
||||
exportFormat: DEFAULT_EXPORT_SETTINGS.format,
|
||||
gifFrameRate: DEFAULT_GIF_SETTINGS.frameRate,
|
||||
gifLoop: DEFAULT_GIF_SETTINGS.loop,
|
||||
gifSizePreset: DEFAULT_GIF_SETTINGS.sizePreset,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import type { ExportFormat, ExportQuality, GifFrameRate, GifSizePreset } from "@/lib/exporter";
|
||||
import { DEFAULT_WALLPAPER } from "@/lib/wallpaper";
|
||||
import type { AspectRatio } from "@/utils/aspectRatioUtils";
|
||||
import {
|
||||
type CursorVisualSettings,
|
||||
DEFAULT_CROP_REGION,
|
||||
DEFAULT_CURSOR_CLICK_BOUNCE,
|
||||
DEFAULT_CURSOR_CLIP_TO_BOUNDS,
|
||||
DEFAULT_CURSOR_MOTION_BLUR,
|
||||
DEFAULT_CURSOR_SIZE,
|
||||
DEFAULT_CURSOR_SMOOTHING,
|
||||
DEFAULT_WEBCAM_LAYOUT_PRESET,
|
||||
DEFAULT_WEBCAM_MASK_SHAPE,
|
||||
DEFAULT_WEBCAM_POSITION,
|
||||
DEFAULT_WEBCAM_SIZE_PRESET,
|
||||
type WebcamLayoutPreset,
|
||||
type WebcamMaskShape,
|
||||
type WebcamPosition,
|
||||
type WebcamSizePreset,
|
||||
} from "./types";
|
||||
|
||||
export const DEFAULT_SOURCE_DIMENSIONS = {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
} as const;
|
||||
|
||||
export const DEFAULT_GIF_OUTPUT_DIMENSIONS = {
|
||||
width: 1280,
|
||||
height: 720,
|
||||
} as const;
|
||||
|
||||
export const DEFAULT_EDITOR_APPEARANCE_SETTINGS: {
|
||||
shadowIntensity: number;
|
||||
showBlur: boolean;
|
||||
motionBlurAmount: number;
|
||||
borderRadius: number;
|
||||
} = {
|
||||
shadowIntensity: 0,
|
||||
showBlur: false,
|
||||
motionBlurAmount: 0,
|
||||
borderRadius: 0,
|
||||
};
|
||||
|
||||
export const DEFAULT_EDITOR_LAYOUT_SETTINGS: {
|
||||
padding: number;
|
||||
aspectRatio: AspectRatio;
|
||||
cropRegion: typeof DEFAULT_CROP_REGION;
|
||||
wallpaper: string;
|
||||
} = {
|
||||
padding: 50,
|
||||
aspectRatio: "16:9",
|
||||
cropRegion: DEFAULT_CROP_REGION,
|
||||
wallpaper: DEFAULT_WALLPAPER,
|
||||
};
|
||||
|
||||
export const DEFAULT_WEBCAM_SETTINGS = {
|
||||
layoutPreset: DEFAULT_WEBCAM_LAYOUT_PRESET,
|
||||
maskShape: DEFAULT_WEBCAM_MASK_SHAPE,
|
||||
sizePreset: DEFAULT_WEBCAM_SIZE_PRESET,
|
||||
position: DEFAULT_WEBCAM_POSITION,
|
||||
} as const satisfies {
|
||||
layoutPreset: WebcamLayoutPreset;
|
||||
maskShape: WebcamMaskShape;
|
||||
sizePreset: WebcamSizePreset;
|
||||
position: WebcamPosition | null;
|
||||
};
|
||||
|
||||
export const DEFAULT_CURSOR_SETTINGS: CursorVisualSettings & { show: boolean } = {
|
||||
show: true,
|
||||
size: DEFAULT_CURSOR_SIZE,
|
||||
smoothing: DEFAULT_CURSOR_SMOOTHING,
|
||||
motionBlur: DEFAULT_CURSOR_MOTION_BLUR,
|
||||
clickBounce: DEFAULT_CURSOR_CLICK_BOUNCE,
|
||||
clipToBounds: DEFAULT_CURSOR_CLIP_TO_BOUNDS,
|
||||
};
|
||||
|
||||
export const DEFAULT_EXPORT_SETTINGS: {
|
||||
quality: ExportQuality;
|
||||
format: ExportFormat;
|
||||
} = {
|
||||
quality: "good",
|
||||
format: "mp4",
|
||||
};
|
||||
|
||||
export const DEFAULT_GIF_SETTINGS: {
|
||||
frameRate: GifFrameRate;
|
||||
loop: boolean;
|
||||
sizePreset: GifSizePreset;
|
||||
outputDimensions: typeof DEFAULT_GIF_OUTPUT_DIMENSIONS;
|
||||
} = {
|
||||
frameRate: 15,
|
||||
loop: true,
|
||||
sizePreset: "medium",
|
||||
outputDimensions: DEFAULT_GIF_OUTPUT_DIMENSIONS,
|
||||
};
|
||||
@@ -4,6 +4,13 @@ import type { ProjectMedia } from "@/lib/recordingSession";
|
||||
import { normalizeProjectMedia } from "@/lib/recordingSession";
|
||||
import { DEFAULT_WALLPAPER, WALLPAPER_PATHS } from "@/lib/wallpaper";
|
||||
import { ASPECT_RATIOS, type AspectRatio, isPortraitAspectRatio } from "@/utils/aspectRatioUtils";
|
||||
import {
|
||||
DEFAULT_EDITOR_APPEARANCE_SETTINGS,
|
||||
DEFAULT_EDITOR_LAYOUT_SETTINGS,
|
||||
DEFAULT_EXPORT_SETTINGS,
|
||||
DEFAULT_GIF_SETTINGS,
|
||||
DEFAULT_WEBCAM_SETTINGS,
|
||||
} from "./editorDefaults";
|
||||
import {
|
||||
type AnnotationRegion,
|
||||
type CropRegion,
|
||||
@@ -15,14 +22,10 @@ import {
|
||||
DEFAULT_BLUR_DATA,
|
||||
DEFAULT_BLUR_FREEHAND_POINTS,
|
||||
DEFAULT_BLUR_INTENSITY,
|
||||
DEFAULT_CROP_REGION,
|
||||
DEFAULT_FIGURE_DATA,
|
||||
DEFAULT_PLAYBACK_SPEED,
|
||||
DEFAULT_WEBCAM_LAYOUT_PRESET,
|
||||
DEFAULT_WEBCAM_MASK_SHAPE,
|
||||
DEFAULT_WEBCAM_POSITION,
|
||||
DEFAULT_WEBCAM_SIZE_PRESET,
|
||||
DEFAULT_ZOOM_DEPTH,
|
||||
DEFAULT_ZOOM_MOTION_BLUR,
|
||||
MAX_BLUR_BLOCK_SIZE,
|
||||
MAX_BLUR_INTENSITY,
|
||||
MAX_PLAYBACK_SPEED,
|
||||
@@ -104,13 +107,13 @@ function computeNormalizedWebcamLayoutPreset(
|
||||
case "vertical-stack":
|
||||
return isPortraitAspectRatio(normalizedAspectRatio)
|
||||
? webcamLayoutPreset
|
||||
: DEFAULT_WEBCAM_LAYOUT_PRESET;
|
||||
: DEFAULT_WEBCAM_SETTINGS.layoutPreset;
|
||||
case "dual-frame":
|
||||
return isPortraitAspectRatio(normalizedAspectRatio)
|
||||
? DEFAULT_WEBCAM_LAYOUT_PRESET
|
||||
? DEFAULT_WEBCAM_SETTINGS.layoutPreset
|
||||
: webcamLayoutPreset;
|
||||
default:
|
||||
return DEFAULT_WEBCAM_LAYOUT_PRESET;
|
||||
return DEFAULT_WEBCAM_SETTINGS.layoutPreset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +214,7 @@ export function normalizeProjectEditor(editor: Partial<ProjectEditorState>): Pro
|
||||
editor.aspectRatio as AspectRatio,
|
||||
)
|
||||
? (editor.aspectRatio as AspectRatio)
|
||||
: "16:9";
|
||||
: DEFAULT_EDITOR_LAYOUT_SETTINGS.aspectRatio;
|
||||
const normalizedWebcamLayoutPreset = computeNormalizedWebcamLayoutPreset(
|
||||
editor.webcamLayoutPreset,
|
||||
normalizedAspectRatio,
|
||||
@@ -226,7 +229,7 @@ export function normalizeProjectEditor(editor: Partial<ProjectEditorState>): Pro
|
||||
cx: clamp((editor.webcamPosition as WebcamPosition).cx, 0, 1),
|
||||
cy: clamp((editor.webcamPosition as WebcamPosition).cy, 0, 1),
|
||||
}
|
||||
: DEFAULT_WEBCAM_POSITION;
|
||||
: DEFAULT_WEBCAM_SETTINGS.position;
|
||||
|
||||
const normalizedZoomRegions: ZoomRegion[] = Array.isArray(editor.zoomRegions)
|
||||
? editor.zoomRegions
|
||||
@@ -413,16 +416,16 @@ export function normalizeProjectEditor(editor: Partial<ProjectEditorState>): Pro
|
||||
|
||||
const rawCropX = isFiniteNumber(editor.cropRegion?.x)
|
||||
? editor.cropRegion.x
|
||||
: DEFAULT_CROP_REGION.x;
|
||||
: DEFAULT_EDITOR_LAYOUT_SETTINGS.cropRegion.x;
|
||||
const rawCropY = isFiniteNumber(editor.cropRegion?.y)
|
||||
? editor.cropRegion.y
|
||||
: DEFAULT_CROP_REGION.y;
|
||||
: DEFAULT_EDITOR_LAYOUT_SETTINGS.cropRegion.y;
|
||||
const rawCropWidth = isFiniteNumber(editor.cropRegion?.width)
|
||||
? editor.cropRegion.width
|
||||
: DEFAULT_CROP_REGION.width;
|
||||
: DEFAULT_EDITOR_LAYOUT_SETTINGS.cropRegion.width;
|
||||
const rawCropHeight = isFiniteNumber(editor.cropRegion?.height)
|
||||
? editor.cropRegion.height
|
||||
: DEFAULT_CROP_REGION.height;
|
||||
: DEFAULT_EDITOR_LAYOUT_SETTINGS.cropRegion.height;
|
||||
|
||||
const cropX = clamp(rawCropX, 0, 1);
|
||||
const cropY = clamp(rawCropY, 0, 1);
|
||||
@@ -433,18 +436,29 @@ export function normalizeProjectEditor(editor: Partial<ProjectEditorState>): Pro
|
||||
wallpaper:
|
||||
typeof editor.wallpaper === "string"
|
||||
? normalizeWallpaperValue(editor.wallpaper)
|
||||
: DEFAULT_WALLPAPER,
|
||||
shadowIntensity: typeof editor.shadowIntensity === "number" ? editor.shadowIntensity : 0,
|
||||
showBlur: typeof editor.showBlur === "boolean" ? editor.showBlur : false,
|
||||
: DEFAULT_EDITOR_LAYOUT_SETTINGS.wallpaper,
|
||||
shadowIntensity:
|
||||
typeof editor.shadowIntensity === "number"
|
||||
? editor.shadowIntensity
|
||||
: DEFAULT_EDITOR_APPEARANCE_SETTINGS.shadowIntensity,
|
||||
showBlur:
|
||||
typeof editor.showBlur === "boolean"
|
||||
? editor.showBlur
|
||||
: DEFAULT_EDITOR_APPEARANCE_SETTINGS.showBlur,
|
||||
motionBlurAmount: isFiniteNumber(editor.motionBlurAmount)
|
||||
? clamp(editor.motionBlurAmount, 0, 1)
|
||||
: typeof (editor as { motionBlurEnabled?: unknown }).motionBlurEnabled === "boolean"
|
||||
? (editor as { motionBlurEnabled?: boolean }).motionBlurEnabled
|
||||
? 0.35
|
||||
: 0
|
||||
: 0,
|
||||
borderRadius: typeof editor.borderRadius === "number" ? editor.borderRadius : 0,
|
||||
padding: isFiniteNumber(editor.padding) ? clamp(editor.padding, 0, 100) : 50,
|
||||
? DEFAULT_ZOOM_MOTION_BLUR
|
||||
: DEFAULT_EDITOR_APPEARANCE_SETTINGS.motionBlurAmount
|
||||
: DEFAULT_EDITOR_APPEARANCE_SETTINGS.motionBlurAmount,
|
||||
borderRadius:
|
||||
typeof editor.borderRadius === "number"
|
||||
? editor.borderRadius
|
||||
: DEFAULT_EDITOR_APPEARANCE_SETTINGS.borderRadius,
|
||||
padding: isFiniteNumber(editor.padding)
|
||||
? clamp(editor.padding, 0, 100)
|
||||
: DEFAULT_EDITOR_LAYOUT_SETTINGS.padding,
|
||||
cropRegion: {
|
||||
x: cropX,
|
||||
y: cropY,
|
||||
@@ -463,31 +477,31 @@ export function normalizeProjectEditor(editor: Partial<ProjectEditorState>): Pro
|
||||
editor.webcamMaskShape === "square" ||
|
||||
editor.webcamMaskShape === "rounded"
|
||||
? editor.webcamMaskShape
|
||||
: DEFAULT_WEBCAM_MASK_SHAPE,
|
||||
: DEFAULT_WEBCAM_SETTINGS.maskShape,
|
||||
webcamSizePreset:
|
||||
typeof editor.webcamSizePreset === "number" && isFiniteNumber(editor.webcamSizePreset)
|
||||
? Math.max(10, Math.min(50, editor.webcamSizePreset))
|
||||
: DEFAULT_WEBCAM_SIZE_PRESET,
|
||||
: DEFAULT_WEBCAM_SETTINGS.sizePreset,
|
||||
webcamPosition: normalizedWebcamPosition,
|
||||
exportQuality:
|
||||
editor.exportQuality === "medium" || editor.exportQuality === "source"
|
||||
? editor.exportQuality
|
||||
: "good",
|
||||
exportFormat: editor.exportFormat === "gif" ? "gif" : "mp4",
|
||||
: DEFAULT_EXPORT_SETTINGS.quality,
|
||||
exportFormat: editor.exportFormat === "gif" ? "gif" : DEFAULT_EXPORT_SETTINGS.format,
|
||||
gifFrameRate:
|
||||
editor.gifFrameRate === 15 ||
|
||||
editor.gifFrameRate === 20 ||
|
||||
editor.gifFrameRate === 25 ||
|
||||
editor.gifFrameRate === 30
|
||||
? editor.gifFrameRate
|
||||
: 15,
|
||||
gifLoop: typeof editor.gifLoop === "boolean" ? editor.gifLoop : true,
|
||||
: DEFAULT_GIF_SETTINGS.frameRate,
|
||||
gifLoop: typeof editor.gifLoop === "boolean" ? editor.gifLoop : DEFAULT_GIF_SETTINGS.loop,
|
||||
gifSizePreset:
|
||||
editor.gifSizePreset === "medium" ||
|
||||
editor.gifSizePreset === "large" ||
|
||||
editor.gifSizePreset === "original"
|
||||
? editor.gifSizePreset
|
||||
: "medium",
|
||||
: DEFAULT_GIF_SETTINGS.sizePreset,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -188,6 +188,7 @@ export interface CursorVisualSettings {
|
||||
smoothing: number;
|
||||
motionBlur: number;
|
||||
clickBounce: number;
|
||||
clipToBounds: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_CURSOR_SIZE = 3.0;
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import {
|
||||
DEFAULT_EDITOR_APPEARANCE_SETTINGS,
|
||||
DEFAULT_EDITOR_LAYOUT_SETTINGS,
|
||||
DEFAULT_WEBCAM_SETTINGS,
|
||||
} from "@/components/video-editor/editorDefaults";
|
||||
import type {
|
||||
AnnotationRegion,
|
||||
CropRegion,
|
||||
@@ -10,14 +15,7 @@ import type {
|
||||
WebcamSizePreset,
|
||||
ZoomRegion,
|
||||
} from "@/components/video-editor/types";
|
||||
import {
|
||||
DEFAULT_CROP_REGION,
|
||||
DEFAULT_WEBCAM_LAYOUT_PRESET,
|
||||
DEFAULT_WEBCAM_MASK_SHAPE,
|
||||
DEFAULT_WEBCAM_POSITION,
|
||||
DEFAULT_WEBCAM_SIZE_PRESET,
|
||||
} from "@/components/video-editor/types";
|
||||
import { DEFAULT_WALLPAPER } from "@/lib/wallpaper";
|
||||
import { DEFAULT_CROP_REGION } from "@/components/video-editor/types";
|
||||
import type { AspectRatio } from "@/utils/aspectRatioUtils";
|
||||
|
||||
// Undoable state — selection IDs are intentionally excluded (undoing a
|
||||
@@ -47,17 +45,17 @@ export const INITIAL_EDITOR_STATE: EditorState = {
|
||||
speedRegions: [],
|
||||
annotationRegions: [],
|
||||
cropRegion: DEFAULT_CROP_REGION,
|
||||
wallpaper: DEFAULT_WALLPAPER,
|
||||
shadowIntensity: 0,
|
||||
showBlur: false,
|
||||
motionBlurAmount: 0,
|
||||
borderRadius: 0,
|
||||
padding: 50,
|
||||
aspectRatio: "16:9",
|
||||
webcamLayoutPreset: DEFAULT_WEBCAM_LAYOUT_PRESET,
|
||||
webcamMaskShape: DEFAULT_WEBCAM_MASK_SHAPE,
|
||||
webcamSizePreset: DEFAULT_WEBCAM_SIZE_PRESET,
|
||||
webcamPosition: DEFAULT_WEBCAM_POSITION,
|
||||
wallpaper: DEFAULT_EDITOR_LAYOUT_SETTINGS.wallpaper,
|
||||
shadowIntensity: DEFAULT_EDITOR_APPEARANCE_SETTINGS.shadowIntensity,
|
||||
showBlur: DEFAULT_EDITOR_APPEARANCE_SETTINGS.showBlur,
|
||||
motionBlurAmount: DEFAULT_EDITOR_APPEARANCE_SETTINGS.motionBlurAmount,
|
||||
borderRadius: DEFAULT_EDITOR_APPEARANCE_SETTINGS.borderRadius,
|
||||
padding: DEFAULT_EDITOR_LAYOUT_SETTINGS.padding,
|
||||
aspectRatio: DEFAULT_EDITOR_LAYOUT_SETTINGS.aspectRatio,
|
||||
webcamLayoutPreset: DEFAULT_WEBCAM_SETTINGS.layoutPreset,
|
||||
webcamMaskShape: DEFAULT_WEBCAM_SETTINGS.maskShape,
|
||||
webcamSizePreset: DEFAULT_WEBCAM_SETTINGS.sizePreset,
|
||||
webcamPosition: DEFAULT_WEBCAM_SETTINGS.position,
|
||||
};
|
||||
|
||||
type StateUpdate = Partial<EditorState> | ((prev: EditorState) => Partial<EditorState>);
|
||||
|
||||
@@ -989,8 +989,8 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
enabled: webcamEnabled,
|
||||
deviceId: webcamDeviceId,
|
||||
deviceName: webcamDeviceName,
|
||||
width: WEBCAM_TARGET_WIDTH,
|
||||
height: WEBCAM_TARGET_HEIGHT,
|
||||
width: 0,
|
||||
height: 0,
|
||||
fps: WEBCAM_TARGET_FRAME_RATE,
|
||||
},
|
||||
cursor: {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
DEFAULT_EDITOR_LAYOUT_SETTINGS,
|
||||
DEFAULT_EXPORT_SETTINGS,
|
||||
} from "@/components/video-editor/editorDefaults";
|
||||
import type { ExportFormat, ExportQuality } from "@/lib/exporter";
|
||||
import type { AspectRatio } from "@/utils/aspectRatioUtils";
|
||||
|
||||
@@ -27,11 +31,11 @@ export interface UserPreferences {
|
||||
exportFolder: string | null;
|
||||
}
|
||||
|
||||
const DEFAULT_PREFS: UserPreferences = {
|
||||
padding: 50,
|
||||
aspectRatio: "16:9",
|
||||
exportQuality: "good",
|
||||
exportFormat: "mp4",
|
||||
export const DEFAULT_PREFS: UserPreferences = {
|
||||
padding: DEFAULT_EDITOR_LAYOUT_SETTINGS.padding,
|
||||
aspectRatio: DEFAULT_EDITOR_LAYOUT_SETTINGS.aspectRatio,
|
||||
exportQuality: DEFAULT_EXPORT_SETTINGS.quality,
|
||||
exportFormat: DEFAULT_EXPORT_SETTINGS.format,
|
||||
exportFolder: null,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user