@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.
- package/LICENSE +21 -0
- package/README.md +203 -2
- package/REFERENCE.md +2198 -0
- package/dist/renderengine-workspace.umd.js +46905 -0
- package/dist/renderengine.umd.js +42223 -0
- package/examples/archimate.html +66 -0
- package/examples/diagram-editor.html +51 -0
- package/package.json +65 -4
- package/src/BaseRenderer.js +57 -0
- package/src/CanvasRenderer.js +194 -0
- package/src/Mat4.js +111 -0
- package/src/OrbitCamera.js +57 -0
- package/src/RendererFactory.js +50 -0
- package/src/SVGRenderer.js +439 -0
- package/src/archimate.js +723 -0
- package/src/archimateArchiFormat.js +1693 -0
- package/src/archimateArchiTypes.js +72 -0
- package/src/archimateArchive.js +140 -0
- package/src/archimateCSV.js +127 -0
- package/src/archimateDrawio.js +182 -0
- package/src/archimateExchangeFormat.js +546 -0
- package/src/archimateRelationshipTable.js +93 -0
- package/src/archimateSpecializations.js +161 -0
- package/src/chartCatalog.js +589 -0
- package/src/charting/ArchimateDiagramEditor.js +1624 -0
- package/src/charting/ArchimateSpecializationSelector.js +134 -0
- package/src/charting/Axis.js +68 -0
- package/src/charting/CameraSeeder.js +30 -0
- package/src/charting/CatalogDialog.js +92 -0
- package/src/charting/Chart.js +37 -0
- package/src/charting/Chart3D.js +99 -0
- package/src/charting/ChartBox.js +80 -0
- package/src/charting/ChartData.js +27 -0
- package/src/charting/ChartRegistry.js +76 -0
- package/src/charting/CheckboxTreeSelector.js +304 -0
- package/src/charting/ColorField.js +107 -0
- package/src/charting/ColorPalette.js +88 -0
- package/src/charting/CommandHistory.js +176 -0
- package/src/charting/DataFilter.js +52 -0
- package/src/charting/Diagram.js +36 -0
- package/src/charting/Diagram3D.js +29 -0
- package/src/charting/DiagramClipboard.js +31 -0
- package/src/charting/DiagramEditor.js +3330 -0
- package/src/charting/DiagramEditorCommand.js +110 -0
- package/src/charting/DiagramEditorCommands.js +237 -0
- package/src/charting/DiagramEditorEdgeCommands.js +142 -0
- package/src/charting/DiagramEditorState.js +98 -0
- package/src/charting/DiagramRegistry.js +86 -0
- package/src/charting/DiagramTypeRegistry.js +326 -0
- package/src/charting/DiagramViewPropertiesDialog.js +46 -0
- package/src/charting/Edge.js +113 -0
- package/src/charting/ErrorBars.js +37 -0
- package/src/charting/FilterSystem.js +285 -0
- package/src/charting/FindReplaceDialog.js +139 -0
- package/src/charting/FormDialog.js +385 -0
- package/src/charting/Gauge.js +58 -0
- package/src/charting/Hierarchy.js +175 -0
- package/src/charting/HintPanel.js +86 -0
- package/src/charting/ImageSelector.js +148 -0
- package/src/charting/InteractionManager.js +725 -0
- package/src/charting/Legend.js +58 -0
- package/src/charting/MenuBar.js +62 -0
- package/src/charting/ModelFileFormat.js +115 -0
- package/src/charting/OrbitControls.js +77 -0
- package/src/charting/Palette.js +119 -0
- package/src/charting/PopupMenu.js +117 -0
- package/src/charting/PropertiesPanel.js +155 -0
- package/src/charting/PropertyListEditor.js +96 -0
- package/src/charting/PropertyLog.js +92 -0
- package/src/charting/SettingsCascade.js +93 -0
- package/src/charting/SettingsDialog.js +53 -0
- package/src/charting/SettingsManager.js +179 -0
- package/src/charting/SettingsViewPropertiesDialog.js +58 -0
- package/src/charting/ShapePaths.js +27 -0
- package/src/charting/Statistics.js +134 -0
- package/src/charting/TemplateCatalog.js +59 -0
- package/src/charting/TemplateLibrary.js +103 -0
- package/src/charting/TextStyle.js +168 -0
- package/src/charting/ViewFit.js +55 -0
- package/src/charting/ViewMenu.js +70 -0
- package/src/charting/ViewPropertiesDialog.js +77 -0
- package/src/charting/ViewTabStrip.js +81 -0
- package/src/charting/archimate/ArchiFileFormat.js +32 -0
- package/src/charting/archimate/ArchimateCanvasEditor.js +815 -0
- package/src/charting/archimate/ArchimateCsvFileFormat.js +35 -0
- package/src/charting/archimate/ArchimateEdge.js +137 -0
- package/src/charting/archimate/ArchimateElementTemplate.js +195 -0
- package/src/charting/archimate/ArchimatePalette.js +102 -0
- package/src/charting/archimate/CsvImportDialog.js +105 -0
- package/src/charting/archimate/CustomViewpoints.js +74 -0
- package/src/charting/archimate/DerivedEdge.js +29 -0
- package/src/charting/archimate/DrawioFileFormat.js +24 -0
- package/src/charting/archimate/ElementFilterRule.js +21 -0
- package/src/charting/archimate/ExchangeFileFormat.js +28 -0
- package/src/charting/archimate/ViewGroupTemplate.js +137 -0
- package/src/charting/archimate/ViewLineEdge.js +84 -0
- package/src/charting/archimate/ViewpointRule.js +45 -0
- package/src/charting/archimate/archimateFileFormats.js +12 -0
- package/src/charting/archimate/archimateNames.js +5 -0
- package/src/charting/archimate/csvMapping.js +122 -0
- package/src/charting/archimate/elementMenuItems.js +32 -0
- package/src/charting/archimate/elementProperties.js +146 -0
- package/src/charting/archimate/elementTypeChoices.js +7 -0
- package/src/charting/archimate/elementTypeGroups.js +19 -0
- package/src/charting/archimate/index.js +17 -0
- package/src/charting/archimate/specializationCatalogDialog.js +64 -0
- package/src/charting/archimate/typeGlyph.js +29 -0
- package/src/charting/arrangement/Arrangement.js +112 -0
- package/src/charting/arrangement/ArrangementCommand.js +29 -0
- package/src/charting/arrangement/BackgroundCommand.js +41 -0
- package/src/charting/arrangement/BackgroundGroup.js +18 -0
- package/src/charting/arrangement/GroupCommand.js +46 -0
- package/src/charting/arrangement/ItemGroup.js +62 -0
- package/src/charting/arrangement/LockCommand.js +32 -0
- package/src/charting/arrangement/UngroupCommand.js +27 -0
- package/src/charting/arrangement/alignment.js +88 -0
- package/src/charting/arrangement/arrangeMenuItems.js +25 -0
- package/src/charting/arrangement/index.js +11 -0
- package/src/charting/arrangement/layouts.js +233 -0
- package/src/charting/canvas/ArchiHint.js +78 -0
- package/src/charting/canvas/CanvasBoxKind.js +39 -0
- package/src/charting/canvas/CanvasPalette.js +49 -0
- package/src/charting/canvas/CanvasTemplateLibrary.js +61 -0
- package/src/charting/canvas/CanvasTemplateMenu.js +151 -0
- package/src/charting/canvas/EditHintCommand.js +28 -0
- package/src/charting/canvas/canvasBoxKinds.js +27 -0
- package/src/charting/canvas/canvasBoxMenuItems.js +40 -0
- package/src/charting/canvas/defaultCanvasTemplates.js +186 -0
- package/src/charting/canvas/diagramBoxKinds.js +12 -0
- package/src/charting/canvas/hintMenuItems.js +49 -0
- package/src/charting/charts/ArcGauge.js +55 -0
- package/src/charting/charts/AreaChart.js +71 -0
- package/src/charting/charts/BarChart.js +80 -0
- package/src/charting/charts/BarChart3D.js +56 -0
- package/src/charting/charts/BarChart3DOrbit.js +53 -0
- package/src/charting/charts/BarChartBase.js +70 -0
- package/src/charting/charts/BarGauge.js +42 -0
- package/src/charting/charts/BoxPlotChart.js +41 -0
- package/src/charting/charts/BubbleChart.js +99 -0
- package/src/charting/charts/BubbleChart3D.js +78 -0
- package/src/charting/charts/BulletGauge.js +87 -0
- package/src/charting/charts/CalendarHeatmapChart.js +204 -0
- package/src/charting/charts/CandlestickChart.js +54 -0
- package/src/charting/charts/ChordDiagram.js +130 -0
- package/src/charting/charts/DensityChart.js +73 -0
- package/src/charting/charts/DialGauge.js +65 -0
- package/src/charting/charts/DonutChart.js +15 -0
- package/src/charting/charts/FunnelChart.js +47 -0
- package/src/charting/charts/GanttChart.js +201 -0
- package/src/charting/charts/GaugeChart.js +175 -0
- package/src/charting/charts/GeoMapChart.js +146 -0
- package/src/charting/charts/GroupedBarChart.js +88 -0
- package/src/charting/charts/GroupedDistributionChart.js +70 -0
- package/src/charting/charts/HeatmapChart.js +80 -0
- package/src/charting/charts/HierarchyChart.js +39 -0
- package/src/charting/charts/HistogramChart.js +45 -0
- package/src/charting/charts/KpiTile.js +72 -0
- package/src/charting/charts/LagPlotChart.js +41 -0
- package/src/charting/charts/LineChart.js +124 -0
- package/src/charting/charts/MultiRingGauge.js +54 -0
- package/src/charting/charts/OutlineChart.js +95 -0
- package/src/charting/charts/ParallelCoordinatesChart.js +130 -0
- package/src/charting/charts/PieChart.js +56 -0
- package/src/charting/charts/PipeChart.js +88 -0
- package/src/charting/charts/PopulationPyramidChart.js +106 -0
- package/src/charting/charts/QQPlotChart.js +64 -0
- package/src/charting/charts/RadarChart.js +76 -0
- package/src/charting/charts/RadialBarChart.js +96 -0
- package/src/charting/charts/RingGauge.js +23 -0
- package/src/charting/charts/SankeyDiagram.js +174 -0
- package/src/charting/charts/ScatterChart.js +104 -0
- package/src/charting/charts/StackedAreaChart.js +15 -0
- package/src/charting/charts/StackedBarChart.js +33 -0
- package/src/charting/charts/StackedBarChart3DOrbit.js +58 -0
- package/src/charting/charts/StreamGraphChart.js +131 -0
- package/src/charting/charts/SunburstChart.js +57 -0
- package/src/charting/charts/ThermometerGauge.js +43 -0
- package/src/charting/charts/TreemapChart.js +54 -0
- package/src/charting/charts/ViolinChart.js +37 -0
- package/src/charting/charts/WaterfallChart.js +66 -0
- package/src/charting/charts/WordCloudChart.js +104 -0
- package/src/charting/diagramModel/AddViewCommand.js +33 -0
- package/src/charting/diagramModel/DiagramModel.js +39 -0
- package/src/charting/diagramModel/DiagramModelSearch.js +22 -0
- package/src/charting/diagramModel/DiagramView.js +50 -0
- package/src/charting/diagramModel/ModelHistory.js +43 -0
- package/src/charting/diagramModel/ModelReport.js +153 -0
- package/src/charting/diagramModel/ModelSearch.js +223 -0
- package/src/charting/diagramModel/RemoveViewCommand.js +25 -0
- package/src/charting/diagramModel/RenameViewCommand.js +21 -0
- package/src/charting/diagramModel/ReplaceTextCommand.js +25 -0
- package/src/charting/diagramModel/ViewSetChecker.js +34 -0
- package/src/charting/diagramModel/ViewSetModel.js +243 -0
- package/src/charting/diagramModel/index.js +6 -0
- package/src/charting/diagramModel/nameTransforms.js +29 -0
- package/src/charting/diagrams/ArchimateCommands.js +141 -0
- package/src/charting/diagrams/ArchimateDiagram.js +263 -0
- package/src/charting/diagrams/ArchimateModel.js +1155 -0
- package/src/charting/diagrams/ArchimateModelChecker.js +125 -0
- package/src/charting/diagrams/ArchimateModelReport.js +128 -0
- package/src/charting/diagrams/ArchimateModelSearch.js +41 -0
- package/src/charting/diagrams/ArchimateValidator.js +357 -0
- package/src/charting/diagrams/CanvasView.js +112 -0
- package/src/charting/diagrams/ContextMenu.js +37 -0
- package/src/charting/diagrams/DataModelDiagram.js +86 -0
- package/src/charting/diagrams/DataModelRelationshipStrategy.js +38 -0
- package/src/charting/diagrams/DerivedRelationships.js +102 -0
- package/src/charting/diagrams/EbnfDiagram.js +39 -0
- package/src/charting/diagrams/EdgeGeometry.js +553 -0
- package/src/charting/diagrams/EditViewItemCommand.js +128 -0
- package/src/charting/diagrams/ElementInstance.js +157 -0
- package/src/charting/diagrams/GraphDiagram.js +1340 -0
- package/src/charting/diagrams/GraphDiagram3D.js +152 -0
- package/src/charting/diagrams/GraphMetrics.js +81 -0
- package/src/charting/diagrams/GridOverlay.js +137 -0
- package/src/charting/diagrams/ImportRowsCommand.js +62 -0
- package/src/charting/diagrams/LabelHelper.js +108 -0
- package/src/charting/diagrams/MergeElementsCommand.js +101 -0
- package/src/charting/diagrams/NestInstanceCommand.js +34 -0
- package/src/charting/diagrams/NodeGeometry.js +158 -0
- package/src/charting/diagrams/PlaceItemsCommand.js +77 -0
- package/src/charting/diagrams/RadialLayout.js +101 -0
- package/src/charting/diagrams/RelationshipInstance.js +93 -0
- package/src/charting/diagrams/RemoveGroupFromViewCommand.js +28 -0
- package/src/charting/diagrams/RemoveLineCommand.js +27 -0
- package/src/charting/diagrams/ShowElementCommand.js +43 -0
- package/src/charting/diagrams/SvgCoordinate.js +17 -0
- package/src/charting/diagrams/SvgZoom.js +127 -0
- package/src/charting/diagrams/Tooltip.js +62 -0
- package/src/charting/diagrams/ViewConnectionInstance.js +102 -0
- package/src/charting/diagrams/ViewObjectInstance.js +56 -0
- package/src/charting/diagrams/commands/AddEdgeCommand.js +34 -0
- package/src/charting/diagrams/commands/AddNodeCommand.js +28 -0
- package/src/charting/diagrams/commands/AddWaypointCommand.js +27 -0
- package/src/charting/diagrams/commands/CreateFolderCommand.js +27 -0
- package/src/charting/diagrams/commands/DeleteEdgeCommand.js +30 -0
- package/src/charting/diagrams/commands/DeleteFolderCommand.js +47 -0
- package/src/charting/diagrams/commands/DeleteNodeCommand.js +35 -0
- package/src/charting/diagrams/commands/DeleteWaypointCommand.js +24 -0
- package/src/charting/diagrams/commands/GraphCommand.js +65 -0
- package/src/charting/diagrams/commands/InvertEdgeCommand.js +24 -0
- package/src/charting/diagrams/commands/MoveNodeCommand.js +44 -0
- package/src/charting/diagrams/commands/MoveWaypointCommand.js +27 -0
- package/src/charting/diagrams/commands/RenameEdgeCommand.js +23 -0
- package/src/charting/diagrams/commands/RenameFolderCommand.js +31 -0
- package/src/charting/diagrams/commands/RenameNodeCommand.js +27 -0
- package/src/charting/diagrams/commands/ResizeNodeCommand.js +46 -0
- package/src/charting/diagrams/commands/UpdateEdgeSideCommand.js +40 -0
- package/src/charting/diagrams/commands/index.js +17 -0
- package/src/charting/diagrams/duplicates.js +49 -0
- package/src/charting/diagrams/nesting.js +30 -0
- package/src/charting/diagrams/roadmap.js +77 -0
- package/src/charting/edgeMarkers/ArrowMarker.js +15 -0
- package/src/charting/edgeMarkers/CircleMarker.js +13 -0
- package/src/charting/edgeMarkers/CrowsFootMarker.js +16 -0
- package/src/charting/edgeMarkers/DiamondMarker.js +15 -0
- package/src/charting/edgeMarkers/EdgeMarker.js +34 -0
- package/src/charting/edgeMarkers/HalfArrowMarker.js +8 -0
- package/src/charting/edgeMarkers/HollowDiamondMarker.js +18 -0
- package/src/charting/edgeMarkers/HollowTriangleMarker.js +18 -0
- package/src/charting/edgeMarkers/OpenArrowMarker.js +17 -0
- package/src/charting/edgeMarkers/index.js +27 -0
- package/src/charting/edgeStyle/EdgeStyleCascade.js +22 -0
- package/src/charting/edgeStyle/EdgeStyleDialog.js +51 -0
- package/src/charting/edgeStyle/EdgeStyleSchema.js +43 -0
- package/src/charting/edgeStyle/SetStyleCommand.js +12 -0
- package/src/charting/edgeStyle/index.js +4 -0
- package/src/charting/emphasis/EmphasisRule.js +42 -0
- package/src/charting/emphasis/HighlightRule.js +24 -0
- package/src/charting/gauges/ArcTrack.js +51 -0
- package/src/charting/gauges/GaugeAnimation.js +51 -0
- package/src/charting/gauges/GaugeBands.js +48 -0
- package/src/charting/gauges/GaugeScale.js +87 -0
- package/src/charting/gauges/GaugeTrack.js +44 -0
- package/src/charting/gauges/GaugeValue.js +61 -0
- package/src/charting/gauges/HorizontalTrack.js +14 -0
- package/src/charting/gauges/LinearTrack.js +29 -0
- package/src/charting/gauges/Sparkline.js +42 -0
- package/src/charting/gauges/VerticalTrack.js +17 -0
- package/src/charting/glyphs/ArrowGlyph.js +21 -0
- package/src/charting/glyphs/BatteryGlyph.js +18 -0
- package/src/charting/glyphs/ChevronGlyph.js +18 -0
- package/src/charting/glyphs/CircleGlyph.js +15 -0
- package/src/charting/glyphs/CloudGlyph.js +19 -0
- package/src/charting/glyphs/ComponentGlyph.js +18 -0
- package/src/charting/glyphs/ConstraintGlyph.js +24 -0
- package/src/charting/glyphs/ContractBoxGlyph.js +10 -0
- package/src/charting/glyphs/CubeGlyph.js +19 -0
- package/src/charting/glyphs/CylinderGlyph.js +19 -0
- package/src/charting/glyphs/DashedDoubleArrowGlyph.js +11 -0
- package/src/charting/glyphs/DeviceGlyph.js +17 -0
- package/src/charting/glyphs/DiamondGlyph.js +17 -0
- package/src/charting/glyphs/DocumentGlyph.js +17 -0
- package/src/charting/glyphs/DoubleArrowGlyph.js +23 -0
- package/src/charting/glyphs/DoubleCircleGlyph.js +24 -0
- package/src/charting/glyphs/EllipseGlyph.js +18 -0
- package/src/charting/glyphs/FactoryGlyph.js +20 -0
- package/src/charting/glyphs/FlagGlyph.js +16 -0
- package/src/charting/glyphs/GapCircleGlyph.js +17 -0
- package/src/charting/glyphs/GearGlyph.js +37 -0
- package/src/charting/glyphs/Glyph.js +98 -0
- package/src/charting/glyphs/GroupingBoxGlyph.js +13 -0
- package/src/charting/glyphs/HalfCircleGlyph.js +22 -0
- package/src/charting/glyphs/HexagonGlyph.js +28 -0
- package/src/charting/glyphs/IconGlyph.js +22 -0
- package/src/charting/glyphs/ImageGlyph.js +16 -0
- package/src/charting/glyphs/JunctionGlyph.js +17 -0
- package/src/charting/glyphs/LollipopGlyph.js +22 -0
- package/src/charting/glyphs/MagnifierGlyph.js +17 -0
- package/src/charting/glyphs/NoteGlyph.js +33 -0
- package/src/charting/glyphs/ObjectBoxGlyph.js +21 -0
- package/src/charting/glyphs/OrJunctionGlyph.js +17 -0
- package/src/charting/glyphs/ParallelogramGlyph.js +20 -0
- package/src/charting/glyphs/PersonGlyph.js +23 -0
- package/src/charting/glyphs/PillGlyph.js +23 -0
- package/src/charting/glyphs/PinGlyph.js +18 -0
- package/src/charting/glyphs/PrincipleGlyph.js +23 -0
- package/src/charting/glyphs/ProductBoxGlyph.js +16 -0
- package/src/charting/glyphs/RectGlyph.js +15 -0
- package/src/charting/glyphs/RoundedBoxGlyph.js +13 -0
- package/src/charting/glyphs/StackGlyph.js +14 -0
- package/src/charting/glyphs/StepsGlyph.js +20 -0
- package/src/charting/glyphs/StickFigureGlyph.js +32 -0
- package/src/charting/glyphs/SystemSoftwareBoxGlyph.js +16 -0
- package/src/charting/glyphs/TabbedBoxGlyph.js +37 -0
- package/src/charting/glyphs/TargetGlyph.js +23 -0
- package/src/charting/glyphs/WavyBoxGlyph.js +17 -0
- package/src/charting/glyphs/WheelGlyph.js +28 -0
- package/src/charting/glyphs/drawGlyph.js +28 -0
- package/src/charting/glyphs/glyphSvg.js +55 -0
- package/src/charting/glyphs/index.js +109 -0
- package/src/charting/modelTree/AnalysisPanel.js +159 -0
- package/src/charting/modelTree/DiagramTabs.js +217 -0
- package/src/charting/modelTree/ModelTree.js +164 -0
- package/src/charting/modelTree/ModelTreePanel.js +914 -0
- package/src/charting/modelTree/TreeBuilder.js +238 -0
- package/src/charting/modelTree/TreeModeRegistry.js +51 -0
- package/src/charting/modelTree/TreeViewMode.js +110 -0
- package/src/charting/modelTree/buildFilterTrees.js +138 -0
- package/src/charting/modelTree/modes/FlatTreeMode.js +46 -0
- package/src/charting/modelTree/modes/LayerGroupedTreeMode.js +62 -0
- package/src/charting/modelTree/modes/TypeGroupedTreeMode.js +60 -0
- package/src/charting/modelTree/treeLayers.js +11 -0
- package/src/charting/report/reportHtml.js +82 -0
- package/src/charting/report/reportViewer.js +118 -0
- package/src/charting/shapes/CircleShape.js +19 -0
- package/src/charting/shapes/PathShape.js +17 -0
- package/src/charting/shapes/RectShape.js +20 -0
- package/src/charting/shapes/Shape.js +20 -0
- package/src/charting/shapes/ShapeFactory.js +23 -0
- package/src/charting/storage/LocalJsonStore.js +43 -0
- package/src/charting/templates/ActorTemplate.js +36 -0
- package/src/charting/templates/BoxTemplate.js +27 -0
- package/src/charting/templates/CircleTemplate.js +18 -0
- package/src/charting/templates/ClassTemplate.js +20 -0
- package/src/charting/templates/CloudTemplate.js +43 -0
- package/src/charting/templates/ComponentTemplate.js +19 -0
- package/src/charting/templates/CylinderTemplate.js +30 -0
- package/src/charting/templates/DataStoreTemplate.js +16 -0
- package/src/charting/templates/DiamondTemplate.js +10 -0
- package/src/charting/templates/DocumentTemplate.js +32 -0
- package/src/charting/templates/EllipseTemplate.js +15 -0
- package/src/charting/templates/FinalStateTemplate.js +12 -0
- package/src/charting/templates/GlyphTemplate.js +47 -0
- package/src/charting/templates/HexagonTemplate.js +17 -0
- package/src/charting/templates/LifelineTemplate.js +20 -0
- package/src/charting/templates/ManualInputTemplate.js +12 -0
- package/src/charting/templates/NoteTemplate.js +23 -0
- package/src/charting/templates/OctagonTemplate.js +7 -0
- package/src/charting/templates/PackageTemplate.js +25 -0
- package/src/charting/templates/ParallelogramTemplate.js +11 -0
- package/src/charting/templates/PentagonTemplate.js +5 -0
- package/src/charting/templates/PolygonTemplate.js +18 -0
- package/src/charting/templates/PredefinedProcessTemplate.js +13 -0
- package/src/charting/templates/RectangleTemplate.js +28 -0
- package/src/charting/templates/RegularPolygonTemplate.js +21 -0
- package/src/charting/templates/RoundedRectangleTemplate.js +5 -0
- package/src/charting/templates/StadiumTemplate.js +8 -0
- package/src/charting/templates/StarTemplate.js +17 -0
- package/src/charting/templates/SwimlaneTemplate.js +18 -0
- package/src/charting/templates/Template.js +208 -0
- package/src/charting/templates/TrapezoidTemplate.js +11 -0
- package/src/charting/templates/TriangleTemplate.js +12 -0
- package/src/charting/templates/index.js +31 -0
- package/src/charting/templates/outline.js +59 -0
- package/src/charting/templates/svg.js +19 -0
- package/src/charting/textToShapes.js +61 -0
- package/src/charting/toolbar/ToolbarButton.js +38 -0
- package/src/charting/toolbar/ToolbarCheckbox.js +31 -0
- package/src/charting/toolbar/ToolbarItem.js +19 -0
- package/src/charting/toolbar/ToolbarMenu.js +36 -0
- package/src/charting/toolbar/ToolbarSelect.js +38 -0
- package/src/charting/toolbar/ToolbarSeparator.js +7 -0
- package/src/charting/toolbar/ToolbarText.js +22 -0
- package/src/charting/toolbar/index.js +8 -0
- package/src/charting/toolbar/pickFile.js +14 -0
- package/src/charting/viewDecorations.js +85 -0
- package/src/charting/zOrder.js +57 -0
- package/src/csvTable.js +61 -0
- package/src/datamodel.js +122 -0
- package/src/datawindow/BoundedWindow.js +36 -0
- package/src/datawindow/FlatDataWindow.js +34 -0
- package/src/datawindow/GraphDataWindow.js +40 -0
- package/src/datawindow/GroupedSampleDataWindow.js +16 -0
- package/src/datawindow/SeriesDataWindow.js +42 -0
- package/src/datawindow/index.js +31 -0
- package/src/diagramCatalog.js +30 -0
- package/src/ebnfDiagram.js +57 -0
- package/src/ebnfParser.js +129 -0
- package/src/exportImage.js +233 -0
- package/src/index.js +2636 -0
- package/src/itIcons.js +84 -0
- package/src/kernelExpr.js +243 -0
- package/src/propertiesDialog.js +463 -0
- package/src/railroadDiagram.js +571 -0
- package/src/treeModeCatalog.js +16 -0
- package/src/workspace/ContextBus.js +79 -0
- package/src/workspace/EditorShell.js +67 -0
- package/src/workspace/Part.js +163 -0
- package/src/workspace/PartFactory.js +49 -0
- package/src/workspace/PartRegistry.js +86 -0
- package/src/workspace/Workspace.js +321 -0
- package/src/workspace/archimateServices.js +33 -0
- package/src/workspace/dom.js +120 -0
- package/src/workspace/index.js +75 -0
- package/src/workspace/layout/GridNode.js +280 -0
- package/src/workspace/layout/LayoutNode.js +89 -0
- package/src/workspace/layout/PartNode.js +77 -0
- package/src/workspace/layout/SplitNode.js +114 -0
- package/src/workspace/layout/TabsNode.js +191 -0
- package/src/workspace/layout/index.js +24 -0
- package/src/workspace/parts/AdapterPart.js +33 -0
- package/src/workspace/parts/ArchimateCanvasPart.js +345 -0
- package/src/workspace/parts/ArchimateEditorPart.js +79 -0
- package/src/workspace/parts/ChartPart.js +115 -0
- package/src/workspace/parts/ContentPart.js +34 -0
- package/src/workspace/parts/DetailPart.js +46 -0
- package/src/workspace/parts/DiagramEditorPart.js +135 -0
- package/src/workspace/parts/EditorPart.js +388 -0
- package/src/workspace/parts/HtmlPart.js +25 -0
- package/src/workspace/parts/ListPart.js +57 -0
- package/src/workspace/parts/ModelFile.js +187 -0
- package/src/workspace/parts/ModelsPart.js +135 -0
- package/src/workspace/styles.js +258 -0
- package/src/workspace/themes.js +96 -0
- package/src/workspace/workArea.js +413 -0
- package/src/workspace/workAreaModel.js +197 -0
- package/src/workspace.js +16 -0
- package/src/zip/ZipArchive.js +160 -0
- package/src/zip/crc32.js +25 -0
- 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
|
+
}
|