diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index b4338c4b..7c82c97c 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -1,4 +1,5 @@ import { ipcMain, desktopCapturer, BrowserWindow, shell, app, dialog, systemPreferences } from 'electron' +import type { SaveDialogOptions } from 'electron' import { execFile, spawn, spawnSync } from 'node:child_process' import type { ChildProcessWithoutNullStreams } from 'node:child_process' @@ -2592,20 +2593,24 @@ export function registerIpcHandlers( } }) - ipcMain.handle('save-exported-video', async (_, videoData: ArrayBuffer, fileName: string) => { + ipcMain.handle('save-exported-video', async (event, videoData: ArrayBuffer, fileName: string) => { try { // Determine file type from extension const isGif = fileName.toLowerCase().endsWith('.gif'); const filters = isGif ? [{ name: 'GIF Image', extensions: ['gif'] }] : [{ name: 'MP4 Video', extensions: ['mp4'] }]; - - const result = await dialog.showSaveDialog({ + const parentWindow = BrowserWindow.fromWebContents(event.sender) + const saveDialogOptions: SaveDialogOptions = { title: isGif ? 'Save Exported GIF' : 'Save Exported Video', defaultPath: path.join(app.getPath('downloads'), fileName), filters, - properties: ['createDirectory', 'showOverwriteConfirmation'] - }); + properties: ['createDirectory', 'showOverwriteConfirmation'], + } + + const result = parentWindow + ? await dialog.showSaveDialog(parentWindow, saveDialogOptions) + : await dialog.showSaveDialog(saveDialogOptions) if (result.canceled || !result.filePath) { return { diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 487b009b..7926e0a0 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -231,6 +231,7 @@ export function SettingsPanel({ cropRegion, onCropChange, aspectRatio, + onAspectRatioChange, videoElement, exportQuality = "good", onExportQualityChange, @@ -265,6 +266,10 @@ export function SettingsPanel({ const [customImages, setCustomImages] = useState( initialEditorPreferences.customWallpapers, ); + const removeBackgroundStateRef = useRef<{ + aspectRatio: AspectRatio; + padding: number; + } | null>(null); const fileInputRef = useRef(null); useEffect(() => { @@ -310,6 +315,7 @@ export function SettingsPanel({ const [gradient, setGradient] = useState( GRADIENTS.includes(selected) ? selected : GRADIENTS[0], ); + const removeBackgroundEnabled = aspectRatio === "native" && padding === 0; const [backgroundTab, setBackgroundTab] = useState(() => getBackgroundTabForWallpaper(selected), ); @@ -336,6 +342,24 @@ export function SettingsPanel({ saveEditorPreferences({ customWallpapers: customImages }); }, [customImages]); + const handleRemoveBackgroundToggle = (checked: boolean) => { + if (checked) { + removeBackgroundStateRef.current = { + aspectRatio, + padding, + }; + onAspectRatioChange?.("native"); + onPaddingChange?.(0); + return; + } + + if (removeBackgroundStateRef.current) { + onAspectRatioChange?.(removeBackgroundStateRef.current.aspectRatio); + onPaddingChange?.(removeBackgroundStateRef.current.padding); + removeBackgroundStateRef.current = null; + } + }; + const webcamFileName = webcam?.sourcePath?.split(/[\\/]/).pop() ?? null; const zoomEnabled = Boolean(selectedZoomDepth); @@ -860,6 +884,14 @@ export function SettingsPanel({ parseInput={(t) => parseFloat(t.replace(/%$/, ""))} /> +
+
{tSettings("effects.removeBackground")}
+ +