@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,1155 @@
|
|
|
1
|
+
import { ElementInstance } from './ElementInstance.js';
|
|
2
|
+
import { ViewObjectInstance, instanceFromJSON } from './ViewObjectInstance.js';
|
|
3
|
+
import { RelationshipInstance } from './RelationshipInstance.js';
|
|
4
|
+
import { ViewConnectionInstance } from './ViewConnectionInstance.js';
|
|
5
|
+
import { DiagramEditorState } from '../DiagramEditorState.js';
|
|
6
|
+
import { ViewSetModel } from '../diagramModel/ViewSetModel.js';
|
|
7
|
+
import { ArchimateModelChecker } from './ArchimateModelChecker.js';
|
|
8
|
+
import { ArchimateModelReport } from './ArchimateModelReport.js';
|
|
9
|
+
import { ArchimateModelSearch } from './ArchimateModelSearch.js';
|
|
10
|
+
import { MergeElementsCommand } from './MergeElementsCommand.js';
|
|
11
|
+
import { roadmapPlan, parseDate } from './roadmap.js';
|
|
12
|
+
import { PlaceItemsCommand } from './PlaceItemsCommand.js';
|
|
13
|
+
import { DIAGRAM_BOX_KINDS } from '../canvas/diagramBoxKinds.js';
|
|
14
|
+
import { RemoveLineCommand } from './RemoveLineCommand.js';
|
|
15
|
+
import { CompositeCommand } from '../CommandHistory.js';
|
|
16
|
+
import { AddToViewCommand, DeleteElementCommand, AddRelationshipToViewCommand, RemoveRelationshipFromViewCommand } from './ArchimateCommands.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Canonical, id-indexed home for ArchiMate model data (elements, relationships, views).
|
|
20
|
+
* Replaces ad-hoc flat-array handling and fixes multi-view import flattening.
|
|
21
|
+
*
|
|
22
|
+
* ARCHITECTURE: Model layer is view-agnostic. Elements and relationships have NO x/y/labelOverride/
|
|
23
|
+
* or visibility properties. Those belong in ElementInstance/RelationshipInstance (view layer).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export class ArchimateModel extends ViewSetModel {
|
|
27
|
+
constructor({ id = undefined, name = '', elements = [], relationships = [], folders = [], elementFolders = [], views = [], documentation = undefined, properties = [], archi = null } = {}) {
|
|
28
|
+
// Views (create, duplicate, rename, delete, switch, change listeners) come from ViewSetModel.
|
|
29
|
+
super({ idPrefix: 'view', viewNoun: 'View' });
|
|
30
|
+
this.name = name;
|
|
31
|
+
this._elements = new Map(); // id -> element object
|
|
32
|
+
this._relationships = new Map(); // id -> relationship object
|
|
33
|
+
this._folders = new Map(); // id -> folder object {id, name, documentation, parentId, children[]}
|
|
34
|
+
/** The model's id (an Archi file's, kept on export); undefined for a new model. */
|
|
35
|
+
this.id = id;
|
|
36
|
+
/** The model's own documentation (Archi's "purpose") and properties ([{key, value}], keys may repeat). */
|
|
37
|
+
this.documentation = documentation;
|
|
38
|
+
this.properties = properties.map(p => ({ ...p }));
|
|
39
|
+
/** What an Archi file said about the model that it does not use (profiles, ...), written back on export. */
|
|
40
|
+
this.archi = archi;
|
|
41
|
+
this._elementFolder = new Map(); // elementId -> folderId (which folder contains this element)
|
|
42
|
+
|
|
43
|
+
// Ingest elements
|
|
44
|
+
for (const el of elements) {
|
|
45
|
+
if (!el.id) throw new TypeError('Element must have an id');
|
|
46
|
+
this._validateElementNoViewProperties(el);
|
|
47
|
+
this._elements.set(el.id, { ...el });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Ingest relationships; auto-assign id if missing
|
|
51
|
+
for (let i = 0; i < relationships.length; i++) {
|
|
52
|
+
const rel = relationships[i];
|
|
53
|
+
const id = rel.id ?? `rel-${i}`;
|
|
54
|
+
if (this._relationships.has(id)) {
|
|
55
|
+
throw new TypeError(`Duplicate relationship id: "${id}"`);
|
|
56
|
+
}
|
|
57
|
+
this._relationships.set(id, { ...rel, id });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Ingest folders (top-level only; hierarchy maintained in folder.children)
|
|
61
|
+
for (const folder of folders) {
|
|
62
|
+
if (!folder.id) throw new TypeError('Folder must have an id');
|
|
63
|
+
this._folders.set(folder.id, { ...folder });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Ingest element-to-folder mappings
|
|
67
|
+
for (const [elementId, folderId] of elementFolders) {
|
|
68
|
+
this._elementFolder.set(elementId, folderId);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Ingest views; the first is current.
|
|
72
|
+
for (const viewSpec of views) {
|
|
73
|
+
const view = this._adopt(this._createView(viewSpec));
|
|
74
|
+
this._views.set(view.id, view);
|
|
75
|
+
}
|
|
76
|
+
this.currentViewId = views.length ? this.views()[0].id : null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** A new view starts empty: elements are added to it, not inherited. */
|
|
80
|
+
_newView(id, title) {
|
|
81
|
+
return new ArchimateView(this, { id, name: title, elementIds: [] });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The view for a spec: the class registered for its Archi view type
|
|
86
|
+
* (registerViewType: CanvasView for 'canvas-model'), else an ArchimateView.
|
|
87
|
+
* @protected
|
|
88
|
+
*/
|
|
89
|
+
_createView(spec) {
|
|
90
|
+
const ViewClass = ArchimateModel.VIEW_TYPES.get(spec?.archiViewType) ?? ArchimateView;
|
|
91
|
+
return new ViewClass(this, spec);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** A view class for an Archi view type ('canvas-model', ...). A subclass of ArchimateView registers itself. */
|
|
95
|
+
static registerViewType(archiViewType, ViewClass) {
|
|
96
|
+
ArchimateModel.VIEW_TYPES.set(archiViewType, ViewClass);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Canvas views that are not templates, in order. */
|
|
100
|
+
canvases() {
|
|
101
|
+
return this.views().filter(v => v.constructor.kind === 'canvas' && !v.template);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** The model's own templates (canvas views kept to copy from), in order. */
|
|
105
|
+
templates() {
|
|
106
|
+
return this.views().filter(v => v.template);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** ArchiMate (and sketch) views: every view that is not a canvas or a template. */
|
|
110
|
+
diagramViews() {
|
|
111
|
+
return this.views().filter(v => v.constructor.kind !== 'canvas' && !v.template);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A new canvas made from a template: a model template (a view) or a library
|
|
116
|
+
* entry's `view` (CanvasView JSON). It gets new ids and the template's boxes and
|
|
117
|
+
* lines; the boxes are its background (one BackgroundGroup: behind everything
|
|
118
|
+
* added later, moved as one, locked until unlocked). It becomes the current view.
|
|
119
|
+
* @param {ArchimateView|Object} template - A template view, or CanvasView JSON
|
|
120
|
+
* @param {string} [title]
|
|
121
|
+
* @returns {ArchimateView}
|
|
122
|
+
*/
|
|
123
|
+
newCanvasFromTemplate(template, title) {
|
|
124
|
+
const canvas = this._canvasFrom(template, title);
|
|
125
|
+
// What the template draws is the canvas's background: one group, behind everything, locked.
|
|
126
|
+
canvas.arrangeAsBackground(canvas.elementInstances.keys(), { name: `${template.name ?? template.title ?? 'Template'} (template)` });
|
|
127
|
+
return this._add(canvas);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* A new view of some elements and what they are related to (Archi's
|
|
132
|
+
* "Generate View For"): the elements in the middle, each related element
|
|
133
|
+
* around them, and every relationship between what is shown (a view made
|
|
134
|
+
* from a layout draws them all). It becomes
|
|
135
|
+
* the current view.
|
|
136
|
+
* @param {Array<string>} elementIds
|
|
137
|
+
* @param {Object} [options]
|
|
138
|
+
* @param {string} [options.name] - Defaults to the first element's name
|
|
139
|
+
* @param {boolean} [options.related] - Bring in the elements they are related to (default true)
|
|
140
|
+
* @returns {ArchimateView|null} the view, or null if no element is known
|
|
141
|
+
*/
|
|
142
|
+
generateView(elementIds, { name, related = true } = {}) {
|
|
143
|
+
const focus = [...new Set(elementIds)].filter(id => this.getElement(id));
|
|
144
|
+
if (!focus.length) return null;
|
|
145
|
+
const neighbours = [];
|
|
146
|
+
if (related) {
|
|
147
|
+
const seen = new Set(focus);
|
|
148
|
+
for (const id of focus) {
|
|
149
|
+
const { incoming, outgoing } = this.relationshipsFor(id);
|
|
150
|
+
for (const r of [...incoming, ...outgoing]) {
|
|
151
|
+
const other = r.source === id ? r.target : r.source;
|
|
152
|
+
if (!seen.has(other) && this.getElement(other)) { seen.add(other); neighbours.push(other); }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// The focus in a row through the middle; the related ones on a circle around it.
|
|
157
|
+
const GAP = 180, centre = { x: 500, y: 400 };
|
|
158
|
+
const radius = Math.max(260, (focus.length * GAP) / 2 + 160, (neighbours.length * 150) / (2 * Math.PI));
|
|
159
|
+
const layout = {};
|
|
160
|
+
focus.forEach((id, i) => { layout[id] = { x: centre.x + (i - (focus.length - 1) / 2) * GAP, y: centre.y }; });
|
|
161
|
+
neighbours.forEach((id, i) => {
|
|
162
|
+
const angle = -Math.PI / 2 + (2 * Math.PI * i) / neighbours.length;
|
|
163
|
+
layout[id] = { x: Math.round(centre.x + radius * Math.cos(angle)), y: Math.round(centre.y + radius * Math.sin(angle)) };
|
|
164
|
+
});
|
|
165
|
+
const view = this._createView({
|
|
166
|
+
id: this._newId(),
|
|
167
|
+
name: name ?? `${this.getElement(focus[0]).name || 'Element'} (generated)`,
|
|
168
|
+
elementIds: [...focus, ...neighbours],
|
|
169
|
+
layout,
|
|
170
|
+
});
|
|
171
|
+
return this._add(view);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* A new view of elements on a time line (a roadmap): each element with a
|
|
176
|
+
* start and an end date (its `startProperty` and `endProperty` properties,
|
|
177
|
+
* ISO or day/month/year) a bar from one to the other, under a header of
|
|
178
|
+
* years and months; bars that overlap in time in lanes below each other.
|
|
179
|
+
* Elements without both dates (or ending before they start) are left out
|
|
180
|
+
* and listed. It becomes the current view; one undo step.
|
|
181
|
+
* @param {Array<string>} elementIds
|
|
182
|
+
* @param {Object} [options]
|
|
183
|
+
* @param {string} [options.startProperty] - Default 'Start Date'
|
|
184
|
+
* @param {string} [options.endProperty] - Default 'End Date'
|
|
185
|
+
* @param {string} [options.name] - The view's name
|
|
186
|
+
* @param {'short'|'full'} [options.monthNames]
|
|
187
|
+
* @returns {{view: (ArchimateView|null), skipped: Array<Object>}} the view (null when no element has dates), and the elements left out
|
|
188
|
+
*/
|
|
189
|
+
generateRoadmap(elementIds, { startProperty = 'Start Date', endProperty = 'End Date', name = 'Roadmap', monthNames = 'short' } = {}) {
|
|
190
|
+
const prop = (e, key) => (e.properties ?? []).find(p => (p.name ?? p.key) === key)?.value;
|
|
191
|
+
const elements = [...new Set(elementIds)].map(id => this.getElement(id)).filter(Boolean);
|
|
192
|
+
const items = elements.map(e => ({ id: e.id, element: e, start: parseDate(prop(e, startProperty)), end: parseDate(prop(e, endProperty)) }));
|
|
193
|
+
const dated = items.filter(i => i.start && i.end && i.end >= i.start);
|
|
194
|
+
const skipped = items.filter(i => !dated.includes(i)).map(i => i.element);
|
|
195
|
+
if (!dated.length) return { view: null, skipped };
|
|
196
|
+
const plan = roadmapPlan(dated, { monthNames, monthWidth: monthNames === 'full' ? 90 : 60 });
|
|
197
|
+
const view = this._createView({ id: this._newId(), name, elementIds: dated.map(i => i.id) });
|
|
198
|
+
const note = (key, label, box, extra = {}) => {
|
|
199
|
+
const inst = new ViewObjectInstance({ id: `${view.id}-${key}`, name: label, label, shape: 'note' }, box.x + box.width / 2, box.y + box.height / 2);
|
|
200
|
+
inst.size = { width: box.width, height: box.height };
|
|
201
|
+
inst.appearance = { fillColor: '#ffffff', textAlign: 'center', ...extra };
|
|
202
|
+
inst.zIndex = -1;
|
|
203
|
+
return inst;
|
|
204
|
+
};
|
|
205
|
+
view.elementInstances.clear();
|
|
206
|
+
plan.years.forEach((y, i) => { const n = note(`year-${i}`, y.label, y, { fontWeight: 'bold' }); view.elementInstances.set(n.id, n); });
|
|
207
|
+
plan.months.forEach((m, i) => {
|
|
208
|
+
const header = note(`month-${i}`, m.label, m);
|
|
209
|
+
const column = note(`column-${i}`, '', { x: m.x, y: m.y + m.height, width: m.width, height: plan.height - 2 * m.height });
|
|
210
|
+
column.appearance = { fillColor: i % 2 ? '#f5f5f5' : '#ffffff', strokeColor: '#dddddd' };
|
|
211
|
+
column.zIndex = -2;
|
|
212
|
+
view.elementInstances.set(header.id, header);
|
|
213
|
+
view.elementInstances.set(column.id, column);
|
|
214
|
+
});
|
|
215
|
+
for (const bar of plan.bars) {
|
|
216
|
+
const inst = new ElementInstance(bar.id, bar.x + bar.width / 2, bar.y + bar.height / 2);
|
|
217
|
+
inst.size = { width: bar.width, height: bar.height };
|
|
218
|
+
inst.appearance = { fontSize: 9 };
|
|
219
|
+
view.elementInstances.set(inst.id, inst);
|
|
220
|
+
}
|
|
221
|
+
view.relationshipInstances?.clear?.();
|
|
222
|
+
return { view: this._add(view), skipped };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* The checks this model is held to: the view-set ones and ArchiMate's own
|
|
227
|
+
* (ArchimateModelChecker).
|
|
228
|
+
* @param {{version?: string}} [options] - The ArchiMate version for the relationship check
|
|
229
|
+
*/
|
|
230
|
+
checker(options) {
|
|
231
|
+
return new ArchimateModelChecker(this, options);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Make elements that are one thing twice into one: `keepId` takes the
|
|
236
|
+
* others' relationships, boxes, missing documentation and properties
|
|
237
|
+
* (MergeElementsCommand, not yet executed; put it on `history`).
|
|
238
|
+
*/
|
|
239
|
+
mergeCommand(keepId, otherIds) {
|
|
240
|
+
return new MergeElementsCommand(this, keepId, otherIds);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Find and replace: the views, notes, elements and relationships (ArchimateModelSearch). */
|
|
244
|
+
search() {
|
|
245
|
+
return new ArchimateModelSearch(this);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** The model's report: its views, elements and relationships (ArchimateModelReport). */
|
|
249
|
+
report(options) {
|
|
250
|
+
return new ArchimateModelReport(this, options);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** A new, empty canvas; it becomes the current view. */
|
|
254
|
+
createCanvas(title = `Canvas ${this.canvases().length + 1}`) {
|
|
255
|
+
return this._add(this._createView({ id: this._newId(), name: title, archiViewType: 'canvas-model' }));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Keep a copy of a canvas as one of the model's templates. The current
|
|
260
|
+
* view stays as it is.
|
|
261
|
+
* @param {string} viewId - A canvas
|
|
262
|
+
* @param {string} [title] - Defaults to the canvas's name
|
|
263
|
+
* @param {{itemIds?: Iterable<string>}} [options] - Only these boxes (and the lines between them)
|
|
264
|
+
* @returns {ArchimateView|null} the template, or null if `viewId` is not a canvas
|
|
265
|
+
*/
|
|
266
|
+
saveAsTemplate(viewId, title, { itemIds = null } = {}) {
|
|
267
|
+
const view = this.getView(viewId);
|
|
268
|
+
if (view?.constructor.kind !== 'canvas') return null;
|
|
269
|
+
// Some of its boxes only (a selection, a group): their JSON, with the lines between them.
|
|
270
|
+
return this.addTemplate(itemIds ? view.toJSON({ itemIds }) : view, title ?? (itemIds ? `${view.name} (part)` : view.name));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* A model template copied from a canvas, a template, or a library entry's
|
|
275
|
+
* `view` (CanvasView JSON). The current view stays as it is.
|
|
276
|
+
* @param {ArchimateView|Object} source
|
|
277
|
+
* @param {string} [title]
|
|
278
|
+
* @returns {ArchimateView}
|
|
279
|
+
*/
|
|
280
|
+
addTemplate(source, title) {
|
|
281
|
+
const template = this._canvasFrom(source, title);
|
|
282
|
+
template.template = true;
|
|
283
|
+
return this._add(template, { current: false });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** A new canvas (not a template) copied from a view or from CanvasView JSON. @private */
|
|
287
|
+
_canvasFrom(source, title) {
|
|
288
|
+
const id = this._newId();
|
|
289
|
+
const name = title ?? source.name ?? source.title ?? 'Canvas';
|
|
290
|
+
return typeof source.copy === 'function'
|
|
291
|
+
? source.copy(id, name)
|
|
292
|
+
: ArchimateModel.VIEW_TYPES.get('canvas-model').fromJSON(this, source, { id, name });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Get element by id, or undefined. */
|
|
296
|
+
getElement(id) {
|
|
297
|
+
return this._elements.get(id);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Add an element to the model. Position and other per-view state belong on
|
|
302
|
+
* the ElementInstance, so they are not kept here.
|
|
303
|
+
* @returns {Object} the stored element
|
|
304
|
+
*/
|
|
305
|
+
addElement(el) {
|
|
306
|
+
if (!el?.id) throw new TypeError('Element must have an id');
|
|
307
|
+
if (this._elements.has(el.id)) throw new TypeError(`Duplicate element id: "${el.id}"`);
|
|
308
|
+
const { x, y, labelOverride, visible, diagramInstanceId, ...modelEl } = el;
|
|
309
|
+
this._elements.set(el.id, modelEl);
|
|
310
|
+
return modelEl;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Remove an element from the model (views keep any instances; they no longer resolve). */
|
|
314
|
+
removeElement(id) {
|
|
315
|
+
this._elements.delete(id);
|
|
316
|
+
this._elementFolder.delete(id);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Add a relationship to the model. @returns {Object} the stored relationship */
|
|
320
|
+
addRelationship(rel) {
|
|
321
|
+
if (!rel?.id) throw new TypeError('Relationship must have an id');
|
|
322
|
+
if (this._relationships.has(rel.id)) throw new TypeError(`Duplicate relationship id: "${rel.id}"`);
|
|
323
|
+
const stored = { ...rel };
|
|
324
|
+
this._relationships.set(rel.id, stored);
|
|
325
|
+
return stored;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Remove a relationship from the model (views keep any instances; they no longer resolve). */
|
|
329
|
+
removeRelationship(id) {
|
|
330
|
+
this._relationships.delete(id);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Get relationship by id, or undefined. */
|
|
334
|
+
getRelationship(id) {
|
|
335
|
+
return this._relationships.get(id);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Get folder by id, or undefined. */
|
|
339
|
+
getFolder(id) {
|
|
340
|
+
return this._folders.get(id);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Get the folder id containing an element, or undefined. */
|
|
344
|
+
getElementFolder(elementId) {
|
|
345
|
+
return this._elementFolder.get(elementId);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Set which folder contains an element. */
|
|
349
|
+
setElementFolder(elementId, folderId) {
|
|
350
|
+
if (folderId) {
|
|
351
|
+
this._elementFolder.set(elementId, folderId);
|
|
352
|
+
} else {
|
|
353
|
+
this._elementFolder.delete(elementId);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** Get all elements in a folder (direct children only, not recursive). */
|
|
358
|
+
getElementsInFolder(folderId) {
|
|
359
|
+
const result = [];
|
|
360
|
+
for (const [elementId, folderIdForElement] of this._elementFolder.entries()) {
|
|
361
|
+
if (folderIdForElement === folderId) {
|
|
362
|
+
const el = this._elements.get(elementId);
|
|
363
|
+
if (el) result.push(el);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return result;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** All elements, as a flat array. */
|
|
370
|
+
elements() {
|
|
371
|
+
return Array.from(this._elements.values());
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** All relationships, as a flat array. */
|
|
375
|
+
relationships() {
|
|
376
|
+
return Array.from(this._relationships.values());
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** Top-level folders only (children tracked within folder.children). */
|
|
380
|
+
folders() {
|
|
381
|
+
return Array.from(this._folders.values()).filter(f => !f.parentId);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** All folders including nested (flat array), each after its parent. Nesting
|
|
385
|
+
* is read from `parentId`, which every folder carries (`children` may not be kept). */
|
|
386
|
+
allFolders() {
|
|
387
|
+
const all = Array.from(this._folders.values());
|
|
388
|
+
const byParent = new Map();
|
|
389
|
+
for (const f of all) {
|
|
390
|
+
if (f.parentId) byParent.set(f.parentId, [...(byParent.get(f.parentId) ?? []), f]);
|
|
391
|
+
}
|
|
392
|
+
const result = [];
|
|
393
|
+
const walk = (folder) => {
|
|
394
|
+
result.push(folder);
|
|
395
|
+
(byParent.get(folder.id) ?? []).forEach(walk);
|
|
396
|
+
};
|
|
397
|
+
all.filter(f => !f.parentId || !this._folders.has(f.parentId)).forEach(walk);
|
|
398
|
+
return result;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Incoming and outgoing relationships for an element. */
|
|
402
|
+
relationshipsFor(elementId) {
|
|
403
|
+
const rels = Array.from(this._relationships.values());
|
|
404
|
+
return {
|
|
405
|
+
incoming: rels.filter(r => r.target === elementId),
|
|
406
|
+
outgoing: rels.filter(r => r.source === elementId),
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Validate that an element has no view-specific properties.
|
|
412
|
+
* View-specific state (position, label override, visibility) belongs in ElementInstance,
|
|
413
|
+
* not in base model elements.
|
|
414
|
+
* @private
|
|
415
|
+
*/
|
|
416
|
+
_validateElementNoViewProperties(el) {
|
|
417
|
+
const viewProps = ['x', 'y', 'labelOverride', 'visible', 'diagramInstanceId'];
|
|
418
|
+
for (const prop of viewProps) {
|
|
419
|
+
if (prop in el) {
|
|
420
|
+
console.warn(`WARNING: Element "${el.id}" has view-property "${prop}"; this should be in ElementInstance, not the model.`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/** Add a view from a spec (as the constructor takes them) and make it current. */
|
|
426
|
+
addView(viewSpec) {
|
|
427
|
+
return this._add(this._createView(viewSpec));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** Views that include a given element (or all views if elementId is null). */
|
|
431
|
+
viewsContaining(elementId) {
|
|
432
|
+
if (!elementId) return this.views();
|
|
433
|
+
return this.views().filter(v => v.includesElement(elementId));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** Convert from an Archi-format parsed model (with .elements, .relationships, .folders, .views). */
|
|
437
|
+
static fromArchiModel(archiModel, mappers = {}) {
|
|
438
|
+
const { mapElement = el => el, mapRelationship = rel => rel } = mappers;
|
|
439
|
+
const elements = (archiModel.elements || []).map(mapElement);
|
|
440
|
+
const relationships = (archiModel.relationships || []).map(mapRelationship);
|
|
441
|
+
const folders = archiModel.folders || [];
|
|
442
|
+
const elementFolders = archiModel.elementFolders || [];
|
|
443
|
+
const views = (archiModel.views || []).map(view => ({
|
|
444
|
+
id: view.id,
|
|
445
|
+
name: view.name,
|
|
446
|
+
elementIds: view.elementIds,
|
|
447
|
+
viewpoint: view.viewpoint,
|
|
448
|
+
viewpointMode: view.viewpointMode,
|
|
449
|
+
layout: view.layout || {},
|
|
450
|
+
edgeLayout: view.edgeLayout || {},
|
|
451
|
+
folderLayout: view.folderLayout || {},
|
|
452
|
+
diagramInstances: view.diagramInstances || [],
|
|
453
|
+
}));
|
|
454
|
+
return new ArchimateModel({ elements, relationships, folders, elementFolders, views });
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** View classes by Archi view type; see ArchimateModel.registerViewType. */
|
|
459
|
+
ArchimateModel.VIEW_TYPES = new Map();
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* New boxes like these, for a copied view: each gets a new id and loses its
|
|
463
|
+
* Archi id (Archi's ids are unique in a model), and a box drawn inside another
|
|
464
|
+
* stays inside that box's copy.
|
|
465
|
+
* @param {Array<Object>} items - Instances' JSON (toJSON), each with its `id`
|
|
466
|
+
* @returns {{ elementInstances: Map<string, Object>, instanceIds: Map<string, string> }}
|
|
467
|
+
* the copies by id, and each source id's copy id
|
|
468
|
+
*/
|
|
469
|
+
export function copyViewInstances(items) {
|
|
470
|
+
const instanceIds = new Map();
|
|
471
|
+
const elementInstances = new Map();
|
|
472
|
+
const copyIdOfArchiId = new Map();
|
|
473
|
+
for (const item of items) {
|
|
474
|
+
const twin = instanceFromJSON(JSON.parse(JSON.stringify(item)));
|
|
475
|
+
if (twin.archi?.id) {
|
|
476
|
+
copyIdOfArchiId.set(twin.archi.id, twin.id);
|
|
477
|
+
delete twin.archi.id;
|
|
478
|
+
}
|
|
479
|
+
instanceIds.set(item.id, twin.id);
|
|
480
|
+
elementInstances.set(twin.id, twin);
|
|
481
|
+
}
|
|
482
|
+
// A nested box names its parent's Archi id; the parent's copy is exported as diag-<its id>.
|
|
483
|
+
for (const twin of elementInstances.values()) {
|
|
484
|
+
const parent = twin.archi?.parent;
|
|
485
|
+
if (parent && copyIdOfArchiId.has(parent)) twin.archi.parent = `diag-${copyIdOfArchiId.get(parent)}`;
|
|
486
|
+
}
|
|
487
|
+
return { elementInstances, instanceIds };
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export class ArchimateView extends DiagramEditorState {
|
|
491
|
+
/** A counter for ids of boxes placed in this page (with the time, unique). */
|
|
492
|
+
static placed = 1;
|
|
493
|
+
|
|
494
|
+
/** Fields the view adds to what it hands the renderer (`elements`/`relationships`): bookkeeping, not properties. */
|
|
495
|
+
/** The kind of view: 'archimate' here, 'canvas' for a CanvasView. */
|
|
496
|
+
static kind = 'archimate';
|
|
497
|
+
|
|
498
|
+
/** The boxes this kind of view adds besides elements (CanvasBoxKind): Archi's Note and Image here. */
|
|
499
|
+
static BOX_KINDS = DIAGRAM_BOX_KINDS;
|
|
500
|
+
|
|
501
|
+
/** Archi's class for a plain line drawn on this kind of view. */
|
|
502
|
+
static LINE_TYPE = 'diagram-model-connection';
|
|
503
|
+
|
|
504
|
+
/** Lines drawn so far: part of each new line's id. @private */
|
|
505
|
+
static _lines = 1;
|
|
506
|
+
|
|
507
|
+
static PROJECTION_KEYS = ['elementId', 'relationshipId', 'diagramInstanceId', 'visible', 'zIndex', 'viewObject', 'locked', 'moveWith', 'resizable'];
|
|
508
|
+
|
|
509
|
+
constructor(model, { id = null, name = '', elementIds = null, viewpoint = null, viewpointMode = null, layout = {}, edgeLayout = {}, folderLayout = {}, diagramInstances = [], elementInstances = null, relationshipInstances = null, viewObjects = [], viewConnections = [], archiViewType = 'archimate-diagram-model', properties = [], archiFolder = undefined, archi = null, arrangement = null, documentation = undefined, settings = undefined } = {}) {
|
|
510
|
+
super({ arrangement });
|
|
511
|
+
if (!id) throw new TypeError('View must have an id');
|
|
512
|
+
this.model = model;
|
|
513
|
+
/** @type {Map<string, ElementInstance>} what this view draws, by instance id */
|
|
514
|
+
this.elementInstances = new Map();
|
|
515
|
+
/** @type {Map<string, RelationshipInstance>} */
|
|
516
|
+
this.relationshipInstances = new Map();
|
|
517
|
+
this.id = id;
|
|
518
|
+
this.name = name;
|
|
519
|
+
this.documentation = documentation;
|
|
520
|
+
// This view's level of the settings cascade (SettingsManager reads and writes it).
|
|
521
|
+
if (settings) this._settings = { ...settings };
|
|
522
|
+
this.elementIds = elementIds; // null = "all elements"
|
|
523
|
+
this.viewpoint = viewpoint;
|
|
524
|
+
this.viewpointMode = viewpointMode;
|
|
525
|
+
this.folderLayout = folderLayout; // { folderId -> {x,y,width,height,collapsed}, etc } — per-view folder bounds/state
|
|
526
|
+
|
|
527
|
+
// Migrate old formats to new instance-based approach
|
|
528
|
+
this._migrateToInstances(layout, edgeLayout, diagramInstances, elementInstances, relationshipInstances);
|
|
529
|
+
|
|
530
|
+
// Notes and images that belong to this view only ({content, x, y}, e.g. from an Archi import).
|
|
531
|
+
for (const { content, x = 0, y = 0 } of viewObjects) {
|
|
532
|
+
const inst = new ViewObjectInstance(content, x, y);
|
|
533
|
+
this.elementInstances.set(inst.id, inst);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** @type {Map<string, ViewConnectionInstance>} lines on this view with no relationship behind them */
|
|
537
|
+
this.viewConnections = new Map();
|
|
538
|
+
for (const c of viewConnections) {
|
|
539
|
+
const inst = c instanceof ViewConnectionInstance ? c : ViewConnectionInstance.fromJSON(c);
|
|
540
|
+
this.viewConnections.set(inst.id, inst);
|
|
541
|
+
}
|
|
542
|
+
/** Archi's class for the view ('archimate-diagram-model', 'canvas-model', 'sketch-model'), kept for export. */
|
|
543
|
+
this.archiViewType = archiViewType;
|
|
544
|
+
/** The view's properties, [{key, value}] (keys may repeat). */
|
|
545
|
+
this.properties = properties.map(p => ({ ...p }));
|
|
546
|
+
/** The Views sub-folders an Archi file kept it in ([{id, name, ...}]), kept for export. */
|
|
547
|
+
this.archiFolder = archiFolder;
|
|
548
|
+
/** What an Archi file said about the view that the model does not use (hintTitle, viewpoint, ...). */
|
|
549
|
+
this.archi = archi;
|
|
550
|
+
|
|
551
|
+
// Legacy property kept for backward compatibility (read-only)
|
|
552
|
+
this.layout = layout;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Migrate from old layout/diagramInstances format to new ElementInstance/RelationshipInstance collections.
|
|
557
|
+
* @private
|
|
558
|
+
*/
|
|
559
|
+
_migrateToInstances(layout, edgeLayout, diagramInstances, elementInstances, relationshipInstances) {
|
|
560
|
+
// If new instance maps provided, use them
|
|
561
|
+
if (elementInstances instanceof Map) {
|
|
562
|
+
this.elementInstances = new Map(elementInstances);
|
|
563
|
+
} else if (elementInstances && typeof elementInstances === 'object') {
|
|
564
|
+
// If array or object provided, convert to Map
|
|
565
|
+
if (Array.isArray(elementInstances)) {
|
|
566
|
+
for (const data of elementInstances) {
|
|
567
|
+
const inst = instanceFromJSON(data);
|
|
568
|
+
this.elementInstances.set(inst.id, inst);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Migrate old diagramInstances array
|
|
574
|
+
if (Array.isArray(diagramInstances) && diagramInstances.length > 0) {
|
|
575
|
+
for (const inst of diagramInstances) {
|
|
576
|
+
const elInst = new ElementInstance(inst.elementId, inst.x, inst.y, inst.labelOverride);
|
|
577
|
+
this.elementInstances.set(elInst.id, elInst);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Migrate old layout object (fallback if no instances yet)
|
|
582
|
+
if (layout && Object.keys(layout).length > 0 && this.elementInstances.size === 0) {
|
|
583
|
+
for (const [elementId, pos] of Object.entries(layout)) {
|
|
584
|
+
const inst = new ElementInstance(elementId, pos.x || 0, pos.y || 0);
|
|
585
|
+
this.elementInstances.set(inst.id, inst);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Migrate old edgeLayout to relationship instances
|
|
590
|
+
if (relationshipInstances instanceof Map) {
|
|
591
|
+
this.relationshipInstances = new Map(relationshipInstances);
|
|
592
|
+
} else if (relationshipInstances && typeof relationshipInstances === 'object') {
|
|
593
|
+
if (Array.isArray(relationshipInstances)) {
|
|
594
|
+
for (const data of relationshipInstances) {
|
|
595
|
+
const inst = RelationshipInstance.fromJSON(data);
|
|
596
|
+
this.relationshipInstances.set(inst.id, inst);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// A view given its instances (an import, a copy) draws exactly those, even
|
|
602
|
+
// none; otherwise it draws its filter's elements and the lines between them.
|
|
603
|
+
if (elementInstances == null && this.elementInstances.size === 0) {
|
|
604
|
+
this._createDefaultElementInstances();
|
|
605
|
+
}
|
|
606
|
+
if (relationshipInstances == null && this.relationshipInstances.size === 0) {
|
|
607
|
+
this._createDefaultRelationshipInstances();
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
this._applyEdgeLayout(edgeLayout);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* An imported `edgeLayout` ({relationshipId: {waypoints}}) gives a route to
|
|
615
|
+
* every instance of that relationship that has none of its own. An entry
|
|
616
|
+
* whose waypoints are absent means "straight", not "inherit".
|
|
617
|
+
* @private
|
|
618
|
+
*/
|
|
619
|
+
_applyEdgeLayout(edgeLayout = {}) {
|
|
620
|
+
for (const [relationshipId, entry] of Object.entries(edgeLayout ?? {})) {
|
|
621
|
+
if (!entry || !('waypoints' in entry)) continue;
|
|
622
|
+
for (const inst of this.relationshipInstancesFor(relationshipId)) {
|
|
623
|
+
if (inst.waypoints === null) inst.waypoints = (entry.waypoints ?? []).map(wp => ({ x: wp.x, y: wp.y }));
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Create default element instances for all elements in the view (on first creation).
|
|
630
|
+
* Uses layout if available, otherwise positions at 0,0.
|
|
631
|
+
* @private
|
|
632
|
+
*/
|
|
633
|
+
_createDefaultElementInstances() {
|
|
634
|
+
const elements = this.model.elements();
|
|
635
|
+
const filtered = this.elementIds !== null ? elements.filter(el => this.elementIds.includes(el.id)) : elements;
|
|
636
|
+
|
|
637
|
+
for (const el of filtered) {
|
|
638
|
+
// Don't overwrite existing instances - they contain user modifications!
|
|
639
|
+
// Only create if no instances exist for this element
|
|
640
|
+
const existingInstances = Array.from(this.elementInstances.values())
|
|
641
|
+
.filter(inst => inst.elementId === el.id);
|
|
642
|
+
if (existingInstances.length > 0) {
|
|
643
|
+
continue; // Skip - instances already exist for this element
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const layoutPos = (this.layout && this.layout[el.id]) || {};
|
|
647
|
+
const x = layoutPos.x !== undefined ? layoutPos.x : 0;
|
|
648
|
+
const y = layoutPos.y !== undefined ? layoutPos.y : 0;
|
|
649
|
+
const inst = new ElementInstance(el.id, x, y);
|
|
650
|
+
this.elementInstances.set(inst.id, inst);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Create default relationship instances for all relationships in the view (on first creation).
|
|
656
|
+
* Skips if view has no elements yet (new empty view) - relationships will be added via confirmation prompt.
|
|
657
|
+
* @private
|
|
658
|
+
*/
|
|
659
|
+
_createDefaultRelationshipInstances() {
|
|
660
|
+
const relationships = this.model.relationships();
|
|
661
|
+
for (const rel of relationships) {
|
|
662
|
+
if (this.includesElement(rel.source) && this.includesElement(rel.target)) {
|
|
663
|
+
const inst = new RelationshipInstance(rel.id);
|
|
664
|
+
this.relationshipInstances.set(inst.id, inst);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// ==================== Instance Management ====================
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Get element instance by ID.
|
|
673
|
+
* @param {string} instanceId - The stable diagram instance ID
|
|
674
|
+
* @returns {ElementInstance|undefined}
|
|
675
|
+
*/
|
|
676
|
+
getElementInstance(instanceId) {
|
|
677
|
+
return this.elementInstances.get(instanceId);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Get relationship instance by ID.
|
|
682
|
+
* @param {string} instanceId - The stable diagram instance ID
|
|
683
|
+
* @returns {RelationshipInstance|undefined}
|
|
684
|
+
*/
|
|
685
|
+
getRelationshipInstance(instanceId) {
|
|
686
|
+
return this.relationshipInstances.get(instanceId);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Add an element instance to this view.
|
|
691
|
+
* @param {ElementInstance} instance
|
|
692
|
+
*/
|
|
693
|
+
addElementInstance(instance) {
|
|
694
|
+
if (!(instance instanceof ElementInstance)) {
|
|
695
|
+
throw new TypeError('Expected ElementInstance');
|
|
696
|
+
}
|
|
697
|
+
this.elementInstances.set(instance.id, instance);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Remove an element instance from this view.
|
|
702
|
+
* @param {string} instanceId
|
|
703
|
+
*/
|
|
704
|
+
removeElementInstance(instanceId) {
|
|
705
|
+
this.elementInstances.delete(instanceId);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Add a relationship instance to this view.
|
|
710
|
+
* @param {RelationshipInstance} instance
|
|
711
|
+
*/
|
|
712
|
+
addRelationshipInstance(instance) {
|
|
713
|
+
if (!(instance instanceof RelationshipInstance)) {
|
|
714
|
+
throw new TypeError('Expected RelationshipInstance');
|
|
715
|
+
}
|
|
716
|
+
this.relationshipInstances.set(instance.id, instance);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Remove a relationship instance from this view.
|
|
721
|
+
* @param {string} instanceId
|
|
722
|
+
*/
|
|
723
|
+
removeRelationshipInstance(instanceId) {
|
|
724
|
+
this.relationshipInstances.delete(instanceId);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Get all instances of a specific model element in this view.
|
|
729
|
+
* @param {string} elementId - The model element ID
|
|
730
|
+
* @returns {ElementInstance[]}
|
|
731
|
+
*/
|
|
732
|
+
elementInstancesFor(elementId) {
|
|
733
|
+
return Array.from(this.elementInstances.values())
|
|
734
|
+
.filter(inst => inst.elementId === elementId);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Get all instances of a specific relationship in this view.
|
|
739
|
+
* @param {string} relationshipId - The model relationship ID
|
|
740
|
+
* @returns {RelationshipInstance[]}
|
|
741
|
+
*/
|
|
742
|
+
relationshipInstancesFor(relationshipId) {
|
|
743
|
+
return Array.from(this.relationshipInstances.values())
|
|
744
|
+
.filter(inst => inst.relationshipId === relationshipId);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Clear all element instances.
|
|
749
|
+
*/
|
|
750
|
+
clearElementInstances() {
|
|
751
|
+
this.elementInstances.clear();
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Clear all relationship instances.
|
|
756
|
+
*/
|
|
757
|
+
clearRelationshipInstances() {
|
|
758
|
+
this.relationshipInstances.clear();
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/** A view's title is its name (ViewSetModel works with titles). */
|
|
762
|
+
get title() { return this.name; }
|
|
763
|
+
set title(value) { this.name = value; }
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* An independent copy under a new id: every element and relationship
|
|
767
|
+
* instance is copied (new instance ids, relationships re-linked to the
|
|
768
|
+
* copied ends), with the element filter, viewpoint, documentation, view
|
|
769
|
+
* settings and folder layout.
|
|
770
|
+
* History starts empty.
|
|
771
|
+
*/
|
|
772
|
+
copy(id, title) {
|
|
773
|
+
const { elementInstances, instanceIds } = copyViewInstances([...this.elementInstances.values()].map(i => ({ ...i.toJSON(), id: i.id })));
|
|
774
|
+
const relink = (instanceId) => instanceId == null ? null : (instanceIds.get(instanceId) ?? instanceId);
|
|
775
|
+
const relationshipInstances = new Map();
|
|
776
|
+
for (const inst of this.relationshipInstances.values()) {
|
|
777
|
+
const data = JSON.parse(JSON.stringify(inst.toJSON()));
|
|
778
|
+
const twin = RelationshipInstance.fromJSON({ ...data, sourceInstanceId: relink(data.sourceInstanceId), targetInstanceId: relink(data.targetInstanceId) });
|
|
779
|
+
if (twin.archi) delete twin.archi.id;
|
|
780
|
+
relationshipInstances.set(twin.id, twin);
|
|
781
|
+
}
|
|
782
|
+
const viewConnections = [...this.viewConnections.values()].map(c => {
|
|
783
|
+
const twin = ViewConnectionInstance.fromJSON({
|
|
784
|
+
...JSON.parse(JSON.stringify(c.toJSON())),
|
|
785
|
+
id: `${c.id}-${id}`,
|
|
786
|
+
sourceInstanceId: relink(c.sourceInstanceId),
|
|
787
|
+
targetInstanceId: relink(c.targetInstanceId),
|
|
788
|
+
});
|
|
789
|
+
if (twin.archi) delete twin.archi.id;
|
|
790
|
+
return twin;
|
|
791
|
+
});
|
|
792
|
+
return new this.constructor(this.model, {
|
|
793
|
+
id,
|
|
794
|
+
name: title,
|
|
795
|
+
elementIds: this.elementIds ? [...this.elementIds] : null,
|
|
796
|
+
viewpoint: this.viewpoint,
|
|
797
|
+
viewpointMode: this.viewpointMode,
|
|
798
|
+
documentation: this.documentation,
|
|
799
|
+
settings: this._settings,
|
|
800
|
+
folderLayout: JSON.parse(JSON.stringify(this.folderLayout ?? {})),
|
|
801
|
+
elementInstances,
|
|
802
|
+
relationshipInstances,
|
|
803
|
+
viewConnections,
|
|
804
|
+
archiViewType: this.archiViewType,
|
|
805
|
+
properties: this.properties,
|
|
806
|
+
archiFolder: this.archiFolder,
|
|
807
|
+
archi: this.archi ? JSON.parse(JSON.stringify(this.archi)) : null,
|
|
808
|
+
arrangement: this.arrangement.copy(relink), // the copy's instances have new ids
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/** Element instances stack (relationships are drawn between them). */
|
|
813
|
+
layerItems() {
|
|
814
|
+
return Array.from(this.elementInstances.values());
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/** True if this view includes the element (null elementIds = all). */
|
|
818
|
+
includesElement(id) {
|
|
819
|
+
if (!this.elementIds) return true;
|
|
820
|
+
return this.elementIds.includes(id);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Whether a line drawn between two items here is a relationship (both ends
|
|
825
|
+
* show model elements) or a plain line (a note, a canvas box or an image at
|
|
826
|
+
* either end), as in Archi.
|
|
827
|
+
*/
|
|
828
|
+
linesAreRelationships(sourceId, targetId) {
|
|
829
|
+
const ends = [sourceId, targetId].map(id => this.getElementInstance(id));
|
|
830
|
+
return ends.every(inst => inst && !inst.content && this.model.getElement(inst.elementId));
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/** A new plain line between two items, of this view's line class (not yet on the view). */
|
|
834
|
+
newLine(sourceId, targetId, { label = '' } = {}) {
|
|
835
|
+
const id = `line-${Date.now().toString(36)}-${ArchimateView._lines++}`;
|
|
836
|
+
return new ViewConnectionInstance({ id, sourceInstanceId: sourceId, targetInstanceId: targetId, label, archiType: this.constructor.LINE_TYPE });
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/** Draw a plain line between two items: one undo step. @returns {ViewConnectionInstance} */
|
|
840
|
+
connect(sourceId, targetId, options = {}) {
|
|
841
|
+
const line = this.newLine(sourceId, targetId, options);
|
|
842
|
+
this.history.execute(new PlaceItemsCommand(this, { elementInstances: [], viewConnections: [line] }, { label: 'Connect' }));
|
|
843
|
+
return line;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/** Elements in this view, resolved from instances. Each instance is a fresh, independent projection. */
|
|
847
|
+
get elements() {
|
|
848
|
+
const result = [];
|
|
849
|
+
|
|
850
|
+
for (const instance of this.elementInstances.values()) {
|
|
851
|
+
// The element filter (null = all) applies to model elements, not view objects.
|
|
852
|
+
if (!instance.shownIn(this)) continue;
|
|
853
|
+
// A fresh copy each time: what the instance draws, placed for this view.
|
|
854
|
+
const viewElement = instance.project(this.model);
|
|
855
|
+
if (!viewElement) continue;
|
|
856
|
+
// Fixed in place (the view's arrangement): the renderer will not drag or resize it.
|
|
857
|
+
if (this.arrangement.isLocked(instance.id)) Object.assign(viewElement, { locked: true, resizable: false });
|
|
858
|
+
// In a group: the renderer drags its members with it.
|
|
859
|
+
const together = this.arrangement.movingWith(instance.id);
|
|
860
|
+
if (together.size > 1) viewElement.moveWith = [...together];
|
|
861
|
+
result.push(viewElement);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// Sort by zIndex so elements render in correct stacking order (lower zIndex first = background)
|
|
865
|
+
result.sort((a, b) => (a.zIndex || 0) - (b.zIndex || 0));
|
|
866
|
+
|
|
867
|
+
return result;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/** Relationships in this view, resolved from instances. Returns fresh copies to prevent mutations. */
|
|
871
|
+
get relationships() {
|
|
872
|
+
const result = [];
|
|
873
|
+
|
|
874
|
+
for (const instance of this.relationshipInstances.values()) {
|
|
875
|
+
const baseRel = this.model.getRelationship(instance.relationshipId);
|
|
876
|
+
if (!baseRel) {
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Skip if endpoints aren't in this view
|
|
881
|
+
if (!this.includesElement(baseRel.source) || !this.includesElement(baseRel.target)) {
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const viewRel = { ...baseRel };
|
|
886
|
+
viewRel.id = instance.id; // Use instance ID so rendering engine can track each relationship instance uniquely
|
|
887
|
+
viewRel.relationshipId = baseRel.id; // Keep base relationship ID for reference
|
|
888
|
+
|
|
889
|
+
// Update source/target to use instance IDs
|
|
890
|
+
// If this relationship instance tracks specific source/target instances, use those
|
|
891
|
+
if (instance.sourceInstanceId && instance.targetInstanceId) {
|
|
892
|
+
viewRel.source = instance.sourceInstanceId;
|
|
893
|
+
viewRel.target = instance.targetInstanceId;
|
|
894
|
+
} else {
|
|
895
|
+
// Fallback: use first instance of each element (for backward compatibility)
|
|
896
|
+
const sourceInstances = this.elementInstancesFor(baseRel.source);
|
|
897
|
+
const targetInstances = this.elementInstancesFor(baseRel.target);
|
|
898
|
+
if (sourceInstances.length > 0) {
|
|
899
|
+
viewRel.source = sourceInstances[0].id;
|
|
900
|
+
}
|
|
901
|
+
if (targetInstances.length > 0) {
|
|
902
|
+
viewRel.target = targetInstances[0].id;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// This instance's own route wins; null inherits the model's. A deliberately
|
|
907
|
+
// straight line ([]) reads as no waypoints at all.
|
|
908
|
+
if (instance.waypoints) {
|
|
909
|
+
viewRel.waypoints = instance.waypoints.length ? instance.waypoints.map(wp => ({ ...wp })) : undefined;
|
|
910
|
+
} else {
|
|
911
|
+
viewRel.waypoints = baseRel.waypoints ? baseRel.waypoints.map(wp => ({ ...wp })) : [];
|
|
912
|
+
}
|
|
913
|
+
if (instance.sourceSide) viewRel.sourceSide = instance.sourceSide;
|
|
914
|
+
if (instance.targetSide) viewRel.targetSide = instance.targetSide;
|
|
915
|
+
// This view's line colour, in the renderer's name for it.
|
|
916
|
+
if (instance.appearance?.strokeColor) viewRel.color = instance.appearance.strokeColor;
|
|
917
|
+
|
|
918
|
+
viewRel.visible = instance.visible;
|
|
919
|
+
viewRel.diagramInstanceId = instance.id;
|
|
920
|
+
result.push(viewRel);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Plain lines (no relationship behind them), drawn while both their boxes are.
|
|
924
|
+
for (const connection of this.viewConnections.values()) {
|
|
925
|
+
const edge = connection.project(this);
|
|
926
|
+
if (edge) result.push(edge);
|
|
927
|
+
}
|
|
928
|
+
return result;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/** Top-level folders in this view, with per-view folderLayout applied.
|
|
932
|
+
* Returns fresh copies with bounds/state from folderLayout. */
|
|
933
|
+
get folders() {
|
|
934
|
+
const topLevel = this.model.folders();
|
|
935
|
+
return topLevel.map(folder => {
|
|
936
|
+
const viewFolder = { ...folder };
|
|
937
|
+
// Apply per-view folder layout (bounds, collapsed state, etc.)
|
|
938
|
+
const folderOverride = this.folderLayout[folder.id];
|
|
939
|
+
if (folderOverride) {
|
|
940
|
+
Object.assign(viewFolder, folderOverride);
|
|
941
|
+
}
|
|
942
|
+
return viewFolder;
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* The folders drawn on this view as groups: those with bounds in its
|
|
948
|
+
* `folderLayout`, bounds applied, each after its parent. `parentId` is the
|
|
949
|
+
* nearest ancestor that is also drawn here (a group inside an undrawn
|
|
950
|
+
* folder sits on the view itself).
|
|
951
|
+
*/
|
|
952
|
+
get placedFolders() {
|
|
953
|
+
const placed = this.model.allFolders().filter(f => this.folderLayout[f.id]);
|
|
954
|
+
const drawn = new Set(placed.map(f => f.id));
|
|
955
|
+
return placed.map(folder => ({
|
|
956
|
+
...folder,
|
|
957
|
+
...this.folderLayout[folder.id],
|
|
958
|
+
parentId: this._nearestPlaced(folder.parentId, drawn),
|
|
959
|
+
}));
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/** The group on this view an element is drawn in: its folder, or that folder's nearest drawn ancestor. */
|
|
963
|
+
placedFolderOf(elementId) {
|
|
964
|
+
const drawn = new Set(this.model.allFolders().filter(f => this.folderLayout[f.id]).map(f => f.id));
|
|
965
|
+
return this._nearestPlaced(this.model.getElementFolder(elementId), drawn);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/** @private */
|
|
969
|
+
_nearestPlaced(folderId, drawn) {
|
|
970
|
+
for (let id = folderId; id; id = this.model.getFolder(id)?.parentId) {
|
|
971
|
+
if (drawn.has(id)) return id;
|
|
972
|
+
}
|
|
973
|
+
return undefined;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// ===== Undoable edits (this view's history) =====
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Everything needed to make this view's items again elsewhere: its boxes
|
|
980
|
+
* (with their ids), the lines and relationships drawn between them, their
|
|
981
|
+
* groups and locks, and the model elements and relationships they show (so
|
|
982
|
+
* another model can be given them); its documentation, properties and what
|
|
983
|
+
* Archi said besides. Plain JSON. A canvas template is this; so is what is
|
|
984
|
+
* copied to the clipboard.
|
|
985
|
+
* @param {Object} [options]
|
|
986
|
+
* @param {Iterable<string>} [options.itemIds] - Only these boxes (a selection): the lines
|
|
987
|
+
* between them, and their groups and locks
|
|
988
|
+
*/
|
|
989
|
+
toJSON({ itemIds = null } = {}) {
|
|
990
|
+
const copy = (value) => (value === undefined ? undefined : JSON.parse(JSON.stringify(value)));
|
|
991
|
+
const keep = itemIds ? new Set(itemIds) : null;
|
|
992
|
+
const kept = (id) => !keep || keep.has(id);
|
|
993
|
+
const boxes = [...this.elementInstances.values()].filter(i => kept(i.id));
|
|
994
|
+
// A line's ends: the boxes it names, or (none named) its elements' first boxes, as it is drawn.
|
|
995
|
+
const endsOf = (r) => {
|
|
996
|
+
const rel = this.model.getRelationship(r.relationshipId);
|
|
997
|
+
return [r.sourceInstanceId ?? this.elementInstancesFor(rel?.source)[0]?.id, r.targetInstanceId ?? this.elementInstancesFor(rel?.target)[0]?.id];
|
|
998
|
+
};
|
|
999
|
+
const lines = [...this.relationshipInstances.values()]
|
|
1000
|
+
.map(r => ({ r, ends: endsOf(r) }))
|
|
1001
|
+
.filter(({ ends: [from, to] }) => from && to && kept(from) && kept(to))
|
|
1002
|
+
.map(({ r, ends: [from, to] }) => Object.assign(RelationshipInstance.fromJSON(r.toJSON()), { sourceInstanceId: from, targetInstanceId: to }));
|
|
1003
|
+
const elementIds = new Set(boxes.map(b => b.elementId).filter(id => this.model.getElement(id)));
|
|
1004
|
+
return {
|
|
1005
|
+
name: this.name,
|
|
1006
|
+
documentation: this.documentation,
|
|
1007
|
+
properties: copy(this.properties),
|
|
1008
|
+
archi: copy(this.archi ?? undefined),
|
|
1009
|
+
elementInstances: boxes.map(i => ({ ...copy(i.toJSON()), id: i.id })),
|
|
1010
|
+
viewConnections: [...this.viewConnections.values()]
|
|
1011
|
+
.filter(c => kept(c.sourceInstanceId) && kept(c.targetInstanceId)).map(c => copy(c.toJSON())),
|
|
1012
|
+
relationshipInstances: lines.map(r => copy(r.toJSON())),
|
|
1013
|
+
elements: [...elementIds].map(id => copy(this.model.getElement(id))),
|
|
1014
|
+
relationships: [...new Set(lines.map(r => r.relationshipId))].map(id => this.model.getRelationship(id)).filter(Boolean).map(copy),
|
|
1015
|
+
arrangement: (keep ? this.arrangement.only(keep) : this.arrangement).toJSON(),
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* New boxes and lines like a view's JSON (toJSON): new ids, Archi's ids
|
|
1021
|
+
* left with the source, each line and relationship joined to the copies of
|
|
1022
|
+
* its ends.
|
|
1023
|
+
* @param {Object} data
|
|
1024
|
+
* @param {string} salt - Makes the lines' ids new
|
|
1025
|
+
* @returns {{ elementInstances: Map, viewConnections: Array, relationshipInstances: Array, mapId: Function }}
|
|
1026
|
+
*/
|
|
1027
|
+
static copyItems(data, salt) {
|
|
1028
|
+
const { elementInstances, instanceIds } = copyViewInstances(data.elementInstances ?? []);
|
|
1029
|
+
const mapId = (old) => instanceIds.get(old) ?? old;
|
|
1030
|
+
const viewConnections = (data.viewConnections ?? []).map(c => {
|
|
1031
|
+
const line = ViewConnectionInstance.fromJSON({ ...c, id: `${c.id}-${salt}`, sourceInstanceId: mapId(c.sourceInstanceId), targetInstanceId: mapId(c.targetInstanceId) });
|
|
1032
|
+
if (line.archi) delete line.archi.id;
|
|
1033
|
+
return line;
|
|
1034
|
+
});
|
|
1035
|
+
const relationshipInstances = (data.relationshipInstances ?? []).map(r => {
|
|
1036
|
+
const line = RelationshipInstance.fromJSON({ ...r, sourceInstanceId: mapId(r.sourceInstanceId), targetInstanceId: mapId(r.targetInstanceId) });
|
|
1037
|
+
if (line.archi) delete line.archi.id;
|
|
1038
|
+
return line;
|
|
1039
|
+
});
|
|
1040
|
+
return { elementInstances, viewConnections, relationshipInstances, mapId };
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* Put copies of a view's items (toJSON of a selection, from this view or
|
|
1045
|
+
* any other, of this model or another) on this view, moved by dx, dy: new
|
|
1046
|
+
* boxes for the same elements, the lines and relationships between them;
|
|
1047
|
+
* an element or relationship this model lacks is added with them. In front
|
|
1048
|
+
* of what is there. The command is returned, not run.
|
|
1049
|
+
* @param {Object} data
|
|
1050
|
+
* @param {{dx?: number, dy?: number, label?: string}} [options]
|
|
1051
|
+
* @returns {{ command: PlaceItemsCommand, ids: Array<string> }} the command, and the new boxes' ids
|
|
1052
|
+
*/
|
|
1053
|
+
pasteCommand(data, { dx = 0, dy = 0, label = 'Paste' } = {}) {
|
|
1054
|
+
const salt = `${this.id}-${Date.now().toString(36)}-${ArchimateView.placed++}`;
|
|
1055
|
+
const { elementInstances, viewConnections, relationshipInstances } = ArchimateView.copyItems(data, salt);
|
|
1056
|
+
const boxes = [...elementInstances.values()];
|
|
1057
|
+
const move = (points) => points?.map(p => ({ x: p.x + dx, y: p.y + dy })) ?? points;
|
|
1058
|
+
for (const b of boxes) { b.x += dx; b.y += dy; }
|
|
1059
|
+
for (const line of viewConnections) line.waypoints = move(line.waypoints) ?? [];
|
|
1060
|
+
for (const line of relationshipInstances) line.waypoints = move(line.waypoints) ?? null;
|
|
1061
|
+
const elements = (data.elements ?? []).filter(e => !this.model.getElement(e.id));
|
|
1062
|
+
const has = (id) => !!this.model.getElement(id) || elements.some(e => e.id === id);
|
|
1063
|
+
const relationships = (data.relationships ?? []).filter(r => !this.model.getRelationship(r.id) && has(r.source) && has(r.target));
|
|
1064
|
+
const known = (id) => !!this.model.getRelationship(id) || relationships.some(r => r.id === id);
|
|
1065
|
+
const command = new PlaceItemsCommand(this, {
|
|
1066
|
+
elementInstances: boxes, viewConnections, relationshipInstances: relationshipInstances.filter(r => known(r.relationshipId)),
|
|
1067
|
+
elements, relationships,
|
|
1068
|
+
}, { label });
|
|
1069
|
+
return { command, ids: boxes.map(b => b.id) };
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Add a box of one of this view's kinds (BOX_KINDS: a note or image here;
|
|
1075
|
+
* a canvas's block, sticky or image) centred at `at`: one undo step, in
|
|
1076
|
+
* front of what is there.
|
|
1077
|
+
* @param {string} kindKey
|
|
1078
|
+
* @param {{x: number, y: number}} at
|
|
1079
|
+
* @param {{name?: string, iconUrl?: string}} [options] - Its text, its picture
|
|
1080
|
+
* @returns {ViewObjectInstance}
|
|
1081
|
+
*/
|
|
1082
|
+
addBox(kindKey, at, options = {}) {
|
|
1083
|
+
const kind = this.constructor.BOX_KINDS.find(k => k.key === kindKey);
|
|
1084
|
+
if (!kind) throw new TypeError(`No box kind "${kindKey}" on a ${this.constructor.kind} view`);
|
|
1085
|
+
const id = `${kind.key}-${Date.now().toString(36)}-${ArchimateView.placed++}`;
|
|
1086
|
+
const box = new ViewObjectInstance(kind.content(id, options), at.x, at.y);
|
|
1087
|
+
box.size = { width: kind.width, height: kind.height };
|
|
1088
|
+
box.appearance = { ...kind.appearance };
|
|
1089
|
+
this.history.execute(new PlaceItemsCommand(this, { elementInstances: [box] }, { label: `Add ${kind.label}` }));
|
|
1090
|
+
return box;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Put an instance on this view, creating its model element first when
|
|
1096
|
+
* `element` is given. One undo step.
|
|
1097
|
+
* @param {ElementInstance} instance
|
|
1098
|
+
* @param {Object} [options]
|
|
1099
|
+
* @param {Object} [options.element] - A new model element the instance shows
|
|
1100
|
+
*/
|
|
1101
|
+
addToView(instance, { element = null } = {}) {
|
|
1102
|
+
this.history.execute(new AddToViewCommand(this, instance, { element }));
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/** Take an instance off this view (the model element stays). One undo step. */
|
|
1106
|
+
deleteElement(instanceId) {
|
|
1107
|
+
this.history.execute(new DeleteElementCommand(this, instanceId));
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* Relationship instances this view could show but does not: one per pairing
|
|
1112
|
+
* of an instance of the element with an instance at the other end of each of
|
|
1113
|
+
* its relationships, where both ends are in the view.
|
|
1114
|
+
* @param {string} elementId - Model element id
|
|
1115
|
+
* @returns {RelationshipInstance[]}
|
|
1116
|
+
*/
|
|
1117
|
+
missingRelationshipInstances(elementId) {
|
|
1118
|
+
const { incoming, outgoing } = this.model.relationshipsFor(elementId);
|
|
1119
|
+
const shown = new Set([...this.relationshipInstances.values()].map(inst => this._pairing(inst)));
|
|
1120
|
+
const missing = [];
|
|
1121
|
+
for (const rel of [...incoming, ...outgoing]) {
|
|
1122
|
+
if (!this.includesElement(rel.source) || !this.includesElement(rel.target)) continue;
|
|
1123
|
+
for (const src of this.elementInstancesFor(rel.source)) {
|
|
1124
|
+
for (const tgt of this.elementInstancesFor(rel.target)) {
|
|
1125
|
+
const inst = new RelationshipInstance(rel.id, null, src.id, tgt.id);
|
|
1126
|
+
if (!shown.has(this._pairing(inst))) {
|
|
1127
|
+
shown.add(this._pairing(inst));
|
|
1128
|
+
missing.push(inst);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
return missing;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/** "relationship|source instance|target instance" an instance draws between. @private */
|
|
1137
|
+
_pairing(inst) {
|
|
1138
|
+
const rel = this.model.getRelationship(inst.relationshipId);
|
|
1139
|
+
const source = inst.sourceInstanceId ?? this.elementInstancesFor(rel?.source)[0]?.id;
|
|
1140
|
+
const target = inst.targetInstanceId ?? this.elementInstancesFor(rel?.target)[0]?.id;
|
|
1141
|
+
return `${inst.relationshipId}|${source}|${target}`;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/** Show these relationship instances, as one undo step. */
|
|
1145
|
+
addRelationshipsToView(instances, label = 'Add Relationships to View') {
|
|
1146
|
+
if (!instances.length) return;
|
|
1147
|
+
this.history.execute(new CompositeCommand(label, instances.map(inst => new AddRelationshipToViewCommand(this, inst))));
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
/** Take a relationship instance, or a plain line, off this view (the model relationship stays). One undo step. */
|
|
1151
|
+
removeRelationship(instanceId) {
|
|
1152
|
+
if (this.viewConnections.has(instanceId)) this.history.execute(new RemoveLineCommand(this, instanceId));
|
|
1153
|
+
else this.history.execute(new RemoveRelationshipFromViewCommand(this, instanceId));
|
|
1154
|
+
}
|
|
1155
|
+
}
|