fix: address remaining ui overhaul review feedback

This commit is contained in:
webadderall
2026-04-16 23:39:27 +10:00
parent a83a58c671
commit 8071c6bb0e
5 changed files with 6215 additions and 5491 deletions
+6153 -5458
View File
File diff suppressed because it is too large Load Diff
+15 -3
View File
@@ -17,33 +17,45 @@ function ContentClamp({ children, className, truncateLength = 50, ...props }: Co
const [open, setOpen] = React.useState(false);
const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
const openPopover = () => {
const clearScheduledClose = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
};
const openPopover = () => {
clearScheduledClose();
setOpen(true);
};
const scheduleClose = () => {
clearScheduledClose();
timeoutRef.current = setTimeout(() => {
setOpen(false);
timeoutRef.current = null;
}, 100);
};
const togglePopover = () => {
clearScheduledClose();
setOpen((currentOpen) => !currentOpen);
};
const handleKeyDown = (event: React.KeyboardEvent<HTMLSpanElement>) => {
if (event.key !== "Enter" && event.key !== " ") {
return;
}
event.preventDefault();
setOpen((currentOpen) => !currentOpen);
togglePopover();
};
React.useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
};
}, []);
@@ -69,7 +81,7 @@ function ContentClamp({ children, className, truncateLength = 50, ...props }: Co
onBlur={scheduleClose}
onClick={(event) => {
event.preventDefault();
setOpen((currentOpen) => !currentOpen);
togglePopover();
}}
onKeyDown={handleKeyDown}
role="button"
+28 -26
View File
@@ -689,6 +689,29 @@ export default function VideoEditor() {
}
}, []);
const effectiveSpeedRegions = useMemo<SpeedRegion[]>(() => {
const clipDerived: SpeedRegion[] = clipRegions
.filter((clip) => clip.speed !== 1)
.map((clip) => ({
id: `clip-speed-${clip.id}`,
startMs: clip.startMs,
endMs: clip.endMs,
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(
(sr) => sr.endMs > cs.startMs && sr.startMs < cs.endMs,
);
if (!overlaps) {
result.push(cs);
}
}
return result;
}, [clipRegions, speedRegions]);
const captureProjectThumbnail = useCallback(async () => {
const previewHandle = videoPlaybackRef.current;
const previewVideo = previewHandle?.video ?? null;
@@ -1452,7 +1475,8 @@ export default function VideoEditor() {
setZoomRegions(normalizedEditor.zoomRegions);
setTrimRegions(normalizedEditor.trimRegions);
setClipRegions(normalizedEditor.clipRegions);
clipInitializedRef.current = normalizedEditor.clipRegions.length > 0;
clipInitializedRef.current =
normalizedEditor.clipRegions.length > 0 || normalizedEditor.trimRegions.length > 0;
setSpeedRegions(normalizedEditor.speedRegions);
setAnnotationRegions(normalizedEditor.annotationRegions);
setAudioRegions(normalizedEditor.audioRegions);
@@ -2268,30 +2292,6 @@ export default function VideoEditor() {
const effectiveZoomRegions = zoomRegions;
// Merge clip speeds into speed regions so playback + export respect per-clip speed
const effectiveSpeedRegions = useMemo<SpeedRegion[]>(() => {
const clipDerived: SpeedRegion[] = clipRegions
.filter((clip) => clip.speed !== 1)
.map((clip) => ({
id: `clip-speed-${clip.id}`,
startMs: clip.startMs,
endMs: clip.endMs,
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(
(sr) => sr.endMs > cs.startMs && sr.startMs < cs.endMs,
);
if (!overlaps) {
result.push(cs);
}
}
return result;
}, [clipRegions, speedRegions]);
function togglePlayPause() {
const playback = videoPlaybackRef.current;
const video = playback?.video;
@@ -2571,6 +2571,7 @@ export default function VideoEditor() {
if (!target) return prev;
const leftId = `clip-${nextClipIdRef.current++}`;
const rightId = `clip-${nextClipIdRef.current++}`;
setSelectedClipId(leftId);
const left: ClipRegion = {
id: leftId,
startMs: target.startMs,
@@ -3466,7 +3467,7 @@ export default function VideoEditor() {
: smokeExportConfig.enabled
? (smokeExportConfig.backendPreference ??
(smokeExportConfig.useNativeExport ? "breeze" : "webcodecs"))
: "auto";
: (settings.backendPreference ?? exportBackendPreference);
const supportedSourceDimensions =
await ensureSupportedMp4SourceDimensions(selectedMp4FrameRate);
const { width: exportWidth, height: exportHeight } =
@@ -3773,6 +3774,7 @@ export default function VideoEditor() {
remountPreview,
showExportSuccessToast,
smokeExportConfig,
exportBackendPreference,
],
);
@@ -130,6 +130,19 @@ function clamp(value: number, min: number, max: number) {
return Math.min(max, Math.max(min, value));
}
function normalizeClipPlaybackSpeed(value: unknown): ClipRegion["speed"] {
return value === 0.25 ||
value === 0.5 ||
value === 0.75 ||
value === 1 ||
value === 1.25 ||
value === 1.5 ||
value === 1.75 ||
value === 2
? value
: 1;
}
export function normalizeExportEncodingMode(value: unknown): ExportEncodingMode {
if (value === "fast" || value === "balanced" || value === "quality") {
return value;
@@ -321,6 +334,10 @@ export function normalizeProjectEditor(editor: Partial<ProjectEditorState>): Pro
id: region.id,
startMs,
endMs,
mode:
region.mode === "auto" || region.mode === "manual"
? region.mode
: "manual",
depth: [1, 2, 3, 4, 5, 6].includes(region.depth)
? region.depth
: DEFAULT_ZOOM_DEPTH,
@@ -380,7 +397,7 @@ export function normalizeProjectEditor(editor: Partial<ProjectEditorState>): Pro
id: region.id,
startMs,
endMs,
speed: isFiniteNumber(region.speed) ? region.speed : 1,
speed: normalizeClipPlaybackSpeed(region.speed),
muted: Boolean(region.muted),
};
})
+1 -3
View File
@@ -137,9 +137,7 @@ export interface ClipRegion {
}
export function getClipSourceEndMs(clip: ClipRegion): number {
const displayDurationMs = Math.max(0, clip.endMs - clip.startMs);
const speed = Number.isFinite(clip.speed) && clip.speed > 0 ? clip.speed : 1;
return Math.round(clip.startMs + displayDurationMs * speed);
return Math.round(Math.max(clip.startMs, clip.endMs));
}
/** Convert clip regions (kept segments) to trim regions (gaps to remove). */