@peter.prib/renderengine 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (451) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +203 -2
  3. package/REFERENCE.md +2198 -0
  4. package/dist/renderengine-workspace.umd.js +46905 -0
  5. package/dist/renderengine.umd.js +42223 -0
  6. package/examples/archimate.html +66 -0
  7. package/examples/diagram-editor.html +51 -0
  8. package/package.json +65 -4
  9. package/src/BaseRenderer.js +57 -0
  10. package/src/CanvasRenderer.js +194 -0
  11. package/src/Mat4.js +111 -0
  12. package/src/OrbitCamera.js +57 -0
  13. package/src/RendererFactory.js +50 -0
  14. package/src/SVGRenderer.js +439 -0
  15. package/src/archimate.js +723 -0
  16. package/src/archimateArchiFormat.js +1693 -0
  17. package/src/archimateArchiTypes.js +72 -0
  18. package/src/archimateArchive.js +140 -0
  19. package/src/archimateCSV.js +127 -0
  20. package/src/archimateDrawio.js +182 -0
  21. package/src/archimateExchangeFormat.js +546 -0
  22. package/src/archimateRelationshipTable.js +93 -0
  23. package/src/archimateSpecializations.js +161 -0
  24. package/src/chartCatalog.js +589 -0
  25. package/src/charting/ArchimateDiagramEditor.js +1624 -0
  26. package/src/charting/ArchimateSpecializationSelector.js +134 -0
  27. package/src/charting/Axis.js +68 -0
  28. package/src/charting/CameraSeeder.js +30 -0
  29. package/src/charting/CatalogDialog.js +92 -0
  30. package/src/charting/Chart.js +37 -0
  31. package/src/charting/Chart3D.js +99 -0
  32. package/src/charting/ChartBox.js +80 -0
  33. package/src/charting/ChartData.js +27 -0
  34. package/src/charting/ChartRegistry.js +76 -0
  35. package/src/charting/CheckboxTreeSelector.js +304 -0
  36. package/src/charting/ColorField.js +107 -0
  37. package/src/charting/ColorPalette.js +88 -0
  38. package/src/charting/CommandHistory.js +176 -0
  39. package/src/charting/DataFilter.js +52 -0
  40. package/src/charting/Diagram.js +36 -0
  41. package/src/charting/Diagram3D.js +29 -0
  42. package/src/charting/DiagramClipboard.js +31 -0
  43. package/src/charting/DiagramEditor.js +3330 -0
  44. package/src/charting/DiagramEditorCommand.js +110 -0
  45. package/src/charting/DiagramEditorCommands.js +237 -0
  46. package/src/charting/DiagramEditorEdgeCommands.js +142 -0
  47. package/src/charting/DiagramEditorState.js +98 -0
  48. package/src/charting/DiagramRegistry.js +86 -0
  49. package/src/charting/DiagramTypeRegistry.js +326 -0
  50. package/src/charting/DiagramViewPropertiesDialog.js +46 -0
  51. package/src/charting/Edge.js +113 -0
  52. package/src/charting/ErrorBars.js +37 -0
  53. package/src/charting/FilterSystem.js +285 -0
  54. package/src/charting/FindReplaceDialog.js +139 -0
  55. package/src/charting/FormDialog.js +385 -0
  56. package/src/charting/Gauge.js +58 -0
  57. package/src/charting/Hierarchy.js +175 -0
  58. package/src/charting/HintPanel.js +86 -0
  59. package/src/charting/ImageSelector.js +148 -0
  60. package/src/charting/InteractionManager.js +725 -0
  61. package/src/charting/Legend.js +58 -0
  62. package/src/charting/MenuBar.js +62 -0
  63. package/src/charting/ModelFileFormat.js +115 -0
  64. package/src/charting/OrbitControls.js +77 -0
  65. package/src/charting/Palette.js +119 -0
  66. package/src/charting/PopupMenu.js +117 -0
  67. package/src/charting/PropertiesPanel.js +155 -0
  68. package/src/charting/PropertyListEditor.js +96 -0
  69. package/src/charting/PropertyLog.js +92 -0
  70. package/src/charting/SettingsCascade.js +93 -0
  71. package/src/charting/SettingsDialog.js +53 -0
  72. package/src/charting/SettingsManager.js +179 -0
  73. package/src/charting/SettingsViewPropertiesDialog.js +58 -0
  74. package/src/charting/ShapePaths.js +27 -0
  75. package/src/charting/Statistics.js +134 -0
  76. package/src/charting/TemplateCatalog.js +59 -0
  77. package/src/charting/TemplateLibrary.js +103 -0
  78. package/src/charting/TextStyle.js +168 -0
  79. package/src/charting/ViewFit.js +55 -0
  80. package/src/charting/ViewMenu.js +70 -0
  81. package/src/charting/ViewPropertiesDialog.js +77 -0
  82. package/src/charting/ViewTabStrip.js +81 -0
  83. package/src/charting/archimate/ArchiFileFormat.js +32 -0
  84. package/src/charting/archimate/ArchimateCanvasEditor.js +815 -0
  85. package/src/charting/archimate/ArchimateCsvFileFormat.js +35 -0
  86. package/src/charting/archimate/ArchimateEdge.js +137 -0
  87. package/src/charting/archimate/ArchimateElementTemplate.js +195 -0
  88. package/src/charting/archimate/ArchimatePalette.js +102 -0
  89. package/src/charting/archimate/CsvImportDialog.js +105 -0
  90. package/src/charting/archimate/CustomViewpoints.js +74 -0
  91. package/src/charting/archimate/DerivedEdge.js +29 -0
  92. package/src/charting/archimate/DrawioFileFormat.js +24 -0
  93. package/src/charting/archimate/ElementFilterRule.js +21 -0
  94. package/src/charting/archimate/ExchangeFileFormat.js +28 -0
  95. package/src/charting/archimate/ViewGroupTemplate.js +137 -0
  96. package/src/charting/archimate/ViewLineEdge.js +84 -0
  97. package/src/charting/archimate/ViewpointRule.js +45 -0
  98. package/src/charting/archimate/archimateFileFormats.js +12 -0
  99. package/src/charting/archimate/archimateNames.js +5 -0
  100. package/src/charting/archimate/csvMapping.js +122 -0
  101. package/src/charting/archimate/elementMenuItems.js +32 -0
  102. package/src/charting/archimate/elementProperties.js +146 -0
  103. package/src/charting/archimate/elementTypeChoices.js +7 -0
  104. package/src/charting/archimate/elementTypeGroups.js +19 -0
  105. package/src/charting/archimate/index.js +17 -0
  106. package/src/charting/archimate/specializationCatalogDialog.js +64 -0
  107. package/src/charting/archimate/typeGlyph.js +29 -0
  108. package/src/charting/arrangement/Arrangement.js +112 -0
  109. package/src/charting/arrangement/ArrangementCommand.js +29 -0
  110. package/src/charting/arrangement/BackgroundCommand.js +41 -0
  111. package/src/charting/arrangement/BackgroundGroup.js +18 -0
  112. package/src/charting/arrangement/GroupCommand.js +46 -0
  113. package/src/charting/arrangement/ItemGroup.js +62 -0
  114. package/src/charting/arrangement/LockCommand.js +32 -0
  115. package/src/charting/arrangement/UngroupCommand.js +27 -0
  116. package/src/charting/arrangement/alignment.js +88 -0
  117. package/src/charting/arrangement/arrangeMenuItems.js +25 -0
  118. package/src/charting/arrangement/index.js +11 -0
  119. package/src/charting/arrangement/layouts.js +233 -0
  120. package/src/charting/canvas/ArchiHint.js +78 -0
  121. package/src/charting/canvas/CanvasBoxKind.js +39 -0
  122. package/src/charting/canvas/CanvasPalette.js +49 -0
  123. package/src/charting/canvas/CanvasTemplateLibrary.js +61 -0
  124. package/src/charting/canvas/CanvasTemplateMenu.js +151 -0
  125. package/src/charting/canvas/EditHintCommand.js +28 -0
  126. package/src/charting/canvas/canvasBoxKinds.js +27 -0
  127. package/src/charting/canvas/canvasBoxMenuItems.js +40 -0
  128. package/src/charting/canvas/defaultCanvasTemplates.js +186 -0
  129. package/src/charting/canvas/diagramBoxKinds.js +12 -0
  130. package/src/charting/canvas/hintMenuItems.js +49 -0
  131. package/src/charting/charts/ArcGauge.js +55 -0
  132. package/src/charting/charts/AreaChart.js +71 -0
  133. package/src/charting/charts/BarChart.js +80 -0
  134. package/src/charting/charts/BarChart3D.js +56 -0
  135. package/src/charting/charts/BarChart3DOrbit.js +53 -0
  136. package/src/charting/charts/BarChartBase.js +70 -0
  137. package/src/charting/charts/BarGauge.js +42 -0
  138. package/src/charting/charts/BoxPlotChart.js +41 -0
  139. package/src/charting/charts/BubbleChart.js +99 -0
  140. package/src/charting/charts/BubbleChart3D.js +78 -0
  141. package/src/charting/charts/BulletGauge.js +87 -0
  142. package/src/charting/charts/CalendarHeatmapChart.js +204 -0
  143. package/src/charting/charts/CandlestickChart.js +54 -0
  144. package/src/charting/charts/ChordDiagram.js +130 -0
  145. package/src/charting/charts/DensityChart.js +73 -0
  146. package/src/charting/charts/DialGauge.js +65 -0
  147. package/src/charting/charts/DonutChart.js +15 -0
  148. package/src/charting/charts/FunnelChart.js +47 -0
  149. package/src/charting/charts/GanttChart.js +201 -0
  150. package/src/charting/charts/GaugeChart.js +175 -0
  151. package/src/charting/charts/GeoMapChart.js +146 -0
  152. package/src/charting/charts/GroupedBarChart.js +88 -0
  153. package/src/charting/charts/GroupedDistributionChart.js +70 -0
  154. package/src/charting/charts/HeatmapChart.js +80 -0
  155. package/src/charting/charts/HierarchyChart.js +39 -0
  156. package/src/charting/charts/HistogramChart.js +45 -0
  157. package/src/charting/charts/KpiTile.js +72 -0
  158. package/src/charting/charts/LagPlotChart.js +41 -0
  159. package/src/charting/charts/LineChart.js +124 -0
  160. package/src/charting/charts/MultiRingGauge.js +54 -0
  161. package/src/charting/charts/OutlineChart.js +95 -0
  162. package/src/charting/charts/ParallelCoordinatesChart.js +130 -0
  163. package/src/charting/charts/PieChart.js +56 -0
  164. package/src/charting/charts/PipeChart.js +88 -0
  165. package/src/charting/charts/PopulationPyramidChart.js +106 -0
  166. package/src/charting/charts/QQPlotChart.js +64 -0
  167. package/src/charting/charts/RadarChart.js +76 -0
  168. package/src/charting/charts/RadialBarChart.js +96 -0
  169. package/src/charting/charts/RingGauge.js +23 -0
  170. package/src/charting/charts/SankeyDiagram.js +174 -0
  171. package/src/charting/charts/ScatterChart.js +104 -0
  172. package/src/charting/charts/StackedAreaChart.js +15 -0
  173. package/src/charting/charts/StackedBarChart.js +33 -0
  174. package/src/charting/charts/StackedBarChart3DOrbit.js +58 -0
  175. package/src/charting/charts/StreamGraphChart.js +131 -0
  176. package/src/charting/charts/SunburstChart.js +57 -0
  177. package/src/charting/charts/ThermometerGauge.js +43 -0
  178. package/src/charting/charts/TreemapChart.js +54 -0
  179. package/src/charting/charts/ViolinChart.js +37 -0
  180. package/src/charting/charts/WaterfallChart.js +66 -0
  181. package/src/charting/charts/WordCloudChart.js +104 -0
  182. package/src/charting/diagramModel/AddViewCommand.js +33 -0
  183. package/src/charting/diagramModel/DiagramModel.js +39 -0
  184. package/src/charting/diagramModel/DiagramModelSearch.js +22 -0
  185. package/src/charting/diagramModel/DiagramView.js +50 -0
  186. package/src/charting/diagramModel/ModelHistory.js +43 -0
  187. package/src/charting/diagramModel/ModelReport.js +153 -0
  188. package/src/charting/diagramModel/ModelSearch.js +223 -0
  189. package/src/charting/diagramModel/RemoveViewCommand.js +25 -0
  190. package/src/charting/diagramModel/RenameViewCommand.js +21 -0
  191. package/src/charting/diagramModel/ReplaceTextCommand.js +25 -0
  192. package/src/charting/diagramModel/ViewSetChecker.js +34 -0
  193. package/src/charting/diagramModel/ViewSetModel.js +243 -0
  194. package/src/charting/diagramModel/index.js +6 -0
  195. package/src/charting/diagramModel/nameTransforms.js +29 -0
  196. package/src/charting/diagrams/ArchimateCommands.js +141 -0
  197. package/src/charting/diagrams/ArchimateDiagram.js +263 -0
  198. package/src/charting/diagrams/ArchimateModel.js +1155 -0
  199. package/src/charting/diagrams/ArchimateModelChecker.js +125 -0
  200. package/src/charting/diagrams/ArchimateModelReport.js +128 -0
  201. package/src/charting/diagrams/ArchimateModelSearch.js +41 -0
  202. package/src/charting/diagrams/ArchimateValidator.js +357 -0
  203. package/src/charting/diagrams/CanvasView.js +112 -0
  204. package/src/charting/diagrams/ContextMenu.js +37 -0
  205. package/src/charting/diagrams/DataModelDiagram.js +86 -0
  206. package/src/charting/diagrams/DataModelRelationshipStrategy.js +38 -0
  207. package/src/charting/diagrams/DerivedRelationships.js +102 -0
  208. package/src/charting/diagrams/EbnfDiagram.js +39 -0
  209. package/src/charting/diagrams/EdgeGeometry.js +553 -0
  210. package/src/charting/diagrams/EditViewItemCommand.js +128 -0
  211. package/src/charting/diagrams/ElementInstance.js +157 -0
  212. package/src/charting/diagrams/GraphDiagram.js +1340 -0
  213. package/src/charting/diagrams/GraphDiagram3D.js +152 -0
  214. package/src/charting/diagrams/GraphMetrics.js +81 -0
  215. package/src/charting/diagrams/GridOverlay.js +137 -0
  216. package/src/charting/diagrams/ImportRowsCommand.js +62 -0
  217. package/src/charting/diagrams/LabelHelper.js +108 -0
  218. package/src/charting/diagrams/MergeElementsCommand.js +101 -0
  219. package/src/charting/diagrams/NestInstanceCommand.js +34 -0
  220. package/src/charting/diagrams/NodeGeometry.js +158 -0
  221. package/src/charting/diagrams/PlaceItemsCommand.js +77 -0
  222. package/src/charting/diagrams/RadialLayout.js +101 -0
  223. package/src/charting/diagrams/RelationshipInstance.js +93 -0
  224. package/src/charting/diagrams/RemoveGroupFromViewCommand.js +28 -0
  225. package/src/charting/diagrams/RemoveLineCommand.js +27 -0
  226. package/src/charting/diagrams/ShowElementCommand.js +43 -0
  227. package/src/charting/diagrams/SvgCoordinate.js +17 -0
  228. package/src/charting/diagrams/SvgZoom.js +127 -0
  229. package/src/charting/diagrams/Tooltip.js +62 -0
  230. package/src/charting/diagrams/ViewConnectionInstance.js +102 -0
  231. package/src/charting/diagrams/ViewObjectInstance.js +56 -0
  232. package/src/charting/diagrams/commands/AddEdgeCommand.js +34 -0
  233. package/src/charting/diagrams/commands/AddNodeCommand.js +28 -0
  234. package/src/charting/diagrams/commands/AddWaypointCommand.js +27 -0
  235. package/src/charting/diagrams/commands/CreateFolderCommand.js +27 -0
  236. package/src/charting/diagrams/commands/DeleteEdgeCommand.js +30 -0
  237. package/src/charting/diagrams/commands/DeleteFolderCommand.js +47 -0
  238. package/src/charting/diagrams/commands/DeleteNodeCommand.js +35 -0
  239. package/src/charting/diagrams/commands/DeleteWaypointCommand.js +24 -0
  240. package/src/charting/diagrams/commands/GraphCommand.js +65 -0
  241. package/src/charting/diagrams/commands/InvertEdgeCommand.js +24 -0
  242. package/src/charting/diagrams/commands/MoveNodeCommand.js +44 -0
  243. package/src/charting/diagrams/commands/MoveWaypointCommand.js +27 -0
  244. package/src/charting/diagrams/commands/RenameEdgeCommand.js +23 -0
  245. package/src/charting/diagrams/commands/RenameFolderCommand.js +31 -0
  246. package/src/charting/diagrams/commands/RenameNodeCommand.js +27 -0
  247. package/src/charting/diagrams/commands/ResizeNodeCommand.js +46 -0
  248. package/src/charting/diagrams/commands/UpdateEdgeSideCommand.js +40 -0
  249. package/src/charting/diagrams/commands/index.js +17 -0
  250. package/src/charting/diagrams/duplicates.js +49 -0
  251. package/src/charting/diagrams/nesting.js +30 -0
  252. package/src/charting/diagrams/roadmap.js +77 -0
  253. package/src/charting/edgeMarkers/ArrowMarker.js +15 -0
  254. package/src/charting/edgeMarkers/CircleMarker.js +13 -0
  255. package/src/charting/edgeMarkers/CrowsFootMarker.js +16 -0
  256. package/src/charting/edgeMarkers/DiamondMarker.js +15 -0
  257. package/src/charting/edgeMarkers/EdgeMarker.js +34 -0
  258. package/src/charting/edgeMarkers/HalfArrowMarker.js +8 -0
  259. package/src/charting/edgeMarkers/HollowDiamondMarker.js +18 -0
  260. package/src/charting/edgeMarkers/HollowTriangleMarker.js +18 -0
  261. package/src/charting/edgeMarkers/OpenArrowMarker.js +17 -0
  262. package/src/charting/edgeMarkers/index.js +27 -0
  263. package/src/charting/edgeStyle/EdgeStyleCascade.js +22 -0
  264. package/src/charting/edgeStyle/EdgeStyleDialog.js +51 -0
  265. package/src/charting/edgeStyle/EdgeStyleSchema.js +43 -0
  266. package/src/charting/edgeStyle/SetStyleCommand.js +12 -0
  267. package/src/charting/edgeStyle/index.js +4 -0
  268. package/src/charting/emphasis/EmphasisRule.js +42 -0
  269. package/src/charting/emphasis/HighlightRule.js +24 -0
  270. package/src/charting/gauges/ArcTrack.js +51 -0
  271. package/src/charting/gauges/GaugeAnimation.js +51 -0
  272. package/src/charting/gauges/GaugeBands.js +48 -0
  273. package/src/charting/gauges/GaugeScale.js +87 -0
  274. package/src/charting/gauges/GaugeTrack.js +44 -0
  275. package/src/charting/gauges/GaugeValue.js +61 -0
  276. package/src/charting/gauges/HorizontalTrack.js +14 -0
  277. package/src/charting/gauges/LinearTrack.js +29 -0
  278. package/src/charting/gauges/Sparkline.js +42 -0
  279. package/src/charting/gauges/VerticalTrack.js +17 -0
  280. package/src/charting/glyphs/ArrowGlyph.js +21 -0
  281. package/src/charting/glyphs/BatteryGlyph.js +18 -0
  282. package/src/charting/glyphs/ChevronGlyph.js +18 -0
  283. package/src/charting/glyphs/CircleGlyph.js +15 -0
  284. package/src/charting/glyphs/CloudGlyph.js +19 -0
  285. package/src/charting/glyphs/ComponentGlyph.js +18 -0
  286. package/src/charting/glyphs/ConstraintGlyph.js +24 -0
  287. package/src/charting/glyphs/ContractBoxGlyph.js +10 -0
  288. package/src/charting/glyphs/CubeGlyph.js +19 -0
  289. package/src/charting/glyphs/CylinderGlyph.js +19 -0
  290. package/src/charting/glyphs/DashedDoubleArrowGlyph.js +11 -0
  291. package/src/charting/glyphs/DeviceGlyph.js +17 -0
  292. package/src/charting/glyphs/DiamondGlyph.js +17 -0
  293. package/src/charting/glyphs/DocumentGlyph.js +17 -0
  294. package/src/charting/glyphs/DoubleArrowGlyph.js +23 -0
  295. package/src/charting/glyphs/DoubleCircleGlyph.js +24 -0
  296. package/src/charting/glyphs/EllipseGlyph.js +18 -0
  297. package/src/charting/glyphs/FactoryGlyph.js +20 -0
  298. package/src/charting/glyphs/FlagGlyph.js +16 -0
  299. package/src/charting/glyphs/GapCircleGlyph.js +17 -0
  300. package/src/charting/glyphs/GearGlyph.js +37 -0
  301. package/src/charting/glyphs/Glyph.js +98 -0
  302. package/src/charting/glyphs/GroupingBoxGlyph.js +13 -0
  303. package/src/charting/glyphs/HalfCircleGlyph.js +22 -0
  304. package/src/charting/glyphs/HexagonGlyph.js +28 -0
  305. package/src/charting/glyphs/IconGlyph.js +22 -0
  306. package/src/charting/glyphs/ImageGlyph.js +16 -0
  307. package/src/charting/glyphs/JunctionGlyph.js +17 -0
  308. package/src/charting/glyphs/LollipopGlyph.js +22 -0
  309. package/src/charting/glyphs/MagnifierGlyph.js +17 -0
  310. package/src/charting/glyphs/NoteGlyph.js +33 -0
  311. package/src/charting/glyphs/ObjectBoxGlyph.js +21 -0
  312. package/src/charting/glyphs/OrJunctionGlyph.js +17 -0
  313. package/src/charting/glyphs/ParallelogramGlyph.js +20 -0
  314. package/src/charting/glyphs/PersonGlyph.js +23 -0
  315. package/src/charting/glyphs/PillGlyph.js +23 -0
  316. package/src/charting/glyphs/PinGlyph.js +18 -0
  317. package/src/charting/glyphs/PrincipleGlyph.js +23 -0
  318. package/src/charting/glyphs/ProductBoxGlyph.js +16 -0
  319. package/src/charting/glyphs/RectGlyph.js +15 -0
  320. package/src/charting/glyphs/RoundedBoxGlyph.js +13 -0
  321. package/src/charting/glyphs/StackGlyph.js +14 -0
  322. package/src/charting/glyphs/StepsGlyph.js +20 -0
  323. package/src/charting/glyphs/StickFigureGlyph.js +32 -0
  324. package/src/charting/glyphs/SystemSoftwareBoxGlyph.js +16 -0
  325. package/src/charting/glyphs/TabbedBoxGlyph.js +37 -0
  326. package/src/charting/glyphs/TargetGlyph.js +23 -0
  327. package/src/charting/glyphs/WavyBoxGlyph.js +17 -0
  328. package/src/charting/glyphs/WheelGlyph.js +28 -0
  329. package/src/charting/glyphs/drawGlyph.js +28 -0
  330. package/src/charting/glyphs/glyphSvg.js +55 -0
  331. package/src/charting/glyphs/index.js +109 -0
  332. package/src/charting/modelTree/AnalysisPanel.js +159 -0
  333. package/src/charting/modelTree/DiagramTabs.js +217 -0
  334. package/src/charting/modelTree/ModelTree.js +164 -0
  335. package/src/charting/modelTree/ModelTreePanel.js +914 -0
  336. package/src/charting/modelTree/TreeBuilder.js +238 -0
  337. package/src/charting/modelTree/TreeModeRegistry.js +51 -0
  338. package/src/charting/modelTree/TreeViewMode.js +110 -0
  339. package/src/charting/modelTree/buildFilterTrees.js +138 -0
  340. package/src/charting/modelTree/modes/FlatTreeMode.js +46 -0
  341. package/src/charting/modelTree/modes/LayerGroupedTreeMode.js +62 -0
  342. package/src/charting/modelTree/modes/TypeGroupedTreeMode.js +60 -0
  343. package/src/charting/modelTree/treeLayers.js +11 -0
  344. package/src/charting/report/reportHtml.js +82 -0
  345. package/src/charting/report/reportViewer.js +118 -0
  346. package/src/charting/shapes/CircleShape.js +19 -0
  347. package/src/charting/shapes/PathShape.js +17 -0
  348. package/src/charting/shapes/RectShape.js +20 -0
  349. package/src/charting/shapes/Shape.js +20 -0
  350. package/src/charting/shapes/ShapeFactory.js +23 -0
  351. package/src/charting/storage/LocalJsonStore.js +43 -0
  352. package/src/charting/templates/ActorTemplate.js +36 -0
  353. package/src/charting/templates/BoxTemplate.js +27 -0
  354. package/src/charting/templates/CircleTemplate.js +18 -0
  355. package/src/charting/templates/ClassTemplate.js +20 -0
  356. package/src/charting/templates/CloudTemplate.js +43 -0
  357. package/src/charting/templates/ComponentTemplate.js +19 -0
  358. package/src/charting/templates/CylinderTemplate.js +30 -0
  359. package/src/charting/templates/DataStoreTemplate.js +16 -0
  360. package/src/charting/templates/DiamondTemplate.js +10 -0
  361. package/src/charting/templates/DocumentTemplate.js +32 -0
  362. package/src/charting/templates/EllipseTemplate.js +15 -0
  363. package/src/charting/templates/FinalStateTemplate.js +12 -0
  364. package/src/charting/templates/GlyphTemplate.js +47 -0
  365. package/src/charting/templates/HexagonTemplate.js +17 -0
  366. package/src/charting/templates/LifelineTemplate.js +20 -0
  367. package/src/charting/templates/ManualInputTemplate.js +12 -0
  368. package/src/charting/templates/NoteTemplate.js +23 -0
  369. package/src/charting/templates/OctagonTemplate.js +7 -0
  370. package/src/charting/templates/PackageTemplate.js +25 -0
  371. package/src/charting/templates/ParallelogramTemplate.js +11 -0
  372. package/src/charting/templates/PentagonTemplate.js +5 -0
  373. package/src/charting/templates/PolygonTemplate.js +18 -0
  374. package/src/charting/templates/PredefinedProcessTemplate.js +13 -0
  375. package/src/charting/templates/RectangleTemplate.js +28 -0
  376. package/src/charting/templates/RegularPolygonTemplate.js +21 -0
  377. package/src/charting/templates/RoundedRectangleTemplate.js +5 -0
  378. package/src/charting/templates/StadiumTemplate.js +8 -0
  379. package/src/charting/templates/StarTemplate.js +17 -0
  380. package/src/charting/templates/SwimlaneTemplate.js +18 -0
  381. package/src/charting/templates/Template.js +208 -0
  382. package/src/charting/templates/TrapezoidTemplate.js +11 -0
  383. package/src/charting/templates/TriangleTemplate.js +12 -0
  384. package/src/charting/templates/index.js +31 -0
  385. package/src/charting/templates/outline.js +59 -0
  386. package/src/charting/templates/svg.js +19 -0
  387. package/src/charting/textToShapes.js +61 -0
  388. package/src/charting/toolbar/ToolbarButton.js +38 -0
  389. package/src/charting/toolbar/ToolbarCheckbox.js +31 -0
  390. package/src/charting/toolbar/ToolbarItem.js +19 -0
  391. package/src/charting/toolbar/ToolbarMenu.js +36 -0
  392. package/src/charting/toolbar/ToolbarSelect.js +38 -0
  393. package/src/charting/toolbar/ToolbarSeparator.js +7 -0
  394. package/src/charting/toolbar/ToolbarText.js +22 -0
  395. package/src/charting/toolbar/index.js +8 -0
  396. package/src/charting/toolbar/pickFile.js +14 -0
  397. package/src/charting/viewDecorations.js +85 -0
  398. package/src/charting/zOrder.js +57 -0
  399. package/src/csvTable.js +61 -0
  400. package/src/datamodel.js +122 -0
  401. package/src/datawindow/BoundedWindow.js +36 -0
  402. package/src/datawindow/FlatDataWindow.js +34 -0
  403. package/src/datawindow/GraphDataWindow.js +40 -0
  404. package/src/datawindow/GroupedSampleDataWindow.js +16 -0
  405. package/src/datawindow/SeriesDataWindow.js +42 -0
  406. package/src/datawindow/index.js +31 -0
  407. package/src/diagramCatalog.js +30 -0
  408. package/src/ebnfDiagram.js +57 -0
  409. package/src/ebnfParser.js +129 -0
  410. package/src/exportImage.js +233 -0
  411. package/src/index.js +2636 -0
  412. package/src/itIcons.js +84 -0
  413. package/src/kernelExpr.js +243 -0
  414. package/src/propertiesDialog.js +463 -0
  415. package/src/railroadDiagram.js +571 -0
  416. package/src/treeModeCatalog.js +16 -0
  417. package/src/workspace/ContextBus.js +79 -0
  418. package/src/workspace/EditorShell.js +67 -0
  419. package/src/workspace/Part.js +163 -0
  420. package/src/workspace/PartFactory.js +49 -0
  421. package/src/workspace/PartRegistry.js +86 -0
  422. package/src/workspace/Workspace.js +321 -0
  423. package/src/workspace/archimateServices.js +33 -0
  424. package/src/workspace/dom.js +120 -0
  425. package/src/workspace/index.js +75 -0
  426. package/src/workspace/layout/GridNode.js +280 -0
  427. package/src/workspace/layout/LayoutNode.js +89 -0
  428. package/src/workspace/layout/PartNode.js +77 -0
  429. package/src/workspace/layout/SplitNode.js +114 -0
  430. package/src/workspace/layout/TabsNode.js +191 -0
  431. package/src/workspace/layout/index.js +24 -0
  432. package/src/workspace/parts/AdapterPart.js +33 -0
  433. package/src/workspace/parts/ArchimateCanvasPart.js +345 -0
  434. package/src/workspace/parts/ArchimateEditorPart.js +79 -0
  435. package/src/workspace/parts/ChartPart.js +115 -0
  436. package/src/workspace/parts/ContentPart.js +34 -0
  437. package/src/workspace/parts/DetailPart.js +46 -0
  438. package/src/workspace/parts/DiagramEditorPart.js +135 -0
  439. package/src/workspace/parts/EditorPart.js +388 -0
  440. package/src/workspace/parts/HtmlPart.js +25 -0
  441. package/src/workspace/parts/ListPart.js +57 -0
  442. package/src/workspace/parts/ModelFile.js +187 -0
  443. package/src/workspace/parts/ModelsPart.js +135 -0
  444. package/src/workspace/styles.js +258 -0
  445. package/src/workspace/themes.js +96 -0
  446. package/src/workspace/workArea.js +413 -0
  447. package/src/workspace/workAreaModel.js +197 -0
  448. package/src/workspace.js +16 -0
  449. package/src/zip/ZipArchive.js +160 -0
  450. package/src/zip/crc32.js +25 -0
  451. package/src/zip/inflate.js +159 -0
@@ -0,0 +1,1624 @@
1
+ import { DiagramEditor } from './DiagramEditor.js';
2
+ import { FilterSystem } from './FilterSystem.js';
3
+ import { ElementInstance } from './diagrams/ElementInstance.js';
4
+ import { ViewObjectInstance } from './diagrams/ViewObjectInstance.js';
5
+ import { GraphDiagram } from './diagrams/GraphDiagram.js';
6
+ import { NODE_SIZE_KEYS } from './diagrams/commands/index.js';
7
+ import { RelationshipInstance } from './diagrams/RelationshipInstance.js';
8
+ import { EditViewItemCommand, viewItemChanges, hasViewItemChanges } from './diagrams/EditViewItemCommand.js';
9
+ import { arrangeMenuItems } from './arrangement/index.js';
10
+ import { canvasBoxMenuItems } from './canvas/canvasBoxMenuItems.js';
11
+ import { hintMenuItems } from './canvas/hintMenuItems.js';
12
+ import { HintPanel } from './HintPanel.js';
13
+ import { MenuBar } from './MenuBar.js';
14
+ import { MODEL_ELEMENT_MIME } from './modelTree/ModelTreePanel.js';
15
+ import { ViewMenu } from './ViewMenu.js';
16
+ import { CanvasTemplateLibrary } from './canvas/CanvasTemplateLibrary.js';
17
+ import { CanvasTemplateMenu } from './canvas/CanvasTemplateMenu.js';
18
+ import { SettingsViewPropertiesDialog } from './SettingsViewPropertiesDialog.js';
19
+ import { EditorShell } from '../workspace/EditorShell.js';
20
+ import { SPECIALIZATIONS_STORE } from '../archimateSpecializations.js';
21
+ import { FormDialog } from './FormDialog.js';
22
+ import { ToolbarMenu, ToolbarSeparator, ToolbarText } from './toolbar/index.js';
23
+ import { ArchimateViewpoints } from '../archimate.js';
24
+ import { modelFileMenuItems } from './ModelFileFormat.js';
25
+ import { elementMenuItems } from './archimate/elementMenuItems.js';
26
+ import { editElementProperties } from './archimate/elementProperties.js';
27
+ import { ShowElementCommand } from './diagrams/ShowElementCommand.js';
28
+ import { specializationCatalogDialog } from './archimate/specializationCatalogDialog.js';
29
+ import { CustomViewpoints } from './archimate/CustomViewpoints.js';
30
+ import { ARCHIMATE_FILE_FORMATS } from './archimate/archimateFileFormats.js';
31
+ import { ArchiFileFormat } from './archimate/ArchiFileFormat.js';
32
+ import { ExchangeFileFormat } from './archimate/ExchangeFileFormat.js';
33
+ import { ArchimateCsvFileFormat } from './archimate/ArchimateCsvFileFormat.js';
34
+
35
+ /**
36
+ * ArchimateDiagramEditor — ArchiMate-specific diagram editor extending DiagramEditor base.
37
+ *
38
+ * Encapsulates: ArchiMate model, tree navigator, diagram tabs, rendering, controls wiring.
39
+ * Inherits: common rendering, drag/drop, toolbar, tree, filtering from DiagramEditor.
40
+ * Delegates: filtering to FilterSystem, tree rendering to ModelTree/TreeBuilder.
41
+ * Separates concerns: common logic (base class) from ArchiMate-specific logic (this class).
42
+ */
43
+ /** Specializations, kept per browser. */
44
+
45
+ /** dataTransfer type a tree element carries while dragged onto the diagram. */
46
+
47
+ export class ArchimateDiagramEditor extends DiagramEditor {
48
+ constructor(options = {}) {
49
+ const {
50
+ elements = [],
51
+ relationships = [],
52
+ views = [],
53
+ document,
54
+ appContainerId = 'app',
55
+ treeContainerId = 'tree',
56
+ diagramContainerId = 'diagram',
57
+ analysisContainerId,
58
+ toolbarContainerId = 'toolbar',
59
+ features = null,
60
+ workspace,
61
+ settingsManager = null,
62
+ fullPage = true,
63
+ model = null,
64
+ templateLibrary = undefined,
65
+ fileFormats = ARCHIMATE_FILE_FORMATS,
66
+ } = options;
67
+
68
+ super({
69
+ diagramContainerId,
70
+ // GraphDiagram (via RenderEngine.archimateDiagram) owns zoom, pan and canvas clicks.
71
+ enableCanvasInteractions: false,
72
+ });
73
+ this.document = document;
74
+ this.appContainerId = appContainerId;
75
+ this.treeContainerId = treeContainerId;
76
+ this.analysisContainerId = analysisContainerId;
77
+ this.toolbarContainerId = toolbarContainerId;
78
+ /** Fill the viewport (a page of its own) or the app container (e.g. inside a workbench part). */
79
+ this.fullPage = fullPage;
80
+ this.features = features;
81
+ this.workspace = workspace;
82
+ this.settingsManager = settingsManager || (workspace?.SettingsManager ? new workspace.SettingsManager() : null);
83
+ /**
84
+ * Canvas templates kept across models (a CanvasTemplateLibrary): by default
85
+ * this browser's; pass another, or null for none.
86
+ */
87
+ this.templateLibrary = templateLibrary === undefined ? new CanvasTemplateLibrary() : templateLibrary;
88
+ /** The file formats the File menu exports and imports (ModelFileFormat), each on when its features flags are. */
89
+ this.fileFormats = fileFormats;
90
+
91
+ // Initialize dependencies with defaults (can be overridden via setDependencies)
92
+ this.setDependencies();
93
+
94
+ // Inject styles (generated by class, not hardcoded in HTML)
95
+ this._injectStyles();
96
+
97
+ // Create DOM structure if not already present
98
+ this._createStructure();
99
+
100
+ // Initialize toolbar if features are specified
101
+ if (this.features) {
102
+ this._initializeToolbar();
103
+ }
104
+
105
+ // Core model state
106
+ this.model = null;
107
+ this.currentEngine = null;
108
+ // A host (e.g. ArchimateEditorPart) may hand in the model; otherwise it is built from the data.
109
+ this._bindModel(model ?? new workspace.ArchimateModel({ elements, relationships, views }));
110
+ this.renderFunction = null;
111
+
112
+ // Filtering system (owns all filter UI and logic)
113
+ this.filterSystem = new FilterSystem({
114
+ doc: document,
115
+ model: this.model,
116
+ onFilterChange: (result) => this._onFilterChange(result),
117
+ });
118
+
119
+ // Specialize registry (load/save with localStorage)
120
+ this._initializeSpecializations();
121
+
122
+ // Tab/diagram system
123
+ this._initializeDiagrams();
124
+
125
+ // Tree panel rendering
126
+ this._renderTreePanel();
127
+
128
+ // Tab initialization
129
+ this._initializeTabs();
130
+
131
+ // Set render function for tabs to call
132
+ if (this.diagrams) {
133
+ this.diagrams.setRenderFunction(() => this.render());
134
+ }
135
+
136
+ // The tree lists the library's templates.
137
+ this._unsubscribeLibrary = this.templateLibrary?.onChange(() => {
138
+ if (this.document?.getElementById(this.treeContainerId)) this._renderTreePanel();
139
+ });
140
+
141
+ // Enable tree drag-drop (delayed to allow DOM to be ready)
142
+ setTimeout(() => this.enableTreeDragDrop(), 100);
143
+
144
+ // Wire controls if not using auto-toolbar
145
+ if (!this.features) {
146
+ this.wireControls();
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Undo history of the view being shown: each ArchimateView keeps its own,
152
+ * so the base's undo/redo shortcuts and commands follow the current tab.
153
+ */
154
+ get history() {
155
+ return this.currentView?.history ?? null;
156
+ }
157
+
158
+ set history(_history) {
159
+ throw new TypeError('ArchimateDiagramEditor history is the model\'s (currentView.history)');
160
+ }
161
+
162
+ /** Groups and locks are per view too: the current view's arrangement. */
163
+ get arrangement() {
164
+ return this.currentView?.arrangement ?? this._arrangement;
165
+ }
166
+
167
+ set arrangement(arrangement) {
168
+ this._arrangement = arrangement;
169
+ }
170
+
171
+ /** Group / Ungroup / Lock / Unlock for a drawn item (arrangeMenuItems), on the current view. */
172
+ nodeArrangeMenuItems(itemId) {
173
+ const view = this.currentView;
174
+ if (!view?.getElementInstance(itemId)) return [];
175
+ const after = (fn) => (...args) => { fn(...args); this.render(); };
176
+ return arrangeMenuItems(view.arrangement, [itemId], {
177
+ ungroup: after((id) => view.ungroup(id)),
178
+ setLocked: after((ids, locked) => view.setLocked(ids, locked)),
179
+ });
180
+ }
181
+
182
+ /** The graph engine's drawing. */
183
+ drawingHost() {
184
+ return this.currentEngine?.container ?? null;
185
+ }
186
+
187
+ /** The graph engine draws without the base's grid and handles: nothing to hide. @protected */
188
+ _withPlainDrawing(fn) {
189
+ return fn();
190
+ }
191
+
192
+ /** Zoom and pan the graph engine's view so everything drawn is in sight. @returns {boolean} whether it zoomed */
193
+ zoomToFit({ margin = 20 } = {}) {
194
+ const engine = this.currentEngine;
195
+ const svg = engine?.renderer?.svg;
196
+ if (!engine?._svgZoom || !svg) return false;
197
+ const rects = [...svg.querySelectorAll('[nodeId], [edgeId]')].map(n => n.getBoundingClientRect()).filter(r => r.width || r.height);
198
+ if (!rects.length) {
199
+ engine._svgZoom.reset();
200
+ return false;
201
+ }
202
+ // Screen corners to drawing coordinates.
203
+ const topLeft = engine._clientToSvgPoint(Math.min(...rects.map(r => r.left)), Math.min(...rects.map(r => r.top)));
204
+ const bottomRight = engine._clientToSvgPoint(Math.max(...rects.map(r => r.right)), Math.max(...rects.map(r => r.bottom)));
205
+ engine._svgZoom.fit({ x: topLeft.x, y: topLeft.y, width: bottomRight.x - topLeft.x, height: bottomRight.y - topLeft.y }, { margin });
206
+ return true;
207
+ }
208
+
209
+ /** The notation version the checks use. */
210
+ checkerOptions() {
211
+ return { version: this.getOption('archimate-version', '4') };
212
+ }
213
+
214
+ /** A view opens in its tab; an element or relationship is shown in the tree. */
215
+ revealFinding(finding) {
216
+ const [id] = finding.ids;
217
+ if (finding.kind === 'view') {
218
+ const view = this.model.getView(id);
219
+ if (view) this._showView(view);
220
+ } else {
221
+ this.tree.select(id, finding.kind);
222
+ }
223
+ }
224
+
225
+ /** Back to the zoom the view was drawn at. */
226
+ resetZoom() {
227
+ this.currentEngine?._svgZoom?.reset();
228
+ }
229
+
230
+ /**
231
+ * What right-clicking empty space on a view offers: on a canvas, Archi's
232
+ * canvas palette (canvasBoxMenuItems) and Place template; on an ArchiMate
233
+ * view, a new element, note or image. Then the view's properties.
234
+ * @param {ArchimateView} view
235
+ */
236
+ canvasContextMenuItemsFor(view) {
237
+ const viewProperties = { label: 'View Properties…', action: () => this.deps.showViewPropertiesDialog(this.currentView, () => this.render()) };
238
+ if (view?.constructor.kind === 'canvas') {
239
+ return [
240
+ ...canvasBoxMenuItems(view, { x: 0, y: 0 }).map((item, i) => ({
241
+ label: item.label,
242
+ // At the point clicked: the items are made again there.
243
+ action: (point) => canvasBoxMenuItems(view, point, { onAdded: () => this.render(), document: this.document })[i].action(),
244
+ })),
245
+ { label: 'Place template', items: (point) => this.templateMenu.placeTemplateItems(view.id, point) },
246
+ ...hintMenuItems(view, null, { onChange: () => this.render(), document: this.document }),
247
+ viewProperties,
248
+ ];
249
+ }
250
+ return [
251
+ { label: 'Add ArchiMate Element…', action: (point, engine) => this.deps.showAddArchimateElementDialog(engine, point, this.currentView, this.model) },
252
+ { label: 'Add Note…', action: (point, engine) => this.deps.showAddNoteDialog(engine, point, this.currentView, this.model) },
253
+ { label: 'Add Image…', action: (point, engine) => this.deps.showAddImageDialog(engine, point) },
254
+ viewProperties,
255
+ ];
256
+ }
257
+
258
+ /**
259
+ * Show the hint for an item on a canvas (its own, else the canvas's) in the
260
+ * hint panel; none for other views. Called on every render and when a box
261
+ * is clicked.
262
+ * @param {string|null} [itemId]
263
+ */
264
+ showHintFor(itemId = null) {
265
+ const view = this.currentView;
266
+ const container = this.document?.getElementById(this.diagramContainerId);
267
+ if (!container) return;
268
+ this.hintPanel ??= new HintPanel({ document: this.document });
269
+ const hint = view?.constructor.kind === 'canvas' ? (itemId ? view.hintFor(itemId) : view.hint) : null;
270
+ this.hintPanel.mount(container).show(hint);
271
+ }
272
+
273
+ /** Undo, and bring the model tree in line (an undone edit may rename an element). */
274
+ undo() {
275
+ const undone = super.undo();
276
+ if (undone) this._renderTreePanel();
277
+ return undone;
278
+ }
279
+
280
+ /** Redo, and bring the model tree in line. */
281
+ redo() {
282
+ const redone = super.redo();
283
+ if (redone) this._renderTreePanel();
284
+ return redone;
285
+ }
286
+
287
+ /**
288
+ * Edit this model: a model tree and view menu over it, and follow its
289
+ * changes (views created, duplicated, renamed, deleted or switched,
290
+ * whether from a tab, the tree or code). Used at construction and when
291
+ * an import replaces the model.
292
+ * @private
293
+ */
294
+ _bindModel(model) {
295
+ this._unsubscribeModel?.();
296
+ this.model = model;
297
+ this.viewMenu = new ViewMenu(model, {
298
+ document: () => this.document,
299
+ noun: 'view',
300
+ editProperties: (view) => this.showViewProperties(view),
301
+ });
302
+ this.templateMenu = new CanvasTemplateMenu(model, {
303
+ library: this.templateLibrary,
304
+ document: () => this.document,
305
+ });
306
+ this.tree = new this.workspace.ModelTree(model, { mode: 'byLayer' });
307
+ this.currentView = model.currentView ?? this.tree.getSelectedView();
308
+ this._wireTreeSelection();
309
+ this._unsubscribeModel = model.onChange(() => this._onModelChange());
310
+ }
311
+
312
+ /** Open a view's properties: name, documentation and its own settings (routing, grid, background). */
313
+ showViewProperties(view = this.currentView) {
314
+ if (!view) return null;
315
+ return new SettingsViewPropertiesDialog({
316
+ model: this.model, view, settingsManager: this.settingsManager, modelId: 'default', document: this.document,
317
+ }).open();
318
+ }
319
+
320
+ /** Run `fn` without reacting to the model changes it makes (the editor made them itself). @private */
321
+ _quietly(fn) {
322
+ this._quiet = true;
323
+ try { return fn(); } finally { this._quiet = false; }
324
+ }
325
+
326
+ /**
327
+ * Bring the tabs, the tree and the canvas in line with the model: close
328
+ * the tabs of deleted views and show the current view (opening its tab).
329
+ * @private
330
+ */
331
+ _onModelChange() {
332
+ if (this._quiet || !this.diagrams) return;
333
+ for (const id of this.diagrams.openViewIds()) {
334
+ if (!this.model.getView(id)) this._quietly(() => this.diagrams.closeTab(id));
335
+ }
336
+ this._renderTreePanel();
337
+ // Showing the current view also redraws the tab bar, so renamed tabs follow.
338
+ const view = this.model.currentView;
339
+ if (view) this._showView(view);
340
+ }
341
+
342
+ /** Open (or focus) a view's tab and select it in the tree, which makes it current. @private */
343
+ _showView(view) {
344
+ this.diagrams.show(view, () => this.tree.select(view.id, 'view'));
345
+ this.tree.select(view.id, 'view');
346
+ }
347
+
348
+ /** Restack an element instance on the current view: one undo step on the view's history. */
349
+ executeOrderCommand(instanceId, operation) {
350
+ const method = { front: 'bringToFront', back: 'sendToBack', raise: 'raise', lower: 'lower' }[operation];
351
+ if (!method || !this.currentView?.getElementInstance(instanceId)) return;
352
+ this.currentView[method](instanceId);
353
+ this.render();
354
+ }
355
+
356
+ /** The current tab's engine SVG, for the base's coordinate helpers. @protected */
357
+ _canvasSvg() {
358
+ return this.currentEngine?.renderer?.svg ?? null;
359
+ }
360
+
361
+ /**
362
+ * Lay out the app in the shared workbench EditorShell (toolbar, tree,
363
+ * diagram, optional analysis panel) — only when the host gave an app
364
+ * container and no diagram container of its own.
365
+ */
366
+ _createStructure() {
367
+ const appEl = this.document.getElementById(this.appContainerId);
368
+ if (!appEl || this.document.getElementById(this.diagramContainerId)) return;
369
+ new EditorShell({
370
+ document: this.document,
371
+ fullPage: this.fullPage,
372
+ aside: !!this.analysisContainerId,
373
+ ids: { toolbar: this.toolbarContainerId, side: this.treeContainerId, main: this.diagramContainerId, aside: this.analysisContainerId },
374
+ }).mount(appEl);
375
+ }
376
+ /**
377
+ * Tree and filter styling, scoped to this editor's own containers: hosts
378
+ * (e.g. a gallery page with other diagrams) are left untouched.
379
+ */
380
+ _injectStyles() {
381
+ const styleId = `archimate-editor-styles-${this.treeContainerId}-${this.diagramContainerId}`;
382
+ if (this.document.getElementById(styleId)) return;
383
+ const tree = `#${CSS.escape(this.treeContainerId)}`;
384
+ const diagram = `#${CSS.escape(this.diagramContainerId)}`;
385
+ const style = this.document.createElement('style');
386
+ style.id = styleId;
387
+ style.textContent = `
388
+ ${tree} .archimate-tree-node { padding: 4px 8px; cursor: pointer; user-select: none; }
389
+ ${tree} .archimate-tree-node:hover { background: #e8e8e8; }
390
+ ${tree} .archimate-tree-node.selected { background: #5470c6; color: white; }
391
+ ${tree} .filter-hidden { display: none; }
392
+ ${tree}.tree-filtered::before {
393
+ content: "🔍 filtered"; display: block; padding: 4px 8px; background: #fff3cd;
394
+ border-bottom: 1px solid #ffc107; font-size: 12px; color: #856404; font-weight: bold;
395
+ }
396
+ ${diagram} svg [nodeId]:not(.filter-highlight) { opacity: 0.3; }
397
+ /* ModelTree's own filter button: this editor has its own above the tree. */
398
+ ${tree} > div > div:first-child button { display: none !important; }
399
+ `;
400
+ this.document.head.appendChild(style);
401
+ }
402
+ /**
403
+ * The toolbar: the shared MenuBar, rendering the items from menuBarItems().
404
+ */
405
+ _initializeToolbar() {
406
+ const container = this.document.getElementById(this.toolbarContainerId);
407
+ if (!container) return;
408
+ this.menuBar = new MenuBar({ container, items: this.menuBarItems(), editor: this, document: this.document }).render();
409
+ if (this.features?.search) this._initializeTreeFilter();
410
+ }
411
+
412
+ /**
413
+ * ArchiMate's toolbar items, each gated by its `features` flag. Option
414
+ * values live in the editor (getOption/setOption) and re-render on change.
415
+ * @returns {Array<ToolbarItem>}
416
+ */
417
+ menuBarItems() {
418
+ const f = this.features || {};
419
+ const value = (key, fallback) => () => this.getOption(key, fallback);
420
+ const update = (key) => (v) => { this.setOption(key, v); this.render(); };
421
+ const items = [
422
+ new ToolbarText(() => '🏛️ ArchiMate', { fontWeight: 'bold', color: '#333', fontSize: '13px' }),
423
+ new ToolbarSeparator(),
424
+ ];
425
+
426
+ // Read when the menu opens: the current view and the model (an import replaces it) change.
427
+ const viewItems = () => {
428
+ const saveAs = this.currentView ? this.templateMenu.saveAsTemplateItems(this.currentView.id) : [];
429
+ const importTemplate = this.templateMenu.importItem();
430
+ return [
431
+ this.viewMenu.newViewItem(),
432
+ ...this.templateMenu.newCanvasItems(),
433
+ ...(this.currentView ? [{ separator: true }, ...this.viewMenu.items(this.currentView.id)] : []),
434
+ ...(saveAs.length || importTemplate ? [{ separator: true }, ...saveAs, ...(importTemplate ? [importTemplate] : [])] : []),
435
+ ];
436
+ };
437
+ // Export and import, one group per file format its features flags switch on.
438
+ const file = modelFileMenuItems(this.fileFormats, {
439
+ model: () => this.model,
440
+ onImport: (model) => this.replaceModel(model),
441
+ enabled: (format, direction) => !!f[direction + format.featureName],
442
+ document: () => this.document,
443
+ });
444
+ // The current view as a picture (on unless features.exportImage is false).
445
+ const image = f.exportImage === false ? [] : this.imageMenuItems();
446
+ items.push(new ToolbarMenu('File', () => [
447
+ ...viewItems(),
448
+ ...(file.length ? [{ separator: true }, ...file] : []),
449
+ ...(image.length ? [{ separator: true }, ...image] : []),
450
+ ]));
451
+ items.push(new ToolbarMenu('View', () => this.viewMenuItems()));
452
+ // Model and notation settings live under the cog, as in the template editor.
453
+ items.push(new ToolbarMenu('⚙ Settings', () => this.settingsMenuItems()));
454
+ return items;
455
+ }
456
+
457
+ /**
458
+ * The ⚙ Settings menu: the notation (version, viewpoint and its mode,
459
+ * distinct shapes, derived relationships), each gated by `features`, then
460
+ * the model settings dialog (routing, grid, watermark).
461
+ */
462
+ settingsMenuItems() {
463
+ const f = this.features || {};
464
+ const set = (key, v) => { this.setOption(key, v); this.render(); };
465
+ /** A submenu of choices with the current one ticked. */
466
+ const choose = (label, key, fallback, choices) => {
467
+ const current = this.getOption(key, fallback);
468
+ const shown = choices.find(([v]) => v === current)?.[1] ?? current;
469
+ return { label: `${label}: ${shown}`, items: choices.map(([v, text]) => ({ label: text, checked: v === current, action: () => set(key, v) })) };
470
+ };
471
+ const toggle = (label, key) => {
472
+ const on = !!this.getOption(key, false);
473
+ return { label: `${label}: ${on ? 'on' : 'off'}`, action: () => set(key, !on) };
474
+ };
475
+ const notation = [
476
+ f.version && choose('Version', 'archimate-version', '4', [['4', 'ArchiMate 4'], ['3.2', 'ArchiMate 3.2']]),
477
+ f.viewpoint && choose('Viewpoint', 'archimate-viewpoint', 'All',
478
+ ArchimateViewpoints.all(this.deps?.customViewpoints ?? []).map(vp => [vp.name, vp.name])),
479
+ f.viewpoint && choose('Viewpoint mode', 'archimate-viewpoint-mode', 'fade', [['fade', 'Fade'], ['hide', 'Hide']]),
480
+ f.distinctShapes && toggle('Distinct shapes', 'archimate-distinct-shapes'),
481
+ f.showDerived && toggle('Show derived', 'archimate-show-derived'),
482
+ ].filter(Boolean);
483
+ // The catalogs a user keeps: shared with the canvas editor, saved in this browser.
484
+ const catalogs = [
485
+ { label: 'Specializations…', action: () => specializationCatalogDialog({ version: this.getOption('archimate-version', '4'), document: this.document, onChange: () => this.render() }).open() },
486
+ f.viewpoint && { label: 'Viewpoints…', action: () => CustomViewpoints.dialog({ version: this.getOption('archimate-version', '4'), document: this.document, onChange: (viewpoints) => { this.deps.customViewpoints = viewpoints; this.render(); } }).open() },
487
+ ].filter(Boolean);
488
+ return [
489
+ ...notation,
490
+ ...(notation.length ? [{ separator: true }] : []),
491
+ ...catalogs,
492
+ { separator: true },
493
+ { label: 'Model settings…', action: () => this.showModelSettings() },
494
+ ];
495
+ }
496
+
497
+ /** Open the model settings dialog (grid, routing, watermark, …). */
498
+ showModelSettings() {
499
+ if (!this.settingsManager || !this.workspace?.showGlobalSettingsDialog) {
500
+ console.warn('Cannot open settings - missing settingsManager or showGlobalSettingsDialog');
501
+ return;
502
+ }
503
+ this.workspace.showGlobalSettingsDialog(this.document, this.settingsManager, 'default');
504
+ }
505
+
506
+ /** The file format of class `Format` among this editor's (or a new one). @private */
507
+ _format(Format) {
508
+ return this.fileFormats.find(f => f instanceof Format) ?? new Format();
509
+ }
510
+
511
+ /** Import the combined CSV that exportCSV() writes (elements, then relationships), replacing the model. */
512
+ importCSVText(text) {
513
+ this.replaceModel(this._format(ArchimateCsvFileFormat).import(text));
514
+ }
515
+
516
+ /** Filter box and options above the model tree. */
517
+ _initializeTreeFilter() {
518
+ setTimeout(() => {
519
+ const treeEl = this.document.getElementById(this.treeContainerId);
520
+ if (treeEl && !this.document.getElementById('tree-controls-wrapper')) {
521
+ const controlsWrapper = this.document.createElement('div');
522
+ controlsWrapper.id = 'tree-controls-wrapper';
523
+ controlsWrapper.style.cssText = 'padding:8px;background:#f5f5f5;border-bottom:1px solid #ddd;display:flex;gap:6px;align-items:center;position:relative;';
524
+
525
+ // Filter input (from FilterSystem)
526
+ const filterInput = this.document.createElement('input');
527
+ filterInput.id = 'tree-filter-input';
528
+ filterInput.type = 'text';
529
+ filterInput.placeholder = '🔍 Filter';
530
+ filterInput.title = 'Filter elements and tree';
531
+ filterInput.style.cssText = 'padding:6px 12px;border:1px solid #ccc;border-radius:3px;flex:1;font-size:13px;';
532
+ filterInput.addEventListener('input', () => {
533
+ this.filterSystem.setPattern(filterInput.value);
534
+ });
535
+
536
+ // Settings button
537
+ const settingsBtn = this.document.createElement('button');
538
+ settingsBtn.id = 'tree-settings-btn';
539
+ settingsBtn.type = 'button';
540
+ settingsBtn.textContent = '⚙️';
541
+ settingsBtn.title = 'Filter options';
542
+ settingsBtn.style.cssText = 'padding:6px 12px;border:1px solid #ccc;border-radius:3px;background:white;cursor:pointer;font-size:13px;';
543
+ settingsBtn.addEventListener('click', (e) => {
544
+ e.stopPropagation();
545
+ this.filterSystem.togglePopup();
546
+ });
547
+
548
+ // Popup from FilterSystem
549
+ const popup = this.filterSystem.createPopupUI();
550
+
551
+ // Close document click handler
552
+ this.document.addEventListener('click', () => {
553
+ this.filterSystem.togglePopup(false);
554
+ });
555
+ popup.addEventListener('click', (e) => {
556
+ e.stopPropagation();
557
+ });
558
+
559
+ // Assemble and insert
560
+ controlsWrapper.appendChild(filterInput);
561
+ controlsWrapper.appendChild(settingsBtn);
562
+ controlsWrapper.appendChild(popup);
563
+ treeEl.insertBefore(controlsWrapper, treeEl.firstChild);
564
+ }
565
+ }, 100);
566
+ }
567
+
568
+ _initializeSpecializations() {
569
+ this.workspace.RenderEngine.ArchimateSpecializationRegistry.loadFrom(SPECIALIZATIONS_STORE);
570
+ }
571
+ _initializeDiagrams() {
572
+ const containerEl = this.document.getElementById(this.diagramContainerId);
573
+ this.diagrams = new this.workspace.DiagramTabs(this.document, {
574
+ RenderEngine: this.workspace.RenderEngine,
575
+ settingsManager: this.settingsManager,
576
+ menuItems: (viewId) => this.viewMenu.items(viewId),
577
+ });
578
+ const tabsEl = this.diagrams.build();
579
+ containerEl.appendChild(tabsEl);
580
+ }
581
+
582
+ _wireTreeSelection() {
583
+ this.tree.onSelectionChange((data) => {
584
+ if (data.kind === 'view') {
585
+ if (!this.diagrams.hasTab(data.view.id)) {
586
+ this.diagrams.show(data.view, () => {
587
+ this.tree.select(data.view.id, 'view');
588
+ });
589
+ this.diagrams.switchTo(data.view);
590
+ } else {
591
+ this.diagrams.switchTo(data.view);
592
+ }
593
+ this.currentEngine = this.diagrams.getActiveEngine();
594
+ this.currentView = data.view;
595
+ this._quietly(() => this.model.switchTo(data.view.id));
596
+ this.render();
597
+ } else if (data.kind === 'element') {
598
+ this._highlightElement(data.id);
599
+ this._renderAnalysisPanel(data.id);
600
+ } else if (data.kind === 'relationship') {
601
+ this._highlightElement(data.id, 'relationship');
602
+ this._clearAnalysisPanel();
603
+ } else {
604
+ // Deselection (e.g., clicking on empty canvas)
605
+ // Clear visual selection but keep filter state intact
606
+ this._clearDiagramSelection();
607
+ this._clearAnalysisPanel();
608
+ }
609
+ });
610
+ }
611
+
612
+ _renderTreePanel() {
613
+ const treeEl = this.document.getElementById(this.treeContainerId);
614
+ const treePanel = this.workspace.buildModelTreePanel(this.document, {
615
+ modelTree: this.tree,
616
+ treeDivId: `${this.treeContainerId}-content`,
617
+ viewMenu: this.viewMenu,
618
+ templateMenu: this.templateMenu,
619
+ elementMenu: (element) => elementMenuItems(element, {
620
+ model: this.model, view: this.currentView,
621
+ editProperties: (el) => editElementProperties(el, {
622
+ history: this.currentView?.history, version: this.getOption('archimate-version', '4'), document: this.document,
623
+ onSaved: () => { this.render(); this._renderTreePanel(); },
624
+ }),
625
+ showIn: (view, instanceId) => {
626
+ if (view.id !== this.model.currentViewId) this.model.switchTo(view.id);
627
+ this._highlightElement(instanceId);
628
+ },
629
+ addToView: (el) => {
630
+ const at = this._snapToGrid(this.visibleCentre());
631
+ this.currentView.history.execute(new ShowElementCommand(this.currentView, new ElementInstance(el.id, at.x, at.y)));
632
+ this.render();
633
+ },
634
+ }),
635
+ });
636
+ treeEl.innerHTML = '';
637
+ treeEl.appendChild(treePanel);
638
+ }
639
+
640
+ _initializeTabs() {
641
+ const views = this.model.views();
642
+ for (const view of views) {
643
+ this.diagrams.show(view, () => {
644
+ this.tree.select(view.id, 'view');
645
+ });
646
+ }
647
+ this.currentEngine = this.diagrams.getActiveEngine();
648
+ }
649
+
650
+ _highlightElement(id, kind = 'element') {
651
+ this._clearDiagramSelection();
652
+ if (!this.currentEngine?.renderer?.svg) return;
653
+
654
+ if (kind === 'element') {
655
+ const el = this.currentEngine.renderer.svg.querySelector(`[nodeId="${CSS.escape(id)}"]`);
656
+ if (el) {
657
+ el.classList.add('selected');
658
+ el.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
659
+ }
660
+ } else if (kind === 'relationship') {
661
+ const edgeEl = this.currentEngine.renderer.svg.querySelector(`[edgeId="${CSS.escape(id)}"]`);
662
+ if (edgeEl) {
663
+ edgeEl.classList.add('selected-edge');
664
+ edgeEl.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
665
+ }
666
+ }
667
+ }
668
+
669
+ _clearDiagramSelection() {
670
+ this.currentEngine?.renderer?.svg?.querySelectorAll?.('.selected, .selected-edge')?.forEach(el => {
671
+ el.classList.remove('selected', 'selected-edge');
672
+ });
673
+ }
674
+
675
+ _renderAnalysisPanel(elementId) {
676
+ const analysisEl = this.document.getElementById(this.analysisContainerId);
677
+ if (!analysisEl) return;
678
+
679
+ const panel = this.workspace.buildAnalysisPanel(this.document, {
680
+ model: this.model,
681
+ elementId,
682
+ onSelectItem: (info) => this.tree.select(info.id, info.kind),
683
+ });
684
+ analysisEl.innerHTML = '';
685
+ analysisEl.appendChild(panel);
686
+ }
687
+
688
+ _clearAnalysisPanel() {
689
+ const analysisEl = this.document.getElementById(this.analysisContainerId);
690
+ if (analysisEl) analysisEl.innerHTML = '';
691
+ }
692
+
693
+ /**
694
+ * Set dependencies: dialog functions and other callbacks.
695
+ * Optional—uses bundled workspace dialogs by default.
696
+ */
697
+ setDependencies(deps = {}) {
698
+ this.deps = {
699
+ showPropertiesDialog: deps.showPropertiesDialog || this._defaultShowPropertiesDialog.bind(this),
700
+ showAddArchimateElementDialog: deps.showAddArchimateElementDialog || this._defaultShowAddArchimateElementDialog.bind(this),
701
+ showAddNoteDialog: deps.showAddNoteDialog || this._defaultShowAddNoteDialog.bind(this),
702
+ showAddImageDialog: deps.showAddImageDialog || this._defaultShowAddImageDialog.bind(this),
703
+ showViewPropertiesDialog: deps.showViewPropertiesDialog || this._defaultShowViewPropertiesDialog.bind(this),
704
+ showPickRelationshipKindDialog: deps.showPickRelationshipKindDialog || this._defaultShowPickRelationshipKindDialog.bind(this),
705
+ showDiagramPropertiesDialog: deps.showDiagramPropertiesDialog || this._defaultShowDiagramPropertiesDialog.bind(this),
706
+ customViewpoints: deps.customViewpoints || CustomViewpoints.load(),
707
+ };
708
+ }
709
+
710
+ _defaultShowPickRelationshipKindDialog(engine, edge) {
711
+ try {
712
+ const ArchimateValidator = engine.constructor.ArchimateValidator;
713
+ const archimateVersion = engine._archimateOptions?.archimateVersion || '4';
714
+ const nodes = engine._chartState?.nodes ?? [];
715
+ const sourceNode = nodes.find(n => n.id === edge.source) || { id: edge.source };
716
+ const targetNode = nodes.find(n => n.id === edge.target) || { id: edge.target };
717
+ const allowed = ArchimateValidator
718
+ ? ArchimateValidator.allowedKinds(sourceNode, targetNode, archimateVersion)
719
+ : ['composition', 'aggregation', 'assignment', 'realization', 'serving', 'access', 'influence', 'triggering', 'flow', 'specialization', 'association'];
720
+ const kinds = allowed.includes('association') ? allowed : [...allowed, 'association'];
721
+ new FormDialog({
722
+ title: `New relationship: ${edge.source} → ${edge.target}`,
723
+ description: `${sourceNode.type || 'unknown'} → ${targetNode.type || 'unknown'}: ${kinds.length} allowed kind${kinds.length === 1 ? '' : 's'}`,
724
+ fields: [{ key: 'kind', label: 'Kind', type: 'select', choices: kinds.map(k => [k, k]), value: kinds[0] }],
725
+ submitLabel: 'Set',
726
+ cancelLabel: 'Leave as association',
727
+ idPrefix: 'archimate-relationship-',
728
+ document: this.document,
729
+ onApply: ({ kind }) => {
730
+ Object.assign(edge, engine._archimateBuildEdge({ ...edge, kind }));
731
+ const relationship = this.model.getRelationship(edge.relationshipId);
732
+ if (relationship) relationship.kind = kind;
733
+ engine._rerenderGraph();
734
+ setTimeout(() => this._applyFilterHighlighting(), 0);
735
+ },
736
+ }).open();
737
+ } catch (err) {
738
+ console.error('showPickRelationshipKindDialog failed:', err);
739
+ }
740
+ }
741
+
742
+ _defaultShowPropertiesDialog(subject, engine, kind, options) {
743
+ // Delegate to workspace's bundled properties dialog
744
+ const bundledDialog = this.workspace?.showPropertiesDialog;
745
+ if (bundledDialog) {
746
+ return bundledDialog(subject, engine, kind, { document: this.document, ...options });
747
+ }
748
+ }
749
+
750
+ /**
751
+ * Open the properties of a drawn element, note or line, and keep what is
752
+ * changed: the concept's fields in the model (or a note's or plain line's
753
+ * own), and fill, line and text colour, font, text size and label on this
754
+ * view. One undo step on the view.
755
+ * @param {Object} item - The drawn node or edge
756
+ * @param {'node'|'edge'} kind
757
+ * @param {RenderEngine} [engine]
758
+ */
759
+ editItemProperties(item, kind, engine = this.currentEngine) {
760
+ if (kind === 'node' && this.isFixed(item.id)) return this.tellLocked(`"${item.name || 'This'}"`, item.id);
761
+ // The dialog calls an element's documentation its description.
762
+ if (kind === 'node' && item.description === undefined && item.documentation !== undefined) item.description = item.documentation;
763
+ const before = JSON.parse(JSON.stringify(item));
764
+ return this.deps.showPropertiesDialog(item, engine, kind, {
765
+ onSave: () => this.saveItemEdit(item, kind, before),
766
+ });
767
+ }
768
+
769
+ /**
770
+ * Keep an edit made to a drawn item (see editItemProperties).
771
+ * @param {Object} item - The drawn item, edited
772
+ * @param {'node'|'edge'} kind
773
+ * @param {Object} before - The drawn item as it was
774
+ * @returns {boolean} whether anything changed
775
+ */
776
+ saveItemEdit(item, kind, before) {
777
+ const view = this.currentView;
778
+ if (!view) return false;
779
+ const changes = viewItemChanges(kind, before, item);
780
+ let targets = null;
781
+ if (kind === 'edge') {
782
+ const connection = view.viewConnections.get(item.id);
783
+ const instance = connection ?? view.getRelationshipInstance(item.id);
784
+ const concept = connection ?? (instance && this.model.getRelationship(instance.relationshipId));
785
+ if (concept) targets = { concept, instance };
786
+ } else {
787
+ const instance = view.getElementInstance(item.id);
788
+ const concept = instance && (instance.content ?? this.model.getElement(instance.elementId));
789
+ if (concept) targets = { concept, instance };
790
+ }
791
+ if (!targets) return false;
792
+ // A new layer or type redraws in its layer's colour: the element's own, not a colour set on this view.
793
+ if (kind === 'node' && changes.appearance.fillColor && !targets.instance.appearance?.fillColor
794
+ && changes.appearance.fillColor === this.currentEngine?._archimateLayerColor?.(item.layer)) {
795
+ delete changes.appearance.fillColor;
796
+ }
797
+ if (!hasViewItemChanges(changes)) return false;
798
+ view.history.execute(new EditViewItemCommand(targets, changes, `Edit ${item.name || item.label || kind}`));
799
+ this.render();
800
+ this._renderTreePanel();
801
+ return true;
802
+ }
803
+
804
+ _defaultShowDiagramPropertiesDialog(engine) {
805
+ // Delegate to workspace's bundled diagram properties dialog
806
+ const bundledDialog = this.workspace?.showDiagramPropertiesDialog;
807
+ if (bundledDialog) {
808
+ return bundledDialog(engine);
809
+ }
810
+ }
811
+
812
+ _defaultShowViewPropertiesDialog(view) {
813
+ this.showViewProperties(view);
814
+ }
815
+
816
+ _createImageNode(point, dataURL, href = '') {
817
+ this._addViewObject({
818
+ id: this._newViewObjectId('image'), name: 'Image', shape: 'image', iconUrl: dataURL,
819
+ width: 160, height: 120, resizable: true, href,
820
+ }, point);
821
+ }
822
+
823
+ /**
824
+ * The part of a node's drawn size that differs from what its element draws
825
+ * at by default, so a resize undone back to the default leaves no override.
826
+ * @private
827
+ */
828
+ _sizeOverride(instance, node) {
829
+ const drawn = instance.resolve(this.model) ?? {};
830
+ const defaults = this.currentEngine._archimateBuildNode({ ...drawn }, this.currentEngine._archimateOptions ?? {});
831
+ return Object.fromEntries(NODE_SIZE_KEYS
832
+ .filter(key => node[key] != null && node[key] !== defaults[key])
833
+ .map(key => [key, node[key]]));
834
+ }
835
+
836
+ /** An element id not yet used in the model, derived from a label. @private */
837
+ _newElementId(label) {
838
+ return GraphDiagram.generateNodeId(label, this.model.elements().map(e => e.id));
839
+ }
840
+
841
+ /**
842
+ * Create a model element and show it on the current view at `point`
843
+ * (snapped to the grid), as one undo step.
844
+ * @returns {ElementInstance|null}
845
+ * @private
846
+ */
847
+ _addNewElement(element, point) {
848
+ return this._placeOnView(element.id, point, { element });
849
+ }
850
+
851
+ /**
852
+ * Show a model element on the current view at `point` (snapped to the
853
+ * grid) and redraw. One undo step; with `element`, it is created too.
854
+ * @returns {ElementInstance|null}
855
+ * @private
856
+ */
857
+ _placeOnView(elementId, point, { element = null } = {}) {
858
+ if (!this.currentView) return null;
859
+ const snapped = this._snapToGrid(point);
860
+ return this._show(new ElementInstance(elementId, snapped.x, snapped.y), { element });
861
+ }
862
+
863
+ /**
864
+ * Put a note or image on the current view only (it is not a model element)
865
+ * at `point`, snapped to the grid. One undo step.
866
+ * @returns {ViewObjectInstance|null}
867
+ * @private
868
+ */
869
+ _addViewObject(content, point) {
870
+ if (!this.currentView) return null;
871
+ const snapped = this._snapToGrid(point);
872
+ return this._show(new ViewObjectInstance(content, snapped.x, snapped.y));
873
+ }
874
+
875
+ /** Add an instance to the current view as one undo step and redraw. @private */
876
+ _show(instance, { element = null } = {}) {
877
+ this.currentView.addToView(instance, { element });
878
+ this.render();
879
+ return instance;
880
+ }
881
+
882
+ /** A view-object id clear of every model element and every view's objects. @private */
883
+ _newViewObjectId(label) {
884
+ const taken = [
885
+ ...this.model.elements().map(e => e.id),
886
+ ...this.model.views().flatMap(v => [...v.elementInstances.values()].map(i => i.elementId)),
887
+ ];
888
+ return GraphDiagram.generateNodeId(label, taken);
889
+ }
890
+
891
+ _defaultShowAddArchimateElementDialog(engine, point) {
892
+ try {
893
+ const RenderEngineClass = engine.constructor;
894
+ const archimateVersion = engine._archimateOptions?.archimateVersion || '4';
895
+ const viewpoint = engine._archimateOptions?.viewpoint || 'All';
896
+ const customViewpoints = engine._archimateOptions?.customViewpoints || [];
897
+ const typeChoices = [
898
+ ...RenderEngineClass.ArchimateViewpoints.allowedElementTypes(viewpoint, archimateVersion, customViewpoints),
899
+ ...RenderEngineClass.ITIconNotation.elementTypeNames(),
900
+ ].sort();
901
+ const specSelect = new this.workspace.ArchimateSpecializationSelector(RenderEngineClass.ArchimateSpecializationRegistry, { doc: this.document })
902
+ .buildSelect({ elementType: '' });
903
+
904
+ const dialog = new FormDialog({
905
+ title: 'Add ArchiMate Element',
906
+ description: viewpoint === 'All'
907
+ ? 'Type picks the layer/colour automatically (e.g. business-actor, node, goal).'
908
+ : `Viewpoint "${viewpoint}" restricts the type list; type picks layer/colour automatically.`,
909
+ fields: [
910
+ { key: 'name', label: 'Name', type: 'text' },
911
+ { key: 'type', label: 'Type', type: 'text', suggestions: typeChoices },
912
+ { key: 'specialization', label: 'Specialization', type: 'custom', element: specSelect, read: () => specSelect.value || undefined },
913
+ ],
914
+ submitLabel: 'Add',
915
+ idPrefix: 'archimate-add-element-',
916
+ document: this.document,
917
+ validate: ({ name, type }) => (name.trim() && !type.trim() ? 'Type is required' : null),
918
+ onApply: ({ name, type, specialization }) => {
919
+ name = name.trim();
920
+ type = type.trim();
921
+ if (!name) return;
922
+ const id = this._newElementId(name);
923
+ this._addNewElement(engine._archimateBuildNode({ id, name, type, specialization }, { archimateVersion }), point);
924
+ this._checkAndPromptForRelationships(id);
925
+ },
926
+ }).open();
927
+
928
+ const typeInput = dialog.controls.type;
929
+ for (const event of ['input', 'change']) {
930
+ typeInput.addEventListener(event, () => specSelect._onTypeChanged(typeInput.value));
931
+ }
932
+ dialog.controls.name.focus();
933
+ } catch (err) {
934
+ console.error('showAddArchimateElementDialog failed:', err);
935
+ }
936
+ }
937
+
938
+ _defaultShowAddNoteDialog(engine, point) {
939
+ try {
940
+ new FormDialog({
941
+ title: 'Add Note',
942
+ fields: [{ key: 'text', label: 'Text', type: 'textarea' }],
943
+ submitLabel: 'Add',
944
+ idPrefix: 'archimate-add-note-',
945
+ document: this.document,
946
+ onApply: ({ text }) => {
947
+ text = text.trim();
948
+ if (!text) return;
949
+ this._addViewObject({
950
+ id: this._newViewObjectId(text), name: text, label: text, shape: 'note',
951
+ width: 140, height: 90, resizable: true,
952
+ }, point);
953
+ },
954
+ }).open();
955
+ } catch (err) {
956
+ console.error('showAddNoteDialog failed:', err);
957
+ }
958
+ }
959
+
960
+ _defaultShowAddImageDialog(engine, point) {
961
+ try {
962
+ new FormDialog({
963
+ title: 'Add Image',
964
+ fields: [
965
+ { key: 'file', label: 'File', type: 'file', accept: 'image/*' },
966
+ { key: 'href', label: 'Link', type: 'text', placeholder: 'optional URL opened on click' },
967
+ ],
968
+ submitLabel: 'Add',
969
+ idPrefix: 'archimate-add-image-',
970
+ document: this.document,
971
+ onApply: ({ file, href }) => {
972
+ if (!file) return;
973
+ const reader = new this.document.defaultView.FileReader();
974
+ reader.onload = (e) => this._createImageNode(point, e.target.result, href.trim());
975
+ reader.readAsDataURL(file);
976
+ },
977
+ }).open();
978
+ } catch (err) {
979
+ console.error('showAddImageDialog failed:', err);
980
+ }
981
+ }
982
+
983
+ /**
984
+ * Render the current view's diagram.
985
+ * Gets elements from view, calls rendering engine, applies UI styling.
986
+ */
987
+ render(options = {}) {
988
+ if (!this.currentEngine || !this.currentView) return;
989
+
990
+ console.log(`[render] START - currentView id: ${this.currentView.id}, instances count: ${this.currentView.elementInstances.size}`);
991
+
992
+ // Get elements and relationships to render
993
+ let elementsToRender = this.currentView?.elements;
994
+ let relationshipsToRender = this.currentView?.relationships || [];
995
+
996
+ // Log what we're about to render
997
+ const zIndices = Array.from(this.currentView.elementInstances.values()).map(i => `${i.id}:${i.zIndex}`).join(', ');
998
+ console.log(`[render] instances zIndex: ${zIndices}`);
999
+ const renderZIndexes = elementsToRender?.map(e => `${e.id}:${e.zIndex}`).join(', ');
1000
+ console.log(`[render] elementsToRender zIndex: ${renderZIndexes}`);
1001
+
1002
+ if (!Array.isArray(elementsToRender)) {
1003
+ console.warn('render: currentView.elements is not an array', {
1004
+ viewName: this.currentView?.name,
1005
+ elements: elementsToRender,
1006
+ });
1007
+ return;
1008
+ }
1009
+
1010
+ // Compute and include derived relationships if toggle is on
1011
+ if (this.getOption('archimate-show-derived', false)) {
1012
+ const { derived } = this.workspace.DerivedRelationships.compute(this.model, { maxHops: 2 });
1013
+ const derivedWithInstances = derived.map(rel => {
1014
+ const mapped = { ...rel, lineStyle: 'dotted', label: `${rel.pathSummary}` };
1015
+ // Map source/target to diagramInstanceId (same as view relationships)
1016
+ const sourceInstances = this.currentView.elementInstancesFor(rel.source);
1017
+ const targetInstances = this.currentView.elementInstancesFor(rel.target);
1018
+ if (sourceInstances.length > 0) mapped.source = sourceInstances[0].id;
1019
+ if (targetInstances.length > 0) mapped.target = targetInstances[0].id;
1020
+ return mapped;
1021
+ });
1022
+ relationshipsToRender = [...relationshipsToRender, ...derivedWithInstances];
1023
+ }
1024
+
1025
+ try {
1026
+ // Initialize model settings on first render if needed
1027
+ if (this.settingsManager && !this.settingsManager._modelSettings['default']) {
1028
+ this.settingsManager.initModelSettings('default');
1029
+ }
1030
+
1031
+ // Get effective settings (hierarchy: view > model > tool)
1032
+ const effectiveSettings = this.settingsManager?.getEffectiveSettings(this.currentView, 'default') || {};
1033
+ const gridSize = effectiveSettings.gridSize || 20;
1034
+ const edgeRouting = effectiveSettings.edgeRouting || 'orthogonal';
1035
+ const showGrid = effectiveSettings.showGrid !== false;
1036
+ // Shared with the base's snapping (_snapToGrid) for drops onto the canvas.
1037
+ this.gridSize = gridSize;
1038
+ this.showGrid = showGrid;
1039
+
1040
+ const deps = this.deps || {};
1041
+ const currentArchimateViewpoint = () => this.getOption('archimate-viewpoint', 'All');
1042
+ const currentArchimateViewpointMode = () => this.getOption('archimate-viewpoint-mode', 'fade');
1043
+
1044
+ this.currentEngine.archimateDiagram(elementsToRender, relationshipsToRender, {
1045
+ // The groups drawn on this view (an Archi import's groups), parents first.
1046
+ folders: this.currentView?.placedFolders ?? [],
1047
+ archimateVersion: this.getOption('archimate-version', '4'),
1048
+ useDistinctShapes: this.getOption('archimate-distinct-shapes', false),
1049
+ // How an invalid relationship shows: diagram, then model, then tool setting.
1050
+ invalidRelationships: effectiveSettings.invalidRelationships,
1051
+ viewpoint: currentArchimateViewpoint(),
1052
+ viewpointMode: currentArchimateViewpointMode(),
1053
+ customViewpoints: deps.customViewpoints || [],
1054
+ editable: true,
1055
+ resizable: true,
1056
+ gridSize,
1057
+ showGrid,
1058
+ edgeRouting,
1059
+ // The engine records moves and bends into this view's history, so
1060
+ // they undo across re-renders; these hooks fire on every apply.
1061
+ history: this.currentView.history,
1062
+ internalKeys: this.currentView.constructor.PROJECTION_KEYS,
1063
+ onNodeMove: (nodeId, pos) => {
1064
+ const instance = this.currentView?.getElementInstance(nodeId);
1065
+ if (instance) {
1066
+ instance.x = pos.x;
1067
+ instance.y = pos.y;
1068
+ }
1069
+ },
1070
+ onNodeResize: (node) => {
1071
+ const instance = this.currentView?.getElementInstance(node.id);
1072
+ if (instance) instance.size = this._sizeOverride(instance, node);
1073
+ },
1074
+ // Each relationship instance keeps its own route and pinned sides.
1075
+ onEdgeWaypointsChange: (edge, waypoints) => {
1076
+ const instance = this.currentView?.getRelationshipInstance(edge.id);
1077
+ if (instance) instance.waypoints = (waypoints ?? []).map(wp => ({ x: wp.x, y: wp.y }));
1078
+ },
1079
+ onEdgeSideChange: (edge, endpoint, side) => {
1080
+ const instance = this.currentView?.getRelationshipInstance(edge.id);
1081
+ if (instance) instance[endpoint === 'source' ? 'sourceSide' : 'targetSide'] = side ?? null;
1082
+ },
1083
+ onEdgeAdd: (edge) => this._relationshipDrawn(edge),
1084
+ onEdgeRemove: (edge) => this._relationshipUndrawn(edge),
1085
+ contextMenuItems: [
1086
+ { label: 'Show/Edit Properties…', action: (node, engine) => this.editItemProperties(node, 'node', engine) },
1087
+ { label: 'Add Missing Relationships…', action: (node, engine) => {
1088
+ const instance = this.currentView?.getElementInstance(node.id);
1089
+ if (instance) this._checkAndPromptForRelationships(instance.elementId);
1090
+ }},
1091
+ { label: 'Generate View For This', action: (node) => {
1092
+ const instance = this.currentView?.getElementInstance(node.id);
1093
+ if (instance && this.model.getElement(instance.elementId)) this.model.generateView([instance.elementId]);
1094
+ }},
1095
+ // The same Order submenu as the template editor's (DiagramEditor.ORDER_OPERATIONS).
1096
+ {
1097
+ label: DiagramEditor.ORDER_MENU_LABEL,
1098
+ items: DiagramEditor.ORDER_OPERATIONS.map(([operation, label]) => ({
1099
+ label, action: (node) => this.executeOrderCommand(node.id, operation),
1100
+ })),
1101
+ },
1102
+ // Group / Ungroup / Lock / Unlock (a template's background is unlocked here).
1103
+ { label: 'Arrange', items: (node) => this.nodeArrangeMenuItems(node.id) },
1104
+ // A canvas box: its hint (shown in the Hints panel).
1105
+ { label: 'Hint', items: (node) => hintMenuItems(this.currentView, node.id, { onChange: () => { this.render(); this.showHintFor(node.id); }, document: this.document }) },
1106
+ // A canvas: this box and its group as a template.
1107
+ { label: 'Save as template', items: (node) => this.templateMenu.saveAsTemplateItems(this.currentView?.id, { itemIds: [...(this.currentView?.arrangement.movingWith(node.id) ?? [])] }) },
1108
+ { label: 'Delete', action: (node) => {
1109
+ if (this.isFixed(node.id)) return this.tellLocked(`"${node.name || 'This'}"`, node.id);
1110
+ this.currentView?.deleteElement(node.id);
1111
+ this.render();
1112
+ }},
1113
+ ],
1114
+ edgeContextMenuItems: [
1115
+ { label: 'Show/Edit Properties…', action: (edge, engine) => this.editItemProperties(edge, 'edge', engine) },
1116
+ { label: 'Add Waypoint Here', action: (edge, engine, point) => engine._activeDiagram._insertEdgeWaypoint(edge, point) },
1117
+ { label: 'Delete from View', action: (edge) => {
1118
+ this.currentView?.removeRelationship(edge.diagramInstanceId || edge.id);
1119
+ this.render();
1120
+ }},
1121
+ ],
1122
+ canvasContextMenuItems: this.canvasContextMenuItemsFor(this.currentView),
1123
+ onNodeClick: (node) => {
1124
+ this.tree.select(node.id, 'element');
1125
+ this.showHintFor(node.id);
1126
+ setTimeout(() => this._applyFilterHighlighting(), 0);
1127
+ },
1128
+ onEdgeClick: (edge) => {
1129
+ if (edge.id) this.tree.select(edge.id, 'relationship');
1130
+ setTimeout(() => this._applyFilterHighlighting(), 0);
1131
+ },
1132
+ onNodeDoubleClick: (node) => {
1133
+ this.editItemProperties(node, 'node');
1134
+ setTimeout(() => this._applyFilterHighlighting(), 0);
1135
+ },
1136
+ onEdgeDoubleClick: (edge) => {
1137
+ this.editItemProperties(edge, 'edge');
1138
+ setTimeout(() => this._applyFilterHighlighting(), 0);
1139
+ },
1140
+ // Empty canvas is the view itself: its properties.
1141
+ onCanvasDoubleClick: () => this.deps.showViewPropertiesDialog(this.currentView, () => this.render()),
1142
+ });
1143
+
1144
+ // Wrap diagram handlers once - only if not already wrapped to avoid nested wrappers
1145
+ this._wrapDiagramHandlers();
1146
+ } catch (err) {
1147
+ console.error('ERROR calling archimateDiagram():', err.message, err.stack);
1148
+ }
1149
+
1150
+
1151
+
1152
+ // Apply filter highlighting to diagram
1153
+ this._applyFilterHighlighting();
1154
+
1155
+ // Background, title band and watermark come from the view's resolved settings.
1156
+ this.decorateView(this.diagrams?.containerFor(this.currentView.id) ?? null,
1157
+ this.settingsManager?.getEffectiveSettings(this.currentView, 'default') ?? {});
1158
+
1159
+ // Save element positions to model
1160
+ this._savePositions(elementsToRender);
1161
+
1162
+ // A canvas shows its hints.
1163
+ this.showHintFor();
1164
+
1165
+ console.log(`[render] END - render complete`);
1166
+ this._notifyRendered();
1167
+ }
1168
+
1169
+ /**
1170
+ * Wrap diagram handlers (onClick, _rerenderGraph) to restore highlighting.
1171
+ * Only wraps once - tracks wrapping state to avoid nested wrappers on each render.
1172
+ * @private
1173
+ */
1174
+ _wrapDiagramHandlers() {
1175
+ if (!this.currentEngine?._activeDiagram) return;
1176
+ if (this.currentEngine._activeDiagram._handlersWrapped) return; // Already wrapped
1177
+ this.currentEngine._activeDiagram._handlersWrapped = true;
1178
+
1179
+ // Wrap onClick to restore filter highlighting after clicks
1180
+ const originalOnClick = this.currentEngine._activeDiagram.onClick;
1181
+ if (originalOnClick) {
1182
+ this.currentEngine._activeDiagram.onClick = (event, hitShape) => {
1183
+ originalOnClick.call(this.currentEngine._activeDiagram, event, hitShape);
1184
+ setTimeout(() => this._applyFilterHighlighting(), 0);
1185
+ };
1186
+ }
1187
+
1188
+ // Wrap _rerenderGraph to restore highlighting after any re-render (e.g., dragging elements)
1189
+ const originalRerenderGraph = this.currentEngine._activeDiagram._rerenderGraph;
1190
+ if (originalRerenderGraph) {
1191
+ this.currentEngine._activeDiagram._rerenderGraph = () => {
1192
+ originalRerenderGraph.call(this.currentEngine._activeDiagram);
1193
+ setTimeout(() => this._applyFilterHighlighting(), 0);
1194
+ };
1195
+ }
1196
+ }
1197
+
1198
+ /**
1199
+ * Apply visual highlighting to filtered elements in diagram.
1200
+ * If no filter, highlight all elements (opacity: normal).
1201
+ * If filter active, highlight only matching elements OR elements that appear in diagramInstances.
1202
+ * Elements added to the diagram stay bright even when filtered, showing they're "in use".
1203
+ */
1204
+ _applyFilterHighlighting() {
1205
+ const svg = this.currentEngine?.renderer?.svg;
1206
+ if (!svg) return;
1207
+ for (const node of svg.querySelectorAll('[nodeId]')) {
1208
+ node.classList.toggle('filter-highlight', this._passesFilter(node.getAttribute('nodeId')));
1209
+ }
1210
+ }
1211
+
1212
+ /**
1213
+ * Whether a drawn node stays lit under the active filter. The filter names
1214
+ * model elements, and a node is an instance of one, so every view (a
1215
+ * duplicate included) is judged by what it shows. Notes and images are not
1216
+ * model elements, so a filter never dims them.
1217
+ * @private
1218
+ */
1219
+ _passesFilter(nodeId) {
1220
+ if (!this._elementFilter) return true;
1221
+ const instance = this.currentView?.getElementInstance(nodeId);
1222
+ if (!instance || !this.model.getElement(instance.elementId)) return true;
1223
+ return this._elementFilter.has(instance.elementId);
1224
+ }
1225
+
1226
+ /**
1227
+ * Save element positions from engine back to ElementInstances (not legacy layout).
1228
+ * IMPORTANT: With ElementInstances, positions are source of truth - don't override them.
1229
+ * This method is deprecated with new architecture; instances should drive rendering.
1230
+ * Only called for backward compatibility with diagram engine repositioning.
1231
+ */
1232
+ _savePositions(elementsToRender) {
1233
+ // With new ElementInstance architecture, instances are the source of truth
1234
+ // Don't override instance positions based on what the diagram engine computed
1235
+ // The instances define where elements should be rendered
1236
+ // This method is kept for future compatibility but should not modify instances
1237
+ }
1238
+
1239
+ /**
1240
+ * Save positions before view switch (e.g., when user clicks another tab).
1241
+ * IMPORTANT: With ElementInstances, instances are the source of truth.
1242
+ * Don't override instance positions based on diagram engine computations.
1243
+ */
1244
+ savePositions() {
1245
+ // With new ElementInstance architecture, instances define positions
1246
+ // Don't override them based on diagram engine repositioning
1247
+ // This method is kept for backward compatibility
1248
+ }
1249
+
1250
+ /** Take elements dragged from the model tree (MODEL_ELEMENT_MIME, which the tree panel sets) onto the diagram. */
1251
+ enableTreeDragDrop() {
1252
+ if (this._treeDragDropBound) return;
1253
+ this._treeDragDropBound = true;
1254
+ this.addDropHandler({
1255
+ accepts: (dt) => Array.from(dt.types).includes(MODEL_ELEMENT_MIME),
1256
+ drop: (dt, point) => this._createDiagramInstance(dt.getData(MODEL_ELEMENT_MIME), point.x, point.y),
1257
+ });
1258
+ }
1259
+
1260
+ /**
1261
+ * Offer the element's relationships to elements already on the view, and
1262
+ * add the ones the user accepts as one undo step.
1263
+ * @private
1264
+ */
1265
+ _checkAndPromptForRelationships(elementId) {
1266
+ const view = this.currentView;
1267
+ if (!view) return;
1268
+ const missing = view.missingRelationshipInstances(elementId);
1269
+ if (!missing.length) return;
1270
+ const nameOf = (instanceId) => {
1271
+ const inst = view.getElementInstance(instanceId);
1272
+ return inst?.getDisplayName(this.model) || inst?.elementId || instanceId;
1273
+ };
1274
+ const lines = missing.map(inst => `${nameOf(inst.sourceInstanceId)} → ${nameOf(inst.targetInstanceId)}`).join('\n');
1275
+ const msg = `Found ${missing.length} relationship(s) connecting these elements:\n\n${lines}\n\nAdd them to the diagram?`;
1276
+ if (!this.document.defaultView?.confirm(msg)) return;
1277
+ view.addRelationshipsToView(missing);
1278
+ this.render();
1279
+ }
1280
+
1281
+ /**
1282
+ * A relationship drawn on the canvas: the engine records the gesture as one
1283
+ * undo step and reports each apply here, so the model and view follow it.
1284
+ * The first time, the user picks its kind.
1285
+ * @private
1286
+ */
1287
+ _relationshipDrawn(edge) {
1288
+ const view = this.currentView;
1289
+ const source = view?.getElementInstance(edge.source);
1290
+ const target = view?.getElementInstance(edge.target);
1291
+ if (!source || !target) return;
1292
+ if (!view.linesAreRelationships(source.id, target.id)) {
1293
+ // The engine's own command draws it (and undoes it): the line joins the view directly.
1294
+ // The same line again on redo, kept by the editor rather than on the drawn edge (whose fields are data).
1295
+ this._drawnLines ??= new WeakMap();
1296
+ if (!this._drawnLines.has(edge)) this._drawnLines.set(edge, view.newLine(source.id, target.id));
1297
+ const line = this._drawnLines.get(edge);
1298
+ edge.id = line.id;
1299
+ view.viewConnections.set(line.id, line);
1300
+ return;
1301
+ }
1302
+ const firstTime = !edge.relationshipId;
1303
+ edge.relationshipId ??= GraphDiagram.generateNodeId(
1304
+ `${source.elementId}-${target.elementId}`, this.model.relationships().map(r => r.id));
1305
+ if (!this.model.getRelationship(edge.relationshipId)) {
1306
+ this.model.addRelationship({ id: edge.relationshipId, source: source.elementId, target: target.elementId, kind: edge.kind ?? 'association' });
1307
+ }
1308
+ const instance = new RelationshipInstance(edge.relationshipId, null, source.id, target.id);
1309
+ // The id the view draws it under, so undo finds it after a re-render.
1310
+ edge.id = instance.id;
1311
+ view.addRelationshipInstance(instance);
1312
+ if (firstTime) this.deps.showPickRelationshipKindDialog(this.currentEngine, edge);
1313
+ }
1314
+
1315
+ /** Undo of a drawn relationship: it leaves the view and the model. @private */
1316
+ _relationshipUndrawn(edge) {
1317
+ if (this.currentView?.viewConnections.has(edge.id)) {
1318
+ this.currentView.viewConnections.delete(edge.id);
1319
+ return;
1320
+ }
1321
+ this.currentView?.removeRelationshipInstance(edge.id);
1322
+ if (edge.relationshipId) this.model.removeRelationship(edge.relationshipId);
1323
+ }
1324
+
1325
+ /**
1326
+ * Show an existing model element (dropped from the tree) on the current
1327
+ * view at (x, y), then offer its relationships to elements already shown.
1328
+ * @private
1329
+ */
1330
+ _createDiagramInstance(elementId, x, y) {
1331
+ if (!this.model.getElement(elementId)) {
1332
+ console.warn(`Element not found: ${elementId}`);
1333
+ return;
1334
+ }
1335
+ if (this._placeOnView(elementId, { x, y })) this._checkAndPromptForRelationships(elementId);
1336
+ }
1337
+
1338
+ /**
1339
+ * Get a control value from the DOM.
1340
+ */
1341
+ getOption(controlId, defaultValue) {
1342
+ if (this._options?.has(controlId)) return super.getOption(controlId, defaultValue);
1343
+ // Hosts that bring their own controls (wireControls) keep them in the DOM.
1344
+ try {
1345
+ const el = this.document.getElementById(controlId);
1346
+ if (!el) return defaultValue;
1347
+ if (el.type === 'checkbox') return el.checked;
1348
+ return el.value || defaultValue;
1349
+ } catch {
1350
+ return defaultValue;
1351
+ }
1352
+ }
1353
+
1354
+ /** Save specializations to localStorage. */
1355
+ saveSpecializations() {
1356
+ SPECIALIZATIONS_STORE.save(this.workspace.ArchimateSpecializationRegistry.serialize());
1357
+ }
1358
+ /**
1359
+ * Export model to CSV format (elements + relationships).
1360
+ * Returns a Blob ready to download.
1361
+ */
1362
+ exportCSV() {
1363
+ const { data, mimeType } = this._format(ArchimateCsvFileFormat).export(this.model);
1364
+ return new this.document.defaultView.Blob([data], { type: mimeType });
1365
+ }
1366
+
1367
+ /**
1368
+ * Import the two CSV tables, replacing the model: its elements and
1369
+ * relationships, with one view of them all (a CSV holds no views).
1370
+ */
1371
+ importCSV(elementsCSV, relationshipsCSV) {
1372
+ this.importCSVText(`=== elements.csv ===\n${elementsCSV}\n\n=== relationships.csv ===\n${relationshipsCSV}`);
1373
+ }
1374
+
1375
+ /**
1376
+ * Filter diagram elements by name using regex pattern.
1377
+ * @param {string} pattern - Regex pattern to match element names (case-insensitive)
1378
+ */
1379
+ /**
1380
+ * Filter elements by name/documentation.
1381
+ * Hides non-matching in tree, highlights matching in diagram.
1382
+ * @param {string} pattern - Regex pattern (case-insensitive)
1383
+ * @param {Object} options - {searchDocs: boolean} - also search documentation property
1384
+ */
1385
+ /**
1386
+ * Filter tree by element name only (separate from diagram filtering).
1387
+ * Hides non-matching nodes in tree.
1388
+ */
1389
+
1390
+ /**
1391
+ * Apply unified filter to diagram and/or tree based on mode.
1392
+ */
1393
+
1394
+ /**
1395
+ * Hide non-matching nodes in the tree and show filter indicator
1396
+ */
1397
+ _filterTreeNodes(matchingIds) {
1398
+ const treeEl = this.document.getElementById(this.treeContainerId);
1399
+ if (!treeEl) {
1400
+ return;
1401
+ }
1402
+
1403
+
1404
+ // Remove previous filter hiding
1405
+ for (const node of treeEl.querySelectorAll('.filter-hidden')) {
1406
+ node.classList.remove('filter-hidden');
1407
+ }
1408
+
1409
+ // Update filter indicator
1410
+ if (matchingIds) {
1411
+ treeEl.classList.add('tree-filtered');
1412
+ } else {
1413
+ treeEl.classList.remove('tree-filtered');
1414
+ }
1415
+
1416
+ if (!matchingIds) return;
1417
+
1418
+ // Hide non-matching element nodes
1419
+ const elementNodes = treeEl.querySelectorAll('[data-element-id]');
1420
+ for (const node of elementNodes) {
1421
+ const elementId = node.getAttribute('data-element-id');
1422
+ if (!matchingIds.has(elementId)) {
1423
+ node.classList.add('filter-hidden');
1424
+ }
1425
+ }
1426
+
1427
+ // Hide parent layer nodes if all their children are hidden
1428
+ const allNodes = treeEl.querySelectorAll('[data-node-id]');
1429
+ for (const node of allNodes) {
1430
+ // Only consider nodes that are NOT element nodes (i.e., layers/categories)
1431
+ if (!node.hasAttribute('data-element-id')) {
1432
+ // Check if this node has any visible children
1433
+ const childContainer = node.querySelector(':scope > div');
1434
+ if (childContainer) {
1435
+ const visibleChildren = Array.from(childContainer.children).filter(
1436
+ child => !child.classList.contains('filter-hidden')
1437
+ );
1438
+ if (visibleChildren.length === 0) {
1439
+ // All children hidden, hide this parent too
1440
+ node.classList.add('filter-hidden');
1441
+ }
1442
+ }
1443
+ }
1444
+ }
1445
+ }
1446
+
1447
+ /**
1448
+ * Filter change callback from FilterSystem.
1449
+ * Updates diagram highlighting and tree visibility.
1450
+ */
1451
+ _onFilterChange(result) {
1452
+ const { matchingIds, filterMode } = result;
1453
+
1454
+ // Update diagram highlighting
1455
+ // Model element ids, or null for no filter; applied per node by _passesFilter.
1456
+ this._elementFilter = (filterMode === 'both' || filterMode === 'diagram') ? matchingIds : null;
1457
+ this._applyFilterHighlighting();
1458
+
1459
+ // Update tree visibility
1460
+ if (filterMode === 'both' || filterMode === 'tree') {
1461
+ this._filterTreeNodes(matchingIds);
1462
+ } else {
1463
+ // Show all in tree
1464
+ this._filterTreeNodes(null);
1465
+ }
1466
+ }
1467
+
1468
+ /**
1469
+ * Export the model, with every view, as Archi's XML format.
1470
+ * Returns XML string ready to download.
1471
+ */
1472
+ exportArchimate() {
1473
+ return this.workspace.RenderEngine.exportArchiXML(...this.workspace.toArchiExport(this.model));
1474
+ }
1475
+
1476
+ /**
1477
+ * Export the model as Archi saves it: plain XML, or, when a view holds
1478
+ * pictures, a zip archive with them under images/ (toArchiFile).
1479
+ * @returns {{ data: string|Uint8Array, archive: boolean, mimeType: string }}
1480
+ */
1481
+ exportArchimateFile() {
1482
+ return this._format(ArchiFileFormat).export(this.model);
1483
+ }
1484
+
1485
+ /**
1486
+ * Export the model as an Open Group Exchange Format file (XML text), the
1487
+ * standard route to other ArchiMate tools. Its ArchiMate views are written;
1488
+ * canvases and sketches are not (the format has none).
1489
+ */
1490
+ exportExchange() {
1491
+ return this._format(ExchangeFileFormat).export(this.model).data;
1492
+ }
1493
+
1494
+ /**
1495
+ * Import an Archi file into the model, replacing it: XML text, or the
1496
+ * file's bytes (plain XML, or the zip archive Archi saves when there are
1497
+ * pictures).
1498
+ * @param {string|Uint8Array|ArrayBuffer} file
1499
+ */
1500
+ importArchimate(file) {
1501
+ this.replaceModel(this._format(ArchiFileFormat).import(file));
1502
+ }
1503
+
1504
+ /**
1505
+ * Import an Open Group Exchange Format file into the model, replacing it.
1506
+ * @param {string} xml
1507
+ */
1508
+ importExchange(xml) {
1509
+ this.replaceModel(this._format(ExchangeFileFormat).import(xml));
1510
+ }
1511
+
1512
+ /** Edit another model (an imported one) in place of this one, closing its views' tabs. */
1513
+ replaceModel(model) {
1514
+ for (const id of this.diagrams.openViewIds()) this._quietly(() => this.diagrams.closeTab(id));
1515
+ this._bindModel(model);
1516
+ this.filterSystem.model = this.model;
1517
+ this._renderTreePanel();
1518
+ if (this.currentView) this._showView(this.currentView);
1519
+ }
1520
+
1521
+ /**
1522
+ * Drop or paste image files onto the diagram; each is handed to
1523
+ * `deps.onImageDrop(engine, point, dataURL)`.
1524
+ */
1525
+ setupImageDragDrop() {
1526
+ this.enableImageDrop((dataURL, point) => this.deps?.onImageDrop?.(this.currentEngine, point, dataURL));
1527
+ }
1528
+
1529
+ /**
1530
+ * Wire up all control event listeners (version, viewpoint, distinct shapes, etc.).
1531
+ * Triggers render on change. Optional: provide custom handler functions.
1532
+ */
1533
+ wireControls(options = {}) {
1534
+ const {
1535
+ onVersionChange,
1536
+ onViewpointChange,
1537
+ onViewpointModeChange,
1538
+ onDistinctShapesChange,
1539
+ onShowDerivedChange,
1540
+ onManageViewpoints,
1541
+ onManageSpecializations,
1542
+ populateViewpointSelect,
1543
+ } = options;
1544
+
1545
+ // Version selector
1546
+ const versionEl = this.document.getElementById('archimate-version');
1547
+ if (versionEl) {
1548
+ versionEl.addEventListener('change', () => {
1549
+ if (onVersionChange) onVersionChange();
1550
+ this.render();
1551
+ });
1552
+ }
1553
+
1554
+ // Viewpoint selector and mode
1555
+ const viewpointEl = this.document.getElementById('archimate-viewpoint');
1556
+ const viewpointModeEl = this.document.getElementById('archimate-viewpoint-mode');
1557
+ if (viewpointEl) {
1558
+ viewpointEl.addEventListener('change', () => {
1559
+ if (onViewpointChange) onViewpointChange();
1560
+ this.render();
1561
+ });
1562
+ }
1563
+ if (viewpointModeEl) {
1564
+ viewpointModeEl.addEventListener('change', () => {
1565
+ if (onViewpointModeChange) onViewpointModeChange();
1566
+ this.render();
1567
+ });
1568
+ }
1569
+
1570
+ // Distinct shapes checkbox
1571
+ const distinctShapesEl = this.document.getElementById('archimate-distinct-shapes');
1572
+ if (distinctShapesEl) {
1573
+ distinctShapesEl.addEventListener('change', () => {
1574
+ if (onDistinctShapesChange) onDistinctShapesChange();
1575
+ this.render();
1576
+ });
1577
+ }
1578
+
1579
+ // Show derived relationships checkbox
1580
+ const showDerivedEl = this.document.getElementById('archimate-show-derived');
1581
+ if (showDerivedEl) {
1582
+ showDerivedEl.addEventListener('change', () => {
1583
+ if (onShowDerivedChange) onShowDerivedChange();
1584
+ this.render();
1585
+ });
1586
+ }
1587
+
1588
+ // Manage viewpoints dialog
1589
+ const manageVpEl = this.document.getElementById('archimate-manage-viewpoints');
1590
+ if (manageVpEl && onManageViewpoints) {
1591
+ manageVpEl.addEventListener('click', onManageViewpoints);
1592
+ }
1593
+
1594
+ // Manage specializations dialog
1595
+ const manageSpecEl = this.document.getElementById('archimate-manage-specializations');
1596
+ if (manageSpecEl && onManageSpecializations) {
1597
+ manageSpecEl.addEventListener('click', onManageSpecializations);
1598
+ }
1599
+
1600
+ // Settings dialog
1601
+ const settingsEl = this.document.getElementById('archimate-settings');
1602
+ if (settingsEl) {
1603
+ settingsEl.addEventListener('click', () => {
1604
+ this.workspace.showGlobalSettingsDialog(this.document, this.settingsManager, 'default');
1605
+ });
1606
+ }
1607
+
1608
+ // Filter controls
1609
+ const filterInput = this.document.getElementById('archimate-filter');
1610
+ const filterClearEl = this.document.getElementById('archimate-filter-clear');
1611
+ if (filterInput && this.currentEngine) {
1612
+ const applyFilter = () => {
1613
+ this.currentEngine.filterGraphNodes(filterInput.value);
1614
+ };
1615
+ filterInput.addEventListener('input', applyFilter);
1616
+ if (filterClearEl) {
1617
+ filterClearEl.addEventListener('click', () => {
1618
+ filterInput.value = '';
1619
+ applyFilter();
1620
+ });
1621
+ }
1622
+ }
1623
+ }
1624
+ }