import { app, BrowserWindow, dialog, ipcMain, shell } from "electron"; import path from "node:path"; import type { AppInfo, FlowDefinition } from "../shared/types"; import "./capabilities"; import { catalogPath, loadCatalog } from "./catalog"; import { createFlow, deleteFlow, getFlow, listFlows, saveFlow } from "./flows"; import { capabilityCount, capabilityIds } from "./engine/registry"; import { closeBrowserSession, startRun, stopRun } from "./run/manager"; let mainWindow: BrowserWindow | null = null; function createWindow(): void { mainWindow = new BrowserWindow({ width: 1440, height: 900, minWidth: 1200, minHeight: 760, show: false, backgroundColor: "#E7EDF3", title: "RPA Studio", titleBarStyle: "hidden", titleBarOverlay: { color: "#FFFFFF", symbolColor: "#4E5A6B", height: 58 }, webPreferences: { preload: path.join(__dirname, "../preload/index.js"), contextIsolation: true, nodeIntegration: false, sandbox: false } }); mainWindow.on("ready-to-show", () => mainWindow?.show()); mainWindow.on("closed", () => { mainWindow = null; }); mainWindow.webContents.setWindowOpenHandler(({ url }) => { if (/^https?:/i.test(url)) void shell.openExternal(url); return { action: "deny" }; }); const devUrl = process.env.ELECTRON_RENDERER_URL; if (devUrl) { void mainWindow.loadURL(devUrl); } else { void mainWindow.loadFile(path.join(__dirname, "../renderer/index.html")); } } function registerIpc(): void { ipcMain.handle("app:info", (): AppInfo => ({ version: app.getVersion(), electron: process.versions.electron, chrome: process.versions.chrome, node: process.versions.node, userData: app.getPath("userData"), catalogPath: catalogPath(), packaged: app.isPackaged })); ipcMain.handle("catalog:get", () => loadCatalog()); ipcMain.handle("run:start", (event, flow: FlowDefinition) => startRun(flow, event.sender)); ipcMain.handle("run:stop", (_event, runId?: string) => stopRun(runId)); ipcMain.handle("run:capabilities", () => ({ count: capabilityCount(), ids: capabilityIds() })); ipcMain.handle("flows:list", () => listFlows()); ipcMain.handle("flows:create", (_event, name?: string) => { const flow = createFlow(name); return saveFlow(flow); }); ipcMain.handle("flows:get", (_event, id: string) => getFlow(id)); ipcMain.handle("flows:save", (_event, flow: FlowDefinition) => saveFlow(flow)); ipcMain.handle("flows:delete", (_event, id: string) => { deleteFlow(id); return true; }); ipcMain.handle("dialog:open", async (_event, options: { title?: string; filters?: Electron.FileFilter[]; directories?: boolean }) => { if (!mainWindow) return null; const result = await dialog.showOpenDialog(mainWindow, { title: options?.title || "选择文件", properties: options?.directories ? ["openDirectory"] : ["openFile"], filters: options?.filters }); return result.canceled ? null : result.filePaths[0] || null; }); ipcMain.handle("dialog:save", async (_event, options: { title?: string; defaultPath?: string; filters?: Electron.FileFilter[] }) => { if (!mainWindow) return null; const result = await dialog.showSaveDialog(mainWindow, { title: options?.title || "保存文件", defaultPath: options?.defaultPath, filters: options?.filters }); return result.canceled ? null : result.filePath || null; }); } const gotLock = app.requestSingleInstanceLock(); if (!gotLock) { app.quit(); } else { app.on("second-instance", () => { if (!mainWindow) return; if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); }); app.whenReady().then(() => { registerIpc(); createWindow(); app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); app.on("window-all-closed", () => { if (process.platform !== "darwin") app.quit(); }); app.on("before-quit", () => { void closeBrowserSession(); }); }