@dxos/plugin-tldraw 0.10.0
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.
- package/LICENSE +105 -0
- package/PLUGIN.mdl +412 -0
- package/README.md +3 -0
- package/dist/assets/embed-icons/codepen.png +0 -0
- package/dist/assets/embed-icons/codesandbox.png +0 -0
- package/dist/assets/embed-icons/desmos.png +0 -0
- package/dist/assets/embed-icons/excalidraw.png +0 -0
- package/dist/assets/embed-icons/felt.png +0 -0
- package/dist/assets/embed-icons/figma.png +0 -0
- package/dist/assets/embed-icons/github_gist.png +0 -0
- package/dist/assets/embed-icons/google_calendar.png +0 -0
- package/dist/assets/embed-icons/google_maps.png +0 -0
- package/dist/assets/embed-icons/google_slides.png +0 -0
- package/dist/assets/embed-icons/observable.png +0 -0
- package/dist/assets/embed-icons/replit.png +0 -0
- package/dist/assets/embed-icons/scratch.png +0 -0
- package/dist/assets/embed-icons/spotify.png +0 -0
- package/dist/assets/embed-icons/tldraw.png +0 -0
- package/dist/assets/embed-icons/val_town.png +0 -0
- package/dist/assets/embed-icons/vimeo.png +0 -0
- package/dist/assets/embed-icons/youtube.png +0 -0
- package/dist/assets/fonts/IBMPlexMono-Medium.woff2 +0 -0
- package/dist/assets/fonts/IBMPlexSans-Medium.woff2 +0 -0
- package/dist/assets/fonts/IBMPlexSerif-Medium.woff2 +0 -0
- package/dist/assets/fonts/Montserrat-Regular.woff2 +0 -0
- package/dist/assets/fonts/Shantell_Sans-Tldrawish.woff2 +0 -0
- package/dist/assets/icons/icon/0_merged.svg +1 -0
- package/dist/assets/icons/icon/align-bottom.svg +1 -0
- package/dist/assets/icons/icon/align-center-horizontal.svg +1 -0
- package/dist/assets/icons/icon/align-center-vertical.svg +1 -0
- package/dist/assets/icons/icon/align-left.svg +1 -0
- package/dist/assets/icons/icon/align-right.svg +1 -0
- package/dist/assets/icons/icon/align-top.svg +1 -0
- package/dist/assets/icons/icon/arrow-left.svg +1 -0
- package/dist/assets/icons/icon/arrowhead-arrow.svg +1 -0
- package/dist/assets/icons/icon/arrowhead-bar.svg +1 -0
- package/dist/assets/icons/icon/arrowhead-diamond.svg +1 -0
- package/dist/assets/icons/icon/arrowhead-dot.svg +1 -0
- package/dist/assets/icons/icon/arrowhead-none.svg +1 -0
- package/dist/assets/icons/icon/arrowhead-square.svg +1 -0
- package/dist/assets/icons/icon/arrowhead-triangle-inverted.svg +1 -0
- package/dist/assets/icons/icon/arrowhead-triangle.svg +1 -0
- package/dist/assets/icons/icon/blob.svg +1 -0
- package/dist/assets/icons/icon/bring-forward.svg +1 -0
- package/dist/assets/icons/icon/bring-to-front.svg +1 -0
- package/dist/assets/icons/icon/broken.svg +1 -0
- package/dist/assets/icons/icon/chat-teardrop-text.svg +1 -0
- package/dist/assets/icons/icon/check-circle.svg +1 -0
- package/dist/assets/icons/icon/check.svg +1 -0
- package/dist/assets/icons/icon/chevron-down.svg +1 -0
- package/dist/assets/icons/icon/chevron-left.svg +1 -0
- package/dist/assets/icons/icon/chevron-right.svg +1 -0
- package/dist/assets/icons/icon/chevron-up.svg +1 -0
- package/dist/assets/icons/icon/chevrons-ne.svg +1 -0
- package/dist/assets/icons/icon/chevrons-sw.svg +1 -0
- package/dist/assets/icons/icon/clipboard-copied.svg +1 -0
- package/dist/assets/icons/icon/clipboard-copy.svg +1 -0
- package/dist/assets/icons/icon/color.svg +1 -0
- package/dist/assets/icons/icon/cross-2.svg +1 -0
- package/dist/assets/icons/icon/cross-circle.svg +1 -0
- package/dist/assets/icons/icon/dash-dashed.svg +1 -0
- package/dist/assets/icons/icon/dash-dotted.svg +1 -0
- package/dist/assets/icons/icon/dash-draw.svg +1 -0
- package/dist/assets/icons/icon/dash-solid.svg +1 -0
- package/dist/assets/icons/icon/disconnected.svg +1 -0
- package/dist/assets/icons/icon/discord.svg +1 -0
- package/dist/assets/icons/icon/distribute-horizontal.svg +1 -0
- package/dist/assets/icons/icon/distribute-vertical.svg +1 -0
- package/dist/assets/icons/icon/dot.svg +1 -0
- package/dist/assets/icons/icon/dots-horizontal.svg +1 -0
- package/dist/assets/icons/icon/dots-vertical.svg +1 -0
- package/dist/assets/icons/icon/drag-handle-dots.svg +1 -0
- package/dist/assets/icons/icon/duplicate.svg +1 -0
- package/dist/assets/icons/icon/edit.svg +1 -0
- package/dist/assets/icons/icon/external-link.svg +1 -0
- package/dist/assets/icons/icon/fill-fill.svg +1 -0
- package/dist/assets/icons/icon/fill-none.svg +1 -0
- package/dist/assets/icons/icon/fill-pattern.svg +1 -0
- package/dist/assets/icons/icon/fill-semi.svg +1 -0
- package/dist/assets/icons/icon/fill-solid.svg +1 -0
- package/dist/assets/icons/icon/follow.svg +1 -0
- package/dist/assets/icons/icon/following.svg +1 -0
- package/dist/assets/icons/icon/font-draw.svg +8 -0
- package/dist/assets/icons/icon/font-mono.svg +1 -0
- package/dist/assets/icons/icon/font-sans.svg +1 -0
- package/dist/assets/icons/icon/font-serif.svg +1 -0
- package/dist/assets/icons/icon/geo-arrow-down.svg +1 -0
- package/dist/assets/icons/icon/geo-arrow-left.svg +1 -0
- package/dist/assets/icons/icon/geo-arrow-right.svg +1 -0
- package/dist/assets/icons/icon/geo-arrow-up.svg +1 -0
- package/dist/assets/icons/icon/geo-check-box.svg +1 -0
- package/dist/assets/icons/icon/geo-cloud.svg +1 -0
- package/dist/assets/icons/icon/geo-diamond.svg +1 -0
- package/dist/assets/icons/icon/geo-ellipse.svg +1 -0
- package/dist/assets/icons/icon/geo-heart.svg +1 -0
- package/dist/assets/icons/icon/geo-hexagon.svg +1 -0
- package/dist/assets/icons/icon/geo-octagon.svg +1 -0
- package/dist/assets/icons/icon/geo-oval.svg +1 -0
- package/dist/assets/icons/icon/geo-pentagon.svg +1 -0
- package/dist/assets/icons/icon/geo-rectangle.svg +1 -0
- package/dist/assets/icons/icon/geo-rhombus-2.svg +1 -0
- package/dist/assets/icons/icon/geo-rhombus.svg +1 -0
- package/dist/assets/icons/icon/geo-star.svg +1 -0
- package/dist/assets/icons/icon/geo-trapezoid.svg +1 -0
- package/dist/assets/icons/icon/geo-triangle.svg +1 -0
- package/dist/assets/icons/icon/geo-x-box.svg +1 -0
- package/dist/assets/icons/icon/github.svg +1 -0
- package/dist/assets/icons/icon/group.svg +1 -0
- package/dist/assets/icons/icon/horizontal-align-end.svg +1 -0
- package/dist/assets/icons/icon/horizontal-align-middle.svg +1 -0
- package/dist/assets/icons/icon/horizontal-align-start.svg +1 -0
- package/dist/assets/icons/icon/icon-names.json +143 -0
- package/dist/assets/icons/icon/info-circle.svg +1 -0
- package/dist/assets/icons/icon/leading.svg +1 -0
- package/dist/assets/icons/icon/link.svg +1 -0
- package/dist/assets/icons/icon/lock.svg +1 -0
- package/dist/assets/icons/icon/menu.svg +1 -0
- package/dist/assets/icons/icon/minus.svg +1 -0
- package/dist/assets/icons/icon/mixed.svg +1 -0
- package/dist/assets/icons/icon/pack.svg +1 -0
- package/dist/assets/icons/icon/plus.svg +1 -0
- package/dist/assets/icons/icon/question-mark-circle.svg +1 -0
- package/dist/assets/icons/icon/question-mark.svg +1 -0
- package/dist/assets/icons/icon/redo.svg +1 -0
- package/dist/assets/icons/icon/reset-zoom.svg +1 -0
- package/dist/assets/icons/icon/rotate-ccw.svg +1 -0
- package/dist/assets/icons/icon/rotate-cw.svg +1 -0
- package/dist/assets/icons/icon/send-backward.svg +1 -0
- package/dist/assets/icons/icon/send-to-back.svg +1 -0
- package/dist/assets/icons/icon/share-1.svg +1 -0
- package/dist/assets/icons/icon/size-extra-large.svg +1 -0
- package/dist/assets/icons/icon/size-large.svg +1 -0
- package/dist/assets/icons/icon/size-medium.svg +1 -0
- package/dist/assets/icons/icon/size-small.svg +1 -0
- package/dist/assets/icons/icon/spline-cubic.svg +1 -0
- package/dist/assets/icons/icon/spline-line.svg +1 -0
- package/dist/assets/icons/icon/stack-horizontal.svg +1 -0
- package/dist/assets/icons/icon/stack-vertical.svg +1 -0
- package/dist/assets/icons/icon/status-offline.svg +1 -0
- package/dist/assets/icons/icon/stretch-horizontal.svg +1 -0
- package/dist/assets/icons/icon/stretch-vertical.svg +1 -0
- package/dist/assets/icons/icon/text-align-center.svg +1 -0
- package/dist/assets/icons/icon/text-align-left.svg +1 -0
- package/dist/assets/icons/icon/text-align-right.svg +1 -0
- package/dist/assets/icons/icon/toggle-off.svg +1 -0
- package/dist/assets/icons/icon/toggle-on.svg +1 -0
- package/dist/assets/icons/icon/tool-arrow.svg +1 -0
- package/dist/assets/icons/icon/tool-eraser.svg +1 -0
- package/dist/assets/icons/icon/tool-frame.svg +1 -0
- package/dist/assets/icons/icon/tool-hand.svg +1 -0
- package/dist/assets/icons/icon/tool-highlight.svg +1 -0
- package/dist/assets/icons/icon/tool-laser.svg +1 -0
- package/dist/assets/icons/icon/tool-line.svg +1 -0
- package/dist/assets/icons/icon/tool-media.svg +1 -0
- package/dist/assets/icons/icon/tool-note.svg +1 -0
- package/dist/assets/icons/icon/tool-pencil.svg +1 -0
- package/dist/assets/icons/icon/tool-pointer.svg +1 -0
- package/dist/assets/icons/icon/tool-screenshot.svg +1 -0
- package/dist/assets/icons/icon/tool-text.svg +1 -0
- package/dist/assets/icons/icon/trash.svg +1 -0
- package/dist/assets/icons/icon/twitter.svg +1 -0
- package/dist/assets/icons/icon/undo.svg +1 -0
- package/dist/assets/icons/icon/ungroup.svg +1 -0
- package/dist/assets/icons/icon/unlock.svg +1 -0
- package/dist/assets/icons/icon/vertical-align-end.svg +1 -0
- package/dist/assets/icons/icon/vertical-align-middle.svg +1 -0
- package/dist/assets/icons/icon/vertical-align-start.svg +1 -0
- package/dist/assets/icons/icon/warning-triangle.svg +1 -0
- package/dist/assets/icons/icon/zoom-in.svg +1 -0
- package/dist/assets/icons/icon/zoom-out.svg +1 -0
- package/dist/assets/translations/ar.json +295 -0
- package/dist/assets/translations/ca.json +295 -0
- package/dist/assets/translations/cs.json +352 -0
- package/dist/assets/translations/da.json +160 -0
- package/dist/assets/translations/de.json +310 -0
- package/dist/assets/translations/en.json +1 -0
- package/dist/assets/translations/es.json +299 -0
- package/dist/assets/translations/fa.json +384 -0
- package/dist/assets/translations/fi.json +312 -0
- package/dist/assets/translations/fr.json +372 -0
- package/dist/assets/translations/gl.json +349 -0
- package/dist/assets/translations/he.json +86 -0
- package/dist/assets/translations/hi-in.json +290 -0
- package/dist/assets/translations/hr.json +358 -0
- package/dist/assets/translations/hu.json +365 -0
- package/dist/assets/translations/id.json +373 -0
- package/dist/assets/translations/it.json +330 -0
- package/dist/assets/translations/ja.json +351 -0
- package/dist/assets/translations/ko-kr.json +396 -0
- package/dist/assets/translations/ku.json +93 -0
- package/dist/assets/translations/languages.json +150 -0
- package/dist/assets/translations/main.json +396 -0
- package/dist/assets/translations/my.json +112 -0
- package/dist/assets/translations/ne.json +295 -0
- package/dist/assets/translations/no.json +62 -0
- package/dist/assets/translations/pl.json +113 -0
- package/dist/assets/translations/pt-br.json +333 -0
- package/dist/assets/translations/pt-pt.json +86 -0
- package/dist/assets/translations/ro.json +371 -0
- package/dist/assets/translations/ru.json +375 -0
- package/dist/assets/translations/sl.json +373 -0
- package/dist/assets/translations/sv.json +96 -0
- package/dist/assets/translations/te.json +112 -0
- package/dist/assets/translations/th.json +295 -0
- package/dist/assets/translations/tr.json +355 -0
- package/dist/assets/translations/uk.json +375 -0
- package/dist/assets/translations/vi.json +295 -0
- package/dist/assets/translations/zh-cn.json +386 -0
- package/dist/assets/translations/zh-tw.json +340 -0
- package/dist/lib/TldrawPlugin.mjs +22 -0
- package/dist/lib/TldrawPlugin.mjs.map +1 -0
- package/dist/lib/assets/plugin-tldraw-BDrn_mrV.css +24 -0
- package/dist/lib/capabilities.mjs +8 -0
- package/dist/lib/capabilities.mjs.map +1 -0
- package/dist/lib/chunk-TldrawArticle.mjs +60 -0
- package/dist/lib/chunk-TldrawArticle.mjs.map +1 -0
- package/dist/lib/chunk-TldrawCard.mjs +35 -0
- package/dist/lib/chunk-TldrawCard.mjs.map +1 -0
- package/dist/lib/chunk-meta.mjs +40 -0
- package/dist/lib/chunk-meta.mjs.map +1 -0
- package/dist/lib/chunk-settings.mjs +23 -0
- package/dist/lib/chunk-settings.mjs.map +1 -0
- package/dist/lib/chunk-sketch-variant.mjs +23 -0
- package/dist/lib/chunk-sketch-variant.mjs.map +1 -0
- package/dist/lib/chunk-types.mjs +73 -0
- package/dist/lib/chunk-types.mjs.map +1 -0
- package/dist/lib/components.mjs +443 -0
- package/dist/lib/components.mjs.map +1 -0
- package/dist/lib/containers.mjs +8 -0
- package/dist/lib/containers.mjs.map +1 -0
- package/dist/lib/hooks.mjs +116 -0
- package/dist/lib/hooks.mjs.map +1 -0
- package/dist/lib/index.mjs +4 -0
- package/dist/lib/meta.mjs +2 -0
- package/dist/lib/model.mjs +905 -0
- package/dist/lib/model.mjs.map +1 -0
- package/dist/lib/plugin.mjs +8 -0
- package/dist/lib/plugin.mjs.map +1 -0
- package/dist/lib/testing.mjs +246 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/lib/translations.mjs +10 -0
- package/dist/lib/translations.mjs.map +1 -0
- package/dist/lib/types.mjs +2 -0
- package/dist/types/dx.config.d.ts +28 -0
- package/dist/types/dx.config.d.ts.map +1 -0
- package/dist/types/src/TldrawPlugin.d.ts +4 -0
- package/dist/types/src/TldrawPlugin.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +4 -0
- package/dist/types/src/capabilities/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/settings.d.ts +5 -0
- package/dist/types/src/capabilities/settings.d.ts.map +1 -0
- package/dist/types/src/capabilities/sketch-variant.d.ts +5 -0
- package/dist/types/src/capabilities/sketch-variant.d.ts.map +1 -0
- package/dist/types/src/components/Canvas/Canvas.d.ts +17 -0
- package/dist/types/src/components/Canvas/Canvas.d.ts.map +1 -0
- package/dist/types/src/components/Canvas/Canvas.stories.d.ts +15 -0
- package/dist/types/src/components/Canvas/Canvas.stories.d.ts.map +1 -0
- package/dist/types/src/components/Canvas/index.d.ts +4 -0
- package/dist/types/src/components/Canvas/index.d.ts.map +1 -0
- package/dist/types/src/components/actions.d.ts +6 -0
- package/dist/types/src/components/actions.d.ts.map +1 -0
- package/dist/types/src/components/custom/CustomGrid.d.ts +12 -0
- package/dist/types/src/components/custom/CustomGrid.d.ts.map +1 -0
- package/dist/types/src/components/custom/CustomMenu.d.ts +4 -0
- package/dist/types/src/components/custom/CustomMenu.d.ts.map +1 -0
- package/dist/types/src/components/custom/CustomStylePanel.d.ts +4 -0
- package/dist/types/src/components/custom/CustomStylePanel.d.ts.map +1 -0
- package/dist/types/src/components/custom/CustomToolbar.d.ts +10 -0
- package/dist/types/src/components/custom/CustomToolbar.d.ts.map +1 -0
- package/dist/types/src/components/custom/DottedGrid.d.ts +4 -0
- package/dist/types/src/components/custom/DottedGrid.d.ts.map +1 -0
- package/dist/types/src/components/custom/MeshGrid.d.ts +4 -0
- package/dist/types/src/components/custom/MeshGrid.d.ts.map +1 -0
- package/dist/types/src/components/custom/index.d.ts +6 -0
- package/dist/types/src/components/custom/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/containers/TldrawArticle/TldrawArticle.d.ts +8 -0
- package/dist/types/src/containers/TldrawArticle/TldrawArticle.d.ts.map +1 -0
- package/dist/types/src/containers/TldrawArticle/index.d.ts +2 -0
- package/dist/types/src/containers/TldrawArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/TldrawCard/TldrawCard.d.ts +9 -0
- package/dist/types/src/containers/TldrawCard/TldrawCard.d.ts.map +1 -0
- package/dist/types/src/containers/TldrawCard/TldrawCard.stories.d.ts +15 -0
- package/dist/types/src/containers/TldrawCard/TldrawCard.stories.d.ts.map +1 -0
- package/dist/types/src/containers/TldrawCard/index.d.ts +2 -0
- package/dist/types/src/containers/TldrawCard/index.d.ts.map +1 -0
- package/dist/types/src/containers/index.d.ts +4 -0
- package/dist/types/src/containers/index.d.ts.map +1 -0
- package/dist/types/src/hooks/adapter.d.ts +15 -0
- package/dist/types/src/hooks/adapter.d.ts.map +1 -0
- package/dist/types/src/hooks/adapter.test.d.ts +2 -0
- package/dist/types/src/hooks/adapter.test.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +3 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useStoreAdapter.d.ts +11 -0
- package/dist/types/src/hooks/useStoreAdapter.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +33 -0
- package/dist/types/src/meta.d.ts.map +1 -0
- package/dist/types/src/migrations/index.d.ts +3 -0
- package/dist/types/src/migrations/index.d.ts.map +1 -0
- package/dist/types/src/migrations/migrate.d.ts +9 -0
- package/dist/types/src/migrations/migrate.d.ts.map +1 -0
- package/dist/types/src/migrations/schema.d.ts +7 -0
- package/dist/types/src/migrations/schema.d.ts.map +1 -0
- package/dist/types/src/model/TldrawBuilder.d.ts +110 -0
- package/dist/types/src/model/TldrawBuilder.d.ts.map +1 -0
- package/dist/types/src/model/TldrawBuilder.test.d.ts +2 -0
- package/dist/types/src/model/TldrawBuilder.test.d.ts.map +1 -0
- package/dist/types/src/model/apply.d.ts +5 -0
- package/dist/types/src/model/apply.d.ts.map +1 -0
- package/dist/types/src/model/builder.d.ts +3 -0
- package/dist/types/src/model/builder.d.ts.map +1 -0
- package/dist/types/src/model/index.d.ts +6 -0
- package/dist/types/src/model/index.d.ts.map +1 -0
- package/dist/types/src/model/read.d.ts +8 -0
- package/dist/types/src/model/read.d.ts.map +1 -0
- package/dist/types/src/model/render.d.ts +27 -0
- package/dist/types/src/model/render.d.ts.map +1 -0
- package/dist/types/src/model/scene.test.d.ts +2 -0
- package/dist/types/src/model/scene.test.d.ts.map +1 -0
- package/dist/types/src/plugin.d.ts +3 -0
- package/dist/types/src/plugin.d.ts.map +1 -0
- package/dist/types/src/testing/data.d.ts +242 -0
- package/dist/types/src/testing/data.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/schema.d.ts +7 -0
- package/dist/types/src/testing/schema.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +9 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types/Settings.d.ts +10 -0
- package/dist/types/src/types/Settings.d.ts.map +1 -0
- package/dist/types/src/types/Tldraw.d.ts +25 -0
- package/dist/types/src/types/Tldraw.d.ts.map +1 -0
- package/dist/types/src/types/TldrawCapabilities.d.ts +4 -0
- package/dist/types/src/types/TldrawCapabilities.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +4 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/dist/types/src/types/types.d.ts +6 -0
- package/dist/types/src/types/types.d.ts.map +1 -0
- package/dist/types/src/variant.test.d.ts +2 -0
- package/dist/types/src/variant.test.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/dx.config.ts +35 -0
- package/package.json +157 -0
- package/src/TldrawPlugin.tsx +31 -0
- package/src/capabilities/index.ts +8 -0
- package/src/capabilities/settings.ts +31 -0
- package/src/capabilities/sketch-variant.ts +29 -0
- package/src/components/Canvas/Canvas.stories.tsx +101 -0
- package/src/components/Canvas/Canvas.tsx +284 -0
- package/src/components/Canvas/index.ts +9 -0
- package/src/components/Canvas/theme.css +23 -0
- package/src/components/actions.ts +42 -0
- package/src/components/custom/CustomGrid.tsx +61 -0
- package/src/components/custom/CustomMenu.tsx +31 -0
- package/src/components/custom/CustomStylePanel.tsx +38 -0
- package/src/components/custom/CustomToolbar.tsx +88 -0
- package/src/components/custom/DottedGrid.tsx +53 -0
- package/src/components/custom/MeshGrid.tsx +47 -0
- package/src/components/custom/index.ts +9 -0
- package/src/components/index.ts +5 -0
- package/src/containers/TldrawArticle/TldrawArticle.tsx +78 -0
- package/src/containers/TldrawArticle/index.ts +5 -0
- package/src/containers/TldrawCard/TldrawCard.stories.tsx +53 -0
- package/src/containers/TldrawCard/TldrawCard.tsx +32 -0
- package/src/containers/TldrawCard/index.ts +5 -0
- package/src/containers/index.ts +8 -0
- package/src/hooks/adapter.test.ts +20 -0
- package/src/hooks/adapter.ts +83 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/useStoreAdapter.ts +55 -0
- package/src/index.ts +7 -0
- package/src/meta.ts +9 -0
- package/src/migrations/index.ts +6 -0
- package/src/migrations/migrate.ts +49 -0
- package/src/migrations/schema.ts +98 -0
- package/src/model/TldrawBuilder.test.ts +72 -0
- package/src/model/TldrawBuilder.ts +443 -0
- package/src/model/apply.ts +132 -0
- package/src/model/builder.ts +31 -0
- package/src/model/index.ts +9 -0
- package/src/model/read.ts +203 -0
- package/src/model/render.ts +191 -0
- package/src/model/scene.test.ts +222 -0
- package/src/plugin.ts +9 -0
- package/src/testing/data.ts +238 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/schema.ts +98 -0
- package/src/translations.ts +18 -0
- package/src/types/Settings.ts +32 -0
- package/src/types/Tldraw.ts +38 -0
- package/src/types/TldrawCapabilities.ts +17 -0
- package/src/types/index.ts +8 -0
- package/src/types/types.ts +11 -0
- package/src/typings.d.ts +8 -0
- package/src/variant.test.ts +142 -0
- package/src/vite-env.d.ts +10 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
//
|
|
6
|
+
// A minimal, chainable builder for tldraw (`tldraw.com/2`) canvas records.
|
|
7
|
+
// Produces the record map stored in `Sketch.Canvas.content`. Used directly to seed
|
|
8
|
+
// stories/tests (`build()`), and by the scene renderer (`render.ts`) to compile the
|
|
9
|
+
// neutral scene DSL into records merged into an existing canvas (`records()`).
|
|
10
|
+
//
|
|
11
|
+
|
|
12
|
+
import { invariant } from '@dxos/invariant';
|
|
13
|
+
|
|
14
|
+
export type Color =
|
|
15
|
+
| 'black'
|
|
16
|
+
| 'grey'
|
|
17
|
+
| 'light-violet'
|
|
18
|
+
| 'violet'
|
|
19
|
+
| 'blue'
|
|
20
|
+
| 'light-blue'
|
|
21
|
+
| 'yellow'
|
|
22
|
+
| 'orange'
|
|
23
|
+
| 'green'
|
|
24
|
+
| 'light-green'
|
|
25
|
+
| 'light-red'
|
|
26
|
+
| 'red'
|
|
27
|
+
| 'white';
|
|
28
|
+
|
|
29
|
+
export type Fill = 'none' | 'semi' | 'solid' | 'pattern' | 'fill';
|
|
30
|
+
export type Dash = 'draw' | 'solid' | 'dashed' | 'dotted';
|
|
31
|
+
export type Size = 's' | 'm' | 'l' | 'xl';
|
|
32
|
+
export type Font = 'draw' | 'sans' | 'serif' | 'mono';
|
|
33
|
+
export type Align = 'start' | 'middle' | 'end';
|
|
34
|
+
export type Geo =
|
|
35
|
+
| 'rectangle'
|
|
36
|
+
| 'ellipse'
|
|
37
|
+
| 'triangle'
|
|
38
|
+
| 'diamond'
|
|
39
|
+
| 'pentagon'
|
|
40
|
+
| 'hexagon'
|
|
41
|
+
| 'octagon'
|
|
42
|
+
| 'star'
|
|
43
|
+
| 'rhombus'
|
|
44
|
+
| 'oval'
|
|
45
|
+
| 'cloud'
|
|
46
|
+
| 'heart';
|
|
47
|
+
|
|
48
|
+
/** Common style props shared by all shapes. */
|
|
49
|
+
export type StyleProps = {
|
|
50
|
+
color?: Color;
|
|
51
|
+
fill?: Fill;
|
|
52
|
+
dash?: Dash;
|
|
53
|
+
size?: Size;
|
|
54
|
+
font?: Font;
|
|
55
|
+
text?: string;
|
|
56
|
+
align?: Align;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Geometric shape (rectangle, ellipse, etc.). */
|
|
60
|
+
export type ShapeProps = StyleProps & {
|
|
61
|
+
/** Friendly handle used to link shapes (e.g. as an arrow target). */
|
|
62
|
+
id?: string;
|
|
63
|
+
x?: number;
|
|
64
|
+
y?: number;
|
|
65
|
+
w?: number;
|
|
66
|
+
h?: number;
|
|
67
|
+
rotation?: number;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** Free-standing text label. */
|
|
71
|
+
export type TextProps = Omit<ShapeProps, 'h' | 'fill' | 'dash'>;
|
|
72
|
+
|
|
73
|
+
/** Connector (arrow/line) between two shapes (or between explicit points). */
|
|
74
|
+
export type ConnectorProps = Omit<StyleProps, 'align'> & {
|
|
75
|
+
id?: string;
|
|
76
|
+
/** Handle of the source shape. */
|
|
77
|
+
from?: string;
|
|
78
|
+
/** Handle of the target shape. */
|
|
79
|
+
to?: string;
|
|
80
|
+
/** Explicit start point (used when `from` is omitted). */
|
|
81
|
+
start?: Point;
|
|
82
|
+
/** Explicit end point (used when `to` is omitted). */
|
|
83
|
+
end?: Point;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** Open or closed path through explicit points. */
|
|
87
|
+
export type PathProps = Omit<StyleProps, 'align'> & {
|
|
88
|
+
id?: string;
|
|
89
|
+
points: readonly Point[];
|
|
90
|
+
/** Smooth (cubic spline) instead of straight segments. */
|
|
91
|
+
smooth?: boolean;
|
|
92
|
+
/** Close the path back to the first point. */
|
|
93
|
+
closed?: boolean;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type Point = { x: number; y: number };
|
|
97
|
+
|
|
98
|
+
export type CanvasContent = Record<string, any>;
|
|
99
|
+
|
|
100
|
+
export const PAGE_ID = 'page:page';
|
|
101
|
+
export const DOCUMENT_ID = 'document:document';
|
|
102
|
+
|
|
103
|
+
export type BuilderOptions = {
|
|
104
|
+
/** Fractional-index counter seed; pass a value past existing content when merging. */
|
|
105
|
+
indexStart?: number;
|
|
106
|
+
/** Per-shape meta, keyed by handle; merged into each generated record's `meta`. */
|
|
107
|
+
metaFor?: (handle: string) => Record<string, any>;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const DEFAULTS: {
|
|
111
|
+
color: Color;
|
|
112
|
+
fill: Fill;
|
|
113
|
+
dash: Dash;
|
|
114
|
+
size: Size;
|
|
115
|
+
font: Font;
|
|
116
|
+
align: Align;
|
|
117
|
+
w: number;
|
|
118
|
+
h: number;
|
|
119
|
+
} = {
|
|
120
|
+
color: 'black',
|
|
121
|
+
fill: 'none',
|
|
122
|
+
dash: 'draw',
|
|
123
|
+
size: 'm',
|
|
124
|
+
font: 'draw',
|
|
125
|
+
align: 'middle',
|
|
126
|
+
w: 160,
|
|
127
|
+
h: 120,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
type Box = { id: string; x: number; y: number; w: number; h: number };
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Chainable builder for sketch (tldraw) canvas content.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* const content = new TldrawBuilder()
|
|
138
|
+
* .rectangle({ id: 'a', x: 0, y: 0, text: 'DXOS', color: 'blue' })
|
|
139
|
+
* .ellipse({ id: 'b', x: 320, y: 0, text: 'ECHO', color: 'green' })
|
|
140
|
+
* .arrow({ from: 'a', to: 'b' })
|
|
141
|
+
* .build();
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export class TldrawBuilder {
|
|
145
|
+
readonly #records: CanvasContent = {};
|
|
146
|
+
readonly #boxes = new Map<string, Box>();
|
|
147
|
+
readonly #metaFor?: (handle: string) => Record<string, any>;
|
|
148
|
+
#shapeCount = 0;
|
|
149
|
+
#bindingCount = 0;
|
|
150
|
+
#indexCount: number;
|
|
151
|
+
|
|
152
|
+
constructor(options: BuilderOptions = {}) {
|
|
153
|
+
this.#indexCount = options.indexStart ?? 0;
|
|
154
|
+
this.#metaFor = options.metaFor;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Register a shape that already exists on the canvas (its bounding box in canvas px)
|
|
159
|
+
* so connectors added by this builder can bind to it.
|
|
160
|
+
*/
|
|
161
|
+
external(handle: string, box: { x: number; y: number; w: number; h: number }, shapeId?: string): this {
|
|
162
|
+
invariant(!this.#boxes.has(handle), `duplicate shape id: ${handle}`);
|
|
163
|
+
this.#boxes.set(handle, { id: shapeId ?? `shape:${handle}`, ...box });
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Add a rectangle. */
|
|
168
|
+
rectangle(props: ShapeProps = {}): this {
|
|
169
|
+
return this.geo('rectangle', props);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Add an ellipse. */
|
|
173
|
+
ellipse(props: ShapeProps = {}): this {
|
|
174
|
+
return this.geo('ellipse', props);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Add a circle (an ellipse with equal width and height). */
|
|
178
|
+
circle(props: ShapeProps = {}): this {
|
|
179
|
+
const size = props.w ?? props.h ?? DEFAULTS.w;
|
|
180
|
+
return this.geo('ellipse', { ...props, w: size, h: size });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Add an arbitrary geometric shape. */
|
|
184
|
+
geo(geo: Geo, props: ShapeProps = {}): this {
|
|
185
|
+
const { id, x = 0, y = 0, w = DEFAULTS.w, h = DEFAULTS.h, rotation = 0 } = props;
|
|
186
|
+
const shapeId = this.#registerShape({ id, x, y, w, h });
|
|
187
|
+
this.#records[shapeId] = {
|
|
188
|
+
id: shapeId,
|
|
189
|
+
typeName: 'shape',
|
|
190
|
+
type: 'geo',
|
|
191
|
+
x,
|
|
192
|
+
y,
|
|
193
|
+
rotation,
|
|
194
|
+
index: this.#nextIndex(),
|
|
195
|
+
parentId: PAGE_ID,
|
|
196
|
+
isLocked: false,
|
|
197
|
+
opacity: 1,
|
|
198
|
+
meta: this.#meta(id),
|
|
199
|
+
props: {
|
|
200
|
+
geo,
|
|
201
|
+
w,
|
|
202
|
+
h,
|
|
203
|
+
color: props.color ?? DEFAULTS.color,
|
|
204
|
+
labelColor: DEFAULTS.color,
|
|
205
|
+
fill: props.fill ?? DEFAULTS.fill,
|
|
206
|
+
dash: props.dash ?? DEFAULTS.dash,
|
|
207
|
+
size: props.size ?? DEFAULTS.size,
|
|
208
|
+
font: props.font ?? DEFAULTS.font,
|
|
209
|
+
align: props.align ?? DEFAULTS.align,
|
|
210
|
+
verticalAlign: 'middle',
|
|
211
|
+
url: '',
|
|
212
|
+
growY: 0,
|
|
213
|
+
text: props.text ?? '',
|
|
214
|
+
scale: 1,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Add a free-standing text label. */
|
|
222
|
+
text(props: TextProps = {}): this {
|
|
223
|
+
const { id, x = 0, y = 0, w = DEFAULTS.w, rotation = 0 } = props;
|
|
224
|
+
const shapeId = this.#registerShape({ id, x, y, w, h: 0 });
|
|
225
|
+
this.#records[shapeId] = {
|
|
226
|
+
id: shapeId,
|
|
227
|
+
typeName: 'shape',
|
|
228
|
+
type: 'text',
|
|
229
|
+
x,
|
|
230
|
+
y,
|
|
231
|
+
rotation,
|
|
232
|
+
index: this.#nextIndex(),
|
|
233
|
+
parentId: PAGE_ID,
|
|
234
|
+
isLocked: false,
|
|
235
|
+
opacity: 1,
|
|
236
|
+
meta: this.#meta(id),
|
|
237
|
+
props: {
|
|
238
|
+
color: props.color ?? DEFAULTS.color,
|
|
239
|
+
size: props.size ?? DEFAULTS.size,
|
|
240
|
+
font: props.font ?? DEFAULTS.font,
|
|
241
|
+
textAlign: props.align ?? DEFAULTS.align,
|
|
242
|
+
w,
|
|
243
|
+
text: props.text ?? '',
|
|
244
|
+
scale: 1,
|
|
245
|
+
autoSize: true,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Add an arrow, optionally bound to source/target shapes so it tracks them. */
|
|
253
|
+
arrow(props: ConnectorProps = {}): this {
|
|
254
|
+
return this.#connector('arrow', props);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Add a line between two shapes (or explicit points). */
|
|
258
|
+
line(props: ConnectorProps = {}): this {
|
|
259
|
+
return this.#connector('line', props);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Add a path (open or closed, straight or smooth) through explicit points. */
|
|
263
|
+
path(props: PathProps): this {
|
|
264
|
+
const { id, smooth, closed } = props;
|
|
265
|
+
const source = closed && props.points.length > 1 ? [...props.points, props.points[0]] : [...props.points];
|
|
266
|
+
invariant(source.length >= 2, 'path requires at least two points');
|
|
267
|
+
const origin = source[0];
|
|
268
|
+
const xs = source.map((point) => point.x);
|
|
269
|
+
const ys = source.map((point) => point.y);
|
|
270
|
+
const shapeId = this.#registerShape({
|
|
271
|
+
id,
|
|
272
|
+
x: Math.min(...xs),
|
|
273
|
+
y: Math.min(...ys),
|
|
274
|
+
w: Math.max(...xs) - Math.min(...xs),
|
|
275
|
+
h: Math.max(...ys) - Math.min(...ys),
|
|
276
|
+
});
|
|
277
|
+
this.#records[shapeId] = {
|
|
278
|
+
id: shapeId,
|
|
279
|
+
typeName: 'shape',
|
|
280
|
+
type: 'line',
|
|
281
|
+
x: origin.x,
|
|
282
|
+
y: origin.y,
|
|
283
|
+
rotation: 0,
|
|
284
|
+
index: this.#nextIndex(),
|
|
285
|
+
parentId: PAGE_ID,
|
|
286
|
+
isLocked: false,
|
|
287
|
+
opacity: 1,
|
|
288
|
+
meta: this.#meta(id),
|
|
289
|
+
props: {
|
|
290
|
+
color: props.color ?? DEFAULTS.color,
|
|
291
|
+
dash: props.dash ?? DEFAULTS.dash,
|
|
292
|
+
size: props.size ?? DEFAULTS.size,
|
|
293
|
+
spline: smooth ? 'cubic' : 'line',
|
|
294
|
+
points: Object.fromEntries(
|
|
295
|
+
source.map((point, i) => [
|
|
296
|
+
`a${i + 1}`,
|
|
297
|
+
{ id: `a${i + 1}`, index: `a${i + 1}`, x: point.x - origin.x, y: point.y - origin.y },
|
|
298
|
+
]),
|
|
299
|
+
),
|
|
300
|
+
scale: 1,
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
return this;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Build the full canvas snapshot (document + page + records). */
|
|
308
|
+
build(): CanvasContent {
|
|
309
|
+
return {
|
|
310
|
+
[DOCUMENT_ID]: { gridSize: 10, name: '', meta: {}, id: DOCUMENT_ID, typeName: 'document' },
|
|
311
|
+
[PAGE_ID]: { meta: {}, id: PAGE_ID, name: 'Page 1', index: 'a1', typeName: 'page' },
|
|
312
|
+
...this.#records,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Return only the generated records (for merging into an existing canvas). */
|
|
317
|
+
records(): CanvasContent {
|
|
318
|
+
return { ...this.#records };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
#connector(type: 'arrow' | 'line', props: ConnectorProps): this {
|
|
322
|
+
const from = props.from ? this.#boxes.get(props.from) : undefined;
|
|
323
|
+
const to = props.to ? this.#boxes.get(props.to) : undefined;
|
|
324
|
+
// A referenced handle that resolves to nothing is a typo; fail rather than emit a misplaced connector.
|
|
325
|
+
invariant(!props.from || from, `unknown source shape: ${props.from}`);
|
|
326
|
+
invariant(!props.to || to, `unknown target shape: ${props.to}`);
|
|
327
|
+
const start = from ? center(from) : (props.start ?? { x: 0, y: 0 });
|
|
328
|
+
const end = to ? center(to) : (props.end ?? { x: DEFAULTS.w, y: 0 });
|
|
329
|
+
|
|
330
|
+
const shapeId = this.#registerShape({ id: props.id, x: start.x, y: start.y, w: 0, h: 0 });
|
|
331
|
+
const relativeEnd = { x: end.x - start.x, y: end.y - start.y };
|
|
332
|
+
const style = {
|
|
333
|
+
color: props.color ?? DEFAULTS.color,
|
|
334
|
+
dash: props.dash ?? DEFAULTS.dash,
|
|
335
|
+
size: props.size ?? DEFAULTS.size,
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
if (type === 'arrow') {
|
|
339
|
+
this.#records[shapeId] = {
|
|
340
|
+
id: shapeId,
|
|
341
|
+
typeName: 'shape',
|
|
342
|
+
type: 'arrow',
|
|
343
|
+
x: start.x,
|
|
344
|
+
y: start.y,
|
|
345
|
+
rotation: 0,
|
|
346
|
+
index: this.#nextIndex(),
|
|
347
|
+
parentId: PAGE_ID,
|
|
348
|
+
isLocked: false,
|
|
349
|
+
opacity: 1,
|
|
350
|
+
meta: this.#meta(props.id),
|
|
351
|
+
props: {
|
|
352
|
+
...style,
|
|
353
|
+
labelColor: DEFAULTS.color,
|
|
354
|
+
fill: props.fill ?? DEFAULTS.fill,
|
|
355
|
+
font: props.font ?? DEFAULTS.font,
|
|
356
|
+
arrowheadStart: 'none',
|
|
357
|
+
arrowheadEnd: 'arrow',
|
|
358
|
+
start: { x: 0, y: 0 },
|
|
359
|
+
end: relativeEnd,
|
|
360
|
+
bend: 0,
|
|
361
|
+
text: props.text ?? '',
|
|
362
|
+
labelPosition: 0.5,
|
|
363
|
+
scale: 1,
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// Bindings make the arrow track its bound shapes (tldraw recomputes terminals).
|
|
368
|
+
if (from) {
|
|
369
|
+
this.#binding(shapeId, from.id, 'start');
|
|
370
|
+
}
|
|
371
|
+
if (to) {
|
|
372
|
+
this.#binding(shapeId, to.id, 'end');
|
|
373
|
+
}
|
|
374
|
+
} else {
|
|
375
|
+
this.#records[shapeId] = {
|
|
376
|
+
id: shapeId,
|
|
377
|
+
typeName: 'shape',
|
|
378
|
+
type: 'line',
|
|
379
|
+
x: start.x,
|
|
380
|
+
y: start.y,
|
|
381
|
+
rotation: 0,
|
|
382
|
+
index: this.#nextIndex(),
|
|
383
|
+
parentId: PAGE_ID,
|
|
384
|
+
isLocked: false,
|
|
385
|
+
opacity: 1,
|
|
386
|
+
meta: this.#meta(props.id),
|
|
387
|
+
props: {
|
|
388
|
+
...style,
|
|
389
|
+
spline: 'line',
|
|
390
|
+
points: {
|
|
391
|
+
a1: { id: 'a1', index: 'a1', x: 0, y: 0 },
|
|
392
|
+
a2: { id: 'a2', index: 'a2', x: relativeEnd.x, y: relativeEnd.y },
|
|
393
|
+
},
|
|
394
|
+
scale: 1,
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return this;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
#binding(arrowId: string, shapeId: string, terminal: 'start' | 'end'): void {
|
|
403
|
+
// Deterministic per-terminal id so re-rendering an arrow overwrites its own bindings.
|
|
404
|
+
const bindingId = `binding:${arrowId.slice('shape:'.length)}/${terminal}-${++this.#bindingCount}`;
|
|
405
|
+
this.#records[bindingId] = {
|
|
406
|
+
id: bindingId,
|
|
407
|
+
typeName: 'binding',
|
|
408
|
+
type: 'arrow',
|
|
409
|
+
fromId: arrowId,
|
|
410
|
+
toId: shapeId,
|
|
411
|
+
meta: {},
|
|
412
|
+
props: {
|
|
413
|
+
terminal,
|
|
414
|
+
normalizedAnchor: { x: 0.5, y: 0.5 },
|
|
415
|
+
isExact: false,
|
|
416
|
+
isPrecise: false,
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
#meta(handle: string | undefined): Record<string, any> {
|
|
422
|
+
return handle && this.#metaFor ? this.#metaFor(handle) : {};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
#registerShape(box: { id?: string; x: number; y: number; w: number; h: number }): string {
|
|
426
|
+
// Skip autogenerated ids that collide with an explicit one (e.g. a user-supplied `s1`).
|
|
427
|
+
let shapeId = box.id ? `shape:${box.id}` : `shape:s${++this.#shapeCount}`;
|
|
428
|
+
while (!box.id && this.#records[shapeId]) {
|
|
429
|
+
shapeId = `shape:s${++this.#shapeCount}`;
|
|
430
|
+
}
|
|
431
|
+
const handle = box.id ?? shapeId;
|
|
432
|
+
// The id is the record-map key; reusing one would silently overwrite the earlier shape.
|
|
433
|
+
invariant(!this.#records[shapeId] && !this.#boxes.has(handle), `duplicate shape id: ${handle}`);
|
|
434
|
+
this.#boxes.set(handle, { id: shapeId, x: box.x, y: box.y, w: box.w, h: box.h });
|
|
435
|
+
return shapeId;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
#nextIndex(): string {
|
|
439
|
+
return `a${++this.#indexCount}`;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const center = (box: Box): Point => ({ x: box.x + box.w / 2, y: box.y + box.h / 2 });
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
//
|
|
6
|
+
// Applies scene DSL edit commands to canvas content by mutating the record map in place
|
|
7
|
+
// (callers wrap the call in `Obj.update` so a batch commits atomically).
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
import { invariant } from '@dxos/invariant';
|
|
11
|
+
import { type ApplyResult, type Scene } from '@dxos/plugin-illustrator/model';
|
|
12
|
+
|
|
13
|
+
import { elementBoxes, readScene } from './read';
|
|
14
|
+
import { renderObject } from './render';
|
|
15
|
+
import { type CanvasContent, DOCUMENT_ID, PAGE_ID } from './TldrawBuilder';
|
|
16
|
+
|
|
17
|
+
/** Apply a batch of edit commands to the canvas content (mutates `content`). */
|
|
18
|
+
export const applyCommands = (content: CanvasContent, commands: readonly Scene.Command[]): ApplyResult => {
|
|
19
|
+
ensurePage(content);
|
|
20
|
+
const upserted = new Set<string>();
|
|
21
|
+
let removed = 0;
|
|
22
|
+
|
|
23
|
+
for (const command of commands) {
|
|
24
|
+
switch (command.op) {
|
|
25
|
+
case 'upsert-object': {
|
|
26
|
+
const previous = readScene(content).scene.objects.find((object) => object.id === command.object.id);
|
|
27
|
+
removed += remove(content, (record) => record.meta?.object === command.object.id);
|
|
28
|
+
merge(content, command.object, {
|
|
29
|
+
origin: command.object.origin ?? previous?.origin ?? { x: 0, y: 0 },
|
|
30
|
+
scale: command.object.scale ?? previous?.scale ?? 1,
|
|
31
|
+
});
|
|
32
|
+
upserted.add(command.object.id);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
case 'upsert-elements': {
|
|
36
|
+
const previous = readScene(content).scene.objects.find((object) => object.id === command.objectId);
|
|
37
|
+
invariant(previous, `unknown object: ${command.objectId} (use upsert-object to create it)`);
|
|
38
|
+
const ids = new Set(command.elements.map((element) => element.id));
|
|
39
|
+
removed += remove(
|
|
40
|
+
content,
|
|
41
|
+
(record) => record.meta?.object === command.objectId && ids.has(record.meta?.element),
|
|
42
|
+
);
|
|
43
|
+
merge(
|
|
44
|
+
content,
|
|
45
|
+
{ id: command.objectId, elements: command.elements },
|
|
46
|
+
{ origin: previous.origin, scale: previous.scale },
|
|
47
|
+
);
|
|
48
|
+
upserted.add(command.objectId);
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case 'remove-elements': {
|
|
52
|
+
const ids = new Set(command.elementIds);
|
|
53
|
+
removed += remove(
|
|
54
|
+
content,
|
|
55
|
+
(record) => record.meta?.object === command.objectId && ids.has(record.meta?.element),
|
|
56
|
+
);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
case 'remove-object': {
|
|
60
|
+
removed += remove(content, (record) => record.meta?.object === command.objectId);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case 'move-object': {
|
|
64
|
+
const previous = readScene(content).scene.objects.find((object) => object.id === command.objectId);
|
|
65
|
+
invariant(previous, `unknown object: ${command.objectId}`);
|
|
66
|
+
const delta = { x: command.origin.x - previous.origin.x, y: command.origin.y - previous.origin.y };
|
|
67
|
+
for (const record of Object.values(content)) {
|
|
68
|
+
if (record?.typeName === 'shape' && record.meta?.object === command.objectId) {
|
|
69
|
+
record.x += delta.x;
|
|
70
|
+
record.y += delta.y;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
upserted.add(command.objectId);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { upserted: [...upserted], removed };
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const merge = (
|
|
83
|
+
content: CanvasContent,
|
|
84
|
+
object: Scene.WorldObject | { id: string; elements: readonly Scene.Element[] },
|
|
85
|
+
placement: { origin: Scene.Point; scale: number },
|
|
86
|
+
): void => {
|
|
87
|
+
const records = renderObject(object, {
|
|
88
|
+
...placement,
|
|
89
|
+
indexStart: nextIndex(content),
|
|
90
|
+
external: elementBoxes(content),
|
|
91
|
+
});
|
|
92
|
+
Object.assign(content, records);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/** Delete matching shape records, then any bindings left dangling. */
|
|
96
|
+
const remove = (content: CanvasContent, predicate: (record: any) => boolean): number => {
|
|
97
|
+
let removed = 0;
|
|
98
|
+
for (const [key, record] of Object.entries(content)) {
|
|
99
|
+
if (record?.typeName === 'shape' && predicate(record)) {
|
|
100
|
+
delete content[key];
|
|
101
|
+
removed++;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const [key, record] of Object.entries(content)) {
|
|
105
|
+
if (record?.typeName === 'binding' && (!content[record.fromId] || !content[record.toId])) {
|
|
106
|
+
delete content[key];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return removed;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** Seed the fractional-index counter past any existing `a<n>` indexes. */
|
|
113
|
+
const nextIndex = (content: CanvasContent): number => {
|
|
114
|
+
let max = 0;
|
|
115
|
+
let shapes = 0;
|
|
116
|
+
for (const record of Object.values(content)) {
|
|
117
|
+
if (record?.typeName !== 'shape') {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
shapes++;
|
|
121
|
+
const match = /^a(\d+)$/.exec(record.index ?? '');
|
|
122
|
+
if (match) {
|
|
123
|
+
max = Math.max(max, Number(match[1]));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return Math.max(max, shapes);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const ensurePage = (content: CanvasContent): void => {
|
|
130
|
+
content[DOCUMENT_ID] ??= { gridSize: 10, name: '', meta: {}, id: DOCUMENT_ID, typeName: 'document' };
|
|
131
|
+
content[PAGE_ID] ??= { meta: {}, id: PAGE_ID, name: 'Page 1', index: 'a1', typeName: 'page' };
|
|
132
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
//
|
|
6
|
+
// tldraw implementation of the illustrator `SketchBuilder` contract: maps the scene DSL
|
|
7
|
+
// onto the tldraw record map stored in `Tldraw.Canvas.content`.
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
import { Obj } from '@dxos/echo';
|
|
11
|
+
import { invariant } from '@dxos/invariant';
|
|
12
|
+
import { type ApplyResult, type SketchBuilder } from '@dxos/plugin-illustrator/model';
|
|
13
|
+
|
|
14
|
+
import { Tldraw } from '../types';
|
|
15
|
+
import { applyCommands } from './apply';
|
|
16
|
+
import { readScene } from './read';
|
|
17
|
+
|
|
18
|
+
export const tldrawBuilder: SketchBuilder = {
|
|
19
|
+
read: (canvas) => {
|
|
20
|
+
invariant(Obj.instanceOf(Tldraw.Canvas, canvas));
|
|
21
|
+
return readScene(canvas.content);
|
|
22
|
+
},
|
|
23
|
+
apply: (canvas, commands) => {
|
|
24
|
+
invariant(Obj.instanceOf(Tldraw.Canvas, canvas));
|
|
25
|
+
let result: ApplyResult = { upserted: [], removed: 0 };
|
|
26
|
+
Obj.update(canvas, (canvas) => {
|
|
27
|
+
result = applyCommands(canvas.content as Obj.Mutable<typeof canvas.content>, commands);
|
|
28
|
+
});
|
|
29
|
+
return result;
|
|
30
|
+
},
|
|
31
|
+
};
|