@peter.prib/renderengine 0.4.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +4 -0
  2. package/REFERENCE.md +267 -17
  3. package/dist/renderengine-workspace.umd.js +5289 -2895
  4. package/dist/renderengine.umd.js +4243 -2120
  5. package/examples/archimate.html +8 -28
  6. package/examples/data-model.html +45 -556
  7. package/package.json +70 -70
  8. package/src/archimateArchiFormat.js +11 -3
  9. package/src/archimateExchangeFormat.js +17 -9
  10. package/src/charting/ArchimateDiagramEditor.js +1 -1
  11. package/src/charting/Axis.js +20 -0
  12. package/src/charting/DiagramEditor.js +35 -3
  13. package/src/charting/Dialog.js +195 -0
  14. package/src/charting/DuplicatesDialog.js +108 -0
  15. package/src/charting/FormDialog.js +28 -97
  16. package/src/charting/InteractionManager.js +3 -0
  17. package/src/charting/Legend.js +45 -2
  18. package/src/charting/MergeCompareDialog.js +81 -0
  19. package/src/charting/PopupMenu.js +12 -3
  20. package/src/charting/SettingsDialog.js +6 -2
  21. package/src/charting/SettingsManager.js +14 -1
  22. package/src/charting/archimate/ArchimateCanvasEditor.js +190 -15
  23. package/src/charting/archimate/ArchimatePalette.js +1 -1
  24. package/src/charting/charts/AreaChart.js +5 -2
  25. package/src/charting/charts/BarChart.js +5 -3
  26. package/src/charting/charts/BarChart3DOrbit.js +5 -7
  27. package/src/charting/charts/BubbleChart.js +5 -7
  28. package/src/charting/charts/BubbleChart3D.js +5 -7
  29. package/src/charting/charts/CandlestickChart.js +3 -1
  30. package/src/charting/charts/DensityChart.js +4 -6
  31. package/src/charting/charts/GroupedBarChart.js +2 -2
  32. package/src/charting/charts/LineChart.js +5 -2
  33. package/src/charting/charts/ParallelCoordinatesChart.js +2 -2
  34. package/src/charting/charts/PipeChart.js +4 -6
  35. package/src/charting/charts/RadarChart.js +5 -6
  36. package/src/charting/charts/ScatterChart.js +5 -7
  37. package/src/charting/charts/StackedBarChart.js +4 -2
  38. package/src/charting/charts/StackedBarChart3DOrbit.js +5 -7
  39. package/src/charting/charts/StreamGraphChart.js +5 -2
  40. package/src/charting/charts/WaterfallChart.js +2 -1
  41. package/src/charting/content/ContentSource.js +129 -0
  42. package/src/charting/content/ContentSources.js +77 -0
  43. package/src/charting/datamodel/DataModel.js +116 -0
  44. package/src/charting/diagrams/ArchimateModel.js +38 -3
  45. package/src/charting/diagrams/ArchimateModelChecker.js +13 -4
  46. package/src/charting/diagrams/MergeElementsCommand.js +124 -101
  47. package/src/charting/diagrams/SvgZoom.js +20 -1
  48. package/src/charting/diagrams/duplicates.js +95 -13
  49. package/src/charting/duplicateSearchForm.js +58 -0
  50. package/src/charting/modelTree/ModelTree.js +239 -202
  51. package/src/charting/modelTree/ModelTreePanel.js +955 -919
  52. package/src/charting/modelTree/TreeBuilder.js +93 -45
  53. package/src/charting/modelTree/TreeViewMode.js +9 -6
  54. package/src/charting/modelTree/viewFolders.js +15 -6
  55. package/src/charting/requirements/AzureDevOpsApi.js +55 -0
  56. package/src/charting/requirements/GitLabApi.js +43 -0
  57. package/src/charting/requirements/JiraApi.js +69 -0
  58. package/src/charting/requirements/TrackerApi.js +142 -0
  59. package/src/charting/requirements/TrackerExport.js +3 -2
  60. package/src/charting/requirements/index.js +5 -0
  61. package/src/charting/requirements/trackerApis.js +21 -0
  62. package/src/icons/IconSet.js +86 -0
  63. package/src/icons/index.js +59 -0
  64. package/src/index.js +67 -10
  65. package/src/workspace/dom.js +56 -42
  66. package/src/workspace/index.js +1 -1
  67. package/src/workspace/nav/NavEntry.js +42 -0
  68. package/src/workspace/nav/NavGroup.js +43 -0
  69. package/src/workspace/nav/NavItem.js +53 -0
  70. package/src/workspace/nav/NavMenu.js +143 -0
  71. package/src/workspace/nav/NavSection.js +17 -0
  72. package/src/workspace/nav/index.js +5 -0
  73. package/src/workspace/parts/ArchimateCanvasPart.js +8 -3
  74. package/src/workspace/parts/DataModelPart.js +91 -0
  75. package/src/workspace/parts/EditorPart.js +8 -0
  76. package/src/workspace/parts/ModelHosted.js +1 -0
  77. package/src/workspace/parts/ModelsPart.js +765 -622
  78. package/src/workspace/styles.js +28 -1
@@ -0,0 +1,86 @@
1
+ /**
2
+ * IconSet — named icons as data, built as `<svg>` specs for el().
3
+ *
4
+ * An icon is JSON: a viewBox, how it is painted, and its shapes as
5
+ * `[tag, attrs]` pairs:
6
+ *
7
+ * { viewBox: '0 0 24 24', paint: 'stroke', shapes: [['circle', { cx: 12, cy: 12, r: 10 }]] }
8
+ *
9
+ * `paint` is 'stroke' (outlined in currentColor — the default, so an icon
10
+ * takes its parent's colour), 'fill' (filled in currentColor) or 'none' (the
11
+ * shapes carry their own colours: a brand mark). No markup is parsed: el()
12
+ * builds the spec in the SVG namespace.
13
+ */
14
+ const PAINT = {
15
+ stroke: { fill: 'none', stroke: 'currentColor', 'stroke-width': 2, 'stroke-linecap': 'round', 'stroke-linejoin': 'round' },
16
+ fill: { fill: 'currentColor' },
17
+ none: {},
18
+ };
19
+
20
+ export class IconSet {
21
+ /** @param {Object<string, Object>} [defs] - Name → icon */
22
+ constructor(defs = {}) {
23
+ this.defs = new Map();
24
+ this.add(defs);
25
+ }
26
+
27
+ /** Add (or replace) icons, name → icon. */
28
+ add(defs) {
29
+ for (const [name, def] of Object.entries(defs)) this.define(name, def);
30
+ return this;
31
+ }
32
+
33
+ /** Add (or replace) one icon; throws when it is not one. */
34
+ define(name, def) {
35
+ if (typeof def?.viewBox !== 'string' || !Array.isArray(def.shapes) || !def.shapes.length) {
36
+ throw new Error(`IconSet: icon "${name}" needs a viewBox and shapes`);
37
+ }
38
+ if (def.paint != null && !(def.paint in PAINT)) {
39
+ throw new Error(`IconSet: icon "${name}" paint is ${Object.keys(PAINT).join(', ')}, not "${def.paint}"`);
40
+ }
41
+ for (const shape of def.shapes) {
42
+ if (!Array.isArray(shape) || typeof shape[0] !== 'string') throw new Error(`IconSet: icon "${name}" shapes are [tag, attrs] pairs`);
43
+ }
44
+ this.defs.set(name, def);
45
+ return this;
46
+ }
47
+
48
+ has(name) { return this.defs.has(name); }
49
+
50
+ get(name) { return this.defs.get(name) ?? null; }
51
+
52
+ /** Every icon's name, in the order added. */
53
+ names() { return [...this.defs.keys()]; }
54
+
55
+ /** A copy of this set with `defs` added: a host's own icons over these. */
56
+ with(defs) {
57
+ return new IconSet(Object.fromEntries(this.defs)).add(defs);
58
+ }
59
+
60
+ /**
61
+ * The icon as an el() spec.
62
+ * @param {string} name
63
+ * @param {Object} [options]
64
+ * @param {number|string} [options.size] - Width and height (default 24; CSS can override)
65
+ * @param {string} [options.title] - Its accessible name; without one it is hidden from screen readers
66
+ * @param {string|string[]} [options.cls] - Extra class names
67
+ */
68
+ spec(name, { size = 24, title = null, cls = null } = {}) {
69
+ const def = this.defs.get(name);
70
+ if (!def) throw new Error(`IconSet: no icon "${name}"`);
71
+ return {
72
+ tag: 'svg',
73
+ cls: ['re-icon', ...(Array.isArray(cls) ? cls : [cls])],
74
+ attrs: {
75
+ viewBox: def.viewBox, width: size, height: size, focusable: 'false',
76
+ role: title ? 'img' : null, 'aria-hidden': title ? null : 'true',
77
+ ...PAINT[def.paint ?? 'stroke'],
78
+ },
79
+ dataset: { icon: name },
80
+ children: [
81
+ ...(title ? [{ tag: 'title', text: title }] : []),
82
+ ...def.shapes.map(([tag, attrs]) => ({ tag, attrs })),
83
+ ],
84
+ };
85
+ }
86
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The built-in icons — a shared pool any host draws from (`ICONS.spec('cpu')`,
3
+ * or `icon('cpu')`), extended with its own (`ICONS.with({ ... })`). Outlined
4
+ * icons on a 24-unit grid, stroked in currentColor; the brand marks keep their
5
+ * colours. Names say what the picture is, not where it is used.
6
+ */
7
+ import { IconSet } from './IconSet.js';
8
+
9
+ const box = (x, y, width, height, rx = 1) => ['rect', { x, y, width, height, rx }];
10
+ const path = (d) => ['path', { d }];
11
+
12
+ export const ICON_DEFS = {
13
+ dashboard: { viewBox: '0 0 24 24', shapes: [box(3, 3, 7, 9), box(14, 3, 7, 5), box(14, 12, 7, 9), box(3, 16, 7, 5)] },
14
+ zap: { viewBox: '0 0 24 24', shapes: [path('m13 2-2 10h8L11 22l2-10H5L13 2z')] },
15
+ clock: { viewBox: '0 0 24 24', shapes: [['circle', { cx: 12, cy: 12, r: 10 }], ['polyline', { points: '12 6 12 12 16 14' }]] },
16
+ 'clipboard-list': { viewBox: '0 0 24 24', shapes: [
17
+ box(8, 2, 8, 4),
18
+ path('M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'),
19
+ path('M12 11h4'), path('M12 16h4'), path('M8 11h.01'), path('M8 16h.01'),
20
+ ] },
21
+ book: { viewBox: '0 0 24 24', shapes: [
22
+ path('M4 19.5A2.5 2.5 0 0 1 6.5 17H20'),
23
+ path('M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z'),
24
+ ] },
25
+ cpu: { viewBox: '0 0 24 24', shapes: [
26
+ box(4, 4, 16, 16, 2), box(9, 9, 6, 6),
27
+ ...['M15 2v2', 'M15 20v2', 'M2 15h2', 'M2 9h2', 'M20 15h2', 'M20 9h2', 'M9 2v2', 'M9 20v2'].map(path),
28
+ ] },
29
+ memory: { viewBox: '0 0 24 24', shapes: [
30
+ path('M22 12s-3 1-5 1-3-1-5-1-3 1-5 1-5-1-5-1'), path('M7 21h10'), path('M9 21V10'), path('M15 21V10'),
31
+ path('M8 3h8a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z'),
32
+ ] },
33
+ activity: { viewBox: '0 0 24 24', shapes: [['polyline', { points: '22 12 18 12 15 21 9 3 6 12 2 12' }]] },
34
+ database: { viewBox: '0 0 24 24', shapes: [
35
+ ['ellipse', { cx: 12, cy: 5, rx: 9, ry: 3 }],
36
+ path('M3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5'),
37
+ path('M3 12c0 1.66 4.03 3 9 3s9-1.34 9-3'),
38
+ ] },
39
+ google: { viewBox: '0 0 24 24', paint: 'none', shapes: [
40
+ ['path', { fill: '#4285F4', d: 'M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z' }],
41
+ ['path', { fill: '#34A853', d: 'M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z' }],
42
+ ['path', { fill: '#FBBC05', d: 'M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z' }],
43
+ ['path', { fill: '#EA4335', d: 'M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z' }],
44
+ ] },
45
+ microsoft: { viewBox: '0 0 21 21', paint: 'none', shapes: [
46
+ ['rect', { x: 1, y: 1, width: 9, height: 9, fill: '#F25022' }],
47
+ ['rect', { x: 11, y: 1, width: 9, height: 9, fill: '#7FBA00' }],
48
+ ['rect', { x: 1, y: 11, width: 9, height: 9, fill: '#00A4EF' }],
49
+ ['rect', { x: 11, y: 11, width: 9, height: 9, fill: '#FFB900' }],
50
+ ] },
51
+ };
52
+
53
+ /** The built-in set. */
54
+ export const ICONS = new IconSet(ICON_DEFS);
55
+
56
+ /** A built-in icon as an el() spec — `ICONS.spec(name, options)`. */
57
+ export const icon = (name, options) => ICONS.spec(name, options);
58
+
59
+ export { IconSet };
package/src/index.js CHANGED
@@ -96,7 +96,7 @@ import { ArchimateSpecializationRegistry } from './archimateSpecializations.js';
96
96
  import { ArchimateSpecializationSelector } from './charting/ArchimateSpecializationSelector.js';
97
97
  import { ImageSelector } from './charting/ImageSelector.js';
98
98
  import { CheckboxTreeSelector } from './charting/CheckboxTreeSelector.js';
99
- import { SettingsManager, settingsManager } from './charting/SettingsManager.js';
99
+ import { SettingsManager, settingsManager, TREE_SETTINGS_PROPERTIES } from './charting/SettingsManager.js';
100
100
  import { showGlobalSettingsDialog } from './charting/SettingsDialog.js';
101
101
  import { ArchimateModel, ArchimateView } from './charting/diagrams/ArchimateModel.js';
102
102
  import { CanvasView } from './charting/diagrams/CanvasView.js';
@@ -144,6 +144,7 @@ import { PopupMenu } from './charting/PopupMenu.js';
144
144
  import { ViewTabStrip } from './charting/ViewTabStrip.js';
145
145
  import { EditorShell } from './workspace/EditorShell.js';
146
146
  import { FormDialog } from './charting/FormDialog.js';
147
+ import { Dialog } from './charting/Dialog.js';
147
148
  import { ColorField } from './charting/ColorField.js';
148
149
  import { TextStyle } from './charting/TextStyle.js';
149
150
  import { ArchimateModelChecker } from './charting/diagrams/ArchimateModelChecker.js';
@@ -158,6 +159,10 @@ import { PropertiesPanel } from './charting/PropertiesPanel.js';
158
159
  import { MenuBar } from './charting/MenuBar.js';
159
160
  import { ArchimateDiagramEditor } from './charting/ArchimateDiagramEditor.js';
160
161
  import { ModelTree } from './charting/modelTree/ModelTree.js';
162
+ import { TreeBuilder } from './charting/modelTree/TreeBuilder.js';
163
+ import { ContentSource } from './charting/content/ContentSource.js';
164
+ import { ContentSources } from './charting/content/ContentSources.js';
165
+ import { DUPLICATE_OPTIONS, NOISE_WORDS, FILLER_WORDS, duplicateGroups, duplicateKey, wordKey, nameKey } from './charting/diagrams/duplicates.js';
161
166
  import { TreeModeRegistry } from './charting/modelTree/TreeModeRegistry.js';
162
167
  import { TREE_MODE_CATALOG, treeModeRegistry, getTreeModeCatalogEntry } from './treeModeCatalog.js';
163
168
  import { buildModelTreePanel } from './charting/modelTree/ModelTreePanel.js';
@@ -166,7 +171,7 @@ import { buildTypeCategoryTree, buildPropertyTree } from './charting/modelTree/b
166
171
  import { DiagramTabs } from './charting/modelTree/DiagramTabs.js';
167
172
  import { DataModelNotation, DataModelLevel, AttributedLevel, ConceptualLevel, LogicalLevel, PhysicalLevel, NotationStyle, RelationshipKind, AssociationKind, GeneralizationKind, EntityRole, FactRole, DimensionRole } from './datamodel.js';
168
173
  import { DataModel, DataModelPerspective, DataModelContext, LevelSchema, LevelMapping, ServiceDomain, DataModelFileFormat, ServiceLandscape, BianLandscapeReader, BianSemanticApiReader, DataType, EnumerationType, StructuredType, NamingStandard, NameStyle, ClassificationScheme, ClassificationLens, SqlDdl, SqlDdlFileFormat, ModelScorecard, ScorecardCategory, MeasuredCategory } from './charting/datamodel/index.js';
169
- import { RequirementsModel, RequirementsChecker, RequirementsFileFormat, TrackerExportFileFormat, StoryboardFileFormat, TrackerExport, TRACKERS, Storyboard } from './charting/requirements/index.js';
174
+ import { RequirementsModel, RequirementsChecker, RequirementsFileFormat, TrackerExportFileFormat, StoryboardFileFormat, TrackerExport, TRACKERS, Storyboard, TrackerApi, JiraApi, AzureDevOpsApi, GitLabApi, TRACKER_APIS, trackerApi, trackerConnectionFields } from './charting/requirements/index.js';
170
175
  import { EditableModel } from './charting/model/EditableModel.js';
171
176
  import { ModelEditCommand } from './charting/model/ModelEditCommand.js';
172
177
  import { Benchmark } from './charting/model/Benchmark.js';
@@ -215,20 +220,22 @@ export {
215
220
  TREE_MODE_CATALOG, treeModeRegistry, getTreeModeCatalogEntry,
216
221
  AXIS_KIND, ENCODING_CHANNEL, COORD_SYSTEM, COLOR_MODE, CAMERA_MODE,
217
222
  Command, CompositeCommand, CommandHistory,
218
- DiagramEditor, DiagramEditorState, Template, PolygonTemplate, RegularPolygonTemplate, RectangleTemplate, RoundedRectangleTemplate, StadiumTemplate, PredefinedProcessTemplate, ClassTemplate, ComponentTemplate, SwimlaneTemplate, CircleTemplate, FinalStateTemplate, EllipseTemplate, DiamondTemplate, TriangleTemplate, PentagonTemplate, HexagonTemplate, OctagonTemplate, StarTemplate, ParallelogramTemplate, TrapezoidTemplate, ManualInputTemplate, CylinderTemplate, DocumentTemplate, NoteTemplate, CloudTemplate, ActorTemplate, PackageTemplate, BoxTemplate, DataStoreTemplate, LifelineTemplate, TemplateCatalog, Palette, PropertiesPanel, MenuBar, MoveElementCommand, BringToFrontCommand, SendToBackCommand, RaiseCommand, LowerCommand, CreateTemplateCommand, EditPropertyCommand, ResizeCommand, RotateCommand, SetPropertiesCommand, ReorderCommand, DIAGRAM_TYPES, getTemplatesForDiagramType, getAllDiagramTypes, getDiagramTypesByGroup, EdgeMarker, ArrowMarker, DiamondMarker, CircleMarker, CrowsFootMarker, HollowDiamondMarker, HollowTriangleMarker, OpenArrowMarker, HalfArrowMarker, EDGE_MARKERS, getEdgeMarker, Edge, AddEdgeCommand, DeleteEdgeCommand, ReconnectEdgeCommand, EDGE_STYLE_PROPERTIES, LINE_ONLY_PROPERTIES, LINE_STYLE_DASH, EdgeStyleCascade, SetStyleCommand, EdgeStyleDialog, DiagramModel, DiagramView, ViewSetModel, ViewMenu, ViewPropertiesDialog, SettingsViewPropertiesDialog, DiagramViewPropertiesDialog, inheritableFields, readInheritableFields, LocalJsonStore, PopupMenu, ViewTabStrip, EditorShell, FormDialog, ToolbarItem, ToolbarButton, ToolbarMenu, ToolbarSelect, ToolbarCheckbox, ToolbarText, ToolbarSeparator, pickFile, reorderedZ, stackingOrder, MoveWaypointCommand, AddWaypointCommand, DeleteWaypointCommand,
223
+ DiagramEditor, DiagramEditorState, Template, PolygonTemplate, RegularPolygonTemplate, RectangleTemplate, RoundedRectangleTemplate, StadiumTemplate, PredefinedProcessTemplate, ClassTemplate, ComponentTemplate, SwimlaneTemplate, CircleTemplate, FinalStateTemplate, EllipseTemplate, DiamondTemplate, TriangleTemplate, PentagonTemplate, HexagonTemplate, OctagonTemplate, StarTemplate, ParallelogramTemplate, TrapezoidTemplate, ManualInputTemplate, CylinderTemplate, DocumentTemplate, NoteTemplate, CloudTemplate, ActorTemplate, PackageTemplate, BoxTemplate, DataStoreTemplate, LifelineTemplate, TemplateCatalog, Palette, PropertiesPanel, MenuBar, MoveElementCommand, BringToFrontCommand, SendToBackCommand, RaiseCommand, LowerCommand, CreateTemplateCommand, EditPropertyCommand, ResizeCommand, RotateCommand, SetPropertiesCommand, ReorderCommand, DIAGRAM_TYPES, getTemplatesForDiagramType, getAllDiagramTypes, getDiagramTypesByGroup, EdgeMarker, ArrowMarker, DiamondMarker, CircleMarker, CrowsFootMarker, HollowDiamondMarker, HollowTriangleMarker, OpenArrowMarker, HalfArrowMarker, EDGE_MARKERS, getEdgeMarker, Edge, AddEdgeCommand, DeleteEdgeCommand, ReconnectEdgeCommand, EDGE_STYLE_PROPERTIES, LINE_ONLY_PROPERTIES, LINE_STYLE_DASH, EdgeStyleCascade, SetStyleCommand, EdgeStyleDialog, DiagramModel, DiagramView, ViewSetModel, ViewMenu, ViewPropertiesDialog, SettingsViewPropertiesDialog, DiagramViewPropertiesDialog, inheritableFields, readInheritableFields, LocalJsonStore, PopupMenu, ViewTabStrip, EditorShell, Dialog, FormDialog, ToolbarItem, ToolbarButton, ToolbarMenu, ToolbarSelect, ToolbarCheckbox, ToolbarText, ToolbarSeparator, pickFile, reorderedZ, stackingOrder, MoveWaypointCommand, AddWaypointCommand, DeleteWaypointCommand,
219
224
  ebnfAstToDiagram, parseEbnf,
220
225
  createDataWindow, GraphDataWindow,
221
226
  ArchimateNotation, ArchimateLayer, ArchimateElementType, ArchimateRelationshipType,
222
227
  Glyph, GLYPHS, glyphFor, drawGlyph, glyphElement, glyphToSvg, GlyphTemplate, ArchimateElementTemplate, ArchimateEdge, ArchimateCanvasEditor, ArchimatePalette, KIND_PREFERENCE, ARCHIMATE_TYPE_MIME,
223
228
  ArchimateModel, ArchimateView, CanvasView, PlaceItemsCommand, RemoveLineCommand, CanvasBoxKind, CANVAS_BOX_KINDS, canvasBoxKind, canvasBoxMenuItems, ArchiHint, HintPanel, EditHintCommand, hintMenuItems, CanvasPalette, CANVAS_BOX_MIME, ViewLineEdge, Arrangement, ItemGroup, BackgroundGroup, GroupCommand, UngroupCommand, LockCommand, BackgroundCommand, arrangeMenuItems, TemplateLibrary, CanvasTemplateLibrary, CanvasTemplateMenu, DEFAULT_CANVAS_TEMPLATES, canvasTemplate, EditViewItemCommand, viewItemChanges, ColorField, TextStyle, ArchimateModelChecker, ViewSetChecker, ViewFit, archiElementType, NEAREST_ARCHI_TYPE, ElementInstance, ViewObjectInstance, RelationshipInstance, ArchimateDiagramEditor,
224
229
  ITIconNotation, ITElementType,
225
- ArchimateValidator, ArchimateSpecializationSelector, ImageSelector, CheckboxTreeSelector, SettingsManager, settingsManager,
226
- ModelTree, TreeModeRegistry,
230
+ ArchimateValidator, ArchimateSpecializationSelector, ImageSelector, CheckboxTreeSelector, SettingsManager, settingsManager, TREE_SETTINGS_PROPERTIES,
231
+ ModelTree, TreeBuilder, TreeModeRegistry, ContentSource, ContentSources,
232
+ DUPLICATE_OPTIONS, NOISE_WORDS, FILLER_WORDS, duplicateGroups, duplicateKey, wordKey, nameKey,
227
233
  RadialLayout, DerivedRelationships, EdgeGeometry,
228
234
  buildModelTreePanel, buildAnalysisPanel, buildAnalysisData, buildTypeCategoryTree, buildPropertyTree, DiagramTabs,
229
235
  DataModelNotation, DataModelLevel, AttributedLevel, ConceptualLevel, LogicalLevel, PhysicalLevel, NotationStyle, RelationshipKind, AssociationKind, GeneralizationKind, EntityRole, FactRole, DimensionRole,
230
236
  DataModel, DataModelPerspective, DataModelContext, LevelSchema, LevelMapping, ServiceDomain, DataModelFileFormat, ServiceLandscape, BianLandscapeReader, BianSemanticApiReader, DataType, EnumerationType, StructuredType, NamingStandard, NameStyle, ClassificationScheme, ClassificationLens, SqlDdl, SqlDdlFileFormat, ModelScorecard, ScorecardCategory, MeasuredCategory,
231
237
  RequirementsModel, RequirementsChecker, RequirementsFileFormat, TrackerExportFileFormat, StoryboardFileFormat, TrackerExport, TRACKERS, Storyboard,
238
+ TrackerApi, JiraApi, AzureDevOpsApi, GitLabApi, TRACKER_APIS, trackerApi, trackerConnectionFields,
232
239
  EditableModel, ModelEditCommand, Benchmark,
233
240
  BianNotation, BianFunctionalPattern, BianActionTerm,
234
241
  exportElementsCSV, exportRelationshipsCSV, exportModelCSV,
@@ -250,6 +257,11 @@ export {
250
257
  * @class RenderEngine
251
258
  * This class decides which renderer to use and provides a unified drawing API.
252
259
  */
260
+ // Navigation menus (a sidebar's): DOM built from JSON, safe to import under node.
261
+ export { NavEntry, NavItem, NavGroup, NavSection, NavMenu } from './workspace/nav/index.js';
262
+ // Named icons as data (an <svg> spec each); the built-in pool and its class.
263
+ export { IconSet, ICONS, ICON_DEFS, icon } from './icons/index.js';
264
+
253
265
  export class RenderEngine {
254
266
  /** Layout constants for graphDiagram's `shape: 'table'` node type. */
255
267
  static TABLE_NODE = NodeGeometry.TABLE_NODE;
@@ -438,8 +450,12 @@ export class RenderEngine {
438
450
  * @param {HTMLElement} container The container element for the chart.
439
451
  * @param {object} [options]
440
452
  * @param {'canvas' | 'svg'} [options.rendererType='canvas']
453
+ * @param {false|{wheelModifier?: 'ctrl'|'alt'|'shift'}} [options.zoom] - SVG wheel zoom: off, or only
454
+ * with a modifier held (so a plain wheel scrolls the page); on, with a plain wheel, by default
441
455
  */
442
456
  constructor(container, options = {}) {
457
+ // Kept: the engines refreshChart() makes for other containers are made with the same options.
458
+ this.options = options;
443
459
  if (container) {
444
460
  const { rendererType = 'canvas' } = options;
445
461
  const width = container.clientWidth;
@@ -447,11 +463,9 @@ export class RenderEngine {
447
463
 
448
464
  this.container = container; // Store container for event listeners
449
465
  this.renderer = RendererFactory.create(rendererType, container, width, height);
450
- } else {
451
- // Allow initialization without a container for testing
452
- this.options = options;
453
- console.log('RenderEngine initialized in test mode.');
454
466
  }
467
+ // No container: a test, or an engine keeping a page of registered
468
+ // charts, each drawn in its own element (refreshChart).
455
469
 
456
470
  if (this.container) {
457
471
  this._interactions = new InteractionManager(this);
@@ -2587,7 +2601,10 @@ export class RenderEngine {
2587
2601
 
2588
2602
  /**
2589
2603
  * Refreshes a registered chart, re-resolving its data and re-invoking
2590
- * its render method.
2604
+ * its render method, in the element with that id: this engine's own
2605
+ * container, or — so one engine can keep a page of charts — an engine of
2606
+ * its own for that element, made with this engine's options and kept
2607
+ * (so a zoom survives the next refresh) while the element is the same.
2591
2608
  * @param {string} id Chart container ID
2592
2609
  */
2593
2610
  refreshChart(id) {
@@ -2595,7 +2612,46 @@ export class RenderEngine {
2595
2612
  const config = this.registry[id];
2596
2613
  const container = document.getElementById(id);
2597
2614
  if (!container) return;
2615
+ const target = this._engineFor(id, container);
2616
+ if (target !== this) target.fitToContainer();
2617
+ target._drawRegistered(config);
2618
+ }
2619
+
2620
+ /** Every registered chart, refreshed. */
2621
+ refreshAll() {
2622
+ for (const id of Object.keys(this.registry ?? {})) this.refreshChart(id);
2623
+ }
2624
+
2625
+ /**
2626
+ * Set the data registered charts read by `dataPath`, and refresh them all —
2627
+ * a live page's poll: `engine.setData(await fetchMetrics())`.
2628
+ * @param {object} data
2629
+ */
2630
+ setData(data) {
2631
+ this.setGlobalData(data);
2632
+ this.refreshAll();
2633
+ }
2634
+
2635
+ /** The engine that draws into `container` for registered chart `id`. @private */
2636
+ _engineFor(id, container) {
2637
+ if (container === this.container) return this;
2638
+ this._childEngines ??= new Map();
2639
+ let engine = this._childEngines.get(id);
2640
+ if (engine?.container !== container) {
2641
+ engine = this._newEngine(container);
2642
+ this._childEngines.set(id, engine);
2643
+ }
2644
+ engine.globalData = this.globalData;
2645
+ return engine;
2646
+ }
2647
+
2648
+ /** An engine for another container, with this one's options. @private */
2649
+ _newEngine(container) {
2650
+ return new RenderEngine(container, this.options);
2651
+ }
2598
2652
 
2653
+ /** Draw a registration (`{type, options}`) with this engine. @private */
2654
+ _drawRegistered(config) {
2599
2655
  // Catalog keys ('bar', 'heatmap', 'graph', 'archimate', ...) resolve
2600
2656
  // through the registries to their real method name; railroadDiagram
2601
2657
  // isn't a registry entry, so `type` already IS the method name there.
@@ -2612,6 +2668,7 @@ export class RenderEngine {
2612
2668
  this[methodName](config.options.nodes, config.options.edges || [], config.options);
2613
2669
  } else {
2614
2670
  const data = config.options.data || this.resolvePath(config.options.dataPath, config.options.dataMapper);
2671
+ if (data === undefined) return; // registered before its data has arrived
2615
2672
  this[methodName](data, config.options);
2616
2673
  }
2617
2674
  }
@@ -20,11 +20,20 @@
20
20
  * on: { click: fn }, // addEventListener
21
21
  * children: [spec | Node | string], // strings become text nodes
22
22
  * ref: (element) => {}, // capture the node without querySelector
23
+ * ns: SVG_NS, // the element's namespace (see below)
23
24
  * }
24
25
  *
25
26
  * A bare string spec produces a text node, so `children: ['plain text']` works.
27
+ *
28
+ * An `svg` spec, and everything inside it, is built in the SVG namespace
29
+ * (`createElementNS`) — so a drawing or an icon is a spec too, never parsed
30
+ * markup — until a `foreignObject`, whose children are HTML again. `ns` sets
31
+ * a namespace explicitly (a `path` spec built on its own, to append to an
32
+ * existing <svg>).
26
33
  */
27
- export function el(spec, doc = document) {
34
+ export const SVG_NS = 'http://www.w3.org/2000/svg';
35
+
36
+ export function el(spec, doc = document, ns = null) {
28
37
  if (spec == null) return doc.createTextNode('');
29
38
  if (typeof spec === 'string' || typeof spec === 'number') return doc.createTextNode(String(spec));
30
39
  // Duck-typed rather than `instanceof Node`: `Node` is a browser global, so
@@ -32,11 +41,15 @@ export function el(spec, doc = document) {
32
41
  // substituted document. A spec object never carries either of these.
33
42
  if (typeof spec.nodeType === 'number' || typeof spec.appendChild === 'function') return spec;
34
43
 
35
- const node = doc.createElement(spec.tag ?? 'div');
44
+ const tag = spec.tag ?? 'div';
45
+ const space = spec.ns ?? (tag === 'svg' ? SVG_NS : ns);
46
+ const node = space ? doc.createElementNS(space, tag) : doc.createElement(tag);
36
47
 
37
48
  if (spec.cls) {
38
- const classes = Array.isArray(spec.cls) ? spec.cls : [spec.cls];
39
- node.className = classes.filter(Boolean).join(' ');
49
+ const classes = (Array.isArray(spec.cls) ? spec.cls : [spec.cls]).filter(Boolean).join(' ');
50
+ // An SVG element's className is an SVGAnimatedString, not a string.
51
+ if (space) node.setAttribute('class', classes);
52
+ else node.className = classes;
40
53
  }
41
54
  if (spec.text != null) node.textContent = String(spec.text);
42
55
 
@@ -55,8 +68,9 @@ export function el(spec, doc = document) {
55
68
  for (const [event, handler] of Object.entries(spec.on ?? {})) {
56
69
  if (typeof handler === 'function') node.addEventListener(event, handler);
57
70
  }
71
+ const childSpace = space && tag !== 'foreignObject' ? space : null;
58
72
  for (const child of spec.children ?? []) {
59
- node.appendChild(el(child, doc));
73
+ node.appendChild(el(child, doc, childSpace));
60
74
  }
61
75
 
62
76
  spec.ref?.(node);
@@ -81,40 +95,40 @@ export function render(host, specs, doc = document) {
81
95
  function cssProp(key) {
82
96
  return key.startsWith('--') ? key : key.replace(/[A-Z]/g, c => `-${c.toLowerCase()}`);
83
97
  }
84
-
85
- const VOID_TAGS = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr']);
86
- const escapeHtml = (s) => String(s).replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]);
87
-
88
- /**
89
- * The same spec as el(), written out as HTML text: for a page that is saved
90
- * rather than shown (a report). Text and attribute values are escaped; `on`
91
- * and `ref` have no meaning in text and are left out. `script` is the one
92
- * extra field: the trusted contents of a <script> or <style> (not escaped,
93
- * but `</` is, so it cannot close its element early).
94
- */
95
- export function toHtml(spec) {
96
- if (spec == null) return '';
97
- if (typeof spec === 'string' || typeof spec === 'number') return escapeHtml(spec);
98
- if (Array.isArray(spec)) return spec.map(toHtml).join('');
99
- const tag = spec.tag ?? 'div';
100
- const attrs = [];
101
- if (spec.cls) {
102
- const classes = (Array.isArray(spec.cls) ? spec.cls : [spec.cls]).filter(Boolean);
103
- if (classes.length) attrs.push(['class', classes.join(' ')]);
104
- }
105
- for (const [k, v] of Object.entries(spec.attrs ?? {})) {
106
- if (v == null || v === false) continue;
107
- attrs.push([k, v === true ? '' : String(v)]);
108
- }
109
- for (const [k, v] of Object.entries(spec.dataset ?? {})) {
110
- if (v != null) attrs.push([`data-${cssProp(k)}`, String(v)]);
111
- }
112
- const style = Object.entries(spec.style ?? {}).filter(([, v]) => v != null).map(([k, v]) => `${cssProp(k)}: ${v}`).join('; ');
113
- if (style) attrs.push(['style', style]);
114
- const open = `<${tag}${attrs.map(([k, v]) => ` ${k}="${escapeHtml(v)}"`).join('')}>`;
115
- if (VOID_TAGS.has(tag)) return open;
116
- const inner = spec.script != null
117
- ? String(spec.script).replace(/<\//g, '<\\/')
118
- : (spec.text != null ? escapeHtml(spec.text) : '') + (spec.children ?? []).map(toHtml).join('');
119
- return `${open}${inner}</${tag}>`;
120
- }
98
+
99
+ const VOID_TAGS = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr']);
100
+ const escapeHtml = (s) => String(s).replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]);
101
+
102
+ /**
103
+ * The same spec as el(), written out as HTML text: for a page that is saved
104
+ * rather than shown (a report). Text and attribute values are escaped; `on`
105
+ * and `ref` have no meaning in text and are left out. `script` is the one
106
+ * extra field: the trusted contents of a <script> or <style> (not escaped,
107
+ * but `</` is, so it cannot close its element early).
108
+ */
109
+ export function toHtml(spec) {
110
+ if (spec == null) return '';
111
+ if (typeof spec === 'string' || typeof spec === 'number') return escapeHtml(spec);
112
+ if (Array.isArray(spec)) return spec.map(toHtml).join('');
113
+ const tag = spec.tag ?? 'div';
114
+ const attrs = [];
115
+ if (spec.cls) {
116
+ const classes = (Array.isArray(spec.cls) ? spec.cls : [spec.cls]).filter(Boolean);
117
+ if (classes.length) attrs.push(['class', classes.join(' ')]);
118
+ }
119
+ for (const [k, v] of Object.entries(spec.attrs ?? {})) {
120
+ if (v == null || v === false) continue;
121
+ attrs.push([k, v === true ? '' : String(v)]);
122
+ }
123
+ for (const [k, v] of Object.entries(spec.dataset ?? {})) {
124
+ if (v != null) attrs.push([`data-${cssProp(k)}`, String(v)]);
125
+ }
126
+ const style = Object.entries(spec.style ?? {}).filter(([, v]) => v != null).map(([k, v]) => `${cssProp(k)}: ${v}`).join('; ');
127
+ if (style) attrs.push(['style', style]);
128
+ const open = `<${tag}${attrs.map(([k, v]) => ` ${k}="${escapeHtml(v)}"`).join('')}>`;
129
+ if (VOID_TAGS.has(tag)) return open;
130
+ const inner = spec.script != null
131
+ ? String(spec.script).replace(/<\//g, '<\\/')
132
+ : (spec.text != null ? escapeHtml(spec.text) : '') + (spec.children ?? []).map(toHtml).join('');
133
+ return `${open}${inner}</${tag}>`;
134
+ }
@@ -42,7 +42,7 @@ export {
42
42
  ContextBus, Part, PartRegistry, PartFactory,
43
43
  ContentPart, HtmlPart, ListPart, DetailPart, AdapterPart, ModelsPart,
44
44
  };
45
- export { el, frag, render } from './dom.js';
45
+ export { el, frag, render, SVG_NS } from './dom.js';
46
46
 
47
47
  // The engine's whole public API, re-exported so the workspace UMD bundle is a
48
48
  // superset of the main one: a page that loads renderengine-workspace.umd.js
@@ -0,0 +1,42 @@
1
+ /**
2
+ * NavEntry — one entry in a NavMenu: an item, a group of items, or a section
3
+ * heading. Subclasses return an el() spec; the menu builds every entry from
4
+ * JSON (NavMenu.entry), so a host writes its navigation as data, not markup.
5
+ *
6
+ * `visible` is a boolean or a function of the menu's context (`{ role }`, …),
7
+ * so one definition serves every user and the menu re-renders when the
8
+ * context changes (NavMenu#setContext).
9
+ */
10
+ export class NavEntry {
11
+ /**
12
+ * @param {Object} [options]
13
+ * @param {string} [options.id]
14
+ * @param {string|string[]} [options.cls] - Extra class names
15
+ * @param {string} [options.title] - Tooltip
16
+ * @param {boolean|((context: Object) => boolean)} [options.visible]
17
+ */
18
+ constructor({ id = null, cls = null, title = null, visible = true } = {}) {
19
+ this.id = id;
20
+ this.cls = cls;
21
+ this.title = title;
22
+ this.visible = visible;
23
+ }
24
+
25
+ /** Whether the entry shows, given the menu's context. */
26
+ isVisible(menu) {
27
+ return typeof this.visible === 'function' ? !!this.visible(menu.context) : this.visible !== false;
28
+ }
29
+
30
+ /** @returns {Object} el() spec */
31
+ spec(menu) {
32
+ throw new Error(`${this.constructor.name} must implement spec()`);
33
+ }
34
+
35
+ /** The entries this one holds (a group's items); none by default. */
36
+ get children() { return []; }
37
+
38
+ /** Extra class names as a list, for el()'s `cls`. */
39
+ extraClasses() {
40
+ return Array.isArray(this.cls) ? this.cls : this.cls ? [this.cls] : [];
41
+ }
42
+ }
@@ -0,0 +1,43 @@
1
+ import { NavEntry } from './NavEntry.js';
2
+
3
+ /**
4
+ * NavGroup — entries under a heading that opens and closes (a `<details>`).
5
+ * Whether it is open is state the menu keeps (NavMenu#serialize) and reports
6
+ * (`onToggle`), so a host can remember it. Hidden when none of its entries
7
+ * shows.
8
+ */
9
+ export class NavGroup extends NavEntry {
10
+ /**
11
+ * @param {Object} options
12
+ * @param {string} options.group - The heading's text
13
+ * @param {Array<Object|NavEntry>} options.items - Entries, as JSON or built
14
+ * @param {boolean} [options.open]
15
+ * @param {(json: Object) => NavEntry} entry - Builds an entry from JSON (NavMenu.entry)
16
+ */
17
+ constructor({ group, items = [], open = false, ...rest }, entry) {
18
+ super(rest);
19
+ this.label = group;
20
+ this.open = !!open;
21
+ this.items = items.map(entry);
22
+ }
23
+
24
+ get children() { return this.items; }
25
+
26
+ isVisible(menu) {
27
+ return super.isVisible(menu) && this.items.some(e => e.isVisible(menu));
28
+ }
29
+
30
+ spec(menu) {
31
+ return {
32
+ tag: 'details',
33
+ cls: ['ws-nav-group', ...this.extraClasses()],
34
+ attrs: { open: this.open },
35
+ dataset: { navId: this.id },
36
+ children: [
37
+ { tag: 'summary', cls: 'ws-nav-group-summary', text: this.label, attrs: { title: this.title } },
38
+ ...menu.specsOf(this.items),
39
+ ],
40
+ on: { toggle: (event) => menu.toggled(this, !!event.target.open) },
41
+ };
42
+ }
43
+ }
@@ -0,0 +1,53 @@
1
+ import { NavEntry } from './NavEntry.js';
2
+
3
+ /**
4
+ * NavItem — a place to go or a thing to do. With `href` it is a link (the
5
+ * browser navigates, and the address can be opened in a new tab); without, a
6
+ * button. `action(event, item, menu)` runs on a click either way, and may
7
+ * call `event.preventDefault()` to keep a link from navigating — e.g. a
8
+ * modifier-click that opens the target beside the current view instead.
9
+ */
10
+ export class NavItem extends NavEntry {
11
+ /**
12
+ * @param {Object} options
13
+ * @param {string} options.label
14
+ * @param {string} [options.icon] - Shown before the label, hidden from screen readers: an icon's
15
+ * name in the menu's IconSet (`icons`), or text (an emoji)
16
+ * @param {string|((context: Object) => string|null)} [options.href] - Or a function of the menu's context (an address known later)
17
+ * @param {(event: Event, item: NavItem, menu: NavMenu) => void} [options.action]
18
+ * @param {boolean} [options.selectable] - Whether a click makes it the active item (default: true)
19
+ */
20
+ constructor({ label, icon = null, href = null, action = null, selectable = true, ...rest }) {
21
+ super(rest);
22
+ this.label = label;
23
+ this.icon = icon;
24
+ this.href = href;
25
+ this.action = action;
26
+ this.selectable = selectable;
27
+ }
28
+
29
+ spec(menu) {
30
+ const active = this.id != null && this.id === menu.activeId;
31
+ const href = typeof this.href === 'function' ? this.href(menu.context) : this.href;
32
+ return {
33
+ tag: href != null ? 'a' : 'button',
34
+ cls: ['ws-nav-item', active && 'ws-nav-item-active', ...this.extraClasses()],
35
+ attrs: {
36
+ href,
37
+ type: href != null ? null : 'button',
38
+ title: this.title,
39
+ 'aria-current': active ? 'page' : null,
40
+ },
41
+ dataset: { navId: this.id },
42
+ children: [
43
+ this.icon != null && {
44
+ tag: 'span', cls: 'ws-nav-icon', attrs: { 'aria-hidden': 'true' },
45
+ ...(menu.icons?.has(this.icon) ? { children: [menu.icons.spec(this.icon, { size: 16 })] } : { text: this.icon }),
46
+ },
47
+ { tag: 'span', cls: 'ws-nav-label', text: this.label },
48
+ ].filter(Boolean),
49
+ on: { click: (event) => menu.select(this, event) },
50
+ ref: (n) => menu.nodes.set(this, n),
51
+ };
52
+ }
53
+ }