makersclaw 0.2.0 → 0.3.1
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/THIRD-PARTY-NOTICES.md +180 -0
- package/package.json +3 -2
- package/r/annotate.json +62 -0
- package/r/approval-request.json +52 -0
- package/r/audio-player.json +74 -0
- package/r/auth-form.json +55 -0
- package/r/board.json +10 -6
- package/r/calendar-view.json +105 -0
- package/r/cart.json +62 -0
- package/r/chart.json +83 -0
- package/r/chat.json +95 -0
- package/r/code-editor.json +94 -0
- package/r/comment-box.json +64 -0
- package/r/contact-section.json +53 -0
- package/r/core.json +205 -8
- package/r/csv-import.json +63 -0
- package/r/data-table.json +69 -0
- package/r/diagram.json +74 -0
- package/r/doc-editor.json +82 -0
- package/r/doc-page.json +61 -0
- package/r/email-render.json +73 -0
- package/r/faq.json +50 -0
- package/r/file-upload.json +54 -0
- package/r/flow-canvas.json +93 -0
- package/r/footer.json +52 -0
- package/r/gantt.json +70 -0
- package/r/job-panel.json +12 -7
- package/r/json-editor.json +66 -0
- package/r/map.json +89 -0
- package/r/menubar.json +52 -0
- package/r/mind-map.json +67 -0
- package/r/navigation-menu.json +52 -0
- package/r/order-detail.json +54 -0
- package/r/org-chart.json +66 -0
- package/r/page-builder.json +105 -0
- package/r/payment-methods.json +51 -0
- package/r/pdf-document.json +79 -0
- package/r/pdf-viewer.json +88 -0
- package/r/product-detail.json +76 -0
- package/r/product-grid.json +65 -0
- package/r/record-form.json +67 -0
- package/r/record-notes.json +55 -0
- package/r/registry.json +3411 -29
- package/r/search-dialog.json +51 -0
- package/r/settings-list.json +66 -0
- package/r/setup-checklist.json +51 -0
- package/r/share-dialog.json +52 -0
- package/r/sidebar.json +69 -0
- package/r/signature-pad.json +72 -0
- package/r/sketch-canvas.json +99 -0
- package/r/spreadsheet.json +90 -0
- package/r/stat-cards.json +66 -0
- package/r/task-list.json +63 -0
- package/r/task-model.json +98 -0
- package/r/testimonials.json +52 -0
- package/r/transaction-list.json +53 -0
- package/r/whiteboard.json +66 -0
- package/registry/blocks/annotate/README.md +179 -0
- package/registry/blocks/annotate/annotate-data.js +250 -0
- package/registry/blocks/annotate/annotate.jsx +646 -0
- package/registry/blocks/annotate/block.json +10 -0
- package/registry/blocks/annotate/demo.jsx +135 -0
- package/registry/blocks/annotate/index.js +15 -0
- package/registry/blocks/approval-request/README.md +116 -0
- package/registry/blocks/approval-request/approval-request.jsx +386 -0
- package/registry/blocks/approval-request/block.json +10 -0
- package/registry/blocks/approval-request/demo.jsx +127 -0
- package/registry/blocks/approval-request/index.js +4 -0
- package/registry/blocks/audio-player/README.md +167 -0
- package/registry/blocks/audio-player/audio-player.jsx +623 -0
- package/registry/blocks/audio-player/block.json +14 -0
- package/registry/blocks/audio-player/colors.js +109 -0
- package/registry/blocks/audio-player/demo.jsx +209 -0
- package/registry/blocks/audio-player/index.js +6 -0
- package/registry/blocks/audio-player/time.js +67 -0
- package/registry/blocks/audio-player/wavesurfer.js +9 -0
- package/registry/blocks/auth-form/README.md +126 -0
- package/registry/blocks/auth-form/auth-form.jsx +568 -0
- package/registry/blocks/auth-form/block.json +10 -0
- package/registry/blocks/auth-form/demo.jsx +131 -0
- package/registry/blocks/auth-form/index.js +3 -0
- package/registry/blocks/board/README.md +55 -5
- package/registry/blocks/board/block.json +2 -2
- package/registry/blocks/board/board.jsx +230 -86
- package/registry/blocks/board/demo.jsx +103 -24
- package/registry/blocks/calendar-view/README.md +180 -0
- package/registry/blocks/calendar-view/agenda.jsx +147 -0
- package/registry/blocks/calendar-view/block.json +76 -0
- package/registry/blocks/calendar-view/calendar-model.js +442 -0
- package/registry/blocks/calendar-view/calendar.jsx +627 -0
- package/registry/blocks/calendar-view/demo.jsx +195 -0
- package/registry/blocks/calendar-view/draft.jsx +49 -0
- package/registry/blocks/calendar-view/index.js +39 -0
- package/registry/blocks/calendar-view/month.jsx +346 -0
- package/registry/blocks/calendar-view/paint.js +69 -0
- package/registry/blocks/calendar-view/time-grid.jsx +527 -0
- package/registry/blocks/calendar-view/tray.jsx +87 -0
- package/registry/blocks/cart/README.md +146 -0
- package/registry/blocks/cart/block.json +10 -0
- package/registry/blocks/cart/cart.jsx +597 -0
- package/registry/blocks/cart/demo.jsx +98 -0
- package/registry/blocks/cart/index.js +4 -0
- package/registry/blocks/cart/money.js +28 -0
- package/registry/blocks/chart/README.md +316 -0
- package/registry/blocks/chart/block.json +10 -0
- package/registry/blocks/chart/cartesian.jsx +642 -0
- package/registry/blocks/chart/core.jsx +262 -0
- package/registry/blocks/chart/demo.jsx +357 -0
- package/registry/blocks/chart/heatmap.jsx +336 -0
- package/registry/blocks/chart/index.js +8 -0
- package/registry/blocks/chart/pie.jsx +245 -0
- package/registry/blocks/chart/sparkline.jsx +88 -0
- package/registry/blocks/chat/README.md +273 -0
- package/registry/blocks/chat/attachment.jsx +95 -0
- package/registry/blocks/chat/block.json +10 -0
- package/registry/blocks/chat/chat.jsx +132 -0
- package/registry/blocks/chat/composer.jsx +364 -0
- package/registry/blocks/chat/demo.jsx +578 -0
- package/registry/blocks/chat/header.jsx +34 -0
- package/registry/blocks/chat/index.js +9 -0
- package/registry/blocks/chat/message.jsx +450 -0
- package/registry/blocks/chat/scroller.jsx +247 -0
- package/registry/blocks/chat/text.jsx +104 -0
- package/registry/blocks/code-editor/README.md +210 -0
- package/registry/blocks/code-editor/block.json +22 -0
- package/registry/blocks/code-editor/code-editor.jsx +534 -0
- package/registry/blocks/code-editor/codemirror.js +38 -0
- package/registry/blocks/code-editor/demo.jsx +205 -0
- package/registry/blocks/code-editor/index.js +8 -0
- package/registry/blocks/code-editor/json.js +317 -0
- package/registry/blocks/code-editor/sql.js +136 -0
- package/registry/blocks/code-editor/text.js +101 -0
- package/registry/blocks/code-editor/theme.js +186 -0
- package/registry/blocks/comment-box/README.md +123 -0
- package/registry/blocks/comment-box/block.json +10 -0
- package/registry/blocks/comment-box/comment-body.jsx +217 -0
- package/registry/blocks/comment-box/comment-box.jsx +198 -0
- package/registry/blocks/comment-box/comment-time.jsx +77 -0
- package/registry/blocks/comment-box/demo.jsx +141 -0
- package/registry/blocks/comment-box/index.js +8 -0
- package/registry/blocks/contact-section/README.md +152 -0
- package/registry/blocks/contact-section/block.json +11 -0
- package/registry/blocks/contact-section/contact-section.jsx +509 -0
- package/registry/blocks/contact-section/demo.jsx +93 -0
- package/registry/blocks/contact-section/index.js +4 -0
- package/registry/blocks/csv-import/README.md +133 -0
- package/registry/blocks/csv-import/block.json +13 -0
- package/registry/blocks/csv-import/csv-import.jsx +479 -0
- package/registry/blocks/csv-import/demo.jsx +106 -0
- package/registry/blocks/csv-import/import.js +200 -0
- package/registry/blocks/csv-import/index.js +7 -0
- package/registry/blocks/data-table/README.md +386 -0
- package/registry/blocks/data-table/block.json +10 -0
- package/registry/blocks/data-table/data-table.jsx +1164 -0
- package/registry/blocks/data-table/demo.jsx +588 -0
- package/registry/blocks/data-table/index.js +5 -0
- package/registry/blocks/data-table/model.js +169 -0
- package/registry/blocks/diagram/README.md +108 -0
- package/registry/blocks/diagram/block.json +13 -0
- package/registry/blocks/diagram/demo.jsx +113 -0
- package/registry/blocks/diagram/diagram.jsx +481 -0
- package/registry/blocks/diagram/index.js +7 -0
- package/registry/blocks/diagram/mermaid.js +52 -0
- package/registry/blocks/diagram/palette.js +313 -0
- package/registry/blocks/diagram/source.js +98 -0
- package/registry/blocks/doc-editor/README.md +215 -0
- package/registry/blocks/doc-editor/block.json +15 -0
- package/registry/blocks/doc-editor/demo.jsx +225 -0
- package/registry/blocks/doc-editor/doc-editor.jsx +291 -0
- package/registry/blocks/doc-editor/doc.js +309 -0
- package/registry/blocks/doc-editor/index.js +7 -0
- package/registry/blocks/doc-editor/schema.jsx +56 -0
- package/registry/blocks/doc-editor/shadcn-classes.js +110 -0
- package/registry/blocks/doc-editor/theme.js +82 -0
- package/registry/blocks/doc-page/README.md +119 -0
- package/registry/blocks/doc-page/autosave.js +104 -0
- package/registry/blocks/doc-page/block.json +10 -0
- package/registry/blocks/doc-page/demo.jsx +137 -0
- package/registry/blocks/doc-page/doc-page.jsx +276 -0
- package/registry/blocks/doc-page/index.js +4 -0
- package/registry/blocks/email-render/README.md +151 -0
- package/registry/blocks/email-render/block.json +14 -0
- package/registry/blocks/email-render/demo.jsx +153 -0
- package/registry/blocks/email-render/doc-email.jsx +210 -0
- package/registry/blocks/email-render/email.jsx +221 -0
- package/registry/blocks/email-render/index.js +10 -0
- package/registry/blocks/email-render/render.js +50 -0
- package/registry/blocks/email-render/theme.js +215 -0
- package/registry/blocks/faq/README.md +101 -0
- package/registry/blocks/faq/block.json +11 -0
- package/registry/blocks/faq/demo.jsx +89 -0
- package/registry/blocks/faq/faq.jsx +220 -0
- package/registry/blocks/faq/index.js +4 -0
- package/registry/blocks/file-upload/README.md +138 -0
- package/registry/blocks/file-upload/block.json +10 -0
- package/registry/blocks/file-upload/demo.jsx +204 -0
- package/registry/blocks/file-upload/file-upload.jsx +635 -0
- package/registry/blocks/file-upload/index.js +4 -0
- package/registry/blocks/flow-canvas/README.md +420 -0
- package/registry/blocks/flow-canvas/block.json +13 -0
- package/registry/blocks/flow-canvas/board.js +819 -0
- package/registry/blocks/flow-canvas/chrome.jsx +649 -0
- package/registry/blocks/flow-canvas/context.jsx +35 -0
- package/registry/blocks/flow-canvas/demo.jsx +249 -0
- package/registry/blocks/flow-canvas/edges.jsx +107 -0
- package/registry/blocks/flow-canvas/flow-canvas.jsx +1329 -0
- package/registry/blocks/flow-canvas/index.js +41 -0
- package/registry/blocks/flow-canvas/nodes.jsx +392 -0
- package/registry/blocks/flow-canvas/theme.js +174 -0
- package/registry/blocks/footer/README.md +133 -0
- package/registry/blocks/footer/block.json +11 -0
- package/registry/blocks/footer/demo.jsx +119 -0
- package/registry/blocks/footer/footer.jsx +406 -0
- package/registry/blocks/footer/index.js +4 -0
- package/registry/blocks/gantt/README.md +220 -0
- package/registry/blocks/gantt/block.json +10 -0
- package/registry/blocks/gantt/demo.jsx +148 -0
- package/registry/blocks/gantt/gantt.jsx +923 -0
- package/registry/blocks/gantt/index.js +7 -0
- package/registry/blocks/gantt/rows.jsx +468 -0
- package/registry/blocks/gantt/schedule.js +278 -0
- package/registry/blocks/job-panel/README.md +80 -0
- package/registry/blocks/job-panel/block.json +3 -3
- package/registry/blocks/job-panel/demo.jsx +230 -3
- package/registry/blocks/job-panel/job-panel.jsx +169 -3
- package/registry/blocks/json-editor/README.md +133 -0
- package/registry/blocks/json-editor/block.json +10 -0
- package/registry/blocks/json-editor/demo.jsx +92 -0
- package/registry/blocks/json-editor/index.js +7 -0
- package/registry/blocks/json-editor/json-editor.jsx +305 -0
- package/registry/blocks/json-editor/json-tree.jsx +393 -0
- package/registry/blocks/json-editor/tree.js +225 -0
- package/registry/blocks/map/README.md +221 -0
- package/registry/blocks/map/block.json +14 -0
- package/registry/blocks/map/demo.jsx +201 -0
- package/registry/blocks/map/geo.js +198 -0
- package/registry/blocks/map/index.js +8 -0
- package/registry/blocks/map/list.jsx +128 -0
- package/registry/blocks/map/map-view.jsx +715 -0
- package/registry/blocks/map/maplibre.js +30 -0
- package/registry/blocks/map/marks.jsx +146 -0
- package/registry/blocks/map/theme.js +142 -0
- package/registry/blocks/menubar/README.md +109 -0
- package/registry/blocks/menubar/block.json +10 -0
- package/registry/blocks/menubar/demo.jsx +128 -0
- package/registry/blocks/menubar/index.js +4 -0
- package/registry/blocks/menubar/menubar.jsx +199 -0
- package/registry/blocks/mind-map/README.md +206 -0
- package/registry/blocks/mind-map/block.json +10 -0
- package/registry/blocks/mind-map/demo.jsx +103 -0
- package/registry/blocks/mind-map/index.js +31 -0
- package/registry/blocks/mind-map/mind-map.jsx +1125 -0
- package/registry/blocks/mind-map/topic.jsx +143 -0
- package/registry/blocks/mind-map/tree.js +645 -0
- package/registry/blocks/navigation-menu/README.md +124 -0
- package/registry/blocks/navigation-menu/block.json +10 -0
- package/registry/blocks/navigation-menu/demo.jsx +84 -0
- package/registry/blocks/navigation-menu/index.js +4 -0
- package/registry/blocks/navigation-menu/navigation-menu.jsx +448 -0
- package/registry/blocks/order-detail/README.md +109 -0
- package/registry/blocks/order-detail/block.json +10 -0
- package/registry/blocks/order-detail/demo.jsx +88 -0
- package/registry/blocks/order-detail/index.js +4 -0
- package/registry/blocks/order-detail/order-detail.jsx +354 -0
- package/registry/blocks/org-chart/README.md +145 -0
- package/registry/blocks/org-chart/block.json +10 -0
- package/registry/blocks/org-chart/demo.jsx +94 -0
- package/registry/blocks/org-chart/index.js +7 -0
- package/registry/blocks/org-chart/org-chart.jsx +631 -0
- package/registry/blocks/org-chart/org.js +245 -0
- package/registry/blocks/org-chart/person.jsx +96 -0
- package/registry/blocks/page-builder/README.md +257 -0
- package/registry/blocks/page-builder/block.json +14 -0
- package/registry/blocks/page-builder/demo.jsx +145 -0
- package/registry/blocks/page-builder/grapes.js +18 -0
- package/registry/blocks/page-builder/index.js +11 -0
- package/registry/blocks/page-builder/page-builder.jsx +704 -0
- package/registry/blocks/page-builder/page-view.jsx +33 -0
- package/registry/blocks/page-builder/page.js +118 -0
- package/registry/blocks/page-builder/panels.jsx +588 -0
- package/registry/blocks/page-builder/sections.js +209 -0
- package/registry/blocks/page-builder/theme.js +87 -0
- package/registry/blocks/page-builder/thumbs.jsx +189 -0
- package/registry/blocks/page-builder/tokens.js +329 -0
- package/registry/blocks/payment-methods/README.md +104 -0
- package/registry/blocks/payment-methods/block.json +10 -0
- package/registry/blocks/payment-methods/demo.jsx +71 -0
- package/registry/blocks/payment-methods/index.js +4 -0
- package/registry/blocks/payment-methods/payment-methods.jsx +302 -0
- package/registry/blocks/pdf-document/README.md +213 -0
- package/registry/blocks/pdf-document/block.json +13 -0
- package/registry/blocks/pdf-document/demo.jsx +136 -0
- package/registry/blocks/pdf-document/index.js +33 -0
- package/registry/blocks/pdf-document/money.js +83 -0
- package/registry/blocks/pdf-document/pdf-document.jsx +144 -0
- package/registry/blocks/pdf-document/primitives.jsx +271 -0
- package/registry/blocks/pdf-document/quote.jsx +179 -0
- package/registry/blocks/pdf-document/theme.js +193 -0
- package/registry/blocks/pdf-viewer/README.md +191 -0
- package/registry/blocks/pdf-viewer/block.json +14 -0
- package/registry/blocks/pdf-viewer/demo.jsx +252 -0
- package/registry/blocks/pdf-viewer/helpers.js +270 -0
- package/registry/blocks/pdf-viewer/index.js +8 -0
- package/registry/blocks/pdf-viewer/notes.jsx +195 -0
- package/registry/blocks/pdf-viewer/pdf-viewer.jsx +1368 -0
- package/registry/blocks/pdf-viewer/pdfjs.js +37 -0
- package/registry/blocks/pdf-viewer/ssr-shim-end.js +6 -0
- package/registry/blocks/pdf-viewer/ssr-shim.js +44 -0
- package/registry/blocks/product-detail/README.md +217 -0
- package/registry/blocks/product-detail/block.json +10 -0
- package/registry/blocks/product-detail/demo.jsx +233 -0
- package/registry/blocks/product-detail/index.js +7 -0
- package/registry/blocks/product-detail/price.jsx +115 -0
- package/registry/blocks/product-detail/product-detail.jsx +348 -0
- package/registry/blocks/product-detail/product-media.jsx +102 -0
- package/registry/blocks/product-detail/product-options.jsx +144 -0
- package/registry/blocks/product-grid/README.md +162 -0
- package/registry/blocks/product-grid/block.json +10 -0
- package/registry/blocks/product-grid/demo.jsx +255 -0
- package/registry/blocks/product-grid/index.js +5 -0
- package/registry/blocks/product-grid/product-card.jsx +232 -0
- package/registry/blocks/product-grid/product-grid.jsx +235 -0
- package/registry/blocks/record-form/README.md +196 -0
- package/registry/blocks/record-form/block.json +10 -0
- package/registry/blocks/record-form/demo.jsx +525 -0
- package/registry/blocks/record-form/index.js +4 -0
- package/registry/blocks/record-form/record-fields.jsx +457 -0
- package/registry/blocks/record-form/record-form.jsx +415 -0
- package/registry/blocks/record-form/rules.js +81 -0
- package/registry/blocks/record-notes/README.md +139 -0
- package/registry/blocks/record-notes/block.json +10 -0
- package/registry/blocks/record-notes/demo.jsx +179 -0
- package/registry/blocks/record-notes/index.js +4 -0
- package/registry/blocks/record-notes/record-notes.jsx +266 -0
- package/registry/blocks/search-dialog/README.md +108 -0
- package/registry/blocks/search-dialog/block.json +10 -0
- package/registry/blocks/search-dialog/demo.jsx +116 -0
- package/registry/blocks/search-dialog/index.js +3 -0
- package/registry/blocks/search-dialog/search-dialog.jsx +235 -0
- package/registry/blocks/settings-list/README.md +171 -0
- package/registry/blocks/settings-list/block.json +10 -0
- package/registry/blocks/settings-list/demo.jsx +333 -0
- package/registry/blocks/settings-list/index.js +4 -0
- package/registry/blocks/settings-list/row-state.jsx +93 -0
- package/registry/blocks/settings-list/settings-list.jsx +349 -0
- package/registry/blocks/settings-list/settings-matrix.jsx +118 -0
- package/registry/blocks/setup-checklist/README.md +142 -0
- package/registry/blocks/setup-checklist/block.json +10 -0
- package/registry/blocks/setup-checklist/demo.jsx +159 -0
- package/registry/blocks/setup-checklist/index.js +4 -0
- package/registry/blocks/setup-checklist/setup-checklist.jsx +446 -0
- package/registry/blocks/share-dialog/README.md +113 -0
- package/registry/blocks/share-dialog/block.json +10 -0
- package/registry/blocks/share-dialog/demo.jsx +140 -0
- package/registry/blocks/share-dialog/index.js +3 -0
- package/registry/blocks/share-dialog/share-dialog.jsx +384 -0
- package/registry/blocks/sidebar/README.md +190 -0
- package/registry/blocks/sidebar/block.json +43 -0
- package/registry/blocks/sidebar/demo.jsx +577 -0
- package/registry/blocks/sidebar/index.js +4 -0
- package/registry/blocks/sidebar/sidebar-panel.jsx +149 -0
- package/registry/blocks/sidebar/sidebar.jsx +864 -0
- package/registry/blocks/signature-pad/README.md +137 -0
- package/registry/blocks/signature-pad/block.json +13 -0
- package/registry/blocks/signature-pad/colors.js +115 -0
- package/registry/blocks/signature-pad/demo.jsx +94 -0
- package/registry/blocks/signature-pad/index.js +6 -0
- package/registry/blocks/signature-pad/pad.js +7 -0
- package/registry/blocks/signature-pad/signature-pad.jsx +434 -0
- package/registry/blocks/signature-pad/signature.js +68 -0
- package/registry/blocks/sketch-canvas/README.md +280 -0
- package/registry/blocks/sketch-canvas/block.json +13 -0
- package/registry/blocks/sketch-canvas/demo.jsx +159 -0
- package/registry/blocks/sketch-canvas/excalidraw.js +40 -0
- package/registry/blocks/sketch-canvas/index.js +9 -0
- package/registry/blocks/sketch-canvas/palette.js +226 -0
- package/registry/blocks/sketch-canvas/sketch-canvas.jsx +1188 -0
- package/registry/blocks/sketch-canvas/sketch.js +179 -0
- package/registry/blocks/sketch-canvas/ssr-shim-end.js +6 -0
- package/registry/blocks/sketch-canvas/ssr-shim.js +45 -0
- package/registry/blocks/sketch-canvas/stamps.js +104 -0
- package/registry/blocks/sketch-canvas/theme.js +131 -0
- package/registry/blocks/spreadsheet/README.md +307 -0
- package/registry/blocks/spreadsheet/block.json +13 -0
- package/registry/blocks/spreadsheet/cells.jsx +455 -0
- package/registry/blocks/spreadsheet/chrome.jsx +325 -0
- package/registry/blocks/spreadsheet/demo.jsx +177 -0
- package/registry/blocks/spreadsheet/grid.js +387 -0
- package/registry/blocks/spreadsheet/index.js +34 -0
- package/registry/blocks/spreadsheet/spreadsheet.jsx +1732 -0
- package/registry/blocks/spreadsheet/types.js +473 -0
- package/registry/blocks/stat-cards/README.md +173 -0
- package/registry/blocks/stat-cards/block.json +10 -0
- package/registry/blocks/stat-cards/demo.jsx +257 -0
- package/registry/blocks/stat-cards/index.js +4 -0
- package/registry/blocks/stat-cards/stat-cards.jsx +307 -0
- package/registry/blocks/stat-cards/usage.jsx +243 -0
- package/registry/blocks/task-list/README.md +176 -0
- package/registry/blocks/task-list/block.json +10 -0
- package/registry/blocks/task-list/demo.jsx +134 -0
- package/registry/blocks/task-list/index.js +5 -0
- package/registry/blocks/task-list/task-list.jsx +907 -0
- package/registry/blocks/task-model/README.md +455 -0
- package/registry/blocks/task-model/block.json +167 -0
- package/registry/blocks/task-model/changes.js +89 -0
- package/registry/blocks/task-model/dates.js +371 -0
- package/registry/blocks/task-model/demo.jsx +215 -0
- package/registry/blocks/task-model/dnd.jsx +240 -0
- package/registry/blocks/task-model/index.js +97 -0
- package/registry/blocks/task-model/model.js +919 -0
- package/registry/blocks/task-model/parts.jsx +449 -0
- package/registry/blocks/task-model/toolbar.jsx +271 -0
- package/registry/blocks/testimonials/README.md +139 -0
- package/registry/blocks/testimonials/block.json +11 -0
- package/registry/blocks/testimonials/demo.jsx +137 -0
- package/registry/blocks/testimonials/index.js +4 -0
- package/registry/blocks/testimonials/testimonials.jsx +430 -0
- package/registry/blocks/transaction-list/README.md +126 -0
- package/registry/blocks/transaction-list/block.json +10 -0
- package/registry/blocks/transaction-list/demo.jsx +79 -0
- package/registry/blocks/transaction-list/index.js +4 -0
- package/registry/blocks/transaction-list/transaction-list.jsx +383 -0
- package/registry/blocks/whiteboard/README.md +179 -0
- package/registry/blocks/whiteboard/block.json +10 -0
- package/registry/blocks/whiteboard/demo.jsx +169 -0
- package/registry/blocks/whiteboard/index.js +6 -0
- package/registry/blocks/whiteboard/templates.js +150 -0
- package/registry/blocks/whiteboard/whiteboard.jsx +721 -0
- package/src/cli.mjs +19 -6
- package/src/install.mjs +18 -0
- package/src/registry.mjs +38 -4
- package/src/shadcn.mjs +14 -1
- package/templates/next/files/README.md +145 -11
- package/templates/next/files/app/globals.css +55 -4
- package/templates/next/files/check/run.mjs +3 -3
- package/templates/next/files/components/ui/avatar.jsx +141 -0
- package/templates/next/files/components/ui/breadcrumb.jsx +149 -0
- package/templates/next/files/components/ui/button-group.jsx +70 -0
- package/templates/next/files/components/ui/button.jsx +132 -12
- package/templates/next/files/components/ui/calendar.jsx +609 -0
- package/templates/next/files/components/ui/carousel.jsx +281 -0
- package/templates/next/files/components/ui/collapsible.jsx +208 -0
- package/templates/next/files/components/ui/combobox.jsx +608 -0
- package/templates/next/files/components/ui/command.jsx +577 -0
- package/templates/next/files/components/ui/context-menu.jsx +191 -0
- package/templates/next/files/components/ui/date-picker.jsx +360 -0
- package/templates/next/files/components/ui/distribution.jsx +117 -0
- package/templates/next/files/components/ui/feedback.jsx +143 -0
- package/templates/next/files/components/ui/field.jsx +115 -3
- package/templates/next/files/components/ui/file-input.jsx +234 -0
- package/templates/next/files/components/ui/icon.jsx +130 -8
- package/templates/next/files/components/ui/image.jsx +143 -0
- package/templates/next/files/components/ui/index.js +47 -2
- package/templates/next/files/components/ui/input-group.jsx +213 -0
- package/templates/next/files/components/ui/input-otp.jsx +170 -0
- package/templates/next/files/components/ui/item.jsx +206 -0
- package/templates/next/files/components/ui/menu.jsx +454 -82
- package/templates/next/files/components/ui/notice.jsx +126 -14
- package/templates/next/files/components/ui/pagination.jsx +156 -0
- package/templates/next/files/components/ui/popover.jsx +284 -0
- package/templates/next/files/components/ui/radio.jsx +193 -0
- package/templates/next/files/components/ui/rating.jsx +131 -0
- package/templates/next/files/components/ui/resizable.jsx +338 -0
- package/templates/next/files/components/ui/scroll-area.jsx +123 -0
- package/templates/next/files/components/ui/separator.jsx +119 -0
- package/templates/next/files/components/ui/sheet.jsx +96 -11
- package/templates/next/files/components/ui/slider.jsx +174 -0
- package/templates/next/files/components/ui/stat.jsx +574 -0
- package/templates/next/files/components/ui/steps.jsx +338 -0
- package/templates/next/files/components/ui/swatch.jsx +33 -0
- package/templates/next/files/components/ui/table.jsx +130 -0
- package/templates/next/files/components/ui/toast.jsx +440 -0
- package/templates/next/files/components/ui/toggle.jsx +195 -0
- package/templates/next/files/components/ui/tooltip.jsx +270 -0
- package/templates/next/template.json +264 -8
|
@@ -0,0 +1,819 @@
|
|
|
1
|
+
// The board as data — plain functions, no React, no React Flow. They run
|
|
2
|
+
// anywhere (the server, a script, an agent's tool) and never throw on a
|
|
3
|
+
// partial board. FlowCanvas uses the same functions, so what an agent
|
|
4
|
+
// reads with boardToText is what the person sees.
|
|
5
|
+
//
|
|
6
|
+
// A board is plain JSON an app stores in one jsonb column:
|
|
7
|
+
// { nodes: [{ id, type, position: { x, y }, width?, height?, data, zIndex?,
|
|
8
|
+
// draggable?, deletable?, selectable?, connectable? }],
|
|
9
|
+
// edges: [{ id, source, target, label?, data?: { line, arrow, dashed } }],
|
|
10
|
+
// viewport?: { x, y, zoom } }
|
|
11
|
+
// Positions are absolute (a card in a frame is NOT stored relative to it):
|
|
12
|
+
// a frame holds whatever lies inside its rectangle.
|
|
13
|
+
|
|
14
|
+
export const GRID = 20;
|
|
15
|
+
|
|
16
|
+
/** Colours a person can pick, as TOKEN names — the board stores these, never
|
|
17
|
+
* a colour value, so a board repaints with the workspace theme. */
|
|
18
|
+
export const COLORS = ["warning", "chart-1", "chart-2", "chart-3", "chart-4", "chart-5", "success", "info", "destructive", "muted"];
|
|
19
|
+
/** The sticky palette the rail and the toolbar offer by default. */
|
|
20
|
+
export const STICKY_COLORS = ["warning", "chart-2", "chart-3", "chart-4", "chart-5", "muted"];
|
|
21
|
+
export const COLOR_NAMES = {
|
|
22
|
+
warning: "Yellow",
|
|
23
|
+
"chart-1": "Theme colour",
|
|
24
|
+
"chart-2": "Colour 2",
|
|
25
|
+
"chart-3": "Colour 3",
|
|
26
|
+
"chart-4": "Colour 4",
|
|
27
|
+
"chart-5": "Colour 5",
|
|
28
|
+
success: "Green",
|
|
29
|
+
info: "Blue",
|
|
30
|
+
destructive: "Red",
|
|
31
|
+
muted: "Grey",
|
|
32
|
+
};
|
|
33
|
+
// The chart tokens are derived from each workspace's primary colour, so
|
|
34
|
+
// they get numbers, not hue names: "Colour 3" is violet in one theme and
|
|
35
|
+
// orange in another. Labels only; the board stores the token.
|
|
36
|
+
|
|
37
|
+
export const SHAPES = ["rect", "ellipse", "diamond", "pill"];
|
|
38
|
+
export const SHAPE_NAMES = { rect: "Rectangle", ellipse: "Ellipse", diamond: "Diamond", pill: "Pill" };
|
|
39
|
+
export const TEXT_SIZES = ["sm", "md", "lg", "xl"];
|
|
40
|
+
export const TEXT_SIZE_PX = { sm: 14, md: 18, lg: 24, xl: 36 };
|
|
41
|
+
export const LINES = ["curved", "straight", "elbow"];
|
|
42
|
+
export const ARROWS = ["end", "both", "none"];
|
|
43
|
+
|
|
44
|
+
/** Default size per built-in type. `height: undefined` grows with content. */
|
|
45
|
+
export const SIZES = {
|
|
46
|
+
sticky: { width: 200, height: 200 },
|
|
47
|
+
shape: { width: 160, height: 100 },
|
|
48
|
+
text: { width: 240, height: undefined },
|
|
49
|
+
frame: { width: 560, height: 360 },
|
|
50
|
+
card: { width: 260, height: undefined },
|
|
51
|
+
image: { width: 280, height: 180 },
|
|
52
|
+
};
|
|
53
|
+
const SHAPE_SIZES = { rect: [160, 100], ellipse: [160, 110], diamond: [160, 120], pill: [180, 64] };
|
|
54
|
+
/** Types whose height follows their content unless someone resized them. */
|
|
55
|
+
export const AUTO_HEIGHT = new Set(["text", "card"]);
|
|
56
|
+
/** Types whose words edit in place on double-click / Enter. */
|
|
57
|
+
export const EDITABLE = new Set(["sticky", "shape", "text", "frame", "card"]);
|
|
58
|
+
|
|
59
|
+
const isObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
60
|
+
const num = (v, d = 0) => (typeof v === "number" && Number.isFinite(v) ? v : d);
|
|
61
|
+
|
|
62
|
+
let seq = 0;
|
|
63
|
+
/** A fresh id: `sticky-lx3k9f2a`. Unique enough for a board. */
|
|
64
|
+
export function makeId(prefix = "n") {
|
|
65
|
+
seq = (seq + 1) % 1296;
|
|
66
|
+
return `${prefix}-${Date.now().toString(36)}${seq.toString(36).padStart(2, "0")}${Math.floor(Math.random() * 1296)
|
|
67
|
+
.toString(36)
|
|
68
|
+
.padStart(2, "0")}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** An empty board. */
|
|
72
|
+
export function emptyBoard() {
|
|
73
|
+
return { nodes: [], edges: [] };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Nodes and edges out of anything board-shaped; never throws. */
|
|
77
|
+
export function readBoard(board) {
|
|
78
|
+
const nodes = Array.isArray(board?.nodes) ? board.nodes.filter((n) => isObj(n) && n.id != null) : [];
|
|
79
|
+
const ids = new Set(nodes.map((n) => String(n.id)));
|
|
80
|
+
const edges = Array.isArray(board?.edges)
|
|
81
|
+
? board.edges.filter((e) => isObj(e) && e.id != null && ids.has(String(e.source)) && ids.has(String(e.target)))
|
|
82
|
+
: [];
|
|
83
|
+
return { nodes, edges, viewport: isObj(board?.viewport) ? board.viewport : undefined };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const NODE_KEYS = ["id", "type", "position", "width", "height", "data", "zIndex", "draggable", "deletable", "selectable", "connectable"];
|
|
87
|
+
const EDGE_KEYS = ["id", "source", "target", "label", "data"];
|
|
88
|
+
|
|
89
|
+
/** The board as it is stored: React Flow's working fields (selected,
|
|
90
|
+
* dragging, measured…) dropped, and nothing but JSON left. */
|
|
91
|
+
export function cleanBoard(board, { viewport = true } = {}) {
|
|
92
|
+
const { nodes, edges, viewport: vp } = readBoard(board);
|
|
93
|
+
const out = {
|
|
94
|
+
nodes: nodes.map((n) => {
|
|
95
|
+
const o = {};
|
|
96
|
+
for (const k of NODE_KEYS) if (n[k] !== undefined && n[k] !== null) o[k] = n[k];
|
|
97
|
+
o.position = { x: Math.round(num(n.position?.x) * 100) / 100, y: Math.round(num(n.position?.y) * 100) / 100 };
|
|
98
|
+
if (typeof o.width === "number") o.width = Math.round(o.width);
|
|
99
|
+
if (typeof o.height === "number") o.height = Math.round(o.height);
|
|
100
|
+
o.data = isObj(n.data) ? n.data : {};
|
|
101
|
+
return o;
|
|
102
|
+
}),
|
|
103
|
+
edges: edges.map((e) => {
|
|
104
|
+
const o = {};
|
|
105
|
+
for (const k of EDGE_KEYS) if (e[k] !== undefined && e[k] !== null && e[k] !== "") o[k] = e[k];
|
|
106
|
+
return o;
|
|
107
|
+
}),
|
|
108
|
+
};
|
|
109
|
+
if (viewport && vp) out.viewport = { x: num(vp.x), y: num(vp.y), zoom: num(vp.zoom, 1) };
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** A string that changes when the board's content changes (not the view). */
|
|
114
|
+
export function boardKey(board) {
|
|
115
|
+
const { nodes, edges } = cleanBoard(board, { viewport: false });
|
|
116
|
+
return JSON.stringify([nodes, edges]);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── geometry ─────────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
/** A node's size: set, measured by React Flow, or its type's default. */
|
|
122
|
+
export function nodeSize(node) {
|
|
123
|
+
const d = SIZES[node?.type] ?? { width: 200, height: 120 };
|
|
124
|
+
const width = num(node?.width, num(node?.measured?.width, d.width ?? 200));
|
|
125
|
+
const height = num(node?.height, num(node?.measured?.height, d.height ?? (node?.type === "text" ? 40 : 120)));
|
|
126
|
+
return { width, height };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** { x, y, width, height } in board coordinates. */
|
|
130
|
+
export function nodeRect(node) {
|
|
131
|
+
const { width, height } = nodeSize(node);
|
|
132
|
+
return { x: num(node?.position?.x), y: num(node?.position?.y), width, height };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const center = (r) => ({ x: r.x + r.width / 2, y: r.y + r.height / 2 });
|
|
136
|
+
const inside = (p, r) => p.x >= r.x && p.x <= r.x + r.width && p.y >= r.y && p.y <= r.y + r.height;
|
|
137
|
+
|
|
138
|
+
/** The rectangle around some nodes, or null. */
|
|
139
|
+
export function boardBounds(nodes) {
|
|
140
|
+
const list = (nodes ?? []).filter(Boolean);
|
|
141
|
+
if (!list.length) return null;
|
|
142
|
+
let x1 = Infinity;
|
|
143
|
+
let y1 = Infinity;
|
|
144
|
+
let x2 = -Infinity;
|
|
145
|
+
let y2 = -Infinity;
|
|
146
|
+
for (const n of list) {
|
|
147
|
+
const r = nodeRect(n);
|
|
148
|
+
x1 = Math.min(x1, r.x);
|
|
149
|
+
y1 = Math.min(y1, r.y);
|
|
150
|
+
x2 = Math.max(x2, r.x + r.width);
|
|
151
|
+
y2 = Math.max(y2, r.y + r.height);
|
|
152
|
+
}
|
|
153
|
+
return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The ids of the nodes inside a frame: their centre lies in its rectangle.
|
|
157
|
+
* Frames nested in it count, and so does what they hold. */
|
|
158
|
+
export function frameContents(board, frameId) {
|
|
159
|
+
const { nodes } = readBoard(board);
|
|
160
|
+
const frame = nodes.find((n) => n.id === frameId);
|
|
161
|
+
if (!frame) return [];
|
|
162
|
+
const r = nodeRect(frame);
|
|
163
|
+
return nodes.filter((n) => n.id !== frameId && inside(center(nodeRect(n)), r) && area(nodeRect(n)) < area(r)).map((n) => n.id);
|
|
164
|
+
}
|
|
165
|
+
const area = (r) => r.width * r.height;
|
|
166
|
+
|
|
167
|
+
/** The smallest frame each node sits in: Map(nodeId → frameId). */
|
|
168
|
+
export function frameOf(board) {
|
|
169
|
+
const { nodes } = readBoard(board);
|
|
170
|
+
const frames = nodes.filter((n) => n.type === "frame").map((f) => ({ id: f.id, r: nodeRect(f) }));
|
|
171
|
+
const out = new Map();
|
|
172
|
+
for (const n of nodes) {
|
|
173
|
+
const c = center(nodeRect(n));
|
|
174
|
+
const a = area(nodeRect(n));
|
|
175
|
+
let best = null;
|
|
176
|
+
for (const f of frames) {
|
|
177
|
+
if (f.id === n.id || !inside(c, f.r) || area(f.r) <= a) continue;
|
|
178
|
+
if (!best || area(f.r) < area(best.r)) best = f;
|
|
179
|
+
}
|
|
180
|
+
if (best) out.set(n.id, best.id);
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Round a point onto the grid. */
|
|
186
|
+
export function snapPoint(p, grid = GRID) {
|
|
187
|
+
if (!grid) return { x: p.x, y: p.y };
|
|
188
|
+
return { x: Math.round(p.x / grid) * grid, y: Math.round(p.y / grid) * grid };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Put a rectangle into a cell — for boards with lanes or a grid (a journey
|
|
193
|
+
* map's stage × row). `cells` is [{ id, x, y, width, height }]. The cell
|
|
194
|
+
* is the one holding the rectangle's centre, else the nearest; the
|
|
195
|
+
* rectangle is kept inside it with `pad` to spare. Returns
|
|
196
|
+
* { x, y, cellId } or null when there are no cells.
|
|
197
|
+
*/
|
|
198
|
+
export function snapToCell(rect, cells, { pad = 8 } = {}) {
|
|
199
|
+
const list = (cells ?? []).filter((c) => c && num(c.width) > 0 && num(c.height) > 0);
|
|
200
|
+
if (!list.length) return null;
|
|
201
|
+
const c = center(rect);
|
|
202
|
+
let cell = list.find((k) => inside(c, k));
|
|
203
|
+
if (!cell) {
|
|
204
|
+
let best = Infinity;
|
|
205
|
+
for (const k of list) {
|
|
206
|
+
const dx = Math.max(k.x - c.x, 0, c.x - (k.x + k.width));
|
|
207
|
+
const dy = Math.max(k.y - c.y, 0, c.y - (k.y + k.height));
|
|
208
|
+
const d = dx * dx + dy * dy;
|
|
209
|
+
if (d < best) {
|
|
210
|
+
best = d;
|
|
211
|
+
cell = k;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const clamp = (v, lo, hi) => (hi < lo ? (lo + hi) / 2 : Math.min(Math.max(v, lo), hi));
|
|
216
|
+
return {
|
|
217
|
+
x: clamp(rect.x, cell.x + pad, cell.x + cell.width - pad - rect.width),
|
|
218
|
+
y: clamp(rect.y, cell.y + pad, cell.y + cell.height - pad - rect.height),
|
|
219
|
+
cellId: cell.id,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Where a connector leaves and enters: the sides of two rectangles that
|
|
224
|
+
* face each other. → { source: "right", target: "left" } */
|
|
225
|
+
export function facingSides(a, b) {
|
|
226
|
+
const ca = center(a);
|
|
227
|
+
const cb = center(b);
|
|
228
|
+
const dx = cb.x - ca.x;
|
|
229
|
+
const dy = cb.y - ca.y;
|
|
230
|
+
const horizontal = Math.abs(dx) / Math.max(1, a.width + b.width) >= Math.abs(dy) / Math.max(1, a.height + b.height);
|
|
231
|
+
if (horizontal) return dx >= 0 ? { source: "right", target: "left" } : { source: "left", target: "right" };
|
|
232
|
+
return dy >= 0 ? { source: "bottom", target: "top" } : { source: "top", target: "bottom" };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** The midpoint of one side of a rectangle. */
|
|
236
|
+
export function sidePoint(r, side) {
|
|
237
|
+
if (side === "left") return { x: r.x, y: r.y + r.height / 2 };
|
|
238
|
+
if (side === "right") return { x: r.x + r.width, y: r.y + r.height / 2 };
|
|
239
|
+
if (side === "top") return { x: r.x + r.width / 2, y: r.y };
|
|
240
|
+
return { x: r.x + r.width / 2, y: r.y + r.height };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* The biggest font size (px) at which `text` fits a box — a sticky's words
|
|
245
|
+
* shrink as they grow. An estimate (no DOM): average glyph width `charWidth`
|
|
246
|
+
* em, lines wrap at words.
|
|
247
|
+
*/
|
|
248
|
+
export function fitFontSize(text, width, height, { min = 12, max = 30, lineHeight = 1.25, charWidth = 0.56 } = {}) {
|
|
249
|
+
const words = String(text ?? "").trim();
|
|
250
|
+
if (!words) return max;
|
|
251
|
+
const paras = words.split(/\n/);
|
|
252
|
+
const fits = (s) => {
|
|
253
|
+
const perLine = Math.max(1, Math.floor(width / (s * charWidth)));
|
|
254
|
+
let lines = 0;
|
|
255
|
+
for (const p of paras) {
|
|
256
|
+
let line = 0;
|
|
257
|
+
let count = 1;
|
|
258
|
+
for (const w of p.split(/\s+/).filter(Boolean)) {
|
|
259
|
+
if (w.length > perLine) {
|
|
260
|
+
// A long word breaks across lines.
|
|
261
|
+
const extra = Math.ceil(w.length / perLine);
|
|
262
|
+
count += line ? extra : extra - 1;
|
|
263
|
+
line = w.length % perLine;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const need = line ? line + 1 + w.length : w.length;
|
|
267
|
+
if (need > perLine) {
|
|
268
|
+
count += 1;
|
|
269
|
+
line = w.length;
|
|
270
|
+
} else line = need;
|
|
271
|
+
}
|
|
272
|
+
lines += count;
|
|
273
|
+
}
|
|
274
|
+
return lines * s * lineHeight <= height;
|
|
275
|
+
};
|
|
276
|
+
let lo = min;
|
|
277
|
+
let hi = max;
|
|
278
|
+
if (fits(hi)) return hi;
|
|
279
|
+
if (!fits(lo)) return lo;
|
|
280
|
+
while (hi - lo > 0.5) {
|
|
281
|
+
const mid = (lo + hi) / 2;
|
|
282
|
+
if (fits(mid)) lo = mid;
|
|
283
|
+
else hi = mid;
|
|
284
|
+
}
|
|
285
|
+
return Math.floor(lo);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ── making and changing nodes ────────────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
const DATA = {
|
|
291
|
+
sticky: () => ({ text: "", color: "warning" }),
|
|
292
|
+
shape: () => ({ text: "", shape: "rect", color: "muted", textSize: "md" }),
|
|
293
|
+
text: () => ({ text: "", textSize: "md" }),
|
|
294
|
+
frame: () => ({ title: "Frame" }),
|
|
295
|
+
card: () => ({ title: "", body: "" }),
|
|
296
|
+
image: () => ({ src: "", alt: "" }),
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* A new node CENTRED on `point`, snapped to the grid, with its type's
|
|
301
|
+
* defaults (size, data). Custom types take `width`/`height` from opts
|
|
302
|
+
* (default 200×120).
|
|
303
|
+
*
|
|
304
|
+
* createNode("sticky", { x: 400, y: 200 }, { data: { text: "Idea", color: "chart-3" } })
|
|
305
|
+
* createNode("person", p, { width: 240, data: { name: "New person" } })
|
|
306
|
+
*
|
|
307
|
+
* With `rect` ({ x, y, width, height }) it fills that rectangle instead
|
|
308
|
+
* (a shape dragged out with the tool).
|
|
309
|
+
*/
|
|
310
|
+
export function createNode(type, point, { id, data, width, height, rect, grid = GRID, ...rest } = {}) {
|
|
311
|
+
const base = SIZES[type] ?? { width: 200, height: 120 };
|
|
312
|
+
const kind = type === "shape" ? data?.shape ?? "rect" : null;
|
|
313
|
+
const [sw, sh] = kind ? SHAPE_SIZES[kind] ?? SHAPE_SIZES.rect : [];
|
|
314
|
+
let w = num(rect?.width, num(width, sw ?? base.width));
|
|
315
|
+
let h = rect ? num(rect.height) : height !== undefined ? height : sh ?? base.height;
|
|
316
|
+
if (AUTO_HEIGHT.has(type) && !rect && height === undefined) h = undefined;
|
|
317
|
+
const at = rect ? { x: rect.x, y: rect.y } : { x: num(point?.x) - w / 2, y: num(point?.y) - num(h, 40) / 2 };
|
|
318
|
+
const node = {
|
|
319
|
+
id: id ?? makeId(type),
|
|
320
|
+
type,
|
|
321
|
+
position: snapPoint(at, grid),
|
|
322
|
+
data: { ...(DATA[type]?.() ?? {}), ...(data ?? {}) },
|
|
323
|
+
...rest,
|
|
324
|
+
};
|
|
325
|
+
if (w !== undefined) node.width = grid ? Math.max(grid, Math.round(w / grid) * grid) : w;
|
|
326
|
+
if (h !== undefined) node.height = grid ? Math.max(grid, Math.round(h / grid) * grid) : h;
|
|
327
|
+
if (type === "frame" && node.zIndex === undefined) node.zIndex = -1;
|
|
328
|
+
return node;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** A connector. `data`: { line: curved|straight|elbow, arrow: end|both|none, dashed }. */
|
|
332
|
+
export function createEdge(source, target, { id, label, data } = {}) {
|
|
333
|
+
const e = { id: id ?? makeId("e"), source, target, type: "flow", data: { line: "curved", arrow: "end", ...(data ?? {}) } };
|
|
334
|
+
if (label) e.label = label;
|
|
335
|
+
return e;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Copies of some nodes (and the connectors between them), moved by
|
|
340
|
+
* `offset`, with new ids. Frames bring what they hold. Returns
|
|
341
|
+
* { nodes, edges } — only the new ones.
|
|
342
|
+
*/
|
|
343
|
+
export function duplicateNodes(board, ids, { offset = { x: GRID * 2, y: GRID * 2 } } = {}) {
|
|
344
|
+
const { nodes, edges } = readBoard(board);
|
|
345
|
+
const want = new Set(ids);
|
|
346
|
+
for (const id of ids) {
|
|
347
|
+
const n = nodes.find((x) => x.id === id);
|
|
348
|
+
if (n?.type === "frame") for (const c of frameContents(board, id)) want.add(c);
|
|
349
|
+
}
|
|
350
|
+
const map = new Map();
|
|
351
|
+
const out = [];
|
|
352
|
+
for (const n of nodes) {
|
|
353
|
+
if (!want.has(n.id)) continue;
|
|
354
|
+
const id = makeId(n.type || "n");
|
|
355
|
+
map.set(n.id, id);
|
|
356
|
+
const { selected, dragging, measured, ...rest } = n;
|
|
357
|
+
const copy = { ...rest, id, position: { x: num(n.position?.x) + offset.x, y: num(n.position?.y) + offset.y }, data: structuredCopy(n.data) };
|
|
358
|
+
if (copy.data?.locked) {
|
|
359
|
+
delete copy.data.locked;
|
|
360
|
+
delete copy.draggable;
|
|
361
|
+
delete copy.deletable;
|
|
362
|
+
}
|
|
363
|
+
out.push(copy);
|
|
364
|
+
}
|
|
365
|
+
const outEdges = edges
|
|
366
|
+
.filter((e) => map.has(e.source) && map.has(e.target))
|
|
367
|
+
.map((e) => ({ ...e, id: makeId("e"), source: map.get(e.source), target: map.get(e.target), selected: undefined }));
|
|
368
|
+
return { nodes: out, edges: outEdges };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function structuredCopy(v) {
|
|
372
|
+
try {
|
|
373
|
+
return JSON.parse(JSON.stringify(v ?? {}));
|
|
374
|
+
} catch {
|
|
375
|
+
return { ...(v ?? {}) };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** The board without some nodes and every connector that touched them. */
|
|
380
|
+
export function removeNodes(board, ids) {
|
|
381
|
+
const { nodes, edges } = readBoard(board);
|
|
382
|
+
const gone = new Set(ids);
|
|
383
|
+
return { ...board, nodes: nodes.filter((n) => !gone.has(n.id)), edges: edges.filter((e) => !gone.has(e.source) && !gone.has(e.target)) };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Line up nodes: how = left | center | right | top | middle | bottom.
|
|
388
|
+
* Returns the whole node list with those moved.
|
|
389
|
+
*/
|
|
390
|
+
export function alignNodes(nodes, ids, how) {
|
|
391
|
+
const set = new Set(ids);
|
|
392
|
+
const pick = (nodes ?? []).filter((n) => set.has(n.id));
|
|
393
|
+
const b = boardBounds(pick);
|
|
394
|
+
if (!b || pick.length < 2) return nodes;
|
|
395
|
+
return nodes.map((n) => {
|
|
396
|
+
if (!set.has(n.id) || n.data?.locked) return n;
|
|
397
|
+
const r = nodeRect(n);
|
|
398
|
+
const p = { ...n.position };
|
|
399
|
+
if (how === "left") p.x = b.x;
|
|
400
|
+
else if (how === "center") p.x = b.x + b.width / 2 - r.width / 2;
|
|
401
|
+
else if (how === "right") p.x = b.x + b.width - r.width;
|
|
402
|
+
else if (how === "top") p.y = b.y;
|
|
403
|
+
else if (how === "middle") p.y = b.y + b.height / 2 - r.height / 2;
|
|
404
|
+
else if (how === "bottom") p.y = b.y + b.height - r.height;
|
|
405
|
+
return { ...n, position: p };
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** Even gaps between nodes along an axis: "horizontal" | "vertical". */
|
|
410
|
+
export function distributeNodes(nodes, ids, axis = "horizontal") {
|
|
411
|
+
const set = new Set(ids);
|
|
412
|
+
const h = axis === "horizontal";
|
|
413
|
+
const pick = (nodes ?? []).filter((n) => set.has(n.id)).map((n) => ({ n, r: nodeRect(n) }));
|
|
414
|
+
if (pick.length < 3) return nodes;
|
|
415
|
+
pick.sort((a, b) => (h ? a.r.x - b.r.x : a.r.y - b.r.y));
|
|
416
|
+
const first = pick[0].r;
|
|
417
|
+
const last = pick[pick.length - 1].r;
|
|
418
|
+
const span = h ? last.x + last.width - first.x : last.y + last.height - first.y;
|
|
419
|
+
const used = pick.reduce((s, p) => s + (h ? p.r.width : p.r.height), 0);
|
|
420
|
+
const gap = (span - used) / (pick.length - 1);
|
|
421
|
+
const at = new Map();
|
|
422
|
+
let cursor = h ? first.x : first.y;
|
|
423
|
+
for (const p of pick) {
|
|
424
|
+
at.set(p.n.id, cursor);
|
|
425
|
+
cursor += (h ? p.r.width : p.r.height) + gap;
|
|
426
|
+
}
|
|
427
|
+
return nodes.map((n) => (at.has(n.id) && !n.data?.locked ? { ...n, position: h ? { ...n.position, x: at.get(n.id) } : { ...n.position, y: at.get(n.id) } } : n));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** Arrange: move nodes to the end (front) or start (back) of the list.
|
|
431
|
+
* Paint order is list order; frames always sit under everything. */
|
|
432
|
+
export function arrangeNodes(nodes, ids, where = "front") {
|
|
433
|
+
const set = new Set(ids);
|
|
434
|
+
const moved = nodes.filter((n) => set.has(n.id));
|
|
435
|
+
const rest = nodes.filter((n) => !set.has(n.id));
|
|
436
|
+
return where === "back" ? [...moved, ...rest] : [...rest, ...moved];
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ── layout ───────────────────────────────────────────────────────────────
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* A layered layout (sources first, each step one rank further) with no
|
|
443
|
+
* dependency: cycles are broken, ranks are longest paths, and a few
|
|
444
|
+
* barycentre sweeps untangle crossings. Nodes nobody connects sit in a row
|
|
445
|
+
* after the rest. The result keeps the top-left corner the nodes had, so
|
|
446
|
+
* nothing flies off screen.
|
|
447
|
+
*
|
|
448
|
+
* autoLayout(nodes, edges, { direction: "LR" | "TB", rankGap: 80, nodeGap: 40, ids })
|
|
449
|
+
*
|
|
450
|
+
* `ids` lays out only those nodes (default: every node but frames).
|
|
451
|
+
* Returns the whole node list with new positions.
|
|
452
|
+
*/
|
|
453
|
+
export function autoLayout(nodes, edges, { direction = "LR", rankGap = 80, nodeGap = 40, ids, grid = GRID } = {}) {
|
|
454
|
+
const all = nodes ?? [];
|
|
455
|
+
const pickIds = new Set(ids ?? all.filter((n) => n.type !== "frame").map((n) => n.id));
|
|
456
|
+
const pick = all.filter((n) => pickIds.has(n.id));
|
|
457
|
+
if (!pick.length) return all;
|
|
458
|
+
const before = boardBounds(pick);
|
|
459
|
+
const lr = direction !== "TB";
|
|
460
|
+
const size = new Map(pick.map((n) => [n.id, nodeSize(n)]));
|
|
461
|
+
const main = (id) => (lr ? size.get(id).width : size.get(id).height);
|
|
462
|
+
const cross = (id) => (lr ? size.get(id).height : size.get(id).width);
|
|
463
|
+
|
|
464
|
+
const order = pick.map((n) => n.id);
|
|
465
|
+
const index = new Map(order.map((id, i) => [id, i]));
|
|
466
|
+
const links = (edges ?? []).filter((e) => pickIds.has(e.source) && pickIds.has(e.target) && e.source !== e.target);
|
|
467
|
+
|
|
468
|
+
// Break cycles: a DFS in list order; an edge back into the stack is reversed.
|
|
469
|
+
const out = new Map(order.map((id) => [id, []]));
|
|
470
|
+
for (const e of links) out.get(e.source).push(e.target);
|
|
471
|
+
const state = new Map();
|
|
472
|
+
const dag = [];
|
|
473
|
+
const visit = (u) => {
|
|
474
|
+
state.set(u, 1);
|
|
475
|
+
for (const v of out.get(u)) {
|
|
476
|
+
if (state.get(v) === 1) dag.push([v, u]);
|
|
477
|
+
else {
|
|
478
|
+
dag.push([u, v]);
|
|
479
|
+
if (!state.get(v)) visit(v);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
state.set(u, 2);
|
|
483
|
+
};
|
|
484
|
+
for (const id of order) if (!state.get(id)) visit(id);
|
|
485
|
+
|
|
486
|
+
const preds = new Map(order.map((id) => [id, []]));
|
|
487
|
+
const succs = new Map(order.map((id) => [id, []]));
|
|
488
|
+
const seen = new Set();
|
|
489
|
+
for (const [u, v] of dag) {
|
|
490
|
+
const k = `${u}>${v}`;
|
|
491
|
+
if (seen.has(k)) continue;
|
|
492
|
+
seen.add(k);
|
|
493
|
+
succs.get(u).push(v);
|
|
494
|
+
preds.get(v).push(u);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Components: connected groups, laid side by side on the cross axis.
|
|
498
|
+
const comp = new Map();
|
|
499
|
+
let compCount = 0;
|
|
500
|
+
for (const id of order) {
|
|
501
|
+
if (comp.has(id)) continue;
|
|
502
|
+
const stack = [id];
|
|
503
|
+
comp.set(id, compCount);
|
|
504
|
+
while (stack.length) {
|
|
505
|
+
const u = stack.pop();
|
|
506
|
+
for (const v of [...succs.get(u), ...preds.get(u)]) if (!comp.has(v)) (comp.set(v, compCount), stack.push(v));
|
|
507
|
+
}
|
|
508
|
+
compCount++;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Ranks: longest path from a source (Kahn order).
|
|
512
|
+
const rank = new Map();
|
|
513
|
+
const indeg = new Map(order.map((id) => [id, preds.get(id).length]));
|
|
514
|
+
const queue = order.filter((id) => indeg.get(id) === 0);
|
|
515
|
+
for (const id of queue) rank.set(id, 0);
|
|
516
|
+
for (let i = 0; i < queue.length; i++) {
|
|
517
|
+
const u = queue[i];
|
|
518
|
+
for (const v of succs.get(u)) {
|
|
519
|
+
rank.set(v, Math.max(rank.get(v) ?? 0, rank.get(u) + 1));
|
|
520
|
+
indeg.set(v, indeg.get(v) - 1);
|
|
521
|
+
if (indeg.get(v) === 0) queue.push(v);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const loose = order.filter((id) => !preds.get(id).length && !succs.get(id).length);
|
|
526
|
+
const looseSet = new Set(loose);
|
|
527
|
+
const groups = [];
|
|
528
|
+
for (let c = 0; c < compCount; c++) {
|
|
529
|
+
const members = order.filter((id) => comp.get(id) === c && !looseSet.has(id));
|
|
530
|
+
if (members.length) groups.push(members);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const positions = new Map();
|
|
534
|
+
let crossCursor = 0;
|
|
535
|
+
for (const members of groups) {
|
|
536
|
+
const maxRank = Math.max(...members.map((id) => rank.get(id)));
|
|
537
|
+
const layers = Array.from({ length: maxRank + 1 }, () => []);
|
|
538
|
+
for (const id of members) layers[rank.get(id)].push(id);
|
|
539
|
+
for (const l of layers) l.sort((a, b) => index.get(a) - index.get(b));
|
|
540
|
+
// Barycentre sweeps: order each layer by the mean slot of its neighbours.
|
|
541
|
+
const slot = new Map();
|
|
542
|
+
const reslot = () => layers.forEach((l) => l.forEach((id, i) => slot.set(id, i)));
|
|
543
|
+
reslot();
|
|
544
|
+
const bary = (id, nbrs) => {
|
|
545
|
+
const s = nbrs.filter((x) => slot.has(x));
|
|
546
|
+
return s.length ? s.reduce((t, x) => t + slot.get(x), 0) / s.length : slot.get(id);
|
|
547
|
+
};
|
|
548
|
+
for (let pass = 0; pass < 4; pass++) {
|
|
549
|
+
for (let r = 1; r < layers.length; r++) {
|
|
550
|
+
layers[r].sort((a, b) => bary(a, preds.get(a)) - bary(b, preds.get(b)) || index.get(a) - index.get(b));
|
|
551
|
+
reslot();
|
|
552
|
+
}
|
|
553
|
+
for (let r = layers.length - 2; r >= 0; r--) {
|
|
554
|
+
layers[r].sort((a, b) => bary(a, succs.get(a)) - bary(b, succs.get(b)) || index.get(a) - index.get(b));
|
|
555
|
+
reslot();
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
// Place: ranks along the main axis, each layer centred on the widest.
|
|
559
|
+
const extent = layers.map((l) => l.reduce((s, id) => s + cross(id), 0) + nodeGap * Math.max(0, l.length - 1));
|
|
560
|
+
const widest = Math.max(...extent);
|
|
561
|
+
let mainCursor = 0;
|
|
562
|
+
layers.forEach((l, r) => {
|
|
563
|
+
const depth = Math.max(...l.map(main));
|
|
564
|
+
let c = crossCursor + (widest - extent[r]) / 2;
|
|
565
|
+
for (const id of l) {
|
|
566
|
+
const m = mainCursor + (depth - main(id)) / 2;
|
|
567
|
+
positions.set(id, lr ? { x: m, y: c } : { x: c, y: m });
|
|
568
|
+
c += cross(id) + nodeGap;
|
|
569
|
+
}
|
|
570
|
+
mainCursor += depth + rankGap;
|
|
571
|
+
});
|
|
572
|
+
crossCursor += widest + rankGap;
|
|
573
|
+
}
|
|
574
|
+
// The unconnected ones: one row (or column) after the groups.
|
|
575
|
+
if (loose.length) {
|
|
576
|
+
let m = 0;
|
|
577
|
+
const depth = Math.max(...loose.map(cross));
|
|
578
|
+
for (const id of loose) {
|
|
579
|
+
positions.set(id, lr ? { x: m, y: crossCursor + (depth - cross(id)) / 2 } : { x: crossCursor + (depth - cross(id)) / 2, y: m });
|
|
580
|
+
m += main(id) + nodeGap;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Keep the old top-left corner.
|
|
585
|
+
const laid = pick.map((n) => ({ ...n, position: positions.get(n.id) }));
|
|
586
|
+
const after = boardBounds(laid);
|
|
587
|
+
const dx = before.x - after.x;
|
|
588
|
+
const dy = before.y - after.y;
|
|
589
|
+
const moved = new Map(laid.map((n) => [n.id, snapPoint({ x: n.position.x + dx, y: n.position.y + dy }, grid)]));
|
|
590
|
+
return all.map((n) => (moved.has(n.id) && !n.data?.locked ? { ...n, position: moved.get(n.id) } : n));
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// ── words out ────────────────────────────────────────────────────────────
|
|
594
|
+
|
|
595
|
+
/** The words a node shows: text, title, label or name. */
|
|
596
|
+
export function nodeText(node) {
|
|
597
|
+
const d = node?.data ?? {};
|
|
598
|
+
for (const k of ["text", "title", "label", "name"]) if (typeof d[k] === "string" && d[k].trim()) return d[k].trim();
|
|
599
|
+
if (node?.type === "image") return String(d.alt || d.caption || "").trim();
|
|
600
|
+
return "";
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const KIND = { sticky: "Sticky", shape: "Shape", text: "Text", frame: "Frame", card: "Card", image: "Image" };
|
|
604
|
+
const quote = (s) => `"${String(s).replace(/\s+/g, " ").trim()}"`;
|
|
605
|
+
const readingOrder = (a, b) => {
|
|
606
|
+
const ra = nodeRect(a);
|
|
607
|
+
const rb = nodeRect(b);
|
|
608
|
+
return Math.abs(ra.y - rb.y) > 24 ? ra.y - rb.y : ra.x - rb.x;
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
/** One line for a node: `Sticky (yellow): "Ship the beta"`. */
|
|
612
|
+
export function describeNode(node, describe) {
|
|
613
|
+
const own = describe?.(node);
|
|
614
|
+
if (typeof own === "string" && own) return own;
|
|
615
|
+
const d = node?.data ?? {};
|
|
616
|
+
const text = nodeText(node);
|
|
617
|
+
if (node?.type === "shape") return `${SHAPE_NAMES[d.shape] ?? "Shape"}: ${text ? quote(text) : "(empty)"}`;
|
|
618
|
+
if (node?.type === "sticky") {
|
|
619
|
+
const extra = [d.badge != null && d.badge !== "" ? `${d.badge}` : null, d.meta || null].filter(Boolean).join(", ");
|
|
620
|
+
return `Sticky: ${text ? quote(text) : "(empty)"}${extra ? ` [${extra}]` : ""}`;
|
|
621
|
+
}
|
|
622
|
+
if (node?.type === "card") return `Card${d.kind ? ` (${d.kind})` : ""}: ${text ? quote(text) : "(untitled)"}${d.body ? ` — ${String(d.body).replace(/\s+/g, " ").trim()}` : ""}`;
|
|
623
|
+
if (node?.type === "image") return `Image: ${text ? quote(text) : "(no description)"}`;
|
|
624
|
+
const kind = KIND[node?.type] ?? String(node?.type ?? "Item").replace(/^\w/, (c) => c.toUpperCase());
|
|
625
|
+
return `${kind}: ${text ? quote(text) : "(empty)"}`;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function outline(board) {
|
|
629
|
+
const { nodes, edges } = readBoard(board);
|
|
630
|
+
const parent = frameOf(board);
|
|
631
|
+
const kids = new Map();
|
|
632
|
+
for (const n of nodes) {
|
|
633
|
+
const p = parent.get(n.id) ?? null;
|
|
634
|
+
if (!kids.has(p)) kids.set(p, []);
|
|
635
|
+
kids.get(p).push(n);
|
|
636
|
+
}
|
|
637
|
+
for (const list of kids.values()) list.sort((a, b) => (a.type === "frame") - (b.type === "frame") || readingOrder(a, b));
|
|
638
|
+
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
639
|
+
return { nodes, edges, kids, byId };
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const edgeLine = (e, byId) => {
|
|
643
|
+
const a = nodeText(byId.get(e.source)) || byId.get(e.source)?.type || e.source;
|
|
644
|
+
const b = nodeText(byId.get(e.target)) || byId.get(e.target)?.type || e.target;
|
|
645
|
+
const arrow = e.data?.arrow === "both" ? "↔" : e.data?.arrow === "none" ? "—" : "→";
|
|
646
|
+
return `${quote(a)} ${arrow} ${quote(b)}${e.label ? ` (${String(e.label).trim()})` : ""}`;
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* The board as plain text, for an agent or a screen reader: frames with
|
|
651
|
+
* what they hold (in reading order, nested), loose items, connectors.
|
|
652
|
+
* `describe(node)` returns a line for a custom node type (or nothing, for
|
|
653
|
+
* the default).
|
|
654
|
+
*/
|
|
655
|
+
export function boardToText(board, { describe } = {}) {
|
|
656
|
+
const { nodes, edges, kids, byId } = outline(board);
|
|
657
|
+
if (!nodes.length) return "Empty board.";
|
|
658
|
+
const lines = [`Board: ${nodes.length} item${nodes.length === 1 ? "" : "s"}, ${edges.length} connector${edges.length === 1 ? "" : "s"}.`];
|
|
659
|
+
const walk = (parentId, depth) => {
|
|
660
|
+
for (const n of kids.get(parentId) ?? []) {
|
|
661
|
+
const pad = " ".repeat(depth);
|
|
662
|
+
if (n.type === "frame") {
|
|
663
|
+
const count = (kids.get(n.id) ?? []).length;
|
|
664
|
+
lines.push(`${pad}Frame ${quote(n.data?.title || "Untitled")} (${count} item${count === 1 ? "" : "s"}):`);
|
|
665
|
+
walk(n.id, depth + 1);
|
|
666
|
+
} else lines.push(`${pad}- ${describeNode(n, describe)}`);
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
walk(null, 0);
|
|
670
|
+
if (edges.length) {
|
|
671
|
+
lines.push("Connectors:");
|
|
672
|
+
for (const e of edges) lines.push(`- ${edgeLine(e, byId)}`);
|
|
673
|
+
}
|
|
674
|
+
return lines.join("\n");
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* The board as GitHub markdown: `# title`, a heading per frame (nested
|
|
679
|
+
* frames one level down), a bullet per item, a Connections list.
|
|
680
|
+
*/
|
|
681
|
+
export function boardToMarkdown(board, { title, describe } = {}) {
|
|
682
|
+
const { nodes, edges, kids, byId } = outline(board);
|
|
683
|
+
const out = [];
|
|
684
|
+
if (title) out.push(`# ${title}`, "");
|
|
685
|
+
if (!nodes.length) return [...out, "_Empty board._"].join("\n");
|
|
686
|
+
const esc = (s) => String(s).replace(/([*_`[\]])/g, "\\$1").replace(/\s+/g, " ").trim();
|
|
687
|
+
const bullet = (n) => {
|
|
688
|
+
const own = describe?.(n);
|
|
689
|
+
if (typeof own === "string" && own) return `- ${own}`;
|
|
690
|
+
const d = n.data ?? {};
|
|
691
|
+
const text = nodeText(n);
|
|
692
|
+
if (n.type === "text") return `- ${esc(text) || "_(empty)_"}`;
|
|
693
|
+
if (n.type === "shape") return `- **${esc(text) || "(empty)"}** _(${(SHAPE_NAMES[d.shape] ?? "shape").toLowerCase()})_`;
|
|
694
|
+
if (n.type === "card") return `- **${esc(text) || "(untitled)"}**${d.body ? ` — ${esc(d.body)}` : ""}`;
|
|
695
|
+
if (n.type === "image") return d.src ? `- ` : `- _image: ${esc(text)}_`;
|
|
696
|
+
const extra = n.type === "sticky" && d.badge != null && d.badge !== "" ? ` (${d.badge})` : "";
|
|
697
|
+
return `- ${esc(text) || "_(empty)_"}${extra}`;
|
|
698
|
+
};
|
|
699
|
+
const loose = (kids.get(null) ?? []).filter((n) => n.type !== "frame");
|
|
700
|
+
for (const n of loose) out.push(bullet(n));
|
|
701
|
+
if (loose.length) out.push("");
|
|
702
|
+
const walk = (parentId, level) => {
|
|
703
|
+
for (const f of (kids.get(parentId) ?? []).filter((n) => n.type === "frame")) {
|
|
704
|
+
out.push(`${"#".repeat(Math.min(6, level))} ${esc(f.data?.title || "Untitled")}`, "");
|
|
705
|
+
const items = (kids.get(f.id) ?? []).filter((n) => n.type !== "frame");
|
|
706
|
+
for (const n of items) out.push(bullet(n));
|
|
707
|
+
if (items.length) out.push("");
|
|
708
|
+
walk(f.id, level + 1);
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
walk(null, title ? 2 : 1);
|
|
712
|
+
if (edges.length) {
|
|
713
|
+
out.push(`${"#".repeat(title ? 2 : 1)} Connections`, "");
|
|
714
|
+
for (const e of edges) out.push(`- ${edgeLine(e, byId)}`);
|
|
715
|
+
out.push("");
|
|
716
|
+
}
|
|
717
|
+
return out.join("\n").trimEnd() + "\n";
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// ── history ──────────────────────────────────────────────────────────────
|
|
721
|
+
|
|
722
|
+
/** Undo/redo as a reducer over board snapshots (use cleanBoard ones).
|
|
723
|
+
* const [h, dispatch] = useReducer(historyReducer, board, historyInit)
|
|
724
|
+
* dispatch({ type: "push", present }) a committed change (a no-op when equal)
|
|
725
|
+
* dispatch({ type: "undo" }) · dispatch({ type: "redo" })
|
|
726
|
+
* dispatch({ type: "reset", present }) a new document; no undo past it
|
|
727
|
+
* h.present is the board to show; h.past / h.future the stacks. */
|
|
728
|
+
export function historyInit(present) {
|
|
729
|
+
const p = cleanBoard(present ?? emptyBoard(), { viewport: false });
|
|
730
|
+
return { past: [], present: p, future: [], key: boardKey(p) };
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
export function historyReducer(h, action) {
|
|
734
|
+
const limit = action?.limit ?? 100;
|
|
735
|
+
switch (action?.type) {
|
|
736
|
+
case "push": {
|
|
737
|
+
const present = cleanBoard(action.present, { viewport: false });
|
|
738
|
+
const key = boardKey(present);
|
|
739
|
+
if (key === h.key) return h;
|
|
740
|
+
return { past: [...h.past, h.present].slice(-limit), present, future: [], key };
|
|
741
|
+
}
|
|
742
|
+
case "undo": {
|
|
743
|
+
if (!h.past.length) return h;
|
|
744
|
+
const present = h.past[h.past.length - 1];
|
|
745
|
+
return { past: h.past.slice(0, -1), present, future: [h.present, ...h.future].slice(0, limit), key: boardKey(present) };
|
|
746
|
+
}
|
|
747
|
+
case "redo": {
|
|
748
|
+
if (!h.future.length) return h;
|
|
749
|
+
const [present, ...future] = h.future;
|
|
750
|
+
return { past: [...h.past, h.present].slice(-limit), present, future, key: boardKey(present) };
|
|
751
|
+
}
|
|
752
|
+
case "reset":
|
|
753
|
+
return historyInit(action.present);
|
|
754
|
+
default:
|
|
755
|
+
return h;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// ── starters ─────────────────────────────────────────────────────────────
|
|
760
|
+
|
|
761
|
+
/** The empty-state starters FlowCanvas offers by default. Each `board()`
|
|
762
|
+
* makes fresh ids. Pass your own list in `templates`, or [] for none. */
|
|
763
|
+
export function starterTemplates() {
|
|
764
|
+
return [
|
|
765
|
+
{
|
|
766
|
+
id: "brainstorm",
|
|
767
|
+
label: "Brainstorm",
|
|
768
|
+
description: "A frame and a few stickies to start from",
|
|
769
|
+
icon: "lightbulb",
|
|
770
|
+
board: () => {
|
|
771
|
+
const f = createNode("frame", null, { rect: { x: 0, y: 0, width: 720, height: 420 }, data: { title: "Ideas" } });
|
|
772
|
+
const s = (x, y, color, text) => createNode("sticky", { x, y }, { data: { color, text } });
|
|
773
|
+
return {
|
|
774
|
+
nodes: [
|
|
775
|
+
f,
|
|
776
|
+
s(140, 160, "warning", "What would make this a yes?"),
|
|
777
|
+
s(360, 160, "chart-2", ""),
|
|
778
|
+
s(580, 160, "chart-3", ""),
|
|
779
|
+
createNode("text", { x: 360, y: 360 }, { width: 480, data: { text: "Add one idea per sticky. Cluster them later.", textSize: "sm" } }),
|
|
780
|
+
],
|
|
781
|
+
edges: [],
|
|
782
|
+
};
|
|
783
|
+
},
|
|
784
|
+
},
|
|
785
|
+
{
|
|
786
|
+
id: "flowchart",
|
|
787
|
+
label: "Flowchart",
|
|
788
|
+
description: "Start, steps, a decision, connected",
|
|
789
|
+
icon: "git-branch",
|
|
790
|
+
board: () => {
|
|
791
|
+
const sh = (x, y, shape, text, color) => createNode("shape", { x, y }, { data: { shape, text, color } });
|
|
792
|
+
const a = sh(100, 200, "pill", "Start", "chart-2");
|
|
793
|
+
const b = sh(340, 200, "rect", "Do the thing", "muted");
|
|
794
|
+
const c = sh(580, 200, "diamond", "Worked?", "warning");
|
|
795
|
+
const d = sh(820, 200, "pill", "Done", "success");
|
|
796
|
+
return {
|
|
797
|
+
nodes: [a, b, c, d],
|
|
798
|
+
edges: [createEdge(a.id, b.id), createEdge(b.id, c.id), createEdge(c.id, d.id, { label: "Yes" }), createEdge(c.id, b.id, { label: "No", data: { line: "elbow" } })],
|
|
799
|
+
};
|
|
800
|
+
},
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
id: "retro",
|
|
804
|
+
label: "Retro",
|
|
805
|
+
description: "Went well, to improve, actions",
|
|
806
|
+
icon: "history",
|
|
807
|
+
board: () => {
|
|
808
|
+
const col = (i, title, color) => {
|
|
809
|
+
const x = i * 340;
|
|
810
|
+
return [
|
|
811
|
+
createNode("frame", null, { rect: { x, y: 0, width: 320, height: 460 }, data: { title } }),
|
|
812
|
+
createNode("sticky", { x: x + 160, y: 140 }, { width: 180, height: 180, data: { color, text: "" } }),
|
|
813
|
+
];
|
|
814
|
+
};
|
|
815
|
+
return { nodes: [...col(0, "Went well", "success"), ...col(1, "To improve", "warning"), ...col(2, "Actions", "chart-5")], edges: [] };
|
|
816
|
+
},
|
|
817
|
+
},
|
|
818
|
+
];
|
|
819
|
+
}
|