feat: implement handlers to store last export location

This commit is contained in:
AbhinRustagi
2026-05-02 01:05:17 +05:30
parent a38454a7fb
commit c40727672f
3 changed files with 94 additions and 45 deletions
+60 -42
View File
@@ -822,54 +822,72 @@ export function registerIpcHandlers(
* @returns Object with success status, optional file path, and error details.
*/
ipcMain.handle("save-exported-video", async (_, videoData: ArrayBuffer, fileName: string) => {
try {
// Determine file type from extension
const isGif = fileName.toLowerCase().endsWith(".gif");
const filters = isGif
? [{ name: mainT("dialogs", "fileDialogs.gifImage"), extensions: ["gif"] }]
: [{ name: mainT("dialogs", "fileDialogs.mp4Video"), extensions: ["mp4"] }];
ipcMain.handle(
"save-exported-video",
async (_, videoData: ArrayBuffer, fileName: string, exportFolder?: string) => {
try {
// Determine file type from extension
const isGif = fileName.toLowerCase().endsWith(".gif");
const filters = isGif
? [{ name: mainT("dialogs", "fileDialogs.gifImage"), extensions: ["gif"] }]
: [{ name: mainT("dialogs", "fileDialogs.mp4Video"), extensions: ["mp4"] }];
const result = await dialog.showSaveDialog({
title: isGif
? mainT("dialogs", "fileDialogs.saveGif")
: mainT("dialogs", "fileDialogs.saveVideo"),
defaultPath: path.join(app.getPath("downloads"), fileName),
filters,
properties: ["createDirectory", "showOverwriteConfirmation"],
});
// Prefer the user's last export folder if it still exists, otherwise fall
// back to ~/Downloads. Validation must happen here because the renderer
// can't stat the filesystem.
let defaultDir = app.getPath("downloads");
if (exportFolder) {
try {
const stats = await fs.stat(exportFolder);
if (stats.isDirectory()) {
defaultDir = exportFolder;
}
} catch {
// Folder was moved or deleted since the last export; keep Downloads.
}
}
if (result.canceled || !result.filePath) {
const result = await dialog.showSaveDialog({
title: isGif
? mainT("dialogs", "fileDialogs.saveGif")
: mainT("dialogs", "fileDialogs.saveVideo"),
defaultPath: path.join(defaultDir, fileName),
filters,
properties: ["createDirectory", "showOverwriteConfirmation"],
});
if (result.canceled || !result.filePath) {
return {
success: false,
canceled: true,
message: "Export canceled",
};
}
// --- FIX: Normalize the path for Windows compatibility ---
const normalizedPath = path.normalize(result.filePath);
// Ensure the parent directory exists (Windows may fail if the folder is missing)
await fs.mkdir(path.dirname(normalizedPath), { recursive: true });
// --- END FIX ---
await fs.writeFile(normalizedPath, Buffer.from(videoData));
return {
success: true,
path: normalizedPath,
message: "Video exported successfully",
};
} catch (error) {
console.error("Failed to save exported video:", error);
return {
success: false,
canceled: true,
message: "Export canceled",
message: "Failed to save exported video",
error: String(error),
};
}
// --- FIX: Normalize the path for Windows compatibility ---
const normalizedPath = path.normalize(result.filePath);
// Ensure the parent directory exists (Windows may fail if the folder is missing)
await fs.mkdir(path.dirname(normalizedPath), { recursive: true });
// --- END FIX ---
await fs.writeFile(normalizedPath, Buffer.from(videoData));
return {
success: true,
path: normalizedPath,
message: "Video exported successfully",
};
} catch (error) {
console.error("Failed to save exported video:", error);
return {
success: false,
message: "Failed to save exported video",
error: String(error),
};
}
});
},
);
ipcMain.handle("open-video-file-picker", async () => {
try {
const result = await dialog.showOpenDialog({
+16 -3
View File
@@ -31,7 +31,7 @@ import {
import { computeFrameStepTime } from "@/lib/frameStep";
import type { ProjectMedia } from "@/lib/recordingSession";
import { matchesShortcut } from "@/lib/shortcuts";
import { loadUserPreferences, saveUserPreferences } from "@/lib/userPreferences";
import { loadUserPreferences, parentDirectoryOf, saveUserPreferences } from "@/lib/userPreferences";
import { BackgroundLoadError } from "@/lib/wallpaper";
import {
getAspectRatioValue,
@@ -1285,6 +1285,10 @@ export default function VideoEditor() {
const handleExportSaved = useCallback(
(formatLabel: "GIF" | "Video", filePath: string) => {
setExportedFilePath(filePath);
const folder = parentDirectoryOf(filePath);
if (folder) {
saveUserPreferences({ exportFolder: folder });
}
toast.success(
t("export.exportedSuccessfully", {
format: formatLabel,
@@ -1309,6 +1313,7 @@ export default function VideoEditor() {
const saveResult = await window.electronAPI.saveExportedVideo(
unsavedExport.arrayBuffer,
unsavedExport.fileName,
loadUserPreferences().exportFolder ?? undefined,
);
if (saveResult.canceled) {
toast.info("Export canceled");
@@ -1410,7 +1415,11 @@ export default function VideoEditor() {
}
}
const saveResult = await window.electronAPI.saveExportedVideo(arrayBuffer, fileName);
const saveResult = await window.electronAPI.saveExportedVideo(
arrayBuffer,
fileName,
loadUserPreferences().exportFolder ?? undefined,
);
if (saveResult.canceled) {
setUnsavedExport({ arrayBuffer, fileName, format: "gif" });
@@ -1550,7 +1559,11 @@ export default function VideoEditor() {
}
}
const saveResult = await window.electronAPI.saveExportedVideo(arrayBuffer, fileName);
const saveResult = await window.electronAPI.saveExportedVideo(
arrayBuffer,
fileName,
loadUserPreferences().exportFolder ?? undefined,
);
if (saveResult.canceled) {
setUnsavedExport({ arrayBuffer, fileName, format: "mp4" });
+18
View File
@@ -23,6 +23,8 @@ export interface UserPreferences {
exportQuality: ExportQuality;
/** Default export format */
exportFormat: ExportFormat;
/** Folder used for the most recent successful export, if any */
exportFolder: string | null;
}
const DEFAULT_PREFS: UserPreferences = {
@@ -30,6 +32,7 @@ const DEFAULT_PREFS: UserPreferences = {
aspectRatio: "16:9",
exportQuality: "good",
exportFormat: "mp4",
exportFolder: null,
};
function safeJsonParse(text: string | null): Record<string, unknown> | null {
@@ -76,9 +79,24 @@ export function loadUserPreferences(): UserPreferences {
raw.exportFormat === "gif" || raw.exportFormat === "mp4"
? (raw.exportFormat as ExportFormat)
: DEFAULT_PREFS.exportFormat,
exportFolder:
typeof raw.exportFolder === "string" && raw.exportFolder.length > 0
? raw.exportFolder
: DEFAULT_PREFS.exportFolder,
};
}
/**
* Extracts the parent directory from a saved file path. Handles both POSIX
* and Windows separators since the path comes from the OS save dialog.
* Returns null if no separator is found.
*/
export function parentDirectoryOf(filePath: string): string | null {
const lastSep = Math.max(filePath.lastIndexOf("/"), filePath.lastIndexOf("\\"));
if (lastSep <= 0) return null;
return filePath.slice(0, lastSep);
}
/**
* Persist user preferences to localStorage.
* Only the explicitly provided fields are updated.