@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
@@ -1,67 +1,166 @@
1
- import { el } from './dom.js';
2
-
3
- /**
4
- * EditorShell — the workbench layout diagram editors share: a toolbar row, a
5
- * side panel (model tree, palette), the main area (tabs + canvas) and an
6
- * optional right-hand aside. Built with el() and inline styles, so it needs no
7
- * stylesheet and never restyles the host page.
8
- *
9
- * Not a custom element, so the engine bundle can use it under node too.
10
- */
11
- export class EditorShell {
12
- /**
13
- * @param {Object} [options]
14
- * @param {Document} [options.document]
15
- * @param {string} [options.sideWidth]
16
- * @param {boolean} [options.aside] - Add a right-hand panel
17
- * @param {string} [options.asideWidth]
18
- * @param {Object<string, string>} [options.ids] - Element ids per region: toolbar, side, main, aside
19
- * @param {boolean} [options.fullPage] - Fill the viewport (and drop the body margin)
20
- */
21
- constructor({ document: doc = document, sideWidth = '280px', aside = false, asideWidth = '300px', ids = {}, fullPage = false } = {}) {
22
- this.doc = doc;
23
- this.sideWidth = sideWidth;
24
- this.aside = aside;
25
- this.asideWidth = asideWidth;
26
- this.ids = ids;
27
- this.fullPage = fullPage;
28
- }
29
-
30
- /** @returns {{root: HTMLElement, toolbar: HTMLElement, side: HTMLElement, main: HTMLElement, aside?: HTMLElement}} */
31
- build() {
32
- const regions = {};
33
- const region = (name, style) => ({
34
- attrs: { id: this.ids[name] ?? null, 'data-shell-region': name },
35
- style,
36
- ref: (n) => { regions[name] = n; },
37
- });
38
- const panel = { background: '#fafafa', overflowY: 'auto', minHeight: '0', boxSizing: 'border-box' };
39
- regions.root = el({
40
- attrs: { 'data-editor-shell': '' },
41
- style: {
42
- display: 'flex', flexDirection: 'column', height: this.fullPage ? '100vh' : '100%', minHeight: '0',
43
- fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif", color: '#333', background: '#f5f5f5',
44
- },
45
- children: [
46
- region('toolbar', { flex: '0 0 auto' }),
47
- {
48
- style: { display: 'flex', flex: '1 1 auto', minHeight: '0', overflow: 'hidden' },
49
- children: [
50
- region('side', { ...panel, flex: `0 0 ${this.sideWidth}`, borderRight: '1px solid #ddd' }),
51
- region('main', { flex: '1 1 auto', display: 'flex', flexDirection: 'column', minWidth: '0', minHeight: '0', background: '#fff', position: 'relative' }),
52
- this.aside && region('aside', { ...panel, flex: `0 0 ${this.asideWidth}`, borderLeft: '1px solid #ddd' }),
53
- ].filter(Boolean),
54
- },
55
- ],
56
- }, this.doc);
57
- return regions;
58
- }
59
-
60
- /** Replace the host's contents with the shell. */
61
- mount(host) {
62
- const regions = this.build();
63
- host.replaceChildren(regions.root);
64
- if (this.fullPage) this.doc.body.style.margin = '0';
65
- return regions;
66
- }
67
- }
1
+ import { el } from './dom.js';
2
+ import { PanelLayout } from './PanelLayout.js';
3
+
4
+ /** How wide a splitter is, and a folded panel's stub. */
5
+ const SPLITTER_WIDTH = 6;
6
+ const STUB_WIDTH = 24;
7
+
8
+ /**
9
+ * EditorShell — the workbench layout diagram editors share: a toolbar row, a
10
+ * side panel (model tree, palette), the main area (tabs + canvas) and an
11
+ * optional right-hand aside. Built with el() and inline styles, so it needs no
12
+ * stylesheet and never restyles the host page.
13
+ *
14
+ * With a `layout` (PanelLayout, kept by the part so it outlives this shell), a
15
+ * side panel's inner edge is a splitter: drag it to resize the panel, drag it
16
+ * nearly shut or double-click it to fold the panel to a stub (a strip with its
17
+ * name), and click the stub to open it at the width it had.
18
+ *
19
+ * Not a custom element, so the engine bundle can use it under node too.
20
+ */
21
+ export class EditorShell {
22
+ /**
23
+ * @param {Object} [options]
24
+ * @param {Document} [options.document]
25
+ * @param {string} [options.sideWidth]
26
+ * @param {boolean} [options.aside] - Add a right-hand panel
27
+ * @param {string} [options.asideWidth]
28
+ * @param {Object<string, string>} [options.ids] - Element ids per region: toolbar, side, main, aside
29
+ * @param {boolean} [options.fullPage] - Fill the viewport (and drop the body margin)
30
+ * @param {PanelLayout} [options.layout] - Resizable, foldable side panels (their widths kept here)
31
+ * @param {{side?: string, aside?: string}} [options.labels] - What a folded panel's stub says
32
+ */
33
+ constructor({ document: doc = document, sideWidth = '280px', aside = false, asideWidth = '300px', ids = {}, fullPage = false, layout = null, labels = {} } = {}) {
34
+ this.doc = doc;
35
+ this.sideWidth = sideWidth;
36
+ this.aside = aside;
37
+ this.asideWidth = asideWidth;
38
+ this.ids = ids;
39
+ this.fullPage = fullPage;
40
+ this.layout = layout;
41
+ this.labels = { side: 'Side panel', aside: 'Panel', ...labels };
42
+ /** Each side panel's elements (with a layout): region, splitter, stub. */
43
+ this._panels = {};
44
+ }
45
+
46
+ /** @returns {{root: HTMLElement, toolbar: HTMLElement, side: HTMLElement, main: HTMLElement, aside?: HTMLElement}} */
47
+ build() {
48
+ const regions = {};
49
+ const region = (name, style) => ({
50
+ attrs: { id: this.ids[name] ?? null, 'data-shell-region': name },
51
+ style,
52
+ ref: (n) => { regions[name] = n; },
53
+ });
54
+ const panel = { background: '#fafafa', overflowY: 'auto', minHeight: '0', boxSizing: 'border-box' };
55
+ const width = (name, given) => (this.layout ? `${this.layout.panel(name, parseInt(given, 10)).width}px` : given);
56
+ // With a layout: [stub] side [splitter] main [splitter] aside [stub], the stubs at the outer edges.
57
+ const extra = (make) => (this.layout ? [make()] : []);
58
+ const row = [
59
+ ...extra(() => this._stub('side')),
60
+ region('side', { ...panel, flex: `0 0 ${width('side', this.sideWidth)}`, borderRight: '1px solid #ddd' }),
61
+ ...extra(() => this._splitter('side')),
62
+ region('main', { flex: '1 1 auto', display: 'flex', flexDirection: 'column', minWidth: '0', minHeight: '0', background: '#fff', position: 'relative' }),
63
+ ...(this.aside ? [
64
+ ...extra(() => this._splitter('aside')),
65
+ region('aside', { ...panel, flex: `0 0 ${width('aside', this.asideWidth)}`, borderLeft: '1px solid #ddd' }),
66
+ ...extra(() => this._stub('aside')),
67
+ ] : []),
68
+ ];
69
+ regions.root = el({
70
+ attrs: { 'data-editor-shell': '' },
71
+ style: {
72
+ display: 'flex', flexDirection: 'column', height: this.fullPage ? '100vh' : '100%', minHeight: '0',
73
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif", color: '#333', background: '#f5f5f5',
74
+ },
75
+ children: [
76
+ region('toolbar', { flex: '0 0 auto' }),
77
+ { style: { display: 'flex', flex: '1 1 auto', minHeight: '0', overflow: 'hidden' }, children: row },
78
+ ],
79
+ }, this.doc);
80
+ for (const name of Object.keys(this._panels)) {
81
+ this._panels[name].region = regions[name];
82
+ this._show(name);
83
+ }
84
+ return regions;
85
+ }
86
+
87
+ /** A folded panel's strip: its name, upright; a click opens it. @private */
88
+ _stub(name) {
89
+ const parts = (this._panels[name] ??= {});
90
+ return {
91
+ attrs: { 'data-shell-stub': name, role: 'button', tabindex: '0', title: `Show ${this.labels[name]}`, 'aria-label': `Show ${this.labels[name]}` },
92
+ style: {
93
+ flex: `0 0 ${STUB_WIDTH}px`, display: 'none', alignItems: 'center', justifyContent: 'flex-start', paddingTop: '8px',
94
+ writingMode: 'vertical-rl', background: '#eee', borderRight: name === 'side' ? '1px solid #ddd' : 'none',
95
+ borderLeft: name === 'aside' ? '1px solid #ddd' : 'none', cursor: 'pointer', fontSize: '12px', color: '#555', userSelect: 'none',
96
+ },
97
+ text: this.labels[name],
98
+ on: {
99
+ click: () => this.toggle(name),
100
+ keydown: (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.toggle(name); } },
101
+ },
102
+ ref: (n) => { parts.stub = n; },
103
+ };
104
+ }
105
+
106
+ /** A panel's inner edge: drag to resize (nearly shut folds it), double-click to fold or open. @private */
107
+ _splitter(name) {
108
+ const parts = (this._panels[name] ??= {});
109
+ return {
110
+ attrs: { 'data-shell-splitter': name, role: 'separator', 'aria-orientation': 'vertical', title: 'Drag to resize; double-click to fold' },
111
+ style: { flex: `0 0 ${SPLITTER_WIDTH}px`, cursor: 'col-resize', background: 'transparent', touchAction: 'none' },
112
+ on: {
113
+ pointerdown: (e) => this._drag(name, e),
114
+ dblclick: () => this.toggle(name),
115
+ },
116
+ ref: (n) => { parts.splitter = n; },
117
+ };
118
+ }
119
+
120
+ /** Follow a drag of a panel's edge until the pointer is let go. @private */
121
+ _drag(name, down) {
122
+ const p = this.layout.panels[name];
123
+ if (!p || p.collapsed) return;
124
+ down.preventDefault();
125
+ const start = down.clientX, width = p.width;
126
+ const sign = name === 'side' ? 1 : -1; // the aside grows leftwards
127
+ const win = this.doc.defaultView ?? globalThis;
128
+ const move = (e) => this.resize(name, width + sign * (e.clientX - start));
129
+ const up = () => { win.removeEventListener('pointermove', move); win.removeEventListener('pointerup', up); };
130
+ win.addEventListener('pointermove', move);
131
+ win.addEventListener('pointerup', up);
132
+ }
133
+
134
+ /** Give a panel a width (PanelLayout#resize: nearly shut folds it), and show it so. */
135
+ resize(name, width) {
136
+ this.layout?.resize(name, width);
137
+ this._show(name);
138
+ }
139
+
140
+ /** Fold a panel to its stub, or open it again. */
141
+ toggle(name) {
142
+ this.layout?.toggle(name);
143
+ this._show(name);
144
+ }
145
+
146
+ /** Show a panel as its layout says: open at its width with its splitter, or folded to its stub. @private */
147
+ _show(name) {
148
+ const parts = this._panels[name];
149
+ const p = this.layout?.panels[name];
150
+ if (!parts?.region || !p) return;
151
+ parts.region.style.display = p.collapsed ? 'none' : '';
152
+ parts.region.style.flex = `0 0 ${p.width}px`;
153
+ if (parts.splitter) parts.splitter.style.display = p.collapsed ? 'none' : '';
154
+ if (parts.stub) parts.stub.style.display = p.collapsed ? 'flex' : 'none';
155
+ }
156
+
157
+ /** Replace the host's contents with the shell. */
158
+ mount(host) {
159
+ const regions = this.build();
160
+ host.replaceChildren(regions.root);
161
+ if (this.fullPage) this.doc.body.style.margin = '0';
162
+ return regions;
163
+ }
164
+ }
165
+
166
+ export { SPLITTER_WIDTH, STUB_WIDTH };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * PanelLayout — the widths of an editor's side panels (EditorShell's `side`
3
+ * and `aside`) and which are folded to a stub, kept by the part so they
4
+ * survive the shell being built anew on every render. Pure: EditorShell's
5
+ * splitters and stubs call it, and tests need no DOM.
6
+ *
7
+ * Dragged narrower than `collapseBelow`, a panel folds; opened again, it
8
+ * takes back the width it had.
9
+ */
10
+ export class PanelLayout {
11
+ /** Narrower than this a panel folds to its stub. */
12
+ static COLLAPSE_BELOW = 60;
13
+ static MIN_WIDTH = 120;
14
+ static MAX_WIDTH = 900;
15
+
16
+ /**
17
+ * @param {Object<string, {width: number, collapsed?: boolean}>} [panels] - By name ('side', 'aside')
18
+ */
19
+ constructor(panels = {}) {
20
+ this.panels = Object.fromEntries(Object.entries(panels).map(([name, p]) => [name, { width: p.width, collapsed: !!p.collapsed }]));
21
+ }
22
+
23
+ /** A panel's state, made with `width` the first time it is asked for. */
24
+ panel(name, width) {
25
+ return (this.panels[name] ??= { width, collapsed: false });
26
+ }
27
+
28
+ /** A panel's width as drawn: 0 folded, else its own. */
29
+ widthOf(name) {
30
+ const p = this.panels[name];
31
+ return !p ? null : p.collapsed ? 0 : p.width;
32
+ }
33
+
34
+ /**
35
+ * A drag of the panel's edge to `width`: narrower than COLLAPSE_BELOW folds
36
+ * it (keeping the width it had), else it takes the width, kept within bounds.
37
+ */
38
+ resize(name, width) {
39
+ const p = this.panels[name];
40
+ if (!p) return null;
41
+ if (width < PanelLayout.COLLAPSE_BELOW) p.collapsed = true;
42
+ else Object.assign(p, { collapsed: false, width: Math.round(Math.min(PanelLayout.MAX_WIDTH, Math.max(PanelLayout.MIN_WIDTH, width))) });
43
+ return p;
44
+ }
45
+
46
+ /** Fold a panel to its stub, or open it at the width it had. */
47
+ toggle(name) {
48
+ const p = this.panels[name];
49
+ if (p) p.collapsed = !p.collapsed;
50
+ return p;
51
+ }
52
+
53
+ toJSON() {
54
+ return Object.fromEntries(Object.entries(this.panels).map(([name, p]) => [name, { ...p }]));
55
+ }
56
+ }