diff --git a/src/components/video-editor/project/useProjectLifecycle.ts b/src/components/video-editor/project/useProjectLifecycle.ts index ee3d7e38..8b221595 100644 --- a/src/components/video-editor/project/useProjectLifecycle.ts +++ b/src/components/video-editor/project/useProjectLifecycle.ts @@ -104,11 +104,10 @@ export function useProjectLifecycle(input: Input) { current.setCurrentTime(0); current.setDuration(0); project.setError(null); - project.setVideoSourcePath(sourcePath); - project.setCurrentProjectPath(path ?? null); + refs.pendingFreshRecordingAutoZoomPathRef.current = null; if (editor.webcam.sourcePath) { - await window.electronAPI.setCurrentRecordingSession?.( + const result = await window.electronAPI.setCurrentRecordingSession?.( { videoPath: sourcePath, webcamPath: editor.webcam.sourcePath, @@ -116,15 +115,20 @@ export function useProjectLifecycle(input: Input) { }, { preserveProjectPath: Boolean(path) }, ); + if (result && !result.success) throw new Error("Could not load project media"); const session = await window.electronAPI.getCurrentRecordingSession?.(); current.applySessionPresentation(session?.success ? session.session : null); } else { - await window.electronAPI.setCurrentVideoPath(sourcePath, { + const result = await window.electronAPI.setCurrentVideoPath(sourcePath, { preserveProjectPath: Boolean(path), }); + if (!result.success) throw new Error("Could not load project media"); current.applySessionPresentation(null); } - project.setVideoPath(await resolveVideoUrl(sourcePath)); + const videoUrl = await resolveVideoUrl(sourcePath); + project.setVideoSourcePath(sourcePath); + project.setCurrentProjectPath(path ?? null); + project.setVideoPath(videoUrl); appearance.setWallpaper(editor.wallpaper); appearance.setShadowIntensity(editor.shadowIntensity); diff --git a/src/components/video-editor/project/useProjectOpenActions.ts b/src/components/video-editor/project/useProjectOpenActions.ts index b4d3f768..ac44f3f3 100644 --- a/src/components/video-editor/project/useProjectOpenActions.ts +++ b/src/components/video-editor/project/useProjectOpenActions.ts @@ -66,19 +66,25 @@ export function useProjectOpenActions({ const handleOpenProjectFromLibrary = useCallback( async (projectPath: string) => { if (!(await confirmReplaceSourceWithUnsavedChanges("open another project"))) return; - const result = await window.electronAPI.openProjectFileAtPath(projectPath); - if (result.canceled) return; - if (!result.success) { - toast.error(result.message || "Failed to load project"); - return; + try { + const result = await window.electronAPI.openProjectFileAtPath(projectPath); + if (result.canceled) return; + if (!result.success) { + project.setError(result.error || result.message || "Failed to load project"); + return; + } + if (!(await applyLoadedProject(result.project, result.path ?? null))) { + project.setError("Could not load project: invalid project file format"); + return; + } + project.setProjectBrowserOpen(false); + await refreshProjectLibrary(); + toast.success(`Project loaded from ${result.path}`); + } catch (error) { + project.setError( + `Could not load project: ${error instanceof Error ? error.message : String(error)}`, + ); } - if (!(await applyLoadedProject(result.project, result.path ?? null))) { - toast.error("Invalid project file format"); - return; - } - project.setProjectBrowserOpen(false); - await refreshProjectLibrary(); - toast.success(`Project loaded from ${result.path}`); }, [ applyLoadedProject, @@ -90,57 +96,66 @@ export function useProjectOpenActions({ const handleImportMediaOrProject = useCallback(async () => { if (!(await confirmReplaceSourceWithUnsavedChanges("import a file"))) return; - const result = await window.electronAPI.openVideoFilePicker({ includeProjects: true }); - if (result.canceled) return; - if (!result.success) { - toast.error(result.message || "Failed to import file"); - return; - } - if (result.kind === "project" || result.project) { - if (!(await applyLoadedProject(result.project, result.path ?? null))) { - toast.error("Invalid project file format"); + try { + const result = await window.electronAPI.openVideoFilePicker({ includeProjects: true }); + if (result.canceled) return; + if (!result.success) { + toast.error(result.message || "Failed to import file"); return; } + if (result.kind === "project" || result.project) { + if (!(await applyLoadedProject(result.project, result.path ?? null))) { + project.setError("Could not load project: invalid project file format"); + return; + } + project.setProjectBrowserOpen(false); + await refreshProjectLibrary(); + toast.success( + result.path ? `Project loaded from ${result.path}` : "Project loaded", + ); + return; + } + if (!result.path) { + toast.error("No media file selected"); + return; + } + + const sourcePath = fromFileUrl(result.path); + await window.electronAPI.setCurrentVideoPath(sourcePath, { + preserveProjectPath: false, + }); + const sourceVideoUrl = await resolveVideoUrl(sourcePath); + try { + videoPlaybackRef.current?.pause(); + } catch { + // The preview may already be tearing down. + } + setIsPlaying(false); + setCurrentTime(0); + setDuration(0); + project.setVideoSourcePath(sourcePath); + project.setVideoPath(sourceVideoUrl); + project.setCurrentProjectPath(null); + project.setLastSavedSnapshot(null); + resetSourceScopedEditorState(); + pendingFreshRecordingAutoZoomPathRef.current = + appearance.autoApplyFreshRecordingAutoZooms ? sourceVideoUrl : null; + appearance.setWebcam((previous) => ({ + ...previous, + visibleRanges: undefined, + enabled: false, + sourcePath: null, + timeOffsetMs: DEFAULT_WEBCAM_TIME_OFFSET_MS, + })); + applySessionPresentation(null); project.setProjectBrowserOpen(false); await refreshProjectLibrary(); - toast.success(result.path ? `Project loaded from ${result.path}` : "Project loaded"); - return; + toast.success("Media imported"); + } catch (error) { + project.setError( + `Could not load file: ${error instanceof Error ? error.message : String(error)}`, + ); } - if (!result.path) { - toast.error("No media file selected"); - return; - } - - const sourcePath = fromFileUrl(result.path); - await window.electronAPI.setCurrentVideoPath(sourcePath, { preserveProjectPath: false }); - const sourceVideoUrl = await resolveVideoUrl(sourcePath); - try { - videoPlaybackRef.current?.pause(); - } catch { - // The preview may already be tearing down. - } - setIsPlaying(false); - setCurrentTime(0); - setDuration(0); - project.setVideoSourcePath(sourcePath); - project.setVideoPath(sourceVideoUrl); - project.setCurrentProjectPath(null); - project.setLastSavedSnapshot(null); - resetSourceScopedEditorState(); - pendingFreshRecordingAutoZoomPathRef.current = appearance.autoApplyFreshRecordingAutoZooms - ? sourceVideoUrl - : null; - appearance.setWebcam((previous) => ({ - ...previous, - visibleRanges: undefined, - enabled: false, - sourcePath: null, - timeOffsetMs: DEFAULT_WEBCAM_TIME_OFFSET_MS, - })); - applySessionPresentation(null); - project.setProjectBrowserOpen(false); - await refreshProjectLibrary(); - toast.success("Media imported"); }, [ confirmReplaceSourceWithUnsavedChanges, applyLoadedProject,