// ============================================================ // 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 | null>(null); const pendingRef = useRef>(new Map()); /** * 提交元数据更新(250ms 防抖)。 * 更新 Store → 延迟写入 sidecar JSON(失败时保留 Store 中的副本)。 */ const commitMetadata = useCallback( (itemId: number, filePath: string, partial: Partial) => { 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 }; }