import { app, BrowserWindow, ipcMain, dialog, Menu, nativeTheme } from "electron" import { join, dirname } from "node:path" import { fileURLToPath } from "node:url" import { readFileSync, writeFileSync } from "node:fs" const __dirname = dirname(fileURLToPath(import.meta.url)) process.env.DIST = join(__dirname, "../dist") process.env.VITE_PUBLIC = app.isPackaged ? process.env.DIST : join(process.env.DIST, "../public") let win: BrowserWindow | null const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"] app.disableHardwareAcceleration() app.commandLine.appendSwitch("disable-direct-composition") Menu.setApplicationMenu(null) function createWindow() { win = new BrowserWindow({ width: 1480, height: 880, minWidth: 1024, minHeight: 600, frame: false, webPreferences: { preload: join(__dirname, "preload.js"), contextIsolation: true, nodeIntegration: false, }, show: false, }) win.on("ready-to-show", () => { win?.show() win?.focus() }) win.on("maximize", () => win?.webContents?.send("window:maximize-change", true)) win.on("unmaximize", () => win?.webContents?.send("window:maximize-change", false)) win.on("enter-full-screen", () => win?.webContents?.send("window:maximize-change", true)) win.on("leave-full-screen", () => win?.webContents?.send("window:maximize-change", false)) if (VITE_DEV_SERVER_URL) { win.loadURL(VITE_DEV_SERVER_URL) } else { win.loadFile(join(process.env.DIST!, "index.html")) } } // ── IPC Handlers ── ipcMain.on("window:focus", () => win?.focus()) ipcMain.on("window:setTheme", (_event, dark: boolean) => { nativeTheme.themeSource = dark ? "dark" : "light" }) ipcMain.on("window:minimize", () => win?.minimize()) ipcMain.on("window:maximize", () => { if (win?.isMaximized()) win.unmaximize(); else win?.maximize() }) ipcMain.on("window:close", () => win?.close()) ipcMain.handle("window:isMaximized", () => win?.isMaximized()) ipcMain.handle("dialog:openFile", async (_event, filters: { name: string; extensions: string[] }[]) => { const result = await dialog.showOpenDialog(win!, { properties: ["openFile"], filters, }) win?.webContents?.focus() return result.filePaths }) ipcMain.handle("dialog:saveFile", async (_event, defaultName: string, filters: { name: string; extensions: string[] }[]) => { const result = await dialog.showSaveDialog(win!, { defaultPath: defaultName, filters, }) win?.webContents?.focus() return result.filePath ?? "" }) ipcMain.handle("file:read", async (_event, path: string) => { return readFileSync(path, "utf-8") }) ipcMain.handle("file:write", async (_event, path: string, data: string) => { writeFileSync(path, data, "utf-8") }) ipcMain.handle("file:readImage", async (_event, path: string) => { const buffer = readFileSync(path) const ext = path.split(".").pop()?.toLowerCase() ?? "png" const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png" return `data:${mime};base64,${buffer.toString("base64")}` }) ipcMain.handle("file:writeBinary", async (_event, path: string, base64: string) => { writeFileSync(path, Buffer.from(base64, "base64")) }) app.whenReady().then(createWindow) app.on("window-all-closed", () => { if (process.platform !== "darwin") app.quit() }) app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) createWindow() })