@peter.prib/renderengine 0.2.5 → 0.2.7

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 (67) hide show
  1. package/README.md +219 -213
  2. package/REFERENCE.md +272 -6
  3. package/dist/renderengine-workspace.umd.js +11980 -7421
  4. package/dist/renderengine.umd.js +8991 -5784
  5. package/examples/archimate.html +68 -66
  6. package/examples/data-model.html +534 -0
  7. package/package.json +70 -68
  8. package/src/bian.js +182 -0
  9. package/src/charting/CheckboxTreeSelector.js +373 -304
  10. package/src/charting/DiagramEditor.js +53 -32
  11. package/src/charting/DiagramTypeRegistry.js +13 -3
  12. package/src/charting/ModelCheckDialog.js +63 -0
  13. package/src/charting/ModelChecker.js +26 -0
  14. package/src/charting/ViewTabStrip.js +94 -81
  15. package/src/charting/archimate/ArchimateCanvasEditor.js +825 -815
  16. package/src/charting/archimate/ArchimateElementTemplate.js +208 -195
  17. package/src/charting/datamodel/BianLandscapeReader.js +185 -0
  18. package/src/charting/datamodel/BianSemanticApiReader.js +183 -0
  19. package/src/charting/datamodel/DataModel.js +645 -0
  20. package/src/charting/datamodel/DataModelChecker.js +92 -0
  21. package/src/charting/datamodel/DataModelCompared.js +127 -0
  22. package/src/charting/datamodel/DataModelContext.js +108 -0
  23. package/src/charting/datamodel/DataModelFileFormat.js +33 -0
  24. package/src/charting/datamodel/DataModelPerspective.js +129 -0
  25. package/src/charting/datamodel/DataType.js +108 -0
  26. package/src/charting/datamodel/EnumerationType.js +51 -0
  27. package/src/charting/datamodel/LevelMapping.js +38 -0
  28. package/src/charting/datamodel/LevelSchema.js +41 -0
  29. package/src/charting/datamodel/ServiceDomain.js +177 -0
  30. package/src/charting/datamodel/ServiceLandscape.js +104 -0
  31. package/src/charting/datamodel/StructuredType.js +63 -0
  32. package/src/charting/datamodel/index.js +15 -0
  33. package/src/charting/diagramModel/MergeModelCommand.js +43 -37
  34. package/src/charting/diagramModel/OpenViewList.js +75 -0
  35. package/src/charting/diagramModel/ViewSetChecker.js +22 -34
  36. package/src/charting/diagrams/DataModelDiagram.js +160 -86
  37. package/src/charting/diagrams/DataModelRelationshipStrategy.js +94 -38
  38. package/src/charting/diagrams/EdgeGeometry.js +578 -553
  39. package/src/charting/diagrams/GraphDiagram.js +11 -1
  40. package/src/charting/diagrams/NodeGeometry.js +169 -158
  41. package/src/charting/diagrams/nesting.js +31 -0
  42. package/src/charting/glyphs/PersonGlyph.js +24 -23
  43. package/src/charting/modelTree/ModelTree.js +202 -164
  44. package/src/charting/modelTree/ModelTreePanel.js +36 -3
  45. package/src/charting/modelTree/TreeViewMode.js +131 -110
  46. package/src/charting/modelTree/modes/FlatTreeMode.js +32 -46
  47. package/src/charting/modelTree/modes/LayerGroupedTreeMode.js +47 -62
  48. package/src/charting/modelTree/modes/TypeGroupedTreeMode.js +46 -60
  49. package/src/datamodel.js +132 -122
  50. package/src/diagramCatalog.js +35 -30
  51. package/src/index.js +2666 -2659
  52. package/src/workspace/EditorShell.js +166 -67
  53. package/src/workspace/PanelLayout.js +56 -0
  54. package/src/workspace/index.js +1 -0
  55. package/src/workspace/layout/TabsNode.js +205 -191
  56. package/src/workspace/parts/ArchimateCanvasPart.js +10 -9
  57. package/src/workspace/parts/ChartPart.js +6 -1
  58. package/src/workspace/parts/DataModelPart.js +544 -0
  59. package/src/workspace/parts/DiagramEditorPart.js +2 -8
  60. package/src/workspace/parts/EditorPart.js +461 -407
  61. package/src/workspace/parts/ModelFile.js +216 -187
  62. package/src/workspace/parts/ModelsPart.js +483 -192
  63. package/src/workspace/storage/BackupFolder.js +145 -0
  64. package/src/workspace/storage/KeyValueStore.js +92 -0
  65. package/src/workspace/storage/ModelLibrary.js +65 -0
  66. package/src/workspace/workArea.js +417 -413
  67. package/src/yaml/YamlReader.js +372 -0
@@ -1,164 +1,202 @@
1
- import { treeModeRegistry } from '../../treeModeCatalog.js';
2
- import { buildTypeCategoryTree, buildPropertyTree } from './buildFilterTrees.js';
3
-
4
- /**
5
- * Pure-logic model tree orchestrator. Holds model, manages grouping mode,
6
- * filter text, and selection. DOM shells (ModelTreePanel/AnalysisPanel)
7
- * use this via getters/setters and onSelectionChange callbacks.
8
- */
9
- export class ModelTree {
10
- constructor(model, { mode = 'byLayer', viewId = null } = {}) {
11
- this.model = model;
12
- this._filterText = '';
13
- this._typeFilter = null; // Set<string> | null (null = no filter = all types shown)
14
- this._specializationFilter = null;
15
- this._propertyFilter = null;
16
- this._selectedId = null;
17
- this._selectedKind = null;
18
- this._onSelectionChange = [];
19
- /** Which tree folders are open, by node id (TreeBuilder openState), kept while the tree is redrawn. */
20
- this.openFolders = new Map();
21
- this._selectedViewId = viewId || (model.views().length > 0 ? model.views()[0].id : null);
22
- this.setMode(mode);
23
- }
24
-
25
- getSelectedView() {
26
- return this._selectedViewId ? this.model.getView?.(this._selectedViewId) : null;
27
- }
28
-
29
- setView(viewId) {
30
- // null means "show all elements from the model"
31
- if (viewId !== null) {
32
- const view = this.model.getView?.(viewId);
33
- if (!view) {
34
- throw new Error(`Unknown view: "${viewId}"`);
35
- }
36
- }
37
- this._selectedViewId = viewId;
38
- this._rebuild();
39
- }
40
-
41
- getAvailableViews() {
42
- return this.model.views();
43
- }
44
-
45
- setMode(key) {
46
- const entry = treeModeRegistry.get(key);
47
- if (!entry) {
48
- throw new Error(`Unknown tree mode: "${key}"`);
49
- }
50
- this._mode = new entry.TreeModeClass();
51
- this._rebuild();
52
- }
53
-
54
- setFilter(text) {
55
- this._filterText = text;
56
- this._rebuild();
57
- }
58
-
59
- setTypeFilter(typeIds) {
60
- this._typeFilter = typeIds instanceof Set ? typeIds : (typeIds ? new Set(typeIds) : null);
61
- this._rebuild();
62
- }
63
-
64
- setSpecializationFilter(specIds) {
65
- this._specializationFilter = specIds instanceof Set ? specIds : (specIds ? new Set(specIds) : null);
66
- this._rebuild();
67
- }
68
-
69
- setPropertyFilter(propIds) {
70
- this._propertyFilter = propIds instanceof Set ? propIds : (propIds ? new Set(propIds) : null);
71
- this._rebuild();
72
- }
73
-
74
- select(id, kind = 'element') {
75
- this._selectedId = id;
76
- this._selectedKind = kind;
77
- this._notifySelectionChange();
78
- }
79
-
80
- getSelectedId() { return this._selectedId; }
81
- getSelectedKind() { return this._selectedKind; }
82
- getFilterText() { return this._filterText; }
83
-
84
- /**
85
- * Get the type/specialization hierarchy tree for this model.
86
- * Ready to pass to CheckboxTreeSelector.
87
- */
88
- getAvailableTypeTree() {
89
- return buildTypeCategoryTree(this.model);
90
- }
91
-
92
- /**
93
- * Get the property name/value hierarchy tree for this model.
94
- * Ready to pass to CheckboxTreeSelector.
95
- */
96
- getAvailablePropertyTree() {
97
- return buildPropertyTree(this.model);
98
- }
99
-
100
- buildGroups() {
101
- const view = this.getSelectedView();
102
- const dataSource = view || this.model;
103
- return this._mode.buildGroups(dataSource, {
104
- filterText: this._filterText,
105
- typeIds: this._typeFilter,
106
- specializationIds: this._specializationFilter,
107
- propertyIds: this._propertyFilter,
108
- });
109
- }
110
-
111
- onSelectionChange(callback) {
112
- this._onSelectionChange.push(callback);
113
- return () => {
114
- const idx = this._onSelectionChange.indexOf(callback);
115
- if (idx !== -1) this._onSelectionChange.splice(idx, 1);
116
- };
117
- }
118
-
119
- _rebuild() {
120
- // Consumers call buildGroups() as needed; listeners hear that the filters changed.
121
- for (const cb of this._onFilterChange ?? []) cb(this);
122
- }
123
-
124
- /** Whether any filter is on (text, type, specialization or property). */
125
- get filtering() {
126
- return !!(this._filterText || this._typeFilter?.size || this._specializationFilter?.size || this._propertyFilter?.size);
127
- }
128
-
129
- /**
130
- * The model elements the tree's filters keep (over the whole model, not
131
- * one view), or null when no filter is on.
132
- * @returns {Set<string>|null}
133
- */
134
- matchingElementIds() {
135
- if (!this.filtering) return null;
136
- const groups = this._mode.buildGroups(this.model, {
137
- filterText: this._filterText, typeIds: this._typeFilter,
138
- specializationIds: this._specializationFilter, propertyIds: this._propertyFilter,
139
- });
140
- const ids = new Set();
141
- const walk = (items) => { for (const item of items ?? []) { if (item.kind === 'element') ids.add(item.id); walk(item.items); walk(item.children); } };
142
- for (const group of groups) walk(group.items);
143
- return ids;
144
- }
145
-
146
- /** Call `fn(tree)` whenever a filter changes. Returns an unsubscribe function. */
147
- onFilterChange(fn) {
148
- (this._onFilterChange ??= []).push(fn);
149
- return () => { this._onFilterChange = this._onFilterChange.filter(f => f !== fn); };
150
- }
151
-
152
- _notifySelectionChange() {
153
- const data = {
154
- id: this._selectedId,
155
- kind: this._selectedKind,
156
- element: this._selectedKind === 'element' ? this.model.getElement(this._selectedId) : null,
157
- relationship: this._selectedKind === 'relationship' ? this.model.getRelationship(this._selectedId) : null,
158
- view: this._selectedKind === 'view' ? this.model.getView?.(this._selectedId) : null,
159
- };
160
- for (const cb of this._onSelectionChange) {
161
- cb(data);
162
- }
163
- }
164
- }
1
+ import { treeModeRegistry } from '../../treeModeCatalog.js';
2
+ import { buildTypeCategoryTree, buildPropertyTree } from './buildFilterTrees.js';
3
+
4
+ /**
5
+ * Pure-logic model tree orchestrator. Holds model, manages grouping mode,
6
+ * filter text, and selection. DOM shells (ModelTreePanel/AnalysisPanel)
7
+ * use this via getters/setters and onSelectionChange callbacks.
8
+ */
9
+ export class ModelTree {
10
+ constructor(model, { mode = 'byLayer', viewId = null } = {}) {
11
+ this.model = model;
12
+ this._filterText = '';
13
+ this._typeFilter = null; // Set<string> | null (null = no filter = all types shown)
14
+ this._specializationFilter = null;
15
+ this._propertyFilter = null;
16
+ /** Whether the search text is looked for in descriptions too. */
17
+ this._inDescriptions = false;
18
+ /** What the search covers: 'model' (every element) or 'view' (those on the diagram in front). */
19
+ this._searchScope = 'model';
20
+ this._selectedId = null;
21
+ this._selectedKind = null;
22
+ this._onSelectionChange = [];
23
+ /** Which tree folders are open, by node id (TreeBuilder openState), kept while the tree is redrawn. */
24
+ this.openFolders = new Map();
25
+ this._selectedViewId = viewId || (model.views().length > 0 ? model.views()[0].id : null);
26
+ this.setMode(mode);
27
+ }
28
+
29
+ getSelectedView() {
30
+ return this._selectedViewId ? this.model.getView?.(this._selectedViewId) : null;
31
+ }
32
+
33
+ setView(viewId) {
34
+ // null means "show all elements from the model"
35
+ if (viewId !== null) {
36
+ const view = this.model.getView?.(viewId);
37
+ if (!view) {
38
+ throw new Error(`Unknown view: "${viewId}"`);
39
+ }
40
+ }
41
+ this._selectedViewId = viewId;
42
+ this._rebuild();
43
+ }
44
+
45
+ getAvailableViews() {
46
+ return this.model.views();
47
+ }
48
+
49
+ setMode(key) {
50
+ const entry = treeModeRegistry.get(key);
51
+ if (!entry) {
52
+ throw new Error(`Unknown tree mode: "${key}"`);
53
+ }
54
+ this._mode = new entry.TreeModeClass();
55
+ this._rebuild();
56
+ }
57
+
58
+ setFilter(text) {
59
+ this._filterText = text;
60
+ this._rebuild();
61
+ }
62
+
63
+ setTypeFilter(typeIds) {
64
+ this._typeFilter = typeIds instanceof Set ? typeIds : (typeIds ? new Set(typeIds) : null);
65
+ this._rebuild();
66
+ }
67
+
68
+ setSpecializationFilter(specIds) {
69
+ this._specializationFilter = specIds instanceof Set ? specIds : (specIds ? new Set(specIds) : null);
70
+ this._rebuild();
71
+ }
72
+
73
+ setPropertyFilter(propIds) {
74
+ this._propertyFilter = propIds instanceof Set ? propIds : (propIds ? new Set(propIds) : null);
75
+ this._rebuild();
76
+ }
77
+
78
+ /** Look for the search text in descriptions (documentation) too, or not. */
79
+ setSearchInDescriptions(on) {
80
+ this._inDescriptions = !!on;
81
+ this._rebuild();
82
+ }
83
+
84
+ getSearchInDescriptions() { return this._inDescriptions; }
85
+
86
+ /** What the search covers: 'model' (every element) or 'view' (only those on the diagram in front). */
87
+ setSearchScope(scope) {
88
+ if (!ModelTree.SEARCH_SCOPES.some(([key]) => key === scope)) throw new Error(`Unknown search scope: "${scope}"`);
89
+ this._searchScope = scope;
90
+ this._rebuild();
91
+ }
92
+
93
+ getSearchScope() { return this._searchScope; }
94
+
95
+ /** The filters on now (null: none), for a filter dialog to show. */
96
+ getTypeFilter() { return this._typeFilter; }
97
+ getSpecializationFilter() { return this._specializationFilter; }
98
+ getPropertyFilter() { return this._propertyFilter; }
99
+
100
+ select(id, kind = 'element') {
101
+ this._selectedId = id;
102
+ this._selectedKind = kind;
103
+ this._notifySelectionChange();
104
+ }
105
+
106
+ getSelectedId() { return this._selectedId; }
107
+ getSelectedKind() { return this._selectedKind; }
108
+ getFilterText() { return this._filterText; }
109
+
110
+ /**
111
+ * Get the type/specialization hierarchy tree for this model.
112
+ * Ready to pass to CheckboxTreeSelector.
113
+ */
114
+ getAvailableTypeTree() {
115
+ return buildTypeCategoryTree(this.model);
116
+ }
117
+
118
+ /**
119
+ * Get the property name/value hierarchy tree for this model.
120
+ * Ready to pass to CheckboxTreeSelector.
121
+ */
122
+ getAvailablePropertyTree() {
123
+ return buildPropertyTree(this.model);
124
+ }
125
+
126
+ buildGroups() {
127
+ const view = this.getSelectedView();
128
+ const dataSource = view || this.model;
129
+ return this._mode.buildGroups(dataSource, this._filters());
130
+ }
131
+
132
+ onSelectionChange(callback) {
133
+ this._onSelectionChange.push(callback);
134
+ return () => {
135
+ const idx = this._onSelectionChange.indexOf(callback);
136
+ if (idx !== -1) this._onSelectionChange.splice(idx, 1);
137
+ };
138
+ }
139
+
140
+ _rebuild() {
141
+ // Consumers call buildGroups() as needed; listeners hear that the filters changed.
142
+ for (const cb of this._onFilterChange ?? []) cb(this);
143
+ }
144
+
145
+ /** Whether any filter is on (text, type, specialization or property). */
146
+ get filtering() {
147
+ return !!(this._filterText || this._typeFilter?.size || this._specializationFilter?.size || this._propertyFilter?.size);
148
+ }
149
+
150
+ /** What every mode filters by. @private */
151
+ _filters() {
152
+ return {
153
+ filterText: this._filterText, typeIds: this._typeFilter, specializationIds: this._specializationFilter,
154
+ propertyIds: this._propertyFilter, inDescriptions: this._inDescriptions,
155
+ };
156
+ }
157
+
158
+ /**
159
+ * The model elements the tree's filters keep, or null when no filter is
160
+ * on: over the whole model, or with the 'view' scope only those on the
161
+ * diagram in front.
162
+ * @returns {Set<string>|null}
163
+ */
164
+ matchingElementIds() {
165
+ if (!this.filtering) return null;
166
+ const groups = this._mode.buildGroups(this.model, this._filters());
167
+ const ids = new Set();
168
+ const walk = (items) => { for (const item of items ?? []) { if (item.kind === 'element') ids.add(item.id); walk(item.items); walk(item.children); } };
169
+ for (const group of groups) walk(group.items);
170
+ const onView = this._searchScope === 'view' ? this._elementIdsOnView() : null;
171
+ return onView ? new Set([...ids].filter(id => onView.has(id))) : ids;
172
+ }
173
+
174
+ /** The model elements drawn on the diagram in front, or null when there is none. @private */
175
+ _elementIdsOnView() {
176
+ const view = this.model.currentView;
177
+ if (!view?.elementInstances) return null;
178
+ return new Set([...view.elementInstances.values()].map(i => i.elementId).filter(Boolean));
179
+ }
180
+
181
+ /** Call `fn(tree)` whenever a filter changes. Returns an unsubscribe function. */
182
+ onFilterChange(fn) {
183
+ (this._onFilterChange ??= []).push(fn);
184
+ return () => { this._onFilterChange = this._onFilterChange.filter(f => f !== fn); };
185
+ }
186
+
187
+ _notifySelectionChange() {
188
+ const data = {
189
+ id: this._selectedId,
190
+ kind: this._selectedKind,
191
+ element: this._selectedKind === 'element' ? this.model.getElement(this._selectedId) : null,
192
+ relationship: this._selectedKind === 'relationship' ? this.model.getRelationship(this._selectedId) : null,
193
+ view: this._selectedKind === 'view' ? this.model.getView?.(this._selectedId) : null,
194
+ };
195
+ for (const cb of this._onSelectionChange) {
196
+ cb(data);
197
+ }
198
+ }
199
+ }
200
+
201
+ /** What the search can cover: [key, label]. */
202
+ ModelTree.SEARCH_SCOPES = Object.freeze([['model', 'Whole model'], ['view', 'This diagram']]);
@@ -1,5 +1,9 @@
1
1
  import { treeModeRegistry } from '../../treeModeCatalog.js';
2
- import { CheckboxTreeSelector } from '../CheckboxTreeSelector.js';
2
+ import { CheckboxTreeSelector, OptInCheckboxTreeSelector } from '../CheckboxTreeSelector.js';
3
+ import { ModelTree } from './ModelTree.js';
4
+
5
+ /** A property with more values than this lists them folded, with a box to find one. */
6
+ const PROPERTY_VALUES_FOLDED_ABOVE = 10;
3
7
  import { TreeBuilder } from './TreeBuilder.js';
4
8
  import { typeGlyph } from '../archimate/typeGlyph.js';
5
9
  import { ArchimateNotation } from '../../archimate.js';
@@ -125,6 +129,28 @@ export function buildModelTreePanel(doc, { modelTree, onSelectElement = null, on
125
129
  box.style.cssText = 'width: 100%; box-sizing: border-box; padding: 6px 8px; border: 1px solid #ccc; border-radius: 3px; font-size: 13px;';
126
130
  box.addEventListener('input', () => { modelTree.setFilter(box.value.trim()); showMatches(); });
127
131
  panel.appendChild(box);
132
+
133
+ // What the search covers (the whole model, or the diagram in front) and whether it reads descriptions.
134
+ const options = doc.createElement('div');
135
+ options.style.cssText = 'display: flex; gap: 8px; align-items: center; font-size: 12px; color: #555;';
136
+ const scope = doc.createElement('select');
137
+ scope.setAttribute('aria-label', 'Search in');
138
+ scope.style.cssText = 'font-size: 12px; padding: 1px 2px;';
139
+ for (const [key, label] of ModelTree.SEARCH_SCOPES) {
140
+ const option = doc.createElement('option');
141
+ Object.assign(option, { value: key, textContent: label, selected: key === modelTree.getSearchScope?.() });
142
+ scope.appendChild(option);
143
+ }
144
+ scope.addEventListener('change', () => { modelTree.setSearchScope(scope.value); showMatches(); });
145
+ const described = doc.createElement('label');
146
+ described.style.cssText = 'display: flex; gap: 4px; align-items: center; cursor: pointer;';
147
+ const tick = doc.createElement('input');
148
+ Object.assign(tick, { type: 'checkbox', checked: !!modelTree.getSearchInDescriptions?.() });
149
+ tick.setAttribute('data-search-descriptions', '');
150
+ tick.addEventListener('change', () => { modelTree.setSearchInDescriptions(tick.checked); showMatches(); });
151
+ described.append(tick, 'Descriptions');
152
+ options.append('In:', scope, described);
153
+ panel.appendChild(options);
128
154
  }
129
155
 
130
156
  // Filter button opens modal dialog
@@ -168,7 +194,9 @@ export function buildModelTreePanel(doc, { modelTree, onSelectElement = null, on
168
194
  // Rename labels for better UX (strip common prefixes)
169
195
  const cleanTreeData = _cleanTreeLabels(typeTreeData);
170
196
 
171
- const typeSelector = new CheckboxTreeSelector({ doc, nodes: cleanTreeData });
197
+ // Showing the filter on now (none: every type ticked).
198
+ const typeOn = [...(modelTree.getTypeFilter?.() ?? []), ...(modelTree.getSpecializationFilter?.() ?? [])];
199
+ const typeSelector = new CheckboxTreeSelector({ doc, nodes: cleanTreeData, checkedIds: typeOn.length ? new Set(typeOn) : null });
172
200
  const typeContainer = typeSelector.build();
173
201
  typeContainer.style.cssText = 'padding-left: 8px; font-size: 12px;';
174
202
  typeDetails.appendChild(typeContainer);
@@ -218,7 +246,12 @@ export function buildModelTreePanel(doc, { modelTree, onSelectElement = null, on
218
246
  propSummary.textContent = '▼ Properties';
219
247
  propDetails.appendChild(propSummary);
220
248
 
221
- const propSelector = new CheckboxTreeSelector({ doc, nodes: propTreeData });
249
+ // Nothing ticked to start (no property filter); a value is ticked to keep what has it.
250
+ // A property with many values is folded, with a box to find one.
251
+ const propOn = modelTree.getPropertyFilter?.();
252
+ const propSelector = new OptInCheckboxTreeSelector({
253
+ doc, nodes: propTreeData, checkedIds: propOn ? new Set(propOn) : null, searchAbove: PROPERTY_VALUES_FOLDED_ABOVE,
254
+ });
222
255
  const propContainer = propSelector.build();
223
256
  propContainer.style.cssText = 'padding-left: 8px; font-size: 12px;';
224
257
  propDetails.appendChild(propContainer);