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 行
This commit is contained in:
124
electron/preload/canvas.ts
Normal file
124
electron/preload/canvas.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
// ============================================================
|
||||
// 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);
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user