Files
ele-HeiXiu/electron/preload/canvas.ts
YoungestSongMo 136a17c0bf feat: 任务列表右键菜单回调 + 主进程下载 + sharp 缩略图 + IPC 精简重构
- 任务右键菜单:实现打开输出目录(inputs/outputs/.cache 结构)、
    复制资源链接、重新编辑、再次生成、拉取结果、重新下载、收藏、删除记录
  - 主进程下载:通过 Electron net 模块绕过 CORS,支持 CDN 媒体文件下载
  - sharp 缩略图:替换 nativeImage,支持 4K/8K+ 大尺寸素材的流式处理
  - IPC 精简:40+ 冗余通道 → 15 个分组通道,移除未使用的 preload 桥接方法
  - 任务存储:新增收藏字段、schema 迁移、task-store 持久化
  - 清理废弃模块:canvas-ipc / updater / request / pricing

refactor: IPC/Preload/Services 模块化拆分 — 单一职责 + 向后兼容

  主要变更:
  - shared/constants/ipc-channels.ts → ipc/ 模块 (base/storage/canvas/updater)
  - electron/preload.ts → preload/ 模块 (base/safe-storage/file-storage/canvas)
  - electron/main/ipc/canvas-ipc.ts → canvas/ 模块 (types/walk/sidecar/mime/thumbnail)
  - electron/main/ipc/storage-ipc.ts → 提取 utils/ (path/download/thumbnail)
  - electron/main/updater.ts → updater/ 模块 (provider/platform-updaters)
  - src/services/request.ts → request/ 模块 (token/interceptors/methods)
  - src/shared/utils/pricing.ts → pricing/ 模块 (types/helpers/format/calculate/fields)
  - src/modules/home/center/views/ModelInputForm.tsx → 提取 utils + DependentFormItem

  架构优化:
  - 所有拆分均保持向后兼容(旧导入路径仍可用)
  - TypeScript 编译通过 ✓
  - 每个模块单一职责,平均 ~130 行
2026-07-03 11:13:36 +08:00

125 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// preload/canvas.ts — 无限画布 API
//
// 提供画布窗口、文件扫描、缩略图、拖拽等接口
// 渲染进程通过 window.canvas 调用
// ============================================================
import { ipcRenderer, contextBridge } from 'electron';
import { BIDIRECTIONAL, RENDERER_TO_MAIN } from '../../shared/constants/ipc';
/** 无限画布 API */
export function exposeCanvas(): void {
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://<host>/<path> 格式而非 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);
},
});
}