import { ipcRenderer, contextBridge } from 'electron'; import { BIDIRECTIONAL, RENDERER_TO_MAIN } from '../shared/constants/ipc-channels'; // --------- Expose some API to the Renderer process --------- contextBridge.exposeInMainWorld('ipcRenderer', { on(...args: Parameters) { const [channel, listener] = args; return ipcRenderer.on(channel, (event, ...args) => listener(event, ...args)); }, off(...args: Parameters) { const [channel, ...omit] = args; return ipcRenderer.off(channel, ...omit); }, send(...args: Parameters) { const [channel, ...omit] = args; return ipcRenderer.send(channel, ...omit); }, invoke(...args: Parameters) { const [channel, ...omit] = args; return ipcRenderer.invoke(channel, ...omit); }, }); // --------- 安全加密 API(基于 Electron safeStorage)--------- contextBridge.exposeInMainWorld('safeStorage', { /** * 加密明文字符串 * @param plaintext - 待加密的明文 * @returns Base64 编码的密文,失败返回 null */ async encrypt(plaintext: string): Promise { const result = await ipcRenderer.invoke(BIDIRECTIONAL.SAFESTORAGE_ENCRYPT, plaintext); return result?.success ? result.data : null; }, /** * 解密 Base64 密文 * @param encryptedBase64 - Base64 编码的密文 * @returns 解密后的明文,失败返回 null */ async decrypt(encryptedBase64: string): Promise { const result = await ipcRenderer.invoke(BIDIRECTIONAL.SAFESTORAGE_DECRYPT, encryptedBase64); return result?.success ? result.data : null; }, }); // --------- 本地存储文件 API(主进程 fs 桥接)--------- contextBridge.exposeInMainWorld('fileStorage', { /** 获取 heixiu-data 根目录路径 */ getDataDir(): Promise { return ipcRenderer.invoke(BIDIRECTIONAL.GET_DATA_DIR) .then((r: { success: boolean; data: string }) => r?.success ? r.data : null); }, /** 写入文件(Base64 → 磁盘),返回写入的绝对路径 */ writeFile(relativePath: string, data: string, encoding?: 'base64' | 'utf8'): Promise { return ipcRenderer.invoke(BIDIRECTIONAL.FILE_WRITE, { relativePath, data, encoding }) .then((r: { success: boolean; data: string }) => r?.success ? r.data : null); }, /** 读取文件(磁盘 → Base64) */ readFile(relativePath: string): Promise { return ipcRenderer.invoke(BIDIRECTIONAL.FILE_READ, { relativePath }) .then((r: { success: boolean; data: string }) => r?.success ? r.data : null); }, /** 删除文件 */ deleteFile(relativePath: string): Promise { return ipcRenderer.invoke(BIDIRECTIONAL.FILE_DELETE, { relativePath }) .then((r: { success: boolean }) => r?.success ?? false); }, /** 递归创建目录 */ makeDir(relativePath: string): Promise { return ipcRenderer.invoke(BIDIRECTIONAL.MAKE_DIR, { relativePath }) .then((r: { success: boolean; data: string }) => r?.success ? r.data : null); }, /** 在文件管理器中显示文件(绝对路径) */ showItemInFolder(filePath: string): Promise { return ipcRenderer.invoke(BIDIRECTIONAL.SHOW_ITEM_IN_FOLDER, { filePath }) .then((r: { success: boolean }) => r?.success ?? false); }, /** 获取文件大小(字节) */ fileSize(relativePath: string): Promise { return ipcRenderer.invoke(BIDIRECTIONAL.FILE_SIZE, { relativePath }) .then((r: { success: boolean; data: number }) => r?.success ? r.data : null); }, /** 检查文件/目录是否存在 */ fileExists(relativePath: string): Promise { return ipcRenderer.invoke(BIDIRECTIONAL.FILE_EXISTS, { relativePath }) .then((r: { success: boolean; data: boolean }) => r?.success ? r.data : false); }, }); /** 应用运行时 API */ contextBridge.exposeInMainWorld('appRuntime', { /** * 登录/登出后更新窗口标题中的版控后缀 * @param edition - 'personal' | 'enterprise' | null(null 清除后缀) */ setWindowEdition(edition: string | null): void { ipcRenderer.send(RENDERER_TO_MAIN.SET_WINDOW_TITLE, edition); }, }); // --------- 无限画布 API --------- contextBridge.exposeInMainWorld('canvas', { /** 打开画布窗口(主进程创建新 BrowserWindow) */ openWindow(projectPath?: string): void { ipcRenderer.send(RENDERER_TO_MAIN.OPEN_CANVAS_WINDOW, { projectPath }); }, /** 扫描目录,返回匹配的文件列表 */ scanDirectory(params: { roots: string[]; extensions: string[]; recursive: boolean; }): Promise<{ success: boolean; data: Array<{ path: string; fileName: string; ext: string; fileSize: number; modifiedAt: string }>; error?: string }> { return ipcRenderer.invoke(BIDIRECTIONAL.CANVAS_SCAN_DIRECTORY, params); }, /** 扫描项目(主进程内部完成 buildScanRoots + walkDirectory,推荐使用) */ scanProject(params: { projectPath: string; stage: string; assetTypeFilter: string; shotQuery: string; }): Promise<{ success: boolean; data: Array<{ path: string; fileName: string; ext: string; fileSize: number; modifiedAt: string }>; error?: string }> { return ipcRenderer.invoke(BIDIRECTIONAL.CANVAS_SCAN_PROJECT, params); }, /** 读取旁加载 JSON 元数据 */ readSidecar(params: { filePath: string }): Promise<{ success: boolean; data: object | null }> { return ipcRenderer.invoke(BIDIRECTIONAL.CANVAS_READ_SIDECAR, params); }, /** 写入旁加载 JSON 元数据 */ writeSidecar(params: { filePath: string; metadata: object }): Promise<{ success: boolean; error?: string }> { return ipcRenderer.invoke(BIDIRECTIONAL.CANVAS_WRITE_SIDECAR, params); }, /** 生成缩略图(JPEG base64) */ generateThumbnail(params: { filePath: string; size: number }): Promise<{ success: boolean; data: { data: string; mimeType: string; width: number; height: number }; error?: string; }> { return ipcRenderer.invoke(BIDIRECTIONAL.CANVAS_GENERATE_THUMBNAIL, params); }, /** 复制文件到目标目录(导入操作) */ copyFile(params: { src: string; destDir: string; fileName: string }): Promise<{ success: boolean; data: string; error?: string }> { return ipcRenderer.invoke(BIDIRECTIONAL.CANVAS_COPY_FILE, params); }, /** 列出目录下的子文件夹(非递归,排除隐藏) */ listSubdirs(params: { parentPath: string }): Promise<{ success: boolean; data: string[]; error?: string }> { return ipcRenderer.invoke(BIDIRECTIONAL.CANVAS_LIST_SUBDIRS, params); }, /** 扫描阶段文件(单个 episode/shot/stage 组合) */ scanStageFiles(params: { projectPath: string; episode: string; shot: string; stage: string }): Promise<{ success: boolean; data: Array<{ path: string; fileName: string; ext: string; fileSize: number; modifiedAt: string }>; error?: string }> { return ipcRenderer.invoke(BIDIRECTIONAL.CANVAS_SCAN_STAGE_FILES, params); }, /** 扫描资产文件(深度递归扫描 assets/ 目录) */ scanAssetFiles(params: { projectPath: string; assetTypeFilter: string }): Promise<{ success: boolean; data: Array<{ path: string; fileName: string; ext: string; fileSize: number; modifiedAt: string }>; error?: string }> { return ipcRenderer.invoke(BIDIRECTIONAL.CANVAS_SCAN_ASSET_FILES, params); }, /** 创建目录 */ createDirectory(params: { parentPath: string; dirName: string }): Promise<{ success: boolean; data: string; error?: string }> { return ipcRenderer.invoke(BIDIRECTIONAL.CANVAS_CREATE_DIRECTORY, params); }, /** 将本地文件路径转换为可通过 local-media:// 协议加载的 URL */ getMediaUrl(filePath: string): string { const normalized = filePath.replace(/\\/g, '/'); // UNC 路径(//server/share/…)开头 // 会被 URL 解析器视为 authority, // 必须用 local-media:/// 格式而非 local-media:/// 前缀 if (normalized.startsWith('//')) { // normalized = "//192.168.110.250/share/path" return 'local-media://' + normalized.slice(2); } // 本地路径:C:/path → local-media:///C:/path return 'local-media:///' + normalized; }, /** 启动原生文件拖拽(从缩略图拖放媒体文件到外部应用) */ startDrag(params: { filePath: string; fileName: string }): Promise<{ success: boolean; error?: string }> { return ipcRenderer.invoke(BIDIRECTIONAL.CANVAS_START_DRAG, params); }, });