@peter.prib/renderengine 0.2.6 → 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.
- package/README.md +4 -0
- package/REFERENCE.md +180 -2
- package/dist/renderengine-workspace.umd.js +8704 -5310
- package/dist/renderengine.umd.js +5675 -3398
- package/examples/archimate.html +68 -66
- package/examples/data-model.html +223 -13
- package/package.json +1 -1
- package/src/bian.js +182 -0
- package/src/charting/DiagramEditor.js +5 -26
- package/src/charting/DiagramTypeRegistry.js +8 -2
- package/src/charting/ModelCheckDialog.js +63 -0
- package/src/charting/ModelChecker.js +26 -0
- package/src/charting/ViewTabStrip.js +94 -81
- package/src/charting/datamodel/BianLandscapeReader.js +185 -0
- package/src/charting/datamodel/BianSemanticApiReader.js +183 -0
- package/src/charting/datamodel/DataModel.js +356 -10
- package/src/charting/datamodel/DataModelChecker.js +92 -0
- package/src/charting/datamodel/DataModelCompared.js +127 -0
- package/src/charting/datamodel/DataModelFileFormat.js +33 -0
- package/src/charting/datamodel/DataModelPerspective.js +45 -0
- package/src/charting/datamodel/DataType.js +108 -0
- package/src/charting/datamodel/EnumerationType.js +51 -0
- package/src/charting/datamodel/ServiceDomain.js +177 -0
- package/src/charting/datamodel/ServiceLandscape.js +104 -0
- package/src/charting/datamodel/StructuredType.js +63 -0
- package/src/charting/datamodel/index.js +10 -0
- package/src/charting/diagramModel/MergeModelCommand.js +43 -37
- package/src/charting/diagramModel/OpenViewList.js +75 -0
- package/src/charting/diagramModel/ViewSetChecker.js +22 -34
- package/src/charting/diagrams/DataModelRelationshipStrategy.js +12 -4
- package/src/charting/diagrams/EdgeGeometry.js +578 -553
- package/src/charting/diagrams/GraphDiagram.js +11 -1
- package/src/charting/diagrams/NodeGeometry.js +169 -158
- package/src/charting/glyphs/PersonGlyph.js +24 -23
- package/src/index.js +2666 -2664
- package/src/workspace/EditorShell.js +166 -67
- package/src/workspace/PanelLayout.js +56 -0
- package/src/workspace/layout/TabsNode.js +205 -191
- package/src/workspace/parts/ArchimateCanvasPart.js +5 -7
- package/src/workspace/parts/DataModelPart.js +339 -29
- package/src/workspace/parts/DiagramEditorPart.js +2 -8
- package/src/workspace/parts/EditorPart.js +461 -407
- package/src/workspace/parts/ModelFile.js +216 -187
- package/src/workspace/parts/ModelsPart.js +483 -192
- package/src/workspace/storage/BackupFolder.js +145 -0
- package/src/workspace/storage/KeyValueStore.js +92 -0
- package/src/workspace/storage/ModelLibrary.js +65 -0
- package/src/workspace/workArea.js +417 -413
- package/src/yaml/YamlReader.js +372 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { DataType } from './DataType.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* StructuredType — a data type made of named fields, each of a type (a plain
|
|
5
|
+
* type's name or another data type's): BIAN's Amount, Datetime, Identifier.
|
|
6
|
+
* `fields` as `[{ name, type?, description?, iso20022? }]`. Drawn as a box with
|
|
7
|
+
* a row per field, joined to the data types its fields are.
|
|
8
|
+
*/
|
|
9
|
+
export class StructuredType extends DataType {
|
|
10
|
+
static kind = 'structure';
|
|
11
|
+
static kindLabel = 'Structure';
|
|
12
|
+
|
|
13
|
+
/** @param {Object} spec - As DataType, plus `fields: [{ name, type?, description?, iso20022? }]` */
|
|
14
|
+
constructor({ fields = [], ...spec } = {}) {
|
|
15
|
+
super(spec);
|
|
16
|
+
this.fields = fields.map(f => ({
|
|
17
|
+
name: String(f.name),
|
|
18
|
+
...(f.type && { type: String(f.type) }),
|
|
19
|
+
...(f.description && { description: f.description }),
|
|
20
|
+
...(f.iso20022 && { iso20022: f.iso20022 }),
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
summary() {
|
|
25
|
+
return `${this.name}: ${this.fields.map(f => (f.type ? `${f.name} (${f.type})` : f.name)).join(', ')}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
rows() {
|
|
29
|
+
return this.fields.map(f => ({ name: f.name, ...(f.type && { type: f.type }) }));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
uses() {
|
|
33
|
+
return [...super.uses(), ...this.fields.map(f => f.type).filter(Boolean)];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
toJSON() {
|
|
37
|
+
return { ...super.toJSON(), fields: this.fields.map(f => ({ ...f })) };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A plain type's fields, then its fields a line each: `name | type`. */
|
|
41
|
+
static fields(model, type = null) {
|
|
42
|
+
return [
|
|
43
|
+
...DataType.fields(model, type),
|
|
44
|
+
{
|
|
45
|
+
key: 'fields', label: 'Fields', type: 'textarea', rows: 6,
|
|
46
|
+
value: (type?.fields ?? []).map(f => (f.type ? `${f.name} | ${f.type}` : f.name)).join('\n'),
|
|
47
|
+
hint: 'One a line: name | type (a plain type, or a data type by name)',
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static specFrom(values, model, type = null) {
|
|
53
|
+
return {
|
|
54
|
+
...super.specFrom(values),
|
|
55
|
+
fields: DataType.lines(values.fields).map(([name, fieldType]) => {
|
|
56
|
+
const kept = type?.fields.find(f => f.name === name);
|
|
57
|
+
return { ...kept, name, ...(fieldType ? { type: fieldType } : { type: undefined }) };
|
|
58
|
+
}).map(f => JSON.parse(JSON.stringify(f))),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
DataType.register(StructuredType);
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
export { DataModel } from './DataModel.js';
|
|
2
2
|
export { DataModelPerspective } from './DataModelPerspective.js';
|
|
3
3
|
export { DataModelContext } from './DataModelContext.js';
|
|
4
|
+
export { ServiceDomain } from './ServiceDomain.js';
|
|
5
|
+
export { DataModelChecker } from './DataModelChecker.js';
|
|
6
|
+
export { DataType } from './DataType.js';
|
|
7
|
+
export { EnumerationType } from './EnumerationType.js';
|
|
8
|
+
export { StructuredType } from './StructuredType.js';
|
|
9
|
+
export { ServiceLandscape } from './ServiceLandscape.js';
|
|
10
|
+
export { BianLandscapeReader } from './BianLandscapeReader.js';
|
|
11
|
+
export { BianSemanticApiReader } from './BianSemanticApiReader.js';
|
|
4
12
|
export { LevelSchema } from './LevelSchema.js';
|
|
5
13
|
export { LevelMapping } from './LevelMapping.js';
|
|
14
|
+
export { DataModelFileFormat } from './DataModelFileFormat.js';
|
|
15
|
+
export { LevelEntitiesCompared, LevelRelationshipsCompared, MappingsCompared, ContextsCompared, PerspectivesCompared, DataTypesCompared } from './DataModelCompared.js';
|
|
@@ -1,37 +1,43 @@
|
|
|
1
|
-
import { Command } from '../CommandHistory.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* MergeModelCommand — things from another model brought into this one
|
|
5
|
-
* (ModelComparison#mergeCommand): each step a ComparedKind's `bring`, in the
|
|
6
|
-
* order given (what is needed first). One undo step; undo takes each back,
|
|
7
|
-
* last first.
|
|
8
|
-
*/
|
|
9
|
-
export class MergeModelCommand extends Command {
|
|
10
|
-
/** A merge is no edit on one view: undoing it shows no other. */
|
|
11
|
-
viewId = null;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @param {ViewSetModel} model - Merged into
|
|
15
|
-
* @param {ViewSetModel} from - Merged from
|
|
16
|
-
* @param {Array<{kind: ComparedKind, item: Object}>} steps
|
|
17
|
-
* @param {string} [label]
|
|
18
|
-
*/
|
|
19
|
-
constructor(model, from, steps, label = `Merge ${steps.length} item${steps.length === 1 ? '' : 's'} from "${from.name || 'another model'}"`) {
|
|
20
|
-
super(label);
|
|
21
|
-
this.model = model;
|
|
22
|
-
this.from = from;
|
|
23
|
-
this.steps = steps;
|
|
24
|
-
this._undos = [];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
execute() {
|
|
28
|
-
this._undos = this.steps.map(({ kind, item }) => kind.bring(this.model, item, this.from));
|
|
29
|
-
this.
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
undo() {
|
|
33
|
-
for (const undo of [...this._undos].reverse()) undo();
|
|
34
|
-
this._undos = [];
|
|
35
|
-
this.
|
|
36
|
-
}
|
|
37
|
-
|
|
1
|
+
import { Command } from '../CommandHistory.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MergeModelCommand — things from another model brought into this one
|
|
5
|
+
* (ModelComparison#mergeCommand): each step a ComparedKind's `bring`, in the
|
|
6
|
+
* order given (what is needed first). One undo step; undo takes each back,
|
|
7
|
+
* last first.
|
|
8
|
+
*/
|
|
9
|
+
export class MergeModelCommand extends Command {
|
|
10
|
+
/** A merge is no edit on one view: undoing it shows no other. */
|
|
11
|
+
viewId = null;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {ViewSetModel} model - Merged into
|
|
15
|
+
* @param {ViewSetModel} from - Merged from
|
|
16
|
+
* @param {Array<{kind: ComparedKind, item: Object}>} steps
|
|
17
|
+
* @param {string} [label]
|
|
18
|
+
*/
|
|
19
|
+
constructor(model, from, steps, label = `Merge ${steps.length} item${steps.length === 1 ? '' : 's'} from "${from.name || 'another model'}"`) {
|
|
20
|
+
super(label);
|
|
21
|
+
this.model = model;
|
|
22
|
+
this.from = from;
|
|
23
|
+
this.steps = steps;
|
|
24
|
+
this._undos = [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
execute() {
|
|
28
|
+
this._undos = this.steps.map(({ kind, item }) => kind.bring(this.model, item, this.from));
|
|
29
|
+
this._changed();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
undo() {
|
|
33
|
+
for (const undo of [...this._undos].reverse()) undo();
|
|
34
|
+
this._undos = [];
|
|
35
|
+
this._changed();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The model changed: a view set tells its listeners; a model without views (a DataModel) says it was edited. @private */
|
|
39
|
+
_changed() {
|
|
40
|
+
this.model._notify?.();
|
|
41
|
+
this.model.edited?.();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenViewList — the views of a model that have a tab, in tab order, as a
|
|
3
|
+
* browser keeps its tabs: closing a tab leaves the view in the model, and a
|
|
4
|
+
* view is opened by going to it (from the tree, a search, a new view). It
|
|
5
|
+
* starts with the view in front only, so a model opened or imported with many
|
|
6
|
+
* views does not fill the tab strip.
|
|
7
|
+
*
|
|
8
|
+
* It follows the model: `sync()` drops views since deleted and opens the one in
|
|
9
|
+
* front, unless paused (`while`: a report passing through every view opens none).
|
|
10
|
+
*/
|
|
11
|
+
export class OpenViewList {
|
|
12
|
+
/**
|
|
13
|
+
* @param {ViewSetModel} model
|
|
14
|
+
* @param {Iterable<string>} [ids] - Those open to start with (default: the view in front)
|
|
15
|
+
*/
|
|
16
|
+
constructor(model, ids = null) {
|
|
17
|
+
this.model = model;
|
|
18
|
+
this.ids = ids ? [...ids] : (model.currentViewId ? [model.currentViewId] : []);
|
|
19
|
+
this._paused = 0;
|
|
20
|
+
this.sync();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Follow the model: deleted views gone, the one in front open (last). Returns the open views. */
|
|
24
|
+
sync() {
|
|
25
|
+
this.ids = this.ids.filter(id => this.model.getView(id));
|
|
26
|
+
const current = this.model.currentViewId;
|
|
27
|
+
if (!this._paused && current && !this.ids.includes(current)) this.ids.push(current);
|
|
28
|
+
return this.views();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The open views, in tab order. */
|
|
32
|
+
views() {
|
|
33
|
+
return this.ids.map(id => this.model.getView(id)).filter(Boolean);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
isOpen(id) {
|
|
37
|
+
return this.ids.includes(id);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Open a view's tab (if not open) and bring it to the front. */
|
|
41
|
+
open(id) {
|
|
42
|
+
if (!this.model.getView(id)) return false;
|
|
43
|
+
if (!this.ids.includes(id)) this.ids.push(id);
|
|
44
|
+
if (this.model.currentViewId !== id) this.model.switchTo(id);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Whether a tab may be closed: one always stays open. */
|
|
49
|
+
canClose(id) {
|
|
50
|
+
return this.ids.includes(id) && this.ids.length > 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Close a view's tab; the view stays in the model. Closing the tab in front
|
|
55
|
+
* brings its neighbour (the next, else the one before) to the front.
|
|
56
|
+
* @returns {boolean} whether it closed
|
|
57
|
+
*/
|
|
58
|
+
close(id) {
|
|
59
|
+
if (!this.canClose(id)) return false;
|
|
60
|
+
const index = this.ids.indexOf(id);
|
|
61
|
+
this.ids.splice(index, 1);
|
|
62
|
+
if (this.model.currentViewId === id) this.model.switchTo(this.ids[Math.min(index, this.ids.length - 1)]);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Run `fn` with the list paused: going through views (a report's pictures) opens none. */
|
|
67
|
+
while(fn) {
|
|
68
|
+
this._paused++;
|
|
69
|
+
try { return fn(); } finally { this._paused--; }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
toJSON() {
|
|
73
|
+
return [...this.ids];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -1,34 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Every finding. @returns {Array<Object>} */
|
|
24
|
-
run() {
|
|
25
|
-
return this.checks().flatMap(check => check.call(this));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Views with nothing on them (a template is left out: an empty one is still a start). */
|
|
29
|
-
emptyViews() {
|
|
30
|
-
return this.model.views()
|
|
31
|
-
.filter(v => !v.template && v.isEmpty())
|
|
32
|
-
.map(v => ({ code: 'empty-view', severity: 'info', kind: 'view', ids: [v.id], message: `View "${v.title || v.name || v.id}" is empty` }));
|
|
33
|
-
}
|
|
34
|
-
}
|
|
1
|
+
import { ModelChecker } from '../ModelChecker.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ViewSetChecker — checks over any ViewSetModel (a template editor's
|
|
5
|
+
* DiagramModel, an ArchimateModel): what is wrong or worth a look across its
|
|
6
|
+
* views (a ModelChecker: findings, checks listed by `checks()`). A notation
|
|
7
|
+
* adds its own checks in a subclass (ArchimateModelChecker). `kind` in a
|
|
8
|
+
* finding is 'view' here; a subclass adds its own.
|
|
9
|
+
*/
|
|
10
|
+
export class ViewSetChecker extends ModelChecker {
|
|
11
|
+
/** The checks to run, in the order their findings are listed. */
|
|
12
|
+
checks() {
|
|
13
|
+
return [this.emptyViews];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Views with nothing on them (a template is left out: an empty one is still a start). */
|
|
17
|
+
emptyViews() {
|
|
18
|
+
return this.model.views()
|
|
19
|
+
.filter(v => !v.template && v.isEmpty())
|
|
20
|
+
.map(v => ({ code: 'empty-view', severity: 'info', kind: 'view', ids: [v.id], message: `View "${v.title || v.name || v.id}" is empty` }));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -42,7 +42,7 @@ export class RelationshipAsNodeStrategy extends DataModelRelationshipStrategy {
|
|
|
42
42
|
/**
|
|
43
43
|
* Where a diamond between two places goes: their midpoint when it is clear
|
|
44
44
|
* of everything in `occupied`, else the nearest clear spot stepping out
|
|
45
|
-
* across the line, one side then the other;
|
|
45
|
+
* across the line, one side then the other; when none is clear, the least covered.
|
|
46
46
|
* @returns {{x: number, y: number}}
|
|
47
47
|
*/
|
|
48
48
|
static spotFor(from, to, occupied) {
|
|
@@ -50,14 +50,22 @@ export class RelationshipAsNodeStrategy extends DataModelRelationshipStrategy {
|
|
|
50
50
|
const mid = { x: (from.x + to.x) / 2, y: (from.y + to.y) / 2 };
|
|
51
51
|
const length = Math.hypot(to.x - from.x, to.y - from.y) || 1;
|
|
52
52
|
const across = { x: -(to.y - from.y) / length, y: (to.x - from.x) / length };
|
|
53
|
-
|
|
53
|
+
// How much of a diamond at a spot is covered by what is there (0: clear).
|
|
54
|
+
const covered = ({ x, y }) => occupied.reduce((sum, o) => {
|
|
55
|
+
const w = Math.min(x + width / 2, o.x1) - Math.max(x - width / 2, o.x0);
|
|
56
|
+
const h = Math.min(y + height / 2, o.y1) - Math.max(y - height / 2, o.y0);
|
|
57
|
+
return sum + (w > 0 && h > 0 ? w * h : 0);
|
|
58
|
+
}, 0);
|
|
59
|
+
let best = mid, least = Infinity;
|
|
54
60
|
for (let k = 0; k <= RelationshipAsNodeStrategy.TRIES; k++) {
|
|
55
61
|
for (const side of k ? [1, -1] : [0]) {
|
|
56
62
|
const at = { x: mid.x + across.x * side * k * RelationshipAsNodeStrategy.STEP, y: mid.y + across.y * side * k * RelationshipAsNodeStrategy.STEP };
|
|
57
|
-
|
|
63
|
+
const c = covered(at);
|
|
64
|
+
if (c === 0) return at;
|
|
65
|
+
if (c < least) { best = at; least = c; }
|
|
58
66
|
}
|
|
59
67
|
}
|
|
60
|
-
return
|
|
68
|
+
return best; // none clear: the least covered
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
apply(relationships, { notation, entity = () => undefined, occupied = [] }) {
|