diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 42debcad..381cdca5 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -745,8 +745,7 @@ export default function VideoEditor() { } context.imageSmoothingEnabled = true; context.imageSmoothingQuality = "high"; - const editorBgHsl = getComputedStyle(document.documentElement).getPropertyValue("--editor-bg").trim(); - context.fillStyle = editorBgHsl ? `hsl(${editorBgHsl})` : "#111113"; + context.fillStyle = "#111113"; context.fillRect(0, 0, targetWidth, targetHeight); const previewWidth = previewHandle?.containerRef.current?.clientWidth || 1920; @@ -781,7 +780,7 @@ export default function VideoEditor() { padding, cropRegion, webcam, - webcamUrl: resolvedWebcamVideoUrl ?? (webcam.sourcePath ? toFileUrl(webcam.sourcePath) : null), + webcamUrl: webcam.sourcePath ? toFileUrl(webcam.sourcePath) : null, videoWidth: previewVideo.videoWidth, videoHeight: previewVideo.videoHeight, annotationRegions, @@ -1807,6 +1806,7 @@ export default function VideoEditor() { setResolvedWebcamVideoUrl(null); return; } + setResolvedWebcamVideoUrl(null); void resolveVideoUrl(webcam.sourcePath).then((url) => { if (!cancelled) setResolvedWebcamVideoUrl(url); }); @@ -2808,12 +2808,22 @@ export default function VideoEditor() { const handleClipDelete = useCallback( (id: string) => { + const deletedClip = clipRegions.find((c) => c.id === id); setClipRegions((prev) => prev.filter((clip) => clip.id !== id)); + if (deletedClip) { + const { startMs, endMs } = deletedClip; + // Cascade: remove all timeline items fully within the deleted clip's span + setZoomRegions((prev) => prev.filter((r) => r.startMs < startMs || r.endMs > endMs)); + setAnnotationRegions((prev) => prev.filter((r) => r.startMs < startMs || r.endMs > endMs)); + setTrimRegions((prev) => prev.filter((r) => r.startMs < startMs || r.endMs > endMs)); + setSpeedRegions((prev) => prev.filter((r) => r.startMs < startMs || r.endMs > endMs)); + setAudioRegions((prev) => prev.filter((r) => r.startMs < startMs || r.endMs > endMs)); + } if (selectedClipId === id) { setSelectedClipId(null); } }, - [selectedClipId], + [clipRegions, selectedClipId], ); const handleSelectSpeed = useCallback((id: string | null) => { @@ -3539,7 +3549,7 @@ export default function VideoEditor() { videoPadding: padding, cropRegion, webcam, - webcamUrl: resolvedWebcamVideoUrl ?? (webcam.sourcePath ? toFileUrl(webcam.sourcePath) : null), + webcamUrl: webcam.sourcePath ? toFileUrl(webcam.sourcePath) : null, annotationRegions, autoCaptions, autoCaptionSettings, @@ -3708,7 +3718,7 @@ export default function VideoEditor() { padding, cropRegion, webcam, - webcamUrl: resolvedWebcamVideoUrl ?? (webcam.sourcePath ? toFileUrl(webcam.sourcePath) : null), + webcamUrl: webcam.sourcePath ? toFileUrl(webcam.sourcePath) : null, annotationRegions, autoCaptions, autoCaptionSettings, @@ -4177,8 +4187,8 @@ export default function VideoEditor() { ? Math.min( typeof exportProgress?.renderProgress === "number" ? exportProgress.renderProgress - : (exportProgress?.percentage ?? 100), - 100, + : (exportProgress?.percentage ?? 99), + 99, ) : null; const isLightningExportInProgress = @@ -4234,7 +4244,7 @@ export default function VideoEditor() { }) : isExportFinalizing ? t("editor.exportStatus.finalizingPercent", "Finalizing {{percent}}%", { - percent: Math.round(exportFinalizingProgress ?? 100), + percent: Math.round(exportFinalizingProgress ?? 99), }) : t("editor.exportStatus.completePercent", "{{percent}}% complete", { percent: Math.round(exportProgress.percentage), @@ -4258,7 +4268,7 @@ export default function VideoEditor() {
Loading video...
{projectBrowser} - +
); } @@ -4271,21 +4281,21 @@ export default function VideoEditor() { ref={projectBrowserFallbackTriggerRef} type="button" onClick={handleOpenProjectBrowser} - className="rounded-[5px] bg-neutral-800 px-3 py-1.5 text-sm font-semibold text-white shadow-[0_14px_32px_rgba(0,0,0,0.18)] transition-colors hover:bg-neutral-700 dark:bg-white dark:text-black dark:hover:bg-white/90" + className="rounded-[5px] bg-white px-3 py-1.5 text-sm font-semibold text-black shadow-[0_14px_32px_rgba(0,0,0,0.18)] transition-colors hover:bg-white/92" > Open Projects {projectBrowser} - + ); } return ( -
+
-
+
@@ -4348,7 +4358,7 @@ export default function VideoEditor() { ref={projectBrowserTriggerRef} type="button" onClick={handleOpenProjectBrowser} - className="inline-flex h-8 min-w-[96px] items-center justify-center gap-1.5 rounded-[5px] bg-neutral-800 px-4 text-white shadow-[0_14px_32px_rgba(0,0,0,0.18)] transition-colors hover:bg-neutral-700 dark:bg-white dark:text-black dark:hover:bg-white/90" + className="inline-flex h-8 min-w-[96px] items-center justify-center gap-1.5 rounded-[5px] bg-white px-4 text-black shadow-[0_14px_32px_rgba(0,0,0,0.18)] transition-colors hover:bg-white/92" > @@ -4358,7 +4368,7 @@ export default function VideoEditor() { -
+
{isExporting ? ( -
+
-

+

{t("editor.exportStatus.exporting", "Exporting")}

-

+

{t( "editor.exportStatus.renderingFile", "Rendering your file.", )}

{isLightningExportInProgress ? ( -

+

PLEASE @@ -4422,7 +4432,7 @@ export default function VideoEditor() {

) : null} {isLegacyExportInProgress ? ( -

+

Export too slow? Cancel and try Lightning export!

@@ -4437,7 +4447,7 @@ export default function VideoEditor() { {t("common.actions.cancel")}
-
+
{isExportSaving ? (
) : ( @@ -4449,35 +4459,36 @@ export default function VideoEditor() { /> )}
-

+

{exportPercentLabel}

{isRenderingAudio ? ( -

- {t("editor.export.processingAudioEdits", "Processing audio with speed/overlay edits")} +

+ Audio requires real-time playback for speed/overlay + edits

) : exportRenderSpeedLabel ? ( -

+

{exportRenderSpeedLabel}

) : null} {exportRuntimeLabel ? ( -

+

Path: {exportRuntimeLabel}

) : null}
) : exportError ? ( -
-

+

+

{t("editor.exportStatus.issue", "Export issue")}

{exportRuntimeLabel ? ( -

+

Path: {exportRuntimeLabel}

) : null} -

+

{exportError}

@@ -4494,29 +4505,29 @@ export default function VideoEditor() { type="button" variant="outline" onClick={handleExportDropdownClose} - className="h-8 flex-1 border-foreground/10 bg-foreground/5 text-xs text-muted-foreground hover:bg-foreground/10" + className="h-8 flex-1 border-white/10 bg-white/5 text-xs text-slate-300 hover:bg-white/10" > {t("common.actions.close", "Close")}
) : exportedFilePath ? ( -
-

+

+

{t("editor.exportStatus.complete", "Export complete")}

-

+

{t( "editor.exportStatus.savedSuccessfully", "Your file was saved successfully.", )}

{exportRuntimeLabel ? ( -

+

Path: {exportRuntimeLabel}

) : null} -

+

{exportedFilePath.split("/").pop()}

@@ -4531,7 +4542,7 @@ export default function VideoEditor() { type="button" variant="outline" onClick={handleExportDropdownClose} - className="h-8 flex-1 border-foreground/10 bg-foreground/5 text-xs text-muted-foreground hover:bg-foreground/10" + className="h-8 flex-1 border-white/10 bg-white/5 text-xs text-slate-300 hover:bg-white/10" > Done @@ -4567,7 +4578,7 @@ export default function VideoEditor() {
-
+
{/* Settings sidebar */}
{/* Icon rail */} @@ -4587,7 +4598,7 @@ export default function VideoEditor() { {isActive && ( @@ -4636,16 +4647,16 @@ export default function VideoEditor() {
); })} -
+
toast.info("Account coming soon")} title="Account" - className="group relative flex h-9 w-9 items-center justify-center rounded-lg text-foreground/55 outline-none transition hover:text-foreground focus:outline-none focus-visible:outline-none" + className="group relative flex h-9 w-9 items-center justify-center rounded-lg text-white/55 outline-none transition hover:text-white focus:outline-none focus-visible:outline-none" whileHover={{ opacity: 1 }} initial={{ opacity: 0.55 }} > - +
@@ -4813,7 +4824,7 @@ export default function VideoEditor() { - + {formatTime(duration)}
@@ -5106,7 +5117,7 @@ export default function VideoEditor() { ? t("editor.timeline.expand") : t("editor.timeline.collapse") } - className="h-7 w-7 rounded-full text-muted-foreground transition-all hover:bg-foreground/10 hover:text-foreground" + className="h-7 w-7 rounded-full text-slate-400 transition-all hover:bg-white/10 hover:text-white" onClick={() => { setTimelineCollapsed((p) => !p); }} @@ -5120,7 +5131,7 @@ export default function VideoEditor() {
-
+
0 @@ -5145,10 +5156,10 @@ export default function VideoEditor() { }} />
- + {Math.round(previewVolume * 100)}% -
+
- + {t("settings.crop.title")} -

+

{t("settings.crop.instruction")}

@@ -5247,7 +5258,7 @@ export default function VideoEditor() { variant="ghost" size="icon" onClick={handleCancelCropEditor} - className="text-muted-foreground hover:bg-foreground/10 hover:text-foreground" + className="text-slate-400 hover:bg-white/10 hover:text-white" > @@ -5273,7 +5284,7 @@ export default function VideoEditor() { {projectBrowser} - +
); } diff --git a/src/components/video-editor/timeline/AudioWaveform.tsx b/src/components/video-editor/timeline/AudioWaveform.tsx index 1fb07b91..da6105a1 100644 --- a/src/components/video-editor/timeline/AudioWaveform.tsx +++ b/src/components/video-editor/timeline/AudioWaveform.tsx @@ -83,7 +83,7 @@ export default function AudioWaveform({ peaks }: AudioWaveformProps) { ); } diff --git a/src/components/video-editor/timeline/Row.tsx b/src/components/video-editor/timeline/Row.tsx index a59286e7..f2aba723 100644 --- a/src/components/video-editor/timeline/Row.tsx +++ b/src/components/video-editor/timeline/Row.tsx @@ -30,7 +30,7 @@ export default function Row({ id, children, label, hint, isEmpty, labelColor = " {hint}
)} -
+
{children}
diff --git a/src/components/video-editor/timeline/TimelineEditor.tsx b/src/components/video-editor/timeline/TimelineEditor.tsx index c55e636f..886d78ed 100644 --- a/src/components/video-editor/timeline/TimelineEditor.tsx +++ b/src/components/video-editor/timeline/TimelineEditor.tsx @@ -1,5 +1,3 @@ -import type { Range, Span } from "dnd-timeline"; -import { useTimelineContext } from "dnd-timeline"; import { Check, CaretDown as ChevronDown, @@ -11,6 +9,8 @@ import { MagicWand as WandSparkles, MagnifyingGlassPlus as ZoomIn, } from "@phosphor-icons/react"; +import type { Range, Span } from "dnd-timeline"; +import { useTimelineContext } from "dnd-timeline"; import { forwardRef, type KeyboardEvent as ReactKeyboardEvent,