@peter.prib/renderengine 0.5.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 (71) hide show
  1. package/README.md +4 -0
  2. package/REFERENCE.md +252 -17
  3. package/dist/renderengine-workspace.umd.js +4995 -2932
  4. package/dist/renderengine.umd.js +3985 -2193
  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/icons/IconSet.js +86 -0
  56. package/src/icons/index.js +59 -0
  57. package/src/index.js +65 -9
  58. package/src/workspace/dom.js +56 -42
  59. package/src/workspace/index.js +1 -1
  60. package/src/workspace/nav/NavEntry.js +42 -0
  61. package/src/workspace/nav/NavGroup.js +43 -0
  62. package/src/workspace/nav/NavItem.js +53 -0
  63. package/src/workspace/nav/NavMenu.js +143 -0
  64. package/src/workspace/nav/NavSection.js +17 -0
  65. package/src/workspace/nav/index.js +5 -0
  66. package/src/workspace/parts/ArchimateCanvasPart.js +8 -3
  67. package/src/workspace/parts/DataModelPart.js +91 -0
  68. package/src/workspace/parts/EditorPart.js +8 -0
  69. package/src/workspace/parts/ModelHosted.js +1 -0
  70. package/src/workspace/parts/ModelsPart.js +765 -622
  71. package/src/workspace/styles.js +28 -1
@@ -1,202 +1,239 @@
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
+ import { treeModeRegistry } from '../../treeModeCatalog.js';
2
+ import { buildTypeCategoryTree, buildPropertyTree } from './buildFilterTrees.js';
3
+ import { TreeViewMode } from './TreeViewMode.js';
4
+
5
+ /**
6
+ * Pure-logic model tree orchestrator. Holds model, manages grouping mode,
7
+ * filter text, and selection. DOM shells (ModelTreePanel/AnalysisPanel)
8
+ * use this via getters/setters and onSelectionChange callbacks.
9
+ */
10
+ export class ModelTree {
11
+ /**
12
+ * @param {Object} model
13
+ * @param {Object} [options]
14
+ * @param {number} [options.minFilterChars] - The search text filters from this many characters on (shorter: no text filter)
15
+ * @param {number} [options.leafLimit] - How many leaves a folder shows before a "more" row (the rest on asking)
16
+ */
17
+ constructor(model, { mode = 'byLayer', viewId = null, minFilterChars = ModelTree.MIN_FILTER_CHARS, leafLimit = ModelTree.LEAF_LIMIT } = {}) {
18
+ this.model = model;
19
+ this._filterText = '';
20
+ this.minFilterChars = minFilterChars;
21
+ this.leafLimit = leafLimit;
22
+ this._typeFilter = null; // Set<string> | null (null = no filter = all types shown)
23
+ this._specializationFilter = null;
24
+ this._propertyFilter = null;
25
+ /** Whether the search text is looked for in descriptions too. */
26
+ this._inDescriptions = false;
27
+ /** What the search covers: 'model' (every element) or 'view' (those on the diagram in front). */
28
+ this._searchScope = 'model';
29
+ this._selectedId = null;
30
+ this._selectedKind = null;
31
+ this._onSelectionChange = [];
32
+ /** Which tree folders are open, by node id (TreeBuilder openState), kept while the tree is redrawn. */
33
+ this.openFolders = new Map();
34
+ this._selectedViewId = viewId || (model.views().length > 0 ? model.views()[0].id : null);
35
+ this.setMode(mode);
36
+ }
37
+
38
+ getSelectedView() {
39
+ return this._selectedViewId ? this.model.getView?.(this._selectedViewId) : null;
40
+ }
41
+
42
+ setView(viewId) {
43
+ // null means "show all elements from the model"
44
+ if (viewId !== null) {
45
+ const view = this.model.getView?.(viewId);
46
+ if (!view) {
47
+ throw new Error(`Unknown view: "${viewId}"`);
48
+ }
49
+ }
50
+ this._selectedViewId = viewId;
51
+ this._rebuild();
52
+ }
53
+
54
+ getAvailableViews() {
55
+ return this.model.views();
56
+ }
57
+
58
+ setMode(key) {
59
+ const entry = treeModeRegistry.get(key);
60
+ if (!entry) {
61
+ throw new Error(`Unknown tree mode: "${key}"`);
62
+ }
63
+ this._mode = new entry.TreeModeClass();
64
+ this._rebuild();
65
+ }
66
+
67
+ setFilter(text) {
68
+ this._filterText = text;
69
+ this._rebuild();
70
+ }
71
+
72
+ /**
73
+ * How the tree searches and shows: `minFilterChars` (the search text filters from this
74
+ * many characters on) and `leafLimit` (leaves a folder shows before its "more" row).
75
+ * Either may be left out.
76
+ */
77
+ setOptions({ minFilterChars = this.minFilterChars, leafLimit = this.leafLimit } = {}) {
78
+ this.minFilterChars = minFilterChars;
79
+ this.leafLimit = leafLimit;
80
+ this._rebuild();
81
+ }
82
+
83
+ /** The search text that filters: the text typed, or '' while it is shorter than minFilterChars. */
84
+ get filterText() {
85
+ return this._filterText.length >= this.minFilterChars ? this._filterText : '';
86
+ }
87
+
88
+ /**
89
+ * Whether something named (a view, a folder) is kept by the search text: in its name,
90
+ * or in its documentation when descriptions are searched. True with no text filtering.
91
+ */
92
+ textMatches(name, documentation = '') {
93
+ return TreeViewMode.textMatches(this.filterText, [name, ...(this._inDescriptions ? [documentation] : [])]);
94
+ }
95
+
96
+ setTypeFilter(typeIds) {
97
+ this._typeFilter = typeIds instanceof Set ? typeIds : (typeIds ? new Set(typeIds) : null);
98
+ this._rebuild();
99
+ }
100
+
101
+ setSpecializationFilter(specIds) {
102
+ this._specializationFilter = specIds instanceof Set ? specIds : (specIds ? new Set(specIds) : null);
103
+ this._rebuild();
104
+ }
105
+
106
+ setPropertyFilter(propIds) {
107
+ this._propertyFilter = propIds instanceof Set ? propIds : (propIds ? new Set(propIds) : null);
108
+ this._rebuild();
109
+ }
110
+
111
+ /** Look for the search text in descriptions (documentation) too, or not. */
112
+ setSearchInDescriptions(on) {
113
+ this._inDescriptions = !!on;
114
+ this._rebuild();
115
+ }
116
+
117
+ getSearchInDescriptions() { return this._inDescriptions; }
118
+
119
+ /** What the search covers: 'model' (every element) or 'view' (only those on the diagram in front). */
120
+ setSearchScope(scope) {
121
+ if (!ModelTree.SEARCH_SCOPES.some(([key]) => key === scope)) throw new Error(`Unknown search scope: "${scope}"`);
122
+ this._searchScope = scope;
123
+ this._rebuild();
124
+ }
125
+
126
+ getSearchScope() { return this._searchScope; }
127
+
128
+ /** The filters on now (null: none), for a filter dialog to show. */
129
+ getTypeFilter() { return this._typeFilter; }
130
+ getSpecializationFilter() { return this._specializationFilter; }
131
+ getPropertyFilter() { return this._propertyFilter; }
132
+
133
+ select(id, kind = 'element') {
134
+ this._selectedId = id;
135
+ this._selectedKind = kind;
136
+ this._notifySelectionChange();
137
+ }
138
+
139
+ getSelectedId() { return this._selectedId; }
140
+ getSelectedKind() { return this._selectedKind; }
141
+ getFilterText() { return this._filterText; }
142
+
143
+ /**
144
+ * Get the type/specialization hierarchy tree for this model.
145
+ * Ready to pass to CheckboxTreeSelector.
146
+ */
147
+ getAvailableTypeTree() {
148
+ return buildTypeCategoryTree(this.model);
149
+ }
150
+
151
+ /**
152
+ * Get the property name/value hierarchy tree for this model.
153
+ * Ready to pass to CheckboxTreeSelector.
154
+ */
155
+ getAvailablePropertyTree() {
156
+ return buildPropertyTree(this.model);
157
+ }
158
+
159
+ buildGroups() {
160
+ const view = this.getSelectedView();
161
+ const dataSource = view || this.model;
162
+ return this._mode.buildGroups(dataSource, this._filters());
163
+ }
164
+
165
+ onSelectionChange(callback) {
166
+ this._onSelectionChange.push(callback);
167
+ return () => {
168
+ const idx = this._onSelectionChange.indexOf(callback);
169
+ if (idx !== -1) this._onSelectionChange.splice(idx, 1);
170
+ };
171
+ }
172
+
173
+ _rebuild() {
174
+ // Consumers call buildGroups() as needed; listeners hear that the filters changed.
175
+ for (const cb of this._onFilterChange ?? []) cb(this);
176
+ }
177
+
178
+ /** Whether any filter is on (text, type, specialization or property). */
179
+ get filtering() {
180
+ return !!(this.filterText || this._typeFilter?.size || this._specializationFilter?.size || this._propertyFilter?.size);
181
+ }
182
+
183
+ /** What every mode filters by. @private */
184
+ _filters() {
185
+ return {
186
+ filterText: this.filterText, typeIds: this._typeFilter, specializationIds: this._specializationFilter,
187
+ propertyIds: this._propertyFilter, inDescriptions: this._inDescriptions,
188
+ };
189
+ }
190
+
191
+ /**
192
+ * The model elements the tree's filters keep, or null when no filter is
193
+ * on: over the whole model, or with the 'view' scope only those on the
194
+ * diagram in front.
195
+ * @returns {Set<string>|null}
196
+ */
197
+ matchingElementIds() {
198
+ if (!this.filtering) return null;
199
+ const groups = this._mode.buildGroups(this.model, this._filters());
200
+ const ids = new Set();
201
+ const walk = (items) => { for (const item of items ?? []) { if (item.kind === 'element') ids.add(item.id); walk(item.items); walk(item.children); } };
202
+ for (const group of groups) walk(group.items);
203
+ const onView = this._searchScope === 'view' ? this._elementIdsOnView() : null;
204
+ return onView ? new Set([...ids].filter(id => onView.has(id))) : ids;
205
+ }
206
+
207
+ /** The model elements drawn on the diagram in front, or null when there is none. @private */
208
+ _elementIdsOnView() {
209
+ const view = this.model.currentView;
210
+ if (!view?.elementInstances) return null;
211
+ return new Set([...view.elementInstances.values()].map(i => i.elementId).filter(Boolean));
212
+ }
213
+
214
+ /** Call `fn(tree)` whenever a filter changes. Returns an unsubscribe function. */
215
+ onFilterChange(fn) {
216
+ (this._onFilterChange ??= []).push(fn);
217
+ return () => { this._onFilterChange = this._onFilterChange.filter(f => f !== fn); };
218
+ }
219
+
220
+ _notifySelectionChange() {
221
+ const data = {
222
+ id: this._selectedId,
223
+ kind: this._selectedKind,
224
+ element: this._selectedKind === 'element' ? this.model.getElement(this._selectedId) : null,
225
+ relationship: this._selectedKind === 'relationship' ? this.model.getRelationship(this._selectedId) : null,
226
+ view: this._selectedKind === 'view' ? this.model.getView?.(this._selectedId) : null,
227
+ };
228
+ for (const cb of this._onSelectionChange) {
229
+ cb(data);
230
+ }
231
+ }
232
+ }
233
+
234
+ /** What the search can cover: [key, label]. */
235
+ ModelTree.SEARCH_SCOPES = Object.freeze([['model', 'Whole model'], ['view', 'This diagram']]);
236
+ /** The search text filters from this many characters on, unless a tree is given its own. */
237
+ ModelTree.MIN_FILTER_CHARS = 3;
238
+ /** The leaves a folder shows before its "more" row, unless a tree is given its own. */
239
+ ModelTree.LEAF_LIMIT = 8;