diff --git a/src/components/video-editor/export/useExportDialogActions.ts b/src/components/video-editor/export/useExportDialogActions.ts index 32e334cb..813286c0 100644 --- a/src/components/video-editor/export/useExportDialogActions.ts +++ b/src/components/video-editor/export/useExportDialogActions.ts @@ -47,6 +47,7 @@ export function useExportDialogActions({ session.setShowExportDropdown(true); session.setExportProgress(null); session.setExportError(null); + session.setExportedFilePath(undefined); }, [videoPath, session]); const resolveCurrentSettings = useCallback( diff --git a/src/components/video-editor/library/useRecordingLibrary.ts b/src/components/video-editor/library/useRecordingLibrary.ts index 146a9aee..e454c39c 100644 --- a/src/components/video-editor/library/useRecordingLibrary.ts +++ b/src/components/video-editor/library/useRecordingLibrary.ts @@ -120,10 +120,15 @@ export function useRecordingLibrary( let insertAt = Math.min(before.length, Math.max(0, index ?? before.length)); const addedZooms: ZoomRegion[] = []; let id = ""; + let completed = 0; for (const path of [...new Set(typeof paths === "string" ? [paths] : paths)]) { - if (cancelled.current) return; + if (cancelled.current) break; const result = await window.electronAPI.importRecording(sourcePath, path, webcam); - if (!result.success) throw new Error(result.error); + if (!result.success) { + if (cancelled.current) break; + throw new Error(result.error); + } + completed++; if (current.current.project.videoSourcePath !== source) throw new Error( "The project changed while importing. Add the recordings again.", @@ -174,7 +179,7 @@ export function useRecordingLibrary( ); } } - if (!media || cancelled.current) return; + if (!media) return; const { project, timeline, ui, appearance } = current.current; if (project.videoSourcePath !== source) throw new Error("The project changed while importing. Add the recordings again."); @@ -202,9 +207,9 @@ export function useRecordingLibrary( ui.setIsPreviewReady(false); ui.setPreviewVersion((version) => version + 1); toast.success( - typeof paths === "string" + completed === 1 ? "Video added to timeline" - : `${paths.length} videos added to timeline`, + : `${completed} videos added to timeline`, ); } catch (error) { if (!cancelled.current) toast.error(`Could not add video: ${String(error)}`); diff --git a/src/components/video-editor/project/useProjectOpenActions.ts b/src/components/video-editor/project/useProjectOpenActions.ts index ac44f3f3..9d740492 100644 --- a/src/components/video-editor/project/useProjectOpenActions.ts +++ b/src/components/video-editor/project/useProjectOpenActions.ts @@ -121,9 +121,10 @@ export function useProjectOpenActions({ } const sourcePath = fromFileUrl(result.path); - await window.electronAPI.setCurrentVideoPath(sourcePath, { + const setPathResult = await window.electronAPI.setCurrentVideoPath(sourcePath, { preserveProjectPath: false, }); + if (!setPathResult.success) throw new Error("Could not load media"); const sourceVideoUrl = await resolveVideoUrl(sourcePath); try { videoPlaybackRef.current?.pause(); diff --git a/tests/ui/videos-library.spec.ts b/tests/ui/videos-library.spec.ts index a2b28998..7b3bb63c 100644 --- a/tests/ui/videos-library.spec.ts +++ b/tests/ui/videos-library.spec.ts @@ -359,3 +359,44 @@ for (const enabled of [true, false]) { } }); } + +test("cancelling a batch keeps completed clips and stops the remaining import", async ({ + page, +}) => { + await setup(page); + await page.evaluate(() => { + let count = 0; + let finish: ((result: { success: false; error: string }) => void) | undefined; + window.electronAPI.importRecording = async () => { + count++; + if (count === 1) + return { + success: true, + value: { + path: "/sequence-first.mp4", + url: `${location.origin}/tests/ui/fixtures/filmstrip.mp4`, + sourceStartMs: 6000, + durationMs: 1000, + totalDurationMs: 7000, + }, + }; + document.documentElement.dataset.pendingImport = "true"; + return new Promise((resolve) => { + finish = resolve; + }); + }; + window.electronAPI.cancelRecordingImport = async () => { + finish?.({ success: false, error: "Cancelled" }); + return { success: true }; + }; + }); + const panel = page.getByRole("complementary", { name: "Videos" }); + await panel.locator('[data-slot="checkbox-control"]').first().click(); + await panel + .locator('[data-recording-path="/recordings/second.mp4"]') + .dragTo(page.locator('[data-variant="clip"]'), { targetPosition: { x: 5, y: 15 } }); + await expect(page.locator("html")).toHaveAttribute("data-pending-import", "true"); + await page.getByRole("button", { name: "Cancel", exact: true }).click(); + await expect(page.getByText("Adding video…")).toHaveCount(0); + await expect(page.locator('[data-variant="clip"]')).toHaveCount(2); +});