chalkbridge 1.1.0 → 1.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/dist/{chunk-XDK5UUTO.js → chunk-BCHUGREF.js} +83 -6
- package/dist/cli.js +901 -178
- package/dist/index.js +1 -1
- package/guides/layout.md +28 -1
- package/package.json +1 -1
- package/ui-dist/assets/{abnfDiagram-VCTEODGH-BCawxiKB.js → abnfDiagram-VCTEODGH-o3pL7yuH.js} +1 -1
- package/ui-dist/assets/{ar-SA-G6X2FPQ2-UCm9FftA.js → ar-SA-G6X2FPQ2-DBW5vJrl.js} +1 -1
- package/ui-dist/assets/architecture-7GRP2DOG-RDk7_3oa.js +1 -0
- package/ui-dist/assets/{architectureDiagram-5GKGNRK7-DIw1pNEk.js → architectureDiagram-5GKGNRK7-DSy67j6A.js} +1 -1
- package/ui-dist/assets/{az-AZ-76LH7QW2-B5LEIxkk.js → az-AZ-76LH7QW2-BmAulnA5.js} +1 -1
- package/ui-dist/assets/{bg-BG-XCXSNQG7-2bpThpZL.js → bg-BG-XCXSNQG7-MsfQK8qt.js} +1 -1
- package/ui-dist/assets/{blockDiagram-NRAW4CY4-BGr2WGp_.js → blockDiagram-NRAW4CY4-pYwdCg5K.js} +1 -1
- package/ui-dist/assets/{bn-BD-2XOGV67Q-Drz-y_cX.js → bn-BD-2XOGV67Q-YBAjV44K.js} +1 -1
- package/ui-dist/assets/{c4Diagram-UCG6FXSJ-vbm87bpC.js → c4Diagram-UCG6FXSJ-Cw8oeBvh.js} +1 -1
- package/ui-dist/assets/{ca-ES-6MX7JW3Y-D89p_FZ-.js → ca-ES-6MX7JW3Y-DowmBq0t.js} +1 -1
- package/ui-dist/assets/channel-DLSyDDT5.js +1 -0
- package/ui-dist/assets/{chunk-75Z2AOVW-B7kf5jVz.js → chunk-75Z2AOVW-C_9Gsbyz.js} +1 -1
- package/ui-dist/assets/{chunk-DU6HZSFF-BlGPS6B6.js → chunk-DU6HZSFF-CDriaWMp.js} +1 -1
- package/ui-dist/assets/{chunk-EIO257PC-nzXnwORk.js → chunk-EIO257PC-D_pewQb7.js} +1 -1
- package/ui-dist/assets/{chunk-F27PBJKO-B7qepoj_.js → chunk-F27PBJKO-Bjx929X5.js} +1 -1
- package/ui-dist/assets/{chunk-G27WJ6UU-CSjcvmOf.js → chunk-G27WJ6UU-CUP8Px4l.js} +1 -1
- package/ui-dist/assets/{chunk-GMAD6QVW-CtaZAO5u.js → chunk-GMAD6QVW-BDiTKJ44.js} +1 -1
- package/ui-dist/assets/{chunk-JQ64N6SF-CuhtdTse.js → chunk-JQ64N6SF-B-1BJXWR.js} +1 -1
- package/ui-dist/assets/{chunk-LCL6LL3I-DMfA3a_s.js → chunk-LCL6LL3I-BL1AQkvu.js} +1 -1
- package/ui-dist/assets/{chunk-OBVCFTLP-BoBuso2l.js → chunk-OBVCFTLP-BCWN-rmO.js} +1 -1
- package/ui-dist/assets/{chunk-P2QGCYS3-BZHK87sP.js → chunk-P2QGCYS3-CmgTdIFO.js} +1 -1
- package/ui-dist/assets/{chunk-POPQ4Y6H-Dk_v51-0.js → chunk-POPQ4Y6H-KQZYy9sO.js} +1 -1
- package/ui-dist/assets/{chunk-PWAF6VOD-DQTvHbxy.js → chunk-PWAF6VOD-BouCnRui.js} +1 -1
- package/ui-dist/assets/{chunk-R7TYR2AO-CQ_StUs1.js → chunk-R7TYR2AO-_u4RvBBs.js} +1 -1
- package/ui-dist/assets/{chunk-RHFEMEQ7-BTgGEQit.js → chunk-RHFEMEQ7-wg76vEGp.js} +1 -1
- package/ui-dist/assets/{chunk-SVP7TREG-lkVXeHxJ.js → chunk-SVP7TREG-DbwdbtHd.js} +1 -1
- package/ui-dist/assets/{chunk-TEH6E4GO-DIEC-0PO.js → chunk-TEH6E4GO-BzhVoidg.js} +1 -1
- package/ui-dist/assets/classDiagram-DTDB5LWJ-gerVLyI5.js +1 -0
- package/ui-dist/assets/classDiagram-v2-JRS7N3AN-gerVLyI5.js +1 -0
- package/ui-dist/assets/{cs-CZ-2BRQDIVT-CmSaQCAW.js → cs-CZ-2BRQDIVT-DF4u_CNK.js} +1 -1
- package/ui-dist/assets/{cynefin-OW5HDTMX-B6f8qooM.js → cynefin-OW5HDTMX-_c7TUx7k.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-5FMLGOSQ-DFu8uB2c.js → cynefinDiagram-5FMLGOSQ-Baphukwe.js} +1 -1
- package/ui-dist/assets/{da-DK-5WZEPLOC-DZ68cfJh.js → da-DK-5WZEPLOC-BHVp_PHH.js} +1 -1
- package/ui-dist/assets/{dagre-3AP2YEHR-CuUE3cEY.js → dagre-3AP2YEHR-0kiD869k.js} +1 -1
- package/ui-dist/assets/{de-DE-XR44H4JA-ClBUqbwZ.js → de-DE-XR44H4JA-CgW8oFzH.js} +1 -1
- package/ui-dist/assets/{diagram-S7CK7UJ4-DjLndaJK.js → diagram-S7CK7UJ4-zoqnxuLt.js} +1 -1
- package/ui-dist/assets/{diagram-UQ7AKVKN-wLhxEBwN.js → diagram-UQ7AKVKN-DMMWXR23.js} +1 -1
- package/ui-dist/assets/{diagram-VSXAHHWV-CVek4gvW.js → diagram-VSXAHHWV-HjDZHJFx.js} +1 -1
- package/ui-dist/assets/{diagram-VX7I27RA-CXyuaDuW.js → diagram-VX7I27RA-W7XiAa3Y.js} +1 -1
- package/ui-dist/assets/{diagram-Z3DM3KII-BGk8dw5c.js → diagram-Z3DM3KII-Ch_-xBqE.js} +1 -1
- package/ui-dist/assets/{dist-DyDCC61R.js → dist-BWboiH3s.js} +4 -4
- package/ui-dist/assets/{ebnfDiagram-PWID7BFC-Bq5wdWGX.js → ebnfDiagram-PWID7BFC-CKRspb8c.js} +1 -1
- package/ui-dist/assets/{el-GR-BZB4AONW-oCuJWxYE.js → el-GR-BZB4AONW-DH-i72OV.js} +1 -1
- package/ui-dist/assets/{en-B4ZKOASM-DevQWFhD.js → en-B4ZKOASM-BHQxRb4V.js} +1 -1
- package/ui-dist/assets/{erDiagram-SSCWMZ5O-CXZW4I_b.js → erDiagram-SSCWMZ5O-zX3V6vuZ.js} +1 -1
- package/ui-dist/assets/{es-ES-U4NZUMDT-DgzMLG1J.js → es-ES-U4NZUMDT-eUPIrX1m.js} +1 -1
- package/ui-dist/assets/{eu-ES-A7QVB2H4-1tA3vosZ.js → eu-ES-A7QVB2H4-Cf5MkJHT.js} +1 -1
- package/ui-dist/assets/eventmodeling-NTZA5JFV-DJ7hv96e.js +1 -0
- package/ui-dist/assets/{fa-IR-HGAKTJCU-BVbN6Ifb.js → fa-IR-HGAKTJCU-G0NL5O32.js} +1 -1
- package/ui-dist/assets/{fi-FI-Z5N7JZ37-DsGMdP7T.js → fi-FI-Z5N7JZ37-eDytnoCD.js} +1 -1
- package/ui-dist/assets/flowDiagram-A5DVABFB-B14HNvA3.js +1 -0
- package/ui-dist/assets/{fr-FR-RHASNOE6-BEZa94DA.js → fr-FR-RHASNOE6-CDn5YGxh.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-EL5Y4UJY-BZxyNgRP.js → ganttDiagram-EL5Y4UJY-Cgpii2nh.js} +1 -1
- package/ui-dist/assets/{gitGraph-4MIJSDKK-Cz7F-_6d.js → gitGraph-4MIJSDKK-B2FLI0YT.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-WWUBYQGX-77gOaAOD.js → gitGraphDiagram-WWUBYQGX-BFZVNaJk.js} +1 -1
- package/ui-dist/assets/{gl-ES-HMX3MZ6V-DfSDz_sy.js → gl-ES-HMX3MZ6V-DqTa_6zQ.js} +1 -1
- package/ui-dist/assets/{he-IL-6SHJWFNN-DT8nqxK2.js → he-IL-6SHJWFNN-CF_hmTDD.js} +1 -1
- package/ui-dist/assets/{hi-IN-IWLTKZ5I-BtL1eizk.js → hi-IN-IWLTKZ5I-BurR-HWO.js} +1 -1
- package/ui-dist/assets/{hu-HU-A5ZG7DT2-5FwjCLbT.js → hu-HU-A5ZG7DT2-CTMiHFNL.js} +1 -1
- package/ui-dist/assets/{id-ID-SAP4L64H-DXfUbYET.js → id-ID-SAP4L64H-v9eAe-Pl.js} +1 -1
- package/ui-dist/assets/image-GAAHSSAO-aYcCZOxV.js +1 -0
- package/ui-dist/assets/{index-Boxo043A.js → index-DyAw3r3e.js} +8 -8
- package/ui-dist/assets/{info-A6RAGUB7-DIe8iET8.js → info-A6RAGUB7-D9vkABZg.js} +1 -1
- package/ui-dist/assets/{infoDiagram-RXCK75RN-Dik-rzpk.js → infoDiagram-RXCK75RN-DkIWJAJT.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-5VMMS53U-Ccb-hHW6.js → ishikawaDiagram-5VMMS53U-Bft8Dz3r.js} +1 -1
- package/ui-dist/assets/{it-IT-JPQ66NNP-6_sPHBVO.js → it-IT-JPQ66NNP-BBzW6blD.js} +1 -1
- package/ui-dist/assets/{ja-JP-DBVTYXUO-DgY_5_c8.js → ja-JP-DBVTYXUO-BTXAY95B.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-EYS64GPL-CgzxmpyV.js → journeyDiagram-EYS64GPL-CNPbcef4.js} +1 -1
- package/ui-dist/assets/{kaa-6HZHGXH3-glmGx672.js → kaa-6HZHGXH3-DYouzW_m.js} +1 -1
- package/ui-dist/assets/{kab-KAB-ZGHBKWFO--G7baVL0.js → kab-KAB-ZGHBKWFO-DEG1xQQq.js} +1 -1
- package/ui-dist/assets/{kanban-definition-3QL26DDD-D6TnbrdQ.js → kanban-definition-3QL26DDD-DS4F4kyE.js} +1 -1
- package/ui-dist/assets/{kk-KZ-P5N5QNE5-D3Vn0qgF.js → kk-KZ-P5N5QNE5-CPOAfzr_.js} +1 -1
- package/ui-dist/assets/{km-KH-HSX4SM5Z-CAHt3o8M.js → km-KH-HSX4SM5Z-BO5OGKgL.js} +1 -1
- package/ui-dist/assets/{ko-KR-MTYHY66A-N3tCiPap.js → ko-KR-MTYHY66A-D_3EAP_r.js} +1 -1
- package/ui-dist/assets/{ku-TR-6OUDTVRD-CSZFwS--.js → ku-TR-6OUDTVRD-C5yHTP7U.js} +1 -1
- package/ui-dist/assets/{line-BhVZwKwh.js → line-BOfOxQ0L.js} +1 -1
- package/ui-dist/assets/{lt-LT-XHIRWOB4-DuSlkfc-.js → lt-LT-XHIRWOB4-D8sald8e.js} +1 -1
- package/ui-dist/assets/{lv-LV-5QDEKY6T-COuWGNLd.js → lv-LV-5QDEKY6T-nMvlSBwd.js} +1 -1
- package/ui-dist/assets/{mermaid-parser.core-DlqbfUOM.js → mermaid-parser.core-DbslZ7z6.js} +3 -3
- package/ui-dist/assets/{mindmap-definition-FBJOCRG2-D9wLjZXa.js → mindmap-definition-FBJOCRG2-CC7YRs1I.js} +1 -1
- package/ui-dist/assets/{mr-IN-CRQNXWMA-Bv4WHAzM.js → mr-IN-CRQNXWMA-DwYEdJ6g.js} +1 -1
- package/ui-dist/assets/{my-MM-5M5IBNSE-BNltBCCh.js → my-MM-5M5IBNSE-CkVtPsgu.js} +1 -1
- package/ui-dist/assets/{nb-NO-T6EIAALU-DgsGabVE.js → nb-NO-T6EIAALU-CfcrwCef.js} +1 -1
- package/ui-dist/assets/{nl-NL-IS3SIHDZ-MPMxu0cH.js → nl-NL-IS3SIHDZ-DPVjOdP8.js} +1 -1
- package/ui-dist/assets/{nn-NO-6E72VCQL-CY-NT6bD.js → nn-NO-6E72VCQL-z4XCBvtQ.js} +1 -1
- package/ui-dist/assets/{oc-FR-POXYY2M6-BCWtA2dJ.js → oc-FR-POXYY2M6-XiwDZwgM.js} +1 -1
- package/ui-dist/assets/{pa-IN-N4M65BXN-CU1D5Lce.js → pa-IN-N4M65BXN-DuXPS7Vb.js} +1 -1
- package/ui-dist/assets/{packet-AYTQ26CC-DCMIIosG.js → packet-AYTQ26CC-B4TcY0kB.js} +1 -1
- package/ui-dist/assets/{pegDiagram-XKGWAZYB-CpU2aj-A.js → pegDiagram-XKGWAZYB-DUSJlo42.js} +1 -1
- package/ui-dist/assets/percentages-BXMCSKIN-B9EK3GBV.js +1 -0
- package/ui-dist/assets/{pie-WAS4IAKB-BEVkH0em.js → pie-WAS4IAKB-T3VzDsca.js} +1 -1
- package/ui-dist/assets/{pieDiagram-E7YTZNPT-D-jyX8nJ.js → pieDiagram-E7YTZNPT-gIAK7l_u.js} +1 -1
- package/ui-dist/assets/{pl-PL-T2D74RX3-CwNWACCb.js → pl-PL-T2D74RX3-D00kamQC.js} +1 -1
- package/ui-dist/assets/{pt-BR-5N22H2LF-DCs1W5P0.js → pt-BR-5N22H2LF-C5ZNqytO.js} +1 -1
- package/ui-dist/assets/{pt-PT-UZXXM6DQ-Bm9Hlh9S.js → pt-PT-UZXXM6DQ-DJoIVZb6.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-AXDQQJYC-BqzJD4zL.js → quadrantDiagram-AXDQQJYC-QB418pxc.js} +1 -1
- package/ui-dist/assets/{radar-RG4KPBEZ-B6T3SZ4z.js → radar-RG4KPBEZ-BNg2Od-m.js} +1 -1
- package/ui-dist/assets/{railroad-74A4TZTK-BPKCaP4g.js → railroad-74A4TZTK-HxmWWcYY.js} +1 -1
- package/ui-dist/assets/railroad-abnf-HS5TGJTU-E1hw9v0U.js +1 -0
- package/ui-dist/assets/railroad-ebnf-LZEXJU2U-D11k3-Yx.js +1 -0
- package/ui-dist/assets/railroad-peg-WCYAUIDC-djipl3Qi.js +1 -0
- package/ui-dist/assets/{railroadDiagram-O6MQD6OU-CzTuUKE-.js → railroadDiagram-O6MQD6OU-BhkRr0Xw.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-EFPCY7ZU-Bly8_reu.js → requirementDiagram-EFPCY7ZU-oFFR0XIe.js} +1 -1
- package/ui-dist/assets/{ro-RO-JPDTUUEW-DbKkWzmT.js → ro-RO-JPDTUUEW-BVCBG6Yb.js} +1 -1
- package/ui-dist/assets/{ru-RU-B4JR7IUQ-C7y8hptt.js → ru-RU-B4JR7IUQ-CL22wPes.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-P5KCCOFB-BNe1jsYh.js → sankeyDiagram-P5KCCOFB-Cod_IgpA.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-WJ2MYXX4-jpU3VDzA.js → sequenceDiagram-WJ2MYXX4-BS1ccxYk.js} +1 -1
- package/ui-dist/assets/{si-LK-N5RQ5JYF-DsyN9eZC.js → si-LK-N5RQ5JYF-DLaLR7Cr.js} +1 -1
- package/ui-dist/assets/{sk-SK-C5VTKIMK-C3GmutL2.js → sk-SK-C5VTKIMK-ZkUf5uTC.js} +1 -1
- package/ui-dist/assets/{sl-SI-NN7IZMDC-C6dxM6aa.js → sl-SI-NN7IZMDC-EUpD9Npa.js} +1 -1
- package/ui-dist/assets/{stateDiagram-HBIQ2CUA-B2ZRnydN.js → stateDiagram-HBIQ2CUA-bZ2iHCKf.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-4QOOHH4V-UKcZzetZ.js +1 -0
- package/ui-dist/assets/subset-shared.chunk-DiI1sxPG.js +1 -0
- package/ui-dist/assets/{subset-worker.chunk-WItGSQq_.js → subset-worker.chunk-TUbActJA.js} +1 -1
- package/ui-dist/assets/{sv-SE-XGPEYMSR-BPU1z_vz.js → sv-SE-XGPEYMSR-Cn1sCwWu.js} +1 -1
- package/ui-dist/assets/{swimlanes-XN3QIQJK-BuYIN15M.js → swimlanes-XN3QIQJK-CuBYIsLr.js} +1 -1
- package/ui-dist/assets/swimlanesDiagram-VK2B7HYN-E0ldUagU.js +8 -0
- package/ui-dist/assets/{ta-IN-2NMHFXQM-AJVbdGTO.js → ta-IN-2NMHFXQM-ChpHwna3.js} +1 -1
- package/ui-dist/assets/{th-TH-HPSO5L25-B1vvYNmv.js → th-TH-HPSO5L25-DmDVSHGj.js} +1 -1
- package/ui-dist/assets/{timeline-definition-24CTP7MA-Bld3Oj9i.js → timeline-definition-24CTP7MA-C2U3-ZAF.js} +1 -1
- package/ui-dist/assets/{tr-TR-DEFEU3FU-C_emJDca.js → tr-TR-DEFEU3FU-BUP3Lszu.js} +1 -1
- package/ui-dist/assets/{treeView-Q6P3EWNA-CQEi50Re.js → treeView-Q6P3EWNA-CySxZ2VI.js} +1 -1
- package/ui-dist/assets/{treemap-WGGIJYW6-D3gRwGbr.js → treemap-WGGIJYW6-D_4Wf_x5.js} +1 -1
- package/ui-dist/assets/{uk-UA-QMV73CPH-B69Ddo0p.js → uk-UA-QMV73CPH-BDn1-hez.js} +1 -1
- package/ui-dist/assets/{vennDiagram-4TSXK5OY-BWtR81LD.js → vennDiagram-4TSXK5OY-DoYDFYaB.js} +1 -1
- package/ui-dist/assets/{vi-VN-M7AON7JQ-ByM0OkIF.js → vi-VN-M7AON7JQ-D1JkmMTs.js} +1 -1
- package/ui-dist/assets/{wardley-WFR3VGLG-DpBUmo8s.js → wardley-WFR3VGLG-3NuaJoyt.js} +1 -1
- package/ui-dist/assets/{wardleyDiagram-VM6X3IG4-n39Dh9vZ.js → wardleyDiagram-VM6X3IG4-BkSb9uJ5.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-S5SC5T6Z-D8-lcdGr.js → xychartDiagram-S5SC5T6Z-DaStHnZJ.js} +1 -1
- package/ui-dist/assets/{zh-CN-LNUGB5OW-g_S93sCp.js → zh-CN-LNUGB5OW-D6ucAnvY.js} +1 -1
- package/ui-dist/assets/{zh-HK-E62DVLB3-B3J_lyrF.js → zh-HK-E62DVLB3-tvTQyrAF.js} +1 -1
- package/ui-dist/assets/{zh-TW-RAJ6MFWO-mYLP5aqQ.js → zh-TW-RAJ6MFWO-y6am-Uwk.js} +1 -1
- package/ui-dist/index.html +1 -1
- package/ui-dist/assets/architecture-7GRP2DOG-CiyYoVgW.js +0 -1
- package/ui-dist/assets/channel-DtgwkzBZ.js +0 -1
- package/ui-dist/assets/classDiagram-DTDB5LWJ-B83TJF6p.js +0 -1
- package/ui-dist/assets/classDiagram-v2-JRS7N3AN-B83TJF6p.js +0 -1
- package/ui-dist/assets/eventmodeling-NTZA5JFV-Bh0QsTUi.js +0 -1
- package/ui-dist/assets/flowDiagram-A5DVABFB-Chb6hSSG.js +0 -1
- package/ui-dist/assets/image-GAAHSSAO-AgKiDvHK.js +0 -1
- package/ui-dist/assets/percentages-BXMCSKIN-CMz17NMy.js +0 -1
- package/ui-dist/assets/railroad-abnf-HS5TGJTU-RL2kk25t.js +0 -1
- package/ui-dist/assets/railroad-ebnf-LZEXJU2U-NY4mALmg.js +0 -1
- package/ui-dist/assets/railroad-peg-WCYAUIDC-BNAC9MGy.js +0 -1
- package/ui-dist/assets/stateDiagram-v2-4QOOHH4V-BUscqu-N.js +0 -1
- package/ui-dist/assets/subset-shared.chunk-CRXudW0i.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-VK2B7HYN-Cn_HC7uw.js +0 -8
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { SceneElement, PackName, PackKind, IconId, SceneId, Scene, SnapshotId, SERVER_MANAGED_FIELDS, BINDING_FIELDS, GUIDE_TOPICS, DetailLevel, createNodeSkeletonExpander, ok, fail, bridgeSpawnFailed, bridgeVersionMismatch, bridgeUnreachable, GuideTopic, storeUnavailable, describeThrown, unreadableSceneFile, pathOutsideRepo, packInstallFailed, sceneFileMissing, notAPack, PackRecipe, portUnavailable, DescribeSceneOutput, DescribeSceneInput, GetSelectionOutput, GetSelectionInput, GetElementsOutput, GetElementsInput, ListScenesOutput, ListScenesInput, SceneSettingsOutput, SceneSettingsInput, SearchIconsOutput, SearchIconsInput, ScreenshotOutput, ScreenshotInput, ReadMeOutput, ReadMeInput, PlaceIconOutput, PlaceIconInput, SnapshotOutput, SnapshotInput, RestoreSnapshotOutput, RestoreSnapshotInput, ExportSceneOutput, ExportSceneInput, ImportSceneOutput, ImportSceneInput, ExportImageOutput, ExportImageInput, InstallPackOutput, InstallPackInput, notARepo, ScreenshotResponse, RemovePackOutput, ListPacksOutput, RawElementsOutput, FocusOutput, EmbedOutput, AnnotateOutput, FrameOutput, UngroupOutput, GroupOutput, ArrangeOutput, DeleteElementsOutput, UpdateElementsOutput, ConnectOutput, CreateElementsOutput, HealthResponse, iconIdOf, unknownScene, unknownSnapshot, exportFailed, unknownIcon, HOUSE, FileId, ElementId, diskConflict, refusedFieldMessage, idCollision, expansionFailed, emptySelection, unknownElement, frameIdOf, GroupId, staleVersion, noBoundLabel, deepEqual, exportTimedOut, noBrowser, DebugElementRequest, CreateElementsInput, ConnectToolInput, UpdateElementsInput, DeleteElementsInput, ArrangeToolInput, GroupInput, UngroupInput, FrameInput, AnnotateInput, EmbedInput, FocusInput, RawElementsInput, ListPacksInput, RemovePackInput, iconNeedsAPlace, DataUrl, Index, ROUNDNESS, STALE_SELECTION_MS, noScenes, ClientMessage, assertNever, PROTOCOL_VERSION, ApiError, EXCALIDRAW_FILE_VERSION, EXCALIDRAW_FILE_TYPE, point, FLAG_GLYPH,
|
|
2
|
+
import { SceneElement, PackName, PackKind, IconId, SceneId, Scene, SnapshotId, SERVER_MANAGED_FIELDS, BINDING_FIELDS, GUIDE_TOPICS, DetailLevel, createNodeSkeletonExpander, ok, fail, bridgeSpawnFailed, bridgeVersionMismatch, bridgeUnreachable, GuideTopic, storeUnavailable, describeThrown, unreadableSceneFile, pathOutsideRepo, packInstallFailed, sceneFileMissing, notAPack, PackRecipe, portUnavailable, DescribeSceneOutput, DescribeSceneInput, GetSelectionOutput, GetSelectionInput, GetElementsOutput, GetElementsInput, ListScenesOutput, ListScenesInput, SceneSettingsOutput, SceneSettingsInput, SearchIconsOutput, SearchIconsInput, ScreenshotOutput, ScreenshotInput, ReadMeOutput, ReadMeInput, PlaceIconOutput, PlaceIconInput, SnapshotOutput, SnapshotInput, RestoreSnapshotOutput, RestoreSnapshotInput, ExportSceneOutput, ExportSceneInput, ImportSceneOutput, ImportSceneInput, ExportImageOutput, ExportImageInput, InstallPackOutput, InstallPackInput, notARepo, ScreenshotResponse, RemovePackOutput, ListPacksOutput, RawElementsOutput, FocusOutput, EmbedOutput, AnnotateOutput, FrameOutput, UngroupOutput, GroupOutput, ArrangeOutput, DeleteElementsOutput, UpdateElementsOutput, ConnectOutput, CreateElementsOutput, HealthResponse, iconIdOf, unknownScene, unknownSnapshot, exportFailed, unknownIcon, HOUSE, FileId, ElementId, diskConflict, refusedFieldMessage, idCollision, expansionFailed, emptySelection, unknownElement, measureLine, frameIdOf, GroupId, staleVersion, noBoundLabel, deepEqual, exportTimedOut, noBrowser, DebugElementRequest, CreateElementsInput, ConnectToolInput, UpdateElementsInput, DeleteElementsInput, ArrangeToolInput, GroupInput, UngroupInput, FrameInput, AnnotateInput, EmbedInput, FocusInput, RawElementsInput, ListPacksInput, RemovePackInput, iconNeedsAPlace, DataUrl, Index, wrapLines, ROUNDNESS, STALE_SELECTION_MS, noScenes, ClientMessage, assertNever, PROTOCOL_VERSION, ApiError, EXCALIDRAW_FILE_VERSION, EXCALIDRAW_FILE_TYPE, point, FLAG_GLYPH, KIND_ABBREVIATION, catalogUnavailable, FrameId, IconItem, ExcalidrawFile } from './chunk-BCHUGREF.js';
|
|
3
3
|
import './chunk-7D4SUZUM.js';
|
|
4
4
|
import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
@@ -2128,6 +2128,151 @@ function overlapWarning(elements) {
|
|
|
2128
2128
|
return `${count2} on the canvas \u2014 worst ${worst.a} \xD7 ${worst.b} (${size}). chalk_describe_scene lists them; chalk_arrange moves them apart`;
|
|
2129
2129
|
}
|
|
2130
2130
|
|
|
2131
|
+
// src/domain/scene/anchors.ts
|
|
2132
|
+
var ANCHOR_GAP = 12;
|
|
2133
|
+
var anchorsOf = (element) => element.customData?.chalk?.anchorIds ?? [];
|
|
2134
|
+
var isAnchored = (element) => anchorsOf(element).length > 0;
|
|
2135
|
+
function anchoredPosition(anchor, size, placement) {
|
|
2136
|
+
const midX = anchor.x + anchor.width / 2 - size.width / 2;
|
|
2137
|
+
const midY = anchor.y + anchor.height / 2 - size.height / 2;
|
|
2138
|
+
switch (placement) {
|
|
2139
|
+
case "above":
|
|
2140
|
+
return { x: midX, y: anchor.y - size.height - ANCHOR_GAP };
|
|
2141
|
+
case "below":
|
|
2142
|
+
return { x: midX, y: anchor.y + anchor.height + ANCHOR_GAP };
|
|
2143
|
+
case "left":
|
|
2144
|
+
return { x: anchor.x - size.width - ANCHOR_GAP, y: midY };
|
|
2145
|
+
case "right":
|
|
2146
|
+
return { x: anchor.x + anchor.width + ANCHOR_GAP, y: midY };
|
|
2147
|
+
case "badge":
|
|
2148
|
+
return { x: anchor.x + anchor.width - size.width / 2, y: anchor.y - size.height / 2 };
|
|
2149
|
+
case "inside":
|
|
2150
|
+
return { x: midX, y: midY };
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
var captionBelow = (position, size) => ({
|
|
2154
|
+
x: position.x,
|
|
2155
|
+
y: position.y + size.height + 8
|
|
2156
|
+
});
|
|
2157
|
+
function withAnchoredElements(scene, deltas) {
|
|
2158
|
+
const attached = scene.filter((element) => !element.isDeleted && isAnchored(element));
|
|
2159
|
+
if (attached.length === 0) return deltas;
|
|
2160
|
+
const widened = new Map(deltas);
|
|
2161
|
+
const present2 = new Set(scene.filter((element) => !element.isDeleted).map((e) => e.id));
|
|
2162
|
+
for (const element of attached) {
|
|
2163
|
+
if (widened.has(element.id)) continue;
|
|
2164
|
+
const live = anchorsOf(element).filter((id) => present2.has(id));
|
|
2165
|
+
if (live.length === 0) continue;
|
|
2166
|
+
const moves = live.map((id) => deltas.get(id));
|
|
2167
|
+
const [first] = moves;
|
|
2168
|
+
if (first === void 0) continue;
|
|
2169
|
+
if (!moves.every((move) => move !== void 0 && move[0] === first[0] && move[1] === first[1])) {
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
widened.set(element.id, first);
|
|
2173
|
+
}
|
|
2174
|
+
return widened;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
// src/domain/scene/recommend.ts
|
|
2178
|
+
var ENOUGH_TO_MATTER = 5;
|
|
2179
|
+
var HUB_SHARE = 0.5;
|
|
2180
|
+
var HUB_MIN_DEGREE = 4;
|
|
2181
|
+
var RING_LABEL_WIDTH = 200;
|
|
2182
|
+
function recommendLayout(scene, targetIds) {
|
|
2183
|
+
const wanted = new Set(targetIds);
|
|
2184
|
+
const nodes = scene.filter(
|
|
2185
|
+
(element) => wanted.has(element.id) && !isLinear(element) && !isAnchored(element) && !element.isDeleted
|
|
2186
|
+
);
|
|
2187
|
+
if (nodes.length < ENOUGH_TO_MATTER) return null;
|
|
2188
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
2189
|
+
const degree = new Map(nodes.map((node) => [node.id, 0]));
|
|
2190
|
+
const out = new Map(nodes.map((node) => [node.id, []]));
|
|
2191
|
+
let edges = 0;
|
|
2192
|
+
for (const element of scene) {
|
|
2193
|
+
if (element.type !== "arrow" || element.isDeleted) continue;
|
|
2194
|
+
const from = startOf(element);
|
|
2195
|
+
const to = endOf(element);
|
|
2196
|
+
if (from === null || to === null || from === to) continue;
|
|
2197
|
+
if (!ids.has(from) || !ids.has(to)) continue;
|
|
2198
|
+
edges += 1;
|
|
2199
|
+
degree.set(from, (degree.get(from) ?? 0) + 1);
|
|
2200
|
+
degree.set(to, (degree.get(to) ?? 0) + 1);
|
|
2201
|
+
out.get(from)?.push(to);
|
|
2202
|
+
}
|
|
2203
|
+
if (edges === 0) {
|
|
2204
|
+
return {
|
|
2205
|
+
op: "grid",
|
|
2206
|
+
why: `${nodes.length} elements with no arrows between them \u2014 chalk_arrange(op: "grid") is the layout for a set rather than a graph`
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
const busiest = Math.max(...degree.values());
|
|
2210
|
+
const hubShare = busiest / (nodes.length - 1);
|
|
2211
|
+
const depth = longestChain(nodes, out);
|
|
2212
|
+
const widestLabel = Math.max(...nodes.map((node) => node.width));
|
|
2213
|
+
if (hubShare >= HUB_SHARE && busiest >= HUB_MIN_DEGREE) {
|
|
2214
|
+
const hub = nodes.find((node) => (degree.get(node.id) ?? 0) === busiest);
|
|
2215
|
+
const name = hub === void 0 ? "one element" : shortLabel(scene, hub);
|
|
2216
|
+
return widestLabel > RING_LABEL_WIDTH ? {
|
|
2217
|
+
op: "layout-tree",
|
|
2218
|
+
why: `${name} touches ${busiest} of ${nodes.length - 1} others and the labels are wide \u2014 chalk_arrange(op: "layout-tree") puts it on the left and fans the rest out; a ring this wide crosses its own middle`
|
|
2219
|
+
} : {
|
|
2220
|
+
op: "layout-radial",
|
|
2221
|
+
why: `${name} touches ${busiest} of ${nodes.length - 1} others \u2014 chalk_arrange(op: "layout-radial") puts it at the centre, which ranks cannot show`
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
return {
|
|
2225
|
+
op: "layout-layered",
|
|
2226
|
+
why: `${nodes.length} elements, ${edges} arrows, longest chain ${depth} \u2014 chalk_arrange(op: "layout-layered") ranks them by the direction of the arrows`
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
function longestChain(nodes, out) {
|
|
2230
|
+
const ON_STACK = 1;
|
|
2231
|
+
const DONE = 2;
|
|
2232
|
+
const state = /* @__PURE__ */ new Map();
|
|
2233
|
+
const depth = /* @__PURE__ */ new Map();
|
|
2234
|
+
const visit = (start) => {
|
|
2235
|
+
const stack = [{ node: start, cursor: 0 }];
|
|
2236
|
+
state.set(start, ON_STACK);
|
|
2237
|
+
while (stack.length > 0) {
|
|
2238
|
+
const frame = stack[stack.length - 1];
|
|
2239
|
+
if (frame === void 0) break;
|
|
2240
|
+
const kids = out.get(frame.node) ?? [];
|
|
2241
|
+
if (frame.cursor >= kids.length) {
|
|
2242
|
+
let best = 1;
|
|
2243
|
+
for (const kid2 of kids) {
|
|
2244
|
+
if (state.get(kid2) === DONE) best = Math.max(best, 1 + (depth.get(kid2) ?? 0));
|
|
2245
|
+
}
|
|
2246
|
+
depth.set(frame.node, best);
|
|
2247
|
+
state.set(frame.node, DONE);
|
|
2248
|
+
stack.pop();
|
|
2249
|
+
continue;
|
|
2250
|
+
}
|
|
2251
|
+
const kid = kids[frame.cursor];
|
|
2252
|
+
frame.cursor += 1;
|
|
2253
|
+
if (kid === void 0 || state.has(kid)) continue;
|
|
2254
|
+
state.set(kid, ON_STACK);
|
|
2255
|
+
stack.push({ node: kid, cursor: 0 });
|
|
2256
|
+
}
|
|
2257
|
+
return depth.get(start) ?? 1;
|
|
2258
|
+
};
|
|
2259
|
+
let longest = 0;
|
|
2260
|
+
for (const node of [...nodes].sort((a, b) => a.id < b.id ? -1 : 1)) {
|
|
2261
|
+
if (!state.has(node.id)) visit(node.id);
|
|
2262
|
+
longest = Math.max(longest, depth.get(node.id) ?? 1);
|
|
2263
|
+
}
|
|
2264
|
+
return longest;
|
|
2265
|
+
}
|
|
2266
|
+
function shortLabel(scene, element) {
|
|
2267
|
+
const bound = (element.boundElements ?? []).find((b) => b.type === "text")?.id;
|
|
2268
|
+
const caption = bound === void 0 ? void 0 : scene.find((e) => e.id === bound);
|
|
2269
|
+
if (caption !== void 0 && caption.type === "text") {
|
|
2270
|
+
const text = (caption.originalText || caption.text).replace(/\n/g, " ").trim();
|
|
2271
|
+
if (text !== "") return `"${text}"`;
|
|
2272
|
+
}
|
|
2273
|
+
return String(element.id).slice(0, 4);
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2131
2276
|
// src/domain/scene/observe.ts
|
|
2132
2277
|
var SHORT_ID_LENGTH = 4;
|
|
2133
2278
|
var shortId2 = (id) => id.slice(0, SHORT_ID_LENGTH);
|
|
@@ -2245,6 +2390,12 @@ function observeScene(input) {
|
|
|
2245
2390
|
elements: rows.map((element) => describeElement(element, context)),
|
|
2246
2391
|
overlaps: overlaps.slice(0, MAX_REPORTED_OVERLAPS),
|
|
2247
2392
|
overlapCount: overlaps.length,
|
|
2393
|
+
// Over the rows, not the raw elements: a caption is part of the box it names, and
|
|
2394
|
+
// counting one as a node would halve every degree the advice is computed from.
|
|
2395
|
+
layoutAdvice: recommendLayout(
|
|
2396
|
+
scene.elements,
|
|
2397
|
+
rows.map((element) => element.id)
|
|
2398
|
+
)?.why ?? null,
|
|
2248
2399
|
detail: input.detail
|
|
2249
2400
|
};
|
|
2250
2401
|
}
|
|
@@ -3319,8 +3470,108 @@ function labelledSize(label, fontSize = HOUSE.fontSize) {
|
|
|
3319
3470
|
return { width, height };
|
|
3320
3471
|
}
|
|
3321
3472
|
|
|
3473
|
+
// src/domain/scene/skeleton.ts
|
|
3474
|
+
var isContainer = (type) => type === "rectangle" || type === "diamond" || type === "ellipse";
|
|
3475
|
+
function explicit(input) {
|
|
3476
|
+
const kept = {};
|
|
3477
|
+
for (const [key, value] of Object.entries(input)) {
|
|
3478
|
+
if (value === void 0) continue;
|
|
3479
|
+
if (key === "type" || key === "id" || key === "role" || key === "label") continue;
|
|
3480
|
+
kept[key] = value;
|
|
3481
|
+
}
|
|
3482
|
+
return kept;
|
|
3483
|
+
}
|
|
3484
|
+
var defaultSize = (element) => labelledSize(element.label, element.fontSize ?? HOUSE.fontSize);
|
|
3485
|
+
function noteWrap(text, fontSize, requested) {
|
|
3486
|
+
const column = requested ?? HOUSE.sizes.note;
|
|
3487
|
+
if (text === "") return { text };
|
|
3488
|
+
if (requested === void 0 && measureLine(text, fontSize) <= column) return { text };
|
|
3489
|
+
const lines = wrapLines(text, fontSize, column);
|
|
3490
|
+
return { text: lines.join("\n"), width: column, autoResize: false };
|
|
3491
|
+
}
|
|
3492
|
+
function normalizeElements(input) {
|
|
3493
|
+
return input.elements.map((element, i) => {
|
|
3494
|
+
const fallback = input.ids[i];
|
|
3495
|
+
if (fallback === void 0) {
|
|
3496
|
+
throw new Error(`normalizeElements needs one id per element: none at position ${i}`);
|
|
3497
|
+
}
|
|
3498
|
+
const id = element.id ?? fallback;
|
|
3499
|
+
const role = styleForRole(element.role ?? "compute");
|
|
3500
|
+
const set = explicit(element);
|
|
3501
|
+
if (element.type === "text") {
|
|
3502
|
+
const text = element.text ?? element.label ?? "";
|
|
3503
|
+
const skeleton2 = {
|
|
3504
|
+
type: "text",
|
|
3505
|
+
id,
|
|
3506
|
+
x: element.x,
|
|
3507
|
+
y: element.y,
|
|
3508
|
+
fontSize: HOUSE.fontSize,
|
|
3509
|
+
fontFamily: HOUSE.fontFamily,
|
|
3510
|
+
// A free-standing note is not a service box: it takes the role's ink but never
|
|
3511
|
+
// its fill, because a filled rectangle behind a caption reads as a shape.
|
|
3512
|
+
strokeColor: role.strokeColor,
|
|
3513
|
+
...set,
|
|
3514
|
+
// Last, because it owns `text`: `explicit` copies the caller's unwrapped string
|
|
3515
|
+
// across and this is what replaces it.
|
|
3516
|
+
...noteWrap(text, element.fontSize ?? HOUSE.fontSize, element.width)
|
|
3517
|
+
};
|
|
3518
|
+
return skeleton2;
|
|
3519
|
+
}
|
|
3520
|
+
if (element.type === "line") {
|
|
3521
|
+
const skeleton2 = {
|
|
3522
|
+
type: "line",
|
|
3523
|
+
id,
|
|
3524
|
+
x: element.x,
|
|
3525
|
+
y: element.y,
|
|
3526
|
+
strokeColor: role.strokeColor,
|
|
3527
|
+
strokeWidth: HOUSE.strokeWidth,
|
|
3528
|
+
roughness: HOUSE.roughness,
|
|
3529
|
+
...set,
|
|
3530
|
+
...element.label === void 0 ? {} : { label: houseLabel(element.label) }
|
|
3531
|
+
};
|
|
3532
|
+
return skeleton2;
|
|
3533
|
+
}
|
|
3534
|
+
if (!isContainer(element.type)) {
|
|
3535
|
+
throw new Error(`normalizeElements cannot build a ${element.type}`);
|
|
3536
|
+
}
|
|
3537
|
+
const skeleton = {
|
|
3538
|
+
type: element.type,
|
|
3539
|
+
id,
|
|
3540
|
+
x: element.x,
|
|
3541
|
+
y: element.y,
|
|
3542
|
+
...defaultSize(element),
|
|
3543
|
+
...role,
|
|
3544
|
+
...set,
|
|
3545
|
+
...element.label === void 0 ? {} : { label: houseLabel(element.label) }
|
|
3546
|
+
};
|
|
3547
|
+
return skeleton;
|
|
3548
|
+
});
|
|
3549
|
+
}
|
|
3550
|
+
var LAYOUT_HINT_THRESHOLD = 4;
|
|
3551
|
+
function layoutHint(elements) {
|
|
3552
|
+
if (elements.length < LAYOUT_HINT_THRESHOLD) return null;
|
|
3553
|
+
const positions = new Set(elements.map((element) => `${element.x},${element.y}`));
|
|
3554
|
+
if (positions.size > 2) return null;
|
|
3555
|
+
return `${elements.length} elements with no explicit layout \u2014 chalk_arrange(op: "layout-layered") will place them`;
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3322
3558
|
// src/app/usecases/write/annotate-elements.ts
|
|
3323
3559
|
var NOTE_GAP = 24;
|
|
3560
|
+
var NOTE_FONT_SIZE = 16;
|
|
3561
|
+
var NOTE_SEARCH_STEP = 40;
|
|
3562
|
+
var NOTE_SEARCH_LIMIT = 24;
|
|
3563
|
+
function clearSpotBelow(scene, preferred, size) {
|
|
3564
|
+
for (let i = 0; i <= NOTE_SEARCH_LIMIT; i++) {
|
|
3565
|
+
const candidate = {
|
|
3566
|
+
x: preferred.x,
|
|
3567
|
+
y: preferred.y + i * NOTE_SEARCH_STEP,
|
|
3568
|
+
width: size.width,
|
|
3569
|
+
height: size.height
|
|
3570
|
+
};
|
|
3571
|
+
if (!wouldCollide(candidate, scene)) return { x: candidate.x, y: candidate.y };
|
|
3572
|
+
}
|
|
3573
|
+
return preferred;
|
|
3574
|
+
}
|
|
3324
3575
|
function annotateElements(deps) {
|
|
3325
3576
|
return async (input) => {
|
|
3326
3577
|
const scene = deps.write.resolve(input.sceneId);
|
|
@@ -3335,14 +3586,20 @@ function annotateElements(deps) {
|
|
|
3335
3586
|
if (anchors.length === 0) return fail(emptySelection("chalk_annotate"));
|
|
3336
3587
|
const left = Math.min(...anchors.map((element) => element.x));
|
|
3337
3588
|
const bottom = Math.max(...anchors.map((element) => element.y + element.height));
|
|
3589
|
+
const wrapped = noteWrap(input.text, NOTE_FONT_SIZE, void 0);
|
|
3590
|
+
const size = {
|
|
3591
|
+
width: wrapped.width ?? measureLine(wrapped.text, NOTE_FONT_SIZE),
|
|
3592
|
+
height: wrapped.text.split("\n").length * NOTE_FONT_SIZE * LINE_HEIGHT
|
|
3593
|
+
};
|
|
3594
|
+
const spot = clearSpotBelow(scene.value.elements, { x: left, y: bottom + NOTE_GAP }, size);
|
|
3338
3595
|
const skeleton = {
|
|
3339
3596
|
type: "text",
|
|
3340
3597
|
id: deps.idGen.elementId(),
|
|
3341
|
-
x:
|
|
3342
|
-
y:
|
|
3343
|
-
|
|
3598
|
+
x: spot.x,
|
|
3599
|
+
y: spot.y,
|
|
3600
|
+
...wrapped,
|
|
3344
3601
|
// A size below the labels, so a note reads as commentary rather than as content.
|
|
3345
|
-
fontSize:
|
|
3602
|
+
fontSize: NOTE_FONT_SIZE,
|
|
3346
3603
|
fontFamily: HOUSE.fontFamily,
|
|
3347
3604
|
strokeColor: styleForRole("external").strokeColor,
|
|
3348
3605
|
customData: {
|
|
@@ -3397,52 +3654,6 @@ function focusElements(deps) {
|
|
|
3397
3654
|
};
|
|
3398
3655
|
}
|
|
3399
3656
|
|
|
3400
|
-
// src/domain/scene/anchors.ts
|
|
3401
|
-
var ANCHOR_GAP = 12;
|
|
3402
|
-
var anchorsOf = (element) => element.customData?.chalk?.anchorIds ?? [];
|
|
3403
|
-
var isAnchored = (element) => anchorsOf(element).length > 0;
|
|
3404
|
-
function anchoredPosition(anchor, size, placement) {
|
|
3405
|
-
const midX = anchor.x + anchor.width / 2 - size.width / 2;
|
|
3406
|
-
const midY = anchor.y + anchor.height / 2 - size.height / 2;
|
|
3407
|
-
switch (placement) {
|
|
3408
|
-
case "above":
|
|
3409
|
-
return { x: midX, y: anchor.y - size.height - ANCHOR_GAP };
|
|
3410
|
-
case "below":
|
|
3411
|
-
return { x: midX, y: anchor.y + anchor.height + ANCHOR_GAP };
|
|
3412
|
-
case "left":
|
|
3413
|
-
return { x: anchor.x - size.width - ANCHOR_GAP, y: midY };
|
|
3414
|
-
case "right":
|
|
3415
|
-
return { x: anchor.x + anchor.width + ANCHOR_GAP, y: midY };
|
|
3416
|
-
case "badge":
|
|
3417
|
-
return { x: anchor.x + anchor.width - size.width / 2, y: anchor.y - size.height / 2 };
|
|
3418
|
-
case "inside":
|
|
3419
|
-
return { x: midX, y: midY };
|
|
3420
|
-
}
|
|
3421
|
-
}
|
|
3422
|
-
var captionBelow = (position, size) => ({
|
|
3423
|
-
x: position.x,
|
|
3424
|
-
y: position.y + size.height + 8
|
|
3425
|
-
});
|
|
3426
|
-
function withAnchoredElements(scene, deltas) {
|
|
3427
|
-
const attached = scene.filter((element) => !element.isDeleted && isAnchored(element));
|
|
3428
|
-
if (attached.length === 0) return deltas;
|
|
3429
|
-
const widened = new Map(deltas);
|
|
3430
|
-
const present2 = new Set(scene.filter((element) => !element.isDeleted).map((e) => e.id));
|
|
3431
|
-
for (const element of attached) {
|
|
3432
|
-
if (widened.has(element.id)) continue;
|
|
3433
|
-
const live = anchorsOf(element).filter((id) => present2.has(id));
|
|
3434
|
-
if (live.length === 0) continue;
|
|
3435
|
-
const moves = live.map((id) => deltas.get(id));
|
|
3436
|
-
const [first] = moves;
|
|
3437
|
-
if (first === void 0) continue;
|
|
3438
|
-
if (!moves.every((move) => move !== void 0 && move[0] === first[0] && move[1] === first[1])) {
|
|
3439
|
-
continue;
|
|
3440
|
-
}
|
|
3441
|
-
widened.set(element.id, first);
|
|
3442
|
-
}
|
|
3443
|
-
return widened;
|
|
3444
|
-
}
|
|
3445
|
-
|
|
3446
3657
|
// src/domain/scene/frames.ts
|
|
3447
3658
|
function boundsAround(children) {
|
|
3448
3659
|
const pad2 = HOUSE.sizes.framePad;
|
|
@@ -3532,6 +3743,176 @@ function betweenIndices(lower, upper, count2) {
|
|
|
3532
3743
|
return generateNKeysBetween(lower, upper, count2).map((key) => Index.parse(key));
|
|
3533
3744
|
}
|
|
3534
3745
|
|
|
3746
|
+
// src/domain/scene/route.ts
|
|
3747
|
+
var ROUTE_CLEARANCE = 30;
|
|
3748
|
+
var TURN_PENALTY = 60;
|
|
3749
|
+
var MAX_GRID = 64;
|
|
3750
|
+
var EPSILON = 1e-3;
|
|
3751
|
+
function segmentHitsRect(a, b, rect) {
|
|
3752
|
+
let t0 = 0;
|
|
3753
|
+
let t1 = 1;
|
|
3754
|
+
const dx = b[0] - a[0];
|
|
3755
|
+
const dy = b[1] - a[1];
|
|
3756
|
+
const left = rect.x + EPSILON;
|
|
3757
|
+
const right = rect.x + rect.width - EPSILON;
|
|
3758
|
+
const top = rect.y + EPSILON;
|
|
3759
|
+
const bottom = rect.y + rect.height - EPSILON;
|
|
3760
|
+
const edges = [
|
|
3761
|
+
[-dx, a[0] - left],
|
|
3762
|
+
[dx, right - a[0]],
|
|
3763
|
+
[-dy, a[1] - top],
|
|
3764
|
+
[dy, bottom - a[1]]
|
|
3765
|
+
];
|
|
3766
|
+
for (const edge of edges) {
|
|
3767
|
+
const den = edge[0];
|
|
3768
|
+
const num = edge[1];
|
|
3769
|
+
if (den === 0) {
|
|
3770
|
+
if (num < 0) return false;
|
|
3771
|
+
continue;
|
|
3772
|
+
}
|
|
3773
|
+
const t = num / den;
|
|
3774
|
+
if (den < 0) {
|
|
3775
|
+
if (t > t1) return false;
|
|
3776
|
+
if (t > t0) t0 = t;
|
|
3777
|
+
} else {
|
|
3778
|
+
if (t < t0) return false;
|
|
3779
|
+
if (t < t1) t1 = t;
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
return t1 - t0 > EPSILON;
|
|
3783
|
+
}
|
|
3784
|
+
function routeHitsAny(points, obstacles) {
|
|
3785
|
+
for (let i = 1; i < points.length; i++) {
|
|
3786
|
+
const a = points[i - 1];
|
|
3787
|
+
const b = points[i];
|
|
3788
|
+
if (a === void 0 || b === void 0) continue;
|
|
3789
|
+
for (const rect of obstacles) if (segmentHitsRect(a, b, rect)) return true;
|
|
3790
|
+
}
|
|
3791
|
+
return false;
|
|
3792
|
+
}
|
|
3793
|
+
function axis(ends, sides, clearance) {
|
|
3794
|
+
const values = new Set(ends);
|
|
3795
|
+
for (const [low, high] of sides) {
|
|
3796
|
+
values.add(low - clearance);
|
|
3797
|
+
values.add(high + clearance);
|
|
3798
|
+
}
|
|
3799
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
3800
|
+
if (sorted.length <= MAX_GRID) return sorted;
|
|
3801
|
+
const step = sorted.length / MAX_GRID;
|
|
3802
|
+
const kept = new Set(ends);
|
|
3803
|
+
for (let i = 0; i < MAX_GRID; i++) {
|
|
3804
|
+
const value = sorted[Math.floor(i * step)];
|
|
3805
|
+
if (value !== void 0) kept.add(value);
|
|
3806
|
+
}
|
|
3807
|
+
return [...kept].sort((a, b) => a - b);
|
|
3808
|
+
}
|
|
3809
|
+
function routeAround(start, end, obstacles, clearance = ROUTE_CLEARANCE) {
|
|
3810
|
+
if (obstacles.length === 0) return null;
|
|
3811
|
+
const xs = axis(
|
|
3812
|
+
[start[0], end[0]],
|
|
3813
|
+
obstacles.map((r) => [r.x, r.x + r.width]),
|
|
3814
|
+
clearance
|
|
3815
|
+
);
|
|
3816
|
+
const ys = axis(
|
|
3817
|
+
[start[1], end[1]],
|
|
3818
|
+
obstacles.map((r) => [r.y, r.y + r.height]),
|
|
3819
|
+
clearance
|
|
3820
|
+
);
|
|
3821
|
+
const xi = xs.indexOf(start[0]);
|
|
3822
|
+
const yi = ys.indexOf(start[1]);
|
|
3823
|
+
const xg = xs.indexOf(end[0]);
|
|
3824
|
+
const yg = ys.indexOf(end[1]);
|
|
3825
|
+
if (xi < 0 || yi < 0 || xg < 0 || yg < 0) return null;
|
|
3826
|
+
const width = xs.length;
|
|
3827
|
+
const index = (cx, cy) => cy * width + cx;
|
|
3828
|
+
const goal = index(xg, yg);
|
|
3829
|
+
const at = (cx, cy) => {
|
|
3830
|
+
const x = xs[cx];
|
|
3831
|
+
const y = ys[cy];
|
|
3832
|
+
return [x ?? 0, y ?? 0];
|
|
3833
|
+
};
|
|
3834
|
+
const passable = (a, b) => {
|
|
3835
|
+
for (const rect of obstacles) if (segmentHitsRect(a, b, rect)) return false;
|
|
3836
|
+
return true;
|
|
3837
|
+
};
|
|
3838
|
+
const total = width * ys.length;
|
|
3839
|
+
const best = new Float64Array(total).fill(Number.POSITIVE_INFINITY);
|
|
3840
|
+
const cameFrom = new Int32Array(total).fill(-1);
|
|
3841
|
+
const arrived = new Uint8Array(total);
|
|
3842
|
+
const done = new Uint8Array(total);
|
|
3843
|
+
const startAt = index(xi, yi);
|
|
3844
|
+
best[startAt] = 0;
|
|
3845
|
+
const heuristic = (cx, cy) => Math.abs((xs[cx] ?? 0) - end[0]) + Math.abs((ys[cy] ?? 0) - end[1]);
|
|
3846
|
+
for (; ; ) {
|
|
3847
|
+
let node = -1;
|
|
3848
|
+
let cheapest = Number.POSITIVE_INFINITY;
|
|
3849
|
+
for (let i = 0; i < total; i++) {
|
|
3850
|
+
if (done[i] === 1) continue;
|
|
3851
|
+
const score = best[i];
|
|
3852
|
+
if (score === void 0 || !Number.isFinite(score)) continue;
|
|
3853
|
+
const estimate = score + heuristic(i % width, Math.floor(i / width));
|
|
3854
|
+
if (estimate < cheapest) {
|
|
3855
|
+
cheapest = estimate;
|
|
3856
|
+
node = i;
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
if (node < 0) return null;
|
|
3860
|
+
if (node === goal) break;
|
|
3861
|
+
done[node] = 1;
|
|
3862
|
+
const cx = node % width;
|
|
3863
|
+
const cy = Math.floor(node / width);
|
|
3864
|
+
const here = at(cx, cy);
|
|
3865
|
+
const cost = best[node] ?? 0;
|
|
3866
|
+
const steps = [
|
|
3867
|
+
[cx - 1, cy, 1],
|
|
3868
|
+
[cx + 1, cy, 1],
|
|
3869
|
+
[cx, cy - 1, 2],
|
|
3870
|
+
[cx, cy + 1, 2]
|
|
3871
|
+
];
|
|
3872
|
+
for (const [nx, ny, axisOf] of steps) {
|
|
3873
|
+
if (nx < 0 || ny < 0 || nx >= width || ny >= ys.length) continue;
|
|
3874
|
+
const next = index(nx, ny);
|
|
3875
|
+
if (done[next] === 1) continue;
|
|
3876
|
+
const there = at(nx, ny);
|
|
3877
|
+
if (!passable(here, there)) continue;
|
|
3878
|
+
const length = Math.abs(there[0] - here[0]) + Math.abs(there[1] - here[1]);
|
|
3879
|
+
const turn = arrived[node] !== 0 && arrived[node] !== axisOf ? TURN_PENALTY : 0;
|
|
3880
|
+
const candidate = cost + length + turn;
|
|
3881
|
+
const known = best[next];
|
|
3882
|
+
if (known === void 0 || candidate < known) {
|
|
3883
|
+
best[next] = candidate;
|
|
3884
|
+
cameFrom[next] = node;
|
|
3885
|
+
arrived[next] = axisOf;
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
const path = [];
|
|
3890
|
+
for (let node = goal; node >= 0; node = cameFrom[node] ?? -1) {
|
|
3891
|
+
path.push(at(node % width, Math.floor(node / width)));
|
|
3892
|
+
if (node === startAt) break;
|
|
3893
|
+
}
|
|
3894
|
+
path.reverse();
|
|
3895
|
+
return simplify(path);
|
|
3896
|
+
}
|
|
3897
|
+
function simplify(points) {
|
|
3898
|
+
if (points.length < 3) return points;
|
|
3899
|
+
const out = [];
|
|
3900
|
+
for (let i = 0; i < points.length; i++) {
|
|
3901
|
+
const previous = out[out.length - 1];
|
|
3902
|
+
const current = points[i];
|
|
3903
|
+
const next = points[i + 1];
|
|
3904
|
+
if (current === void 0) continue;
|
|
3905
|
+
if (previous === void 0 || next === void 0) {
|
|
3906
|
+
out.push(current);
|
|
3907
|
+
continue;
|
|
3908
|
+
}
|
|
3909
|
+
const straightX = previous[0] === current[0] && current[0] === next[0];
|
|
3910
|
+
const straightY = previous[1] === current[1] && current[1] === next[1];
|
|
3911
|
+
if (!straightX && !straightY) out.push(current);
|
|
3912
|
+
}
|
|
3913
|
+
return out;
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3535
3916
|
// src/domain/scene/arrange.ts
|
|
3536
3917
|
var boxOf = (element) => ({
|
|
3537
3918
|
x: element.x,
|
|
@@ -3574,6 +3955,45 @@ function edgePoint(from, to, gap) {
|
|
|
3574
3955
|
const t = scale + push;
|
|
3575
3956
|
return [cx + dx * t, cy + dy * t];
|
|
3576
3957
|
}
|
|
3958
|
+
var ROUTABLE_OBSTACLE = /* @__PURE__ */ new Set(["rectangle", "diamond", "ellipse", "image", "embeddable"]);
|
|
3959
|
+
function obstaclesFor(current, fromId, toId) {
|
|
3960
|
+
const out = [];
|
|
3961
|
+
for (const element of current.values()) {
|
|
3962
|
+
if (element.id === fromId || element.id === toId) continue;
|
|
3963
|
+
if (element.isDeleted || !ROUTABLE_OBSTACLE.has(element.type)) continue;
|
|
3964
|
+
out.push(boxOf(element));
|
|
3965
|
+
}
|
|
3966
|
+
return out;
|
|
3967
|
+
}
|
|
3968
|
+
var NO_FAN = { start: 0, end: 0 };
|
|
3969
|
+
function stubFrom(box, at) {
|
|
3970
|
+
const [cx, cy] = centre(box);
|
|
3971
|
+
const horizontal = at[0] <= box.x || at[0] >= box.x + box.width;
|
|
3972
|
+
if (horizontal) return [at[0] + (at[0] < cx ? -ROUTE_CLEARANCE : ROUTE_CLEARANCE), at[1]];
|
|
3973
|
+
return [at[0], at[1] + (at[1] < cy ? -ROUTE_CLEARANCE : ROUTE_CLEARANCE)];
|
|
3974
|
+
}
|
|
3975
|
+
function anchorOn(box, adjacent, gap) {
|
|
3976
|
+
const [cx, cy] = centre(box);
|
|
3977
|
+
const dx = adjacent[0] - cx;
|
|
3978
|
+
const dy = adjacent[1] - cy;
|
|
3979
|
+
if (dx === 0 && dy === 0) return [cx, cy];
|
|
3980
|
+
const toSide = Math.min(
|
|
3981
|
+
dx === 0 ? Number.POSITIVE_INFINITY : box.width / 2 / Math.abs(dx),
|
|
3982
|
+
dy === 0 ? Number.POSITIVE_INFINITY : box.height / 2 / Math.abs(dy)
|
|
3983
|
+
);
|
|
3984
|
+
const bx = cx + dx * toSide;
|
|
3985
|
+
const by = cy + dy * toSide;
|
|
3986
|
+
const reach = Math.hypot(dx * toSide, dy * toSide);
|
|
3987
|
+
if (reach === 0) return [bx, by];
|
|
3988
|
+
return [bx + dx * toSide * gap / reach, by + dy * toSide * gap / reach];
|
|
3989
|
+
}
|
|
3990
|
+
function isOrthogonal(arrow) {
|
|
3991
|
+
return arrow.customData?.chalk?.route === "orthogonal" || arrow.elbowed === true;
|
|
3992
|
+
}
|
|
3993
|
+
function withOrthogonalMark(arrow) {
|
|
3994
|
+
const chalk = arrow.customData?.chalk ?? { author: "human" };
|
|
3995
|
+
return { customData: { ...arrow.customData, chalk: { ...chalk, route: "orthogonal" } } };
|
|
3996
|
+
}
|
|
3577
3997
|
function redrawOne(current, labels, arrow, lane) {
|
|
3578
3998
|
if (arrow.type !== "arrow") return [];
|
|
3579
3999
|
const fromId = startOf(arrow);
|
|
@@ -3584,23 +4004,64 @@ function redrawOne(current, labels, arrow, lane) {
|
|
|
3584
4004
|
if (from === void 0 || to === void 0) return [];
|
|
3585
4005
|
const startGap = arrow.startBinding?.gap ?? 4;
|
|
3586
4006
|
const endGap = arrow.endBinding?.gap ?? 4;
|
|
3587
|
-
const elbowed = arrow
|
|
4007
|
+
const elbowed = isOrthogonal(arrow);
|
|
3588
4008
|
const [sx, sy, ex, ey] = elbowed ? facePoints(boxOf(from), boxOf(to), startGap, endGap, lane) : [
|
|
3589
4009
|
...edgePoint(boxOf(from), boxOf(to), startGap),
|
|
3590
4010
|
...edgePoint(boxOf(to), boxOf(from), endGap)
|
|
3591
4011
|
];
|
|
3592
|
-
const
|
|
3593
|
-
const
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
);
|
|
4012
|
+
const direct = elbowed ? elbowRoute(ex - sx, ey - sy) : [point(0, 0), point(ex - sx, ey - sy)];
|
|
4013
|
+
const obstacles = obstaclesFor(current, fromId, toId);
|
|
4014
|
+
const absolute = direct.map(([px2, py]) => [sx + px2, sy + py]);
|
|
4015
|
+
const detoured = routeHitsAny(absolute, obstacles);
|
|
4016
|
+
const [fsx, fsy, fex, fey] = detoured ? facePoints(boxOf(from), boxOf(to), startGap, endGap, NO_FAN) : [sx, sy, ex, ey];
|
|
4017
|
+
const stubStart = stubFrom(boxOf(from), [fsx, fsy]);
|
|
4018
|
+
const stubEnd = stubFrom(boxOf(to), [fex, fey]);
|
|
4019
|
+
const found = detoured ? routeAround(stubStart, stubEnd, obstacles) : null;
|
|
4020
|
+
const route = found === null ? null : simplify([[fsx, fsy], ...found, [fex, fey]]);
|
|
4021
|
+
const drawn = route ?? direct.map(([px2, py]) => [sx + px2, sy + py]);
|
|
4022
|
+
const settled = [...drawn];
|
|
4023
|
+
const secondFromStart = settled[1];
|
|
4024
|
+
const secondFromEnd = settled[settled.length - 2];
|
|
4025
|
+
if (secondFromStart !== void 0) {
|
|
4026
|
+
settled[0] = anchorOn(boxOf(from), secondFromStart, startGap);
|
|
4027
|
+
}
|
|
4028
|
+
if (secondFromEnd !== void 0) {
|
|
4029
|
+
settled[settled.length - 1] = anchorOn(boxOf(to), secondFromEnd, endGap);
|
|
4030
|
+
}
|
|
4031
|
+
const first = settled[0] ?? [sx, sy];
|
|
4032
|
+
const origin = [first[0], first[1]];
|
|
4033
|
+
const points = settled.map(([ax, ay]) => point(ax - origin[0], ay - origin[1]));
|
|
4034
|
+
const pxs = points.map(([px2]) => px2);
|
|
4035
|
+
const pys = points.map(([, py]) => py);
|
|
4036
|
+
const spans = {
|
|
4037
|
+
w: Math.max(...pxs) - Math.min(...pxs),
|
|
4038
|
+
h: Math.max(...pys) - Math.min(...pys)
|
|
4039
|
+
};
|
|
3600
4040
|
const redrawn = {
|
|
3601
4041
|
...arrow,
|
|
3602
|
-
|
|
3603
|
-
|
|
4042
|
+
// Repair on contact: an arrow drawn by an earlier version carries `elbowed: true` and
|
|
4043
|
+
// a binding the renderer cannot use. Clearing it here means the scene heals the first
|
|
4044
|
+
// time anything moves under it.
|
|
4045
|
+
...elbowed ? { elbowed: false, roundness: null, ...withOrthogonalMark(arrow) } : {},
|
|
4046
|
+
// A detour is an orthogonal route, so it is drawn as one.
|
|
4047
|
+
//
|
|
4048
|
+
// This is not cosmetic. Excalidraw owns the endpoints of a *bound* arrow at paint
|
|
4049
|
+
// time: with `focus: 0` it re-derives where the line meets each box from the segment
|
|
4050
|
+
// adjacent to it, and a detour changes exactly that segment. The stored endpoints are
|
|
4051
|
+
// right — all twenty-nine measured on the boundary of the box they name — and the
|
|
4052
|
+
// canvas still drew several of them leaving from nowhere, because the renderer had
|
|
4053
|
+
// recomputed them from a first segment that now pointed the wrong way. An elbow arrow
|
|
4054
|
+
// is anchored differently and keeps the route it was given.
|
|
4055
|
+
//
|
|
4056
|
+
// The second reason is what it looks like. A right-angled detour rendered with
|
|
4057
|
+
// `roundness` comes out as a long S sweeping across the diagram — which is what the
|
|
4058
|
+
// bend at the bottom of the sample was. Square corners are what an orthogonal route
|
|
4059
|
+
// actually means.
|
|
4060
|
+
// Square corners and the route marker — never upstream's `elbowed`, whose binding
|
|
4061
|
+
// contract we cannot meet and which left dragged boxes trailing detached arrows.
|
|
4062
|
+
...route === null ? {} : { elbowed: false, roundness: null, ...withOrthogonalMark(arrow) },
|
|
4063
|
+
x: origin[0],
|
|
4064
|
+
y: origin[1],
|
|
3604
4065
|
width: spans.w,
|
|
3605
4066
|
height: spans.h,
|
|
3606
4067
|
points,
|
|
@@ -3608,7 +4069,7 @@ function redrawOne(current, labels, arrow, lane) {
|
|
|
3608
4069
|
};
|
|
3609
4070
|
const label = labels.get(arrow.id);
|
|
3610
4071
|
if (label === void 0) return [redrawn];
|
|
3611
|
-
const mid = routeMidpoint({ x:
|
|
4072
|
+
const mid = routeMidpoint({ x: origin[0], y: origin[1], points });
|
|
3612
4073
|
const at = { x: mid.x - label.width / 2, y: mid.y - label.height / 2 };
|
|
3613
4074
|
return [redrawn, { ...label, ...at }];
|
|
3614
4075
|
}
|
|
@@ -3621,6 +4082,7 @@ function faceBetween(from, to) {
|
|
|
3621
4082
|
return dy >= 0 ? "bottom" : "top";
|
|
3622
4083
|
}
|
|
3623
4084
|
var LANE_SPREAD = 44;
|
|
4085
|
+
var LANE_INSET = 10;
|
|
3624
4086
|
function laneOffsets(arrows, current) {
|
|
3625
4087
|
const groups = /* @__PURE__ */ new Map();
|
|
3626
4088
|
const add = (nodeId, face, node, slot) => {
|
|
@@ -3631,7 +4093,7 @@ function laneOffsets(arrows, current) {
|
|
|
3631
4093
|
};
|
|
3632
4094
|
const across = (face, box) => face === "right" || face === "left" ? centre(box)[1] : centre(box)[0];
|
|
3633
4095
|
for (const arrow of arrows) {
|
|
3634
|
-
if (arrow.type !== "arrow" || arrow
|
|
4096
|
+
if (arrow.type !== "arrow" || !isOrthogonal(arrow)) continue;
|
|
3635
4097
|
const fromId = startOf(arrow);
|
|
3636
4098
|
const toId = endOf(arrow);
|
|
3637
4099
|
if (fromId === null || toId === null) continue;
|
|
@@ -3656,7 +4118,8 @@ function laneOffsets(arrows, current) {
|
|
|
3656
4118
|
if (slots.length < 2) continue;
|
|
3657
4119
|
slots.sort((a, b) => a.along - b.along || (a.arrow < b.arrow ? -1 : 1));
|
|
3658
4120
|
const span = face === "right" || face === "left" ? node.height : node.width;
|
|
3659
|
-
const
|
|
4121
|
+
const usable = Math.max(0, span - LANE_INSET * 2);
|
|
4122
|
+
const spread = Math.min(LANE_SPREAD, usable / Math.max(1, slots.length - 1));
|
|
3660
4123
|
slots.forEach((slot, i) => {
|
|
3661
4124
|
const shift = (i - (slots.length - 1) / 2) * spread;
|
|
3662
4125
|
const so_far = offsets.get(slot.arrow) ?? { start: 0, end: 0 };
|
|
@@ -3745,14 +4208,14 @@ var alignments = {
|
|
|
3745
4208
|
"align-center-h": (box, bounds) => [0, bounds.y + bounds.height / 2 - (box.y + box.height / 2)],
|
|
3746
4209
|
"align-center-v": (box, bounds) => [bounds.x + bounds.width / 2 - (box.x + box.width / 2), 0]
|
|
3747
4210
|
};
|
|
3748
|
-
function distribute(boxes, spacing,
|
|
3749
|
-
const size =
|
|
3750
|
-
const order = boxes.map((box, i) => ({ box, i })).sort((a, b) => a.box[
|
|
4211
|
+
function distribute(boxes, spacing, axis2) {
|
|
4212
|
+
const size = axis2 === "x" ? "width" : "height";
|
|
4213
|
+
const order = boxes.map((box, i) => ({ box, i })).sort((a, b) => a.box[axis2] - b.box[axis2]);
|
|
3751
4214
|
const deltas = boxes.map(() => [0, 0]);
|
|
3752
|
-
let cursor = order[0]?.box[
|
|
4215
|
+
let cursor = order[0]?.box[axis2] ?? 0;
|
|
3753
4216
|
for (const { box, i } of order) {
|
|
3754
|
-
const delta = cursor - box[
|
|
3755
|
-
deltas[i] =
|
|
4217
|
+
const delta = cursor - box[axis2];
|
|
4218
|
+
deltas[i] = axis2 === "x" ? [delta, 0] : [0, delta];
|
|
3756
4219
|
cursor += box[size] + spacing;
|
|
3757
4220
|
}
|
|
3758
4221
|
return deltas;
|
|
@@ -3768,10 +4231,10 @@ function grid(boxes, options) {
|
|
|
3768
4231
|
return [anchor.x + column * cellW - box.x, anchor.y + rowIndex * cellH - box.y];
|
|
3769
4232
|
});
|
|
3770
4233
|
}
|
|
3771
|
-
function flip(boxes,
|
|
4234
|
+
function flip(boxes, axis2) {
|
|
3772
4235
|
const bounds = boundsOfAll(boxes);
|
|
3773
4236
|
return boxes.map((box) => {
|
|
3774
|
-
if (
|
|
4237
|
+
if (axis2 === "x") {
|
|
3775
4238
|
const mirrored2 = bounds.x + bounds.width - (box.x - bounds.x) - box.width;
|
|
3776
4239
|
return [mirrored2 - box.x, 0];
|
|
3777
4240
|
}
|
|
@@ -3832,6 +4295,12 @@ function arrangeGeometry(input) {
|
|
|
3832
4295
|
if (input.op === "layout-layered") {
|
|
3833
4296
|
return ok({ moved: layoutLayered(input.scene, targets, input.spacing) });
|
|
3834
4297
|
}
|
|
4298
|
+
if (input.op === "layout-radial") {
|
|
4299
|
+
return ok({ moved: layoutRadial(input.scene, targets, input.spacing) });
|
|
4300
|
+
}
|
|
4301
|
+
if (input.op === "layout-tree") {
|
|
4302
|
+
return ok({ moved: layoutTree(input.scene, targets, input.spacing) });
|
|
4303
|
+
}
|
|
3835
4304
|
const boxes = targets.map(boxOf);
|
|
3836
4305
|
const spacing = input.spacing ?? HOUSE.spacing.h;
|
|
3837
4306
|
const columns = input.columns ?? Math.ceil(Math.sqrt(targets.length));
|
|
@@ -3863,6 +4332,44 @@ function arrangeGeometry(input) {
|
|
|
3863
4332
|
});
|
|
3864
4333
|
return ok({ moved: translate(input.scene, byDelta) });
|
|
3865
4334
|
}
|
|
4335
|
+
function backEdgeIndices(nodeIds, edges) {
|
|
4336
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
4337
|
+
for (const id of nodeIds) outgoing.set(id, []);
|
|
4338
|
+
edges.forEach((edge, index) => {
|
|
4339
|
+
outgoing.get(edge.from)?.push(index);
|
|
4340
|
+
});
|
|
4341
|
+
const ON_STACK = 1;
|
|
4342
|
+
const DONE = 2;
|
|
4343
|
+
const state = /* @__PURE__ */ new Map();
|
|
4344
|
+
const back = /* @__PURE__ */ new Set();
|
|
4345
|
+
for (const root of [...nodeIds].sort()) {
|
|
4346
|
+
if (state.has(root)) continue;
|
|
4347
|
+
state.set(root, ON_STACK);
|
|
4348
|
+
const stack = [{ node: root, cursor: 0 }];
|
|
4349
|
+
while (stack.length > 0) {
|
|
4350
|
+
const frame = stack[stack.length - 1];
|
|
4351
|
+
if (frame === void 0) break;
|
|
4352
|
+
const fanOut = outgoing.get(frame.node) ?? [];
|
|
4353
|
+
if (frame.cursor >= fanOut.length) {
|
|
4354
|
+
state.set(frame.node, DONE);
|
|
4355
|
+
stack.pop();
|
|
4356
|
+
continue;
|
|
4357
|
+
}
|
|
4358
|
+
const index = fanOut[frame.cursor];
|
|
4359
|
+
frame.cursor += 1;
|
|
4360
|
+
if (index === void 0) continue;
|
|
4361
|
+
const head = edges[index]?.to;
|
|
4362
|
+
if (head === void 0) continue;
|
|
4363
|
+
const seen = state.get(head);
|
|
4364
|
+
if (seen === ON_STACK) back.add(index);
|
|
4365
|
+
else if (seen === void 0) {
|
|
4366
|
+
state.set(head, ON_STACK);
|
|
4367
|
+
stack.push({ node: head, cursor: 0 });
|
|
4368
|
+
}
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
return back;
|
|
4372
|
+
}
|
|
3866
4373
|
function layoutLayered(scene, targets, spacing) {
|
|
3867
4374
|
const inSet = new Set(targets.map((element) => element.id));
|
|
3868
4375
|
const edges = [];
|
|
@@ -3876,13 +4383,18 @@ function layoutLayered(scene, targets, spacing) {
|
|
|
3876
4383
|
}
|
|
3877
4384
|
const nodes = targets.filter((element) => !isLinear(element) && !isAnchored(element));
|
|
3878
4385
|
if (nodes.length === 0) return [];
|
|
4386
|
+
const back = backEdgeIndices(
|
|
4387
|
+
nodes.map((node) => node.id),
|
|
4388
|
+
edges
|
|
4389
|
+
);
|
|
4390
|
+
const forward = edges.filter((_, index) => !back.has(index));
|
|
3879
4391
|
const incoming = /* @__PURE__ */ new Map();
|
|
3880
4392
|
for (const node of nodes) incoming.set(node.id, []);
|
|
3881
|
-
for (const edge of
|
|
4393
|
+
for (const edge of forward) incoming.get(edge.to)?.push(edge.from);
|
|
3882
4394
|
const rank = new Map(nodes.map((node) => [node.id, 0]));
|
|
3883
4395
|
for (let pass = 0; pass < nodes.length; pass++) {
|
|
3884
4396
|
let settled = true;
|
|
3885
|
-
for (const edge of
|
|
4397
|
+
for (const edge of forward) {
|
|
3886
4398
|
const from = rank.get(edge.from) ?? 0;
|
|
3887
4399
|
const to = rank.get(edge.to) ?? 0;
|
|
3888
4400
|
if (to < from + 1) {
|
|
@@ -3923,26 +4435,87 @@ function layoutLayered(scene, targets, spacing) {
|
|
|
3923
4435
|
previous = key;
|
|
3924
4436
|
}
|
|
3925
4437
|
}
|
|
3926
|
-
const position = /* @__PURE__ */ new Map();
|
|
3927
4438
|
const rankNumbers = [...slots.keys()].sort((a, b) => a - b);
|
|
4439
|
+
const childrenOf2 = /* @__PURE__ */ new Map();
|
|
3928
4440
|
for (const r of rankNumbers) {
|
|
3929
|
-
const
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
(a, b) => a.barycentre - b.barycentre || (a.slot.key < b.slot.key ? -1 : a.slot.key > b.slot.key ? 1 : 0)
|
|
3937
|
-
);
|
|
3938
|
-
scored.forEach(({ slot }, i) => {
|
|
3939
|
-
position.set(slot.key, i);
|
|
3940
|
-
});
|
|
3941
|
-
slots.set(
|
|
3942
|
-
r,
|
|
3943
|
-
scored.map((entry) => entry.slot)
|
|
3944
|
-
);
|
|
4441
|
+
for (const slot of slots.get(r) ?? []) {
|
|
4442
|
+
for (const parent of slot.parents) {
|
|
4443
|
+
const kids = childrenOf2.get(parent);
|
|
4444
|
+
if (kids === void 0) childrenOf2.set(parent, [slot.key]);
|
|
4445
|
+
else kids.push(slot.key);
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
3945
4448
|
}
|
|
4449
|
+
const indicesOf = (ordering) => {
|
|
4450
|
+
const at = /* @__PURE__ */ new Map();
|
|
4451
|
+
for (const bucket of ordering.values()) {
|
|
4452
|
+
bucket.forEach((slot, i) => {
|
|
4453
|
+
at.set(slot.key, i);
|
|
4454
|
+
});
|
|
4455
|
+
}
|
|
4456
|
+
return at;
|
|
4457
|
+
};
|
|
4458
|
+
const crossings = (ordering) => {
|
|
4459
|
+
const at = indicesOf(ordering);
|
|
4460
|
+
let total = 0;
|
|
4461
|
+
for (const r of rankNumbers) {
|
|
4462
|
+
const pairs = [];
|
|
4463
|
+
for (const slot of ordering.get(r) ?? []) {
|
|
4464
|
+
const below = at.get(slot.key);
|
|
4465
|
+
if (below === void 0) continue;
|
|
4466
|
+
for (const parent of slot.parents) {
|
|
4467
|
+
const above = at.get(parent);
|
|
4468
|
+
if (above !== void 0) pairs.push([above, below]);
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4471
|
+
for (let i = 0; i < pairs.length; i++) {
|
|
4472
|
+
for (let j = i + 1; j < pairs.length; j++) {
|
|
4473
|
+
const a = pairs[i];
|
|
4474
|
+
const b = pairs[j];
|
|
4475
|
+
if (a === void 0 || b === void 0) continue;
|
|
4476
|
+
if ((a[0] - b[0]) * (a[1] - b[1]) < 0) total += 1;
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
return total;
|
|
4481
|
+
};
|
|
4482
|
+
const sweep = (ordering, direction) => {
|
|
4483
|
+
const at = indicesOf(ordering);
|
|
4484
|
+
const next = /* @__PURE__ */ new Map();
|
|
4485
|
+
const order = direction === "down" ? rankNumbers : [...rankNumbers].reverse();
|
|
4486
|
+
for (const r of order) {
|
|
4487
|
+
const scored = (ordering.get(r) ?? []).map((slot) => {
|
|
4488
|
+
const adjacent = direction === "down" ? slot.parents : childrenOf2.get(slot.key) ?? [];
|
|
4489
|
+
const known = adjacent.map((key) => at.get(key)).filter((index) => index !== void 0);
|
|
4490
|
+
return {
|
|
4491
|
+
slot,
|
|
4492
|
+
barycentre: known.length === 0 ? Number.POSITIVE_INFINITY : known.reduce((sum, index) => sum + index, 0) / known.length
|
|
4493
|
+
};
|
|
4494
|
+
});
|
|
4495
|
+
scored.sort(
|
|
4496
|
+
(a, b) => a.barycentre - b.barycentre || (a.slot.key < b.slot.key ? -1 : a.slot.key > b.slot.key ? 1 : 0)
|
|
4497
|
+
);
|
|
4498
|
+
const settled = scored.map((entry) => entry.slot);
|
|
4499
|
+
settled.forEach((slot, i) => {
|
|
4500
|
+
at.set(slot.key, i);
|
|
4501
|
+
});
|
|
4502
|
+
next.set(r, settled);
|
|
4503
|
+
}
|
|
4504
|
+
return next;
|
|
4505
|
+
};
|
|
4506
|
+
const SWEEPS = 4;
|
|
4507
|
+
let current = new Map(rankNumbers.map((r) => [r, [...slots.get(r) ?? []]]));
|
|
4508
|
+
let best = current;
|
|
4509
|
+
let fewest = Number.POSITIVE_INFINITY;
|
|
4510
|
+
for (let round = 0; round < SWEEPS; round++) {
|
|
4511
|
+
current = sweep(current, round % 2 === 0 ? "down" : "up");
|
|
4512
|
+
const score = crossings(current);
|
|
4513
|
+
if (score < fewest) {
|
|
4514
|
+
fewest = score;
|
|
4515
|
+
best = current;
|
|
4516
|
+
}
|
|
4517
|
+
}
|
|
4518
|
+
for (const r of rankNumbers) slots.set(r, [...best.get(r) ?? []]);
|
|
3946
4519
|
const gapV = HOUSE.spacing.v;
|
|
3947
4520
|
const labelWidths = labelsByContainer(scene);
|
|
3948
4521
|
const captionRoom = (column) => {
|
|
@@ -3990,6 +4563,202 @@ function layoutLayered(scene, targets, spacing) {
|
|
|
3990
4563
|
});
|
|
3991
4564
|
return translate(scene, deltas);
|
|
3992
4565
|
}
|
|
4566
|
+
var TAU = Math.PI * 2;
|
|
4567
|
+
var normalizeAngle = (radians) => (radians % TAU + TAU) % TAU;
|
|
4568
|
+
function meanAngle(angles) {
|
|
4569
|
+
let x = 0;
|
|
4570
|
+
let y = 0;
|
|
4571
|
+
for (const angle of angles) {
|
|
4572
|
+
x += Math.cos(angle);
|
|
4573
|
+
y += Math.sin(angle);
|
|
4574
|
+
}
|
|
4575
|
+
return Math.atan2(y, x);
|
|
4576
|
+
}
|
|
4577
|
+
var ringExtent = (element) => Math.max(element.width, element.height);
|
|
4578
|
+
function adjacency(scene, nodes) {
|
|
4579
|
+
const inSet = new Set(nodes.map((node) => node.id));
|
|
4580
|
+
const neighbours = /* @__PURE__ */ new Map();
|
|
4581
|
+
for (const node of nodes) neighbours.set(node.id, /* @__PURE__ */ new Set());
|
|
4582
|
+
for (const element of scene) {
|
|
4583
|
+
if (element.type !== "arrow") continue;
|
|
4584
|
+
const from = startOf(element);
|
|
4585
|
+
const to = endOf(element);
|
|
4586
|
+
if (from === null || to === null || from === to) continue;
|
|
4587
|
+
if (!inSet.has(from) || !inSet.has(to)) continue;
|
|
4588
|
+
neighbours.get(from)?.add(to);
|
|
4589
|
+
neighbours.get(to)?.add(from);
|
|
4590
|
+
}
|
|
4591
|
+
return neighbours;
|
|
4592
|
+
}
|
|
4593
|
+
function pickHub(neighbours, ordered) {
|
|
4594
|
+
const first = ordered[0];
|
|
4595
|
+
if (first === void 0) return void 0;
|
|
4596
|
+
const degree = (element) => neighbours.get(element.id)?.size ?? 0;
|
|
4597
|
+
let hub = first;
|
|
4598
|
+
for (const node of ordered) if (degree(node) > degree(hub)) hub = node;
|
|
4599
|
+
return hub;
|
|
4600
|
+
}
|
|
4601
|
+
function layoutRadial(scene, targets, spacing) {
|
|
4602
|
+
const nodes = targets.filter((element) => !isLinear(element) && !isAnchored(element));
|
|
4603
|
+
const ordered = [...nodes].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
4604
|
+
const neighbours = adjacency(scene, nodes);
|
|
4605
|
+
const hub = pickHub(neighbours, ordered);
|
|
4606
|
+
if (hub === void 0) return [];
|
|
4607
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
4608
|
+
const gap = spacing ?? HOUSE.spacing.h;
|
|
4609
|
+
const ringOf = /* @__PURE__ */ new Map([[hub.id, 0]]);
|
|
4610
|
+
const parentsOf = /* @__PURE__ */ new Map();
|
|
4611
|
+
let frontier = [hub.id];
|
|
4612
|
+
let depth = 0;
|
|
4613
|
+
while (frontier.length > 0) {
|
|
4614
|
+
depth += 1;
|
|
4615
|
+
const discovered = /* @__PURE__ */ new Map();
|
|
4616
|
+
for (const id of frontier) {
|
|
4617
|
+
for (const other of [...neighbours.get(id) ?? []].sort()) {
|
|
4618
|
+
if (ringOf.has(other)) continue;
|
|
4619
|
+
const found = discovered.get(other);
|
|
4620
|
+
if (found === void 0) discovered.set(other, [id]);
|
|
4621
|
+
else found.push(id);
|
|
4622
|
+
}
|
|
4623
|
+
}
|
|
4624
|
+
for (const [child, parents] of discovered) {
|
|
4625
|
+
ringOf.set(child, depth);
|
|
4626
|
+
parentsOf.set(child, parents);
|
|
4627
|
+
}
|
|
4628
|
+
frontier = [...discovered.keys()];
|
|
4629
|
+
}
|
|
4630
|
+
for (const node of ordered) {
|
|
4631
|
+
if (!ringOf.has(node.id)) ringOf.set(node.id, depth + 1);
|
|
4632
|
+
}
|
|
4633
|
+
const rings = /* @__PURE__ */ new Map();
|
|
4634
|
+
for (const node of ordered) {
|
|
4635
|
+
const ring = ringOf.get(node.id) ?? 0;
|
|
4636
|
+
const bucket = rings.get(ring);
|
|
4637
|
+
if (bucket === void 0) rings.set(ring, [node.id]);
|
|
4638
|
+
else bucket.push(node.id);
|
|
4639
|
+
}
|
|
4640
|
+
const [cx, cy] = centre(boundsOfAll(nodes.map(boxOf)));
|
|
4641
|
+
const deltas = /* @__PURE__ */ new Map();
|
|
4642
|
+
deltas.set(hub.id, [cx - (hub.x + hub.width / 2), cy - (hub.y + hub.height / 2)]);
|
|
4643
|
+
const angleOf = /* @__PURE__ */ new Map([[hub.id, 0]]);
|
|
4644
|
+
let cleared = ringExtent(hub) / 2;
|
|
4645
|
+
for (const ring of [...rings.keys()].filter((r) => r > 0).sort((a, b) => a - b)) {
|
|
4646
|
+
const members = rings.get(ring) ?? [];
|
|
4647
|
+
if (members.length === 0) continue;
|
|
4648
|
+
const scored = members.map((id) => {
|
|
4649
|
+
const known = (parentsOf.get(id) ?? []).map((parent) => angleOf.get(parent)).filter((angle) => angle !== void 0);
|
|
4650
|
+
return {
|
|
4651
|
+
id,
|
|
4652
|
+
at: known.length === 0 ? Number.POSITIVE_INFINITY : normalizeAngle(meanAngle(known))
|
|
4653
|
+
};
|
|
4654
|
+
});
|
|
4655
|
+
scored.sort((a, b) => a.at - b.at || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
4656
|
+
const extents = members.map((id) => {
|
|
4657
|
+
const node = byId.get(id);
|
|
4658
|
+
return node === void 0 ? 0 : ringExtent(node);
|
|
4659
|
+
});
|
|
4660
|
+
const widest = Math.max(...extents);
|
|
4661
|
+
const seating = extents.reduce((sum, extent) => sum + extent + gap, 0) / TAU;
|
|
4662
|
+
const radius = Math.max(seating, cleared + widest / 2 + gap);
|
|
4663
|
+
scored.forEach(({ id }, i) => {
|
|
4664
|
+
const theta = -Math.PI / 2 + TAU * i / members.length;
|
|
4665
|
+
angleOf.set(id, normalizeAngle(theta));
|
|
4666
|
+
const node = byId.get(id);
|
|
4667
|
+
if (node === void 0) return;
|
|
4668
|
+
deltas.set(id, [
|
|
4669
|
+
cx + radius * Math.cos(theta) - node.width / 2 - node.x,
|
|
4670
|
+
cy + radius * Math.sin(theta) - node.height / 2 - node.y
|
|
4671
|
+
]);
|
|
4672
|
+
});
|
|
4673
|
+
cleared = radius + widest / 2;
|
|
4674
|
+
}
|
|
4675
|
+
return translate(scene, deltas);
|
|
4676
|
+
}
|
|
4677
|
+
function layoutTree(scene, targets, spacing) {
|
|
4678
|
+
const nodes = targets.filter((element) => !isLinear(element) && !isAnchored(element));
|
|
4679
|
+
const ordered = [...nodes].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
4680
|
+
const neighbours = adjacency(scene, nodes);
|
|
4681
|
+
const root = pickHub(neighbours, ordered);
|
|
4682
|
+
if (root === void 0) return [];
|
|
4683
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
4684
|
+
const gapH = spacing ?? HOUSE.spacing.h;
|
|
4685
|
+
const gapV = HOUSE.spacing.v;
|
|
4686
|
+
const depthOf = /* @__PURE__ */ new Map([[root.id, 0]]);
|
|
4687
|
+
const childrenOf2 = /* @__PURE__ */ new Map();
|
|
4688
|
+
const order = [];
|
|
4689
|
+
const stack = [root.id];
|
|
4690
|
+
const seen = /* @__PURE__ */ new Set([root.id]);
|
|
4691
|
+
while (stack.length > 0) {
|
|
4692
|
+
const id = stack.pop();
|
|
4693
|
+
if (id === void 0) continue;
|
|
4694
|
+
order.push(id);
|
|
4695
|
+
const kids = [];
|
|
4696
|
+
const adjacent = [...neighbours.get(id) ?? []].sort();
|
|
4697
|
+
for (const other of adjacent) {
|
|
4698
|
+
if (seen.has(other)) continue;
|
|
4699
|
+
seen.add(other);
|
|
4700
|
+
depthOf.set(other, (depthOf.get(id) ?? 0) + 1);
|
|
4701
|
+
kids.push(other);
|
|
4702
|
+
}
|
|
4703
|
+
childrenOf2.set(id, kids);
|
|
4704
|
+
for (let i = kids.length - 1; i >= 0; i--) {
|
|
4705
|
+
const kid = kids[i];
|
|
4706
|
+
if (kid !== void 0) stack.push(kid);
|
|
4707
|
+
}
|
|
4708
|
+
}
|
|
4709
|
+
for (const node of ordered) {
|
|
4710
|
+
if (seen.has(node.id)) continue;
|
|
4711
|
+
seen.add(node.id);
|
|
4712
|
+
depthOf.set(node.id, 1);
|
|
4713
|
+
order.push(node.id);
|
|
4714
|
+
}
|
|
4715
|
+
const columns = /* @__PURE__ */ new Map();
|
|
4716
|
+
for (const id of order) {
|
|
4717
|
+
const d = depthOf.get(id) ?? 0;
|
|
4718
|
+
const bucket = columns.get(d);
|
|
4719
|
+
if (bucket === void 0) columns.set(d, [id]);
|
|
4720
|
+
else bucket.push(id);
|
|
4721
|
+
}
|
|
4722
|
+
const captions = labelsByContainer(scene);
|
|
4723
|
+
const crossing = /* @__PURE__ */ new Map();
|
|
4724
|
+
for (const element of scene) {
|
|
4725
|
+
if (element.type !== "arrow") continue;
|
|
4726
|
+
const from = startOf(element);
|
|
4727
|
+
const to = endOf(element);
|
|
4728
|
+
if (from === null || to === null) continue;
|
|
4729
|
+
const a = depthOf.get(from);
|
|
4730
|
+
const b = depthOf.get(to);
|
|
4731
|
+
if (a === void 0 || b === void 0 || a === b) continue;
|
|
4732
|
+
const caption = captions.get(element.id);
|
|
4733
|
+
if (caption === void 0) continue;
|
|
4734
|
+
const gap = Math.min(a, b);
|
|
4735
|
+
crossing.set(gap, Math.max(crossing.get(gap) ?? 0, caption.width));
|
|
4736
|
+
}
|
|
4737
|
+
const anchor = boundsOfAll(nodes.map(boxOf));
|
|
4738
|
+
const depths = [...columns.keys()].sort((a, b) => a - b);
|
|
4739
|
+
const columnX = /* @__PURE__ */ new Map();
|
|
4740
|
+
let cursorX = anchor.x;
|
|
4741
|
+
for (const d of depths) {
|
|
4742
|
+
columnX.set(d, cursorX);
|
|
4743
|
+
const widest = Math.max(...(columns.get(d) ?? []).map((id) => byId.get(id)?.width ?? 0));
|
|
4744
|
+
const caption = crossing.get(d) ?? 0;
|
|
4745
|
+
cursorX += widest + Math.max(gapH, caption === 0 ? 0 : caption + HOUSE.sizes.labelPadX * 2);
|
|
4746
|
+
}
|
|
4747
|
+
const columnHeight = (d) => (columns.get(d) ?? []).reduce((sum, id) => sum + (byId.get(id)?.height ?? 0) + gapV, -gapV);
|
|
4748
|
+
const tallest = Math.max(...depths.map(columnHeight));
|
|
4749
|
+
const deltas = /* @__PURE__ */ new Map();
|
|
4750
|
+
for (const d of depths) {
|
|
4751
|
+
let cursorY = anchor.y + (tallest - columnHeight(d)) / 2;
|
|
4752
|
+
for (const id of columns.get(d) ?? []) {
|
|
4753
|
+
const node = byId.get(id);
|
|
4754
|
+
if (node === void 0) continue;
|
|
4755
|
+
const x = columnX.get(d) ?? anchor.x;
|
|
4756
|
+
deltas.set(id, [x - node.x, cursorY - node.y]);
|
|
4757
|
+
cursorY += node.height + gapV;
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4760
|
+
return translate(scene, deltas);
|
|
4761
|
+
}
|
|
3993
4762
|
|
|
3994
4763
|
// src/app/usecases/write/arrange-elements.ts
|
|
3995
4764
|
function arrangeElements(deps) {
|
|
@@ -4031,7 +4800,7 @@ function shapeOf(arrowType) {
|
|
|
4031
4800
|
case "sharp":
|
|
4032
4801
|
return { roundness: null, elbowed: false };
|
|
4033
4802
|
case "elbow":
|
|
4034
|
-
return { roundness: null, elbowed: true };
|
|
4803
|
+
return { roundness: null, elbowed: false, orthogonal: true };
|
|
4035
4804
|
default:
|
|
4036
4805
|
return { roundness: { type: ROUNDNESS.PROPORTIONAL_RADIUS }, elbowed: false };
|
|
4037
4806
|
}
|
|
@@ -4047,7 +4816,7 @@ function connectElements(deps) {
|
|
|
4047
4816
|
const to = live.get(input.toId);
|
|
4048
4817
|
if (from === void 0) return fail(unknownElement(input.fromId));
|
|
4049
4818
|
if (to === void 0) return fail(unknownElement(input.toId));
|
|
4050
|
-
const shape = shapeOf(input.arrowType);
|
|
4819
|
+
const { orthogonal, ...shape } = shapeOf(input.arrowType);
|
|
4051
4820
|
const skeleton = {
|
|
4052
4821
|
type: "arrow",
|
|
4053
4822
|
id: deps.idGen.elementId(),
|
|
@@ -4060,6 +4829,11 @@ function connectElements(deps) {
|
|
|
4060
4829
|
strokeWidth: HOUSE.strokeWidth,
|
|
4061
4830
|
roughness: HOUSE.roughness,
|
|
4062
4831
|
...shape,
|
|
4832
|
+
// Right angles are recorded under `customData.chalk`, which is where Chalkbridge
|
|
4833
|
+
// metadata lives (invariant 23), rather than as upstream's `elbowed` — that flag
|
|
4834
|
+
// narrows both bindings to `FixedPointBinding` and an arrow carrying it without one
|
|
4835
|
+
// does not follow its box when the box is dragged. `ChalkMeta.route` has the detail.
|
|
4836
|
+
...orthogonal === true ? { customData: { chalk: { author: "claude", route: "orthogonal" } } } : {},
|
|
4063
4837
|
...input.strokeStyle === void 0 ? {} : { strokeStyle: input.strokeStyle },
|
|
4064
4838
|
...input.startArrowhead === void 0 ? {} : { startArrowhead: input.startArrowhead },
|
|
4065
4839
|
...input.endArrowhead === void 0 ? {} : { endArrowhead: input.endArrowhead },
|
|
@@ -4165,82 +4939,6 @@ function createDebugElement(deps) {
|
|
|
4165
4939
|
};
|
|
4166
4940
|
}
|
|
4167
4941
|
|
|
4168
|
-
// src/domain/scene/skeleton.ts
|
|
4169
|
-
var isContainer = (type) => type === "rectangle" || type === "diamond" || type === "ellipse";
|
|
4170
|
-
function explicit(input) {
|
|
4171
|
-
const kept = {};
|
|
4172
|
-
for (const [key, value] of Object.entries(input)) {
|
|
4173
|
-
if (value === void 0) continue;
|
|
4174
|
-
if (key === "type" || key === "id" || key === "role" || key === "label") continue;
|
|
4175
|
-
kept[key] = value;
|
|
4176
|
-
}
|
|
4177
|
-
return kept;
|
|
4178
|
-
}
|
|
4179
|
-
var defaultSize = (element) => labelledSize(element.label, element.fontSize ?? HOUSE.fontSize);
|
|
4180
|
-
function normalizeElements(input) {
|
|
4181
|
-
return input.elements.map((element, i) => {
|
|
4182
|
-
const fallback = input.ids[i];
|
|
4183
|
-
if (fallback === void 0) {
|
|
4184
|
-
throw new Error(`normalizeElements needs one id per element: none at position ${i}`);
|
|
4185
|
-
}
|
|
4186
|
-
const id = element.id ?? fallback;
|
|
4187
|
-
const role = styleForRole(element.role ?? "compute");
|
|
4188
|
-
const set = explicit(element);
|
|
4189
|
-
if (element.type === "text") {
|
|
4190
|
-
const text = element.text ?? element.label ?? "";
|
|
4191
|
-
const skeleton2 = {
|
|
4192
|
-
type: "text",
|
|
4193
|
-
id,
|
|
4194
|
-
x: element.x,
|
|
4195
|
-
y: element.y,
|
|
4196
|
-
fontSize: HOUSE.fontSize,
|
|
4197
|
-
fontFamily: HOUSE.fontFamily,
|
|
4198
|
-
// A free-standing note is not a service box: it takes the role's ink but never
|
|
4199
|
-
// its fill, because a filled rectangle behind a caption reads as a shape.
|
|
4200
|
-
strokeColor: role.strokeColor,
|
|
4201
|
-
...set,
|
|
4202
|
-
text
|
|
4203
|
-
};
|
|
4204
|
-
return skeleton2;
|
|
4205
|
-
}
|
|
4206
|
-
if (element.type === "line") {
|
|
4207
|
-
const skeleton2 = {
|
|
4208
|
-
type: "line",
|
|
4209
|
-
id,
|
|
4210
|
-
x: element.x,
|
|
4211
|
-
y: element.y,
|
|
4212
|
-
strokeColor: role.strokeColor,
|
|
4213
|
-
strokeWidth: HOUSE.strokeWidth,
|
|
4214
|
-
roughness: HOUSE.roughness,
|
|
4215
|
-
...set,
|
|
4216
|
-
...element.label === void 0 ? {} : { label: houseLabel(element.label) }
|
|
4217
|
-
};
|
|
4218
|
-
return skeleton2;
|
|
4219
|
-
}
|
|
4220
|
-
if (!isContainer(element.type)) {
|
|
4221
|
-
throw new Error(`normalizeElements cannot build a ${element.type}`);
|
|
4222
|
-
}
|
|
4223
|
-
const skeleton = {
|
|
4224
|
-
type: element.type,
|
|
4225
|
-
id,
|
|
4226
|
-
x: element.x,
|
|
4227
|
-
y: element.y,
|
|
4228
|
-
...defaultSize(element),
|
|
4229
|
-
...role,
|
|
4230
|
-
...set,
|
|
4231
|
-
...element.label === void 0 ? {} : { label: houseLabel(element.label) }
|
|
4232
|
-
};
|
|
4233
|
-
return skeleton;
|
|
4234
|
-
});
|
|
4235
|
-
}
|
|
4236
|
-
var LAYOUT_HINT_THRESHOLD = 4;
|
|
4237
|
-
function layoutHint(elements) {
|
|
4238
|
-
if (elements.length < LAYOUT_HINT_THRESHOLD) return null;
|
|
4239
|
-
const positions = new Set(elements.map((element) => `${element.x},${element.y}`));
|
|
4240
|
-
if (positions.size > 2) return null;
|
|
4241
|
-
return `${elements.length} elements with no explicit layout \u2014 chalk_arrange(op: "layout-layered") will place them`;
|
|
4242
|
-
}
|
|
4243
|
-
|
|
4244
4942
|
// src/app/usecases/write/create-elements.ts
|
|
4245
4943
|
function createElements(deps) {
|
|
4246
4944
|
return async (input) => {
|
|
@@ -4742,6 +5440,24 @@ function rawElements(deps) {
|
|
|
4742
5440
|
};
|
|
4743
5441
|
}
|
|
4744
5442
|
|
|
5443
|
+
// src/domain/scene/captions.ts
|
|
5444
|
+
function fitCaption(container, caption) {
|
|
5445
|
+
if (caption.type !== "text") return caption;
|
|
5446
|
+
const source = caption.originalText === "" ? caption.text : caption.originalText;
|
|
5447
|
+
const inner = Math.max(1, container.width - HOUSE.sizes.labelPadX * 2);
|
|
5448
|
+
const lines = wrapLines(source, caption.fontSize, inner);
|
|
5449
|
+
const width = Math.max(...lines.map((line) => measureLine(line, caption.fontSize)));
|
|
5450
|
+
const height = lines.length * caption.fontSize * caption.lineHeight;
|
|
5451
|
+
return {
|
|
5452
|
+
...caption,
|
|
5453
|
+
text: lines.join("\n"),
|
|
5454
|
+
width,
|
|
5455
|
+
height,
|
|
5456
|
+
x: container.x + (container.width - width) / 2,
|
|
5457
|
+
y: container.y + (container.height - height) / 2
|
|
5458
|
+
};
|
|
5459
|
+
}
|
|
5460
|
+
|
|
4745
5461
|
// src/app/usecases/write/update-elements.ts
|
|
4746
5462
|
var missing = (id) => toRejection(id, unknownElement(id));
|
|
4747
5463
|
var onlyRequested = (landed, requested) => {
|
|
@@ -4811,11 +5527,14 @@ function updateElements(deps) {
|
|
|
4811
5527
|
const before = byId.get(id);
|
|
4812
5528
|
const caption = captionOf(scene.value.elements, id);
|
|
4813
5529
|
if (before === void 0 || caption === void 0) continue;
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
5530
|
+
const resized = next.width !== before.width || next.height !== before.height;
|
|
5531
|
+
knockOn.push(
|
|
5532
|
+
resized ? fitCaption(next, caption) : {
|
|
5533
|
+
...caption,
|
|
5534
|
+
x: caption.x + (next.x - before.x),
|
|
5535
|
+
y: caption.y + (next.y - before.y)
|
|
5536
|
+
}
|
|
5537
|
+
);
|
|
4819
5538
|
}
|
|
4820
5539
|
knockOn.push(...redrawArrowsTouching(scene.value.elements, moved));
|
|
4821
5540
|
const patched = new Set(changed.map((element) => element.id));
|
|
@@ -4944,7 +5663,7 @@ function readEnv(source) {
|
|
|
4944
5663
|
}
|
|
4945
5664
|
|
|
4946
5665
|
// src/composition/container.ts
|
|
4947
|
-
var PACKAGE_VERSION = "1.1.
|
|
5666
|
+
var PACKAGE_VERSION = "1.1.2";
|
|
4948
5667
|
function readRuntime() {
|
|
4949
5668
|
return {
|
|
4950
5669
|
env: process.env,
|
|
@@ -5552,9 +6271,13 @@ function renderScene(description) {
|
|
|
5552
6271
|
}
|
|
5553
6272
|
lines.push("elements:");
|
|
5554
6273
|
for (const element of description.elements) lines.push(row(element, true));
|
|
6274
|
+
lines.push(...adviceLines(description));
|
|
5555
6275
|
lines.push(...overlapLines(description));
|
|
5556
6276
|
return lines.join("\n");
|
|
5557
6277
|
}
|
|
6278
|
+
function adviceLines(description) {
|
|
6279
|
+
return description.layoutAdvice === null ? [] : [`layout: ${description.layoutAdvice}`];
|
|
6280
|
+
}
|
|
5558
6281
|
function overlapLines(description) {
|
|
5559
6282
|
if (description.overlapCount === 0) return [];
|
|
5560
6283
|
const shown = description.overlaps.length;
|
|
@@ -5958,7 +6681,7 @@ var createElementsTool = proxyTool({
|
|
|
5958
6681
|
var connectTool = proxyTool({
|
|
5959
6682
|
name: "chalk_connect",
|
|
5960
6683
|
title: "Connect two elements",
|
|
5961
|
-
description: "Draw an arrow between two elements. This is the ONLY way to make one: it binds both ends, so the arrow follows its boxes when the user drags them \u2014 an arrow made from coordinates comes loose the first time anybody tidies the diagram. Label it with what actually moves ('embeddings', 'POST /ingest', 'nightly'); an unlabelled arrow only says 'related', which the reader already assumed.
|
|
6684
|
+
description: "Draw an arrow between two elements. This is the ONLY way to make one: it binds both ends, so the arrow follows its boxes when the user drags them \u2014 an arrow made from coordinates comes loose the first time anybody tidies the diagram. Label it with what actually moves ('embeddings', 'POST /ingest', 'nightly'); an unlabelled arrow only says 'related', which the reader already assumed. `arrowType` is the one option worth knowing by name: it defaults to `round`, and `elbow` gives right-angled routing that suits an architecture diagram far better \u2014 pick one and keep it consistent across the canvas. Ends and `strokeStyle` carry meaning too (`dashed` for async is the modifier a reader most often wants) \u2014 read chalk_read_me('arrows') before drawing a diagram\u2019s worth of them, or every edge comes out an identical straight solid line.",
|
|
5962
6685
|
input: ConnectToolInput,
|
|
5963
6686
|
output: ConnectOutput,
|
|
5964
6687
|
call: (bridge) => bridge.connect
|
|
@@ -5984,7 +6707,7 @@ var deleteElementsTool = proxyTool({
|
|
|
5984
6707
|
var arrangeTool = proxyTool({
|
|
5985
6708
|
name: "chalk_arrange",
|
|
5986
6709
|
title: "Arrange elements",
|
|
5987
|
-
description: "Lay elements out. `layout-layered`
|
|
6710
|
+
description: "Lay elements out. Three of these read the arrows as well as the boxes and they answer different questions \u2014 picking the wrong one destroys the structure the right one would show. `layout-layered` ranks by arrow direction: reach for it when the diagram is a flow. `layout-radial` puts the most-connected box at the centre with the rest ringed around it. `layout-tree` puts it on the LEFT and fans the rest out rightwards, which reads better than a ring whenever the labels are wide \u2014 a ring has to be big enough to seat its members, and a big ring crosses its own middle. **chalk_describe_scene ends with a `layout:` line naming which of the three this graph wants and why** \u2014 read it rather than guessing. The rest are tidying: align\xD76, distribute-h/v, grid, flip\xD72, and four z-order ops. Arrange only what you added; running a layout over a diagram the user has arranged will move their work.",
|
|
5988
6711
|
input: ArrangeToolInput,
|
|
5989
6712
|
output: ArrangeOutput,
|
|
5990
6713
|
annotations: { idempotentHint: true },
|