@heycys/gallery 0.1.0 → 0.1.2
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 +51 -1
- package/browser/editor/components/callout/index.mjs +40 -15
- package/browser/editor/components/columns/page.mjs +45 -32
- package/browser/editor/components/details/index.mjs +2 -1
- package/browser/editor/components/dialog/index.mjs +2 -1
- package/browser/editor/components/dialog/page.mjs +0 -18
- package/browser/editor/components/divider/index.mjs +1 -1
- package/browser/editor/components/embed/index.mjs +2 -2
- package/browser/editor/components/figure/page.mjs +10 -1
- package/browser/editor/components/file/page.mjs +2 -2
- package/browser/editor/components/flowchart/add.mjs +7 -6
- package/browser/editor/components/flowchart/page.mjs +1 -1
- package/browser/editor/components/heading/index.mjs +2 -0
- package/browser/editor/components/heading/page.mjs +6 -0
- package/browser/editor/components/highlight/index.mjs +4 -20
- package/browser/editor/components/icons.mjs +13 -2
- package/browser/editor/components/image/crop.mjs +6 -9
- package/browser/editor/components/image/page.mjs +2 -2
- package/browser/editor/components/installed/index.mjs +82 -29
- package/browser/editor/components/installed/page.mjs +146 -34
- package/browser/editor/components/installed/served.mjs +6 -5
- package/browser/editor/components/instances.mjs +18 -0
- package/browser/editor/components/kit.mjs +3 -0
- package/browser/editor/components/list/page.mjs +3 -6
- package/browser/editor/components/media/page.mjs +44 -14
- package/browser/editor/components/pages.mjs +2 -2
- package/browser/editor/components/registry.mjs +68 -33
- package/browser/editor/components/section/index.mjs +2 -1
- package/browser/editor/components/set-apart.mjs +24 -0
- package/browser/editor/components/table/index.mjs +64 -5
- package/browser/editor/components/table/page.mjs +30 -19
- package/browser/editor/page/annotate/anchors.mjs +4 -3
- package/browser/editor/page/annotate/card.mjs +3 -6
- package/browser/editor/page/annotate/index.mjs +61 -34
- package/browser/editor/page/autosave.mjs +357 -38
- package/browser/editor/page/beside.mjs +5 -5
- package/browser/editor/page/block-menu/colors.mjs +3 -1
- package/browser/editor/page/block-menu/index.mjs +8 -6
- package/browser/editor/page/blocks.mjs +16 -7
- package/browser/editor/page/board/client.mjs +57 -0
- package/browser/editor/page/board/hint.mjs +145 -0
- package/browser/editor/page/board/history.mjs +119 -0
- package/browser/editor/page/board/index.mjs +970 -0
- package/browser/editor/page/board/settings.mjs +209 -0
- package/browser/editor/page/caret.mjs +5 -2
- package/browser/editor/page/changes/bar.mjs +94 -0
- package/browser/editor/page/changes/ghost.mjs +206 -0
- package/browser/editor/page/changes/gone-words.mjs +94 -0
- package/browser/editor/page/changes/index.mjs +447 -0
- package/browser/editor/page/changes/words.mjs +27 -0
- package/browser/editor/page/clipboard/import/colors.mjs +3 -3
- package/browser/editor/page/clipboard/import/html.mjs +29 -6
- package/browser/editor/page/clipboard/import/inline.mjs +32 -0
- package/browser/editor/page/clipboard/import/markdown.mjs +58 -13
- package/browser/editor/page/clipboard/import/read.mjs +6 -1
- package/browser/editor/page/clipboard/import/to-blocks.mjs +44 -12
- package/browser/editor/page/clipboard/import/yuque.mjs +622 -0
- package/browser/editor/page/clipboard/index.mjs +156 -26
- package/browser/editor/page/clipboard/own/blocks.mjs +25 -16
- package/browser/editor/page/clipboard/own/chart-keys.mjs +41 -28
- package/browser/editor/page/clipboard/own/describe.mjs +16 -7
- package/browser/editor/page/clipboard/own/export.mjs +6 -6
- package/browser/editor/page/clipboard/own/selection.mjs +3 -3
- package/browser/editor/page/clipboard/own/writers.mjs +0 -11
- package/browser/editor/page/columns/index.mjs +48 -46
- package/browser/editor/page/commit.mjs +98 -25
- package/browser/editor/page/conflicts.mjs +13 -33
- package/browser/editor/page/convert.mjs +8 -7
- package/browser/editor/page/core.mjs +81 -27
- package/browser/editor/page/custom-page.mjs +106 -0
- package/browser/editor/page/drag/index.mjs +40 -24
- package/browser/editor/page/drag/preview.mjs +9 -4
- package/browser/editor/page/editor.mjs +120 -28
- package/browser/editor/page/emoji-picker/emoji-tab.mjs +22 -13
- package/browser/editor/page/emoji-picker/grid.mjs +7 -5
- package/browser/editor/page/emoji-picker/icon-tab.mjs +4 -4
- package/browser/editor/page/emoji-picker/icons.mjs +0 -1
- package/browser/editor/page/emoji-picker/index.mjs +11 -9
- package/browser/editor/page/emoji-picker/styles.mjs +156 -2
- package/browser/editor/page/emoji-picker/support.mjs +6 -4
- package/browser/editor/page/emoji-picker/upload.mjs +14 -13
- package/browser/editor/page/fills.mjs +5 -3
- package/browser/editor/page/fold/index.mjs +348 -0
- package/browser/editor/page/fold/rule.mjs +125 -0
- package/browser/editor/page/folded.mjs +23 -0
- package/browser/editor/page/format-bar/bar.mjs +2 -1
- package/browser/editor/page/format-bar/index.mjs +1 -1
- package/browser/editor/page/geometry.mjs +8 -6
- package/browser/editor/page/handle/index.mjs +22 -14
- package/browser/editor/page/hints/index.mjs +1 -1
- package/browser/editor/page/history.mjs +24 -4
- package/browser/editor/page/insert/index.mjs +30 -17
- package/browser/editor/page/links/index.mjs +5 -3
- package/browser/editor/page/links/pages.mjs +27 -9
- package/browser/editor/page/long-runs.mjs +75 -0
- package/browser/editor/page/look.mjs +69 -6
- package/browser/editor/page/margin.mjs +63 -27
- package/browser/editor/page/media-menu/index.mjs +6 -7
- package/browser/editor/page/menu.mjs +86 -31
- package/browser/editor/page/merge.mjs +24 -0
- package/browser/editor/page/notices.mjs +3 -3
- package/browser/editor/page/offered.mjs +1 -1
- package/browser/editor/page/outline/folds.mjs +11 -10
- package/browser/editor/page/outline/index.mjs +131 -45
- package/browser/editor/page/outline/list.mjs +23 -11
- package/browser/editor/page/outline/read.mjs +1 -1
- package/browser/editor/page/outline/strip.mjs +7 -1
- package/browser/editor/page/page-objects.mjs +13 -2
- package/browser/editor/page/panel.mjs +9 -13
- package/browser/editor/page/present-links.mjs +11 -2
- package/browser/editor/page/resize/index.mjs +42 -29
- package/browser/editor/page/selection/delete.mjs +8 -6
- package/browser/editor/page/selection/index.mjs +10 -3
- package/browser/editor/page/served.mjs +14 -0
- package/browser/editor/page/session.mjs +101 -6
- package/browser/editor/page/slash-menu/index.mjs +3 -3
- package/browser/editor/page/table/cell-bar.mjs +4 -1
- package/browser/editor/page/table/cells.mjs +103 -47
- package/browser/editor/page/table/icons.mjs +3 -1
- package/browser/editor/page/table/index.mjs +40 -16
- package/browser/editor/page/table/pictures.mjs +299 -0
- package/browser/editor/page/table/views.mjs +150 -0
- package/browser/editor/page/table/widths.mjs +9 -4
- package/browser/editor/page/text-select/index.mjs +43 -14
- package/browser/editor/page/todo-writes.mjs +75 -0
- package/browser/editor/page/tooltip.mjs +1 -1
- package/browser/editor/page/type-grid.mjs +4 -3
- package/browser/editor/page/typing/code-edges.mjs +63 -17
- package/browser/editor/page/typing/index.mjs +65 -9
- package/browser/editor/page/typing/leave.mjs +1 -1
- package/browser/editor/page/ui.mjs +13 -0
- package/browser/shared/annotations.mjs +8 -10
- package/browser/shared/block-packs.mjs +404 -0
- package/browser/shared/board-places.mjs +224 -0
- package/browser/shared/card-size.mjs +59 -0
- package/browser/shared/code-folds.mjs +5 -1
- package/browser/shared/colors.mjs +17 -14
- package/browser/shared/component-catalog.mjs +345 -54
- package/browser/shared/component-source.mjs +37 -0
- package/browser/shared/components/callout.mjs +4 -2
- package/browser/shared/components/index.mjs +72 -9
- package/browser/shared/components/installed.mjs +110 -8
- package/browser/shared/components/page.mjs +22 -3
- package/browser/shared/components/state.mjs +5 -1
- package/browser/shared/components/table.mjs +1 -1
- package/browser/shared/components/text.mjs +2 -2
- package/browser/shared/document-look.mjs +183 -0
- package/browser/shared/drafts.mjs +432 -0
- package/browser/shared/emoji/data/LICENSE-lucide.txt +43 -0
- package/browser/shared/emoji/data/README.md +4 -2
- package/browser/shared/emoji/data/categories.json +1 -1
- package/browser/shared/emoji/data/keywords-en.json +1 -1
- package/browser/shared/emoji/data/keywords-zh.json +1 -1
- package/browser/shared/emoji/data/names.json +1 -1
- package/browser/shared/emoji/data/positions.json +1 -1
- package/browser/shared/emoji/data/twemoji-index.json +1 -1
- package/browser/shared/emoji/data/twemoji-svg.bin +0 -0
- package/browser/shared/emoji/data.mjs +10 -12
- package/browser/shared/emoji/emoji.mjs +8 -7
- package/browser/shared/emoji/icon-value.mjs +4 -3
- package/browser/shared/extension-sidebar.mjs +126 -0
- package/browser/shared/extensions.mjs +79 -0
- package/browser/shared/file-table.mjs +101 -0
- package/browser/shared/html-tree.mjs +11 -5
- package/browser/shared/i18n/en.mjs +374 -35
- package/browser/shared/json-layout.mjs +47 -0
- package/browser/shared/long-runs.mjs +39 -0
- package/browser/shared/net-cache.mjs +133 -0
- package/browser/shared/net-keys.mjs +32 -0
- package/browser/shared/net-policy.mjs +177 -0
- package/browser/shared/new-names.mjs +5 -2
- package/browser/shared/outline-shape.mjs +128 -0
- package/browser/shared/outline.mjs +458 -0
- package/browser/shared/pack-manifest.mjs +77 -0
- package/browser/shared/page-address.mjs +6 -3
- package/browser/shared/page-cards.mjs +308 -0
- package/browser/shared/page-links.mjs +69 -6
- package/browser/shared/pages.mjs +388 -0
- package/browser/shared/paths.mjs +6 -0
- package/browser/shared/preferences.mjs +10 -4
- package/browser/shared/preview-server.mjs +3 -7
- package/browser/shared/session.mjs +13 -5
- package/browser/shared/shipped-blocks.mjs +299 -0
- package/browser/shared/table-chart.mjs +33 -1
- package/browser/shared/todos.mjs +83 -0
- package/browser/shared/user-directory.mjs +24 -0
- package/browser/shared/versions.mjs +14 -0
- package/browser/shared/web-links.mjs +134 -0
- package/browser/shared/workspace-columns.mjs +120 -0
- package/browser/shared/workspace-look.mjs +91 -0
- package/browser/shared/workspace-name.mjs +154 -0
- package/browser/shared/workspace-query.mjs +125 -0
- package/browser/shared/workspace-registry.mjs +272 -0
- package/browser/shared/workspace.mjs +171 -20
- package/browser/shared/zoom.mjs +5 -5
- package/dist/chunk-2NRBY6AJ.mjs +2 -0
- package/dist/chunk-32ZUEFZ7.mjs +2 -0
- package/dist/chunk-35NQ4PLQ.mjs +2 -0
- package/dist/chunk-3KJHLHUO.mjs +2 -0
- package/dist/chunk-3NJACR4W.mjs +2 -0
- package/dist/chunk-4AV456RQ.mjs +2 -0
- package/dist/chunk-4OZWXZE5.mjs +5 -0
- package/dist/chunk-5XYHDBJ5.mjs +2 -0
- package/dist/chunk-5ZJA7CXT.mjs +2 -0
- package/dist/chunk-6A53MZYZ.mjs +2 -0
- package/dist/chunk-6EXH3ORZ.mjs +166 -0
- package/dist/chunk-6EZ7FNIK.mjs +3 -0
- package/dist/{chunk-UXUK4PMC.mjs → chunk-6QBHU4DF.mjs} +1 -1
- package/dist/{chunk-6U2SLSDN.mjs → chunk-6VTU6KEO.mjs} +1 -1
- package/dist/chunk-B4P2CM2J.mjs +3 -0
- package/dist/chunk-BPL64HGA.mjs +4 -0
- package/dist/chunk-BVEFGE4B.mjs +2 -0
- package/dist/chunk-BZ5RAF7L.mjs +2 -0
- package/dist/chunk-CRJEG7MT.mjs +2 -0
- package/dist/{chunk-B3VTYBYR.mjs → chunk-D4CKRD3V.mjs} +91 -91
- package/dist/chunk-D4EURAX4.mjs +4 -0
- package/dist/chunk-DQCE7TJY.mjs +5 -0
- package/dist/chunk-DR73Y2UJ.mjs +3 -0
- package/dist/{chunk-TMYZB6AV.mjs → chunk-EKNHM3P4.mjs} +1 -1
- package/dist/{chunk-DB4WKGYF.mjs → chunk-ERLAIBOA.mjs} +2 -2
- package/dist/chunk-EURLTOTZ.mjs +4 -0
- package/dist/chunk-FJYN7RTH.mjs +4 -0
- package/dist/chunk-FWNRPMCJ.mjs +12 -0
- package/dist/chunk-FWW4CSAZ.mjs +2 -0
- package/dist/{chunk-2B5E4CDJ.mjs → chunk-GPIWQ4X4.mjs} +1 -1
- package/dist/chunk-GY3FAUVM.mjs +2 -0
- package/dist/chunk-HLYV7RIH.mjs +2 -0
- package/dist/chunk-HMYTM3GT.mjs +15 -0
- package/dist/chunk-IA4PSIMD.mjs +2 -0
- package/dist/chunk-JEUK3ZO7.mjs +2 -0
- package/dist/chunk-JVHHHGEA.mjs +3 -0
- package/dist/chunk-KTKHRC6H.mjs +2 -0
- package/dist/chunk-L7UP52JO.mjs +2 -0
- package/dist/chunk-MHW7ZKOU.mjs +2 -0
- package/dist/chunk-N76HWBA3.mjs +10 -0
- package/dist/chunk-N7AZZLGT.mjs +548 -0
- package/dist/chunk-NAMN3RAF.mjs +2 -0
- package/dist/{chunk-YXAUQPVK.mjs → chunk-NFMCP2HI.mjs} +3 -3
- package/dist/chunk-NG375ILQ.mjs +2 -0
- package/dist/chunk-NVRNLK25.mjs +6 -0
- package/dist/chunk-NWFBE2YA.mjs +2 -0
- package/dist/chunk-NWR2LT5B.mjs +2 -0
- package/dist/chunk-OJKMINIU.mjs +20 -0
- package/dist/chunk-OUUP3WKT.mjs +2 -0
- package/dist/chunk-OW5NWPOJ.mjs +10 -0
- package/dist/chunk-PAREWQGB.mjs +4 -0
- package/dist/chunk-POQSE4C2.mjs +2 -0
- package/dist/chunk-PUOQ65NC.mjs +2 -0
- package/dist/{chunk-Z52HTUW7.mjs → chunk-PVER7LSC.mjs} +2 -2
- package/dist/chunk-PZDLJJTJ.mjs +3 -0
- package/dist/chunk-Q47PLKIJ.mjs +15 -0
- package/dist/chunk-Q4UEPUWF.mjs +2 -0
- package/dist/chunk-QKXMPUXC.mjs +2 -0
- package/dist/chunk-QOUEFUCH.mjs +16 -0
- package/dist/chunk-RFDZXFJF.mjs +13 -0
- package/dist/chunk-RKPWLGZ3.mjs +4 -0
- package/dist/chunk-RMECZA2K.mjs +2 -0
- package/dist/{chunk-EUX7IC5J.mjs → chunk-SQ6Y2ZMA.mjs} +2 -2
- package/dist/chunk-SX4LJYT6.mjs +8 -0
- package/dist/chunk-SXHBYZST.mjs +16 -0
- package/dist/chunk-TANZRINB.mjs +2 -0
- package/dist/chunk-TEZAWDSQ.mjs +2 -0
- package/dist/chunk-UBBRTJ2R.mjs +24 -0
- package/dist/chunk-UFNYFFKO.mjs +20 -0
- package/dist/chunk-UGWYKHBL.mjs +170 -0
- package/dist/chunk-UKSHDZZ6.mjs +2 -0
- package/dist/chunk-UMHUSOPJ.mjs +2 -0
- package/dist/chunk-UNOYVIYR.mjs +2 -0
- package/dist/chunk-V6RIYQZZ.mjs +4 -0
- package/dist/chunk-VJPFVNNW.mjs +15 -0
- package/dist/chunk-VS3ZMVGX.mjs +5 -0
- package/dist/chunk-VTKNEYT3.mjs +6 -0
- package/dist/chunk-VXACTLO3.mjs +3 -0
- package/dist/chunk-W3BGBJQW.mjs +2 -0
- package/dist/chunk-YI43RG4P.mjs +2 -0
- package/dist/chunk-YQGT4AB2.mjs +6 -0
- package/dist/chunk-ZB4H7ZN4.mjs +2 -0
- package/dist/{chunk-ACOPDCNB.mjs → chunk-ZHEHPMBK.mjs} +1 -1
- package/dist/chunk-ZTHICSSF.mjs +2 -0
- package/dist/chunk-ZUNVAXLH.mjs +2 -0
- package/dist/chunk-ZWPMCK7P.mjs +2 -0
- package/dist/cli.mjs +1 -1
- package/dist/server.mjs +1 -1
- package/dist/{chunk-EVJ3BX3M.mjs → update-fetch.mjs} +1 -1
- package/dist/worker.mjs +1 -1
- package/kit/board.css +95 -0
- package/kit/board.js +250 -0
- package/kit/charts.js +5 -71
- package/kit/code.js +14 -14
- package/kit/math.js +18 -2
- package/kit/report.css +500 -151
- package/kit/runtime.js +1720 -170
- package/kit/state.js +10 -1
- package/kit/tables.js +172 -37
- package/kit/ui.js +166 -27
- package/package.json +2 -1
- package/packs/gallery/card/component.json +14 -0
- package/packs/gallery/card/example.html +10 -0
- package/packs/gallery/card/preview.svg +1 -0
- package/packs/gallery/card/style.css +6 -0
- package/packs/gallery/card/template.html +6 -0
- package/packs/gallery/card/writing.md +14 -0
- package/packs/gallery/chart-card/component.json +15 -0
- package/packs/gallery/chart-card/example.html +14 -0
- package/packs/gallery/chart-card/kit.js +130 -0
- package/packs/gallery/chart-card/preview.svg +1 -0
- package/packs/gallery/chart-card/style.css +42 -0
- package/packs/gallery/chart-card/template.html +13 -0
- package/packs/gallery/chart-card/writing.md +20 -0
- package/packs/gallery/chart-view/component.json +20 -0
- package/packs/gallery/chart-view/example.html +13 -0
- package/packs/gallery/chart-view/kit.js +355 -0
- package/packs/gallery/chart-view/preview.svg +1 -0
- package/packs/gallery/chart-view/style.css +94 -0
- package/packs/gallery/chart-view/template.html +3 -0
- package/packs/gallery/chart-view/writing.md +22 -0
- package/packs/gallery/countdown/component.json +18 -0
- package/packs/gallery/countdown/example.html +3 -0
- package/packs/gallery/countdown/kit.js +156 -0
- package/packs/gallery/countdown/preview.svg +1 -0
- package/packs/gallery/countdown/style.css +130 -0
- package/packs/gallery/countdown/template.html +3 -0
- package/packs/gallery/countdown/writing.md +22 -0
- package/packs/gallery/habit/component.json +14 -0
- package/packs/gallery/habit/example.html +3 -0
- package/packs/gallery/habit/kit.js +295 -0
- package/packs/gallery/habit/preview.svg +1 -0
- package/packs/gallery/habit/style.css +246 -0
- package/packs/gallery/habit/template.html +3 -0
- package/packs/gallery/habit/writing.md +21 -0
- package/packs/gallery/heatmap/component.json +18 -0
- package/packs/gallery/heatmap/example.html +3 -0
- package/packs/gallery/heatmap/kit.js +351 -0
- package/packs/gallery/heatmap/preview.svg +1 -0
- package/packs/gallery/heatmap/style.css +201 -0
- package/packs/gallery/heatmap/template.html +3 -0
- package/packs/gallery/heatmap/writing.md +21 -0
- package/packs/gallery/kanban/component.json +18 -0
- package/packs/gallery/kanban/example.html +12 -0
- package/packs/gallery/kanban/kit.js +540 -0
- package/packs/gallery/kanban/preview.svg +1 -0
- package/packs/gallery/kanban/style.css +212 -0
- package/packs/gallery/kanban/template.html +3 -0
- package/packs/gallery/kanban/writing.md +20 -0
- package/packs/gallery/metric/component.json +22 -0
- package/packs/gallery/metric/example.html +15 -0
- package/packs/gallery/metric/kit.js +248 -0
- package/packs/gallery/metric/preview.svg +1 -0
- package/packs/gallery/metric/style.css +92 -0
- package/packs/gallery/metric/template.html +3 -0
- package/packs/gallery/metric/writing.md +23 -0
- package/packs/gallery/note/component.json +14 -0
- package/packs/gallery/note/example.html +3 -0
- package/packs/gallery/note/kit.js +102 -0
- package/packs/gallery/note/preview.svg +1 -0
- package/packs/gallery/note/style.css +76 -0
- package/packs/gallery/note/template.html +3 -0
- package/packs/gallery/note/writing.md +18 -0
- package/packs/gallery/pack.json +8 -0
- package/packs/gallery/recent/component.json +13 -0
- package/packs/gallery/recent/example.html +1 -0
- package/packs/gallery/recent/kit.js +238 -0
- package/packs/gallery/recent/preview.svg +1 -0
- package/packs/gallery/recent/style.css +158 -0
- package/packs/gallery/recent/template.html +1 -0
- package/packs/gallery/recent/writing.md +15 -0
- package/packs/gallery/static/component.json +13 -0
- package/packs/gallery/static/example.html +11 -0
- package/packs/gallery/static/preview.svg +1 -0
- package/packs/gallery/static/style.css +138 -0
- package/packs/gallery/static/template.html +1 -0
- package/packs/gallery/static/writing.md +18 -0
- package/packs/gallery/stats/component.json +13 -0
- package/packs/gallery/stats/example.html +1 -0
- package/packs/gallery/stats/kit.js +166 -0
- package/packs/gallery/stats/preview.svg +1 -0
- package/packs/gallery/stats/style.css +151 -0
- package/packs/gallery/stats/template.html +1 -0
- package/packs/gallery/stats/writing.md +11 -0
- package/packs/gallery/today/component.json +12 -0
- package/packs/gallery/today/example.html +1 -0
- package/packs/gallery/today/kit.js +185 -0
- package/packs/gallery/today/preview.svg +1 -0
- package/packs/gallery/today/style.css +120 -0
- package/packs/gallery/today/template.html +1 -0
- package/packs/gallery/today/writing.md +14 -0
- package/packs/gallery/todos/component.json +19 -0
- package/packs/gallery/todos/example.html +3 -0
- package/packs/gallery/todos/kit.js +342 -0
- package/packs/gallery/todos/preview.svg +1 -0
- package/packs/gallery/todos/style.css +267 -0
- package/packs/gallery/todos/template.html +3 -0
- package/packs/gallery/todos/writing.md +30 -0
- package/packs/gallery/weather/component.json +19 -0
- package/packs/gallery/weather/example.html +3 -0
- package/packs/gallery/weather/kit.js +312 -0
- package/packs/gallery/weather/preview.svg +1 -0
- package/packs/gallery/weather/style.css +258 -0
- package/packs/gallery/weather/template.html +3 -0
- package/packs/gallery/weather/writing.md +26 -0
- package/packs/gallery-soft/pack.json +8 -0
- package/release.json +13 -11
- package/sdk/doc-kit.d.ts +404 -7
- package/sdk/doc-state.d.ts +2 -0
- package/skill/SKILL.md +71 -26
- package/skill/references/block-script.md +140 -0
- package/skill/references/building-blocks.md +257 -0
- package/skill/references/example-report.html +1 -1
- package/skill/references/extensions.md +59 -0
- package/skill/references/pages-and-data.md +221 -0
- package/skill/references/writing-html.md +9 -7
- package/skill/sdk/doc-kit.d.ts +404 -7
- package/skill/sdk/doc-state.d.ts +2 -0
- package/web/assets/index-BK2e_07w.js +13 -0
- package/web/assets/index-CWFI83mF.css +1 -0
- package/web/index.html +2 -2
- package/browser/editor/components/callout/page.mjs +0 -13
- package/dist/chunk-2PBALTR3.mjs +0 -57
- package/dist/chunk-3LA3EMDE.mjs +0 -15
- package/dist/chunk-5FRMCDPY.mjs +0 -16
- package/dist/chunk-5LEK2CBS.mjs +0 -2
- package/dist/chunk-5OFN5AHC.mjs +0 -2
- package/dist/chunk-5SIYS5HN.mjs +0 -2
- package/dist/chunk-5TEK3RAD.mjs +0 -2
- package/dist/chunk-6CYG3Z4V.mjs +0 -2
- package/dist/chunk-7GHT2UCA.mjs +0 -15
- package/dist/chunk-7QQBR7PB.mjs +0 -2
- package/dist/chunk-ATCJYAR4.mjs +0 -2
- package/dist/chunk-C7HWOBIF.mjs +0 -2
- package/dist/chunk-E7VYDG2X.mjs +0 -2
- package/dist/chunk-EZY3WC6Z.mjs +0 -15
- package/dist/chunk-FQGQH4KM.mjs +0 -2
- package/dist/chunk-G7TOXONE.mjs +0 -6
- package/dist/chunk-GPF4YWWB.mjs +0 -9
- package/dist/chunk-GYQV7Y4J.mjs +0 -120
- package/dist/chunk-HMHK3EQ6.mjs +0 -2
- package/dist/chunk-HQQMJCQJ.mjs +0 -10
- package/dist/chunk-IERUOGC3.mjs +0 -6
- package/dist/chunk-ITNLYXXV.mjs +0 -3
- package/dist/chunk-L4DVONOG.mjs +0 -2
- package/dist/chunk-LAE5NQ4B.mjs +0 -2
- package/dist/chunk-LC7YNQFG.mjs +0 -2
- package/dist/chunk-LHTFLRB6.mjs +0 -4
- package/dist/chunk-NFIBI4IP.mjs +0 -26
- package/dist/chunk-NSKUTQ5J.mjs +0 -5
- package/dist/chunk-O52JMOCZ.mjs +0 -2
- package/dist/chunk-P2CAZPDP.mjs +0 -3
- package/dist/chunk-QMQUXTU4.mjs +0 -12
- package/dist/chunk-QVWVFC5K.mjs +0 -6
- package/dist/chunk-QYKCJ7FG.mjs +0 -8
- package/dist/chunk-QZY6Q64C.mjs +0 -2
- package/dist/chunk-TDQP3DKQ.mjs +0 -10
- package/dist/chunk-TX6JH7TR.mjs +0 -2
- package/dist/chunk-UNUEWH4S.mjs +0 -2
- package/dist/chunk-V4CPWKMZ.mjs +0 -2
- package/dist/chunk-VACRB2NE.mjs +0 -3
- package/dist/chunk-W63L7367.mjs +0 -7
- package/dist/chunk-W7CXPCRV.mjs +0 -6
- package/dist/chunk-XINNG4F2.mjs +0 -4
- package/dist/chunk-YL2DPWVS.mjs +0 -4
- package/dist/chunk-YZ5Z6H4W.mjs +0 -4
- package/dist/chunk-ZBIM6ARY.mjs +0 -216
- package/skill/references/building-components.md +0 -327
- package/web/assets/index-B6Ai3wsa.js +0 -13
- package/web/assets/index-SYPC3EZy.css +0 -1
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { componentSettings, problemIn, writeComponentSettings } from "./component-catalog.mjs";
|
|
4
|
+
import { refusal, said } from "./i18n/index.mjs";
|
|
5
|
+
import { PACK_FILE, PACK_ID, readPackManifest } from "./pack-manifest.mjs";
|
|
6
|
+
import { packsDirectory } from "./paths.mjs";
|
|
7
|
+
import { filesIn, fingerprintOf, fingerprintOfFiles, isSystemFile, recordOf, shippedBlocks, shippedPackIds, versionAfter, writeRecord, writingOrder } from "./shipped-blocks.mjs";
|
|
8
|
+
import { DEVICE_NAMES, PACKAGE_MANIFEST, layoutOf, markerOf, packageFolders } from "./workspace.mjs";
|
|
9
|
+
|
|
10
|
+
// Block packs (自定义块包: notes/plans/block-packs-plan-2026-11-09.md; docs/render.md "Block packs"). A pack
|
|
11
|
+
// is a scope of the workspace's custom blocks, components/<id>/, with its pack.json (./pack-manifest.mjs)
|
|
12
|
+
// and a folder of each of its blocks, <id>/<name>/, the blocks' ids <id>/<name> as ever, so that nothing
|
|
13
|
+
// written of a block changes; a scope with no pack.json (local/, the Agent's own) is a pack of its own,
|
|
14
|
+
// named by its scope. A pack is shared as its folder, or a zip of it (the folder itself, or what it
|
|
15
|
+
// holds, at the zip's top), and installed into a workspace by gallery block pack install (src/cli/component.mjs)
|
|
16
|
+
// or 设置 › 组件列表's 导入 (src/server/component-settings.mjs), which both read it (src/transfer/pack-source.mjs)
|
|
17
|
+
// and hand its files here, checked alike:
|
|
18
|
+
//
|
|
19
|
+
// installPack its files copied in, as gallery block add copies one of Gallery's own: each block's
|
|
20
|
+
// manifest last; .gallery/blocks.json records each block's version and fingerprint, and
|
|
21
|
+
// under `packs` the pack's version, the fingerprint of its own files (pack.json, and what
|
|
22
|
+
// else stands at its top) and its blocks. Installed again (a newer version), a block as it
|
|
23
|
+
// was installed is replaced, one changed since kept as it is (someone's work), a block
|
|
24
|
+
// the new version has not taken out where it is as installed; pack.json the same way.
|
|
25
|
+
// Refused, with why (said()'s, a \`code\`): a file that points out of the pack ("path"),
|
|
26
|
+
// one too large ("size"), a pack.json missing or that does not read ("manifest"), a block
|
|
27
|
+
// folder of no component.json naming it <id>/<name> ("block"), the id of one of Gallery's
|
|
28
|
+
// own packs ("official"), an id a scope of the workspace has that no install put there
|
|
29
|
+
// ("taken": another pack, or blocks someone wrote).
|
|
30
|
+
// removePack a pack installed taken out, its folder and records; never one of Gallery's own (its
|
|
31
|
+
// switch turns it off), nor a scope no install put there ("own": someone's work).
|
|
32
|
+
// workspacePacks the workspace's packs as 设置 › 组件列表 and gallery block pack list say them.
|
|
33
|
+
//
|
|
34
|
+
// Gallery's own packs (src/packs/<id>/: gallery, Gallery 风格; gallery-soft, 柔光风格) are installed the
|
|
35
|
+
// same way as a workspace's server starts and by gallery init (installOfficialPacks): once each, a pack
|
|
36
|
+
// switched off at first where OFF_AT_FIRST says so (gallery.json's components.packs), and again at each
|
|
37
|
+
// start with the same rule, so that a block as it was installed follows Gallery, a block Gallery's pack
|
|
38
|
+
// has come to hold is added, and one taken away by hand stays away (its record says it was there).
|
|
39
|
+
// A pack's switch (components.packs, { "<id>": { "enabled": false } }, none written on) keeps all its
|
|
40
|
+
// blocks out of every menu and library, as each one's master switch does (shared/components/installed.mjs
|
|
41
|
+
// inDocumentMenus); what is written of them shows as ever.
|
|
42
|
+
|
|
43
|
+
/** Gallery's own packs that a workspace has switched off when they are first installed. */
|
|
44
|
+
export const OFF_AT_FIRST = Object.freeze(["gallery-soft"]);
|
|
45
|
+
|
|
46
|
+
/** The environment variable that keeps a start (and gallery init) from installing Gallery's own packs: the tests' workspaces. */
|
|
47
|
+
export const NO_PACKS = "GALLERY_NO_PACKS";
|
|
48
|
+
|
|
49
|
+
/** Whether `env` says to install none of Gallery's own packs (NO_PACKS set, not to "", "0" or "false"). */
|
|
50
|
+
export const packsSkipped = (env = process.env) => !["", "0", "false", undefined].includes(env[NO_PACKS]);
|
|
51
|
+
|
|
52
|
+
const MOST_FILES = 2000;
|
|
53
|
+
const MOST_BYTES = 64 * 1024 * 1024;
|
|
54
|
+
const NAME = /^[a-z0-9][a-z0-9-]*$/;
|
|
55
|
+
const isTable = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
56
|
+
const fail = (code, message) => {
|
|
57
|
+
throw refusal(message, { code });
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** The files of `folder`, [{ name (its path in it, with "/"), data }]: a link is none of them. */
|
|
61
|
+
export function folderFiles(folder) {
|
|
62
|
+
const files = [];
|
|
63
|
+
for (const entry of fs.readdirSync(folder, { recursive: true, withFileTypes: true })) {
|
|
64
|
+
if (!entry.isFile()) continue;
|
|
65
|
+
if (files.length >= MOST_FILES) fail("size", said("块包太大:最多 {0} 个文件、{1} MB", MOST_FILES, MOST_BYTES / 1024 / 1024));
|
|
66
|
+
const name = path.relative(folder, path.join(entry.parentPath, entry.name)).split(path.sep).join("/");
|
|
67
|
+
files.push({ name, data: fs.readFileSync(path.join(folder, ...name.split("/"))) });
|
|
68
|
+
}
|
|
69
|
+
return files;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A pack's files ([{ name, data }], as a folder or a zip gives them) read as one: { manifest, blocks: Map
|
|
74
|
+
* of each block's folder name to its files, own: the files at its top, pack.json among them }. A pack
|
|
75
|
+
* zipped with its folder (one folder at the top, pack.json in it) is read from that folder; what a
|
|
76
|
+
* system wrote (.DS_Store, __MACOSX/…) is passed over. Refused as installPack says; `refuse(manifest)`, run
|
|
77
|
+
* once its pack.json reads, before its blocks are looked at, refuses what it will.
|
|
78
|
+
*/
|
|
79
|
+
export function readPack(given, { refuse = () => {} } = {}) {
|
|
80
|
+
const files = [];
|
|
81
|
+
let bytes = 0;
|
|
82
|
+
for (const { name: written, data } of given) {
|
|
83
|
+
const name = String(written).replace(/\\/g, "/");
|
|
84
|
+
if (name.endsWith("/")) continue; // a folder of a zip
|
|
85
|
+
const parts = name.split("/");
|
|
86
|
+
if (name.startsWith("/") || /^[A-Za-z]:/.test(name) || name.includes("\u0000") || parts.some((part) => part === "" || part === "." || part === "..")) fail("path", said("{0} 指向块包外面,不能装", String(written)));
|
|
87
|
+
if (parts[0] === "__MACOSX" || isSystemFile(parts.at(-1))) continue;
|
|
88
|
+
bytes += data.length;
|
|
89
|
+
files.push({ name, data });
|
|
90
|
+
}
|
|
91
|
+
if (files.length > MOST_FILES || bytes > MOST_BYTES) fail("size", said("块包太大:最多 {0} 个文件、{1} MB", MOST_FILES, MOST_BYTES / 1024 / 1024));
|
|
92
|
+
let inner = files;
|
|
93
|
+
if (!files.some(({ name }) => name === PACK_FILE)) {
|
|
94
|
+
const tops = new Set(files.map(({ name }) => name.split("/")[0]));
|
|
95
|
+
const [top] = tops;
|
|
96
|
+
if (tops.size !== 1 || !files.some(({ name }) => name === `${top}/${PACK_FILE}`)) fail("manifest", said("最上一层没有 pack.json:块包是一个文件夹,最上一层放 pack.json,每个块一个文件夹"));
|
|
97
|
+
inner = files.map(({ name, data }) => ({ name: name.slice(top.length + 1), data }));
|
|
98
|
+
}
|
|
99
|
+
const manifest = readPackManifest(inner.find(({ name }) => name === PACK_FILE).data.toString("utf8"));
|
|
100
|
+
refuse(manifest);
|
|
101
|
+
const blocks = new Map();
|
|
102
|
+
const own = [];
|
|
103
|
+
for (const file of inner) {
|
|
104
|
+
const [first, ...rest] = file.name.split("/");
|
|
105
|
+
if (rest.length === 0) {
|
|
106
|
+
own.push(file);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (!blocks.has(first)) blocks.set(first, []);
|
|
110
|
+
blocks.get(first).push({ name: rest.join("/"), data: file.data });
|
|
111
|
+
}
|
|
112
|
+
for (const [name, list] of blocks) {
|
|
113
|
+
if (!NAME.test(name) || DEVICE_NAMES.has(name)) fail("block", said("{0} 不是块的文件夹名:只用小写字母、数字和连字符", name));
|
|
114
|
+
const file = list.find((each) => each.name === PACKAGE_MANIFEST);
|
|
115
|
+
if (!file) fail("block", said("{0}/ 里没有 component.json", name));
|
|
116
|
+
let id;
|
|
117
|
+
try {
|
|
118
|
+
id = JSON.parse(file.data.toString("utf8").replace(/^/, "")).id;
|
|
119
|
+
} catch {
|
|
120
|
+
fail("block", said("{0}/component.json 不是 JSON", name));
|
|
121
|
+
}
|
|
122
|
+
if (id !== `${manifest.id}/${name}`) fail("block", said("{0}/component.json 的 id 要写成 {1}", name, `${manifest.id}/${name}`));
|
|
123
|
+
}
|
|
124
|
+
return { manifest, blocks, own };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// A block's version, as its manifest says it, or null.
|
|
128
|
+
function versionIn(list) {
|
|
129
|
+
try {
|
|
130
|
+
const version = JSON.parse(list.find(({ name }) => name === PACKAGE_MANIFEST).data.toString("utf8").replace(/^/, "")).version;
|
|
131
|
+
return typeof version === "string" ? version : null;
|
|
132
|
+
} catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// The files right at the top of a pack's folder (pack.json and what else stands beside the blocks).
|
|
138
|
+
function topFiles(folder) {
|
|
139
|
+
try {
|
|
140
|
+
return fs.readdirSync(folder, { withFileTypes: true }).filter((entry) => entry.isFile() && !isSystemFile(entry.name)).map((entry) => entry.name).sort();
|
|
141
|
+
} catch {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// What the top of a pack's folder holds, as a fingerprint, or null for nothing there.
|
|
147
|
+
function ownFingerprint(folder) {
|
|
148
|
+
const names = topFiles(folder);
|
|
149
|
+
return names.length ? fingerprintOfFiles(names.map((name) => ({ name, data: fs.readFileSync(path.join(folder, name)) }))) : null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Whether a folder holds anything a system did not write there.
|
|
153
|
+
function holds(folder) {
|
|
154
|
+
try {
|
|
155
|
+
return fs.readdirSync(folder).some((name) => !isSystemFile(name));
|
|
156
|
+
} catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// `files` written into `folder`, the files of what was there that they have not taken out first
|
|
162
|
+
// (`replacing`), the manifest last: what makes a folder a block to a running server's watcher.
|
|
163
|
+
function writeFiles(folder, files, { replacing = false, last = PACKAGE_MANIFEST } = {}) {
|
|
164
|
+
const names = files.map(({ name }) => name);
|
|
165
|
+
if (replacing) for (const old of filesIn(folder)) if (!names.includes(old)) fs.rmSync(path.join(folder, ...old.split("/")), { force: true });
|
|
166
|
+
const byName = new Map(files.map((file) => [file.name, file.data]));
|
|
167
|
+
const order = last === PACKAGE_MANIFEST ? writingOrder(names) : [...names.filter((name) => name !== last), ...(names.includes(last) ? [last] : [])];
|
|
168
|
+
for (const name of order) {
|
|
169
|
+
const target = path.join(folder, ...name.split("/"));
|
|
170
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
171
|
+
fs.writeFileSync(target, byName.get(name));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The pack `given` holds ([{ name, data }]: src/transfer/pack-source.mjs reads a folder or a zip into
|
|
177
|
+
* them) installed into the workspace at `root` (above): { id, name, version, from (the version installed
|
|
178
|
+
* before, or null), did ("installed", "updated" or "same"), folder, added, updated, same, kept (blocks
|
|
179
|
+
* changed since they were installed, left as they are), removed (blocks the new version has not), skipped
|
|
180
|
+
* (blocks of Gallery's own taken away by hand, which stay away), ownKept (its pack.json changed since,
|
|
181
|
+
* left) }, each list of block ids. `official`: one of Gallery's own packs (installOfficialPacks), whose
|
|
182
|
+
* id is its own and whose scope may hold its blocks already, as gallery block add put them there.
|
|
183
|
+
*/
|
|
184
|
+
export function installPack(root, given, { official = false, shippedIds = shippedPackIds() } = {}) {
|
|
185
|
+
const { manifest, blocks, own } = readPack(given, {
|
|
186
|
+
refuse: ({ id }) => {
|
|
187
|
+
if (!official && shippedIds.includes(id)) fail("official", said("{0} 是 Gallery 自带的块包用的 id:换一个 id 再装", id));
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
const { id } = manifest;
|
|
191
|
+
const folder = path.join(layoutOf(root).components, id);
|
|
192
|
+
const record = recordOf(root);
|
|
193
|
+
const packs = isTable(record.packs) ? record.packs : {};
|
|
194
|
+
const was = isTable(packs[id]) ? packs[id] : null;
|
|
195
|
+
if (!official && !was && holds(folder)) fail("taken", said("工作区里已经有 components/{0}:这个名字被别的块包或别人写的块用着,换一个 id,或先把那个文件夹挪走", id));
|
|
196
|
+
const done = { id, name: manifest.name, version: manifest.version, from: was?.version ?? null, folder, added: [], updated: [], same: [], kept: [], removed: [], skipped: [], ownKept: false };
|
|
197
|
+
const names = [...blocks.keys()].sort();
|
|
198
|
+
for (const name of names) {
|
|
199
|
+
const block = `${id}/${name}`;
|
|
200
|
+
const list = blocks.get(name);
|
|
201
|
+
const target = path.join(folder, name);
|
|
202
|
+
const next = fingerprintOfFiles(list);
|
|
203
|
+
const now = fingerprintOf(target);
|
|
204
|
+
const kept = isTable(record[block]) ? record[block] : null;
|
|
205
|
+
if (now === null) {
|
|
206
|
+
if (official && kept) {
|
|
207
|
+
done.skipped.push(block);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
writeFiles(target, list);
|
|
211
|
+
done.added.push(block);
|
|
212
|
+
} else if (now === next) done.same.push(block);
|
|
213
|
+
else if (kept && now === kept.fingerprint) {
|
|
214
|
+
writeFiles(target, list, { replacing: true });
|
|
215
|
+
done.updated.push(block);
|
|
216
|
+
} else {
|
|
217
|
+
done.kept.push(block);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
record[block] = { version: versionIn(list), fingerprint: next };
|
|
221
|
+
}
|
|
222
|
+
// The blocks of the pack as it was installed that this version has not: out, where they are as installed.
|
|
223
|
+
for (const block of Array.isArray(was?.blocks) ? was.blocks : []) {
|
|
224
|
+
const name = String(block).split("/")[1];
|
|
225
|
+
if (!name || blocks.has(name) || !NAME.test(name)) continue;
|
|
226
|
+
const target = path.join(folder, name);
|
|
227
|
+
const now = fingerprintOf(target);
|
|
228
|
+
if (now !== null && now !== record[block]?.fingerprint) {
|
|
229
|
+
done.kept.push(block);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (now !== null) {
|
|
233
|
+
fs.rmSync(target, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
234
|
+
done.removed.push(block);
|
|
235
|
+
}
|
|
236
|
+
delete record[block];
|
|
237
|
+
}
|
|
238
|
+
// Its own files, pack.json last, the same way.
|
|
239
|
+
const ownNow = ownFingerprint(folder);
|
|
240
|
+
const ownNext = fingerprintOfFiles(own);
|
|
241
|
+
if (ownNow !== ownNext) {
|
|
242
|
+
if (ownNow === null || ownNow === was?.fingerprint) {
|
|
243
|
+
const names = own.map(({ name }) => name);
|
|
244
|
+
for (const old of topFiles(folder)) if (!names.includes(old)) fs.rmSync(path.join(folder, old), { force: true });
|
|
245
|
+
fs.mkdirSync(folder, { recursive: true });
|
|
246
|
+
writeFiles(folder, own, { last: PACK_FILE });
|
|
247
|
+
} else done.ownKept = true;
|
|
248
|
+
}
|
|
249
|
+
const changed = done.added.length + done.updated.length + done.removed.length > 0 || (ownNow !== ownNext && !done.ownKept);
|
|
250
|
+
done.did = !was ? "installed" : changed ? "updated" : "same";
|
|
251
|
+
record.packs = { ...packs, [id]: { version: manifest.version, fingerprint: done.ownKept ? was.fingerprint : ownNext, blocks: names.map((name) => `${id}/${name}`), ...(official ? { official: true } : {}) } };
|
|
252
|
+
writeRecord(root, record);
|
|
253
|
+
return done;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* The pack `id` taken out of the workspace at `root`: its folder and what .gallery/blocks.json records of
|
|
258
|
+
* it ({ id, version, blocks }). What documents and pages hold of its blocks stays, shown as blocks that
|
|
259
|
+
* are not there. Refused: one of Gallery's own ("official": its switch in 设置 › 组件列表 turns it off), none
|
|
260
|
+
* there ("missing"), a scope no install put there ("own": blocks someone wrote, theirs to delete).
|
|
261
|
+
*/
|
|
262
|
+
export function removePack(root, id, { shippedIds = shippedPackIds() } = {}) {
|
|
263
|
+
if (shippedIds.includes(id)) fail("official", said("{0} 是 Gallery 自带的块包,不能删除:在 设置 › 组件列表 里把它关掉", id));
|
|
264
|
+
const folder = path.join(layoutOf(root).components, id);
|
|
265
|
+
if (!PACK_ID.test(id) || !fs.existsSync(folder)) fail("missing", said("工作区里没有块包 {0}", id));
|
|
266
|
+
const record = recordOf(root);
|
|
267
|
+
const was = isTable(record.packs) && isTable(record.packs[id]) ? record.packs[id] : null;
|
|
268
|
+
if (!was) fail("own", said("components/{0} 不是装进来的块包,里面的块是有人在这个工作区写的:要删就自己删掉这个文件夹", id));
|
|
269
|
+
fs.rmSync(folder, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
270
|
+
for (const key of Object.keys(record)) if (key.startsWith(`${id}/`)) delete record[key];
|
|
271
|
+
const { [id]: _gone, ...rest } = record.packs;
|
|
272
|
+
writeRecord(root, { ...record, packs: rest });
|
|
273
|
+
return { id, version: was.version ?? null, blocks: Array.isArray(was.blocks) ? was.blocks : [] };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Why, in both languages.
|
|
277
|
+
const both = (problem) => ({ zh: problemIn("zh", problem), en: problemIn("en", problem) });
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* The workspace's packs, as 设置 › 组件列表's cards and gallery block pack list say them: each scope of
|
|
281
|
+
* components/ that holds a block or a pack.json, [{ id, name: { zh, en } (its scope's for one with no
|
|
282
|
+
* pack.json), description, version, author, homepage, icon (null where it says none), kind ("official":
|
|
283
|
+
* one of Gallery's own; "installed": put there by an install; "own": a scope of blocks someone wrote
|
|
284
|
+
* here), problem ({ zh, en }: why its pack.json does not read, or null), blocks (how many), enabled (its
|
|
285
|
+
* switch, gallery.json's components.packs), changed (of what was installed, the blocks changed since, and
|
|
286
|
+
* pack.json where it was), newer (of one of Gallery's own that was changed since, Gallery's version of it
|
|
287
|
+
* when that comes after the one installed, which is not put over it; else null) }]: Gallery's own first,
|
|
288
|
+
* then those installed, then the workspace's own, each by id.
|
|
289
|
+
*/
|
|
290
|
+
export function workspacePacks(root, { settings = null, shippedIds = shippedPackIds(), directory = packsDirectory } = {}) {
|
|
291
|
+
const { components } = layoutOf(root);
|
|
292
|
+
const record = recordOf(root);
|
|
293
|
+
const installed = isTable(record.packs) ? record.packs : {};
|
|
294
|
+
const blocks = packageFolders(root);
|
|
295
|
+
const scopes = new Set(blocks.map(({ id }) => id.split("/")[0]));
|
|
296
|
+
try {
|
|
297
|
+
for (const name of fs.readdirSync(components)) if (PACK_ID.test(name) && fs.existsSync(path.join(components, name, PACK_FILE))) scopes.add(name);
|
|
298
|
+
} catch {}
|
|
299
|
+
const switches = (settings ?? componentSettings(root)).packs ?? {};
|
|
300
|
+
const listed = [...scopes].map((id) => {
|
|
301
|
+
const folder = path.join(components, id);
|
|
302
|
+
let manifest = null;
|
|
303
|
+
let problem = null;
|
|
304
|
+
if (fs.existsSync(path.join(folder, PACK_FILE))) {
|
|
305
|
+
try {
|
|
306
|
+
manifest = readPackManifest(fs.readFileSync(path.join(folder, PACK_FILE), "utf8"));
|
|
307
|
+
if (manifest.id !== id) {
|
|
308
|
+
problem = said("pack.json 的 id 是 {0},它的文件夹却是 components/{1}", manifest.id, id);
|
|
309
|
+
manifest = null;
|
|
310
|
+
}
|
|
311
|
+
} catch (error) {
|
|
312
|
+
problem = error;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const was = isTable(installed[id]) ? installed[id] : null;
|
|
316
|
+
const ids = blocks.filter((block) => block.id.startsWith(`${id}/`));
|
|
317
|
+
const changed = was ? ids.filter((block) => isTable(record[block.id]) && fingerprintOf(block.folder) !== record[block.id].fingerprint).map((block) => block.id) : [];
|
|
318
|
+
if (was && ownFingerprint(folder) !== was.fingerprint) changed.push(`${id}/${PACK_FILE}`);
|
|
319
|
+
const official = shippedIds.includes(id);
|
|
320
|
+
// One of Gallery's own: what Gallery's pack.json says of it, where the workspace's has none (its blocks
|
|
321
|
+
// put there one by one, gallery block add's, before a start installed the pack).
|
|
322
|
+
let shipped = null;
|
|
323
|
+
if (official) {
|
|
324
|
+
try {
|
|
325
|
+
shipped = readPackManifest(fs.readFileSync(path.join(directory, id, PACK_FILE), "utf8"));
|
|
326
|
+
} catch {}
|
|
327
|
+
}
|
|
328
|
+
// Gallery's newer version, not put over a change: a changed block of it whose version Gallery's comes after, or a changed pack.json.
|
|
329
|
+
const versionAt = (block) => {
|
|
330
|
+
try {
|
|
331
|
+
return JSON.parse(fs.readFileSync(path.join(components, ...block.split("/"), PACKAGE_MANIFEST), "utf8")).version ?? null;
|
|
332
|
+
} catch {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
const ownBlocks = official ? shippedBlocks(directory) : [];
|
|
337
|
+
const behind = official && shipped && changed.some((block) => (block.endsWith(`/${PACK_FILE}`) ? versionAfter(shipped.version, manifest?.version ?? null) : (ownBlocks.some((one) => one.id === block) && versionAfter(ownBlocks.find((one) => one.id === block).version, versionAt(block)))));
|
|
338
|
+
const newer = behind ? shipped.version : null;
|
|
339
|
+
const told = manifest ?? (problem ? null : shipped);
|
|
340
|
+
return {
|
|
341
|
+
id,
|
|
342
|
+
name: told?.name ?? { zh: id, en: id },
|
|
343
|
+
description: told?.description ?? null,
|
|
344
|
+
version: manifest?.version ?? null,
|
|
345
|
+
author: told?.author ?? null,
|
|
346
|
+
homepage: told?.homepage ?? null,
|
|
347
|
+
icon: told?.icon ?? null,
|
|
348
|
+
kind: official ? "official" : was ? "installed" : "own",
|
|
349
|
+
problem: problem ? both(problem) : null,
|
|
350
|
+
blocks: ids.length,
|
|
351
|
+
enabled: switches[id] !== false,
|
|
352
|
+
changed,
|
|
353
|
+
newer,
|
|
354
|
+
};
|
|
355
|
+
});
|
|
356
|
+
const rank = ({ id, kind }) => (kind === "official" ? shippedIds.indexOf(id) : kind === "installed" ? 1000 : 2000);
|
|
357
|
+
return listed.sort((a, b) => rank(a) - rank(b) || (a.id < b.id ? -1 : 1));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// What installOfficialPacks tells the log of one pack, a line each.
|
|
361
|
+
function installedLines(root, made, first) {
|
|
362
|
+
const lines = [];
|
|
363
|
+
const where = path.resolve(root);
|
|
364
|
+
if (first) lines.push(`Gallery's pack ${made.id} ${made.version} installed in ${where} (${made.added.length + made.same.length + made.updated.length} blocks)${OFF_AT_FIRST.includes(made.id) ? ", switched off: 设置 › 组件列表 turns it on" : ""}`);
|
|
365
|
+
else {
|
|
366
|
+
if (made.added.length) lines.push(`Gallery's pack ${made.id} in ${where} has new blocks: ${made.added.join(", ")}`);
|
|
367
|
+
if (made.updated.length) lines.push(`Gallery's pack ${made.id} in ${where}, its blocks as they were installed, updated to this Gallery's: ${made.updated.join(", ")}`);
|
|
368
|
+
if (made.removed.length) lines.push(`Gallery's pack ${made.id} in ${where}: ${made.removed.join(", ")} no longer in it, taken out`);
|
|
369
|
+
}
|
|
370
|
+
return lines;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Gallery's own packs (in `directory`, the package's) installed into the workspace at `root`, as
|
|
375
|
+
* installPack installs any, whenever its server starts and by gallery init: one not installed yet is
|
|
376
|
+
* (one of OFF_AT_FIRST switched off first in gallery.json, where it says nothing of it: a gallery.json that
|
|
377
|
+
* cannot be written leaves that pack for the next start), one installed is brought up to Gallery's.
|
|
378
|
+
* `env`: whose NO_PACKS is read; `log`: told what was done, a line each. Never rejects: { did: "off"
|
|
379
|
+
* (NO_PACKS), "unread" (gallery.json does not read as Gallery's) or "done", packs: [installPack's answer
|
|
380
|
+
* with `first`, or { id, failed }] }.
|
|
381
|
+
*/
|
|
382
|
+
export async function installOfficialPacks(root, { directory = packsDirectory, env = process.env, log = () => {} } = {}) {
|
|
383
|
+
if (packsSkipped(env)) return { did: "off", packs: [] };
|
|
384
|
+
let marker = { ours: false };
|
|
385
|
+
try {
|
|
386
|
+
marker = markerOf(fs.readFileSync(layoutOf(root).marker, "utf8"));
|
|
387
|
+
} catch {}
|
|
388
|
+
if (!marker.ours) return { did: "unread", packs: [] };
|
|
389
|
+
const ids = shippedPackIds(directory);
|
|
390
|
+
const packs = [];
|
|
391
|
+
for (const id of ids) {
|
|
392
|
+
try {
|
|
393
|
+
const first = !isTable(recordOf(root).packs?.[id]);
|
|
394
|
+
if (first && OFF_AT_FIRST.includes(id) && componentSettings(root).packs[id] === undefined) await writeComponentSettings(root, { pack: id, enabled: false });
|
|
395
|
+
const made = installPack(root, folderFiles(path.join(directory, id)), { official: true, shippedIds: ids });
|
|
396
|
+
packs.push({ ...made, first });
|
|
397
|
+
for (const line of installedLines(root, made, first)) log(line);
|
|
398
|
+
} catch (error) {
|
|
399
|
+
log(`Gallery's pack ${id} could not be installed in ${path.resolve(root)}: ${error?.message ?? error}`);
|
|
400
|
+
packs.push({ id, failed: error?.message ?? String(error) });
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return { did: "done", packs };
|
|
404
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// Where the cards of a custom page's board go (notes/plans/custom-blocks-spec-2026-11-07.md §6, §8;
|
|
2
|
+
// docs/render.md "A custom page"): the one set of rules every side places a card by, so that what the
|
|
3
|
+
// page shows while a card is dragged, what the page service writes and what gallery page add writes
|
|
4
|
+
// are the same. The page service (src/editor/page-service.mjs) and gallery page add place and push
|
|
5
|
+
// cards in the file by them; the kit's board script (src/render/kit/board/) draws a board by them,
|
|
6
|
+
// cards of `fit: "content"` as high as what they hold, those under them pushed down; the page's board
|
|
7
|
+
// editing (src/editor/page/board/) shows a drag's spot and the cards it pushes by them before it asks
|
|
8
|
+
// the service. Pure: no DOM, no Node.
|
|
9
|
+
//
|
|
10
|
+
// A card is { id, x, y, w, h } in whole cells of a board 12 columns wide, at most 24 cells high: what the
|
|
11
|
+
// file says, wherever it is drawn. A cell's side is fixed (CELL), as a document's reading width is, so a
|
|
12
|
+
// board is as wide whatever the window (the workbench's sidebar open or folded); where the room holds
|
|
13
|
+
// fewer than twelve of them the board is drawn in as many as it holds (gridFor), 4 at the least, its
|
|
14
|
+
// cards one after another in their order (flow), as wide as they are, which nothing writes: nothing is
|
|
15
|
+
// placed there. Nothing here ever moves a card up: a card pushed down stays where it was pushed, the room
|
|
16
|
+
// it left a room on the board, as on a board things are pinned to.
|
|
17
|
+
|
|
18
|
+
/** The cells of a board's row, and the most a card is high (the manifest's own range, spec §1). */
|
|
19
|
+
export const GRID = Object.freeze({ columns: 12, rows: 24 });
|
|
20
|
+
|
|
21
|
+
/** The pixels between two cells. */
|
|
22
|
+
export const GAP = 16;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A cell's side in pixels, the same however wide the window (the user's word of 2026-11-09: the cards no
|
|
26
|
+
* larger with the sidebar folded, only further in): twelve of them and their gaps are 944px, which the
|
|
27
|
+
* workbench's content holds on a 1280px laptop with the sidebar open at its default width.
|
|
28
|
+
*/
|
|
29
|
+
export const CELL = 64;
|
|
30
|
+
|
|
31
|
+
/** The fewest columns a board is drawn in: where not even these fit their cells are drawn smaller (a phone). */
|
|
32
|
+
export const LEAST = 4;
|
|
33
|
+
|
|
34
|
+
/** The pixels `n` cells take on a board whose cell is `cell` pixels (n cells and the n - 1 gaps between them). */
|
|
35
|
+
export const spanOf = (n, cell, gap = GAP) => n * cell + Math.max(0, n - 1) * gap;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A board drawn in `room` pixels: { columns, cell, width }, as many whole cells of CELL, GAP apart, as
|
|
39
|
+
* the room holds, from LEAST to 12; where not even LEAST fit, LEAST cells as large as the room lets
|
|
40
|
+
* them be. `width` is what its columns take (the board stands them in its middle).
|
|
41
|
+
*/
|
|
42
|
+
export function gridFor(room) {
|
|
43
|
+
const fit = Math.floor((Math.max(0, room) + GAP) / (CELL + GAP));
|
|
44
|
+
const columns = Math.max(LEAST, Math.min(GRID.columns, fit));
|
|
45
|
+
const cell = fit >= LEAST ? CELL : Math.max(0, (room - (LEAST - 1) * GAP) / LEAST);
|
|
46
|
+
return { columns, cell, width: spanOf(columns, cell) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const whole = (value, fallback) => (Number.isInteger(value) ? value : fallback);
|
|
50
|
+
|
|
51
|
+
/** Whether cards `a` and `b` share a cell. */
|
|
52
|
+
export const overlaps = (a, b) => a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Cells `{ x, y, w, h }` held to a board of `columns`: 1 <= w <= columns, 1 <= h <= 24, 0 <= x <=
|
|
56
|
+
* columns - w, 0 <= y; what is no whole number the default, a 4 x 2 card at 0, 0.
|
|
57
|
+
*/
|
|
58
|
+
export function onBoard({ x, y, w, h }, columns = GRID.columns) {
|
|
59
|
+
const width = Math.min(columns, Math.max(1, whole(w, 4)));
|
|
60
|
+
const height = Math.min(GRID.rows, Math.max(1, whole(h, 2)));
|
|
61
|
+
return { x: Math.min(columns - width, Math.max(0, whole(x, 0))), y: Math.max(0, whole(y, 0)), w: width, h: height };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The cards of a board with none sharing a cell, none moved up: `fixed` (ids) stay where they are,
|
|
66
|
+
* and every other card, in the order of its place (top to bottom, left to right, then as given), goes
|
|
67
|
+
* down until it shares no cell with one placed before it, right under the one it met. What the file
|
|
68
|
+
* says of a board that shares no cell is what this gives back. Returns new cards, in the order given.
|
|
69
|
+
* @template {{ id: string, x: number, y: number, w: number, h: number }} Card
|
|
70
|
+
* @param {Card[]} cards
|
|
71
|
+
* @param {Iterable<string>} fixed
|
|
72
|
+
* @returns {Card[]}
|
|
73
|
+
*/
|
|
74
|
+
export function settle(cards, fixed = []) {
|
|
75
|
+
const kept = new Set(fixed);
|
|
76
|
+
const placed = cards.filter((card) => kept.has(card.id)).map((card) => ({ ...card }));
|
|
77
|
+
const rest = cards.map((card, index) => ({ card, index })).filter(({ card }) => !kept.has(card.id))
|
|
78
|
+
.sort((a, b) => a.card.y - b.card.y || a.card.x - b.card.x || a.index - b.index);
|
|
79
|
+
for (const { card } of rest) {
|
|
80
|
+
const going = { ...card };
|
|
81
|
+
for (let met = placed.filter((other) => overlaps(going, other)); met.length > 0; met = placed.filter((other) => overlaps(going, other))) {
|
|
82
|
+
going.y = Math.max(...met.map((other) => other.y + other.h));
|
|
83
|
+
}
|
|
84
|
+
placed.push(going);
|
|
85
|
+
}
|
|
86
|
+
const at = new Map(placed.map((card) => [card.id, card]));
|
|
87
|
+
return cards.map((card) => at.get(card.id));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The cards with card `moved.id` at `moved`'s cells (held to the board), and those it now shares a cell
|
|
92
|
+
* with pushed down under it, and so on (settle): what a drop, a resize and a card put on a board at a
|
|
93
|
+
* place of its own do. A card of no id there joins them (a card added).
|
|
94
|
+
*/
|
|
95
|
+
export function pushDown(cards, moved, columns = GRID.columns) {
|
|
96
|
+
const cells = onBoard(moved, columns);
|
|
97
|
+
const next = cards.some((card) => card.id === moved.id)
|
|
98
|
+
? cards.map((card) => (card.id === moved.id ? { ...card, ...cells } : card))
|
|
99
|
+
: [...cards, { ...moved, ...cells }];
|
|
100
|
+
return settle(next, [moved.id]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The first place a card of `w` x `h` cells takes on a board holding `cards`, reading the board as a
|
|
105
|
+
* page is read: the topmost row it fits in, and there the leftmost column, sharing a cell with none.
|
|
106
|
+
* @returns {{ x: number, y: number }}
|
|
107
|
+
*/
|
|
108
|
+
export function freeSpot(cards, { w, h }, columns = GRID.columns) {
|
|
109
|
+
const { w: width, h: height } = onBoard({ x: 0, y: 0, w, h }, columns);
|
|
110
|
+
const bottom = cards.reduce((most, card) => Math.max(most, card.y + card.h), 0);
|
|
111
|
+
for (let y = 0; y <= bottom; y++) {
|
|
112
|
+
for (let x = 0; x + width <= columns; x++) {
|
|
113
|
+
if (!cards.some((card) => overlaps({ x, y, w: width, h: height }, card))) return { x, y };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { x: 0, y: bottom };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The size a card of a block takes when it is put on a board (the manifest's `card`, as the catalog
|
|
121
|
+
* reads it: component-catalog.mjs cardOf): the first of its `sizes`, else its `w` and `h`; `h` null
|
|
122
|
+
* where the manifest says none, the fewest whole cells its content takes, which only the page measures
|
|
123
|
+
* (`measured`: 2 until then).
|
|
124
|
+
* @param {{ sizes?: number[][] | null, w?: number, h?: number | null } | null | undefined} card
|
|
125
|
+
* @returns {{ w: number, h: number, measured: boolean }}
|
|
126
|
+
*/
|
|
127
|
+
export function defaultSize(card) {
|
|
128
|
+
if (card?.sizes?.length) return { w: card.sizes[0][0], h: card.sizes[0][1], measured: false };
|
|
129
|
+
return { w: card?.w ?? 4, h: card?.h ?? 2, measured: card?.h === null || card?.h === undefined };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Of `sizes` ([w, h] each), the nearest to `w` x `h` (by the distance of their corners; the first of two as near). */
|
|
133
|
+
export function nearestSize(sizes, { w, h }) {
|
|
134
|
+
let best = sizes[0];
|
|
135
|
+
let far = Infinity;
|
|
136
|
+
for (const size of sizes) {
|
|
137
|
+
const distance = (size[0] - w) ** 2 + (size[1] - h) ** 2;
|
|
138
|
+
if (distance < far) {
|
|
139
|
+
far = distance;
|
|
140
|
+
best = size;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return { w: best[0], h: best[1] };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* What a card of a block may be resized to, for `w` x `h` asked: the nearest of the manifest's `sizes`
|
|
148
|
+
* where it lists them (unless `any`: a manifest that wrote a min or a max beside them frees the card,
|
|
149
|
+
* its sizes only the common ones its menu lists), else whole cells between its `min` and `max`
|
|
150
|
+
* (1 x 1 and 12 x 24 where it says none), never past the board.
|
|
151
|
+
*/
|
|
152
|
+
export function sizeFor(card, { w, h }, columns = GRID.columns) {
|
|
153
|
+
if (card?.sizes?.length && !card.any) {
|
|
154
|
+
const near = nearestSize(card.sizes, { w, h });
|
|
155
|
+
return { w: Math.min(columns, near.w), h: near.h };
|
|
156
|
+
}
|
|
157
|
+
const min = { w: card?.min?.w ?? 1, h: card?.min?.h ?? 1 };
|
|
158
|
+
const max = { w: Math.min(columns, card?.max?.w ?? GRID.columns), h: Math.min(GRID.rows, card?.max?.h ?? GRID.rows) };
|
|
159
|
+
return { w: Math.min(max.w, Math.max(min.w, Math.round(w))), h: Math.min(max.h, Math.max(min.h, Math.round(h))) };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The cells high a card of `fit: "content"` is drawn, for its content `height` in pixels (what it
|
|
164
|
+
* needs, its frame and padding with it) on a board whose cell is `cell` pixels: the spec's
|
|
165
|
+
* ceil((height + 16) / (cell + 16)), 1 at least and 24 at most.
|
|
166
|
+
*/
|
|
167
|
+
export function fitHeight(height, cell, gap = GAP) {
|
|
168
|
+
if (!(cell > 0)) return 1;
|
|
169
|
+
return Math.min(GRID.rows, Math.max(1, Math.ceil((height + gap) / (cell + gap) - 1e-6)));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The cards of a board drawn in `columns` fewer than twelve (a narrow room: gridFor): one after another
|
|
174
|
+
* in the order of their places (top to bottom, left to right, then as given), each as wide as it is but
|
|
175
|
+
* never wider than the row (`w` held to `columns`) and as high, each at the first spot at or after where
|
|
176
|
+
* the one before it ends, row by row, that shares no cell with those before it (as a grid flows its items
|
|
177
|
+
* with no place of their own: no card goes before one it came after, so a room may be left beside a tall
|
|
178
|
+
* one). What is drawn only, never written: the file's twelve-column places stay as they are, and the
|
|
179
|
+
* board is drawn by them again once the room holds twelve. Returns new cards, in the order given.
|
|
180
|
+
* @template {{ id: string, x: number, y: number, w: number, h: number }} Card
|
|
181
|
+
* @param {Card[]} cards
|
|
182
|
+
* @param {number} columns
|
|
183
|
+
* @returns {Card[]}
|
|
184
|
+
*/
|
|
185
|
+
export function flow(cards, columns) {
|
|
186
|
+
const order = cards.map((card, index) => ({ card, index })).sort((a, b) => a.card.y - b.card.y || a.card.x - b.card.x || a.index - b.index);
|
|
187
|
+
const placed = [];
|
|
188
|
+
const at = new Map();
|
|
189
|
+
let row = 0;
|
|
190
|
+
let column = 0;
|
|
191
|
+
for (const { card, index } of order) {
|
|
192
|
+
const w = Math.max(1, Math.min(card.w, columns));
|
|
193
|
+
let x = column;
|
|
194
|
+
let y = row;
|
|
195
|
+
for (;;) {
|
|
196
|
+
if (x + w > columns) {
|
|
197
|
+
x = 0;
|
|
198
|
+
y += 1;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const spot = { x, y, w, h: card.h };
|
|
202
|
+
if (!placed.some((other) => overlaps(spot, other))) break;
|
|
203
|
+
x += 1;
|
|
204
|
+
}
|
|
205
|
+
const going = { ...card, x, y, w };
|
|
206
|
+
placed.push(going);
|
|
207
|
+
at.set(index, going);
|
|
208
|
+
row = y;
|
|
209
|
+
column = x + w;
|
|
210
|
+
}
|
|
211
|
+
return cards.map((_, index) => at.get(index));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** The whole cells `pixels` are nearest to, from a board's edge: what a drag snaps to. */
|
|
215
|
+
export const cellAt = (pixels, cell, gap = GAP) => (cell > 0 ? Math.round(pixels / (cell + gap)) : 0);
|
|
216
|
+
|
|
217
|
+
/** The cards whose cells changed from `before` to `after` (lists of cards), by id: [{ id, x, y, w, h }] as `after` has them. */
|
|
218
|
+
export function moved(before, after) {
|
|
219
|
+
const was = new Map(before.map((card) => [card.id, card]));
|
|
220
|
+
return after.filter((card) => {
|
|
221
|
+
const old = was.get(card.id);
|
|
222
|
+
return !old || old.x !== card.x || old.y !== card.y || old.w !== card.w || old.h !== card.h;
|
|
223
|
+
});
|
|
224
|
+
}
|