@peter.prib/renderengine 0.2.4 → 0.2.6
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/README.md +215 -213
- package/REFERENCE.md +99 -7
- package/dist/renderengine-workspace.umd.js +13168 -11966
- package/dist/renderengine.umd.js +7439 -6472
- package/examples/data-model.html +324 -0
- package/package.json +70 -68
- package/src/charting/CheckboxTreeSelector.js +373 -304
- package/src/charting/DiagramEditor.js +3372 -3330
- package/src/charting/DiagramTypeRegistry.js +365 -326
- package/src/charting/Palette.js +119 -119
- package/src/charting/TemplateCatalog.js +61 -59
- package/src/charting/archimate/ArchimateCanvasEditor.js +825 -815
- package/src/charting/archimate/ArchimateElementTemplate.js +208 -195
- package/src/charting/datamodel/DataModel.js +299 -0
- package/src/charting/datamodel/DataModelContext.js +108 -0
- package/src/charting/datamodel/DataModelPerspective.js +84 -0
- package/src/charting/datamodel/LevelMapping.js +38 -0
- package/src/charting/datamodel/LevelSchema.js +41 -0
- package/src/charting/datamodel/index.js +5 -0
- package/src/charting/diagrams/DataModelDiagram.js +160 -86
- package/src/charting/diagrams/DataModelRelationshipStrategy.js +86 -38
- package/src/charting/diagrams/nesting.js +31 -0
- package/src/charting/modelTree/ModelTree.js +202 -164
- package/src/charting/modelTree/ModelTreePanel.js +36 -3
- package/src/charting/modelTree/TreeViewMode.js +131 -110
- package/src/charting/modelTree/modes/FlatTreeMode.js +32 -46
- package/src/charting/modelTree/modes/LayerGroupedTreeMode.js +47 -62
- package/src/charting/modelTree/modes/TypeGroupedTreeMode.js +46 -60
- package/src/datamodel.js +132 -122
- package/src/diagramCatalog.js +35 -30
- package/src/index.js +6 -1
- package/src/workspace/index.js +1 -0
- package/src/workspace/parts/ArchimateCanvasPart.js +5 -2
- package/src/workspace/parts/ChartPart.js +6 -1
- package/src/workspace/parts/DataModelPart.js +234 -0
|
@@ -1,110 +1,131 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Abstract base class for tree grouping/presentation modes.
|
|
3
|
-
* Subclasses implement buildGroups() to organize model elements into groups.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export class TreeViewMode {
|
|
7
|
-
static key = null; // registry key; subclasses override
|
|
8
|
-
static label = ''; // human-readable label; subclasses override
|
|
9
|
-
|
|
10
|
-
constructor() {
|
|
11
|
-
if (new.target === TreeViewMode) {
|
|
12
|
-
throw new TypeError('TreeViewMode is abstract — subclass it');
|
|
13
|
-
}
|
|
14
|
-
if (!this.constructor.key) {
|
|
15
|
-
throw new TypeError(`${this.constructor.name} must define static key`);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Build groups from model elements.
|
|
21
|
-
* @param {ArchimateModel} model
|
|
22
|
-
* @param {Object} opts
|
|
23
|
-
* @param {string} opts.filterText - text to filter by (case-insensitive name/type/layer)
|
|
24
|
-
* @param {
|
|
25
|
-
* @param {Set<string>} opts.
|
|
26
|
-
* @param {Set<string>} opts.
|
|
27
|
-
* @
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (!
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Abstract base class for tree grouping/presentation modes.
|
|
3
|
+
* Subclasses implement buildGroups() to organize model elements into groups.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class TreeViewMode {
|
|
7
|
+
static key = null; // registry key; subclasses override
|
|
8
|
+
static label = ''; // human-readable label; subclasses override
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
if (new.target === TreeViewMode) {
|
|
12
|
+
throw new TypeError('TreeViewMode is abstract — subclass it');
|
|
13
|
+
}
|
|
14
|
+
if (!this.constructor.key) {
|
|
15
|
+
throw new TypeError(`${this.constructor.name} must define static key`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Build groups from model elements.
|
|
21
|
+
* @param {ArchimateModel} model
|
|
22
|
+
* @param {Object} opts
|
|
23
|
+
* @param {string} opts.filterText - text to filter by (case-insensitive name/type/layer)
|
|
24
|
+
* @param {boolean} [opts.inDescriptions] - the text may be found in an item's description (documentation) too
|
|
25
|
+
* @param {Set<string>} opts.typeIds - filter by element type (e.g. Set(['type:business-actor']))
|
|
26
|
+
* @param {Set<string>} opts.specializationIds - filter by specialization (e.g. Set(['spec:Microservice']))
|
|
27
|
+
* @param {Set<string>} opts.propertyIds - filter by property values (e.g. Set(['propval:Owner=Alice']))
|
|
28
|
+
* @returns {Array<{label, items: Array<{id, kind, label, type}>}>}
|
|
29
|
+
*/
|
|
30
|
+
buildGroups(model, { filterText = '', typeIds = null, specializationIds = null, propertyIds = null, inDescriptions = false } = {}) {
|
|
31
|
+
throw new Error(`${this.constructor.name} must implement buildGroups()`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A tree item for a model element: what the filters look at. @protected */
|
|
35
|
+
_elementItem(el) {
|
|
36
|
+
return {
|
|
37
|
+
id: el.id, kind: 'element', label: el.name || '(unnamed)', type: el.type, layer: el.layer, icon: el.icon,
|
|
38
|
+
specialization: el.specialization, properties: el.properties, description: el.documentation ?? el.description ?? '',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A tree item for a relationship. @protected */
|
|
43
|
+
_relationshipItem(rel) {
|
|
44
|
+
return {
|
|
45
|
+
id: rel.id, kind: 'relationship', label: rel.label || `${rel.source} → ${rel.target}`, type: rel.kind,
|
|
46
|
+
properties: rel.properties, description: rel.documentation ?? rel.description ?? '',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The items of `list` that every filter keeps. @protected */
|
|
51
|
+
_filtered(list, { filterText = '', typeIds = null, specializationIds = null, propertyIds = null, inDescriptions = false } = {}) {
|
|
52
|
+
return list.filter(item => this._matchesFilter(item, { text: filterText, typeIds, specializationIds, propertyIds, inDescriptions }));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if an item matches all active filters (text + type + specialization + properties).
|
|
57
|
+
* Subclasses can override for custom filtering logic.
|
|
58
|
+
* @protected
|
|
59
|
+
*/
|
|
60
|
+
_matchesFilter(item, { text = '', typeIds = null, specializationIds = null, propertyIds = null, inDescriptions = false } = {}) {
|
|
61
|
+
// Text filter (label or type; the description too when asked)
|
|
62
|
+
if (text) {
|
|
63
|
+
const textLower = text.toLowerCase();
|
|
64
|
+
const fields = [item.label, item.type, ...(inDescriptions ? [item.description] : [])];
|
|
65
|
+
if (!fields.some(f => String(f ?? '').toLowerCase().includes(textLower))) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Type filter (elements only; 'type:business-actor' format)
|
|
71
|
+
if (typeIds && typeIds.size > 0 && item.kind === 'element') {
|
|
72
|
+
const typeId = `type:${item.type}`;
|
|
73
|
+
if (!typeIds.has(typeId)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Specialization filter (elements only; 'spec:Microservice' format)
|
|
79
|
+
if (specializationIds && specializationIds.size > 0 && item.kind === 'element') {
|
|
80
|
+
if (!item.specialization) {
|
|
81
|
+
// Elements without specialization don't match specialization filters
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
const specId = `spec:${item.specialization}`;
|
|
85
|
+
if (!specializationIds.has(specId)) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Property filter ('propval:PropertyName=Value' format; both elements and relationships)
|
|
91
|
+
if (propertyIds && propertyIds.size > 0) {
|
|
92
|
+
if (!Array.isArray(item.properties)) {
|
|
93
|
+
// No properties defined — doesn't match property filter
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
// Check if any property matches the filter
|
|
97
|
+
const matches = item.properties.some(prop => {
|
|
98
|
+
const propId = `propval:${prop.name}=${prop.value}`;
|
|
99
|
+
return propertyIds.has(propId);
|
|
100
|
+
});
|
|
101
|
+
if (!matches) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Group items into {label, items} structure. @protected */
|
|
110
|
+
_group(items, keyFn, labelFn) {
|
|
111
|
+
const groups = new Map();
|
|
112
|
+
for (const item of items) {
|
|
113
|
+
const key = keyFn(item);
|
|
114
|
+
if (!groups.has(key)) {
|
|
115
|
+
groups.set(key, { label: labelFn(key), items: [] });
|
|
116
|
+
}
|
|
117
|
+
groups.get(key).items.push(item);
|
|
118
|
+
}
|
|
119
|
+
return Array.from(groups.values());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Get elements from model (handles both ArchimateModel.elements() and ArchimateView.elements). @protected */
|
|
123
|
+
_getElements(model) {
|
|
124
|
+
return typeof model.elements === 'function' ? model.elements() : model.elements;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Get relationships from model (handles both ArchimateModel.relationships() and ArchimateView.relationships). @protected */
|
|
128
|
+
_getRelationships(model) {
|
|
129
|
+
return typeof model.relationships === 'function' ? model.relationships() : model.relationships;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -1,46 +1,32 @@
|
|
|
1
|
-
import { TreeViewMode } from '../TreeViewMode.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Tree mode: flat alphabetical list of all elements and relationships.
|
|
5
|
-
*/
|
|
6
|
-
export class FlatTreeMode extends TreeViewMode {
|
|
7
|
-
static key = 'flat';
|
|
8
|
-
static label = 'Flat';
|
|
9
|
-
|
|
10
|
-
buildGroups(model,
|
|
11
|
-
// Build element items (include specialization and properties for filtering)
|
|
12
|
-
const elementItems = this._getElements(model).map(el => (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const filtered = allItems.filter(item => this._matchesFilter(item, { text: filterText, typeIds, specializationIds, propertyIds }));
|
|
34
|
-
|
|
35
|
-
// Sort alphabetically
|
|
36
|
-
filtered.sort((a, b) => a.label.localeCompare(b.label));
|
|
37
|
-
|
|
38
|
-
// Single group containing all
|
|
39
|
-
if (filtered.length === 0) return [];
|
|
40
|
-
|
|
41
|
-
return [{
|
|
42
|
-
label: '',
|
|
43
|
-
items: filtered,
|
|
44
|
-
}];
|
|
45
|
-
}
|
|
46
|
-
}
|
|
1
|
+
import { TreeViewMode } from '../TreeViewMode.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tree mode: flat alphabetical list of all elements and relationships.
|
|
5
|
+
*/
|
|
6
|
+
export class FlatTreeMode extends TreeViewMode {
|
|
7
|
+
static key = 'flat';
|
|
8
|
+
static label = 'Flat';
|
|
9
|
+
|
|
10
|
+
buildGroups(model, filters = {}) {
|
|
11
|
+
// Build element items (include specialization and properties for filtering)
|
|
12
|
+
const elementItems = this._getElements(model).map(el => this._elementItem(el));
|
|
13
|
+
|
|
14
|
+
// Build relationship items
|
|
15
|
+
const relationshipItems = this._getRelationships(model).map(rel => this._relationshipItem(rel));
|
|
16
|
+
|
|
17
|
+
// Combine and filter with all criteria
|
|
18
|
+
const allItems = [...elementItems, ...relationshipItems];
|
|
19
|
+
const filtered = this._filtered(allItems, filters);
|
|
20
|
+
|
|
21
|
+
// Sort alphabetically
|
|
22
|
+
filtered.sort((a, b) => a.label.localeCompare(b.label));
|
|
23
|
+
|
|
24
|
+
// Single group containing all
|
|
25
|
+
if (filtered.length === 0) return [];
|
|
26
|
+
|
|
27
|
+
return [{
|
|
28
|
+
label: '',
|
|
29
|
+
items: filtered,
|
|
30
|
+
}];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -1,62 +1,47 @@
|
|
|
1
|
-
import { TreeViewMode } from '../TreeViewMode.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Tree mode: groups elements by ArchiMate layer, then relationships.
|
|
5
|
-
* Replicates the demo's current behavior.
|
|
6
|
-
*/
|
|
7
|
-
export class LayerGroupedTreeMode extends TreeViewMode {
|
|
8
|
-
static key = 'byLayer';
|
|
9
|
-
static label = 'By Layer';
|
|
10
|
-
|
|
11
|
-
buildGroups(model,
|
|
12
|
-
// Build element items (include specialization and properties for filtering)
|
|
13
|
-
const elementItems = this._getElements(model).map(el => (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
properties: rel.properties,
|
|
49
|
-
}));
|
|
50
|
-
|
|
51
|
-
const filtered_rels = relationshipItems.filter(item => this._matchesFilter(item, { text: filterText, typeIds, specializationIds, propertyIds }));
|
|
52
|
-
|
|
53
|
-
if (filtered_rels.length > 0) {
|
|
54
|
-
layerGroups.push({
|
|
55
|
-
label: 'Relationships',
|
|
56
|
-
items: filtered_rels,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return layerGroups;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
import { TreeViewMode } from '../TreeViewMode.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tree mode: groups elements by ArchiMate layer, then relationships.
|
|
5
|
+
* Replicates the demo's current behavior.
|
|
6
|
+
*/
|
|
7
|
+
export class LayerGroupedTreeMode extends TreeViewMode {
|
|
8
|
+
static key = 'byLayer';
|
|
9
|
+
static label = 'By Layer';
|
|
10
|
+
|
|
11
|
+
buildGroups(model, filters = {}) {
|
|
12
|
+
// Build element items (include specialization and properties for filtering)
|
|
13
|
+
const elementItems = this._getElements(model).map(el => this._elementItem(el));
|
|
14
|
+
|
|
15
|
+
// Filter elements with all criteria
|
|
16
|
+
const filtered = this._filtered(elementItems, filters);
|
|
17
|
+
|
|
18
|
+
// Group by layer
|
|
19
|
+
const layerGroups = this._group(
|
|
20
|
+
filtered,
|
|
21
|
+
item => item.layer || 'Other',
|
|
22
|
+
layer => layer
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Sort layers in a sensible order (layers are lowercase-kebab)
|
|
26
|
+
const layerOrder = ['strategy', 'business', 'application', 'technology', 'physical', 'implementation', 'motivation', 'Other'];
|
|
27
|
+
layerGroups.sort((a, b) => {
|
|
28
|
+
const aIdx = layerOrder.indexOf(a.label);
|
|
29
|
+
const bIdx = layerOrder.indexOf(b.label);
|
|
30
|
+
return (aIdx === -1 ? 999 : aIdx) - (bIdx === -1 ? 999 : bIdx);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Add relationships group
|
|
34
|
+
const relationshipItems = this._getRelationships(model).map(rel => this._relationshipItem(rel));
|
|
35
|
+
|
|
36
|
+
const filtered_rels = this._filtered(relationshipItems, filters);
|
|
37
|
+
|
|
38
|
+
if (filtered_rels.length > 0) {
|
|
39
|
+
layerGroups.push({
|
|
40
|
+
label: 'Relationships',
|
|
41
|
+
items: filtered_rels,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return layerGroups;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,60 +1,46 @@
|
|
|
1
|
-
import { TreeViewMode } from '../TreeViewMode.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Tree mode: groups elements by ArchiMate type, then relationships.
|
|
5
|
-
*/
|
|
6
|
-
export class TypeGroupedTreeMode extends TreeViewMode {
|
|
7
|
-
static key = 'byType';
|
|
8
|
-
static label = 'By Type';
|
|
9
|
-
|
|
10
|
-
buildGroups(model,
|
|
11
|
-
// Build element items (include specialization and properties for filtering)
|
|
12
|
-
const elementItems = this._getElements(model).map(el => (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}));
|
|
48
|
-
|
|
49
|
-
const filtered_rels = relationshipItems.filter(item => this._matchesFilter(item, { text: filterText, typeIds, specializationIds, propertyIds }));
|
|
50
|
-
|
|
51
|
-
if (filtered_rels.length > 0) {
|
|
52
|
-
typeGroups.push({
|
|
53
|
-
label: 'Relationships',
|
|
54
|
-
items: filtered_rels.sort((a, b) => a.label.localeCompare(b.label)),
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return typeGroups;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
1
|
+
import { TreeViewMode } from '../TreeViewMode.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tree mode: groups elements by ArchiMate type, then relationships.
|
|
5
|
+
*/
|
|
6
|
+
export class TypeGroupedTreeMode extends TreeViewMode {
|
|
7
|
+
static key = 'byType';
|
|
8
|
+
static label = 'By Type';
|
|
9
|
+
|
|
10
|
+
buildGroups(model, filters = {}) {
|
|
11
|
+
// Build element items (include specialization and properties for filtering)
|
|
12
|
+
const elementItems = this._getElements(model).map(el => this._elementItem(el));
|
|
13
|
+
|
|
14
|
+
// Filter elements with all criteria
|
|
15
|
+
const filtered = this._filtered(elementItems, filters);
|
|
16
|
+
|
|
17
|
+
// Group by type
|
|
18
|
+
const typeGroups = this._group(
|
|
19
|
+
filtered,
|
|
20
|
+
item => item.type,
|
|
21
|
+
type => type || 'Other'
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// Sort alphabetically within each group
|
|
25
|
+
typeGroups.forEach(group => {
|
|
26
|
+
group.items.sort((a, b) => a.label.localeCompare(b.label));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Sort groups alphabetically
|
|
30
|
+
typeGroups.sort((a, b) => a.label.localeCompare(b.label));
|
|
31
|
+
|
|
32
|
+
// Add relationships group
|
|
33
|
+
const relationshipItems = this._getRelationships(model).map(rel => this._relationshipItem(rel));
|
|
34
|
+
|
|
35
|
+
const filtered_rels = this._filtered(relationshipItems, filters);
|
|
36
|
+
|
|
37
|
+
if (filtered_rels.length > 0) {
|
|
38
|
+
typeGroups.push({
|
|
39
|
+
label: 'Relationships',
|
|
40
|
+
items: filtered_rels.sort((a, b) => a.label.localeCompare(b.label)),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return typeGroups;
|
|
45
|
+
}
|
|
46
|
+
}
|