98 lines
2.6 KiB
JavaScript
98 lines
2.6 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),
|
||
});
|
||
}
|
||
};
|