@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,187 +1,216 @@
1
- import { pickFile } from '../../charting/toolbar/pickFile.js';
2
- import { downloadBlob } from '../../exportImage.js';
3
-
4
- /** Each page's own title, as it was before any model's file name went in front of it (several models share one page). */
5
- const PAGE_TITLES = new WeakMap();
6
-
7
- /**
8
- * ModelFile — the file an EditorPart's model is open from and saved to: its
9
- * name, its format (one of the part's ModelFileFormats) and, where the
10
- * browser lets a page write a file back (the File System Access API, in
11
- * Chrome and Edge), its handle. Open… and Save As… choose one; Save writes
12
- * back to it, or (no handle, or no such browser) asks where (Save As). After
13
- * opening or saving, the model is marked saved; its edits since make the part
14
- * "dirty", which `label` and the page title show.
15
- */
16
- export class ModelFile {
17
- /** @param {EditorPart} part */
18
- constructor(part) {
19
- this.part = part;
20
- this.name = null;
21
- this.format = null;
22
- this.handle = null;
23
- }
24
-
25
- get doc() { return this.part.document; }
26
- get win() { return this.doc?.defaultView ?? globalThis; }
27
-
28
- /** The format a new file is saved in: the part's `saveFormat()`, else its first. */
29
- get defaultFormat() {
30
- return this.part.saveFormat?.() ?? this.part.fileFormats()[0] ?? null;
31
- }
32
-
33
- /**
34
- * What the model is called: the file's name, else (not from a file) the
35
- * model's own name, else Untitled; and • when it has changed since it was
36
- * opened or saved.
37
- */
38
- get label() {
39
- return `${this.name ?? (this.part.model.name || 'Untitled')}${this.part.model.isDirty?.() ? ' •' : ''}`;
40
- }
41
-
42
- /** Open…, Save, Save As… for the File menu. */
43
- menuItems() {
44
- return [
45
- { label: 'Open…', action: () => this.open() },
46
- { label: `Save${this.name ? ` (${this.name})` : ''}`, action: () => this.save() },
47
- { label: 'Save As…', action: () => this.saveAs() },
48
- ];
49
- }
50
-
51
- /** Ctrl+O, Ctrl+S and Ctrl+Shift+S, while the part is on the page. */
52
- onKey(e) {
53
- // Only the part on screen: another in a hidden tab has its own file.
54
- if (!(e.ctrlKey || e.metaKey) || e.altKey || !this.part.isShown()) return;
55
- const key = e.key.toLowerCase();
56
- if (key === 's') { e.preventDefault(); e.shiftKey ? this.saveAs() : this.save(); }
57
- else if (key === 'o' && !e.shiftKey) { e.preventDefault(); this.open(); }
58
- }
59
-
60
- /**
61
- * Ask for a file and open it, replacing the model: its format is the first
62
- * of the part's whose extension and contents fit. Keeps its handle, so Save
63
- * writes back to it. Returns the new model, or null.
64
- */
65
- async open() {
66
- const chosen = await this.choose();
67
- return chosen ? this.adopt(chosen) : null;
68
- }
69
-
70
- /**
71
- * Ask for a file and read it as a model, without opening it anywhere yet:
72
- * `{ model, name, format, handle }`, or null (cancelled, or not a model).
73
- * `adopt()` opens it, in this part or another of its kind.
74
- */
75
- async choose() {
76
- const formats = this.part.fileFormats().filter(f => f.imports !== false);
77
- const accept = [...new Set(formats.flatMap(f => f.accept.split(',').map(e => e.trim()).filter(Boolean)))];
78
- let file = null, handle = null;
79
- if (typeof this.win.showOpenFilePicker === 'function') {
80
- try {
81
- [handle] = await this.win.showOpenFilePicker({ types: [{ description: 'Models', accept: { 'application/octet-stream': accept } }] });
82
- file = await handle.getFile();
83
- } catch (err) {
84
- if (err?.name === 'AbortError') return null;
85
- throw err;
86
- }
87
- } else {
88
- file = await pickFile(accept.join(','), this.doc);
89
- }
90
- if (!file) return null;
91
- const bytes = new Uint8Array(await file.arrayBuffer());
92
- const text = () => new TextDecoder().decode(bytes);
93
- const format = formats.find(f => f.acceptsName(file.name) && f.recognizes(f.readsBytes ? bytes : text()))
94
- ?? formats.find(f => f.acceptsName(file.name));
95
- if (!format) { this.tell(`Not a file this editor opens: ${file.name}`); return null; }
96
- try {
97
- return { model: format.import(format.readsBytes ? bytes : text()), name: file.name, format, handle };
98
- } catch (err) {
99
- this.tell(`Open failed: ${err.message}`);
100
- return null;
101
- }
102
- }
103
-
104
- /** Open what `choose()` read: the part edits its model, from that file. Returns the model. */
105
- adopt({ model, name, format, handle = null }) {
106
- this.part.replaceModel(model);
107
- Object.assign(this, { name, format, handle });
108
- this.showState();
109
- return model;
110
- }
111
-
112
- /** Save to the open file (where the browser can write it back), else Save As. */
113
- async save() {
114
- if (!this.handle?.createWritable || !this.format) return this.saveAs();
115
- return this._write(this.handle, this.format, this.name);
116
- }
117
-
118
- /**
119
- * Ask where to save (the browser's save dialog where it has one, else a
120
- * download), in the open file's format or the default one, and keep it.
121
- */
122
- async saveAs() {
123
- const format = this.format ?? this.defaultFormat;
124
- if (!format) return false;
125
- const suggested = this.name ?? format.fileName;
126
- if (typeof this.win.showSaveFilePicker === 'function') {
127
- let handle;
128
- try {
129
- const ext = format.accept.split(',')[0].trim();
130
- handle = await this.win.showSaveFilePicker({ suggestedName: suggested, types: [{ description: format.label, accept: { 'application/octet-stream': [ext] } }] });
131
- } catch (err) {
132
- if (err?.name === 'AbortError') return false;
133
- throw err;
134
- }
135
- return this._write(handle, format, handle.name ?? suggested);
136
- }
137
- // No way to write a file: download it (the page cannot write it back later).
138
- const { data, mimeType } = format.export(this.part.model);
139
- downloadBlob(new this.win.Blob([data], { type: mimeType }), suggested);
140
- Object.assign(this, { name: suggested, format, handle: null });
141
- this._saved();
142
- return true;
143
- }
144
-
145
- /** @private */
146
- async _write(handle, format, name) {
147
- const { data } = format.export(this.part.model);
148
- const writable = await handle.createWritable();
149
- await writable.write(data);
150
- await writable.close();
151
- Object.assign(this, { name, format, handle });
152
- this._saved();
153
- return true;
154
- }
155
-
156
- /** @private */
157
- _saved() {
158
- this.part.model.markSaved?.();
159
- this.showState();
160
- }
161
-
162
- /** The model now is not from a file (an import, a new model). */
163
- forget() {
164
- Object.assign(this, { name: null, format: null, handle: null });
165
- this.showState();
166
- }
167
-
168
- /** Show the saved state: the page title, and anything listening (the part's toolbar). */
169
- showState() {
170
- const doc = this.doc;
171
- if (doc && this.part.isShown()) {
172
- if (!PAGE_TITLES.has(doc)) PAGE_TITLES.set(doc, doc.title);
173
- doc.title = `${this.label} — ${PAGE_TITLES.get(doc)}`;
174
- }
175
- this.part.onFileState?.(this);
176
- }
177
-
178
- /** Put the page title back (the part is going). */
179
- restoreTitle() {
180
- if (this.doc && PAGE_TITLES.has(this.doc)) this.doc.title = PAGE_TITLES.get(this.doc);
181
- }
182
-
183
- /** @private */
184
- tell(message) {
185
- (this.win.alert ?? (() => {}))(message);
186
- }
187
- }
1
+ import { pickFile } from '../../charting/toolbar/pickFile.js';
2
+ import { downloadBlob } from '../../exportImage.js';
3
+
4
+ /** Each page's own title, as it was before any model's file name went in front of it (several models share one page). */
5
+ const PAGE_TITLES = new WeakMap();
6
+
7
+ /**
8
+ * ModelFile — the file an EditorPart's model is open from and saved to: its
9
+ * name, its format (one of the part's ModelFileFormats) and, where the
10
+ * browser lets a page write a file back (the File System Access API, in
11
+ * Chrome and Edge), its handle. Open… and Save As… choose one; Save writes
12
+ * back to it, or (no handle, or no such browser) asks where (Save As). After
13
+ * opening or saving, the model is marked saved; its edits since make the part
14
+ * "dirty", which `label` and the page title show.
15
+ */
16
+ export class ModelFile {
17
+ /** @param {EditorPart} part */
18
+ constructor(part) {
19
+ this.part = part;
20
+ this.name = null;
21
+ this.format = null;
22
+ this.handle = null;
23
+ }
24
+
25
+ get doc() { return this.part.document; }
26
+ get win() { return this.doc?.defaultView ?? globalThis; }
27
+
28
+ /** The format a new file is saved in: the part's `saveFormat()`, else its first. */
29
+ get defaultFormat() {
30
+ return this.part.saveFormat?.() ?? this.part.fileFormats()[0] ?? null;
31
+ }
32
+
33
+ /**
34
+ * What the model is called: the file's name, else (not from a file) the
35
+ * model's own name, else Untitled; and • when it has changed since it was
36
+ * opened or saved.
37
+ */
38
+ get label() {
39
+ return `${this.name ?? (this.part.model.name || 'Untitled')}${this.part.model.isDirty?.() ? ' •' : ''}`;
40
+ }
41
+
42
+ /**
43
+ * The model as kept in this browser (ModelLibrary, by a ModelsPart with
44
+ * `keep`): in this file's format, else the part's save format, with its
45
+ * name and this file's name; null when the part has no format.
46
+ */
47
+ keepEntry() {
48
+ const format = this.format ?? this.defaultFormat;
49
+ if (!format) return null;
50
+ const { data } = format.export(this.part.model);
51
+ return { name: this.part.model.name || this.name || 'Untitled', format: format.label, data, fileName: this.name };
52
+ }
53
+
54
+ /**
55
+ * Ctrl+O, Ctrl+S and Ctrl+Shift+S on the page (onKey), and asking before
56
+ * the page is left with changes unsaved; through the part's `listen`, so
57
+ * they go with it.
58
+ */
59
+ listen() {
60
+ const win = this.doc?.defaultView;
61
+ this.part.listen(this.doc, 'keydown', (e) => this.onKey(e));
62
+ if (win) {
63
+ this.part.listen(win, 'beforeunload', (e) => {
64
+ if (!this.part.model.isDirty?.()) return;
65
+ e.preventDefault();
66
+ e.returnValue = '';
67
+ });
68
+ }
69
+ }
70
+
71
+ /** Open…, Save, Save As… for the File menu. */
72
+ menuItems() {
73
+ return [
74
+ { label: 'Open…', action: () => this.open() },
75
+ { label: `Save${this.name ? ` (${this.name})` : ''}`, action: () => this.save() },
76
+ { label: 'Save As…', action: () => this.saveAs() },
77
+ ];
78
+ }
79
+
80
+ /** Ctrl+O, Ctrl+S and Ctrl+Shift+S, while the part is on the page. */
81
+ onKey(e) {
82
+ // Only the part on screen: another in a hidden tab has its own file.
83
+ if (!(e.ctrlKey || e.metaKey) || e.altKey || !this.part.isShown()) return;
84
+ const key = e.key.toLowerCase();
85
+ if (key === 's') { e.preventDefault(); e.shiftKey ? this.saveAs() : this.save(); }
86
+ else if (key === 'o' && !e.shiftKey) { e.preventDefault(); this.open(); }
87
+ }
88
+
89
+ /**
90
+ * Ask for a file and open it, replacing the model: its format is the first
91
+ * of the part's whose extension and contents fit. Keeps its handle, so Save
92
+ * writes back to it. Returns the new model, or null.
93
+ */
94
+ async open() {
95
+ const chosen = await this.choose();
96
+ return chosen ? this.adopt(chosen) : null;
97
+ }
98
+
99
+ /**
100
+ * Ask for a file and read it as a model, without opening it anywhere yet:
101
+ * `{ model, name, format, handle }`, or null (cancelled, or not a model).
102
+ * `adopt()` opens it, in this part or another of its kind.
103
+ */
104
+ async choose() {
105
+ const formats = this.part.fileFormats().filter(f => f.imports !== false);
106
+ const accept = [...new Set(formats.flatMap(f => f.accept.split(',').map(e => e.trim()).filter(Boolean)))];
107
+ let file = null, handle = null;
108
+ if (typeof this.win.showOpenFilePicker === 'function') {
109
+ try {
110
+ [handle] = await this.win.showOpenFilePicker({ types: [{ description: 'Models', accept: { 'application/octet-stream': accept } }] });
111
+ file = await handle.getFile();
112
+ } catch (err) {
113
+ if (err?.name === 'AbortError') return null;
114
+ throw err;
115
+ }
116
+ } else {
117
+ file = await pickFile(accept.join(','), this.doc);
118
+ }
119
+ if (!file) return null;
120
+ const bytes = new Uint8Array(await file.arrayBuffer());
121
+ const text = () => new TextDecoder().decode(bytes);
122
+ const format = formats.find(f => f.acceptsName(file.name) && f.recognizes(f.readsBytes ? bytes : text()))
123
+ ?? formats.find(f => f.acceptsName(file.name));
124
+ if (!format) { this.tell(`Not a file this editor opens: ${file.name}`); return null; }
125
+ try {
126
+ return { model: format.import(format.readsBytes ? bytes : text()), name: file.name, format, handle };
127
+ } catch (err) {
128
+ this.tell(`Open failed: ${err.message}`);
129
+ return null;
130
+ }
131
+ }
132
+
133
+ /** Open what `choose()` read: the part edits its model, from that file. Returns the model. */
134
+ adopt({ model, name, format, handle = null }) {
135
+ this.part.replaceModel(model);
136
+ Object.assign(this, { name, format, handle });
137
+ this.showState();
138
+ return model;
139
+ }
140
+
141
+ /** Save to the open file (where the browser can write it back), else Save As. */
142
+ async save() {
143
+ if (!this.handle?.createWritable || !this.format) return this.saveAs();
144
+ return this._write(this.handle, this.format, this.name);
145
+ }
146
+
147
+ /**
148
+ * Ask where to save (the browser's save dialog where it has one, else a
149
+ * download), in the open file's format or the default one, and keep it.
150
+ */
151
+ async saveAs() {
152
+ const format = this.format ?? this.defaultFormat;
153
+ if (!format) return false;
154
+ const suggested = this.name ?? format.fileName;
155
+ if (typeof this.win.showSaveFilePicker === 'function') {
156
+ let handle;
157
+ try {
158
+ const ext = format.accept.split(',')[0].trim();
159
+ handle = await this.win.showSaveFilePicker({ suggestedName: suggested, types: [{ description: format.label, accept: { 'application/octet-stream': [ext] } }] });
160
+ } catch (err) {
161
+ if (err?.name === 'AbortError') return false;
162
+ throw err;
163
+ }
164
+ return this._write(handle, format, handle.name ?? suggested);
165
+ }
166
+ // No way to write a file: download it (the page cannot write it back later).
167
+ const { data, mimeType } = format.export(this.part.model);
168
+ downloadBlob(new this.win.Blob([data], { type: mimeType }), suggested);
169
+ Object.assign(this, { name: suggested, format, handle: null });
170
+ this._saved();
171
+ return true;
172
+ }
173
+
174
+ /** @private */
175
+ async _write(handle, format, name) {
176
+ const { data } = format.export(this.part.model);
177
+ const writable = await handle.createWritable();
178
+ await writable.write(data);
179
+ await writable.close();
180
+ Object.assign(this, { name, format, handle });
181
+ this._saved();
182
+ return true;
183
+ }
184
+
185
+ /** @private */
186
+ _saved() {
187
+ this.part.model.markSaved?.();
188
+ this.showState();
189
+ }
190
+
191
+ /** The model now is not from a file (an import, a new model). */
192
+ forget() {
193
+ Object.assign(this, { name: null, format: null, handle: null });
194
+ this.showState();
195
+ }
196
+
197
+ /** Show the saved state: the page title, and anything listening (the part's toolbar). */
198
+ showState() {
199
+ const doc = this.doc;
200
+ if (doc && this.part.isShown()) {
201
+ if (!PAGE_TITLES.has(doc)) PAGE_TITLES.set(doc, doc.title);
202
+ doc.title = `${this.label} — ${PAGE_TITLES.get(doc)}`;
203
+ }
204
+ this.part.onFileState?.(this);
205
+ }
206
+
207
+ /** Put the page title back (the part is going). */
208
+ restoreTitle() {
209
+ if (this.doc && PAGE_TITLES.has(this.doc)) this.doc.title = PAGE_TITLES.get(this.doc);
210
+ }
211
+
212
+ /** @private */
213
+ tell(message) {
214
+ (this.win.alert ?? (() => {}))(message);
215
+ }
216
+ }