diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index d7deb55..74240fc 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -43,6 +43,7 @@ interface Window { loadProjectFile: () => Promise<{ success: boolean; path?: string; project?: unknown; message?: string; cancelled?: boolean; error?: string }> onMenuLoadProject: (callback: () => void) => () => void onMenuSaveProject: (callback: () => void) => () => void + onMenuSaveProjectAs: (callback: () => void) => () => void getPlatform: () => Promise hudOverlayHide: () => void; hudOverlayClose: () => void; diff --git a/electron/main.ts b/electron/main.ts index 6d863d1..5bcfd67 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -57,7 +57,7 @@ function isEditorWindow(window: BrowserWindow) { return window.webContents.getURL().includes('windowType=editor') } -function sendEditorMenuAction(channel: 'menu-load-project' | 'menu-save-project') { +function sendEditorMenuAction(channel: 'menu-load-project' | 'menu-save-project' | 'menu-save-project-as') { let targetWindow = BrowserWindow.getFocusedWindow() ?? mainWindow if (!targetWindow || targetWindow.isDestroyed() || !isEditorWindow(targetWindow)) { @@ -102,6 +102,11 @@ function setupApplicationMenu() { accelerator: 'CmdOrCtrl+S', click: () => sendEditorMenuAction('menu-save-project'), }, + { + label: 'Save Project As…', + accelerator: 'CmdOrCtrl+Shift+S', + click: () => sendEditorMenuAction('menu-save-project-as'), + }, ], }, { diff --git a/electron/preload.ts b/electron/preload.ts index 425440f..ac96ce5 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -76,6 +76,11 @@ contextBridge.exposeInMainWorld('electronAPI', { ipcRenderer.on('menu-save-project', listener) return () => ipcRenderer.removeListener('menu-save-project', listener) }, + onMenuSaveProjectAs: (callback: () => void) => { + const listener = () => callback() + ipcRenderer.on('menu-save-project-as', listener) + return () => ipcRenderer.removeListener('menu-save-project-as', listener) + }, getPlatform: () => { return ipcRenderer.invoke('get-platform') }, diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index cae6770..187988e 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -303,7 +303,7 @@ export default function VideoEditor() { loadVideo(); }, []); - const handleSaveProject = useCallback(async () => { + const saveProject = useCallback(async (forceSaveAs: boolean) => { if (!videoPath) { toast.error('No video loaded'); return; @@ -339,7 +339,11 @@ export default function VideoEditor() { }; const fileNameBase = sourcePath.split(/[\\/]/).pop()?.replace(/\.[^.]+$/, '') || `project-${Date.now()}`; - const result = await window.electronAPI.saveProjectFile(projectData, fileNameBase, currentProjectPath ?? undefined); + const result = await window.electronAPI.saveProjectFile( + projectData, + fileNameBase, + forceSaveAs ? undefined : currentProjectPath ?? undefined, + ); if (result.cancelled) { toast.info('Project save cancelled'); @@ -378,6 +382,14 @@ export default function VideoEditor() { gifSizePreset, ]); + const handleSaveProject = useCallback(async () => { + await saveProject(false); + }, [saveProject]); + + const handleSaveProjectAs = useCallback(async () => { + await saveProject(true); + }, [saveProject]); + const handleLoadProject = useCallback(async () => { const result = await window.electronAPI.loadProjectFile(); @@ -455,12 +467,14 @@ export default function VideoEditor() { useEffect(() => { const removeLoadListener = window.electronAPI.onMenuLoadProject(handleLoadProject); const removeSaveListener = window.electronAPI.onMenuSaveProject(handleSaveProject); + const removeSaveAsListener = window.electronAPI.onMenuSaveProjectAs(handleSaveProjectAs); return () => { removeLoadListener?.(); removeSaveListener?.(); + removeSaveAsListener?.(); }; - }, [handleLoadProject, handleSaveProject]); + }, [handleLoadProject, handleSaveProject, handleSaveProjectAs]); // Initialize default wallpaper with resolved asset path useEffect(() => { diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index cc0a9dc..206000d 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -59,5 +59,6 @@ interface Window { }> onMenuLoadProject: (callback: () => void) => () => void onMenuSaveProject: (callback: () => void) => () => void + onMenuSaveProjectAs: (callback: () => void) => () => void } } \ No newline at end of file