fix: stabilize editor settings and extension UI

This commit is contained in:
webadderall
2026-04-17 01:50:14 +10:00
parent 0206e38e2c
commit c7ed81129e
11 changed files with 2628 additions and 2247 deletions
+14 -5
View File
@@ -9,6 +9,7 @@
"version": "1.1.23",
"hasInstallScript": true,
"dependencies": {
"@phosphor-icons/react": "^2.1.10",
"capturekit": "^1.0.13",
"electron-updater": "^6.8.3",
"ffmpeg-static": "^5.3.0",
@@ -2843,6 +2844,19 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
"node_modules/@phosphor-icons/react": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.10.tgz",
"integrity": "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">= 16.8",
"react-dom": ">= 16.8"
}
},
"node_modules/@pixi/color": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.4.3.tgz",
@@ -9046,7 +9060,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
@@ -9361,7 +9374,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
@@ -11227,7 +11239,6 @@
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
@@ -11240,7 +11251,6 @@
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
@@ -11948,7 +11958,6 @@
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
+1
View File
@@ -37,6 +37,7 @@
"test:watch": "vitest"
},
"dependencies": {
"@phosphor-icons/react": "^2.1.10",
"capturekit": "^1.0.13",
"electron-updater": "^6.8.3",
"ffmpeg-static": "^5.3.0",
File diff suppressed because it is too large Load Diff
+7 -21
View File
@@ -40,6 +40,7 @@ import type {
CaptionCue,
CropRegion,
CursorStyle,
EditorEffectSection,
FigureData,
PlaybackSpeed,
WebcamOverlaySettings,
@@ -111,19 +112,6 @@ const CAPTION_ANIMATION_OPTIONS: Array<{ value: AutoCaptionAnimation; label: str
];
type BackgroundTab = "image" | "video" | "color" | "gradient";
export type EditorEffectSection =
| "scene"
| "cursor"
| "captions"
| "webcam"
| "settings"
| "zoom"
| "frame"
| "crop"
| "extensions"
| "clip"
| `ext:${string}`;
function isHexWallpaper(value: string): boolean {
return /^#(?:[0-9a-f]{3}){1,2}$/i.test(value);
}
@@ -439,8 +427,6 @@ interface SettingsPanelProps {
onSpeedDelete?: (id: string) => void;
}
export default SettingsPanel;
const ZOOM_DEPTH_OPTIONS: Array<{ depth: ZoomDepth; label: string }> = [
{ depth: 1, label: "1.25×" },
{ depth: 2, label: "1.5×" },
@@ -2291,13 +2277,13 @@ export function SettingsPanel({
<p className="mt-1.5 text-[10px] text-slate-500">
{selectedZoomMode === "manual"
? tSettings(
"zoom.modeManualDescription",
"Set a fixed focus point for this zoom",
)
"zoom.modeManualDescription",
"Set a fixed focus point for this zoom",
)
: tSettings(
"zoom.modeAutoDescription",
"Camera follows cursor automatically",
)}
"zoom.modeAutoDescription",
"Camera follows cursor automatically",
)}
</p>
</div>
<div className="grid grid-cols-6 gap-1.5">
+59 -28
View File
@@ -1,32 +1,32 @@
import {
ArrowClockwise as Redo2,
ArrowCounterClockwise as Undo2,
Check,
CaretDown as ChevronDown,
CaretUp as ChevronUp,
Check,
ClosedCaptioning,
Crop,
Cursor,
DownloadSimple as Download,
FloppyDisk as Save,
FolderOpen,
Gear,
MagicWand as WandSparkles,
MagnifyingGlassPlus as ZoomIn,
Pause,
Camera as PhCameraRegular,
Play,
Plus,
Camera as PhCameraRegular,
PuzzlePiece,
ArrowClockwise as Redo2,
FloppyDisk as Save,
Scissors,
SkipBack,
SkipForward,
SpeakerHigh as Volume2,
SpeakerLow as Volume1,
SpeakerX as VolumeX,
Sparkle,
ArrowCounterClockwise as Undo2,
UserCircle as User,
SpeakerLow as Volume1,
SpeakerHigh as Volume2,
SpeakerX as VolumeX,
MagicWand as WandSparkles,
X,
MagnifyingGlassPlus as ZoomIn,
} from "@phosphor-icons/react";
import type { Span } from "dnd-timeline";
import { motion } from "motion/react";
@@ -113,7 +113,7 @@ import {
toFileUrl,
validateProjectData,
} from "./projectPersistence";
import { type EditorEffectSection, SettingsPanel } from "./SettingsPanel";
import { SettingsPanel } from "./SettingsPanel";
import {
APP_HEADER_ICON_BUTTON_CLASS,
DiscordLinkButton,
@@ -153,6 +153,7 @@ import {
DEFAULT_ZOOM_IN_OVERLAP_MS,
DEFAULT_ZOOM_OUT_DURATION_MS,
DEFAULT_ZOOM_OUT_EASING,
type EditorEffectSection,
type FigureData,
getClipSourceEndMs,
type PlaybackSpeed,
@@ -676,7 +677,7 @@ export default function VideoEditor() {
window.clearTimeout(pendingFreshRecordingAutoSuggestTimeoutRef.current);
pendingFreshRecordingAutoSuggestTimeoutRef.current = null;
}
}, [videoPath]);
}, []);
// Auto-activate builtin extensions at editor startup (idempotent)
useEffect(() => {
@@ -783,7 +784,27 @@ export default function VideoEditor() {
annotationRegions,
autoCaptions,
autoCaptionSettings,
speedRegions: effectiveSpeedRegions,
speedRegions: (() => {
const clipDerived: SpeedRegion[] = clipRegions
.filter((clip) => clip.speed !== 1)
.map((clip) => ({
id: `clip-speed-${clip.id}`,
startMs: clip.startMs,
endMs: getClipSourceEndMs(clip),
speed: clip.speed as SpeedRegion["speed"],
}));
if (clipDerived.length === 0) return speedRegions;
const result = [...speedRegions];
for (const cs of clipDerived) {
const overlaps = speedRegions.some(
(sr) => sr.endMs > cs.startMs && sr.startMs < cs.endMs,
);
if (!overlaps) {
result.push(cs);
}
}
return result;
})(),
previewWidth,
previewHeight,
cursorTelemetry,
@@ -870,11 +891,11 @@ export default function VideoEditor() {
cursorStyle,
cursorSway,
cursorTelemetry,
clipRegions,
padding,
shadowIntensity,
showCursor,
speedRegions,
clipRegions,
wallpaper,
webcam,
zoomInDurationMs,
@@ -884,6 +905,7 @@ export default function VideoEditor() {
zoomOutDurationMs,
zoomOutEasing,
zoomRegions,
zoomClassicMode,
]);
const markExportAsSaving = useCallback(() => {
@@ -1271,7 +1293,6 @@ export default function VideoEditor() {
shadowIntensity,
backgroundBlur,
zoomMotionBlur,
autoApplyFreshRecordingAutoZooms,
connectZooms,
zoomInDurationMs,
zoomInOverlapMs,
@@ -1313,6 +1334,7 @@ export default function VideoEditor() {
gifFrameRate,
gifLoop,
gifSizePreset,
frame,
],
);
@@ -1729,8 +1751,9 @@ export default function VideoEditor() {
setVideoPath(sourceVideoUrl);
setCurrentProjectPath(null);
setLastSavedSnapshot(null);
pendingFreshRecordingAutoZoomPathRef.current =
autoApplyFreshRecordingAutoZooms ? sourceVideoUrl : null;
pendingFreshRecordingAutoZoomPathRef.current = autoApplyFreshRecordingAutoZooms
? sourceVideoUrl
: null;
setWebcam((prev) => ({
...prev,
enabled: Boolean(sessionResult.session?.webcamPath),
@@ -1747,8 +1770,9 @@ export default function VideoEditor() {
setVideoPath(sourceVideoUrl);
setCurrentProjectPath(null);
setLastSavedSnapshot(null);
pendingFreshRecordingAutoZoomPathRef.current =
autoApplyFreshRecordingAutoZooms ? sourceVideoUrl : null;
pendingFreshRecordingAutoZoomPathRef.current = autoApplyFreshRecordingAutoZooms
? sourceVideoUrl
: null;
setWebcam((prev) => ({
...prev,
enabled: false,
@@ -1767,8 +1791,13 @@ export default function VideoEditor() {
loadInitialData();
}, [
applyLoadedProject,
autoApplyFreshRecordingAutoZooms,
initialEditorPreferences,
smokeExportConfig.enabled,
smokeExportConfig.inputPath,
smokeExportConfig.webcamInputPath,
smokeExportConfig.webcamShadow,
smokeExportConfig.webcamSize,
]);
useEffect(() => {
@@ -1839,8 +1868,6 @@ export default function VideoEditor() {
cursorStyle,
cursorSize,
cursorSmoothing,
zoomSmoothness,
zoomClassicMode,
cursorMotionBlur,
cursorClickBounce,
cursorClickBounceDuration,
@@ -2362,7 +2389,6 @@ export default function VideoEditor() {
speed: clip.speed as SpeedRegion["speed"],
}));
if (clipDerived.length === 0) return speedRegions;
// Timeline speed regions take precedence; only fill in clip speed where no overlap exists
const result = [...speedRegions];
for (const cs of clipDerived) {
const overlaps = speedRegions.some(
@@ -3852,8 +3878,6 @@ export default function VideoEditor() {
videoPath,
wallpaper,
trimRegions,
speedRegions,
clipRegions,
shadowIntensity,
backgroundBlur,
zoomMotionBlur,
@@ -3894,14 +3918,21 @@ export default function VideoEditor() {
effectiveZoomRegions,
ensureSupportedMp4SourceDimensions,
markExportAsSaving,
mp4FrameRate,
remountPreview,
showExportSuccessToast,
smokeExportConfig.backendPreference,
smokeExportConfig.enabled,
smokeExportConfig.useNativeExport,
smokeExportConfig.maxDecodeQueue,
smokeExportConfig.maxEncodeQueue,
smokeExportConfig.maxPendingFrames,
smokeExportConfig.outputPath,
smokeExportConfig.pipelineModel,
smokeExportConfig.shadowIntensity,
effectiveSpeedRegions,
frame,
smokeExportConfig.encodingMode,
],
);
@@ -4593,9 +4624,7 @@ export default function VideoEditor() {
whileHover={{ opacity: 1 }}
initial={{ opacity: 0.55 }}
>
<motion.span
className="absolute inset-0 rounded-lg bg-white/[0.04] opacity-0 transition group-hover:opacity-100"
/>
<motion.span className="absolute inset-0 rounded-lg bg-white/[0.04] opacity-0 transition group-hover:opacity-100" />
<User className="relative z-10 h-[22px] w-[22px]" />
</motion.button>
</div>
@@ -4921,7 +4950,9 @@ export default function VideoEditor() {
className="h-7 gap-1 rounded-full border border-white/[0.08] bg-white/[0.04] px-2.5 text-[11px] text-white/65 shadow-[inset_0_1px_0_rgba(255,255,255,0.06)] transition-all hover:bg-white/[0.08] hover:text-white"
>
<Plus className="w-3.5 h-3.5" />
<span className="font-medium">{t("editor.toolbar.addLayer")}</span>
<span className="font-medium">
{t("editor.toolbar.addLayer")}
</span>
<ChevronDown className="w-3 h-3" />
</Button>
</DropdownMenuTrigger>
+5 -6
View File
@@ -1,6 +1,5 @@
export { default as VideoEditor } from './VideoEditor';
export { default as VideoPlayback } from './VideoPlayback';
export { default as PlaybackControls } from './PlaybackControls';
export { default as TimelineEditor } from './timeline/TimelineEditor';
export { default as SettingsPanel } from './SettingsPanel';
export { default as PlaybackControls } from "./PlaybackControls";
export { SettingsPanel } from "./SettingsPanel";
export { default as TimelineEditor } from "./timeline/TimelineEditor";
export { default as VideoEditor } from "./VideoEditor";
export { default as VideoPlayback } from "./VideoPlayback";
File diff suppressed because it is too large Load Diff
+21 -8
View File
@@ -20,7 +20,13 @@ export interface CursorTelemetryPoint {
timeMs: number;
cx: number;
cy: number;
interactionType?: "move" | "click" | "double-click" | "right-click" | "middle-click" | "mouseup";
interactionType?:
| "move"
| "click"
| "double-click"
| "right-click"
| "middle-click"
| "mouseup";
cursorType?:
| "arrow"
| "text"
@@ -43,14 +49,22 @@ export interface CursorVisualSettings {
style: CursorStyle;
}
export type CursorStyle =
| "tahoe"
| "dot"
| "figma"
| "mono"
| (string & {}); // extension-contributed cursor styles
export type CursorStyle = "tahoe" | "dot" | "figma" | "mono" | (string & {}); // extension-contributed cursor styles
export const DEFAULT_CURSOR_STYLE: CursorStyle = "tahoe";
export type EditorEffectSection =
| "scene"
| "cursor"
| "captions"
| "webcam"
| "settings"
| "zoom"
| "frame"
| "crop"
| "extensions"
| "clip"
| `ext:${string}`;
export type ZoomTransitionEasing = "recordly" | "glide" | "smooth" | "snappy" | "linear";
export type WebcamCorner = "top-left" | "top-right" | "bottom-left" | "bottom-right";
@@ -183,7 +197,6 @@ export const BLUR_ANNOTATION_STRENGTH = 20;
export const BASE_PREVIEW_WIDTH = 1920;
export const BASE_PREVIEW_HEIGHT = 1080;
export type ArrowDirection =
| "up"
| "down"
+2
View File
@@ -27,6 +27,8 @@
"noMicrophonesFound": "마이크를 찾을 수 없습니다",
"webcam": "웹캠",
"turnOffWebcam": "웹캠 끄기",
"hideFloatingWebcamPreview": "플로팅 미리보기 숨기기",
"showFloatingWebcamPreview": "플로팅 미리보기 표시",
"selectWebcamToEnable": "사용할 웹캠을 선택하세요",
"noWebcamsFound": "웹캠을 찾을 수 없습니다",
"recordingsFolder": "녹화 폴더",
+1
View File
@@ -159,6 +159,7 @@
"high": "높음",
"original": "원본"
},
"fpsTitle": "FPS",
"loop": "반복",
"outputDimensions": "출력: {{dimensions}}px",
"sizePresetOriginalShort": "원본",
+2
View File
@@ -27,6 +27,8 @@
"noMicrophonesFound": "Geen microfoons gevonden",
"webcam": "Webcam",
"turnOffWebcam": "Webcam uitschakelen",
"hideFloatingWebcamPreview": "Zwevende voorbeeldweergave verbergen",
"showFloatingWebcamPreview": "Zwevende voorbeeldweergave tonen",
"selectWebcamToEnable": "Selecteer een webcam om in te schakelen",
"noWebcamsFound": "Geen webcams gevonden",
"recordingsFolder": "Opnamepad",