mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-25 23:35:43 +00:00
fix: address remaining ui overhaul review feedback
This commit is contained in:
+6153
-5458
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
})
|
||||
|
||||
@@ -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). */
|
||||
|
||||
Reference in New Issue
Block a user