## 构建、打包与分发系统重构 (build/) - 构建系统收敛至 build/ 目录:build.mjs + electron-builder.js + 6 个脚本 - electron-builder 配置从 package.json 提取为 build/electron-builder.js - 新增 --edition (personal/enterprise) 和 --channel (stable/beta/alpha) 参数 - OSS 路径按渠道隔离 - 新增 pre-build-check.mjs:Git/CHANGELOG/版本/环境变量校验 - 新增 bump-version.mjs:版本号管理 + CHANGELOG 自动生成 - updater.ts 发送 channel/edition/deviceFingerprint - 新增 src/shared/utils/rollout.ts 灰度测试工具 - 新增 10 条 NPM 脚本 ## VCHSM 五层架构迁移 - 7 个业务模块 + ~500 处导入路径同步 ## 修复 - MediaCardContextMenu 改用共享 ContextMenu 组件 - ComboBox 尺寸参数显示统一 (size-format.ts) ## 文档 - UpdateA.md / build/README.md / README.md / .env.example 更新 Co-Authored-By: Claude <noreply@anthropic.com>
60 lines
2.4 KiB
TypeScript
60 lines
2.4 KiB
TypeScript
// ============================================================
|
||
// useSidecarSync — Sidecar 元数据防抖同步 Hook
|
||
//
|
||
// QuickEditPanel 编辑 → 250ms 防抖 → dataSource.writeSidecar() → 保存到 JSON。
|
||
// Phase 4 扩展:写入后通过事件总线通知宿主窗口刷新。
|
||
// ============================================================
|
||
|
||
import { useRef, useCallback } from 'react';
|
||
import { useCanvasStore, getCanvasStore } from '../stores/useCanvasStore';
|
||
import type { SidecarMetadata } from '../utils/ICanvasDataSource';
|
||
|
||
export function useSidecarSync() {
|
||
const updateItemMetadata = useCanvasStore((s) => s.updateItemMetadata);
|
||
const getDataSource = useCanvasStore((s) => s.getDataSource);
|
||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||
const pendingRef = useRef<Map<number, SidecarMetadata>>(new Map());
|
||
|
||
/**
|
||
* 提交元数据更新(250ms 防抖)。
|
||
* 更新 Store → 延迟写入 sidecar JSON(失败时保留 Store 中的副本)。
|
||
*/
|
||
const commitMetadata = useCallback(
|
||
(itemId: number, filePath: string, partial: Partial<SidecarMetadata>) => {
|
||
const current =
|
||
pendingRef.current.get(itemId) ||
|
||
({} as SidecarMetadata);
|
||
const merged = { ...current, ...partial };
|
||
pendingRef.current.set(itemId, merged);
|
||
|
||
// 立即更新 Store(乐观更新)
|
||
updateItemMetadata(itemId, partial);
|
||
|
||
// 防抖写入 sidecar
|
||
if (timerRef.current) clearTimeout(timerRef.current);
|
||
timerRef.current = setTimeout(async () => {
|
||
const batch = new Map(pendingRef.current);
|
||
pendingRef.current.clear();
|
||
|
||
const dataSource = getDataSource();
|
||
const store = getCanvasStore();
|
||
for (const [id, meta] of batch) {
|
||
try {
|
||
const item = store.getState().items.find(
|
||
(i) => i.id === id,
|
||
);
|
||
if (item) {
|
||
await dataSource.writeSidecar(item.path, meta);
|
||
}
|
||
} catch {
|
||
console.warn(`[useSidecarSync] 写入 sidecar 失败: ${filePath}`);
|
||
}
|
||
}
|
||
}, 250);
|
||
},
|
||
[updateItemMetadata, getDataSource],
|
||
);
|
||
|
||
return { commitMetadata };
|
||
}
|