UNPKG

@tldraw/sync-core

Version:

tldraw infinite canvas SDK (multiplayer sync).

197 lines (196 loc) • 5.52 kB
import { isEqual, objectMapEntries, objectMapValues } from "@tldraw/utils"; const RecordOpType = { Put: "put", Patch: "patch", Remove: "remove" }; function getNetworkDiff(diff) { let res = null; for (const [k, v] of objectMapEntries(diff.added)) { if (!res) res = {}; res[k] = [RecordOpType.Put, v]; } for (const [from, to] of objectMapValues(diff.updated)) { const diff2 = diffRecord(from, to); if (diff2) { if (!res) res = {}; res[to.id] = [RecordOpType.Patch, diff2]; } } for (const removed of Object.keys(diff.removed)) { if (!res) res = {}; res[removed] = [RecordOpType.Remove]; } return res; } const ValueOpType = { Put: "put", Delete: "delete", Append: "append", Patch: "patch" }; function diffRecord(prev, next) { return diffObject(prev, next, /* @__PURE__ */ new Set(["props"])); } function diffObject(prev, next, nestedKeys) { if (prev === next) { return null; } let result = null; for (const key of Object.keys(prev)) { if (!(key in next)) { if (!result) result = {}; result[key] = [ValueOpType.Delete]; continue; } const prevVal = prev[key]; const nextVal = next[key]; if (!isEqual(prevVal, nextVal)) { if (nestedKeys?.has(key) && prevVal && nextVal) { const diff = diffObject(prevVal, nextVal); if (diff) { if (!result) result = {}; result[key] = [ValueOpType.Patch, diff]; } } else if (Array.isArray(nextVal) && Array.isArray(prevVal)) { const op = diffArray(prevVal, nextVal); if (op) { if (!result) result = {}; result[key] = op; } } else { if (!result) result = {}; result[key] = [ValueOpType.Put, nextVal]; } } } for (const key of Object.keys(next)) { if (!(key in prev)) { if (!result) result = {}; result[key] = [ValueOpType.Put, next[key]]; } } return result; } function diffValue(valueA, valueB) { if (Object.is(valueA, valueB)) return null; if (Array.isArray(valueA) && Array.isArray(valueB)) { return diffArray(valueA, valueB); } else if (!valueA || !valueB || typeof valueA !== "object" || typeof valueB !== "object") { return isEqual(valueA, valueB) ? null : [ValueOpType.Put, valueB]; } else { const diff = diffObject(valueA, valueB); return diff ? [ValueOpType.Patch, diff] : null; } } function diffArray(prevArray, nextArray) { if (Object.is(prevArray, nextArray)) return null; if (prevArray.length === nextArray.length) { const maxPatchIndexes = Math.max(prevArray.length / 5, 1); const toPatchIndexes = []; for (let i = 0; i < prevArray.length; i++) { if (!isEqual(prevArray[i], nextArray[i])) { toPatchIndexes.push(i); if (toPatchIndexes.length > maxPatchIndexes) { return [ValueOpType.Put, nextArray]; } } } if (toPatchIndexes.length === 0) { return null; } const diff = {}; for (const i of toPatchIndexes) { const prevItem = prevArray[i]; const nextItem = nextArray[i]; if (!prevItem || !nextItem) { diff[i] = [ValueOpType.Put, nextItem]; } else if (typeof prevItem === "object" && typeof nextItem === "object") { const op = diffValue(prevItem, nextItem); if (op) { diff[i] = op; } } else { diff[i] = [ValueOpType.Put, nextItem]; } } return [ValueOpType.Patch, diff]; } for (let i = 0; i < prevArray.length; i++) { if (!isEqual(prevArray[i], nextArray[i])) { return [ValueOpType.Put, nextArray]; } } return [ValueOpType.Append, nextArray.slice(prevArray.length), prevArray.length]; } function applyObjectDiff(object, objectDiff) { if (!object || typeof object !== "object") return object; const isArray = Array.isArray(object); let newObject = void 0; const set = (k, v) => { if (!newObject) { if (isArray) { newObject = [...object]; } else { newObject = { ...object }; } } if (isArray) { newObject[Number(k)] = v; } else { newObject[k] = v; } }; for (const [key, op] of Object.entries(objectDiff)) { switch (op[0]) { case ValueOpType.Put: { const value = op[1]; if (!isEqual(object[key], value)) { set(key, value); } break; } case ValueOpType.Append: { const values = op[1]; const offset = op[2]; const arr = object[key]; if (Array.isArray(arr) && arr.length === offset) { set(key, [...arr, ...values]); } break; } case ValueOpType.Patch: { if (object[key] && typeof object[key] === "object") { const diff = op[1]; const patched = applyObjectDiff(object[key], diff); if (patched !== object[key]) { set(key, patched); } } break; } case ValueOpType.Delete: { if (key in object) { if (!newObject) { if (isArray) { console.error("Can't delete array item yet (this should never happen)"); newObject = [...object]; } else { newObject = { ...object }; } } delete newObject[key]; } } } } return newObject ?? object; } export { RecordOpType, ValueOpType, applyObjectDiff, diffRecord, getNetworkDiff }; //# sourceMappingURL=diff.mjs.map