chalkbridge 1.0.4 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-U2ZMSGNS.js → chunk-XFH76JS6.js} +306 -11
- package/dist/cli.js +1346 -189
- package/dist/index.js +1 -1
- package/guides/icons.md +22 -3
- package/guides/layout.md +70 -4
- package/package.json +1 -1
- package/ui-dist/assets/{abnfDiagram-VCTEODGH-C_cqCKun.js → abnfDiagram-VCTEODGH-Bwcewgyl.js} +1 -1
- package/ui-dist/assets/{ar-SA-G6X2FPQ2-CEKRHtmX.js → ar-SA-G6X2FPQ2-58dCrzRZ.js} +1 -1
- package/ui-dist/assets/architecture-7GRP2DOG-Df8gKqbg.js +1 -0
- package/ui-dist/assets/{architectureDiagram-5GKGNRK7-BuWVonvB.js → architectureDiagram-5GKGNRK7-CMPSnIrP.js} +1 -1
- package/ui-dist/assets/{az-AZ-76LH7QW2-mn2Y6BCY.js → az-AZ-76LH7QW2-CCpEVexR.js} +1 -1
- package/ui-dist/assets/{bg-BG-XCXSNQG7-CraLx0fE.js → bg-BG-XCXSNQG7-DDlArZMH.js} +1 -1
- package/ui-dist/assets/{blockDiagram-NRAW4CY4-83XozRPe.js → blockDiagram-NRAW4CY4-D7TrKIo5.js} +1 -1
- package/ui-dist/assets/{bn-BD-2XOGV67Q-CsTrJsPu.js → bn-BD-2XOGV67Q-BXyhMN4V.js} +1 -1
- package/ui-dist/assets/{c4Diagram-UCG6FXSJ-DTdS8B8G.js → c4Diagram-UCG6FXSJ-CazStwlL.js} +1 -1
- package/ui-dist/assets/{ca-ES-6MX7JW3Y-DD8cv_ze.js → ca-ES-6MX7JW3Y-JHxjajfS.js} +1 -1
- package/ui-dist/assets/channel-C4uQlBVf.js +1 -0
- package/ui-dist/assets/{chunk-75Z2AOVW-DQDqkKP0.js → chunk-75Z2AOVW-BJsQGYsA.js} +1 -1
- package/ui-dist/assets/{chunk-DU6HZSFF-_YwywVCK.js → chunk-DU6HZSFF-C1xw9RZQ.js} +1 -1
- package/ui-dist/assets/{chunk-EIO257PC-CLwbKeso.js → chunk-EIO257PC-BrhyYOZD.js} +1 -1
- package/ui-dist/assets/{chunk-F27PBJKO-BwhryBf6.js → chunk-F27PBJKO-f6XMRQIS.js} +1 -1
- package/ui-dist/assets/{chunk-G27WJ6UU-BixSJdhz.js → chunk-G27WJ6UU-Cws9GsBU.js} +1 -1
- package/ui-dist/assets/{chunk-GMAD6QVW-Bp7aUQup.js → chunk-GMAD6QVW-CbhLp4Jt.js} +1 -1
- package/ui-dist/assets/{chunk-JQ64N6SF-Dt5bAScf.js → chunk-JQ64N6SF-B5lJbHea.js} +1 -1
- package/ui-dist/assets/{chunk-LCL6LL3I-B5MsvPPp.js → chunk-LCL6LL3I-bGYRjzHM.js} +1 -1
- package/ui-dist/assets/{chunk-OBVCFTLP-DBqcHzNF.js → chunk-OBVCFTLP-CxeUZPN4.js} +1 -1
- package/ui-dist/assets/{chunk-P2QGCYS3-TMRACTMO.js → chunk-P2QGCYS3-CZEugM1f.js} +1 -1
- package/ui-dist/assets/{chunk-POPQ4Y6H-BsmX_MmQ.js → chunk-POPQ4Y6H-CoDq2Q_g.js} +1 -1
- package/ui-dist/assets/{chunk-PWAF6VOD-g4MniF8u.js → chunk-PWAF6VOD-CX_HLotF.js} +1 -1
- package/ui-dist/assets/{chunk-R7TYR2AO-S7THFl9x.js → chunk-R7TYR2AO-B4M26mv9.js} +1 -1
- package/ui-dist/assets/{chunk-RHFEMEQ7-CZYull6D.js → chunk-RHFEMEQ7-CVXQBX33.js} +1 -1
- package/ui-dist/assets/{chunk-SVP7TREG-DCss0vmb.js → chunk-SVP7TREG-L7TJWT_o.js} +1 -1
- package/ui-dist/assets/{chunk-TEH6E4GO-B-6h5v_5.js → chunk-TEH6E4GO-Dct0SlmV.js} +1 -1
- package/ui-dist/assets/classDiagram-DTDB5LWJ-DRbrF0Ha.js +1 -0
- package/ui-dist/assets/classDiagram-v2-JRS7N3AN-DRbrF0Ha.js +1 -0
- package/ui-dist/assets/{cs-CZ-2BRQDIVT-C9fIM1z5.js → cs-CZ-2BRQDIVT-CvvJph-H.js} +1 -1
- package/ui-dist/assets/{cynefin-OW5HDTMX-CtdCt-Da.js → cynefin-OW5HDTMX-Ei_Xgi2_.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-5FMLGOSQ-CF9L7X85.js → cynefinDiagram-5FMLGOSQ-AFFSo_cm.js} +1 -1
- package/ui-dist/assets/{da-DK-5WZEPLOC-DXGP9-TU.js → da-DK-5WZEPLOC-BaezbVeN.js} +1 -1
- package/ui-dist/assets/{dagre-3AP2YEHR-Bt0GOj3f.js → dagre-3AP2YEHR-B-FVEWH2.js} +1 -1
- package/ui-dist/assets/{de-DE-XR44H4JA-ChR8kF4h.js → de-DE-XR44H4JA-BAGdMA-L.js} +1 -1
- package/ui-dist/assets/{diagram-S7CK7UJ4-D2NIL20U.js → diagram-S7CK7UJ4-eyl1yJj7.js} +1 -1
- package/ui-dist/assets/{diagram-UQ7AKVKN-Dty3qvRq.js → diagram-UQ7AKVKN-CugR7jCP.js} +1 -1
- package/ui-dist/assets/{diagram-VSXAHHWV-znpNlomZ.js → diagram-VSXAHHWV-Dz85hV5k.js} +1 -1
- package/ui-dist/assets/{diagram-VX7I27RA-DMKYB58y.js → diagram-VX7I27RA-BYSNxzTB.js} +1 -1
- package/ui-dist/assets/{diagram-Z3DM3KII-BLsAgfm1.js → diagram-Z3DM3KII-BN4pFJaP.js} +1 -1
- package/ui-dist/assets/{dist-Biz3NhE4.js → dist-CVqcfLn4.js} +4 -4
- package/ui-dist/assets/{ebnfDiagram-PWID7BFC-sN1WpIzH.js → ebnfDiagram-PWID7BFC-Ddprc5Rv.js} +1 -1
- package/ui-dist/assets/{el-GR-BZB4AONW-BMi-5oTn.js → el-GR-BZB4AONW-C8EbIpNQ.js} +1 -1
- package/ui-dist/assets/{en-B4ZKOASM-DTTcVPyw.js → en-B4ZKOASM-BZp0XUv6.js} +1 -1
- package/ui-dist/assets/{erDiagram-SSCWMZ5O-qLib13wp.js → erDiagram-SSCWMZ5O-BoMeaxjf.js} +1 -1
- package/ui-dist/assets/{es-ES-U4NZUMDT-DSk-whe2.js → es-ES-U4NZUMDT-CA2eQinb.js} +1 -1
- package/ui-dist/assets/{eu-ES-A7QVB2H4-Dlhf1HMz.js → eu-ES-A7QVB2H4-CcaNArBV.js} +1 -1
- package/ui-dist/assets/eventmodeling-NTZA5JFV-CSPN2jeA.js +1 -0
- package/ui-dist/assets/{fa-IR-HGAKTJCU-C4NL1SUQ.js → fa-IR-HGAKTJCU-BH7DgnZN.js} +1 -1
- package/ui-dist/assets/{fi-FI-Z5N7JZ37-BPDGFbjy.js → fi-FI-Z5N7JZ37-DWg6APJ2.js} +1 -1
- package/ui-dist/assets/flowDiagram-A5DVABFB-YrkFRLG9.js +1 -0
- package/ui-dist/assets/{fr-FR-RHASNOE6-BKM44_qc.js → fr-FR-RHASNOE6-qBJplDsT.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-EL5Y4UJY-Cm_9m3AA.js → ganttDiagram-EL5Y4UJY-4wx5f9nv.js} +1 -1
- package/ui-dist/assets/{gitGraph-4MIJSDKK-B53Ccobw.js → gitGraph-4MIJSDKK-DDYlgTud.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-WWUBYQGX-CshbIfrd.js → gitGraphDiagram-WWUBYQGX-DAhZkdQa.js} +1 -1
- package/ui-dist/assets/{gl-ES-HMX3MZ6V-CYq-Wgeo.js → gl-ES-HMX3MZ6V-CTvRbK-4.js} +1 -1
- package/ui-dist/assets/{he-IL-6SHJWFNN-CejOCh7c.js → he-IL-6SHJWFNN-w2aMYZZd.js} +1 -1
- package/ui-dist/assets/{hi-IN-IWLTKZ5I-DdZM6com.js → hi-IN-IWLTKZ5I-DX4ZRE2H.js} +1 -1
- package/ui-dist/assets/{hu-HU-A5ZG7DT2-DcLFRMuO.js → hu-HU-A5ZG7DT2-naHDPNOM.js} +1 -1
- package/ui-dist/assets/{id-ID-SAP4L64H-1x9JAf0A.js → id-ID-SAP4L64H-D5QxdnZa.js} +1 -1
- package/ui-dist/assets/image-GAAHSSAO-C9O0QZ3x.js +1 -0
- package/ui-dist/assets/{index-CzWe1zeF.js → index-W8DLsYN7.js} +8 -8
- package/ui-dist/assets/{info-A6RAGUB7-Cge9ecX_.js → info-A6RAGUB7-RXb_MEIH.js} +1 -1
- package/ui-dist/assets/{infoDiagram-RXCK75RN-VS1BTI_H.js → infoDiagram-RXCK75RN-DzaEiHqC.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-5VMMS53U-Cbf1xbxj.js → ishikawaDiagram-5VMMS53U-C9We7rRC.js} +1 -1
- package/ui-dist/assets/{it-IT-JPQ66NNP-DgmQi0B_.js → it-IT-JPQ66NNP-B0YDAONu.js} +1 -1
- package/ui-dist/assets/{ja-JP-DBVTYXUO-BpmMfXE3.js → ja-JP-DBVTYXUO-CjS5c0my.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-EYS64GPL-C8RWIfHV.js → journeyDiagram-EYS64GPL-BnolpCVx.js} +1 -1
- package/ui-dist/assets/{kaa-6HZHGXH3-CczLR43o.js → kaa-6HZHGXH3-BCxOG4kc.js} +1 -1
- package/ui-dist/assets/{kab-KAB-ZGHBKWFO-Cx74DVre.js → kab-KAB-ZGHBKWFO-D3QotDYI.js} +1 -1
- package/ui-dist/assets/{kanban-definition-3QL26DDD-R8ng9q7k.js → kanban-definition-3QL26DDD-Cy9HKz-0.js} +1 -1
- package/ui-dist/assets/{kk-KZ-P5N5QNE5-BPvg9KTT.js → kk-KZ-P5N5QNE5-BrSjotfG.js} +1 -1
- package/ui-dist/assets/{km-KH-HSX4SM5Z-Blgo7Zit.js → km-KH-HSX4SM5Z-fb8nqdkK.js} +1 -1
- package/ui-dist/assets/{ko-KR-MTYHY66A-DZdo-KOi.js → ko-KR-MTYHY66A-BXj8eDyy.js} +1 -1
- package/ui-dist/assets/{ku-TR-6OUDTVRD-CTpgWpW_.js → ku-TR-6OUDTVRD-COZArCj9.js} +1 -1
- package/ui-dist/assets/{line-Baf15v0x.js → line-7U1712L5.js} +1 -1
- package/ui-dist/assets/{lt-LT-XHIRWOB4-DKNkbcsj.js → lt-LT-XHIRWOB4-C-HJQqop.js} +1 -1
- package/ui-dist/assets/{lv-LV-5QDEKY6T-DwnZRxkH.js → lv-LV-5QDEKY6T-DLfr6jLA.js} +1 -1
- package/ui-dist/assets/{mermaid-parser.core-0V2gZHuN.js → mermaid-parser.core-DrLo74XG.js} +3 -3
- package/ui-dist/assets/{mindmap-definition-FBJOCRG2-7hOTFX4B.js → mindmap-definition-FBJOCRG2-Ba42gC8u.js} +1 -1
- package/ui-dist/assets/{mr-IN-CRQNXWMA-C-BFvdUk.js → mr-IN-CRQNXWMA-B9soDV3M.js} +1 -1
- package/ui-dist/assets/{my-MM-5M5IBNSE-BFs64Lrm.js → my-MM-5M5IBNSE-LZaFHx2a.js} +1 -1
- package/ui-dist/assets/{nb-NO-T6EIAALU-CHGzOQzI.js → nb-NO-T6EIAALU-BwhFAqXH.js} +1 -1
- package/ui-dist/assets/{nl-NL-IS3SIHDZ-CmrNJ43t.js → nl-NL-IS3SIHDZ-D2FhUB0E.js} +1 -1
- package/ui-dist/assets/{nn-NO-6E72VCQL-Cmx8XlD1.js → nn-NO-6E72VCQL-yn-NI0ZL.js} +1 -1
- package/ui-dist/assets/{oc-FR-POXYY2M6-D6ZK0W24.js → oc-FR-POXYY2M6-DOhix0u-.js} +1 -1
- package/ui-dist/assets/{pa-IN-N4M65BXN-CoFjVv1P.js → pa-IN-N4M65BXN-9aoDSwWx.js} +1 -1
- package/ui-dist/assets/{packet-AYTQ26CC-auec35xT.js → packet-AYTQ26CC-DuyZkxuT.js} +1 -1
- package/ui-dist/assets/{pegDiagram-XKGWAZYB-NMMkeAIs.js → pegDiagram-XKGWAZYB-PH6THd_o.js} +1 -1
- package/ui-dist/assets/percentages-BXMCSKIN-BFxN7npZ.js +1 -0
- package/ui-dist/assets/{pie-WAS4IAKB-Brhoju7F.js → pie-WAS4IAKB-BdQJyLvD.js} +1 -1
- package/ui-dist/assets/{pieDiagram-E7YTZNPT-CbaXV2ep.js → pieDiagram-E7YTZNPT-CPm011HM.js} +1 -1
- package/ui-dist/assets/{pl-PL-T2D74RX3-CtZh2-fJ.js → pl-PL-T2D74RX3-BO50j2s4.js} +1 -1
- package/ui-dist/assets/{pt-BR-5N22H2LF-Dwnm2Lle.js → pt-BR-5N22H2LF-Cp8T2vmD.js} +1 -1
- package/ui-dist/assets/{pt-PT-UZXXM6DQ-Cb08TgqC.js → pt-PT-UZXXM6DQ-CdTZJ0Y5.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-AXDQQJYC-CXEPKL_-.js → quadrantDiagram-AXDQQJYC-Cm1Rlzhk.js} +1 -1
- package/ui-dist/assets/{radar-RG4KPBEZ-CTDsgRBl.js → radar-RG4KPBEZ-48srnC1_.js} +1 -1
- package/ui-dist/assets/{railroad-74A4TZTK-Bad-j95k.js → railroad-74A4TZTK-C6601ICA.js} +1 -1
- package/ui-dist/assets/railroad-abnf-HS5TGJTU-Dmq5JjW8.js +1 -0
- package/ui-dist/assets/railroad-ebnf-LZEXJU2U-DzMo_BK8.js +1 -0
- package/ui-dist/assets/railroad-peg-WCYAUIDC-LanIHXaC.js +1 -0
- package/ui-dist/assets/{railroadDiagram-O6MQD6OU-CJiZ6mmg.js → railroadDiagram-O6MQD6OU-C8yR5TE7.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-EFPCY7ZU-T8QMEZu6.js → requirementDiagram-EFPCY7ZU-DeleqP-5.js} +1 -1
- package/ui-dist/assets/{ro-RO-JPDTUUEW-D6H165NT.js → ro-RO-JPDTUUEW-C7SipvaK.js} +1 -1
- package/ui-dist/assets/{ru-RU-B4JR7IUQ-CBp0xVXG.js → ru-RU-B4JR7IUQ-dNLeoFpA.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-P5KCCOFB-Z9s0vmSt.js → sankeyDiagram-P5KCCOFB-CSJKN7--.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-WJ2MYXX4-BulR70nw.js → sequenceDiagram-WJ2MYXX4-B_C10dhq.js} +1 -1
- package/ui-dist/assets/{si-LK-N5RQ5JYF-10AxMH9e.js → si-LK-N5RQ5JYF-DogInl3-.js} +1 -1
- package/ui-dist/assets/{sk-SK-C5VTKIMK-BXvCxb46.js → sk-SK-C5VTKIMK-2YnR2SrM.js} +1 -1
- package/ui-dist/assets/{sl-SI-NN7IZMDC-BI5_PFXZ.js → sl-SI-NN7IZMDC-w1ecqU-U.js} +1 -1
- package/ui-dist/assets/{stateDiagram-HBIQ2CUA-Dam0DwKS.js → stateDiagram-HBIQ2CUA-BIYHmokV.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-4QOOHH4V-BEVYrEze.js +1 -0
- package/ui-dist/assets/subset-shared.chunk-zuCoK3Ax.js +1 -0
- package/ui-dist/assets/{subset-worker.chunk-BaDYCnkg.js → subset-worker.chunk-Fq9aq3Pk.js} +1 -1
- package/ui-dist/assets/{sv-SE-XGPEYMSR-dVM3morJ.js → sv-SE-XGPEYMSR-ZA1w16aZ.js} +1 -1
- package/ui-dist/assets/{swimlanes-XN3QIQJK-txWA6rWV.js → swimlanes-XN3QIQJK-Bsz-wFaq.js} +1 -1
- package/ui-dist/assets/swimlanesDiagram-VK2B7HYN-DVXdrFIL.js +8 -0
- package/ui-dist/assets/{ta-IN-2NMHFXQM-CWJY03pN.js → ta-IN-2NMHFXQM-CanbC1P6.js} +1 -1
- package/ui-dist/assets/{th-TH-HPSO5L25-CR6GhLgj.js → th-TH-HPSO5L25-DyHWEX0G.js} +1 -1
- package/ui-dist/assets/{timeline-definition-24CTP7MA-D8m_1UrT.js → timeline-definition-24CTP7MA-4TgC1OoH.js} +1 -1
- package/ui-dist/assets/{tr-TR-DEFEU3FU-BI8tDJSm.js → tr-TR-DEFEU3FU-D0W6rufc.js} +1 -1
- package/ui-dist/assets/{treeView-Q6P3EWNA-CWJmpg6A.js → treeView-Q6P3EWNA-C9fqdzP-.js} +1 -1
- package/ui-dist/assets/{treemap-WGGIJYW6-CvClsvN5.js → treemap-WGGIJYW6-BqNZy40L.js} +1 -1
- package/ui-dist/assets/{uk-UA-QMV73CPH-D3Uc38wP.js → uk-UA-QMV73CPH-C1iCWdAQ.js} +1 -1
- package/ui-dist/assets/{vennDiagram-4TSXK5OY-C5iS8F8o.js → vennDiagram-4TSXK5OY-BQFCZXqB.js} +1 -1
- package/ui-dist/assets/{vi-VN-M7AON7JQ-B6BZpyS4.js → vi-VN-M7AON7JQ-BWynaJhA.js} +1 -1
- package/ui-dist/assets/{wardley-WFR3VGLG-CdqB7_9-.js → wardley-WFR3VGLG-cNQ06Oix.js} +1 -1
- package/ui-dist/assets/{wardleyDiagram-VM6X3IG4-DAHyWwbW.js → wardleyDiagram-VM6X3IG4-BbLDW2ka.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-S5SC5T6Z-ilyFTLQ_.js → xychartDiagram-S5SC5T6Z-r5VAgI1x.js} +1 -1
- package/ui-dist/assets/{zh-CN-LNUGB5OW-B0PN355q.js → zh-CN-LNUGB5OW-BNvy0Mnj.js} +1 -1
- package/ui-dist/assets/{zh-HK-E62DVLB3-DUEHDaPB.js → zh-HK-E62DVLB3-Csv83LC3.js} +1 -1
- package/ui-dist/assets/{zh-TW-RAJ6MFWO-Ci4YMClQ.js → zh-TW-RAJ6MFWO-CW-6XIyO.js} +1 -1
- package/ui-dist/index.html +1 -1
- package/ui-dist/assets/architecture-7GRP2DOG-fqG4giDT.js +0 -1
- package/ui-dist/assets/channel-BOrwYBHk.js +0 -1
- package/ui-dist/assets/classDiagram-DTDB5LWJ-Dm2kcSSm.js +0 -1
- package/ui-dist/assets/classDiagram-v2-JRS7N3AN-Dm2kcSSm.js +0 -1
- package/ui-dist/assets/eventmodeling-NTZA5JFV-BN91GLT5.js +0 -1
- package/ui-dist/assets/flowDiagram-A5DVABFB-DBrXJ-Jf.js +0 -1
- package/ui-dist/assets/image-GAAHSSAO-BgehxCDd.js +0 -1
- package/ui-dist/assets/percentages-BXMCSKIN-BBuUnwM-.js +0 -1
- package/ui-dist/assets/railroad-abnf-HS5TGJTU-CbvNSNpm.js +0 -1
- package/ui-dist/assets/railroad-ebnf-LZEXJU2U-DdltjnE0.js +0 -1
- package/ui-dist/assets/railroad-peg-WCYAUIDC-CgbxToYh.js +0 -1
- package/ui-dist/assets/stateDiagram-v2-4QOOHH4V-CSjCWsxW.js +0 -1
- package/ui-dist/assets/subset-shared.chunk-Du7WrYQy.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-VK2B7HYN-CU4h0g7a.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, DataUrl, Index, 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-
|
|
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-XFH76JS6.js';
|
|
3
3
|
import './chunk-7D4SUZUM.js';
|
|
4
4
|
import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
@@ -2025,6 +2025,254 @@ function createExportBroker(options, deps) {
|
|
|
2025
2025
|
};
|
|
2026
2026
|
}
|
|
2027
2027
|
|
|
2028
|
+
// src/domain/scene/overlaps.ts
|
|
2029
|
+
var MIN_OVERLAP_PX = 2;
|
|
2030
|
+
var MAX_REPORTED_OVERLAPS = 12;
|
|
2031
|
+
function routeMidpoint(arrow) {
|
|
2032
|
+
const points = arrow.points;
|
|
2033
|
+
const lengths = points.slice(1).map((p, i) => {
|
|
2034
|
+
const prev = points[i];
|
|
2035
|
+
const [px2, py] = p;
|
|
2036
|
+
if (prev === void 0 || px2 === void 0 || py === void 0) return 0;
|
|
2037
|
+
const [qx, qy] = prev;
|
|
2038
|
+
if (qx === void 0 || qy === void 0) return 0;
|
|
2039
|
+
return Math.hypot(px2 - qx, py - qy);
|
|
2040
|
+
});
|
|
2041
|
+
let want = lengths.reduce((sum, l) => sum + l, 0) / 2;
|
|
2042
|
+
for (let i = 0; i < lengths.length; i++) {
|
|
2043
|
+
const from = points[i];
|
|
2044
|
+
const to = points[i + 1];
|
|
2045
|
+
const length = lengths[i];
|
|
2046
|
+
if (from === void 0 || to === void 0 || length === void 0) continue;
|
|
2047
|
+
const [fx, fy] = from;
|
|
2048
|
+
const [tx, ty] = to;
|
|
2049
|
+
if (fx === void 0 || fy === void 0 || tx === void 0 || ty === void 0) continue;
|
|
2050
|
+
if (want > length && i < lengths.length - 1) {
|
|
2051
|
+
want -= length;
|
|
2052
|
+
continue;
|
|
2053
|
+
}
|
|
2054
|
+
const k = length === 0 ? 0 : want / length;
|
|
2055
|
+
return { x: arrow.x + fx + (tx - fx) * k, y: arrow.y + fy + (ty - fy) * k };
|
|
2056
|
+
}
|
|
2057
|
+
return { x: arrow.x, y: arrow.y };
|
|
2058
|
+
}
|
|
2059
|
+
function renderedRect(element, byId) {
|
|
2060
|
+
if (element.type !== "text" || element.containerId === null) return element;
|
|
2061
|
+
const container = byId.get(element.containerId);
|
|
2062
|
+
if (container === void 0 || container.type !== "arrow") return element;
|
|
2063
|
+
const mid = routeMidpoint(container);
|
|
2064
|
+
return {
|
|
2065
|
+
x: mid.x - element.width / 2,
|
|
2066
|
+
y: mid.y - element.height / 2,
|
|
2067
|
+
width: element.width,
|
|
2068
|
+
height: element.height
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
var intersection = (a, b) => ({
|
|
2072
|
+
width: Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x),
|
|
2073
|
+
height: Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y)
|
|
2074
|
+
});
|
|
2075
|
+
function rectsOverlap(a, b) {
|
|
2076
|
+
const { width, height } = intersection(a, b);
|
|
2077
|
+
return width >= MIN_OVERLAP_PX && height >= MIN_OVERLAP_PX;
|
|
2078
|
+
}
|
|
2079
|
+
function frameOf(element, byId) {
|
|
2080
|
+
if (element.frameId !== null) return element.frameId;
|
|
2081
|
+
if (element.type !== "text" || element.containerId === null) return null;
|
|
2082
|
+
return byId.get(element.containerId)?.frameId ?? null;
|
|
2083
|
+
}
|
|
2084
|
+
function nested(a, b, byId) {
|
|
2085
|
+
if (a.type === "frame" && frameOf(b, byId) === frameIdOf(a.id)) return true;
|
|
2086
|
+
if (b.type === "frame" && frameOf(a, byId) === frameIdOf(b.id)) return true;
|
|
2087
|
+
if (a.type === "text" && a.containerId === b.id && b.type !== "arrow") return true;
|
|
2088
|
+
if (b.type === "text" && b.containerId === a.id && a.type !== "arrow") return true;
|
|
2089
|
+
return false;
|
|
2090
|
+
}
|
|
2091
|
+
var participates = (element) => !element.isDeleted && element.type !== "arrow" && element.type !== "line" && // Frames are regions, not shapes. One is 600 px across, so nearly everything near it
|
|
2092
|
+
// intersects its area — and unlike a box it does not obscure what it covers, because a
|
|
2093
|
+
// frame only clips its *own* children. Reporting non-children put a warning on every
|
|
2094
|
+
// framed diagram, which is the noise this file exists to avoid. The case that genuinely
|
|
2095
|
+
// hides content — a child outside the frame holding it — is prevented rather than
|
|
2096
|
+
// reported: `frames.refitFrames` grows the frame on every path that can move a child.
|
|
2097
|
+
element.type !== "frame" && element.width > 0 && element.height > 0;
|
|
2098
|
+
function wouldCollide(candidate, elements, ignore = /* @__PURE__ */ new Set()) {
|
|
2099
|
+
const byId = new Map(elements.map((element) => [element.id, element]));
|
|
2100
|
+
return elements.filter(participates).filter((element) => !ignore.has(element.id)).some((element) => rectsOverlap(candidate, renderedRect(element, byId)));
|
|
2101
|
+
}
|
|
2102
|
+
function findOverlaps(elements) {
|
|
2103
|
+
const byId = new Map(elements.map((element) => [element.id, element]));
|
|
2104
|
+
const live = elements.filter(participates);
|
|
2105
|
+
const found = [];
|
|
2106
|
+
for (let i = 0; i < live.length; i++) {
|
|
2107
|
+
const a = live[i];
|
|
2108
|
+
if (a === void 0) continue;
|
|
2109
|
+
for (let j = i + 1; j < live.length; j++) {
|
|
2110
|
+
const b = live[j];
|
|
2111
|
+
if (b === void 0) continue;
|
|
2112
|
+
if (nested(a, b, byId)) continue;
|
|
2113
|
+
const { width, height } = intersection(renderedRect(a, byId), renderedRect(b, byId));
|
|
2114
|
+
if (width < MIN_OVERLAP_PX || height < MIN_OVERLAP_PX) continue;
|
|
2115
|
+
found.push({ a: a.id, b: b.id, width: Math.round(width), height: Math.round(height) });
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
return found.sort(
|
|
2119
|
+
(p, q) => q.width * q.height - p.width * p.height || (p.a < q.a ? -1 : p.a > q.a ? 1 : 0) || (p.b < q.b ? -1 : p.b > q.b ? 1 : 0)
|
|
2120
|
+
);
|
|
2121
|
+
}
|
|
2122
|
+
function overlapWarning(elements) {
|
|
2123
|
+
const found = findOverlaps(elements);
|
|
2124
|
+
const worst = found[0];
|
|
2125
|
+
if (worst === void 0) return null;
|
|
2126
|
+
const size = `${Math.round(worst.width)}x${Math.round(worst.height)}`;
|
|
2127
|
+
const count2 = found.length === 1 ? "1 overlap" : `${found.length} overlaps`;
|
|
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
|
+
}
|
|
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
|
+
|
|
2028
2276
|
// src/domain/scene/observe.ts
|
|
2029
2277
|
var SHORT_ID_LENGTH = 4;
|
|
2030
2278
|
var shortId2 = (id) => id.slice(0, SHORT_ID_LENGTH);
|
|
@@ -2131,6 +2379,7 @@ function observeScene(input) {
|
|
|
2131
2379
|
const selected = new Set(input.selectedIds ?? []);
|
|
2132
2380
|
const context = { labels, frames, selected, detail: input.detail };
|
|
2133
2381
|
const rows = rowElements(scene.elements).filter((element) => element.type !== "frame");
|
|
2382
|
+
const overlaps = findOverlaps(scene.elements);
|
|
2134
2383
|
return {
|
|
2135
2384
|
sceneId: scene.id,
|
|
2136
2385
|
name: scene.name,
|
|
@@ -2139,6 +2388,14 @@ function observeScene(input) {
|
|
|
2139
2388
|
updatedAgoMs: Math.max(0, input.now - scene.updatedAt),
|
|
2140
2389
|
frames: [...frames.values()],
|
|
2141
2390
|
elements: rows.map((element) => describeElement(element, context)),
|
|
2391
|
+
overlaps: overlaps.slice(0, MAX_REPORTED_OVERLAPS),
|
|
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,
|
|
2142
2399
|
detail: input.detail
|
|
2143
2400
|
};
|
|
2144
2401
|
}
|
|
@@ -3171,6 +3428,10 @@ function restoreSnapshot(deps) {
|
|
|
3171
3428
|
sceneId: scene.id,
|
|
3172
3429
|
sceneVersion: updated.sceneVersion,
|
|
3173
3430
|
broadcastTo,
|
|
3431
|
+
// A restore puts back exactly what was saved, so any collision in it is the one the
|
|
3432
|
+
// snapshot was taken with. Worth saying: the reader is often restoring *because* a
|
|
3433
|
+
// layout went wrong, and silence here would read as "the old one was clean".
|
|
3434
|
+
overlapWarning: overlapWarning(snapshot.elements),
|
|
3174
3435
|
snapshotId: snapshot.summary.id,
|
|
3175
3436
|
label: snapshot.summary.label,
|
|
3176
3437
|
restored: snapshot.elements.filter((element) => !element.isDeleted).length
|
|
@@ -3195,9 +3456,122 @@ function serviceSize() {
|
|
|
3195
3456
|
const [width, height] = HOUSE.sizes.service;
|
|
3196
3457
|
return { width, height };
|
|
3197
3458
|
}
|
|
3459
|
+
var LINE_HEIGHT = 1.25;
|
|
3460
|
+
var quantise = (value) => Math.ceil(value / HOUSE.grid) * HOUSE.grid;
|
|
3461
|
+
function labelledSize(label, fontSize = HOUSE.fontSize) {
|
|
3462
|
+
const floor = serviceSize();
|
|
3463
|
+
if (label === void 0 || label === "") return floor;
|
|
3464
|
+
const padX = HOUSE.sizes.labelPadX * 2;
|
|
3465
|
+
const padY = HOUSE.sizes.labelPadY * 2;
|
|
3466
|
+
const oneLine = measureLine(label, fontSize) + padX;
|
|
3467
|
+
const width = Math.max(floor.width, quantise(Math.min(oneLine, HOUSE.sizes.maxBox)));
|
|
3468
|
+
const lines = wrapLines(label, fontSize, width - padX);
|
|
3469
|
+
const height = Math.max(floor.height, quantise(lines.length * fontSize * LINE_HEIGHT + padY));
|
|
3470
|
+
return { width, height };
|
|
3471
|
+
}
|
|
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
|
+
}
|
|
3198
3557
|
|
|
3199
3558
|
// src/app/usecases/write/annotate-elements.ts
|
|
3200
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
|
+
}
|
|
3201
3575
|
function annotateElements(deps) {
|
|
3202
3576
|
return async (input) => {
|
|
3203
3577
|
const scene = deps.write.resolve(input.sceneId);
|
|
@@ -3212,14 +3586,20 @@ function annotateElements(deps) {
|
|
|
3212
3586
|
if (anchors.length === 0) return fail(emptySelection("chalk_annotate"));
|
|
3213
3587
|
const left = Math.min(...anchors.map((element) => element.x));
|
|
3214
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);
|
|
3215
3595
|
const skeleton = {
|
|
3216
3596
|
type: "text",
|
|
3217
3597
|
id: deps.idGen.elementId(),
|
|
3218
|
-
x:
|
|
3219
|
-
y:
|
|
3220
|
-
|
|
3598
|
+
x: spot.x,
|
|
3599
|
+
y: spot.y,
|
|
3600
|
+
...wrapped,
|
|
3221
3601
|
// A size below the labels, so a note reads as commentary rather than as content.
|
|
3222
|
-
fontSize:
|
|
3602
|
+
fontSize: NOTE_FONT_SIZE,
|
|
3223
3603
|
fontFamily: HOUSE.fontFamily,
|
|
3224
3604
|
strokeColor: styleForRole("external").strokeColor,
|
|
3225
3605
|
customData: {
|
|
@@ -3273,6 +3653,74 @@ function focusElements(deps) {
|
|
|
3273
3653
|
return ok({ sceneId: scene.value.id, focused, broadcastTo });
|
|
3274
3654
|
};
|
|
3275
3655
|
}
|
|
3656
|
+
|
|
3657
|
+
// src/domain/scene/frames.ts
|
|
3658
|
+
function boundsAround(children) {
|
|
3659
|
+
const pad2 = HOUSE.sizes.framePad;
|
|
3660
|
+
const left = Math.min(...children.map((c) => c.x));
|
|
3661
|
+
const top = Math.min(...children.map((c) => c.y));
|
|
3662
|
+
const right = Math.max(...children.map((c) => c.x + c.width));
|
|
3663
|
+
const bottom = Math.max(...children.map((c) => c.y + c.height));
|
|
3664
|
+
return {
|
|
3665
|
+
x: left - pad2,
|
|
3666
|
+
y: top - pad2,
|
|
3667
|
+
width: right - left + pad2 * 2,
|
|
3668
|
+
// Room at the top for the frame's own name, which renders above its border.
|
|
3669
|
+
height: bottom - top + pad2 * 2
|
|
3670
|
+
};
|
|
3671
|
+
}
|
|
3672
|
+
function childrenOf(scene, frame) {
|
|
3673
|
+
const id = frameIdOf(frame.id);
|
|
3674
|
+
return scene.filter(
|
|
3675
|
+
(element) => !element.isDeleted && element.id !== frame.id && element.frameId === id
|
|
3676
|
+
);
|
|
3677
|
+
}
|
|
3678
|
+
function releaseCrossingArrows(scene, frame) {
|
|
3679
|
+
const id = frameIdOf(frame.id);
|
|
3680
|
+
const inside = new Set(childrenOf(scene, frame).map((element) => element.id));
|
|
3681
|
+
const byId = new Map(scene.map((element) => [element.id, element]));
|
|
3682
|
+
return childrenOf(scene, frame).filter((element) => element.type === "arrow" || element.type === "line").filter((arrow) => {
|
|
3683
|
+
if (arrow.type !== "arrow") return true;
|
|
3684
|
+
const from = arrow.startBinding?.elementId;
|
|
3685
|
+
const to = arrow.endBinding?.elementId;
|
|
3686
|
+
if (from === void 0 || to === void 0) return true;
|
|
3687
|
+
return !(inside.has(from) && inside.has(to));
|
|
3688
|
+
}).filter((arrow) => byId.get(arrow.id)?.frameId === id).map((arrow) => ({ ...arrow, frameId: null }));
|
|
3689
|
+
}
|
|
3690
|
+
function withFrameChildren(scene, deltas) {
|
|
3691
|
+
const frames = scene.filter(
|
|
3692
|
+
(element) => element.type === "frame" && !element.isDeleted && deltas.has(element.id)
|
|
3693
|
+
);
|
|
3694
|
+
if (frames.length === 0) return deltas;
|
|
3695
|
+
const widened = new Map(deltas);
|
|
3696
|
+
for (const frame of frames) {
|
|
3697
|
+
const delta = deltas.get(frame.id);
|
|
3698
|
+
if (delta === void 0) continue;
|
|
3699
|
+
for (const child of childrenOf(scene, frame)) {
|
|
3700
|
+
if (widened.has(child.id)) continue;
|
|
3701
|
+
widened.set(child.id, delta);
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
return widened;
|
|
3705
|
+
}
|
|
3706
|
+
function refitFrames(scene, moved) {
|
|
3707
|
+
if (moved.size === 0) return [];
|
|
3708
|
+
const current = scene.map((element) => moved.get(element.id) ?? element);
|
|
3709
|
+
const refitted = [];
|
|
3710
|
+
for (const element of current) {
|
|
3711
|
+
if (element.type !== "frame" || element.isDeleted) continue;
|
|
3712
|
+
const crossing = new Set(releaseCrossingArrows(current, element).map((a) => a.id));
|
|
3713
|
+
const children = childrenOf(current, element).filter((c) => !crossing.has(c.id));
|
|
3714
|
+
if (children.length === 0) continue;
|
|
3715
|
+
if (!children.some((child) => moved.has(child.id))) continue;
|
|
3716
|
+
const box = boundsAround(children);
|
|
3717
|
+
if (element.x === box.x && element.y === box.y && element.width === box.width && element.height === box.height) {
|
|
3718
|
+
continue;
|
|
3719
|
+
}
|
|
3720
|
+
refitted.push({ ...element, ...box });
|
|
3721
|
+
}
|
|
3722
|
+
return refitted;
|
|
3723
|
+
}
|
|
3276
3724
|
function highestIndex(elements) {
|
|
3277
3725
|
let highest = null;
|
|
3278
3726
|
for (const element of elements) {
|
|
@@ -3295,6 +3743,176 @@ function betweenIndices(lower, upper, count2) {
|
|
|
3295
3743
|
return generateNKeysBetween(lower, upper, count2).map((key) => Index.parse(key));
|
|
3296
3744
|
}
|
|
3297
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
|
+
|
|
3298
3916
|
// src/domain/scene/arrange.ts
|
|
3299
3917
|
var boxOf = (element) => ({
|
|
3300
3918
|
x: element.x,
|
|
@@ -3337,7 +3955,39 @@ function edgePoint(from, to, gap) {
|
|
|
3337
3955
|
const t = scale + push;
|
|
3338
3956
|
return [cx + dx * t, cy + dy * t];
|
|
3339
3957
|
}
|
|
3340
|
-
|
|
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 redrawOne(current, labels, arrow, lane) {
|
|
3341
3991
|
if (arrow.type !== "arrow") return [];
|
|
3342
3992
|
const fromId = startOf(arrow);
|
|
3343
3993
|
const toId = endOf(arrow);
|
|
@@ -3347,40 +3997,163 @@ function redrawOne(current, labels, arrow) {
|
|
|
3347
3997
|
if (from === void 0 || to === void 0) return [];
|
|
3348
3998
|
const startGap = arrow.startBinding?.gap ?? 4;
|
|
3349
3999
|
const endGap = arrow.endBinding?.gap ?? 4;
|
|
3350
|
-
const
|
|
3351
|
-
const [ex, ey] =
|
|
3352
|
-
|
|
4000
|
+
const elbowed = arrow.elbowed === true;
|
|
4001
|
+
const [sx, sy, ex, ey] = elbowed ? facePoints(boxOf(from), boxOf(to), startGap, endGap, lane) : [
|
|
4002
|
+
...edgePoint(boxOf(from), boxOf(to), startGap),
|
|
4003
|
+
...edgePoint(boxOf(to), boxOf(from), endGap)
|
|
4004
|
+
];
|
|
4005
|
+
const direct = elbowed ? elbowRoute(ex - sx, ey - sy) : [point(0, 0), point(ex - sx, ey - sy)];
|
|
4006
|
+
const obstacles = obstaclesFor(current, fromId, toId);
|
|
4007
|
+
const absolute = direct.map(([px2, py]) => [sx + px2, sy + py]);
|
|
4008
|
+
const detoured = routeHitsAny(absolute, obstacles);
|
|
4009
|
+
const [fsx, fsy, fex, fey] = detoured ? facePoints(boxOf(from), boxOf(to), startGap, endGap, NO_FAN) : [sx, sy, ex, ey];
|
|
4010
|
+
const stubStart = stubFrom(boxOf(from), [fsx, fsy]);
|
|
4011
|
+
const stubEnd = stubFrom(boxOf(to), [fex, fey]);
|
|
4012
|
+
const found = detoured ? routeAround(stubStart, stubEnd, obstacles) : null;
|
|
4013
|
+
const route = found === null ? null : simplify([[fsx, fsy], ...found, [fex, fey]]);
|
|
4014
|
+
const drawn = route ?? direct.map(([px2, py]) => [sx + px2, sy + py]);
|
|
4015
|
+
const settled = [...drawn];
|
|
4016
|
+
const secondFromStart = settled[1];
|
|
4017
|
+
const secondFromEnd = settled[settled.length - 2];
|
|
4018
|
+
if (secondFromStart !== void 0) {
|
|
4019
|
+
settled[0] = anchorOn(boxOf(from), secondFromStart, startGap);
|
|
4020
|
+
}
|
|
4021
|
+
if (secondFromEnd !== void 0) {
|
|
4022
|
+
settled[settled.length - 1] = anchorOn(boxOf(to), secondFromEnd, endGap);
|
|
4023
|
+
}
|
|
4024
|
+
const first = settled[0] ?? [sx, sy];
|
|
4025
|
+
const origin = [first[0], first[1]];
|
|
4026
|
+
const points = settled.map(([ax, ay]) => point(ax - origin[0], ay - origin[1]));
|
|
4027
|
+
const pxs = points.map(([px2]) => px2);
|
|
4028
|
+
const pys = points.map(([, py]) => py);
|
|
4029
|
+
const spans = {
|
|
4030
|
+
w: Math.max(...pxs) - Math.min(...pxs),
|
|
4031
|
+
h: Math.max(...pys) - Math.min(...pys)
|
|
4032
|
+
};
|
|
3353
4033
|
const redrawn = {
|
|
3354
4034
|
...arrow,
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
4035
|
+
// A detour is an orthogonal route, so it is drawn as one.
|
|
4036
|
+
//
|
|
4037
|
+
// This is not cosmetic. Excalidraw owns the endpoints of a *bound* arrow at paint
|
|
4038
|
+
// time: with `focus: 0` it re-derives where the line meets each box from the segment
|
|
4039
|
+
// adjacent to it, and a detour changes exactly that segment. The stored endpoints are
|
|
4040
|
+
// right — all twenty-nine measured on the boundary of the box they name — and the
|
|
4041
|
+
// canvas still drew several of them leaving from nowhere, because the renderer had
|
|
4042
|
+
// recomputed them from a first segment that now pointed the wrong way. An elbow arrow
|
|
4043
|
+
// is anchored differently and keeps the route it was given.
|
|
4044
|
+
//
|
|
4045
|
+
// The second reason is what it looks like. A right-angled detour rendered with
|
|
4046
|
+
// `roundness` comes out as a long S sweeping across the diagram — which is what the
|
|
4047
|
+
// bend at the bottom of the sample was. Square corners are what an orthogonal route
|
|
4048
|
+
// actually means.
|
|
4049
|
+
...route === null ? {} : { elbowed: true, roundness: null },
|
|
4050
|
+
x: origin[0],
|
|
4051
|
+
y: origin[1],
|
|
4052
|
+
width: spans.w,
|
|
4053
|
+
height: spans.h,
|
|
3359
4054
|
points,
|
|
3360
4055
|
lastCommittedPoint: null
|
|
3361
4056
|
};
|
|
3362
4057
|
const label = labels.get(arrow.id);
|
|
3363
4058
|
if (label === void 0) return [redrawn];
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
4059
|
+
const mid = routeMidpoint({ x: origin[0], y: origin[1], points });
|
|
4060
|
+
const at = { x: mid.x - label.width / 2, y: mid.y - label.height / 2 };
|
|
4061
|
+
return [redrawn, { ...label, ...at }];
|
|
4062
|
+
}
|
|
4063
|
+
function faceBetween(from, to) {
|
|
4064
|
+
const [fx, fy] = centre(from);
|
|
4065
|
+
const [tx, ty] = centre(to);
|
|
4066
|
+
const dx = tx - fx;
|
|
4067
|
+
const dy = ty - fy;
|
|
4068
|
+
if (Math.abs(dx) >= Math.abs(dy)) return dx >= 0 ? "right" : "left";
|
|
4069
|
+
return dy >= 0 ? "bottom" : "top";
|
|
4070
|
+
}
|
|
4071
|
+
var LANE_SPREAD = 44;
|
|
4072
|
+
var LANE_INSET = 10;
|
|
4073
|
+
function laneOffsets(arrows, current) {
|
|
4074
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4075
|
+
const add = (nodeId, face, node, slot) => {
|
|
4076
|
+
const key = `${nodeId}|${face}`;
|
|
4077
|
+
const bucket = groups.get(key);
|
|
4078
|
+
if (bucket === void 0) groups.set(key, { face, node, slots: [slot] });
|
|
4079
|
+
else bucket.slots.push(slot);
|
|
4080
|
+
};
|
|
4081
|
+
const across = (face, box) => face === "right" || face === "left" ? centre(box)[1] : centre(box)[0];
|
|
4082
|
+
for (const arrow of arrows) {
|
|
4083
|
+
if (arrow.type !== "arrow" || arrow.elbowed !== true) continue;
|
|
4084
|
+
const fromId = startOf(arrow);
|
|
4085
|
+
const toId = endOf(arrow);
|
|
4086
|
+
if (fromId === null || toId === null) continue;
|
|
4087
|
+
const from = current.get(fromId);
|
|
4088
|
+
const to = current.get(toId);
|
|
4089
|
+
if (from === void 0 || to === void 0) continue;
|
|
4090
|
+
const outFace = faceBetween(boxOf(from), boxOf(to));
|
|
4091
|
+
const inFace = faceBetween(boxOf(to), boxOf(from));
|
|
4092
|
+
add(fromId, outFace, boxOf(from), {
|
|
4093
|
+
arrow: arrow.id,
|
|
4094
|
+
end: "start",
|
|
4095
|
+
along: across(outFace, boxOf(to))
|
|
4096
|
+
});
|
|
4097
|
+
add(toId, inFace, boxOf(to), {
|
|
4098
|
+
arrow: arrow.id,
|
|
4099
|
+
end: "end",
|
|
4100
|
+
along: across(inFace, boxOf(from))
|
|
4101
|
+
});
|
|
4102
|
+
}
|
|
4103
|
+
const offsets = /* @__PURE__ */ new Map();
|
|
4104
|
+
for (const { face, node, slots } of groups.values()) {
|
|
4105
|
+
if (slots.length < 2) continue;
|
|
4106
|
+
slots.sort((a, b) => a.along - b.along || (a.arrow < b.arrow ? -1 : 1));
|
|
4107
|
+
const span = face === "right" || face === "left" ? node.height : node.width;
|
|
4108
|
+
const usable = Math.max(0, span - LANE_INSET * 2);
|
|
4109
|
+
const spread = Math.min(LANE_SPREAD, usable / Math.max(1, slots.length - 1));
|
|
4110
|
+
slots.forEach((slot, i) => {
|
|
4111
|
+
const shift = (i - (slots.length - 1) / 2) * spread;
|
|
4112
|
+
const so_far = offsets.get(slot.arrow) ?? { start: 0, end: 0 };
|
|
4113
|
+
offsets.set(slot.arrow, { ...so_far, [slot.end]: shift });
|
|
4114
|
+
});
|
|
4115
|
+
}
|
|
4116
|
+
return offsets;
|
|
4117
|
+
}
|
|
4118
|
+
function facePoints(from, to, startGap, endGap, lane) {
|
|
4119
|
+
const [fx, fy] = centre(from);
|
|
4120
|
+
const [tx, ty] = centre(to);
|
|
4121
|
+
const dx = tx - fx;
|
|
4122
|
+
const dy = ty - fy;
|
|
4123
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
4124
|
+
const sx = dx >= 0 ? from.x + from.width + startGap : from.x - startGap;
|
|
4125
|
+
const ex = dx >= 0 ? to.x - endGap : to.x + to.width + endGap;
|
|
4126
|
+
return [sx, fy + lane.start, ex, ty + lane.end];
|
|
4127
|
+
}
|
|
4128
|
+
const sy = dy >= 0 ? from.y + from.height + startGap : from.y - startGap;
|
|
4129
|
+
const ey = dy >= 0 ? to.y - endGap : to.y + to.height + endGap;
|
|
4130
|
+
return [fx + lane.start, sy, tx + lane.end, ey];
|
|
4131
|
+
}
|
|
4132
|
+
function elbowRoute(dx, dy) {
|
|
4133
|
+
const ALIGNED = 1;
|
|
4134
|
+
if (Math.abs(dy) <= ALIGNED || Math.abs(dx) <= ALIGNED) {
|
|
4135
|
+
return [point(0, 0), point(dx, dy)];
|
|
4136
|
+
}
|
|
4137
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
4138
|
+
const mid2 = dx / 2;
|
|
4139
|
+
return [point(0, 0), point(mid2, 0), point(mid2, dy), point(dx, dy)];
|
|
4140
|
+
}
|
|
4141
|
+
const mid = dy / 2;
|
|
4142
|
+
return [point(0, 0), point(0, mid), point(dx, mid), point(dx, dy)];
|
|
3372
4143
|
}
|
|
3373
4144
|
function redrawArrowsTouching(scene, moved) {
|
|
3374
4145
|
const current = new Map(scene.map((element) => [element.id, moved.get(element.id) ?? element]));
|
|
3375
4146
|
const labels = labelsByContainer([...current.values()]);
|
|
3376
4147
|
const redrawn = [];
|
|
4148
|
+
const lanes = laneOffsets([...current.values()], current);
|
|
4149
|
+
const NO_LANE = { start: 0, end: 0 };
|
|
3377
4150
|
for (const element of current.values()) {
|
|
3378
4151
|
if (element.type !== "arrow") continue;
|
|
3379
4152
|
const fromId = startOf(element);
|
|
3380
4153
|
const toId = endOf(element);
|
|
3381
4154
|
if (fromId === null || toId === null) continue;
|
|
3382
4155
|
if (!moved.has(fromId) && !moved.has(toId)) continue;
|
|
3383
|
-
redrawn.push(...redrawOne(current, labels, element));
|
|
4156
|
+
redrawn.push(...redrawOne(current, labels, element, lanes.get(element.id) ?? NO_LANE));
|
|
3384
4157
|
}
|
|
3385
4158
|
return redrawn;
|
|
3386
4159
|
}
|
|
@@ -3388,13 +4161,16 @@ function redrawArrows(scene, ids) {
|
|
|
3388
4161
|
const current = new Map(scene.map((element) => [element.id, element]));
|
|
3389
4162
|
const labels = labelsByContainer(scene);
|
|
3390
4163
|
const wanted = new Set(ids);
|
|
3391
|
-
|
|
4164
|
+
const lanes = laneOffsets([...current.values()], current);
|
|
4165
|
+
const NO_LANE = { start: 0, end: 0 };
|
|
4166
|
+
return [...current.values()].filter((element) => wanted.has(element.id)).flatMap((element) => redrawOne(current, labels, element, lanes.get(element.id) ?? NO_LANE));
|
|
3392
4167
|
}
|
|
3393
4168
|
function translate(scene, deltas) {
|
|
4169
|
+
const widened = withAnchoredElements(scene, withFrameChildren(scene, deltas));
|
|
3394
4170
|
const labels = labelsByContainer(scene);
|
|
3395
4171
|
const moved = /* @__PURE__ */ new Map();
|
|
3396
4172
|
for (const element of scene) {
|
|
3397
|
-
const delta =
|
|
4173
|
+
const delta = widened.get(element.id);
|
|
3398
4174
|
if (delta === void 0) continue;
|
|
3399
4175
|
const [dx, dy] = delta;
|
|
3400
4176
|
if (dx === 0 && dy === 0) continue;
|
|
@@ -3404,7 +4180,10 @@ function translate(scene, deltas) {
|
|
|
3404
4180
|
moved.set(label.id, { ...label, x: label.x + dx, y: label.y + dy });
|
|
3405
4181
|
}
|
|
3406
4182
|
}
|
|
3407
|
-
|
|
4183
|
+
const changed = new Map(moved);
|
|
4184
|
+
for (const element of redrawArrowsTouching(scene, moved)) changed.set(element.id, element);
|
|
4185
|
+
for (const element of refitFrames(scene, moved)) changed.set(element.id, element);
|
|
4186
|
+
return [...changed.values()];
|
|
3408
4187
|
}
|
|
3409
4188
|
var alignments = {
|
|
3410
4189
|
"align-left": (box, bounds) => [bounds.x - box.x, 0],
|
|
@@ -3416,14 +4195,14 @@ var alignments = {
|
|
|
3416
4195
|
"align-center-h": (box, bounds) => [0, bounds.y + bounds.height / 2 - (box.y + box.height / 2)],
|
|
3417
4196
|
"align-center-v": (box, bounds) => [bounds.x + bounds.width / 2 - (box.x + box.width / 2), 0]
|
|
3418
4197
|
};
|
|
3419
|
-
function distribute(boxes, spacing,
|
|
3420
|
-
const size =
|
|
3421
|
-
const order = boxes.map((box, i) => ({ box, i })).sort((a, b) => a.box[
|
|
4198
|
+
function distribute(boxes, spacing, axis2) {
|
|
4199
|
+
const size = axis2 === "x" ? "width" : "height";
|
|
4200
|
+
const order = boxes.map((box, i) => ({ box, i })).sort((a, b) => a.box[axis2] - b.box[axis2]);
|
|
3422
4201
|
const deltas = boxes.map(() => [0, 0]);
|
|
3423
|
-
let cursor = order[0]?.box[
|
|
4202
|
+
let cursor = order[0]?.box[axis2] ?? 0;
|
|
3424
4203
|
for (const { box, i } of order) {
|
|
3425
|
-
const delta = cursor - box[
|
|
3426
|
-
deltas[i] =
|
|
4204
|
+
const delta = cursor - box[axis2];
|
|
4205
|
+
deltas[i] = axis2 === "x" ? [delta, 0] : [0, delta];
|
|
3427
4206
|
cursor += box[size] + spacing;
|
|
3428
4207
|
}
|
|
3429
4208
|
return deltas;
|
|
@@ -3439,10 +4218,10 @@ function grid(boxes, options) {
|
|
|
3439
4218
|
return [anchor.x + column * cellW - box.x, anchor.y + rowIndex * cellH - box.y];
|
|
3440
4219
|
});
|
|
3441
4220
|
}
|
|
3442
|
-
function flip(boxes,
|
|
4221
|
+
function flip(boxes, axis2) {
|
|
3443
4222
|
const bounds = boundsOfAll(boxes);
|
|
3444
4223
|
return boxes.map((box) => {
|
|
3445
|
-
if (
|
|
4224
|
+
if (axis2 === "x") {
|
|
3446
4225
|
const mirrored2 = bounds.x + bounds.width - (box.x - bounds.x) - box.width;
|
|
3447
4226
|
return [mirrored2 - box.x, 0];
|
|
3448
4227
|
}
|
|
@@ -3503,6 +4282,12 @@ function arrangeGeometry(input) {
|
|
|
3503
4282
|
if (input.op === "layout-layered") {
|
|
3504
4283
|
return ok({ moved: layoutLayered(input.scene, targets, input.spacing) });
|
|
3505
4284
|
}
|
|
4285
|
+
if (input.op === "layout-radial") {
|
|
4286
|
+
return ok({ moved: layoutRadial(input.scene, targets, input.spacing) });
|
|
4287
|
+
}
|
|
4288
|
+
if (input.op === "layout-tree") {
|
|
4289
|
+
return ok({ moved: layoutTree(input.scene, targets, input.spacing) });
|
|
4290
|
+
}
|
|
3506
4291
|
const boxes = targets.map(boxOf);
|
|
3507
4292
|
const spacing = input.spacing ?? HOUSE.spacing.h;
|
|
3508
4293
|
const columns = input.columns ?? Math.ceil(Math.sqrt(targets.length));
|
|
@@ -3534,6 +4319,44 @@ function arrangeGeometry(input) {
|
|
|
3534
4319
|
});
|
|
3535
4320
|
return ok({ moved: translate(input.scene, byDelta) });
|
|
3536
4321
|
}
|
|
4322
|
+
function backEdgeIndices(nodeIds, edges) {
|
|
4323
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
4324
|
+
for (const id of nodeIds) outgoing.set(id, []);
|
|
4325
|
+
edges.forEach((edge, index) => {
|
|
4326
|
+
outgoing.get(edge.from)?.push(index);
|
|
4327
|
+
});
|
|
4328
|
+
const ON_STACK = 1;
|
|
4329
|
+
const DONE = 2;
|
|
4330
|
+
const state = /* @__PURE__ */ new Map();
|
|
4331
|
+
const back = /* @__PURE__ */ new Set();
|
|
4332
|
+
for (const root of [...nodeIds].sort()) {
|
|
4333
|
+
if (state.has(root)) continue;
|
|
4334
|
+
state.set(root, ON_STACK);
|
|
4335
|
+
const stack = [{ node: root, cursor: 0 }];
|
|
4336
|
+
while (stack.length > 0) {
|
|
4337
|
+
const frame = stack[stack.length - 1];
|
|
4338
|
+
if (frame === void 0) break;
|
|
4339
|
+
const fanOut = outgoing.get(frame.node) ?? [];
|
|
4340
|
+
if (frame.cursor >= fanOut.length) {
|
|
4341
|
+
state.set(frame.node, DONE);
|
|
4342
|
+
stack.pop();
|
|
4343
|
+
continue;
|
|
4344
|
+
}
|
|
4345
|
+
const index = fanOut[frame.cursor];
|
|
4346
|
+
frame.cursor += 1;
|
|
4347
|
+
if (index === void 0) continue;
|
|
4348
|
+
const head = edges[index]?.to;
|
|
4349
|
+
if (head === void 0) continue;
|
|
4350
|
+
const seen = state.get(head);
|
|
4351
|
+
if (seen === ON_STACK) back.add(index);
|
|
4352
|
+
else if (seen === void 0) {
|
|
4353
|
+
state.set(head, ON_STACK);
|
|
4354
|
+
stack.push({ node: head, cursor: 0 });
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
return back;
|
|
4359
|
+
}
|
|
3537
4360
|
function layoutLayered(scene, targets, spacing) {
|
|
3538
4361
|
const inSet = new Set(targets.map((element) => element.id));
|
|
3539
4362
|
const edges = [];
|
|
@@ -3545,22 +4368,20 @@ function layoutLayered(scene, targets, spacing) {
|
|
|
3545
4368
|
if (!inSet.has(from) || !inSet.has(to) || from === to) continue;
|
|
3546
4369
|
edges.push({ from, to });
|
|
3547
4370
|
}
|
|
3548
|
-
const nodes = targets.filter((element) => !isLinear(element));
|
|
4371
|
+
const nodes = targets.filter((element) => !isLinear(element) && !isAnchored(element));
|
|
3549
4372
|
if (nodes.length === 0) return [];
|
|
4373
|
+
const back = backEdgeIndices(
|
|
4374
|
+
nodes.map((node) => node.id),
|
|
4375
|
+
edges
|
|
4376
|
+
);
|
|
4377
|
+
const forward = edges.filter((_, index) => !back.has(index));
|
|
3550
4378
|
const incoming = /* @__PURE__ */ new Map();
|
|
3551
|
-
const
|
|
3552
|
-
for (const
|
|
3553
|
-
incoming.set(node.id, []);
|
|
3554
|
-
outgoing.set(node.id, []);
|
|
3555
|
-
}
|
|
3556
|
-
for (const edge of edges) {
|
|
3557
|
-
incoming.get(edge.to)?.push(edge.from);
|
|
3558
|
-
outgoing.get(edge.from)?.push(edge.to);
|
|
3559
|
-
}
|
|
4379
|
+
for (const node of nodes) incoming.set(node.id, []);
|
|
4380
|
+
for (const edge of forward) incoming.get(edge.to)?.push(edge.from);
|
|
3560
4381
|
const rank = new Map(nodes.map((node) => [node.id, 0]));
|
|
3561
4382
|
for (let pass = 0; pass < nodes.length; pass++) {
|
|
3562
4383
|
let settled = true;
|
|
3563
|
-
for (const edge of
|
|
4384
|
+
for (const edge of forward) {
|
|
3564
4385
|
const from = rank.get(edge.from) ?? 0;
|
|
3565
4386
|
const to = rank.get(edge.to) ?? 0;
|
|
3566
4387
|
if (to < from + 1) {
|
|
@@ -3570,60 +4391,361 @@ function layoutLayered(scene, targets, spacing) {
|
|
|
3570
4391
|
}
|
|
3571
4392
|
if (settled) break;
|
|
3572
4393
|
}
|
|
3573
|
-
const
|
|
4394
|
+
const LANE_WIDTH = 0;
|
|
4395
|
+
const LANE_HEIGHT = 48;
|
|
4396
|
+
const slots = /* @__PURE__ */ new Map();
|
|
4397
|
+
const push = (r, slot) => {
|
|
4398
|
+
const bucket = slots.get(r);
|
|
4399
|
+
if (bucket === void 0) slots.set(r, [slot]);
|
|
4400
|
+
else bucket.push(slot);
|
|
4401
|
+
};
|
|
3574
4402
|
for (const node of nodes) {
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
4403
|
+
push(rank.get(node.id) ?? 0, {
|
|
4404
|
+
key: node.id,
|
|
4405
|
+
box: boxOf(node),
|
|
4406
|
+
parents: incoming.get(node.id) ?? [],
|
|
4407
|
+
node
|
|
4408
|
+
});
|
|
3579
4409
|
}
|
|
3580
|
-
const
|
|
3581
|
-
|
|
4410
|
+
for (const edge of edges) {
|
|
4411
|
+
const from = rank.get(edge.from);
|
|
4412
|
+
const to = rank.get(edge.to);
|
|
4413
|
+
if (from === void 0 || to === void 0) continue;
|
|
4414
|
+
let previous = edge.from;
|
|
4415
|
+
for (let r = Math.min(from, to) + 1; r < Math.max(from, to); r++) {
|
|
4416
|
+
const key = `lane:${edge.from}>${edge.to}@${r}`;
|
|
4417
|
+
push(r, {
|
|
4418
|
+
key,
|
|
4419
|
+
box: { x: 0, y: 0, width: LANE_WIDTH, height: LANE_HEIGHT },
|
|
4420
|
+
parents: [previous]
|
|
4421
|
+
});
|
|
4422
|
+
previous = key;
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
const rankNumbers = [...slots.keys()].sort((a, b) => a - b);
|
|
4426
|
+
const childrenOf2 = /* @__PURE__ */ new Map();
|
|
3582
4427
|
for (const r of rankNumbers) {
|
|
3583
|
-
const
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
(a, b) => a.barycentre - b.barycentre || (a.node.id < b.node.id ? -1 : a.node.id > b.node.id ? 1 : 0)
|
|
3591
|
-
);
|
|
3592
|
-
scored.forEach(({ node }, i) => {
|
|
3593
|
-
position.set(node.id, i);
|
|
3594
|
-
});
|
|
3595
|
-
ranks.set(
|
|
3596
|
-
r,
|
|
3597
|
-
scored.map((entry) => entry.node)
|
|
3598
|
-
);
|
|
4428
|
+
for (const slot of slots.get(r) ?? []) {
|
|
4429
|
+
for (const parent of slot.parents) {
|
|
4430
|
+
const kids = childrenOf2.get(parent);
|
|
4431
|
+
if (kids === void 0) childrenOf2.set(parent, [slot.key]);
|
|
4432
|
+
else kids.push(slot.key);
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
3599
4435
|
}
|
|
3600
|
-
const
|
|
4436
|
+
const indicesOf = (ordering) => {
|
|
4437
|
+
const at = /* @__PURE__ */ new Map();
|
|
4438
|
+
for (const bucket of ordering.values()) {
|
|
4439
|
+
bucket.forEach((slot, i) => {
|
|
4440
|
+
at.set(slot.key, i);
|
|
4441
|
+
});
|
|
4442
|
+
}
|
|
4443
|
+
return at;
|
|
4444
|
+
};
|
|
4445
|
+
const crossings = (ordering) => {
|
|
4446
|
+
const at = indicesOf(ordering);
|
|
4447
|
+
let total = 0;
|
|
4448
|
+
for (const r of rankNumbers) {
|
|
4449
|
+
const pairs = [];
|
|
4450
|
+
for (const slot of ordering.get(r) ?? []) {
|
|
4451
|
+
const below = at.get(slot.key);
|
|
4452
|
+
if (below === void 0) continue;
|
|
4453
|
+
for (const parent of slot.parents) {
|
|
4454
|
+
const above = at.get(parent);
|
|
4455
|
+
if (above !== void 0) pairs.push([above, below]);
|
|
4456
|
+
}
|
|
4457
|
+
}
|
|
4458
|
+
for (let i = 0; i < pairs.length; i++) {
|
|
4459
|
+
for (let j = i + 1; j < pairs.length; j++) {
|
|
4460
|
+
const a = pairs[i];
|
|
4461
|
+
const b = pairs[j];
|
|
4462
|
+
if (a === void 0 || b === void 0) continue;
|
|
4463
|
+
if ((a[0] - b[0]) * (a[1] - b[1]) < 0) total += 1;
|
|
4464
|
+
}
|
|
4465
|
+
}
|
|
4466
|
+
}
|
|
4467
|
+
return total;
|
|
4468
|
+
};
|
|
4469
|
+
const sweep = (ordering, direction) => {
|
|
4470
|
+
const at = indicesOf(ordering);
|
|
4471
|
+
const next = /* @__PURE__ */ new Map();
|
|
4472
|
+
const order = direction === "down" ? rankNumbers : [...rankNumbers].reverse();
|
|
4473
|
+
for (const r of order) {
|
|
4474
|
+
const scored = (ordering.get(r) ?? []).map((slot) => {
|
|
4475
|
+
const adjacent = direction === "down" ? slot.parents : childrenOf2.get(slot.key) ?? [];
|
|
4476
|
+
const known = adjacent.map((key) => at.get(key)).filter((index) => index !== void 0);
|
|
4477
|
+
return {
|
|
4478
|
+
slot,
|
|
4479
|
+
barycentre: known.length === 0 ? Number.POSITIVE_INFINITY : known.reduce((sum, index) => sum + index, 0) / known.length
|
|
4480
|
+
};
|
|
4481
|
+
});
|
|
4482
|
+
scored.sort(
|
|
4483
|
+
(a, b) => a.barycentre - b.barycentre || (a.slot.key < b.slot.key ? -1 : a.slot.key > b.slot.key ? 1 : 0)
|
|
4484
|
+
);
|
|
4485
|
+
const settled = scored.map((entry) => entry.slot);
|
|
4486
|
+
settled.forEach((slot, i) => {
|
|
4487
|
+
at.set(slot.key, i);
|
|
4488
|
+
});
|
|
4489
|
+
next.set(r, settled);
|
|
4490
|
+
}
|
|
4491
|
+
return next;
|
|
4492
|
+
};
|
|
4493
|
+
const SWEEPS = 4;
|
|
4494
|
+
let current = new Map(rankNumbers.map((r) => [r, [...slots.get(r) ?? []]]));
|
|
4495
|
+
let best = current;
|
|
4496
|
+
let fewest = Number.POSITIVE_INFINITY;
|
|
4497
|
+
for (let round = 0; round < SWEEPS; round++) {
|
|
4498
|
+
current = sweep(current, round % 2 === 0 ? "down" : "up");
|
|
4499
|
+
const score = crossings(current);
|
|
4500
|
+
if (score < fewest) {
|
|
4501
|
+
fewest = score;
|
|
4502
|
+
best = current;
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
for (const r of rankNumbers) slots.set(r, [...best.get(r) ?? []]);
|
|
3601
4506
|
const gapV = HOUSE.spacing.v;
|
|
4507
|
+
const labelWidths = labelsByContainer(scene);
|
|
4508
|
+
const captionRoom = (column) => {
|
|
4509
|
+
const floor = spacing ?? HOUSE.spacing.h;
|
|
4510
|
+
let widest = 0;
|
|
4511
|
+
for (const edge of edges) {
|
|
4512
|
+
const from = rank.get(edge.from);
|
|
4513
|
+
const to = rank.get(edge.to);
|
|
4514
|
+
if (from === void 0 || to === void 0) continue;
|
|
4515
|
+
const low = Math.min(from, to);
|
|
4516
|
+
const high = Math.max(from, to);
|
|
4517
|
+
if (column < low || column >= high) continue;
|
|
4518
|
+
const arrow = scene.find(
|
|
4519
|
+
(element) => element.type === "arrow" && startOf(element) === edge.from && endOf(element) === edge.to
|
|
4520
|
+
);
|
|
4521
|
+
const caption = arrow === void 0 ? void 0 : labelWidths.get(arrow.id);
|
|
4522
|
+
if (caption !== void 0) widest = Math.max(widest, caption.width);
|
|
4523
|
+
}
|
|
4524
|
+
return Math.max(floor, widest + HOUSE.sizes.labelPadX * 2);
|
|
4525
|
+
};
|
|
3602
4526
|
const anchor = boundsOfAll(nodes.map(boxOf));
|
|
3603
4527
|
const columnWidths = rankNumbers.map(
|
|
3604
|
-
(r) => Math.max(...(
|
|
4528
|
+
(r) => Math.max(...(slots.get(r) ?? []).map((slot) => slot.box.width))
|
|
3605
4529
|
);
|
|
3606
4530
|
const tallest = Math.max(
|
|
3607
4531
|
...rankNumbers.map(
|
|
3608
|
-
(r) => (
|
|
4532
|
+
(r) => (slots.get(r) ?? []).reduce((sum, slot) => sum + slot.box.height + gapV, -gapV)
|
|
3609
4533
|
)
|
|
3610
4534
|
);
|
|
3611
4535
|
const deltas = /* @__PURE__ */ new Map();
|
|
3612
4536
|
let cursorX = anchor.x;
|
|
3613
4537
|
rankNumbers.forEach((r, column) => {
|
|
3614
|
-
const bucket =
|
|
3615
|
-
const columnHeight = bucket.reduce((sum,
|
|
4538
|
+
const bucket = slots.get(r) ?? [];
|
|
4539
|
+
const columnHeight = bucket.reduce((sum, slot) => sum + slot.box.height + gapV, -gapV);
|
|
3616
4540
|
let cursorY = anchor.y + (tallest - columnHeight) / 2;
|
|
3617
4541
|
const width = columnWidths[column] ?? 0;
|
|
3618
|
-
for (const
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
4542
|
+
for (const slot of bucket) {
|
|
4543
|
+
if (slot.node !== void 0) {
|
|
4544
|
+
const x = cursorX + (width - slot.box.width) / 2;
|
|
4545
|
+
deltas.set(slot.node.id, [x - slot.box.x, cursorY - slot.box.y]);
|
|
4546
|
+
}
|
|
4547
|
+
cursorY += slot.box.height + gapV;
|
|
3622
4548
|
}
|
|
3623
|
-
cursorX += width +
|
|
4549
|
+
cursorX += width + captionRoom(r);
|
|
3624
4550
|
});
|
|
3625
4551
|
return translate(scene, deltas);
|
|
3626
4552
|
}
|
|
4553
|
+
var TAU = Math.PI * 2;
|
|
4554
|
+
var normalizeAngle = (radians) => (radians % TAU + TAU) % TAU;
|
|
4555
|
+
function meanAngle(angles) {
|
|
4556
|
+
let x = 0;
|
|
4557
|
+
let y = 0;
|
|
4558
|
+
for (const angle of angles) {
|
|
4559
|
+
x += Math.cos(angle);
|
|
4560
|
+
y += Math.sin(angle);
|
|
4561
|
+
}
|
|
4562
|
+
return Math.atan2(y, x);
|
|
4563
|
+
}
|
|
4564
|
+
var ringExtent = (element) => Math.max(element.width, element.height);
|
|
4565
|
+
function adjacency(scene, nodes) {
|
|
4566
|
+
const inSet = new Set(nodes.map((node) => node.id));
|
|
4567
|
+
const neighbours = /* @__PURE__ */ new Map();
|
|
4568
|
+
for (const node of nodes) neighbours.set(node.id, /* @__PURE__ */ new Set());
|
|
4569
|
+
for (const element of scene) {
|
|
4570
|
+
if (element.type !== "arrow") continue;
|
|
4571
|
+
const from = startOf(element);
|
|
4572
|
+
const to = endOf(element);
|
|
4573
|
+
if (from === null || to === null || from === to) continue;
|
|
4574
|
+
if (!inSet.has(from) || !inSet.has(to)) continue;
|
|
4575
|
+
neighbours.get(from)?.add(to);
|
|
4576
|
+
neighbours.get(to)?.add(from);
|
|
4577
|
+
}
|
|
4578
|
+
return neighbours;
|
|
4579
|
+
}
|
|
4580
|
+
function pickHub(neighbours, ordered) {
|
|
4581
|
+
const first = ordered[0];
|
|
4582
|
+
if (first === void 0) return void 0;
|
|
4583
|
+
const degree = (element) => neighbours.get(element.id)?.size ?? 0;
|
|
4584
|
+
let hub = first;
|
|
4585
|
+
for (const node of ordered) if (degree(node) > degree(hub)) hub = node;
|
|
4586
|
+
return hub;
|
|
4587
|
+
}
|
|
4588
|
+
function layoutRadial(scene, targets, spacing) {
|
|
4589
|
+
const nodes = targets.filter((element) => !isLinear(element) && !isAnchored(element));
|
|
4590
|
+
const ordered = [...nodes].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
4591
|
+
const neighbours = adjacency(scene, nodes);
|
|
4592
|
+
const hub = pickHub(neighbours, ordered);
|
|
4593
|
+
if (hub === void 0) return [];
|
|
4594
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
4595
|
+
const gap = spacing ?? HOUSE.spacing.h;
|
|
4596
|
+
const ringOf = /* @__PURE__ */ new Map([[hub.id, 0]]);
|
|
4597
|
+
const parentsOf = /* @__PURE__ */ new Map();
|
|
4598
|
+
let frontier = [hub.id];
|
|
4599
|
+
let depth = 0;
|
|
4600
|
+
while (frontier.length > 0) {
|
|
4601
|
+
depth += 1;
|
|
4602
|
+
const discovered = /* @__PURE__ */ new Map();
|
|
4603
|
+
for (const id of frontier) {
|
|
4604
|
+
for (const other of [...neighbours.get(id) ?? []].sort()) {
|
|
4605
|
+
if (ringOf.has(other)) continue;
|
|
4606
|
+
const found = discovered.get(other);
|
|
4607
|
+
if (found === void 0) discovered.set(other, [id]);
|
|
4608
|
+
else found.push(id);
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
for (const [child, parents] of discovered) {
|
|
4612
|
+
ringOf.set(child, depth);
|
|
4613
|
+
parentsOf.set(child, parents);
|
|
4614
|
+
}
|
|
4615
|
+
frontier = [...discovered.keys()];
|
|
4616
|
+
}
|
|
4617
|
+
for (const node of ordered) {
|
|
4618
|
+
if (!ringOf.has(node.id)) ringOf.set(node.id, depth + 1);
|
|
4619
|
+
}
|
|
4620
|
+
const rings = /* @__PURE__ */ new Map();
|
|
4621
|
+
for (const node of ordered) {
|
|
4622
|
+
const ring = ringOf.get(node.id) ?? 0;
|
|
4623
|
+
const bucket = rings.get(ring);
|
|
4624
|
+
if (bucket === void 0) rings.set(ring, [node.id]);
|
|
4625
|
+
else bucket.push(node.id);
|
|
4626
|
+
}
|
|
4627
|
+
const [cx, cy] = centre(boundsOfAll(nodes.map(boxOf)));
|
|
4628
|
+
const deltas = /* @__PURE__ */ new Map();
|
|
4629
|
+
deltas.set(hub.id, [cx - (hub.x + hub.width / 2), cy - (hub.y + hub.height / 2)]);
|
|
4630
|
+
const angleOf = /* @__PURE__ */ new Map([[hub.id, 0]]);
|
|
4631
|
+
let cleared = ringExtent(hub) / 2;
|
|
4632
|
+
for (const ring of [...rings.keys()].filter((r) => r > 0).sort((a, b) => a - b)) {
|
|
4633
|
+
const members = rings.get(ring) ?? [];
|
|
4634
|
+
if (members.length === 0) continue;
|
|
4635
|
+
const scored = members.map((id) => {
|
|
4636
|
+
const known = (parentsOf.get(id) ?? []).map((parent) => angleOf.get(parent)).filter((angle) => angle !== void 0);
|
|
4637
|
+
return {
|
|
4638
|
+
id,
|
|
4639
|
+
at: known.length === 0 ? Number.POSITIVE_INFINITY : normalizeAngle(meanAngle(known))
|
|
4640
|
+
};
|
|
4641
|
+
});
|
|
4642
|
+
scored.sort((a, b) => a.at - b.at || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
4643
|
+
const extents = members.map((id) => {
|
|
4644
|
+
const node = byId.get(id);
|
|
4645
|
+
return node === void 0 ? 0 : ringExtent(node);
|
|
4646
|
+
});
|
|
4647
|
+
const widest = Math.max(...extents);
|
|
4648
|
+
const seating = extents.reduce((sum, extent) => sum + extent + gap, 0) / TAU;
|
|
4649
|
+
const radius = Math.max(seating, cleared + widest / 2 + gap);
|
|
4650
|
+
scored.forEach(({ id }, i) => {
|
|
4651
|
+
const theta = -Math.PI / 2 + TAU * i / members.length;
|
|
4652
|
+
angleOf.set(id, normalizeAngle(theta));
|
|
4653
|
+
const node = byId.get(id);
|
|
4654
|
+
if (node === void 0) return;
|
|
4655
|
+
deltas.set(id, [
|
|
4656
|
+
cx + radius * Math.cos(theta) - node.width / 2 - node.x,
|
|
4657
|
+
cy + radius * Math.sin(theta) - node.height / 2 - node.y
|
|
4658
|
+
]);
|
|
4659
|
+
});
|
|
4660
|
+
cleared = radius + widest / 2;
|
|
4661
|
+
}
|
|
4662
|
+
return translate(scene, deltas);
|
|
4663
|
+
}
|
|
4664
|
+
function layoutTree(scene, targets, spacing) {
|
|
4665
|
+
const nodes = targets.filter((element) => !isLinear(element) && !isAnchored(element));
|
|
4666
|
+
const ordered = [...nodes].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
4667
|
+
const neighbours = adjacency(scene, nodes);
|
|
4668
|
+
const root = pickHub(neighbours, ordered);
|
|
4669
|
+
if (root === void 0) return [];
|
|
4670
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
4671
|
+
const gapH = spacing ?? HOUSE.spacing.h;
|
|
4672
|
+
const gapV = HOUSE.spacing.v;
|
|
4673
|
+
const depthOf = /* @__PURE__ */ new Map([[root.id, 0]]);
|
|
4674
|
+
const childrenOf2 = /* @__PURE__ */ new Map();
|
|
4675
|
+
const order = [];
|
|
4676
|
+
const stack = [root.id];
|
|
4677
|
+
const seen = /* @__PURE__ */ new Set([root.id]);
|
|
4678
|
+
while (stack.length > 0) {
|
|
4679
|
+
const id = stack.pop();
|
|
4680
|
+
if (id === void 0) continue;
|
|
4681
|
+
order.push(id);
|
|
4682
|
+
const kids = [];
|
|
4683
|
+
const adjacent = [...neighbours.get(id) ?? []].sort();
|
|
4684
|
+
for (const other of adjacent) {
|
|
4685
|
+
if (seen.has(other)) continue;
|
|
4686
|
+
seen.add(other);
|
|
4687
|
+
depthOf.set(other, (depthOf.get(id) ?? 0) + 1);
|
|
4688
|
+
kids.push(other);
|
|
4689
|
+
}
|
|
4690
|
+
childrenOf2.set(id, kids);
|
|
4691
|
+
for (let i = kids.length - 1; i >= 0; i--) {
|
|
4692
|
+
const kid = kids[i];
|
|
4693
|
+
if (kid !== void 0) stack.push(kid);
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4696
|
+
for (const node of ordered) {
|
|
4697
|
+
if (seen.has(node.id)) continue;
|
|
4698
|
+
seen.add(node.id);
|
|
4699
|
+
depthOf.set(node.id, 1);
|
|
4700
|
+
order.push(node.id);
|
|
4701
|
+
}
|
|
4702
|
+
const columns = /* @__PURE__ */ new Map();
|
|
4703
|
+
for (const id of order) {
|
|
4704
|
+
const d = depthOf.get(id) ?? 0;
|
|
4705
|
+
const bucket = columns.get(d);
|
|
4706
|
+
if (bucket === void 0) columns.set(d, [id]);
|
|
4707
|
+
else bucket.push(id);
|
|
4708
|
+
}
|
|
4709
|
+
const captions = labelsByContainer(scene);
|
|
4710
|
+
const crossing = /* @__PURE__ */ new Map();
|
|
4711
|
+
for (const element of scene) {
|
|
4712
|
+
if (element.type !== "arrow") continue;
|
|
4713
|
+
const from = startOf(element);
|
|
4714
|
+
const to = endOf(element);
|
|
4715
|
+
if (from === null || to === null) continue;
|
|
4716
|
+
const a = depthOf.get(from);
|
|
4717
|
+
const b = depthOf.get(to);
|
|
4718
|
+
if (a === void 0 || b === void 0 || a === b) continue;
|
|
4719
|
+
const caption = captions.get(element.id);
|
|
4720
|
+
if (caption === void 0) continue;
|
|
4721
|
+
const gap = Math.min(a, b);
|
|
4722
|
+
crossing.set(gap, Math.max(crossing.get(gap) ?? 0, caption.width));
|
|
4723
|
+
}
|
|
4724
|
+
const anchor = boundsOfAll(nodes.map(boxOf));
|
|
4725
|
+
const depths = [...columns.keys()].sort((a, b) => a - b);
|
|
4726
|
+
const columnX = /* @__PURE__ */ new Map();
|
|
4727
|
+
let cursorX = anchor.x;
|
|
4728
|
+
for (const d of depths) {
|
|
4729
|
+
columnX.set(d, cursorX);
|
|
4730
|
+
const widest = Math.max(...(columns.get(d) ?? []).map((id) => byId.get(id)?.width ?? 0));
|
|
4731
|
+
const caption = crossing.get(d) ?? 0;
|
|
4732
|
+
cursorX += widest + Math.max(gapH, caption === 0 ? 0 : caption + HOUSE.sizes.labelPadX * 2);
|
|
4733
|
+
}
|
|
4734
|
+
const columnHeight = (d) => (columns.get(d) ?? []).reduce((sum, id) => sum + (byId.get(id)?.height ?? 0) + gapV, -gapV);
|
|
4735
|
+
const tallest = Math.max(...depths.map(columnHeight));
|
|
4736
|
+
const deltas = /* @__PURE__ */ new Map();
|
|
4737
|
+
for (const d of depths) {
|
|
4738
|
+
let cursorY = anchor.y + (tallest - columnHeight(d)) / 2;
|
|
4739
|
+
for (const id of columns.get(d) ?? []) {
|
|
4740
|
+
const node = byId.get(id);
|
|
4741
|
+
if (node === void 0) continue;
|
|
4742
|
+
const x = columnX.get(d) ?? anchor.x;
|
|
4743
|
+
deltas.set(id, [x - node.x, cursorY - node.y]);
|
|
4744
|
+
cursorY += node.height + gapV;
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
return translate(scene, deltas);
|
|
4748
|
+
}
|
|
3627
4749
|
|
|
3628
4750
|
// src/app/usecases/write/arrange-elements.ts
|
|
3629
4751
|
function arrangeElements(deps) {
|
|
@@ -3644,6 +4766,7 @@ function arrangeElements(deps) {
|
|
|
3644
4766
|
sceneId: applied.value.sceneId,
|
|
3645
4767
|
sceneVersion: applied.value.sceneVersion,
|
|
3646
4768
|
broadcastTo: applied.value.broadcastTo,
|
|
4769
|
+
overlapWarning: applied.value.overlapWarning,
|
|
3647
4770
|
moved: [...applied.value.applied]
|
|
3648
4771
|
});
|
|
3649
4772
|
};
|
|
@@ -3798,82 +4921,6 @@ function createDebugElement(deps) {
|
|
|
3798
4921
|
};
|
|
3799
4922
|
}
|
|
3800
4923
|
|
|
3801
|
-
// src/domain/scene/skeleton.ts
|
|
3802
|
-
var isContainer = (type) => type === "rectangle" || type === "diamond" || type === "ellipse";
|
|
3803
|
-
function explicit(input) {
|
|
3804
|
-
const kept = {};
|
|
3805
|
-
for (const [key, value] of Object.entries(input)) {
|
|
3806
|
-
if (value === void 0) continue;
|
|
3807
|
-
if (key === "type" || key === "id" || key === "role" || key === "label") continue;
|
|
3808
|
-
kept[key] = value;
|
|
3809
|
-
}
|
|
3810
|
-
return kept;
|
|
3811
|
-
}
|
|
3812
|
-
var defaultSize = () => serviceSize();
|
|
3813
|
-
function normalizeElements(input) {
|
|
3814
|
-
return input.elements.map((element, i) => {
|
|
3815
|
-
const fallback = input.ids[i];
|
|
3816
|
-
if (fallback === void 0) {
|
|
3817
|
-
throw new Error(`normalizeElements needs one id per element: none at position ${i}`);
|
|
3818
|
-
}
|
|
3819
|
-
const id = element.id ?? fallback;
|
|
3820
|
-
const role = styleForRole(element.role ?? "compute");
|
|
3821
|
-
const set = explicit(element);
|
|
3822
|
-
if (element.type === "text") {
|
|
3823
|
-
const text = element.text ?? element.label ?? "";
|
|
3824
|
-
const skeleton2 = {
|
|
3825
|
-
type: "text",
|
|
3826
|
-
id,
|
|
3827
|
-
x: element.x,
|
|
3828
|
-
y: element.y,
|
|
3829
|
-
fontSize: HOUSE.fontSize,
|
|
3830
|
-
fontFamily: HOUSE.fontFamily,
|
|
3831
|
-
// A free-standing note is not a service box: it takes the role's ink but never
|
|
3832
|
-
// its fill, because a filled rectangle behind a caption reads as a shape.
|
|
3833
|
-
strokeColor: role.strokeColor,
|
|
3834
|
-
...set,
|
|
3835
|
-
text
|
|
3836
|
-
};
|
|
3837
|
-
return skeleton2;
|
|
3838
|
-
}
|
|
3839
|
-
if (element.type === "line") {
|
|
3840
|
-
const skeleton2 = {
|
|
3841
|
-
type: "line",
|
|
3842
|
-
id,
|
|
3843
|
-
x: element.x,
|
|
3844
|
-
y: element.y,
|
|
3845
|
-
strokeColor: role.strokeColor,
|
|
3846
|
-
strokeWidth: HOUSE.strokeWidth,
|
|
3847
|
-
roughness: HOUSE.roughness,
|
|
3848
|
-
...set,
|
|
3849
|
-
...element.label === void 0 ? {} : { label: houseLabel(element.label) }
|
|
3850
|
-
};
|
|
3851
|
-
return skeleton2;
|
|
3852
|
-
}
|
|
3853
|
-
if (!isContainer(element.type)) {
|
|
3854
|
-
throw new Error(`normalizeElements cannot build a ${element.type}`);
|
|
3855
|
-
}
|
|
3856
|
-
const skeleton = {
|
|
3857
|
-
type: element.type,
|
|
3858
|
-
id,
|
|
3859
|
-
x: element.x,
|
|
3860
|
-
y: element.y,
|
|
3861
|
-
...defaultSize(),
|
|
3862
|
-
...role,
|
|
3863
|
-
...set,
|
|
3864
|
-
...element.label === void 0 ? {} : { label: houseLabel(element.label) }
|
|
3865
|
-
};
|
|
3866
|
-
return skeleton;
|
|
3867
|
-
});
|
|
3868
|
-
}
|
|
3869
|
-
var LAYOUT_HINT_THRESHOLD = 4;
|
|
3870
|
-
function layoutHint(elements) {
|
|
3871
|
-
if (elements.length < LAYOUT_HINT_THRESHOLD) return null;
|
|
3872
|
-
const positions = new Set(elements.map((element) => `${element.x},${element.y}`));
|
|
3873
|
-
if (positions.size > 2) return null;
|
|
3874
|
-
return `${elements.length} elements with no explicit layout \u2014 chalk_arrange(op: "layout-layered") will place them`;
|
|
3875
|
-
}
|
|
3876
|
-
|
|
3877
4924
|
// src/app/usecases/write/create-elements.ts
|
|
3878
4925
|
function createElements(deps) {
|
|
3879
4926
|
return async (input) => {
|
|
@@ -3901,20 +4948,6 @@ function createElements(deps) {
|
|
|
3901
4948
|
}
|
|
3902
4949
|
|
|
3903
4950
|
// src/app/usecases/write/frame-elements.ts
|
|
3904
|
-
function boundsAround(children) {
|
|
3905
|
-
const pad2 = HOUSE.sizes.framePad;
|
|
3906
|
-
const left = Math.min(...children.map((c) => c.x));
|
|
3907
|
-
const top = Math.min(...children.map((c) => c.y));
|
|
3908
|
-
const right = Math.max(...children.map((c) => c.x + c.width));
|
|
3909
|
-
const bottom = Math.max(...children.map((c) => c.y + c.height));
|
|
3910
|
-
return {
|
|
3911
|
-
x: left - pad2,
|
|
3912
|
-
y: top - pad2,
|
|
3913
|
-
width: right - left + pad2 * 2,
|
|
3914
|
-
// Room at the top for the frame's own name, which renders above its border.
|
|
3915
|
-
height: bottom - top + pad2 * 2
|
|
3916
|
-
};
|
|
3917
|
-
}
|
|
3918
4951
|
function frameElements(deps) {
|
|
3919
4952
|
return async (input) => {
|
|
3920
4953
|
const scene = deps.write.resolve(input.sceneId);
|
|
@@ -3943,6 +4976,18 @@ function frameElements(deps) {
|
|
|
3943
4976
|
if (!created.ok) return created;
|
|
3944
4977
|
const committed = deps.write.append(scene.value, created.value);
|
|
3945
4978
|
if (!committed.ok) return committed;
|
|
4979
|
+
const after = deps.write.resolve(input.sceneId);
|
|
4980
|
+
if (after.ok) {
|
|
4981
|
+
const built = after.value.elements.find((element) => element.id === elementId);
|
|
4982
|
+
if (built !== void 0) {
|
|
4983
|
+
const released = releaseCrossingArrows(after.value.elements, built);
|
|
4984
|
+
if (released.length > 0) {
|
|
4985
|
+
const freed = deps.write.apply(after.value, released);
|
|
4986
|
+
if (!freed.ok) return freed;
|
|
4987
|
+
return ok({ ...freed.value, frameId, children: named2.map((e) => e.id) });
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
}
|
|
3946
4991
|
return ok({ ...committed.value, frameId, children: named2.map((e) => e.id) });
|
|
3947
4992
|
}
|
|
3948
4993
|
if (input.frameId === void 0) {
|
|
@@ -3960,12 +5005,14 @@ function frameElements(deps) {
|
|
|
3960
5005
|
if (members.length > 0) {
|
|
3961
5006
|
changed.push({ ...frame, ...boundsAround(members) });
|
|
3962
5007
|
}
|
|
5008
|
+
changed.push(...releaseCrossingArrows([...scene.value.elements, ...changed], frame));
|
|
3963
5009
|
const applied = deps.write.apply(scene.value, changed);
|
|
3964
5010
|
if (!applied.ok) return applied;
|
|
3965
5011
|
return ok({
|
|
3966
5012
|
sceneId: applied.value.sceneId,
|
|
3967
5013
|
sceneVersion: applied.value.sceneVersion,
|
|
3968
5014
|
broadcastTo: applied.value.broadcastTo,
|
|
5015
|
+
overlapWarning: applied.value.overlapWarning,
|
|
3969
5016
|
frameId: input.frameId,
|
|
3970
5017
|
children: members.map((element) => element.id)
|
|
3971
5018
|
});
|
|
@@ -3991,6 +5038,7 @@ function groupElements(deps) {
|
|
|
3991
5038
|
sceneId: applied.value.sceneId,
|
|
3992
5039
|
sceneVersion: applied.value.sceneVersion,
|
|
3993
5040
|
broadcastTo: applied.value.broadcastTo,
|
|
5041
|
+
overlapWarning: applied.value.overlapWarning,
|
|
3994
5042
|
groupId,
|
|
3995
5043
|
grouped: applied.value.applied
|
|
3996
5044
|
});
|
|
@@ -4011,6 +5059,7 @@ function ungroupElements(deps) {
|
|
|
4011
5059
|
sceneId: applied.value.sceneId,
|
|
4012
5060
|
sceneVersion: applied.value.sceneVersion,
|
|
4013
5061
|
broadcastTo: applied.value.broadcastTo,
|
|
5062
|
+
overlapWarning: applied.value.overlapWarning,
|
|
4014
5063
|
ungrouped: applied.value.applied
|
|
4015
5064
|
});
|
|
4016
5065
|
};
|
|
@@ -4026,6 +5075,29 @@ var iconBox = (art, size) => {
|
|
|
4026
5075
|
};
|
|
4027
5076
|
};
|
|
4028
5077
|
var svgDataUrl = (svg) => DataUrl.parse(`data:image/svg+xml;base64,${Buffer.from(svg, "utf8").toString("base64")}`);
|
|
5078
|
+
function placementOf(elements, input, box) {
|
|
5079
|
+
if (input.attachTo !== void 0) {
|
|
5080
|
+
const anchor = elements.find((element) => element.id === input.attachTo && !element.isDeleted);
|
|
5081
|
+
if (anchor === void 0) return fail(unknownElement(input.attachTo));
|
|
5082
|
+
const sides = input.placement !== void 0 ? [input.placement] : ["above", "below", "right", "left"];
|
|
5083
|
+
const ignore = /* @__PURE__ */ new Set([anchor.id]);
|
|
5084
|
+
for (const side of sides) {
|
|
5085
|
+
const at = anchoredPosition(anchor, box, side);
|
|
5086
|
+
const candidate = { ...at, width: box.width, height: box.height };
|
|
5087
|
+
if (sides.length === 1 || !wouldCollide(candidate, elements, ignore)) {
|
|
5088
|
+
return ok({ ...at, anchorIds: [anchor.id] });
|
|
5089
|
+
}
|
|
5090
|
+
}
|
|
5091
|
+
return ok({
|
|
5092
|
+
...anchoredPosition(anchor, box, "above"),
|
|
5093
|
+
anchorIds: [anchor.id]
|
|
5094
|
+
});
|
|
5095
|
+
}
|
|
5096
|
+
if (input.x === void 0 || input.y === void 0) {
|
|
5097
|
+
return fail(iconNeedsAPlace(input.iconId));
|
|
5098
|
+
}
|
|
5099
|
+
return ok({ x: input.x, y: input.y, anchorIds: null });
|
|
5100
|
+
}
|
|
4029
5101
|
function placeIcon(deps) {
|
|
4030
5102
|
return async (input) => {
|
|
4031
5103
|
const opened = await deps.icons();
|
|
@@ -4044,12 +5116,21 @@ function placeIcon(deps) {
|
|
|
4044
5116
|
if (!resolved.ok) return resolved;
|
|
4045
5117
|
const scene = resolved.value;
|
|
4046
5118
|
const size = input.size ?? HOUSE.sizes.icon;
|
|
4047
|
-
const
|
|
5119
|
+
const box = icon.art.kind === "elements" ? { width: size, height: size } : iconBox(icon.art, size);
|
|
5120
|
+
const spot = placementOf(scene.elements, input, box);
|
|
5121
|
+
if (!spot.ok) return spot;
|
|
5122
|
+
const { x, y, anchorIds } = spot.value;
|
|
5123
|
+
const chalk = {
|
|
5124
|
+
author: "claude",
|
|
5125
|
+
kind: "icon",
|
|
5126
|
+
iconId: icon.id,
|
|
5127
|
+
...anchorIds === null ? {} : { anchorIds }
|
|
5128
|
+
};
|
|
4048
5129
|
if (icon.art.kind === "elements") {
|
|
4049
5130
|
const ids = deps.idGen.elementIds(icon.art.elements.length);
|
|
4050
5131
|
const placed = deps.placer.place({
|
|
4051
5132
|
elements: icon.art.elements,
|
|
4052
|
-
at: { x
|
|
5133
|
+
at: { x, y },
|
|
4053
5134
|
ids
|
|
4054
5135
|
});
|
|
4055
5136
|
const stamped = placed.map(
|
|
@@ -4066,7 +5147,6 @@ function placeIcon(deps) {
|
|
|
4066
5147
|
iconId: icon.id
|
|
4067
5148
|
});
|
|
4068
5149
|
}
|
|
4069
|
-
const box = iconBox(icon.art, size);
|
|
4070
5150
|
const fileId = FileId.parse(deps.idGen.token());
|
|
4071
5151
|
const file = {
|
|
4072
5152
|
id: fileId,
|
|
@@ -4079,8 +5159,8 @@ function placeIcon(deps) {
|
|
|
4079
5159
|
{
|
|
4080
5160
|
type: "image",
|
|
4081
5161
|
id: elementId,
|
|
4082
|
-
x
|
|
4083
|
-
y
|
|
5162
|
+
x,
|
|
5163
|
+
y,
|
|
4084
5164
|
width: box.width,
|
|
4085
5165
|
height: box.height,
|
|
4086
5166
|
fileId,
|
|
@@ -4092,14 +5172,22 @@ function placeIcon(deps) {
|
|
|
4092
5172
|
skeletons.push({
|
|
4093
5173
|
type: "text",
|
|
4094
5174
|
id: labelId,
|
|
4095
|
-
|
|
4096
|
-
y: input.y + box.height + 8,
|
|
5175
|
+
...captionBelow({ x, y }, box),
|
|
4097
5176
|
text: input.label,
|
|
4098
5177
|
width: box.width,
|
|
4099
5178
|
fontSize: HOUSE.fontSize,
|
|
4100
5179
|
fontFamily: HOUSE.fontFamily,
|
|
4101
5180
|
textAlign: "center",
|
|
4102
|
-
|
|
5181
|
+
// The caption carries the anchor too, or the glyph travels with its box and the
|
|
5182
|
+
// word it is captioned with stays behind.
|
|
5183
|
+
customData: {
|
|
5184
|
+
chalk: {
|
|
5185
|
+
author: "claude",
|
|
5186
|
+
kind: "note",
|
|
5187
|
+
iconId: icon.id,
|
|
5188
|
+
...anchorIds === null ? {} : { anchorIds }
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
4103
5191
|
});
|
|
4104
5192
|
}
|
|
4105
5193
|
const expanded = await deps.write.create(scene, skeletons);
|
|
@@ -4185,7 +5273,10 @@ function writeContext(deps) {
|
|
|
4185
5273
|
return ok({
|
|
4186
5274
|
sceneId: scene.id,
|
|
4187
5275
|
sceneVersion: updated.sceneVersion,
|
|
4188
|
-
broadcastTo: broadcast(scene, updated.sceneVersion, pushed, patch?.files)
|
|
5276
|
+
broadcastTo: broadcast(scene, updated.sceneVersion, pushed, patch?.files),
|
|
5277
|
+
// The scene as it now stands, not the batch: a new box buries whatever was
|
|
5278
|
+
// already underneath it, and the batch alone cannot see that.
|
|
5279
|
+
overlapWarning: overlapWarning([...kept, ...expansion.created])
|
|
4189
5280
|
});
|
|
4190
5281
|
},
|
|
4191
5282
|
apply(scene, changed, options) {
|
|
@@ -4195,7 +5286,10 @@ function writeContext(deps) {
|
|
|
4195
5286
|
sceneVersion: scene.sceneVersion,
|
|
4196
5287
|
broadcastTo: 0,
|
|
4197
5288
|
applied: [],
|
|
4198
|
-
rejected: []
|
|
5289
|
+
rejected: [],
|
|
5290
|
+
// Nothing was written, so nothing was made worse. Staying quiet here keeps the
|
|
5291
|
+
// warning meaning "your write did this".
|
|
5292
|
+
overlapWarning: null
|
|
4199
5293
|
});
|
|
4200
5294
|
}
|
|
4201
5295
|
deps.markPresence("writing");
|
|
@@ -4224,7 +5318,8 @@ function writeContext(deps) {
|
|
|
4224
5318
|
sceneVersion: scene.sceneVersion,
|
|
4225
5319
|
broadcastTo: 0,
|
|
4226
5320
|
applied: [...merged.applied],
|
|
4227
|
-
rejected
|
|
5321
|
+
rejected,
|
|
5322
|
+
overlapWarning: null
|
|
4228
5323
|
});
|
|
4229
5324
|
}
|
|
4230
5325
|
const updated = deps.commit(scene.id, merged.elements);
|
|
@@ -4236,7 +5331,8 @@ function writeContext(deps) {
|
|
|
4236
5331
|
sceneVersion: updated.sceneVersion,
|
|
4237
5332
|
broadcastTo: broadcast(scene, updated.sceneVersion, pushed),
|
|
4238
5333
|
applied: [...merged.applied],
|
|
4239
|
-
rejected
|
|
5334
|
+
rejected,
|
|
5335
|
+
overlapWarning: overlapWarning(merged.elements)
|
|
4240
5336
|
});
|
|
4241
5337
|
}
|
|
4242
5338
|
};
|
|
@@ -4326,6 +5422,24 @@ function rawElements(deps) {
|
|
|
4326
5422
|
};
|
|
4327
5423
|
}
|
|
4328
5424
|
|
|
5425
|
+
// src/domain/scene/captions.ts
|
|
5426
|
+
function fitCaption(container, caption) {
|
|
5427
|
+
if (caption.type !== "text") return caption;
|
|
5428
|
+
const source = caption.originalText === "" ? caption.text : caption.originalText;
|
|
5429
|
+
const inner = Math.max(1, container.width - HOUSE.sizes.labelPadX * 2);
|
|
5430
|
+
const lines = wrapLines(source, caption.fontSize, inner);
|
|
5431
|
+
const width = Math.max(...lines.map((line) => measureLine(line, caption.fontSize)));
|
|
5432
|
+
const height = lines.length * caption.fontSize * caption.lineHeight;
|
|
5433
|
+
return {
|
|
5434
|
+
...caption,
|
|
5435
|
+
text: lines.join("\n"),
|
|
5436
|
+
width,
|
|
5437
|
+
height,
|
|
5438
|
+
x: container.x + (container.width - width) / 2,
|
|
5439
|
+
y: container.y + (container.height - height) / 2
|
|
5440
|
+
};
|
|
5441
|
+
}
|
|
5442
|
+
|
|
4329
5443
|
// src/app/usecases/write/update-elements.ts
|
|
4330
5444
|
var missing = (id) => toRejection(id, unknownElement(id));
|
|
4331
5445
|
var onlyRequested = (landed, requested) => {
|
|
@@ -4378,17 +5492,37 @@ function updateElements(deps) {
|
|
|
4378
5492
|
}
|
|
4379
5493
|
}
|
|
4380
5494
|
if (moved.size > 0) {
|
|
5495
|
+
const deltas = /* @__PURE__ */ new Map();
|
|
5496
|
+
for (const [id, next] of moved) {
|
|
5497
|
+
const before = byId.get(id);
|
|
5498
|
+
if (before !== void 0) deltas.set(id, [next.x - before.x, next.y - before.y]);
|
|
5499
|
+
}
|
|
5500
|
+
for (const [id, [dx, dy]] of withAnchoredElements(scene.value.elements, deltas)) {
|
|
5501
|
+
if (deltas.has(id) || dx === 0 && dy === 0) continue;
|
|
5502
|
+
const element = byId.get(id);
|
|
5503
|
+
if (element === void 0) continue;
|
|
5504
|
+
const followed = { ...element, x: element.x + dx, y: element.y + dy };
|
|
5505
|
+
moved.set(id, followed);
|
|
5506
|
+
knockOn.push(followed);
|
|
5507
|
+
}
|
|
4381
5508
|
for (const [id, next] of moved) {
|
|
4382
5509
|
const before = byId.get(id);
|
|
4383
5510
|
const caption = captionOf(scene.value.elements, id);
|
|
4384
5511
|
if (before === void 0 || caption === void 0) continue;
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
5512
|
+
const resized = next.width !== before.width || next.height !== before.height;
|
|
5513
|
+
knockOn.push(
|
|
5514
|
+
resized ? fitCaption(next, caption) : {
|
|
5515
|
+
...caption,
|
|
5516
|
+
x: caption.x + (next.x - before.x),
|
|
5517
|
+
y: caption.y + (next.y - before.y)
|
|
5518
|
+
}
|
|
5519
|
+
);
|
|
4390
5520
|
}
|
|
4391
5521
|
knockOn.push(...redrawArrowsTouching(scene.value.elements, moved));
|
|
5522
|
+
const patched = new Set(changed.map((element) => element.id));
|
|
5523
|
+
knockOn.push(
|
|
5524
|
+
...refitFrames(scene.value.elements, moved).filter((frame) => !patched.has(frame.id))
|
|
5525
|
+
);
|
|
4392
5526
|
}
|
|
4393
5527
|
for (const element of knockOn) {
|
|
4394
5528
|
const before = byId.get(element.id);
|
|
@@ -4402,6 +5536,7 @@ function updateElements(deps) {
|
|
|
4402
5536
|
sceneId: applied.value.sceneId,
|
|
4403
5537
|
sceneVersion: applied.value.sceneVersion,
|
|
4404
5538
|
broadcastTo: applied.value.broadcastTo,
|
|
5539
|
+
overlapWarning: applied.value.overlapWarning,
|
|
4405
5540
|
// What the caller asked for, not everything that moved. A caption that followed its
|
|
4406
5541
|
// box and an arrow that was redrawn are consequences; listing them would turn this
|
|
4407
5542
|
// into "every element that changed" and leave the caller working out which of them
|
|
@@ -4455,6 +5590,7 @@ function deleteElements(deps) {
|
|
|
4455
5590
|
sceneId: applied.value.sceneId,
|
|
4456
5591
|
sceneVersion: applied.value.sceneVersion,
|
|
4457
5592
|
broadcastTo: applied.value.broadcastTo,
|
|
5593
|
+
overlapWarning: applied.value.overlapWarning,
|
|
4458
5594
|
deleted: onlyRequested(
|
|
4459
5595
|
applied.value.applied,
|
|
4460
5596
|
input.deletions.map((deletion) => deletion.id)
|
|
@@ -4509,7 +5645,7 @@ function readEnv(source) {
|
|
|
4509
5645
|
}
|
|
4510
5646
|
|
|
4511
5647
|
// src/composition/container.ts
|
|
4512
|
-
var PACKAGE_VERSION = "1.
|
|
5648
|
+
var PACKAGE_VERSION = "1.1.1";
|
|
4513
5649
|
function readRuntime() {
|
|
4514
5650
|
return {
|
|
4515
5651
|
env: process.env,
|
|
@@ -5117,8 +6253,26 @@ function renderScene(description) {
|
|
|
5117
6253
|
}
|
|
5118
6254
|
lines.push("elements:");
|
|
5119
6255
|
for (const element of description.elements) lines.push(row(element, true));
|
|
6256
|
+
lines.push(...adviceLines(description));
|
|
6257
|
+
lines.push(...overlapLines(description));
|
|
5120
6258
|
return lines.join("\n");
|
|
5121
6259
|
}
|
|
6260
|
+
function adviceLines(description) {
|
|
6261
|
+
return description.layoutAdvice === null ? [] : [`layout: ${description.layoutAdvice}`];
|
|
6262
|
+
}
|
|
6263
|
+
function overlapLines(description) {
|
|
6264
|
+
if (description.overlapCount === 0) return [];
|
|
6265
|
+
const shown = description.overlaps.length;
|
|
6266
|
+
const header = shown === description.overlapCount ? `overlaps: ${description.overlapCount}` : `overlaps: ${description.overlapCount} (worst ${shown} shown)`;
|
|
6267
|
+
return [
|
|
6268
|
+
header,
|
|
6269
|
+
...description.overlaps.map(
|
|
6270
|
+
(overlap) => `! ${shortId2(overlap.a)} \xD7 ${shortId2(overlap.b)} ${px(overlap.width)}x${px(
|
|
6271
|
+
overlap.height
|
|
6272
|
+
)}`
|
|
6273
|
+
)
|
|
6274
|
+
];
|
|
6275
|
+
}
|
|
5122
6276
|
var arrowGlyph = (label) => label === null ? "->" : `-"${label}"->`;
|
|
5123
6277
|
var insideLine = (edge) => ` ${shortId2(edge.from)} ${arrowGlyph(edge.label)} ${shortId2(edge.to)}`;
|
|
5124
6278
|
var named = (id, label) => label === null ? shortId2(id) : `${shortId2(id)} "${label}"`;
|
|
@@ -5235,7 +6389,7 @@ var searchIconsTool = {
|
|
|
5235
6389
|
};
|
|
5236
6390
|
var placeIconTool = {
|
|
5237
6391
|
name: "chalk_place_icon",
|
|
5238
|
-
description: "Put an icon from the catalog on the canvas, at an id from chalk_search_icons. Give it a `label` \u2014 an unlabelled icon says a thing was here and not which thing. The icon id is recorded on the element, so chalk_describe_scene reports it as [icon:aws/lambda] rather than [image] and you can reason about it later without looking at pixels.
|
|
6392
|
+
description: "Put an icon from the catalog on the canvas, at an id from chalk_search_icons. **Prefer `attachTo`** \u2014 the element the icon belongs to \u2014 over x/y: an attached icon travels with that element through every layout, while one at bare coordinates is near its box by coincidence and gets stranded the first time chalk_arrange runs. `placement` picks the side (above by default, or below/left/right/badge/inside). Give it a `label` unless it is attached to something already named \u2014 an unlabelled loose icon says a thing was here and not which thing. The icon id is recorded on the element, so chalk_describe_scene reports it as [icon:aws/lambda] rather than [image] and you can reason about it later without looking at pixels.",
|
|
5239
6393
|
annotations: {
|
|
5240
6394
|
title: "Place an icon",
|
|
5241
6395
|
readOnlyHint: false,
|
|
@@ -5353,7 +6507,7 @@ var sceneSettingsTool = {
|
|
|
5353
6507
|
// src/interfaces/mcp/tools/session.ts
|
|
5354
6508
|
var screenshotTool = {
|
|
5355
6509
|
name: "chalk_screenshot",
|
|
5356
|
-
description: "Look at the canvas as an image. Use it
|
|
6510
|
+
description: "Look at the canvas as an image. Use it for questions only a picture answers \u2014 does the diagram read, does it look deliberate, is the visual weight right. Do NOT use it to find collisions: chalk_describe_scene reports every overlap with ids and sizes, and every write tool warns when it buried something. Rendering happens in the user's browser, so the canvas has to be open; if no tab is connected the error says so. Prefer chalk_describe_scene for anything answerable from structure \u2014 it is far smaller and it gives you ids you can act on.",
|
|
5357
6511
|
annotations: {
|
|
5358
6512
|
title: "Screenshot the canvas",
|
|
5359
6513
|
readOnlyHint: true,
|
|
@@ -5509,7 +6663,7 @@ var createElementsTool = proxyTool({
|
|
|
5509
6663
|
var connectTool = proxyTool({
|
|
5510
6664
|
name: "chalk_connect",
|
|
5511
6665
|
title: "Connect two elements",
|
|
5512
|
-
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.",
|
|
6666
|
+
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.",
|
|
5513
6667
|
input: ConnectToolInput,
|
|
5514
6668
|
output: ConnectOutput,
|
|
5515
6669
|
call: (bridge) => bridge.connect
|
|
@@ -5535,7 +6689,7 @@ var deleteElementsTool = proxyTool({
|
|
|
5535
6689
|
var arrangeTool = proxyTool({
|
|
5536
6690
|
name: "chalk_arrange",
|
|
5537
6691
|
title: "Arrange elements",
|
|
5538
|
-
description: "Lay elements out. `layout-layered`
|
|
6692
|
+
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.",
|
|
5539
6693
|
input: ArrangeToolInput,
|
|
5540
6694
|
output: ArrangeOutput,
|
|
5541
6695
|
annotations: { idempotentHint: true },
|
|
@@ -5853,6 +7007,9 @@ var STATUS = {
|
|
|
5853
7007
|
// Phase 5.
|
|
5854
7008
|
CATALOG_UNAVAILABLE: 500,
|
|
5855
7009
|
UNKNOWN_ICON: 404,
|
|
7010
|
+
// 400: the icon exists and the request is malformed — it says nothing about where to
|
|
7011
|
+
// put the thing.
|
|
7012
|
+
ICON_NEEDS_A_PLACE: 400,
|
|
5856
7013
|
UNKNOWN_PACK: 404,
|
|
5857
7014
|
// 502, not 500: the failure is somebody else registry or somebody else archive, and
|
|
5858
7015
|
// the distinction is what tells a user to check their network rather than file a bug.
|