87 lines
2.5 KiB
JavaScript
87 lines
2.5 KiB
JavaScript
/**
|
||
* Draft Save Worker
|
||
*
|
||
* 在 Web Worker 中执行草稿序列化和指纹计算,避免阻塞主线程。
|
||
* 主线程通过 postMessage 传入 canvas 快照,Worker 返回序列化后的 payload。
|
||
*
|
||
* 放置在 public/ 下以从同源加载,避免 assetPrefix (CDN) 导致的跨域 Worker 加载失败。
|
||
*/
|
||
|
||
let lastFingerprint = '';
|
||
|
||
/** Canonical source: src/views/canvas/workers/draftSaveUtils.ts — keep in sync */
|
||
function djb2(str) {
|
||
let hash = 5381;
|
||
for (let i = 0; i < str.length; i++) {
|
||
hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;
|
||
}
|
||
return hash >>> 0;
|
||
}
|
||
|
||
/**
|
||
* 计算草稿指纹:O(n log n) 排序保证一致性
|
||
*/
|
||
function computeFingerprint(nodes, edges, viewport) {
|
||
const nodeFingerprints = nodes
|
||
.map(
|
||
(n) =>
|
||
`${n.id}:${n.position?.x ?? 0},${n.position?.y ?? 0}:${typeof n.data === 'object' ? djb2(JSON.stringify(n.data)) : 0}`,
|
||
)
|
||
.sort()
|
||
.join('|');
|
||
const edgeFingerprints = edges
|
||
.map((e) => `${e.id}:${e.source}-${e.target}`)
|
||
.sort()
|
||
.join('|');
|
||
const vp = `V${Math.round(viewport.position.x)},${Math.round(viewport.position.y)},${viewport.zoom.toFixed(2)}`;
|
||
return `N${nodes.length}E${edges.length}:${vp}:${nodeFingerprints}::${edgeFingerprints}`;
|
||
}
|
||
|
||
/**
|
||
* 清理 blob:/data: URL,避免序列化无效引用
|
||
*/
|
||
function sanitizeDraftJson(draftJson) {
|
||
try {
|
||
const data = JSON.parse(draftJson);
|
||
return JSON.stringify(data, (_key, value) => {
|
||
if (typeof value === 'string' && (value.startsWith('blob:') || value.startsWith('data:'))) {
|
||
return '';
|
||
}
|
||
return value;
|
||
});
|
||
} catch {
|
||
return draftJson;
|
||
}
|
||
}
|
||
|
||
self.onmessage = (e) => {
|
||
try {
|
||
const { nodes, edges, viewport, projectUuid, syncVersion } = e.data;
|
||
|
||
const fingerprint = computeFingerprint(nodes, edges, viewport);
|
||
if (fingerprint === lastFingerprint) {
|
||
self.postMessage({ type: 'result', fingerprint, payload: null });
|
||
return;
|
||
}
|
||
|
||
lastFingerprint = fingerprint;
|
||
|
||
const draftData = { nodes, edges, savedAt: Date.now(), version: syncVersion };
|
||
const draftJson = sanitizeDraftJson(JSON.stringify(draftData));
|
||
|
||
self.postMessage({
|
||
type: 'result',
|
||
fingerprint,
|
||
payload: {
|
||
projectUuid,
|
||
draftJson,
|
||
viewportX: viewport.position.x.toString(),
|
||
viewportY: viewport.position.y.toString(),
|
||
viewportZoom: viewport.zoom.toString(),
|
||
},
|
||
});
|
||
} catch (err) {
|
||
self.postMessage({ type: 'error', message: err instanceof Error ? err.message : String(err) });
|
||
}
|
||
};
|