@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.
Files changed (49) hide show
  1. package/README.md +4 -0
  2. package/REFERENCE.md +180 -2
  3. package/dist/renderengine-workspace.umd.js +8704 -5310
  4. package/dist/renderengine.umd.js +5675 -3398
  5. package/examples/archimate.html +68 -66
  6. package/examples/data-model.html +223 -13
  7. package/package.json +1 -1
  8. package/src/bian.js +182 -0
  9. package/src/charting/DiagramEditor.js +5 -26
  10. package/src/charting/DiagramTypeRegistry.js +8 -2
  11. package/src/charting/ModelCheckDialog.js +63 -0
  12. package/src/charting/ModelChecker.js +26 -0
  13. package/src/charting/ViewTabStrip.js +94 -81
  14. package/src/charting/datamodel/BianLandscapeReader.js +185 -0
  15. package/src/charting/datamodel/BianSemanticApiReader.js +183 -0
  16. package/src/charting/datamodel/DataModel.js +356 -10
  17. package/src/charting/datamodel/DataModelChecker.js +92 -0
  18. package/src/charting/datamodel/DataModelCompared.js +127 -0
  19. package/src/charting/datamodel/DataModelFileFormat.js +33 -0
  20. package/src/charting/datamodel/DataModelPerspective.js +45 -0
  21. package/src/charting/datamodel/DataType.js +108 -0
  22. package/src/charting/datamodel/EnumerationType.js +51 -0
  23. package/src/charting/datamodel/ServiceDomain.js +177 -0
  24. package/src/charting/datamodel/ServiceLandscape.js +104 -0
  25. package/src/charting/datamodel/StructuredType.js +63 -0
  26. package/src/charting/datamodel/index.js +10 -0
  27. package/src/charting/diagramModel/MergeModelCommand.js +43 -37
  28. package/src/charting/diagramModel/OpenViewList.js +75 -0
  29. package/src/charting/diagramModel/ViewSetChecker.js +22 -34
  30. package/src/charting/diagrams/DataModelRelationshipStrategy.js +12 -4
  31. package/src/charting/diagrams/EdgeGeometry.js +578 -553
  32. package/src/charting/diagrams/GraphDiagram.js +11 -1
  33. package/src/charting/diagrams/NodeGeometry.js +169 -158
  34. package/src/charting/glyphs/PersonGlyph.js +24 -23
  35. package/src/index.js +2666 -2664
  36. package/src/workspace/EditorShell.js +166 -67
  37. package/src/workspace/PanelLayout.js +56 -0
  38. package/src/workspace/layout/TabsNode.js +205 -191
  39. package/src/workspace/parts/ArchimateCanvasPart.js +5 -7
  40. package/src/workspace/parts/DataModelPart.js +339 -29
  41. package/src/workspace/parts/DiagramEditorPart.js +2 -8
  42. package/src/workspace/parts/EditorPart.js +461 -407
  43. package/src/workspace/parts/ModelFile.js +216 -187
  44. package/src/workspace/parts/ModelsPart.js +483 -192
  45. package/src/workspace/storage/BackupFolder.js +145 -0
  46. package/src/workspace/storage/KeyValueStore.js +92 -0
  47. package/src/workspace/storage/ModelLibrary.js +65 -0
  48. package/src/workspace/workArea.js +417 -413
  49. 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.model._notify();
30
- }
31
-
32
- undo() {
33
- for (const undo of [...this._undos].reverse()) undo();
34
- this._undos = [];
35
- this.model._notify();
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
- * ViewSetChecker — checks over any ViewSetModel (a template editor's
3
- * DiagramModel, an ArchimateModel): what is wrong or worth a look across its
4
- * views. A notation adds its own checks in a subclass (ArchimateModelChecker).
5
- * DOM-free and advisory.
6
- *
7
- * Each finding: `{ code, severity: 'warning'|'info', message, kind, ids }`;
8
- * `kind` says what the ids name ('view' here; a subclass adds its own).
9
- * Each check is a method listed by `checks()`, so a subclass can add, drop or
10
- * reorder them.
11
- */
12
- export class ViewSetChecker {
13
- /** @param {ViewSetModel} model */
14
- constructor(model) {
15
- this.model = model;
16
- }
17
-
18
- /** The checks to run, in the order their findings are listed. */
19
- checks() {
20
- return [this.emptyViews];
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; the midpoint when none is clear.
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
- const clear = ({ x, y }) => occupied.every(o => x + width / 2 <= o.x0 || o.x1 <= x - width / 2 || y + height / 2 <= o.y0 || o.y1 <= y - height / 2);
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
- if (clear(at)) return at;
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 mid;
68
+ return best; // none clear: the least covered
61
69
  }
62
70
 
63
71
  apply(relationships, { notation, entity = () => undefined, occupied = [] }) {