@tldraw/editor 5.4.1 → 5.5.0-canary.003bf5951274

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/dist-cjs/index.d.ts +37 -14
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +16 -12
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/MenuClickCapture.js +42 -3
  7. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +5 -1
  9. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +2 -5
  11. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +3 -3
  12. package/dist-cjs/lib/components/default-components/DefaultSpinner.js.map +2 -2
  13. package/dist-cjs/lib/constants.js +2 -0
  14. package/dist-cjs/lib/constants.js.map +2 -2
  15. package/dist-cjs/lib/editor/Editor.js +147 -157
  16. package/dist-cjs/lib/editor/Editor.js.map +3 -3
  17. package/dist-cjs/lib/editor/derivations/bindingsIndex.js +2 -0
  18. package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
  19. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +10 -6
  20. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
  21. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +4 -4
  22. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  23. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js +7 -3
  24. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js.map +2 -2
  25. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +14 -6
  26. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +3 -3
  27. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  28. package/dist-cjs/lib/exports/ExportDelay.js +10 -2
  29. package/dist-cjs/lib/exports/ExportDelay.js.map +2 -2
  30. package/dist-cjs/lib/exports/FontEmbedder.js +25 -11
  31. package/dist-cjs/lib/exports/FontEmbedder.js.map +2 -2
  32. package/dist-cjs/lib/exports/StyleEmbedder.js +9 -4
  33. package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
  34. package/dist-cjs/lib/exports/fetchCache.js +3 -0
  35. package/dist-cjs/lib/exports/fetchCache.js.map +2 -2
  36. package/dist-cjs/lib/exports/getSvgAsImage.js +10 -4
  37. package/dist-cjs/lib/exports/getSvgAsImage.js.map +2 -2
  38. package/dist-cjs/lib/exports/getSvgJsx.js +18 -9
  39. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  40. package/dist-cjs/lib/exports/parseCss.js +1 -1
  41. package/dist-cjs/lib/exports/parseCss.js.map +1 -1
  42. package/dist-cjs/lib/hooks/EditorComponentsContext.js.map +2 -2
  43. package/dist-cjs/lib/hooks/useCanvasEvents.js +24 -0
  44. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  45. package/dist-cjs/lib/hooks/useIdentity.js +6 -2
  46. package/dist-cjs/lib/hooks/useIdentity.js.map +3 -3
  47. package/dist-cjs/lib/license/LicenseManager.js +9 -1
  48. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  49. package/dist-cjs/lib/license/LicenseProvider.js +12 -4
  50. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  51. package/dist-cjs/lib/primitives/geometry/Arc2d.js +1 -1
  52. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  53. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +1 -1
  54. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  55. package/dist-cjs/lib/primitives/geometry/Edge2d.js +1 -1
  56. package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
  57. package/dist-cjs/lib/primitives/geometry/Group2d.js +8 -5
  58. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  59. package/dist-cjs/lib/primitives/geometry/Point2d.js +1 -1
  60. package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
  61. package/dist-cjs/lib/utils/debug-flags.js +0 -6
  62. package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
  63. package/dist-cjs/lib/utils/deepLinks.js +2 -1
  64. package/dist-cjs/lib/utils/deepLinks.js.map +2 -2
  65. package/dist-cjs/lib/utils/sync/LocalIndexedDb.js +22 -24
  66. package/dist-cjs/lib/utils/sync/LocalIndexedDb.js.map +2 -2
  67. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +81 -40
  68. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  69. package/dist-cjs/version.js +3 -3
  70. package/dist-cjs/version.js.map +1 -1
  71. package/dist-esm/index.d.mts +37 -14
  72. package/dist-esm/index.mjs +4 -2
  73. package/dist-esm/index.mjs.map +2 -2
  74. package/dist-esm/lib/TldrawEditor.mjs +17 -13
  75. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  76. package/dist-esm/lib/components/MenuClickCapture.mjs +42 -3
  77. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  78. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +5 -1
  79. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  80. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +2 -5
  81. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +3 -3
  82. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
  83. package/dist-esm/lib/constants.mjs +2 -0
  84. package/dist-esm/lib/constants.mjs.map +2 -2
  85. package/dist-esm/lib/editor/Editor.mjs +148 -157
  86. package/dist-esm/lib/editor/Editor.mjs.map +3 -3
  87. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs +2 -0
  88. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
  89. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +10 -6
  90. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
  91. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +4 -4
  92. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  93. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs +7 -3
  94. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs.map +2 -2
  95. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +14 -6
  96. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +3 -3
  97. package/dist-esm/lib/exports/ExportDelay.mjs +11 -3
  98. package/dist-esm/lib/exports/ExportDelay.mjs.map +2 -2
  99. package/dist-esm/lib/exports/FontEmbedder.mjs +25 -11
  100. package/dist-esm/lib/exports/FontEmbedder.mjs.map +2 -2
  101. package/dist-esm/lib/exports/StyleEmbedder.mjs +9 -4
  102. package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
  103. package/dist-esm/lib/exports/fetchCache.mjs +3 -0
  104. package/dist-esm/lib/exports/fetchCache.mjs.map +2 -2
  105. package/dist-esm/lib/exports/getSvgAsImage.mjs +10 -4
  106. package/dist-esm/lib/exports/getSvgAsImage.mjs.map +2 -2
  107. package/dist-esm/lib/exports/getSvgJsx.mjs +18 -9
  108. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  109. package/dist-esm/lib/exports/parseCss.mjs +1 -1
  110. package/dist-esm/lib/exports/parseCss.mjs.map +1 -1
  111. package/dist-esm/lib/hooks/EditorComponentsContext.mjs.map +2 -2
  112. package/dist-esm/lib/hooks/useCanvasEvents.mjs +24 -0
  113. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  114. package/dist-esm/lib/hooks/useIdentity.mjs +7 -3
  115. package/dist-esm/lib/hooks/useIdentity.mjs.map +3 -3
  116. package/dist-esm/lib/license/LicenseManager.mjs +9 -1
  117. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  118. package/dist-esm/lib/license/LicenseProvider.mjs +13 -5
  119. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  120. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +1 -1
  121. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  122. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +1 -1
  123. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  124. package/dist-esm/lib/primitives/geometry/Edge2d.mjs +1 -1
  125. package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
  126. package/dist-esm/lib/primitives/geometry/Group2d.mjs +12 -6
  127. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  128. package/dist-esm/lib/primitives/geometry/Point2d.mjs +1 -1
  129. package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
  130. package/dist-esm/lib/utils/debug-flags.mjs +0 -6
  131. package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
  132. package/dist-esm/lib/utils/deepLinks.mjs +2 -1
  133. package/dist-esm/lib/utils/deepLinks.mjs.map +2 -2
  134. package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs +22 -24
  135. package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs.map +2 -2
  136. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +81 -40
  137. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  138. package/dist-esm/version.mjs +3 -3
  139. package/dist-esm/version.mjs.map +1 -1
  140. package/editor.css +9 -1
  141. package/package.json +8 -11
  142. package/src/index.ts +5 -1
  143. package/src/lib/TldrawEditor.tsx +35 -21
  144. package/src/lib/components/MenuClickCapture.tsx +55 -4
  145. package/src/lib/components/default-components/DefaultCanvas.tsx +7 -1
  146. package/src/lib/components/default-components/DefaultErrorFallback.tsx +13 -5
  147. package/src/lib/components/default-components/DefaultSpinner.tsx +4 -1
  148. package/src/lib/constants.ts +8 -0
  149. package/src/lib/editor/Editor.test.ts +20 -0
  150. package/src/lib/editor/Editor.ts +228 -262
  151. package/src/lib/editor/derivations/bindingsIndex.ts +3 -0
  152. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +35 -4
  153. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +13 -7
  154. package/src/lib/editor/managers/TextManager/TextManager.test.ts +74 -0
  155. package/src/lib/editor/managers/TextManager/TextManager.ts +6 -5
  156. package/src/lib/editor/managers/ThemeManager/ThemeManager.test.ts +53 -0
  157. package/src/lib/editor/managers/ThemeManager/ThemeManager.ts +9 -3
  158. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +23 -8
  159. package/src/lib/editor/types/external-content.ts +5 -0
  160. package/src/lib/exports/ExportDelay.tsx +16 -4
  161. package/src/lib/exports/FontEmbedder.test.ts +99 -0
  162. package/src/lib/exports/FontEmbedder.ts +32 -11
  163. package/src/lib/exports/StyleEmbedder.ts +13 -4
  164. package/src/lib/exports/fetchCache.test.ts +22 -0
  165. package/src/lib/exports/fetchCache.ts +4 -0
  166. package/src/lib/exports/getSvgAsImage.ts +13 -5
  167. package/src/lib/exports/getSvgJsx.test.ts +43 -1
  168. package/src/lib/exports/getSvgJsx.tsx +28 -16
  169. package/src/lib/exports/parseCss.test.ts +4 -0
  170. package/src/lib/exports/parseCss.ts +2 -2
  171. package/src/lib/hooks/EditorComponentsContext.tsx +2 -1
  172. package/src/lib/hooks/useCanvasEvents.ts +42 -0
  173. package/src/lib/hooks/useIdentity.tsx +12 -1
  174. package/src/lib/license/LicenseManager.ts +13 -1
  175. package/src/lib/license/LicenseProvider.test.tsx +76 -0
  176. package/src/lib/license/LicenseProvider.tsx +19 -5
  177. package/src/lib/primitives/geometry/Arc2d.test.ts +9 -0
  178. package/src/lib/primitives/geometry/Arc2d.ts +1 -1
  179. package/src/lib/primitives/geometry/CubicBezier2d.test.ts +13 -0
  180. package/src/lib/primitives/geometry/CubicBezier2d.ts +1 -1
  181. package/src/lib/primitives/geometry/Edge2d.test.ts +21 -3
  182. package/src/lib/primitives/geometry/Edge2d.ts +1 -1
  183. package/src/lib/primitives/geometry/Group2d.test.ts +26 -5
  184. package/src/lib/primitives/geometry/Group2d.ts +18 -7
  185. package/src/lib/primitives/geometry/Point2d.test.ts +14 -3
  186. package/src/lib/primitives/geometry/Point2d.ts +1 -1
  187. package/src/lib/test/preserveFocus.test.tsx +81 -0
  188. package/src/lib/utils/debug-flags.ts +0 -6
  189. package/src/lib/utils/deepLinks.test.ts +15 -0
  190. package/src/lib/utils/deepLinks.ts +8 -3
  191. package/src/lib/utils/sync/LocalIndexedDb.test.ts +95 -0
  192. package/src/lib/utils/sync/LocalIndexedDb.ts +31 -27
  193. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +257 -1
  194. package/src/lib/utils/sync/TLLocalSyncClient.ts +112 -54
  195. package/src/version.ts +3 -3
  196. package/dist-cjs/lib/primitives/geometry/geometry.bench.js +0 -133
  197. package/dist-cjs/lib/primitives/geometry/geometry.bench.js.map +0 -7
  198. package/dist-esm/lib/primitives/geometry/geometry.bench.mjs +0 -132
  199. package/dist-esm/lib/primitives/geometry/geometry.bench.mjs.map +0 -7
  200. package/src/lib/primitives/geometry/geometry.bench.ts +0 -179
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/primitives/geometry/Group2d.ts"],
4
- "sourcesContent": ["import { assert, invLerp, lerp } from '@tldraw/utils'\nimport { Box } from '../Box'\nimport { Mat } from '../Mat'\nimport { Vec, VecLike } from '../Vec'\nimport { Geometry2d, Geometry2dFilters, Geometry2dOptions } from './Geometry2d'\n\n/** @public */\nexport class Group2d extends Geometry2d {\n\tchildren: Geometry2d[] = []\n\tignoredChildren: Geometry2d[] = []\n\n\tconstructor(\n\t\tconfig: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & {\n\t\t\tchildren: Geometry2d[]\n\t\t}\n\t) {\n\t\tsuper({ ...config, isClosed: true, isFilled: false })\n\n\t\tconst addChildren = (children: Geometry2d[]) => {\n\t\t\tfor (const child of children) {\n\t\t\t\tif (child instanceof Group2d) {\n\t\t\t\t\taddChildren(child.children)\n\t\t\t\t} else if (child.ignore) {\n\t\t\t\t\tthis.ignoredChildren.push(child)\n\t\t\t\t} else {\n\t\t\t\t\tthis.children.push(child)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\taddChildren(config.children)\n\n\t\tif (this.children.length === 0) throw Error('Group2d must have at least one child')\n\t}\n\n\toverride getVertices(filters: Geometry2dFilters): Vec[] {\n\t\tif (this.isExcludedByFilter(filters)) return []\n\t\tconst vertices: Vec[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst childVertices = child.getVertices(filters)\n\t\t\tfor (let i = 0, n = childVertices.length; i < n; i++) {\n\t\t\t\tvertices.push(childVertices[i])\n\t\t\t}\n\t\t}\n\t\treturn vertices\n\t}\n\n\toverride nearestPoint(point: VecLike, filters?: Geometry2dFilters): Vec {\n\t\tlet dist = Infinity\n\t\tlet nearest: Vec | undefined\n\n\t\tconst { children } = this\n\n\t\tif (children.length === 0) {\n\t\t\tthrow Error('no children')\n\t\t}\n\n\t\tlet p: Vec\n\t\tlet d: number\n\t\tfor (const child of children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tp = child.nearestPoint(point, filters)\n\t\t\td = Vec.Dist2(p, point)\n\t\t\tif (d < dist) {\n\t\t\t\tdist = d\n\t\t\t\tnearest = p\n\t\t\t}\n\t\t}\n\t\tif (!nearest) throw Error('nearest point not found')\n\t\treturn nearest\n\t}\n\n\toverride distanceToPoint(point: VecLike, hitInside = false, filters?: Geometry2dFilters) {\n\t\tlet smallestDistance = Infinity\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst distance = child.distanceToPoint(point, hitInside, filters)\n\t\t\tif (distance < smallestDistance) {\n\t\t\t\tsmallestDistance = distance\n\t\t\t}\n\t\t}\n\t\treturn smallestDistance\n\t}\n\n\toverride hitTestPoint(\n\t\tpoint: VecLike,\n\t\tmargin: number,\n\t\thitInside: boolean,\n\t\tfilters = Geometry2dFilters.EXCLUDE_LABELS\n\t): boolean {\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tif (child.hitTestPoint(point, margin, hitInside)) return true\n\t\t}\n\t\treturn false\n\t}\n\n\toverride hitTestLineSegment(\n\t\tA: VecLike,\n\t\tB: VecLike,\n\t\tzoom: number,\n\t\tfilters = Geometry2dFilters.EXCLUDE_LABELS\n\t): boolean {\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tif (child.hitTestLineSegment(A, B, zoom)) return true\n\t\t}\n\t\treturn false\n\t}\n\n\toverride intersectLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectLineSegment(A, B, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride intersectCircle(center: VecLike, radius: number, filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectCircle(center, radius, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride getBoundsVertices(): Vec[] {\n\t\tif (this.excludeFromShapeBounds) return []\n\t\tconst vertices: Vec[] = []\n\t\tfor (const child of this.children) {\n\t\t\tconst childVertices = child.getBoundsVertices()\n\t\t\tfor (let i = 0, n = childVertices.length; i < n; i++) {\n\t\t\t\tvertices.push(childVertices[i])\n\t\t\t}\n\t\t}\n\t\treturn vertices\n\t}\n\n\toverride intersectPolygon(polygon: VecLike[], filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectPolygon(polygon, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride intersectPolyline(polyline: VecLike[], filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectPolyline(polyline, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride interpolateAlongEdge(t: number, filters?: Geometry2dFilters): Vec {\n\t\tconst totalLength = this.getLength(filters)\n\n\t\tconst distanceToTravel = t * totalLength\n\t\tlet distanceTraveled = 0\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst childLength = child.length\n\t\t\tconst newDistanceTraveled = distanceTraveled + childLength\n\t\t\tif (newDistanceTraveled >= distanceToTravel) {\n\t\t\t\treturn child.interpolateAlongEdge(\n\t\t\t\t\tinvLerp(distanceTraveled, newDistanceTraveled, distanceToTravel),\n\t\t\t\t\tfilters\n\t\t\t\t)\n\t\t\t}\n\t\t\tdistanceTraveled = newDistanceTraveled\n\t\t}\n\n\t\treturn this.children[this.children.length - 1].interpolateAlongEdge(1, filters)\n\t}\n\n\toverride uninterpolateAlongEdge(point: VecLike, filters?: Geometry2dFilters): number {\n\t\tconst totalLength = this.getLength(filters)\n\n\t\tlet closestChild = null\n\t\tlet closestDistance = Infinity\n\t\tlet distanceTraveled = 0\n\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst childLength = child.getLength(filters)\n\t\t\tconst newDistanceTraveled = distanceTraveled + childLength\n\n\t\t\tconst distance = child.distanceToPoint(point, false, filters)\n\t\t\tif (distance < closestDistance) {\n\t\t\t\tclosestDistance = distance\n\t\t\t\tclosestChild = {\n\t\t\t\t\tstartLength: distanceTraveled,\n\t\t\t\t\tendLength: newDistanceTraveled,\n\t\t\t\t\tchild,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdistanceTraveled = newDistanceTraveled\n\t\t}\n\n\t\tassert(closestChild)\n\n\t\tconst normalizedDistanceInChild = closestChild.child.uninterpolateAlongEdge(point, filters)\n\t\tconst childTLength = lerp(\n\t\t\tclosestChild.startLength,\n\t\t\tclosestChild.endLength,\n\t\t\tnormalizedDistanceInChild\n\t\t)\n\t\treturn childTLength / totalLength\n\t}\n\n\toverride transform(transform: Mat): Geometry2d {\n\t\treturn new Group2d({\n\t\t\tchildren: this.children.map((c) => c.transform(transform)),\n\t\t\tisLabel: this.isLabel,\n\t\t\tdebugColor: this.debugColor,\n\t\t\tignore: this.ignore,\n\t\t})\n\t}\n\n\tgetArea() {\n\t\t// todo: this is a temporary solution, assuming that the first child defines the group size; we would want to flatten the group and then find the area of the hull polygon\n\t\treturn this.children[0].area\n\t}\n\n\ttoSimpleSvgPath() {\n\t\tlet path = ''\n\t\tfor (const child of this.children) {\n\t\t\tpath += child.toSimpleSvgPath()\n\t\t}\n\n\t\tconst corners = Box.FromPoints(this.boundsVertices).corners\n\t\t// draw just a few pixels around each corner, e.g. an L shape for the bottom left\n\n\t\tfor (let i = 0, n = corners.length; i < n; i++) {\n\t\t\tconst corner = corners[i]\n\t\t\tconst prevCorner = corners[(i - 1 + n) % n]\n\t\t\tconst prevDist = corner.dist(prevCorner)\n\t\t\tconst nextCorner = corners[(i + 1) % n]\n\t\t\tconst nextDist = corner.dist(nextCorner)\n\n\t\t\tconst A = corner.clone().lrp(prevCorner, 4 / prevDist)\n\t\t\tconst B = corner\n\t\t\tconst C = corner.clone().lrp(nextCorner, 4 / nextDist)\n\n\t\t\tpath += `M${A.x},${A.y} L${B.x},${B.y} L${C.x},${C.y} `\n\t\t}\n\t\treturn path\n\t}\n\n\tgetLength(filters?: Geometry2dFilters): number {\n\t\tlet length = 0\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tlength += child.length\n\t\t}\n\t\treturn length\n\t}\n\n\tgetSvgPathData(): string {\n\t\treturn this.children.map((c, i) => (c.isLabel ? '' : c.getSvgPathData(i === 0))).join(' ')\n\t}\n\n\toverlapsPolygon(polygon: VecLike[]): boolean {\n\t\treturn this.children.some((child) => child.overlapsPolygon(polygon))\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { assert, invLerp, lerp } from '@tldraw/utils'\nimport { Box } from '../Box'\nimport { MatModel } from '../Mat'\nimport { Vec, VecLike } from '../Vec'\nimport {\n\tGeometry2d,\n\tGeometry2dFilters,\n\tGeometry2dOptions,\n\tTransformedGeometry2dOptions,\n} from './Geometry2d'\n\n/** @public */\nexport class Group2d extends Geometry2d {\n\tchildren: Geometry2d[] = []\n\tignoredChildren: Geometry2d[] = []\n\n\tconstructor(\n\t\tconfig: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & {\n\t\t\tchildren: Geometry2d[]\n\t\t}\n\t) {\n\t\tsuper({ ...config, isClosed: true, isFilled: false })\n\n\t\tconst addChildren = (children: Geometry2d[]) => {\n\t\t\tfor (const child of children) {\n\t\t\t\tif (child instanceof Group2d) {\n\t\t\t\t\taddChildren(child.children)\n\t\t\t\t} else if (child.ignore) {\n\t\t\t\t\tthis.ignoredChildren.push(child)\n\t\t\t\t} else {\n\t\t\t\t\tthis.children.push(child)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\taddChildren(config.children)\n\n\t\tif (this.children.length === 0) throw Error('Group2d must have at least one child')\n\t}\n\n\toverride getVertices(filters: Geometry2dFilters): Vec[] {\n\t\tif (this.isExcludedByFilter(filters)) return []\n\t\tconst vertices: Vec[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst childVertices = child.getVertices(filters)\n\t\t\tfor (let i = 0, n = childVertices.length; i < n; i++) {\n\t\t\t\tvertices.push(childVertices[i])\n\t\t\t}\n\t\t}\n\t\treturn vertices\n\t}\n\n\toverride nearestPoint(point: VecLike, filters?: Geometry2dFilters): Vec {\n\t\tlet dist = Infinity\n\t\tlet nearest: Vec | undefined\n\n\t\tconst { children } = this\n\n\t\tif (children.length === 0) {\n\t\t\tthrow Error('no children')\n\t\t}\n\n\t\tlet p: Vec\n\t\tlet d: number\n\t\tfor (const child of children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tp = child.nearestPoint(point, filters)\n\t\t\td = Vec.Dist2(p, point)\n\t\t\tif (d < dist) {\n\t\t\t\tdist = d\n\t\t\t\tnearest = p\n\t\t\t}\n\t\t}\n\t\tif (!nearest) throw Error('nearest point not found')\n\t\treturn nearest\n\t}\n\n\toverride distanceToPoint(point: VecLike, hitInside = false, filters?: Geometry2dFilters) {\n\t\tlet smallestDistance = Infinity\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst distance = child.distanceToPoint(point, hitInside, filters)\n\t\t\tif (distance < smallestDistance) {\n\t\t\t\tsmallestDistance = distance\n\t\t\t}\n\t\t}\n\t\treturn smallestDistance\n\t}\n\n\toverride hitTestPoint(\n\t\tpoint: VecLike,\n\t\tmargin: number,\n\t\thitInside: boolean,\n\t\tfilters = Geometry2dFilters.EXCLUDE_LABELS\n\t): boolean {\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tif (child.hitTestPoint(point, margin, hitInside)) return true\n\t\t}\n\t\treturn false\n\t}\n\n\toverride hitTestLineSegment(\n\t\tA: VecLike,\n\t\tB: VecLike,\n\t\tzoom: number,\n\t\tfilters = Geometry2dFilters.EXCLUDE_LABELS\n\t): boolean {\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tif (child.hitTestLineSegment(A, B, zoom)) return true\n\t\t}\n\t\treturn false\n\t}\n\n\toverride intersectLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectLineSegment(A, B, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride intersectCircle(center: VecLike, radius: number, filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectCircle(center, radius, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride getBoundsVertices(): Vec[] {\n\t\tif (this.excludeFromShapeBounds) return []\n\t\tconst vertices: Vec[] = []\n\t\tfor (const child of this.children) {\n\t\t\tconst childVertices = child.getBoundsVertices()\n\t\t\tfor (let i = 0, n = childVertices.length; i < n; i++) {\n\t\t\t\tvertices.push(childVertices[i])\n\t\t\t}\n\t\t}\n\t\treturn vertices\n\t}\n\n\toverride intersectPolygon(polygon: VecLike[], filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectPolygon(polygon, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride intersectPolyline(polyline: VecLike[], filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectPolyline(polyline, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride interpolateAlongEdge(t: number, filters?: Geometry2dFilters): Vec {\n\t\tconst totalLength = this.getLength(filters)\n\n\t\tconst distanceToTravel = t * totalLength\n\t\tlet distanceTraveled = 0\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst childLength = child.length\n\t\t\tconst newDistanceTraveled = distanceTraveled + childLength\n\t\t\tif (newDistanceTraveled >= distanceToTravel) {\n\t\t\t\treturn child.interpolateAlongEdge(\n\t\t\t\t\tinvLerp(distanceTraveled, newDistanceTraveled, distanceToTravel),\n\t\t\t\t\tfilters\n\t\t\t\t)\n\t\t\t}\n\t\t\tdistanceTraveled = newDistanceTraveled\n\t\t}\n\n\t\treturn this.children[this.children.length - 1].interpolateAlongEdge(1, filters)\n\t}\n\n\toverride uninterpolateAlongEdge(point: VecLike, filters?: Geometry2dFilters): number {\n\t\tconst totalLength = this.getLength(filters)\n\n\t\tlet closestChild = null\n\t\tlet closestDistance = Infinity\n\t\tlet distanceTraveled = 0\n\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst childLength = child.getLength(filters)\n\t\t\tconst newDistanceTraveled = distanceTraveled + childLength\n\n\t\t\tconst distance = child.distanceToPoint(point, false, filters)\n\t\t\tif (distance < closestDistance) {\n\t\t\t\tclosestDistance = distance\n\t\t\t\tclosestChild = {\n\t\t\t\t\tstartLength: distanceTraveled,\n\t\t\t\t\tendLength: newDistanceTraveled,\n\t\t\t\t\tchild,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdistanceTraveled = newDistanceTraveled\n\t\t}\n\n\t\tassert(closestChild)\n\n\t\tconst normalizedDistanceInChild = closestChild.child.uninterpolateAlongEdge(point, filters)\n\t\tconst childTLength = lerp(\n\t\t\tclosestChild.startLength,\n\t\t\tclosestChild.endLength,\n\t\t\tnormalizedDistanceInChild\n\t\t)\n\t\treturn childTLength / totalLength\n\t}\n\n\toverride transform(transform: MatModel, opts?: TransformedGeometry2dOptions): Geometry2d {\n\t\t// Each child's own transform preserves its `ignore` flag, so the constructor\n\t\t// re-partitions ignored children back into `ignoredChildren` instead of dropping\n\t\t// them (#10562).\n\t\treturn new Group2d({\n\t\t\tchildren: [...this.children, ...this.ignoredChildren].map((c) => c.transform(transform)),\n\t\t\tisLabel: opts?.isLabel ?? this.isLabel,\n\t\t\tisEmptyLabel: this.isEmptyLabel,\n\t\t\tisInternal: opts?.isInternal ?? this.isInternal,\n\t\t\tdebugColor: opts?.debugColor ?? this.debugColor,\n\t\t\tignore: opts?.ignore ?? this.ignore,\n\t\t\texcludeFromShapeBounds: this.excludeFromShapeBounds,\n\t\t})\n\t}\n\n\tgetArea() {\n\t\t// todo: this is a temporary solution, assuming that the first child defines the group size; we would want to flatten the group and then find the area of the hull polygon\n\t\treturn this.children[0].area\n\t}\n\n\ttoSimpleSvgPath() {\n\t\tlet path = ''\n\t\tfor (const child of this.children) {\n\t\t\tpath += child.toSimpleSvgPath()\n\t\t}\n\n\t\tconst corners = Box.FromPoints(this.boundsVertices).corners\n\t\t// draw just a few pixels around each corner, e.g. an L shape for the bottom left\n\n\t\tfor (let i = 0, n = corners.length; i < n; i++) {\n\t\t\tconst corner = corners[i]\n\t\t\tconst prevCorner = corners[(i - 1 + n) % n]\n\t\t\tconst prevDist = corner.dist(prevCorner)\n\t\t\tconst nextCorner = corners[(i + 1) % n]\n\t\t\tconst nextDist = corner.dist(nextCorner)\n\n\t\t\tconst A = corner.clone().lrp(prevCorner, 4 / prevDist)\n\t\t\tconst B = corner\n\t\t\tconst C = corner.clone().lrp(nextCorner, 4 / nextDist)\n\n\t\t\tpath += `M${A.x},${A.y} L${B.x},${B.y} L${C.x},${C.y} `\n\t\t}\n\t\treturn path\n\t}\n\n\tgetLength(filters?: Geometry2dFilters): number {\n\t\tlet length = 0\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tlength += child.length\n\t\t}\n\t\treturn length\n\t}\n\n\tgetSvgPathData(): string {\n\t\treturn this.children.map((c, i) => (c.isLabel ? '' : c.getSvgPathData(i === 0))).join(' ')\n\t}\n\n\toverlapsPolygon(polygon: VecLike[]): boolean {\n\t\treturn this.children.some((child) => child.overlapsPolygon(polygon))\n\t}\n}\n"],
5
+ "mappings": "AAAA,SAAS,QAAQ,SAAS,YAAY;AACtC,SAAS,WAAW;AAEpB,SAAS,WAAoB;AAC7B;AAAA,EACC;AAAA,EACA;AAAA,OAGM;AAGA,MAAM,gBAAgB,WAAW;AAAA,EACvC,WAAyB,CAAC;AAAA,EAC1B,kBAAgC,CAAC;AAAA,EAEjC,YACC,QAGC;AACD,UAAM,EAAE,GAAG,QAAQ,UAAU,MAAM,UAAU,MAAM,CAAC;AAEpD,UAAM,cAAc,CAAC,aAA2B;AAC/C,iBAAW,SAAS,UAAU;AAC7B,YAAI,iBAAiB,SAAS;AAC7B,sBAAY,MAAM,QAAQ;AAAA,QAC3B,WAAW,MAAM,QAAQ;AACxB,eAAK,gBAAgB,KAAK,KAAK;AAAA,QAChC,OAAO;AACN,eAAK,SAAS,KAAK,KAAK;AAAA,QACzB;AAAA,MACD;AAAA,IACD;AAEA,gBAAY,OAAO,QAAQ;AAE3B,QAAI,KAAK,SAAS,WAAW,EAAG,OAAM,MAAM,sCAAsC;AAAA,EACnF;AAAA,EAES,YAAY,SAAmC;AACvD,QAAI,KAAK,mBAAmB,OAAO,EAAG,QAAO,CAAC;AAC9C,UAAM,WAAkB,CAAC;AACzB,eAAW,SAAS,KAAK,UAAU;AAClC,UAAI,MAAM,mBAAmB,OAAO,EAAG;AACvC,YAAM,gBAAgB,MAAM,YAAY,OAAO;AAC/C,eAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,IAAI,GAAG,KAAK;AACrD,iBAAS,KAAK,cAAc,CAAC,CAAC;AAAA,MAC/B;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA,EAES,aAAa,OAAgB,SAAkC;AACvE,QAAI,OAAO;AACX,QAAI;AAEJ,UAAM,EAAE,SAAS,IAAI;AAErB,QAAI,SAAS,WAAW,GAAG;AAC1B,YAAM,MAAM,aAAa;AAAA,IAC1B;AAEA,QAAI;AACJ,QAAI;AACJ,eAAW,SAAS,UAAU;AAC7B,UAAI,MAAM,mBAAmB,OAAO,EAAG;AACvC,UAAI,MAAM,aAAa,OAAO,OAAO;AACrC,UAAI,IAAI,MAAM,GAAG,KAAK;AACtB,UAAI,IAAI,MAAM;AACb,eAAO;AACP,kBAAU;AAAA,MACX;AAAA,IACD;AACA,QAAI,CAAC,QAAS,OAAM,MAAM,yBAAyB;AACnD,WAAO;AAAA,EACR;AAAA,EAES,gBAAgB,OAAgB,YAAY,OAAO,SAA6B;AACxF,QAAI,mBAAmB;AACvB,eAAW,SAAS,KAAK,UAAU;AAClC,UAAI,MAAM,mBAAmB,OAAO,EAAG;AACvC,YAAM,WAAW,MAAM,gBAAgB,OAAO,WAAW,OAAO;AAChE,UAAI,WAAW,kBAAkB;AAChC,2BAAmB;AAAA,MACpB;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA,EAES,aACR,OACA,QACA,WACA,UAAU,kBAAkB,gBAClB;AACV,eAAW,SAAS,KAAK,UAAU;AAClC,UAAI,MAAM,mBAAmB,OAAO,EAAG;AACvC,UAAI,MAAM,aAAa,OAAO,QAAQ,SAAS,EAAG,QAAO;AAAA,IAC1D;AACA,WAAO;AAAA,EACR;AAAA,EAES,mBACR,GACA,GACA,MACA,UAAU,kBAAkB,gBAClB;AACV,eAAW,SAAS,KAAK,UAAU;AAClC,UAAI,MAAM,mBAAmB,OAAO,EAAG;AACvC,UAAI,MAAM,mBAAmB,GAAG,GAAG,IAAI,EAAG,QAAO;AAAA,IAClD;AACA,WAAO;AAAA,EACR;AAAA,EAES,qBAAqB,GAAY,GAAY,SAA6B;AAClF,UAAM,SAAoB,CAAC;AAC3B,eAAW,SAAS,KAAK,UAAU;AAClC,UAAI,MAAM,mBAAmB,OAAO,EAAG;AACvC,YAAM,OAAO,MAAM,qBAAqB,GAAG,GAAG,OAAO;AACrD,eAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,IAAI,GAAG,KAAK;AAC5C,eAAO,KAAK,KAAK,CAAC,CAAC;AAAA,MACpB;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA,EAES,gBAAgB,QAAiB,QAAgB,SAA6B;AACtF,UAAM,SAAoB,CAAC;AAC3B,eAAW,SAAS,KAAK,UAAU;AAClC,UAAI,MAAM,mBAAmB,OAAO,EAAG;AACvC,YAAM,OAAO,MAAM,gBAAgB,QAAQ,QAAQ,OAAO;AAC1D,eAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,IAAI,GAAG,KAAK;AAC5C,eAAO,KAAK,KAAK,CAAC,CAAC;AAAA,MACpB;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA,EAES,oBAA2B;AACnC,QAAI,KAAK,uBAAwB,QAAO,CAAC;AACzC,UAAM,WAAkB,CAAC;AACzB,eAAW,SAAS,KAAK,UAAU;AAClC,YAAM,gBAAgB,MAAM,kBAAkB;AAC9C,eAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,IAAI,GAAG,KAAK;AACrD,iBAAS,KAAK,cAAc,CAAC,CAAC;AAAA,MAC/B;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA,EAES,iBAAiB,SAAoB,SAA6B;AAC1E,UAAM,SAAoB,CAAC;AAC3B,eAAW,SAAS,KAAK,UAAU;AAClC,UAAI,MAAM,mBAAmB,OAAO,EAAG;AACvC,YAAM,OAAO,MAAM,iBAAiB,SAAS,OAAO;AACpD,eAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,IAAI,GAAG,KAAK;AAC5C,eAAO,KAAK,KAAK,CAAC,CAAC;AAAA,MACpB;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA,EAES,kBAAkB,UAAqB,SAA6B;AAC5E,UAAM,SAAoB,CAAC;AAC3B,eAAW,SAAS,KAAK,UAAU;AAClC,UAAI,MAAM,mBAAmB,OAAO,EAAG;AACvC,YAAM,OAAO,MAAM,kBAAkB,UAAU,OAAO;AACtD,eAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,IAAI,GAAG,KAAK;AAC5C,eAAO,KAAK,KAAK,CAAC,CAAC;AAAA,MACpB;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA,EAES,qBAAqB,GAAW,SAAkC;AAC1E,UAAM,cAAc,KAAK,UAAU,OAAO;AAE1C,UAAM,mBAAmB,IAAI;AAC7B,QAAI,mBAAmB;AACvB,eAAW,SAAS,KAAK,UAAU;AAClC,UAAI,MAAM,mBAAmB,OAAO,EAAG;AACvC,YAAM,cAAc,MAAM;AAC1B,YAAM,sBAAsB,mBAAmB;AAC/C,UAAI,uBAAuB,kBAAkB;AAC5C,eAAO,MAAM;AAAA,UACZ,QAAQ,kBAAkB,qBAAqB,gBAAgB;AAAA,UAC/D;AAAA,QACD;AAAA,MACD;AACA,yBAAmB;AAAA,IACpB;AAEA,WAAO,KAAK,SAAS,KAAK,SAAS,SAAS,CAAC,EAAE,qBAAqB,GAAG,OAAO;AAAA,EAC/E;AAAA,EAES,uBAAuB,OAAgB,SAAqC;AACpF,UAAM,cAAc,KAAK,UAAU,OAAO;AAE1C,QAAI,eAAe;AACnB,QAAI,kBAAkB;AACtB,QAAI,mBAAmB;AAEvB,eAAW,SAAS,KAAK,UAAU;AAClC,UAAI,MAAM,mBAAmB,OAAO,EAAG;AACvC,YAAM,cAAc,MAAM,UAAU,OAAO;AAC3C,YAAM,sBAAsB,mBAAmB;AAE/C,YAAM,WAAW,MAAM,gBAAgB,OAAO,OAAO,OAAO;AAC5D,UAAI,WAAW,iBAAiB;AAC/B,0BAAkB;AAClB,uBAAe;AAAA,UACd,aAAa;AAAA,UACb,WAAW;AAAA,UACX;AAAA,QACD;AAAA,MACD;AAEA,yBAAmB;AAAA,IACpB;AAEA,WAAO,YAAY;AAEnB,UAAM,4BAA4B,aAAa,MAAM,uBAAuB,OAAO,OAAO;AAC1F,UAAM,eAAe;AAAA,MACpB,aAAa;AAAA,MACb,aAAa;AAAA,MACb;AAAA,IACD;AACA,WAAO,eAAe;AAAA,EACvB;AAAA,EAES,UAAU,WAAqB,MAAiD;AAIxF,WAAO,IAAI,QAAQ;AAAA,MAClB,UAAU,CAAC,GAAG,KAAK,UAAU,GAAG,KAAK,eAAe,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,SAAS,CAAC;AAAA,MACvF,SAAS,MAAM,WAAW,KAAK;AAAA,MAC/B,cAAc,KAAK;AAAA,MACnB,YAAY,MAAM,cAAc,KAAK;AAAA,MACrC,YAAY,MAAM,cAAc,KAAK;AAAA,MACrC,QAAQ,MAAM,UAAU,KAAK;AAAA,MAC7B,wBAAwB,KAAK;AAAA,IAC9B,CAAC;AAAA,EACF;AAAA,EAEA,UAAU;AAET,WAAO,KAAK,SAAS,CAAC,EAAE;AAAA,EACzB;AAAA,EAEA,kBAAkB;AACjB,QAAI,OAAO;AACX,eAAW,SAAS,KAAK,UAAU;AAClC,cAAQ,MAAM,gBAAgB;AAAA,IAC/B;AAEA,UAAM,UAAU,IAAI,WAAW,KAAK,cAAc,EAAE;AAGpD,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,IAAI,GAAG,KAAK;AAC/C,YAAM,SAAS,QAAQ,CAAC;AACxB,YAAM,aAAa,SAAS,IAAI,IAAI,KAAK,CAAC;AAC1C,YAAM,WAAW,OAAO,KAAK,UAAU;AACvC,YAAM,aAAa,SAAS,IAAI,KAAK,CAAC;AACtC,YAAM,WAAW,OAAO,KAAK,UAAU;AAEvC,YAAM,IAAI,OAAO,MAAM,EAAE,IAAI,YAAY,IAAI,QAAQ;AACrD,YAAM,IAAI;AACV,YAAM,IAAI,OAAO,MAAM,EAAE,IAAI,YAAY,IAAI,QAAQ;AAErD,cAAQ,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC;AAAA,IACrD;AACA,WAAO;AAAA,EACR;AAAA,EAEA,UAAU,SAAqC;AAC9C,QAAI,SAAS;AACb,eAAW,SAAS,KAAK,UAAU;AAClC,UAAI,MAAM,mBAAmB,OAAO,EAAG;AACvC,gBAAU,MAAM;AAAA,IACjB;AACA,WAAO;AAAA,EACR;AAAA,EAEA,iBAAyB;AACxB,WAAO,KAAK,SAAS,IAAI,CAAC,GAAG,MAAO,EAAE,UAAU,KAAK,EAAE,eAAe,MAAM,CAAC,CAAE,EAAE,KAAK,GAAG;AAAA,EAC1F;AAAA,EAEA,gBAAgB,SAA6B;AAC5C,WAAO,KAAK,SAAS,KAAK,CAAC,UAAU,MAAM,gBAAgB,OAAO,CAAC;AAAA,EACpE;AACD;",
6
6
  "names": []
7
7
  }
@@ -18,7 +18,7 @@ class Point2d extends Geometry2d {
18
18
  }
19
19
  getSvgPathData() {
20
20
  const { _point: point } = this;
21
- return `M${point.toFixed()}`;
21
+ return `M${point}`;
22
22
  }
23
23
  }
24
24
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/primitives/geometry/Point2d.ts"],
4
- "sourcesContent": ["import { Vec, VecLike } from '../Vec'\nimport { Geometry2d, Geometry2dOptions } from './Geometry2d'\n\n/** @public */\nexport class Point2d extends Geometry2d {\n\tprivate _point: Vec\n\n\tconstructor(\n\t\tconfig: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & { margin: number; point: Vec }\n\t) {\n\t\tsuper({ ...config, isClosed: true, isFilled: true })\n\t\tconst { point } = config\n\n\t\tthis._point = point\n\t}\n\n\tgetVertices() {\n\t\treturn [this._point]\n\t}\n\n\tnearestPoint(): Vec {\n\t\treturn this._point\n\t}\n\n\thitTestLineSegment(A: VecLike, B: VecLike, margin: number): boolean {\n\t\treturn Vec.DistanceToLineSegment(A, B, this._point) < margin\n\t}\n\n\tgetSvgPathData() {\n\t\tconst { _point: point } = this\n\t\treturn `M${point.toFixed()}`\n\t}\n}\n"],
5
- "mappings": "AAAA,SAAS,WAAoB;AAC7B,SAAS,kBAAqC;AAGvC,MAAM,gBAAgB,WAAW;AAAA,EAC/B;AAAA,EAER,YACC,QACC;AACD,UAAM,EAAE,GAAG,QAAQ,UAAU,MAAM,UAAU,KAAK,CAAC;AACnD,UAAM,EAAE,MAAM,IAAI;AAElB,SAAK,SAAS;AAAA,EACf;AAAA,EAEA,cAAc;AACb,WAAO,CAAC,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,eAAoB;AACnB,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,mBAAmB,GAAY,GAAY,QAAyB;AACnE,WAAO,IAAI,sBAAsB,GAAG,GAAG,KAAK,MAAM,IAAI;AAAA,EACvD;AAAA,EAEA,iBAAiB;AAChB,UAAM,EAAE,QAAQ,MAAM,IAAI;AAC1B,WAAO,IAAI,MAAM,QAAQ,CAAC;AAAA,EAC3B;AACD;",
4
+ "sourcesContent": ["import { Vec, VecLike } from '../Vec'\nimport { Geometry2d, Geometry2dOptions } from './Geometry2d'\n\n/** @public */\nexport class Point2d extends Geometry2d {\n\tprivate _point: Vec\n\n\tconstructor(\n\t\tconfig: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & { margin: number; point: Vec }\n\t) {\n\t\tsuper({ ...config, isClosed: true, isFilled: true })\n\t\tconst { point } = config\n\n\t\tthis._point = point\n\t}\n\n\tgetVertices() {\n\t\treturn [this._point]\n\t}\n\n\tnearestPoint(): Vec {\n\t\treturn this._point\n\t}\n\n\thitTestLineSegment(A: VecLike, B: VecLike, margin: number): boolean {\n\t\treturn Vec.DistanceToLineSegment(A, B, this._point) < margin\n\t}\n\n\tgetSvgPathData() {\n\t\tconst { _point: point } = this\n\t\treturn `M${point}`\n\t}\n}\n"],
5
+ "mappings": "AAAA,SAAS,WAAoB;AAC7B,SAAS,kBAAqC;AAGvC,MAAM,gBAAgB,WAAW;AAAA,EAC/B;AAAA,EAER,YACC,QACC;AACD,UAAM,EAAE,GAAG,QAAQ,UAAU,MAAM,UAAU,KAAK,CAAC;AACnD,UAAM,EAAE,MAAM,IAAI;AAElB,SAAK,SAAS;AAAA,EACf;AAAA,EAEA,cAAc;AACb,WAAO,CAAC,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,eAAoB;AACnB,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,mBAAmB,GAAY,GAAY,QAAyB;AACnE,WAAO,IAAI,sBAAsB,GAAG,GAAG,KAAK,MAAM,IAAI;AAAA,EACvD;AAAA,EAEA,iBAAiB;AAChB,UAAM,EAAE,QAAQ,MAAM,IAAI;AAC1B,WAAO,IAAI,KAAK;AAAA,EACjB;AACD;",
6
6
  "names": []
7
7
  }
@@ -31,12 +31,6 @@ const debugFlags = {
31
31
  throwToBlob: createDebugValue("throwToBlob", {
32
32
  defaults: { all: false }
33
33
  }),
34
- reconnectOnPing: createDebugValue("reconnectOnPing", {
35
- defaults: { all: false }
36
- }),
37
- debugCursors: createDebugValue("debugCursors", {
38
- defaults: { all: false }
39
- }),
40
34
  forceSrgb: createDebugValue("forceSrgbColors", { defaults: { all: false } }),
41
35
  debugGeometry: createDebugValue("debugGeometry", { defaults: { all: false } }),
42
36
  hideShapes: createDebugValue("hideShapes", { defaults: { all: false } }),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/utils/debug-flags.ts"],
4
- "sourcesContent": ["import { Atom, atom, react } from '@tldraw/state'\nimport { deleteFromSessionStorage, getFromSessionStorage, setInSessionStorage } from '@tldraw/utils'\n\n// --- 1. DEFINE ---\n//\n// Define your debug values and feature flags here. Use `createDebugValue` to\n// create an arbitrary value with defaults for production, staging, and\n// development. Use `createFeatureFlag` to create a boolean flag which will be\n// `true` by default in development and staging, and `false` in production.\n/** @internal */\nexport const featureFlags: Record<string, DebugFlag<boolean>> = {}\n\n/** @internal */\nexport const pointerCaptureTrackingObject = createDebugValue(\n\t'pointerCaptureTrackingObject',\n\t// ideally we wouldn't store this mutable value in an atom but it's not\n\t// a big deal for debug values\n\t{\n\t\tdefaults: { all: new Map<Element, number>() },\n\t\tshouldStoreForSession: false,\n\t}\n)\n\n/** @internal */\nexport const debugFlags = {\n\t// --- DEBUG VALUES ---\n\tlogPreventDefaults: createDebugValue('logPreventDefaults', {\n\t\tdefaults: { all: false },\n\t}),\n\tlogPointerCaptures: createDebugValue('logPointerCaptures', {\n\t\tdefaults: { all: false },\n\t}),\n\tlogElementRemoves: createDebugValue('logElementRemoves', {\n\t\tdefaults: { all: false },\n\t}),\n\tdebugSvg: createDebugValue('debugSvg', {\n\t\tdefaults: { all: false },\n\t}),\n\tshowFps: createDebugValue('showFps', {\n\t\tdefaults: { all: false },\n\t}),\n\tmeasurePerformance: createDebugValue('measurePerformance', { defaults: { all: false } }),\n\tthrowToBlob: createDebugValue('throwToBlob', {\n\t\tdefaults: { all: false },\n\t}),\n\treconnectOnPing: createDebugValue('reconnectOnPing', {\n\t\tdefaults: { all: false },\n\t}),\n\tdebugCursors: createDebugValue('debugCursors', {\n\t\tdefaults: { all: false },\n\t}),\n\tforceSrgb: createDebugValue('forceSrgbColors', { defaults: { all: false } }),\n\tdebugGeometry: createDebugValue('debugGeometry', { defaults: { all: false } }),\n\thideShapes: createDebugValue('hideShapes', { defaults: { all: false } }),\n\teditOnType: createDebugValue('editOnType', { defaults: { all: false } }),\n\ta11y: createDebugValue('a11y', { defaults: { all: false } }),\n\tdebugElbowArrows: createDebugValue('debugElbowArrows', { defaults: { all: false } }),\n} as const\n\n// --- 2. USE ---\n// In normal code, read from debug flags directly by calling .value on them:\n// if (debugFlags.preventDefaultLogging.value) { ... }\n//\n// In react, wrap your reads in `useValue` (or your component in `track`)\n// so they react to changes:\n// const shouldLog = useValue(debugFlags.preventDefaultLogging)\n\n// --- 3. GET FUNKY ---\n// If you need to do fun stuff like monkey-patching in response to flag changes,\n// add that here. Make sure you wrap your code in `react` so it runs\n// automatically when values change!\n\nif (typeof Element !== 'undefined') {\n\tconst nativeElementRemoveChild = Element.prototype.removeChild\n\treact('element removal logging', () => {\n\t\tif (debugFlags.logElementRemoves.get()) {\n\t\t\tElement.prototype.removeChild = function <T extends Node>(this: any, child: Node): T {\n\t\t\t\tconsole.warn('[tldraw] removing child:', child)\n\t\t\t\treturn nativeElementRemoveChild.call(this, child) as T\n\t\t\t}\n\t\t} else {\n\t\t\tElement.prototype.removeChild = nativeElementRemoveChild\n\t\t}\n\t})\n}\n\n// --- IMPLEMENTATION ---\n// you probably don't need to read this if you're just using the debug values system\n/** @public */\nexport function createDebugValue<T>(\n\tname: string,\n\t{\n\t\tdefaults,\n\t\tshouldStoreForSession = true,\n\t}: { defaults: DebugFlagDefaults<T>; shouldStoreForSession?: boolean }\n) {\n\treturn createDebugValueBase({\n\t\tname,\n\t\tdefaults,\n\t\tshouldStoreForSession,\n\t})\n}\n\nfunction createDebugValueBase<T>(def: DebugFlagDef<T>): DebugFlag<T> {\n\tconst defaultValue = getDefaultValue(def)\n\tconst storedValue = def.shouldStoreForSession\n\t\t? (getStoredInitialValue(def.name) as T | null)\n\t\t: null\n\tconst valueAtom = atom(`debug:${def.name}`, storedValue ?? defaultValue)\n\n\tif (typeof window !== 'undefined') {\n\t\tif (def.shouldStoreForSession) {\n\t\t\treact(`debug:${def.name}`, () => {\n\t\t\t\tconst currentValue = valueAtom.get()\n\t\t\t\tif (currentValue === defaultValue) {\n\t\t\t\t\tdeleteFromSessionStorage(`tldraw_debug:${def.name}`)\n\t\t\t\t} else {\n\t\t\t\t\tsetInSessionStorage(`tldraw_debug:${def.name}`, JSON.stringify(currentValue))\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\tObject.defineProperty(window, `tldraw${def.name.replace(/^[a-z]/, (l) => l.toUpperCase())}`, {\n\t\t\tget() {\n\t\t\t\treturn valueAtom.get()\n\t\t\t},\n\t\t\tset(newValue) {\n\t\t\t\tvalueAtom.set(newValue)\n\t\t\t},\n\t\t\tconfigurable: true,\n\t\t})\n\t}\n\n\treturn Object.assign(valueAtom, def, {\n\t\treset: () => valueAtom.set(defaultValue),\n\t})\n}\n\nfunction getStoredInitialValue(name: string) {\n\ttry {\n\t\treturn JSON.parse(getFromSessionStorage(`tldraw_debug:${name}`) ?? 'null')\n\t} catch {\n\t\treturn null\n\t}\n}\n\n// process.env might not be defined, but we can't access it using optional\n// chaining because some bundlers search for `process.env.SOMETHING` as a string\n// and replace it with its value.\nfunction readEnv(fn: () => string | undefined) {\n\ttry {\n\t\treturn fn()\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction getDefaultValue<T>(def: DebugFlagDef<T>): T {\n\tconst env =\n\t\treadEnv(() => process.env.TLDRAW_ENV) ??\n\t\treadEnv(() => process.env.VERCEL_PUBLIC_TLDRAW_ENV) ??\n\t\treadEnv(() => process.env.NEXT_PUBLIC_TLDRAW_ENV) ??\n\t\t// default to production because if we don't have one of these, this is probably a library use\n\t\t'production'\n\n\tswitch (env) {\n\t\tcase 'production':\n\t\t\treturn def.defaults.production ?? def.defaults.all\n\t\tcase 'preview':\n\t\tcase 'staging':\n\t\t\treturn def.defaults.staging ?? def.defaults.all\n\t\tdefault:\n\t\t\treturn def.defaults.development ?? def.defaults.all\n\t}\n}\n\n/** @public */\nexport interface DebugFlagDefaults<T> {\n\tdevelopment?: T\n\tstaging?: T\n\tproduction?: T\n\tall: T\n}\n\n/** @public */\nexport interface DebugFlagDef<T> {\n\tname: string\n\tdefaults: DebugFlagDefaults<T>\n\tshouldStoreForSession: boolean\n}\n\n/** @public */\nexport interface DebugFlag<T> extends DebugFlagDef<T>, Atom<T> {\n\treset(): void\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Atom, atom, react } from '@tldraw/state'\nimport { deleteFromSessionStorage, getFromSessionStorage, setInSessionStorage } from '@tldraw/utils'\n\n// --- 1. DEFINE ---\n//\n// Define your debug values and feature flags here. Use `createDebugValue` to\n// create an arbitrary value with defaults for production, staging, and\n// development. Use `createFeatureFlag` to create a boolean flag which will be\n// `true` by default in development and staging, and `false` in production.\n/** @internal */\nexport const featureFlags: Record<string, DebugFlag<boolean>> = {}\n\n/** @internal */\nexport const pointerCaptureTrackingObject = createDebugValue(\n\t'pointerCaptureTrackingObject',\n\t// ideally we wouldn't store this mutable value in an atom but it's not\n\t// a big deal for debug values\n\t{\n\t\tdefaults: { all: new Map<Element, number>() },\n\t\tshouldStoreForSession: false,\n\t}\n)\n\n/** @internal */\nexport const debugFlags = {\n\t// --- DEBUG VALUES ---\n\tlogPreventDefaults: createDebugValue('logPreventDefaults', {\n\t\tdefaults: { all: false },\n\t}),\n\tlogPointerCaptures: createDebugValue('logPointerCaptures', {\n\t\tdefaults: { all: false },\n\t}),\n\tlogElementRemoves: createDebugValue('logElementRemoves', {\n\t\tdefaults: { all: false },\n\t}),\n\tdebugSvg: createDebugValue('debugSvg', {\n\t\tdefaults: { all: false },\n\t}),\n\tshowFps: createDebugValue('showFps', {\n\t\tdefaults: { all: false },\n\t}),\n\tmeasurePerformance: createDebugValue('measurePerformance', { defaults: { all: false } }),\n\tthrowToBlob: createDebugValue('throwToBlob', {\n\t\tdefaults: { all: false },\n\t}),\n\tforceSrgb: createDebugValue('forceSrgbColors', { defaults: { all: false } }),\n\tdebugGeometry: createDebugValue('debugGeometry', { defaults: { all: false } }),\n\thideShapes: createDebugValue('hideShapes', { defaults: { all: false } }),\n\teditOnType: createDebugValue('editOnType', { defaults: { all: false } }),\n\ta11y: createDebugValue('a11y', { defaults: { all: false } }),\n\tdebugElbowArrows: createDebugValue('debugElbowArrows', { defaults: { all: false } }),\n} as const\n\n// --- 2. USE ---\n// In normal code, read from debug flags directly by calling .value on them:\n// if (debugFlags.preventDefaultLogging.value) { ... }\n//\n// In react, wrap your reads in `useValue` (or your component in `track`)\n// so they react to changes:\n// const shouldLog = useValue(debugFlags.preventDefaultLogging)\n\n// --- 3. GET FUNKY ---\n// If you need to do fun stuff like monkey-patching in response to flag changes,\n// add that here. Make sure you wrap your code in `react` so it runs\n// automatically when values change!\n\nif (typeof Element !== 'undefined') {\n\tconst nativeElementRemoveChild = Element.prototype.removeChild\n\treact('element removal logging', () => {\n\t\tif (debugFlags.logElementRemoves.get()) {\n\t\t\tElement.prototype.removeChild = function <T extends Node>(this: any, child: Node): T {\n\t\t\t\tconsole.warn('[tldraw] removing child:', child)\n\t\t\t\treturn nativeElementRemoveChild.call(this, child) as T\n\t\t\t}\n\t\t} else {\n\t\t\tElement.prototype.removeChild = nativeElementRemoveChild\n\t\t}\n\t})\n}\n\n// --- IMPLEMENTATION ---\n// you probably don't need to read this if you're just using the debug values system\n/** @public */\nexport function createDebugValue<T>(\n\tname: string,\n\t{\n\t\tdefaults,\n\t\tshouldStoreForSession = true,\n\t}: { defaults: DebugFlagDefaults<T>; shouldStoreForSession?: boolean }\n) {\n\treturn createDebugValueBase({\n\t\tname,\n\t\tdefaults,\n\t\tshouldStoreForSession,\n\t})\n}\n\nfunction createDebugValueBase<T>(def: DebugFlagDef<T>): DebugFlag<T> {\n\tconst defaultValue = getDefaultValue(def)\n\tconst storedValue = def.shouldStoreForSession\n\t\t? (getStoredInitialValue(def.name) as T | null)\n\t\t: null\n\tconst valueAtom = atom(`debug:${def.name}`, storedValue ?? defaultValue)\n\n\tif (typeof window !== 'undefined') {\n\t\tif (def.shouldStoreForSession) {\n\t\t\treact(`debug:${def.name}`, () => {\n\t\t\t\tconst currentValue = valueAtom.get()\n\t\t\t\tif (currentValue === defaultValue) {\n\t\t\t\t\tdeleteFromSessionStorage(`tldraw_debug:${def.name}`)\n\t\t\t\t} else {\n\t\t\t\t\tsetInSessionStorage(`tldraw_debug:${def.name}`, JSON.stringify(currentValue))\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\tObject.defineProperty(window, `tldraw${def.name.replace(/^[a-z]/, (l) => l.toUpperCase())}`, {\n\t\t\tget() {\n\t\t\t\treturn valueAtom.get()\n\t\t\t},\n\t\t\tset(newValue) {\n\t\t\t\tvalueAtom.set(newValue)\n\t\t\t},\n\t\t\tconfigurable: true,\n\t\t})\n\t}\n\n\treturn Object.assign(valueAtom, def, {\n\t\treset: () => valueAtom.set(defaultValue),\n\t})\n}\n\nfunction getStoredInitialValue(name: string) {\n\ttry {\n\t\treturn JSON.parse(getFromSessionStorage(`tldraw_debug:${name}`) ?? 'null')\n\t} catch {\n\t\treturn null\n\t}\n}\n\n// process.env might not be defined, but we can't access it using optional\n// chaining because some bundlers search for `process.env.SOMETHING` as a string\n// and replace it with its value.\nfunction readEnv(fn: () => string | undefined) {\n\ttry {\n\t\treturn fn()\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction getDefaultValue<T>(def: DebugFlagDef<T>): T {\n\tconst env =\n\t\treadEnv(() => process.env.TLDRAW_ENV) ??\n\t\treadEnv(() => process.env.VERCEL_PUBLIC_TLDRAW_ENV) ??\n\t\treadEnv(() => process.env.NEXT_PUBLIC_TLDRAW_ENV) ??\n\t\t// default to production because if we don't have one of these, this is probably a library use\n\t\t'production'\n\n\tswitch (env) {\n\t\tcase 'production':\n\t\t\treturn def.defaults.production ?? def.defaults.all\n\t\tcase 'preview':\n\t\tcase 'staging':\n\t\t\treturn def.defaults.staging ?? def.defaults.all\n\t\tdefault:\n\t\t\treturn def.defaults.development ?? def.defaults.all\n\t}\n}\n\n/** @public */\nexport interface DebugFlagDefaults<T> {\n\tdevelopment?: T\n\tstaging?: T\n\tproduction?: T\n\tall: T\n}\n\n/** @public */\nexport interface DebugFlagDef<T> {\n\tname: string\n\tdefaults: DebugFlagDefaults<T>\n\tshouldStoreForSession: boolean\n}\n\n/** @public */\nexport interface DebugFlag<T> extends DebugFlagDef<T>, Atom<T> {\n\treset(): void\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -36,7 +36,8 @@ function parseDeepLinkString(deepLinkString) {
36
36
  case "v": {
37
37
  const [x, y, w, h, pageId] = deepLinkString.slice(1).split(".");
38
38
  const bounds = new Box(Number(x), Number(y), Number(w), Number(h));
39
- if (![bounds.x, bounds.y, bounds.w, bounds.h].every(Number.isFinite)) {
39
+ const isUsableViewport = [bounds.x, bounds.y, bounds.w, bounds.h].every(Number.isFinite) && bounds.w > 0 && bounds.h > 0;
40
+ if (!isUsableViewport) {
40
41
  throw Error("Invalid deep link string");
41
42
  }
42
43
  return {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/utils/deepLinks.ts"],
4
- "sourcesContent": ["import { BoxModel, PageRecordType, TLPageId, TLShapeId, createShapeId } from '@tldraw/tlschema'\nimport { exhaustiveSwitchError } from '@tldraw/utils'\nimport type { Editor } from '../editor/Editor'\nimport { Box } from '../primitives/Box'\n\n/** @public */\nexport type TLDeepLink =\n\t| {\n\t\t\ttype: 'shapes'\n\t\t\tshapeIds: TLShapeId[]\n\t }\n\t| { type: 'viewport'; bounds: BoxModel; pageId?: TLPageId }\n\t| { type: 'page'; pageId: TLPageId }\n\n/**\n * Converts a deep link descriptor to a url-safe string\n *\n * @example\n * ```ts\n * const url = `https://example.com?d=${createDeepLinkString({ type: 'shapes', shapeIds: ['shape:1', 'shape:2'] })}`\n * navigator.clipboard.writeText(url)\n * ```\n *\n * @param deepLink - the deep link descriptor\n * @returns a url-safe string\n *\n * @public\n */\nexport function createDeepLinkString(deepLink: TLDeepLink): string {\n\tswitch (deepLink.type) {\n\t\tcase 'shapes': {\n\t\t\tconst ids = deepLink.shapeIds.map((id) => encodeId(id.slice('shape:'.length)))\n\t\t\treturn `s${ids.join('.')}`\n\t\t}\n\t\tcase 'page': {\n\t\t\treturn 'p' + encodeId(PageRecordType.parseId(deepLink.pageId))\n\t\t}\n\t\tcase 'viewport': {\n\t\t\tconst { bounds, pageId } = deepLink\n\t\t\tlet res = `v${Math.round(bounds.x)}.${Math.round(bounds.y)}.${Math.round(bounds.w)}.${Math.round(bounds.h)}`\n\t\t\tif (pageId) {\n\t\t\t\tres += '.' + encodeId(PageRecordType.parseId(pageId))\n\t\t\t}\n\t\t\treturn res\n\t\t}\n\t\tdefault:\n\t\t\texhaustiveSwitchError(deepLink)\n\t}\n}\n\n/**\n * Parses a string created by {@link createDeepLinkString} back into a deep link descriptor.\n *\n * @param deepLinkString - the deep link string\n * @returns a deep link descriptor\n *\n * @public\n */\nexport function parseDeepLinkString(deepLinkString: string): TLDeepLink {\n\tconst type = deepLinkString[0]\n\tswitch (type) {\n\t\tcase 's': {\n\t\t\tconst shapeIds = deepLinkString\n\t\t\t\t.slice(1)\n\t\t\t\t.split('.')\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.map((id) => createShapeId(decodeURIComponent(id)))\n\t\t\treturn { type: 'shapes', shapeIds }\n\t\t}\n\t\tcase 'p': {\n\t\t\tconst pageId = PageRecordType.createId(decodeURIComponent(deepLinkString.slice(1)))\n\t\t\treturn { type: 'page', pageId }\n\t\t}\n\t\tcase 'v': {\n\t\t\tconst [x, y, w, h, pageId] = deepLinkString.slice(1).split('.')\n\t\t\tconst bounds = new Box(Number(x), Number(y), Number(w), Number(h))\n\t\t\t// Number('abc') is NaN rather than an error, so without this check a malformed\n\t\t\t// viewport link would bypass the caller's fallback and park the camera at the origin\n\t\t\tif (![bounds.x, bounds.y, bounds.w, bounds.h].every(Number.isFinite)) {\n\t\t\t\tthrow Error('Invalid deep link string')\n\t\t\t}\n\t\t\treturn {\n\t\t\t\ttype: 'viewport',\n\t\t\t\tbounds,\n\t\t\t\tpageId: pageId ? PageRecordType.createId(decodeURIComponent(pageId)) : undefined,\n\t\t\t}\n\t\t}\n\t\tdefault:\n\t\t\tthrow Error('Invalid deep link string')\n\t}\n}\n\nfunction encodeId(str: string): string {\n\t// need to encode dots because they are used as separators\n\treturn encodeURIComponent(str).replace(/\\./g, '%2E')\n}\n\n/** @public */\nexport interface TLDeepLinkOptions {\n\t/**\n\t * The name of the url search param to use for the deep link.\n\t *\n\t * Defaults to `'d'`\n\t */\n\tparam?: string\n\t/**\n\t * The debounce time in ms for updating the url.\n\t */\n\tdebounceMs?: number\n\t/**\n\t * Should return the current url to augment with a deep link query parameter.\n\t * If you supply this function, you must also supply an `onChange` function.\n\t */\n\tgetUrl?(editor: Editor): string | URL\n\t/**\n\t * Should return the current deep link target.\n\t * Defaults to returning the current page and viewport position.\n\t */\n\tgetTarget?(editor: Editor): TLDeepLink\n\t/**\n\t * This is fired when the URL is updated.\n\t *\n\t * If not supplied, the default behavior is to update `window.location`.\n\t *\n\t * @param url - the updated URL\n\t */\n\tonChange?(url: URL, editor: Editor): void\n}\n"],
5
- "mappings": "AAAA,SAAmB,gBAAqC,qBAAqB;AAC7E,SAAS,6BAA6B;AAEtC,SAAS,WAAW;AAyBb,SAAS,qBAAqB,UAA8B;AAClE,UAAQ,SAAS,MAAM;AAAA,IACtB,KAAK,UAAU;AACd,YAAM,MAAM,SAAS,SAAS,IAAI,CAAC,OAAO,SAAS,GAAG,MAAM,SAAS,MAAM,CAAC,CAAC;AAC7E,aAAO,IAAI,IAAI,KAAK,GAAG,CAAC;AAAA,IACzB;AAAA,IACA,KAAK,QAAQ;AACZ,aAAO,MAAM,SAAS,eAAe,QAAQ,SAAS,MAAM,CAAC;AAAA,IAC9D;AAAA,IACA,KAAK,YAAY;AAChB,YAAM,EAAE,QAAQ,OAAO,IAAI;AAC3B,UAAI,MAAM,IAAI,KAAK,MAAM,OAAO,CAAC,CAAC,IAAI,KAAK,MAAM,OAAO,CAAC,CAAC,IAAI,KAAK,MAAM,OAAO,CAAC,CAAC,IAAI,KAAK,MAAM,OAAO,CAAC,CAAC;AAC1G,UAAI,QAAQ;AACX,eAAO,MAAM,SAAS,eAAe,QAAQ,MAAM,CAAC;AAAA,MACrD;AACA,aAAO;AAAA,IACR;AAAA,IACA;AACC,4BAAsB,QAAQ;AAAA,EAChC;AACD;AAUO,SAAS,oBAAoB,gBAAoC;AACvE,QAAM,OAAO,eAAe,CAAC;AAC7B,UAAQ,MAAM;AAAA,IACb,KAAK,KAAK;AACT,YAAM,WAAW,eACf,MAAM,CAAC,EACP,MAAM,GAAG,EACT,OAAO,OAAO,EACd,IAAI,CAAC,OAAO,cAAc,mBAAmB,EAAE,CAAC,CAAC;AACnD,aAAO,EAAE,MAAM,UAAU,SAAS;AAAA,IACnC;AAAA,IACA,KAAK,KAAK;AACT,YAAM,SAAS,eAAe,SAAS,mBAAmB,eAAe,MAAM,CAAC,CAAC,CAAC;AAClF,aAAO,EAAE,MAAM,QAAQ,OAAO;AAAA,IAC/B;AAAA,IACA,KAAK,KAAK;AACT,YAAM,CAAC,GAAG,GAAG,GAAG,GAAG,MAAM,IAAI,eAAe,MAAM,CAAC,EAAE,MAAM,GAAG;AAC9D,YAAM,SAAS,IAAI,IAAI,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;AAGjE,UAAI,CAAC,CAAC,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,EAAE,MAAM,OAAO,QAAQ,GAAG;AACrE,cAAM,MAAM,0BAA0B;AAAA,MACvC;AACA,aAAO;AAAA,QACN,MAAM;AAAA,QACN;AAAA,QACA,QAAQ,SAAS,eAAe,SAAS,mBAAmB,MAAM,CAAC,IAAI;AAAA,MACxE;AAAA,IACD;AAAA,IACA;AACC,YAAM,MAAM,0BAA0B;AAAA,EACxC;AACD;AAEA,SAAS,SAAS,KAAqB;AAEtC,SAAO,mBAAmB,GAAG,EAAE,QAAQ,OAAO,KAAK;AACpD;",
4
+ "sourcesContent": ["import { BoxModel, PageRecordType, TLPageId, TLShapeId, createShapeId } from '@tldraw/tlschema'\nimport { exhaustiveSwitchError } from '@tldraw/utils'\nimport type { Editor } from '../editor/Editor'\nimport { Box } from '../primitives/Box'\n\n/** @public */\nexport type TLDeepLink =\n\t| {\n\t\t\ttype: 'shapes'\n\t\t\tshapeIds: TLShapeId[]\n\t }\n\t| { type: 'viewport'; bounds: BoxModel; pageId?: TLPageId }\n\t| { type: 'page'; pageId: TLPageId }\n\n/**\n * Converts a deep link descriptor to a url-safe string\n *\n * @example\n * ```ts\n * const url = `https://example.com?d=${createDeepLinkString({ type: 'shapes', shapeIds: ['shape:1', 'shape:2'] })}`\n * navigator.clipboard.writeText(url)\n * ```\n *\n * @param deepLink - the deep link descriptor\n * @returns a url-safe string\n *\n * @public\n */\nexport function createDeepLinkString(deepLink: TLDeepLink): string {\n\tswitch (deepLink.type) {\n\t\tcase 'shapes': {\n\t\t\tconst ids = deepLink.shapeIds.map((id) => encodeId(id.slice('shape:'.length)))\n\t\t\treturn `s${ids.join('.')}`\n\t\t}\n\t\tcase 'page': {\n\t\t\treturn 'p' + encodeId(PageRecordType.parseId(deepLink.pageId))\n\t\t}\n\t\tcase 'viewport': {\n\t\t\tconst { bounds, pageId } = deepLink\n\t\t\tlet res = `v${Math.round(bounds.x)}.${Math.round(bounds.y)}.${Math.round(bounds.w)}.${Math.round(bounds.h)}`\n\t\t\tif (pageId) {\n\t\t\t\tres += '.' + encodeId(PageRecordType.parseId(pageId))\n\t\t\t}\n\t\t\treturn res\n\t\t}\n\t\tdefault:\n\t\t\texhaustiveSwitchError(deepLink)\n\t}\n}\n\n/**\n * Parses a string created by {@link createDeepLinkString} back into a deep link descriptor.\n *\n * @param deepLinkString - the deep link string\n * @returns a deep link descriptor\n *\n * @public\n */\nexport function parseDeepLinkString(deepLinkString: string): TLDeepLink {\n\tconst type = deepLinkString[0]\n\tswitch (type) {\n\t\tcase 's': {\n\t\t\tconst shapeIds = deepLinkString\n\t\t\t\t.slice(1)\n\t\t\t\t.split('.')\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.map((id) => createShapeId(decodeURIComponent(id)))\n\t\t\treturn { type: 'shapes', shapeIds }\n\t\t}\n\t\tcase 'p': {\n\t\t\tconst pageId = PageRecordType.createId(decodeURIComponent(deepLinkString.slice(1)))\n\t\t\treturn { type: 'page', pageId }\n\t\t}\n\t\tcase 'v': {\n\t\t\tconst [x, y, w, h, pageId] = deepLinkString.slice(1).split('.')\n\t\t\tconst bounds = new Box(Number(x), Number(y), Number(w), Number(h))\n\t\t\t// Number('abc') is NaN rather than an error, and a box with no area parses cleanly but\n\t\t\t// can only be zoomed to by clamping to max zoom at its corner. Both bypass the caller's\n\t\t\t// fallback and park the camera at the origin, so both count as an invalid link here.\n\t\t\tconst isUsableViewport =\n\t\t\t\t[bounds.x, bounds.y, bounds.w, bounds.h].every(Number.isFinite) &&\n\t\t\t\tbounds.w > 0 &&\n\t\t\t\tbounds.h > 0\n\t\t\tif (!isUsableViewport) {\n\t\t\t\tthrow Error('Invalid deep link string')\n\t\t\t}\n\t\t\treturn {\n\t\t\t\ttype: 'viewport',\n\t\t\t\tbounds,\n\t\t\t\tpageId: pageId ? PageRecordType.createId(decodeURIComponent(pageId)) : undefined,\n\t\t\t}\n\t\t}\n\t\tdefault:\n\t\t\tthrow Error('Invalid deep link string')\n\t}\n}\n\nfunction encodeId(str: string): string {\n\t// need to encode dots because they are used as separators\n\treturn encodeURIComponent(str).replace(/\\./g, '%2E')\n}\n\n/** @public */\nexport interface TLDeepLinkOptions {\n\t/**\n\t * The name of the url search param to use for the deep link.\n\t *\n\t * Defaults to `'d'`\n\t */\n\tparam?: string\n\t/**\n\t * The debounce time in ms for updating the url.\n\t */\n\tdebounceMs?: number\n\t/**\n\t * Should return the current url to augment with a deep link query parameter.\n\t * If you supply this function, you must also supply an `onChange` function.\n\t */\n\tgetUrl?(editor: Editor): string | URL\n\t/**\n\t * Should return the current deep link target.\n\t * Defaults to returning the current page and viewport position.\n\t */\n\tgetTarget?(editor: Editor): TLDeepLink\n\t/**\n\t * This is fired when the URL is updated.\n\t *\n\t * If not supplied, the default behavior is to update `window.location`.\n\t *\n\t * @param url - the updated URL\n\t */\n\tonChange?(url: URL, editor: Editor): void\n}\n"],
5
+ "mappings": "AAAA,SAAmB,gBAAqC,qBAAqB;AAC7E,SAAS,6BAA6B;AAEtC,SAAS,WAAW;AAyBb,SAAS,qBAAqB,UAA8B;AAClE,UAAQ,SAAS,MAAM;AAAA,IACtB,KAAK,UAAU;AACd,YAAM,MAAM,SAAS,SAAS,IAAI,CAAC,OAAO,SAAS,GAAG,MAAM,SAAS,MAAM,CAAC,CAAC;AAC7E,aAAO,IAAI,IAAI,KAAK,GAAG,CAAC;AAAA,IACzB;AAAA,IACA,KAAK,QAAQ;AACZ,aAAO,MAAM,SAAS,eAAe,QAAQ,SAAS,MAAM,CAAC;AAAA,IAC9D;AAAA,IACA,KAAK,YAAY;AAChB,YAAM,EAAE,QAAQ,OAAO,IAAI;AAC3B,UAAI,MAAM,IAAI,KAAK,MAAM,OAAO,CAAC,CAAC,IAAI,KAAK,MAAM,OAAO,CAAC,CAAC,IAAI,KAAK,MAAM,OAAO,CAAC,CAAC,IAAI,KAAK,MAAM,OAAO,CAAC,CAAC;AAC1G,UAAI,QAAQ;AACX,eAAO,MAAM,SAAS,eAAe,QAAQ,MAAM,CAAC;AAAA,MACrD;AACA,aAAO;AAAA,IACR;AAAA,IACA;AACC,4BAAsB,QAAQ;AAAA,EAChC;AACD;AAUO,SAAS,oBAAoB,gBAAoC;AACvE,QAAM,OAAO,eAAe,CAAC;AAC7B,UAAQ,MAAM;AAAA,IACb,KAAK,KAAK;AACT,YAAM,WAAW,eACf,MAAM,CAAC,EACP,MAAM,GAAG,EACT,OAAO,OAAO,EACd,IAAI,CAAC,OAAO,cAAc,mBAAmB,EAAE,CAAC,CAAC;AACnD,aAAO,EAAE,MAAM,UAAU,SAAS;AAAA,IACnC;AAAA,IACA,KAAK,KAAK;AACT,YAAM,SAAS,eAAe,SAAS,mBAAmB,eAAe,MAAM,CAAC,CAAC,CAAC;AAClF,aAAO,EAAE,MAAM,QAAQ,OAAO;AAAA,IAC/B;AAAA,IACA,KAAK,KAAK;AACT,YAAM,CAAC,GAAG,GAAG,GAAG,GAAG,MAAM,IAAI,eAAe,MAAM,CAAC,EAAE,MAAM,GAAG;AAC9D,YAAM,SAAS,IAAI,IAAI,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;AAIjE,YAAM,mBACL,CAAC,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,EAAE,MAAM,OAAO,QAAQ,KAC9D,OAAO,IAAI,KACX,OAAO,IAAI;AACZ,UAAI,CAAC,kBAAkB;AACtB,cAAM,MAAM,0BAA0B;AAAA,MACvC;AACA,aAAO;AAAA,QACN,MAAM;AAAA,QACN;AAAA,QACA,QAAQ,SAAS,eAAe,SAAS,mBAAmB,MAAM,CAAC,IAAI;AAAA,MACxE;AAAA,IACD;AAAA,IACA;AACC,YAAM,MAAM,0BAA0B;AAAA,EACxC;AACD;AAEA,SAAS,SAAS,KAAqB;AAEtC,SAAO,mBAAmB,GAAG,EAAE,QAAQ,OAAO,KAAK;AACpD;",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,7 @@ import { deleteDB, openDB } from "idb";
3
3
  const STORE_PREFIX = "TLDRAW_DOCUMENT_v2";
4
4
  const LEGACY_ASSET_STORE_PREFIX = "TLDRAW_ASSET_STORE_v1";
5
5
  const dbNameIndexKey = "TLDRAW_DB_NAME_INDEX_v2";
6
+ const MAX_SESSION_STATE_ROWS = 10;
6
7
  const Table = {
7
8
  Records: "records",
8
9
  Schema: "schema",
@@ -96,23 +97,17 @@ class LocalIndexedDb {
96
97
  assert(!this.isClosed, "db is closed");
97
98
  const db = await this.getDb();
98
99
  const tx = db.transaction(names, mode);
99
- const done = tx.done.catch((e) => {
100
- if (!this.isClosed) {
101
- throw e;
102
- }
103
- });
100
+ const done = tx.done;
101
+ done.catch(noop);
104
102
  try {
105
103
  return await cb(tx);
106
104
  } finally {
107
- if (!this.isClosed) {
108
- await done;
109
- } else {
110
- tx.abort();
111
- }
105
+ await done;
112
106
  }
113
107
  })();
114
108
  this.pendingTransactionSet.add(txPromise);
115
- txPromise.finally(() => this.pendingTransactionSet.delete(txPromise));
109
+ const cleanup = () => this.pendingTransactionSet.delete(txPromise);
110
+ txPromise.then(cleanup, cleanup);
116
111
  return txPromise;
117
112
  }
118
113
  async load({ sessionId } = {}) {
@@ -147,15 +142,17 @@ class LocalIndexedDb {
147
142
  const recordsStore = tx.objectStore(Table.Records);
148
143
  const schemaStore = tx.objectStore(Table.Schema);
149
144
  const sessionStateStore = tx.objectStore(Table.SessionState);
145
+ const requests = [];
150
146
  for (const [id, record] of Object.entries(changes.added)) {
151
- await recordsStore.put(record, id);
147
+ requests.push(recordsStore.put(record, id));
152
148
  }
153
149
  for (const [_prev, updated] of Object.values(changes.updated)) {
154
- await recordsStore.put(updated, updated.id);
150
+ requests.push(recordsStore.put(updated, updated.id));
155
151
  }
156
152
  for (const id of Object.keys(changes.removed)) {
157
- await recordsStore.delete(id);
153
+ requests.push(recordsStore.delete(id));
158
154
  }
155
+ await Promise.all(requests);
159
156
  schemaStore.put(schema.serialize(), Table.Schema);
160
157
  if (sessionStateSnapshot && sessionId) {
161
158
  sessionStateStore.put(
@@ -182,9 +179,9 @@ class LocalIndexedDb {
182
179
  const schemaStore = tx.objectStore(Table.Schema);
183
180
  const sessionStateStore = tx.objectStore(Table.SessionState);
184
181
  await recordsStore.clear();
185
- for (const [id, record] of Object.entries(snapshot)) {
186
- await recordsStore.put(record, id);
187
- }
182
+ await Promise.all(
183
+ Object.entries(snapshot).map(([id, record]) => recordsStore.put(record, id))
184
+ );
188
185
  schemaStore.put(schema.serialize(), Table.Schema);
189
186
  if (sessionStateSnapshot && sessionId) {
190
187
  sessionStateStore.put(
@@ -200,15 +197,16 @@ class LocalIndexedDb {
200
197
  }
201
198
  });
202
199
  }
203
- async pruneSessions() {
200
+ /**
201
+ * Drop all but the most recently updated session state rows. The row for `keepSessionId` is never
202
+ * deleted: it may be an old row that this tab restored its state from and has not rewritten yet,
203
+ * and deleting it would make a later reload fall back to another tab's session state.
204
+ */
205
+ async pruneSessions({ keepSessionId } = {}) {
204
206
  await this.tx("readwrite", [Table.SessionState], async (tx) => {
205
207
  const sessionStateStore = tx.objectStore(Table.SessionState);
206
- const all = (await sessionStateStore.getAll()).sort((a, b) => a.updatedAt - b.updatedAt);
207
- if (all.length < 10) {
208
- await tx.done;
209
- return;
210
- }
211
- const toDelete = all.slice(0, all.length - 10);
208
+ const all = (await sessionStateStore.getAll()).filter((row) => row.id !== keepSessionId).sort((a, b) => a.updatedAt - b.updatedAt);
209
+ const toDelete = all.slice(0, Math.max(0, all.length - MAX_SESSION_STATE_ROWS));
212
210
  for (const { id } of toDelete) {
213
211
  await sessionStateStore.delete(id);
214
212
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/utils/sync/LocalIndexedDb.ts"],
4
- "sourcesContent": ["import { RecordsDiff, SerializedSchema, SerializedStore } from '@tldraw/store'\nimport { TLRecord, TLStoreSchema } from '@tldraw/tlschema'\nimport { assert, getFromLocalStorage, noop, setInLocalStorage } from '@tldraw/utils'\nimport { IDBPDatabase, IDBPTransaction, deleteDB, openDB } from 'idb'\nimport { TLSessionStateSnapshot } from '../../config/TLSessionStateSnapshot'\n\n// DO NOT CHANGE THESE WITHOUT ADDING MIGRATION LOGIC. DOING SO WOULD WIPE ALL EXISTING DATA.\nconst STORE_PREFIX = 'TLDRAW_DOCUMENT_v2'\nconst LEGACY_ASSET_STORE_PREFIX = 'TLDRAW_ASSET_STORE_v1'\nconst dbNameIndexKey = 'TLDRAW_DB_NAME_INDEX_v2'\n\n/** @internal */\nexport const Table = {\n\tRecords: 'records',\n\tSchema: 'schema',\n\tSessionState: 'session_state',\n\tAssets: 'assets',\n} as const\n\n/** @internal */\nexport type StoreName = (typeof Table)[keyof typeof Table]\n\nasync function openLocalDb(persistenceKey: string) {\n\tconst storeId = STORE_PREFIX + persistenceKey\n\n\taddDbName(storeId)\n\n\treturn await openDB<StoreName>(storeId, 4, {\n\t\tupgrade(database) {\n\t\t\tif (!database.objectStoreNames.contains(Table.Records)) {\n\t\t\t\tdatabase.createObjectStore(Table.Records)\n\t\t\t}\n\t\t\tif (!database.objectStoreNames.contains(Table.Schema)) {\n\t\t\t\tdatabase.createObjectStore(Table.Schema)\n\t\t\t}\n\t\t\tif (!database.objectStoreNames.contains(Table.SessionState)) {\n\t\t\t\tdatabase.createObjectStore(Table.SessionState)\n\t\t\t}\n\t\t\tif (!database.objectStoreNames.contains(Table.Assets)) {\n\t\t\t\tdatabase.createObjectStore(Table.Assets)\n\t\t\t}\n\t\t},\n\t})\n}\n\nasync function migrateLegacyAssetDbIfNeeded(persistenceKey: string) {\n\tconst databases = window.indexedDB.databases\n\t\t? (await window.indexedDB.databases()).map((db) => db.name)\n\t\t: getAllIndexDbNames()\n\tconst oldStoreId = LEGACY_ASSET_STORE_PREFIX + persistenceKey\n\tconst existing = databases.find((dbName) => dbName === oldStoreId)\n\tif (!existing) return\n\n\tconst oldAssetDb = await openDB<StoreName>(oldStoreId, 1, {\n\t\tupgrade(database) {\n\t\t\tif (!database.objectStoreNames.contains('assets')) {\n\t\t\t\tdatabase.createObjectStore('assets')\n\t\t\t}\n\t\t},\n\t})\n\tif (!oldAssetDb.objectStoreNames.contains('assets')) return\n\n\tconst oldTx = oldAssetDb.transaction(['assets'], 'readonly')\n\tconst oldAssetStore = oldTx.objectStore('assets')\n\tconst oldAssetsKeys = await oldAssetStore.getAllKeys()\n\tconst oldAssets = await Promise.all(\n\t\toldAssetsKeys.map(async (key) => [key, await oldAssetStore.get(key)] as const)\n\t)\n\tawait oldTx.done\n\n\tconst newDb = await openLocalDb(persistenceKey)\n\tconst newTx = newDb.transaction([Table.Assets], 'readwrite')\n\tconst newAssetTable = newTx.objectStore(Table.Assets)\n\tfor (const [key, value] of oldAssets) {\n\t\tnewAssetTable.put(value, key)\n\t}\n\tawait newTx.done\n\n\toldAssetDb.close()\n\tnewDb.close()\n\n\tawait deleteDB(oldStoreId)\n}\n\ninterface LoadResult {\n\trecords: TLRecord[]\n\tschema?: SerializedSchema\n\tsessionStateSnapshot?: TLSessionStateSnapshot | null\n}\n\ninterface SessionStateSnapshotRow {\n\tid: string\n\tsnapshot: TLSessionStateSnapshot\n\tupdatedAt: number\n}\n\n/** @internal */\nexport class LocalIndexedDb {\n\tprivate getDbPromise: Promise<IDBPDatabase<StoreName>>\n\tprivate isClosed = false\n\tprivate pendingTransactionSet = new Set<Promise<unknown>>()\n\n\t/** @internal */\n\tstatic connectedInstances = new Set<LocalIndexedDb>()\n\n\tconstructor(persistenceKey: string) {\n\t\tLocalIndexedDb.connectedInstances.add(this)\n\t\tthis.getDbPromise = (async () => {\n\t\t\tawait migrateLegacyAssetDbIfNeeded(persistenceKey)\n\t\t\treturn await openLocalDb(persistenceKey)\n\t\t})()\n\t}\n\n\tprivate getDb() {\n\t\treturn this.getDbPromise\n\t}\n\n\t/**\n\t * Wait for any pending transactions to be completed. Useful for tests.\n\t *\n\t * @internal\n\t */\n\tpending(): Promise<void> {\n\t\treturn Promise.allSettled([this.getDbPromise, ...this.pendingTransactionSet]).then(noop)\n\t}\n\n\tasync close() {\n\t\tif (this.isClosed) return\n\t\tthis.isClosed = true\n\t\tawait this.pending()\n\t\t;(await this.getDb()).close()\n\t\tLocalIndexedDb.connectedInstances.delete(this)\n\t}\n\n\tprivate tx<Names extends StoreName[], Mode extends IDBTransactionMode, T>(\n\t\tmode: Mode,\n\t\tnames: Names,\n\t\tcb: (tx: IDBPTransaction<StoreName, Names, Mode>) => Promise<T>\n\t): Promise<T> {\n\t\tconst txPromise = (async () => {\n\t\t\tassert(!this.isClosed, 'db is closed')\n\t\t\tconst db = await this.getDb()\n\t\t\tconst tx = db.transaction(names, mode)\n\t\t\t// need to add a catch here early to prevent unhandled promise rejection\n\t\t\t// during react-strict-mode where this tx.done promise can be rejected\n\t\t\t// before we have a chance to await on it\n\t\t\tconst done = tx.done.catch((e: unknown) => {\n\t\t\t\tif (!this.isClosed) {\n\t\t\t\t\tthrow e\n\t\t\t\t}\n\t\t\t})\n\t\t\ttry {\n\t\t\t\treturn await cb(tx)\n\t\t\t} finally {\n\t\t\t\tif (!this.isClosed) {\n\t\t\t\t\tawait done\n\t\t\t\t} else {\n\t\t\t\t\ttx.abort()\n\t\t\t\t}\n\t\t\t}\n\t\t})()\n\t\tthis.pendingTransactionSet.add(txPromise)\n\t\ttxPromise.finally(() => this.pendingTransactionSet.delete(txPromise))\n\t\treturn txPromise\n\t}\n\n\tasync load({ sessionId }: { sessionId?: string } = {}) {\n\t\treturn await this.tx(\n\t\t\t'readonly',\n\t\t\t[Table.Records, Table.Schema, Table.SessionState],\n\t\t\tasync (tx) => {\n\t\t\t\tconst recordsStore = tx.objectStore(Table.Records)\n\t\t\t\tconst schemaStore = tx.objectStore(Table.Schema)\n\t\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\t\t\t\tlet sessionStateSnapshot = sessionId\n\t\t\t\t\t? ((await sessionStateStore.get(sessionId)) as SessionStateSnapshotRow | undefined)\n\t\t\t\t\t\t\t?.snapshot\n\t\t\t\t\t: null\n\t\t\t\tif (!sessionStateSnapshot) {\n\t\t\t\t\t// get the most recent session state\n\t\t\t\t\tconst all = (await sessionStateStore.getAll()) as SessionStateSnapshotRow[]\n\t\t\t\t\tsessionStateSnapshot = all.sort((a, b) => a.updatedAt - b.updatedAt).pop()?.snapshot\n\t\t\t\t}\n\t\t\t\tconst result = {\n\t\t\t\t\trecords: await recordsStore.getAll(),\n\t\t\t\t\tschema: await schemaStore.get(Table.Schema),\n\t\t\t\t\tsessionStateSnapshot,\n\t\t\t\t} satisfies LoadResult\n\n\t\t\t\treturn result\n\t\t\t}\n\t\t)\n\t}\n\n\tasync storeChanges({\n\t\tschema,\n\t\tchanges,\n\t\tsessionId,\n\t\tsessionStateSnapshot,\n\t}: {\n\t\tschema: TLStoreSchema\n\t\tchanges: RecordsDiff<any>\n\t\tsessionId?: string | null\n\t\tsessionStateSnapshot?: TLSessionStateSnapshot | null\n\t}) {\n\t\tawait this.tx('readwrite', [Table.Records, Table.Schema, Table.SessionState], async (tx) => {\n\t\t\tconst recordsStore = tx.objectStore(Table.Records)\n\t\t\tconst schemaStore = tx.objectStore(Table.Schema)\n\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\n\t\t\tfor (const [id, record] of Object.entries(changes.added)) {\n\t\t\t\tawait recordsStore.put(record, id)\n\t\t\t}\n\n\t\t\tfor (const [_prev, updated] of Object.values(changes.updated)) {\n\t\t\t\tawait recordsStore.put(updated, updated.id)\n\t\t\t}\n\n\t\t\tfor (const id of Object.keys(changes.removed)) {\n\t\t\t\tawait recordsStore.delete(id)\n\t\t\t}\n\n\t\t\tschemaStore.put(schema.serialize(), Table.Schema)\n\t\t\tif (sessionStateSnapshot && sessionId) {\n\t\t\t\tsessionStateStore.put(\n\t\t\t\t\t{\n\t\t\t\t\t\tsnapshot: sessionStateSnapshot,\n\t\t\t\t\t\tupdatedAt: Date.now(),\n\t\t\t\t\t\tid: sessionId,\n\t\t\t\t\t} satisfies SessionStateSnapshotRow,\n\t\t\t\t\tsessionId\n\t\t\t\t)\n\t\t\t} else if (sessionStateSnapshot || sessionId) {\n\t\t\t\tconsole.error('sessionStateSnapshot and instanceId must be provided together')\n\t\t\t}\n\t\t})\n\t}\n\n\tasync storeSnapshot({\n\t\tschema,\n\t\tsnapshot,\n\t\tsessionId,\n\t\tsessionStateSnapshot,\n\t}: {\n\t\tschema: TLStoreSchema\n\t\tsnapshot: SerializedStore<any>\n\t\tsessionId?: string | null\n\t\tsessionStateSnapshot?: TLSessionStateSnapshot | null\n\t}) {\n\t\tawait this.tx('readwrite', [Table.Records, Table.Schema, Table.SessionState], async (tx) => {\n\t\t\tconst recordsStore = tx.objectStore(Table.Records)\n\t\t\tconst schemaStore = tx.objectStore(Table.Schema)\n\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\n\t\t\tawait recordsStore.clear()\n\n\t\t\tfor (const [id, record] of Object.entries(snapshot)) {\n\t\t\t\tawait recordsStore.put(record, id)\n\t\t\t}\n\n\t\t\tschemaStore.put(schema.serialize(), Table.Schema)\n\n\t\t\tif (sessionStateSnapshot && sessionId) {\n\t\t\t\tsessionStateStore.put(\n\t\t\t\t\t{\n\t\t\t\t\t\tsnapshot: sessionStateSnapshot,\n\t\t\t\t\t\tupdatedAt: Date.now(),\n\t\t\t\t\t\tid: sessionId,\n\t\t\t\t\t} satisfies SessionStateSnapshotRow,\n\t\t\t\t\tsessionId\n\t\t\t\t)\n\t\t\t} else if (sessionStateSnapshot || sessionId) {\n\t\t\t\tconsole.error('sessionStateSnapshot and instanceId must be provided together')\n\t\t\t}\n\t\t})\n\t}\n\n\tasync pruneSessions() {\n\t\tawait this.tx('readwrite', [Table.SessionState], async (tx) => {\n\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\t\t\tconst all = (await sessionStateStore.getAll()).sort((a, b) => a.updatedAt - b.updatedAt)\n\t\t\tif (all.length < 10) {\n\t\t\t\tawait tx.done\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst toDelete = all.slice(0, all.length - 10)\n\t\t\tfor (const { id } of toDelete) {\n\t\t\t\tawait sessionStateStore.delete(id)\n\t\t\t}\n\t\t})\n\t}\n\n\tasync getAsset(assetId: string): Promise<File | undefined> {\n\t\treturn await this.tx('readonly', [Table.Assets], async (tx) => {\n\t\t\tconst assetsStore = tx.objectStore(Table.Assets)\n\t\t\treturn await assetsStore.get(assetId)\n\t\t})\n\t}\n\n\tasync storeAsset(assetId: string, blob: File) {\n\t\tawait this.tx('readwrite', [Table.Assets], async (tx) => {\n\t\t\tconst assetsStore = tx.objectStore(Table.Assets)\n\t\t\tawait assetsStore.put(blob, assetId)\n\t\t})\n\t}\n\n\tasync removeAssets(assetId: string[]) {\n\t\tawait this.tx('readwrite', [Table.Assets], async (tx) => {\n\t\t\tconst assetsStore = tx.objectStore(Table.Assets)\n\t\t\tfor (const id of assetId) {\n\t\t\t\tawait assetsStore.delete(id)\n\t\t\t}\n\t\t})\n\t}\n}\n\n/** @internal */\nexport function getAllIndexDbNames(): string[] {\n\tconst result = JSON.parse(getFromLocalStorage(dbNameIndexKey) || '[]') ?? []\n\tif (!Array.isArray(result)) {\n\t\treturn []\n\t}\n\treturn result\n}\n\nfunction addDbName(name: string) {\n\tconst all = new Set(getAllIndexDbNames())\n\tall.add(name)\n\tsetInLocalStorage(dbNameIndexKey, JSON.stringify([...all]))\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { RecordsDiff, SerializedSchema, SerializedStore } from '@tldraw/store'\nimport { TLRecord, TLStoreSchema } from '@tldraw/tlschema'\nimport { assert, getFromLocalStorage, noop, setInLocalStorage } from '@tldraw/utils'\nimport { IDBPDatabase, IDBPTransaction, deleteDB, openDB } from 'idb'\nimport { TLSessionStateSnapshot } from '../../config/TLSessionStateSnapshot'\n\n// DO NOT CHANGE THESE WITHOUT ADDING MIGRATION LOGIC. DOING SO WOULD WIPE ALL EXISTING DATA.\nconst STORE_PREFIX = 'TLDRAW_DOCUMENT_v2'\nconst LEGACY_ASSET_STORE_PREFIX = 'TLDRAW_ASSET_STORE_v1'\nconst dbNameIndexKey = 'TLDRAW_DB_NAME_INDEX_v2'\n\n/** How many session state rows (one per tab) to keep per document when pruning. */\nconst MAX_SESSION_STATE_ROWS = 10\n\n/** @internal */\nexport const Table = {\n\tRecords: 'records',\n\tSchema: 'schema',\n\tSessionState: 'session_state',\n\tAssets: 'assets',\n} as const\n\n/** @internal */\nexport type StoreName = (typeof Table)[keyof typeof Table]\n\nasync function openLocalDb(persistenceKey: string) {\n\tconst storeId = STORE_PREFIX + persistenceKey\n\n\taddDbName(storeId)\n\n\treturn await openDB<StoreName>(storeId, 4, {\n\t\tupgrade(database) {\n\t\t\tif (!database.objectStoreNames.contains(Table.Records)) {\n\t\t\t\tdatabase.createObjectStore(Table.Records)\n\t\t\t}\n\t\t\tif (!database.objectStoreNames.contains(Table.Schema)) {\n\t\t\t\tdatabase.createObjectStore(Table.Schema)\n\t\t\t}\n\t\t\tif (!database.objectStoreNames.contains(Table.SessionState)) {\n\t\t\t\tdatabase.createObjectStore(Table.SessionState)\n\t\t\t}\n\t\t\tif (!database.objectStoreNames.contains(Table.Assets)) {\n\t\t\t\tdatabase.createObjectStore(Table.Assets)\n\t\t\t}\n\t\t},\n\t})\n}\n\nasync function migrateLegacyAssetDbIfNeeded(persistenceKey: string) {\n\tconst databases = window.indexedDB.databases\n\t\t? (await window.indexedDB.databases()).map((db) => db.name)\n\t\t: getAllIndexDbNames()\n\tconst oldStoreId = LEGACY_ASSET_STORE_PREFIX + persistenceKey\n\tconst existing = databases.find((dbName) => dbName === oldStoreId)\n\tif (!existing) return\n\n\tconst oldAssetDb = await openDB<StoreName>(oldStoreId, 1, {\n\t\tupgrade(database) {\n\t\t\tif (!database.objectStoreNames.contains('assets')) {\n\t\t\t\tdatabase.createObjectStore('assets')\n\t\t\t}\n\t\t},\n\t})\n\tif (!oldAssetDb.objectStoreNames.contains('assets')) return\n\n\tconst oldTx = oldAssetDb.transaction(['assets'], 'readonly')\n\tconst oldAssetStore = oldTx.objectStore('assets')\n\tconst oldAssetsKeys = await oldAssetStore.getAllKeys()\n\tconst oldAssets = await Promise.all(\n\t\toldAssetsKeys.map(async (key) => [key, await oldAssetStore.get(key)] as const)\n\t)\n\tawait oldTx.done\n\n\tconst newDb = await openLocalDb(persistenceKey)\n\tconst newTx = newDb.transaction([Table.Assets], 'readwrite')\n\tconst newAssetTable = newTx.objectStore(Table.Assets)\n\tfor (const [key, value] of oldAssets) {\n\t\tnewAssetTable.put(value, key)\n\t}\n\tawait newTx.done\n\n\toldAssetDb.close()\n\tnewDb.close()\n\n\tawait deleteDB(oldStoreId)\n}\n\ninterface LoadResult {\n\trecords: TLRecord[]\n\tschema?: SerializedSchema\n\tsessionStateSnapshot?: TLSessionStateSnapshot | null\n}\n\ninterface SessionStateSnapshotRow {\n\tid: string\n\tsnapshot: TLSessionStateSnapshot\n\tupdatedAt: number\n}\n\n/** @internal */\nexport class LocalIndexedDb {\n\tprivate getDbPromise: Promise<IDBPDatabase<StoreName>>\n\tprivate isClosed = false\n\tprivate pendingTransactionSet = new Set<Promise<unknown>>()\n\n\t/** @internal */\n\tstatic connectedInstances = new Set<LocalIndexedDb>()\n\n\tconstructor(persistenceKey: string) {\n\t\tLocalIndexedDb.connectedInstances.add(this)\n\t\tthis.getDbPromise = (async () => {\n\t\t\tawait migrateLegacyAssetDbIfNeeded(persistenceKey)\n\t\t\treturn await openLocalDb(persistenceKey)\n\t\t})()\n\t}\n\n\tprivate getDb() {\n\t\treturn this.getDbPromise\n\t}\n\n\t/**\n\t * Wait for any pending transactions to be completed. Useful for tests.\n\t *\n\t * @internal\n\t */\n\tpending(): Promise<void> {\n\t\treturn Promise.allSettled([this.getDbPromise, ...this.pendingTransactionSet]).then(noop)\n\t}\n\n\tasync close() {\n\t\tif (this.isClosed) return\n\t\tthis.isClosed = true\n\t\tawait this.pending()\n\t\t;(await this.getDb()).close()\n\t\tLocalIndexedDb.connectedInstances.delete(this)\n\t}\n\n\tprivate tx<Names extends StoreName[], Mode extends IDBTransactionMode, T>(\n\t\tmode: Mode,\n\t\tnames: Names,\n\t\tcb: (tx: IDBPTransaction<StoreName, Names, Mode>) => Promise<T>\n\t): Promise<T> {\n\t\tconst txPromise = (async () => {\n\t\t\tassert(!this.isClosed, 'db is closed')\n\t\t\tconst db = await this.getDb()\n\t\t\tconst tx = db.transaction(names, mode)\n\t\t\t// Commit failures may arrive before the callback finishes; observe them immediately\n\t\t\t// while preserving the rejection for the await below.\n\t\t\tconst done = tx.done\n\t\t\tdone.catch(noop)\n\t\t\ttry {\n\t\t\t\treturn await cb(tx)\n\t\t\t} finally {\n\t\t\t\t// let the transaction commit even if close() was called meanwhile, otherwise a\n\t\t\t\t// persist racing an unmount is silently rolled back\n\t\t\t\tawait done\n\t\t\t}\n\t\t})()\n\t\tthis.pendingTransactionSet.add(txPromise)\n\t\t// not `.finally()`: the promise it returns would re-reject with nobody observing it\n\t\tconst cleanup = () => this.pendingTransactionSet.delete(txPromise)\n\t\ttxPromise.then(cleanup, cleanup)\n\t\treturn txPromise\n\t}\n\n\tasync load({ sessionId }: { sessionId?: string } = {}) {\n\t\treturn await this.tx(\n\t\t\t'readonly',\n\t\t\t[Table.Records, Table.Schema, Table.SessionState],\n\t\t\tasync (tx) => {\n\t\t\t\tconst recordsStore = tx.objectStore(Table.Records)\n\t\t\t\tconst schemaStore = tx.objectStore(Table.Schema)\n\t\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\t\t\t\tlet sessionStateSnapshot = sessionId\n\t\t\t\t\t? ((await sessionStateStore.get(sessionId)) as SessionStateSnapshotRow | undefined)\n\t\t\t\t\t\t\t?.snapshot\n\t\t\t\t\t: null\n\t\t\t\tif (!sessionStateSnapshot) {\n\t\t\t\t\t// get the most recent session state\n\t\t\t\t\tconst all = (await sessionStateStore.getAll()) as SessionStateSnapshotRow[]\n\t\t\t\t\tsessionStateSnapshot = all.sort((a, b) => a.updatedAt - b.updatedAt).pop()?.snapshot\n\t\t\t\t}\n\t\t\t\tconst result = {\n\t\t\t\t\trecords: await recordsStore.getAll(),\n\t\t\t\t\tschema: await schemaStore.get(Table.Schema),\n\t\t\t\t\tsessionStateSnapshot,\n\t\t\t\t} satisfies LoadResult\n\n\t\t\t\treturn result\n\t\t\t}\n\t\t)\n\t}\n\n\tasync storeChanges({\n\t\tschema,\n\t\tchanges,\n\t\tsessionId,\n\t\tsessionStateSnapshot,\n\t}: {\n\t\tschema: TLStoreSchema\n\t\tchanges: RecordsDiff<any>\n\t\tsessionId?: string | null\n\t\tsessionStateSnapshot?: TLSessionStateSnapshot | null\n\t}) {\n\t\tawait this.tx('readwrite', [Table.Records, Table.Schema, Table.SessionState], async (tx) => {\n\t\t\tconst recordsStore = tx.objectStore(Table.Records)\n\t\t\tconst schemaStore = tx.objectStore(Table.Schema)\n\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\n\t\t\tconst requests: Promise<unknown>[] = []\n\t\t\tfor (const [id, record] of Object.entries(changes.added)) {\n\t\t\t\trequests.push(recordsStore.put(record, id))\n\t\t\t}\n\n\t\t\tfor (const [_prev, updated] of Object.values(changes.updated)) {\n\t\t\t\trequests.push(recordsStore.put(updated, updated.id))\n\t\t\t}\n\n\t\t\tfor (const id of Object.keys(changes.removed)) {\n\t\t\t\trequests.push(recordsStore.delete(id))\n\t\t\t}\n\t\t\tawait Promise.all(requests)\n\n\t\t\tschemaStore.put(schema.serialize(), Table.Schema)\n\t\t\tif (sessionStateSnapshot && sessionId) {\n\t\t\t\tsessionStateStore.put(\n\t\t\t\t\t{\n\t\t\t\t\t\tsnapshot: sessionStateSnapshot,\n\t\t\t\t\t\tupdatedAt: Date.now(),\n\t\t\t\t\t\tid: sessionId,\n\t\t\t\t\t} satisfies SessionStateSnapshotRow,\n\t\t\t\t\tsessionId\n\t\t\t\t)\n\t\t\t} else if (sessionStateSnapshot || sessionId) {\n\t\t\t\tconsole.error('sessionStateSnapshot and instanceId must be provided together')\n\t\t\t}\n\t\t})\n\t}\n\n\tasync storeSnapshot({\n\t\tschema,\n\t\tsnapshot,\n\t\tsessionId,\n\t\tsessionStateSnapshot,\n\t}: {\n\t\tschema: TLStoreSchema\n\t\tsnapshot: SerializedStore<any>\n\t\tsessionId?: string | null\n\t\tsessionStateSnapshot?: TLSessionStateSnapshot | null\n\t}) {\n\t\tawait this.tx('readwrite', [Table.Records, Table.Schema, Table.SessionState], async (tx) => {\n\t\t\tconst recordsStore = tx.objectStore(Table.Records)\n\t\t\tconst schemaStore = tx.objectStore(Table.Schema)\n\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\n\t\t\tawait recordsStore.clear()\n\n\t\t\tawait Promise.all(\n\t\t\t\tObject.entries(snapshot).map(([id, record]) => recordsStore.put(record, id))\n\t\t\t)\n\n\t\t\tschemaStore.put(schema.serialize(), Table.Schema)\n\n\t\t\tif (sessionStateSnapshot && sessionId) {\n\t\t\t\tsessionStateStore.put(\n\t\t\t\t\t{\n\t\t\t\t\t\tsnapshot: sessionStateSnapshot,\n\t\t\t\t\t\tupdatedAt: Date.now(),\n\t\t\t\t\t\tid: sessionId,\n\t\t\t\t\t} satisfies SessionStateSnapshotRow,\n\t\t\t\t\tsessionId\n\t\t\t\t)\n\t\t\t} else if (sessionStateSnapshot || sessionId) {\n\t\t\t\tconsole.error('sessionStateSnapshot and instanceId must be provided together')\n\t\t\t}\n\t\t})\n\t}\n\n\t/**\n\t * Drop all but the most recently updated session state rows. The row for `keepSessionId` is never\n\t * deleted: it may be an old row that this tab restored its state from and has not rewritten yet,\n\t * and deleting it would make a later reload fall back to another tab's session state.\n\t */\n\tasync pruneSessions({ keepSessionId }: { keepSessionId?: string } = {}) {\n\t\tawait this.tx('readwrite', [Table.SessionState], async (tx) => {\n\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\t\t\tconst all = ((await sessionStateStore.getAll()) as SessionStateSnapshotRow[])\n\t\t\t\t.filter((row) => row.id !== keepSessionId)\n\t\t\t\t.sort((a, b) => a.updatedAt - b.updatedAt)\n\t\t\tconst toDelete = all.slice(0, Math.max(0, all.length - MAX_SESSION_STATE_ROWS))\n\t\t\tfor (const { id } of toDelete) {\n\t\t\t\tawait sessionStateStore.delete(id)\n\t\t\t}\n\t\t})\n\t}\n\n\tasync getAsset(assetId: string): Promise<File | undefined> {\n\t\treturn await this.tx('readonly', [Table.Assets], async (tx) => {\n\t\t\tconst assetsStore = tx.objectStore(Table.Assets)\n\t\t\treturn await assetsStore.get(assetId)\n\t\t})\n\t}\n\n\tasync storeAsset(assetId: string, blob: File) {\n\t\tawait this.tx('readwrite', [Table.Assets], async (tx) => {\n\t\t\tconst assetsStore = tx.objectStore(Table.Assets)\n\t\t\tawait assetsStore.put(blob, assetId)\n\t\t})\n\t}\n\n\tasync removeAssets(assetId: string[]) {\n\t\tawait this.tx('readwrite', [Table.Assets], async (tx) => {\n\t\t\tconst assetsStore = tx.objectStore(Table.Assets)\n\t\t\tfor (const id of assetId) {\n\t\t\t\tawait assetsStore.delete(id)\n\t\t\t}\n\t\t})\n\t}\n}\n\n/** @internal */\nexport function getAllIndexDbNames(): string[] {\n\tconst result = JSON.parse(getFromLocalStorage(dbNameIndexKey) || '[]') ?? []\n\tif (!Array.isArray(result)) {\n\t\treturn []\n\t}\n\treturn result\n}\n\nfunction addDbName(name: string) {\n\tconst all = new Set(getAllIndexDbNames())\n\tall.add(name)\n\tsetInLocalStorage(dbNameIndexKey, JSON.stringify([...all]))\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,6 +7,7 @@ import {
7
7
  extractSessionStateFromLegacySnapshot,
8
8
  loadSessionStateSnapshotIntoStore
9
9
  } from "../../config/TLSessionStateSnapshot.mjs";
10
+ import { refreshPage } from "../runtime.mjs";
10
11
  import { showCantReadFromIndexDbAlert, showCantWriteToIndexDbAlert } from "./alerts.mjs";
11
12
  import { LocalIndexedDb } from "./LocalIndexedDb.mjs";
12
13
  const PERSIST_THROTTLE_MS = 350;
@@ -39,7 +40,6 @@ class TLLocalSyncClient {
39
40
  this.persistenceKey = persistenceKey;
40
41
  this.sessionId = sessionId;
41
42
  this.db = new LocalIndexedDb(persistenceKey);
42
- this.disposables.add(() => this.db.close());
43
43
  this.serializedSchema = this.store.schema.serialize();
44
44
  this.$sessionStateSnapshot = createSessionStateSnapshotSignal(this.store);
45
45
  this.disposables.add(
@@ -117,6 +117,47 @@ class TLLocalSyncClient {
117
117
  async connect(onLoad, onLoadError) {
118
118
  this.debug("connecting");
119
119
  let data;
120
+ const handleMessage = (msg2) => {
121
+ const res = this.store.schema.getMigrationsSince(msg2.schema);
122
+ if (!res.ok) {
123
+ this.isReloading = true;
124
+ const timeSinceInit = Date.now() - this.initTime;
125
+ if (timeSinceInit < 5e3) {
126
+ onLoadError(new Error("Schema mismatch, please close other tabs and reload the page"));
127
+ return false;
128
+ }
129
+ this.debug("reloading");
130
+ if (typeof window !== "undefined") refreshPage();
131
+ return false;
132
+ } else if (res.value.length > 0) {
133
+ this.debug("telling them to reload");
134
+ this.channel.postMessage({ type: "announce", schema: this.serializedSchema });
135
+ this.shouldDoFullDBWrite = true;
136
+ this.schedulePersist();
137
+ return true;
138
+ }
139
+ if (msg2.type === "diff") {
140
+ this.debug("applying diff");
141
+ transact(() => {
142
+ this.store.mergeRemoteChanges(() => {
143
+ this.store.applyDiff(msg2.changes);
144
+ });
145
+ });
146
+ }
147
+ return true;
148
+ };
149
+ let bufferedMessages = [];
150
+ this.channel.onmessage = ({ data: data2 }) => {
151
+ this.debug("got message", data2);
152
+ if (bufferedMessages) {
153
+ bufferedMessages.push(data2);
154
+ } else {
155
+ handleMessage(data2);
156
+ }
157
+ };
158
+ this.disposables.add(() => {
159
+ this.channel.close();
160
+ });
120
161
  try {
121
162
  data = await this.db.load({ sessionId: this.sessionId });
122
163
  } catch (error) {
@@ -154,42 +195,18 @@ class TLLocalSyncClient {
154
195
  });
155
196
  }
156
197
  }
157
- this.channel.onmessage = ({ data: data2 }) => {
158
- this.debug("got message", data2);
159
- const msg2 = data2;
160
- const res = this.store.schema.getMigrationsSince(msg2.schema);
161
- if (!res.ok) {
162
- const timeSinceInit = Date.now() - this.initTime;
163
- if (timeSinceInit < 5e3) {
164
- onLoadError(new Error("Schema mismatch, please close other tabs and reload the page"));
165
- return;
166
- }
167
- this.debug("reloading");
168
- this.isReloading = true;
169
- window?.location?.reload?.();
170
- return;
171
- } else if (res.value.length > 0) {
172
- this.debug("telling them to reload");
173
- this.channel.postMessage({ type: "announce", schema: this.serializedSchema });
174
- this.shouldDoFullDBWrite = true;
175
- this.persistIfNeeded();
176
- return;
177
- }
178
- if (msg2.type === "diff") {
179
- this.debug("applying diff");
180
- transact(() => {
181
- this.store.mergeRemoteChanges(() => {
182
- this.store.applyDiff(msg2.changes);
183
- });
184
- });
185
- }
186
- };
187
- this.channel.postMessage({ type: "announce", schema: this.serializedSchema });
188
- this.disposables.add(() => {
189
- this.channel.close();
190
- });
198
+ const messages = bufferedMessages;
199
+ bufferedMessages = null;
200
+ for (const message of messages) {
201
+ if (!handleMessage(message)) return;
202
+ }
191
203
  this.didLoad = true;
204
+ if (this.diffQueue.length > 0) this.schedulePersist();
205
+ this.channel.postMessage({ type: "announce", schema: this.serializedSchema });
192
206
  onLoad(this);
207
+ this.db.pruneSessions({ keepSessionId: this.sessionId }).catch((e) => {
208
+ this.debug("failed to prune session state rows", e);
209
+ });
193
210
  } catch (e) {
194
211
  this.debug("error loading data from store", e);
195
212
  if (this.didDispose) return;
@@ -198,14 +215,36 @@ class TLLocalSyncClient {
198
215
  }
199
216
  }
200
217
  close() {
218
+ if (this.didDispose) return;
201
219
  this.debug("closing");
202
220
  this.didDispose = true;
221
+ this.store._flushHistory();
203
222
  this.disposables.forEach((d) => d());
223
+ if (this.scheduledPersistTimeout) {
224
+ clearTimeout(this.scheduledPersistTimeout);
225
+ this.scheduledPersistTimeout = null;
226
+ }
204
227
  if (typeof window !== "undefined" && window.tlsync === this) {
205
228
  delete window.tlsync;
206
229
  }
230
+ this.flushAndCloseDb();
231
+ }
232
+ /**
233
+ * Flush queued changes so edits made in the last PERSIST_THROTTLE_MS before unmount aren't lost,
234
+ * then close the db. Never flushes before load (a full write then would wipe the db with an empty
235
+ * store), and only when something is queued: shouldDoFullDBWrite stays true until the first
236
+ * persist, so an unconditional flush would rewrite the whole document on every no-op close.
237
+ */
238
+ flushAndCloseDb() {
239
+ if (this.persistPromise) {
240
+ this.persistPromise.then(() => this.flushAndCloseDb());
241
+ return;
242
+ }
243
+ if (this.didLoad && this.diffQueue.length > 0) this.persistIfNeeded();
244
+ this.db.close();
207
245
  }
208
246
  isPersisting = false;
247
+ persistPromise = null;
209
248
  didLastWriteError = false;
210
249
  // eslint-disable-next-line no-restricted-globals
211
250
  scheduledPersistTimeout = null;
@@ -218,6 +257,7 @@ class TLLocalSyncClient {
218
257
  schedulePersist() {
219
258
  this.debug("schedulePersist", this.scheduledPersistTimeout);
220
259
  if (this.scheduledPersistTimeout) return;
260
+ if (this.didDispose) return;
221
261
  this.scheduledPersistTimeout = setTimeout(
222
262
  () => {
223
263
  this.scheduledPersistTimeout = null;
@@ -247,11 +287,12 @@ class TLLocalSyncClient {
247
287
  clearTimeout(this.scheduledPersistTimeout);
248
288
  this.scheduledPersistTimeout = null;
249
289
  }
290
+ if (!this.didLoad) return;
250
291
  if (this.isPersisting) return;
251
292
  if (this.isReloading) return;
252
293
  if (this.store.isPossiblyCorrupted()) return;
253
294
  if (this.shouldDoFullDBWrite || this.diffQueue.length > 0) {
254
- this.doPersist();
295
+ this.persistPromise = this.doPersist();
255
296
  }
256
297
  }
257
298
  /**
@@ -260,7 +301,6 @@ class TLLocalSyncClient {
260
301
  */
261
302
  async doPersist() {
262
303
  assert(!this.isPersisting, "persist already in progress");
263
- if (this.didDispose) return;
264
304
  this.isPersisting = true;
265
305
  this.debug("doPersist start");
266
306
  const diffQueue = this.diffQueue;
@@ -290,12 +330,13 @@ class TLLocalSyncClient {
290
330
  this.shouldDoFullDBWrite = true;
291
331
  this.didLastWriteError = true;
292
332
  console.error("failed to store changes in indexed db", e);
293
- showCantWriteToIndexDbAlert();
294
- if (typeof window !== "undefined") {
295
- window.location.reload();
333
+ if (!this.didDispose && typeof window !== "undefined") {
334
+ showCantWriteToIndexDbAlert();
335
+ refreshPage();
296
336
  }
297
337
  }
298
338
  this.isPersisting = false;
339
+ this.persistPromise = null;
299
340
  this.debug("doPersist end");
300
341
  this.schedulePersist();
301
342
  }