@peter.prib/renderengine 0.2.2 → 0.2.4
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/REFERENCE.md +2227 -2205
- package/dist/renderengine-workspace.umd.js +4098 -3452
- package/dist/renderengine.umd.js +4365 -3915
- package/package.json +1 -1
- package/src/charting/FormDialog.js +1 -1
- package/src/charting/ModelComparisonDialog.js +122 -0
- package/src/charting/PopupMenu.js +25 -1
- package/src/charting/diagramModel/ComparedKind.js +67 -0
- package/src/charting/diagramModel/DiagramView.js +11 -0
- package/src/charting/diagramModel/MergeModelCommand.js +37 -0
- package/src/charting/diagramModel/ModelComparison.js +100 -0
- package/src/charting/diagramModel/ViewSetModel.js +260 -243
- package/src/charting/diagramModel/ViewsCompared.js +39 -0
- package/src/charting/diagramModel/index.js +10 -6
- package/src/charting/diagrams/ArchimateModel.js +1170 -1155
- package/src/charting/diagrams/ArchimateViewsCompared.js +70 -0
- package/src/charting/diagrams/ConceptsCompared.js +76 -0
- package/src/charting/modelTree/ModelTreePanel.js +886 -914
- package/src/charting/modelTree/viewFolders.js +31 -0
- package/src/workspace/parts/ArchimateCanvasPart.js +345 -345
- package/src/workspace/parts/DiagramEditorPart.js +135 -135
- package/src/workspace/parts/EditorPart.js +26 -7
- package/src/workspace/parts/ModelsPart.js +58 -1
|
@@ -1,135 +1,135 @@
|
|
|
1
|
-
import { render } from '../dom.js';
|
|
2
|
-
import { EditorShell } from '../EditorShell.js';
|
|
3
|
-
import { EditorPart } from './EditorPart.js';
|
|
4
|
-
import { DiagramEditor } from '../../charting/DiagramEditor.js';
|
|
5
|
-
import { MenuBar } from '../../charting/MenuBar.js';
|
|
6
|
-
import { DiagramModel } from '../../charting/diagramModel/index.js';
|
|
7
|
-
import { ViewTabStrip } from '../../charting/ViewTabStrip.js';
|
|
8
|
-
import { LocalJsonStore } from '../../charting/storage/LocalJsonStore.js';
|
|
9
|
-
import { EdgeStyleCascade } from '../../charting/edgeStyle/index.js';
|
|
10
|
-
import { DiagramViewPropertiesDialog } from '../../charting/DiagramViewPropertiesDialog.js';
|
|
11
|
-
|
|
12
|
-
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* DiagramEditorPart — a complete diagram editor as a workspace part: menu bar,
|
|
16
|
-
* palette, one tab per diagram and the canvas, over a DiagramModel.
|
|
17
|
-
*
|
|
18
|
-
* Spec (JSON): `templateType` (palette category), `tabOrientation`
|
|
19
|
-
* ('horizontal'|'vertical'), `diagramTitle` (first diagram), `toolStyleKey`
|
|
20
|
-
* (localStorage key for the tool-level line style; `null` keeps it in memory).
|
|
21
|
-
*
|
|
22
|
-
* Line styles cascade line → diagram (`view.edgeStyle`) → model
|
|
23
|
-
* (`model.edgeStyle`) → tool. Each diagram keeps its own undo history, so undo
|
|
24
|
-
* survives switching tabs. The diagrams' menu (new, duplicate, rename, delete)
|
|
25
|
-
* comes from EditorPart. Like ChartPart it needs the engine, so it is not in
|
|
26
|
-
* BUILTIN_PARTS: register it explicitly.
|
|
27
|
-
*/
|
|
28
|
-
export class DiagramEditorPart extends EditorPart {
|
|
29
|
-
static type = 'diagram-editor';
|
|
30
|
-
static viewNoun = 'diagram';
|
|
31
|
-
|
|
32
|
-
constructor(spec = {}) {
|
|
33
|
-
super(spec);
|
|
34
|
-
this.tabOrientation = spec.tabOrientation ?? 'horizontal';
|
|
35
|
-
// The editor is rebuilt with the canvas on every render, which follows every model change.
|
|
36
|
-
this._unsubscribe = this.model.onChange(() => { if (this.mounted) this.render(); });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
createModel(spec) {
|
|
40
|
-
const store = spec.toolStyleKey === null ? null
|
|
41
|
-
: new LocalJsonStore(spec.toolStyleKey ?? 'renderengine.diagramEditor.toolEdgeStyle');
|
|
42
|
-
this.toolEdgeStyle = store?.load({}) ?? {};
|
|
43
|
-
const model = new DiagramModel({
|
|
44
|
-
name: spec.name ?? '',
|
|
45
|
-
templateType: spec.templateType ?? 'generic',
|
|
46
|
-
// Tool-level style edits go through the diagram's undo history; persist whenever it moves.
|
|
47
|
-
historyOptions: { onChange: () => store?.save(this.toolEdgeStyle) },
|
|
48
|
-
});
|
|
49
|
-
model.createView(spec.diagramTitle ?? 'Diagram 1');
|
|
50
|
-
return model;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
render() {
|
|
54
|
-
const model = this.model;
|
|
55
|
-
const view = model.currentView;
|
|
56
|
-
const doc = this.document;
|
|
57
|
-
const canvasId = `diagram-canvas-${this.id}`;
|
|
58
|
-
|
|
59
|
-
const canvas = doc.createElementNS(SVG_NS, 'svg');
|
|
60
|
-
canvas.id = canvasId;
|
|
61
|
-
canvas.setAttribute('viewBox', '0 0 800 600');
|
|
62
|
-
canvas.style.cssText = 'flex: 1; width: 100%; height: 100%; display: block; background: #fff; border: 2px solid #333; box-sizing: border-box;';
|
|
63
|
-
const elements = doc.createElementNS(SVG_NS, 'g');
|
|
64
|
-
elements.id = 'elements';
|
|
65
|
-
canvas.appendChild(elements);
|
|
66
|
-
|
|
67
|
-
const tabs = new ViewTabStrip({
|
|
68
|
-
orientation: this.tabOrientation,
|
|
69
|
-
document: doc,
|
|
70
|
-
onSelect: (id) => model.switchTo(id),
|
|
71
|
-
menuItems: (id) => this.viewMenu.items(id),
|
|
72
|
-
}).render(model.views(), model.currentViewId);
|
|
73
|
-
|
|
74
|
-
// The shared workbench layout: toolbar, palette at the side, tabs + canvas in the main area.
|
|
75
|
-
const { toolbar: menuBarEl, side: palette, main } = new EditorShell({ document: doc, sideWidth: '200px' }).mount(this.container);
|
|
76
|
-
palette.classList.add('ws-diagram-palette');
|
|
77
|
-
Object.assign(palette.style, { padding: '8px', fontSize: '12px', display: 'flex', flexDirection: 'column', overflowY: 'hidden' });
|
|
78
|
-
main.style.flexDirection = this.tabOrientation === 'vertical' ? 'row' : 'column';
|
|
79
|
-
render(main, [tabs, { style: { flex: '1', display: 'flex', flexDirection: 'column', minHeight: '0' }, children: [canvas] }], doc);
|
|
80
|
-
|
|
81
|
-
// The editor is bound to the view's own maps and history: nothing to save back on switch.
|
|
82
|
-
const previous = this.editor;
|
|
83
|
-
previous?.dispose();
|
|
84
|
-
this.editor = new DiagramEditor({
|
|
85
|
-
diagramContainerId: canvasId,
|
|
86
|
-
model,
|
|
87
|
-
templateType: model.templateType,
|
|
88
|
-
onTemplateTypeChange: (type) => model.setTemplateType(type),
|
|
89
|
-
templates: view.templates,
|
|
90
|
-
edges: view.edges,
|
|
91
|
-
history: view.history,
|
|
92
|
-
arrangement: view.arrangement,
|
|
93
|
-
edgeStyleLevels: this.edgeStyleLevels(view),
|
|
94
|
-
});
|
|
95
|
-
if (previous) {
|
|
96
|
-
this.editor.showGrid = previous.showGrid;
|
|
97
|
-
this.editor.gridSize = previous.gridSize;
|
|
98
|
-
}
|
|
99
|
-
this.editor.initPaletteForDiagramType(palette, model.templateType);
|
|
100
|
-
this.menuBar?.dispose();
|
|
101
|
-
this.menuBar = new MenuBar({
|
|
102
|
-
container: menuBarEl,
|
|
103
|
-
editor: this.editor,
|
|
104
|
-
items: [this.fileMenu(), this.editMenu(), ...this.editor.menuBarItems()],
|
|
105
|
-
}).render();
|
|
106
|
-
this.editor.render();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** A diagram's line-style levels, most specific first: the diagram, the model, the tool. */
|
|
110
|
-
edgeStyleLevels(view) {
|
|
111
|
-
return [
|
|
112
|
-
{ key: 'view', label: 'Diagram', style: view.edgeStyle },
|
|
113
|
-
{ key: 'model', label: 'Model', style: this.model.edgeStyle },
|
|
114
|
-
{ key: 'tool', label: 'Tool', style: this.toolEdgeStyle },
|
|
115
|
-
];
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** A diagram's properties: name, documentation and its line-style defaults. */
|
|
119
|
-
showViewProperties(view) {
|
|
120
|
-
return new DiagramViewPropertiesDialog({
|
|
121
|
-
model: this.model,
|
|
122
|
-
view,
|
|
123
|
-
document: this.document,
|
|
124
|
-
cascade: new EdgeStyleCascade(this.edgeStyleLevels(view)),
|
|
125
|
-
// The editor shows the current diagram; a style change there needs a redraw.
|
|
126
|
-
onStyleChange: () => { if (view === this.model.currentView) this.editor?.render(); },
|
|
127
|
-
}).open();
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
dispose() {
|
|
131
|
-
this.menuBar?.dispose();
|
|
132
|
-
this._unsubscribe();
|
|
133
|
-
super.dispose();
|
|
134
|
-
}
|
|
135
|
-
}
|
|
1
|
+
import { render } from '../dom.js';
|
|
2
|
+
import { EditorShell } from '../EditorShell.js';
|
|
3
|
+
import { EditorPart } from './EditorPart.js';
|
|
4
|
+
import { DiagramEditor } from '../../charting/DiagramEditor.js';
|
|
5
|
+
import { MenuBar } from '../../charting/MenuBar.js';
|
|
6
|
+
import { DiagramModel } from '../../charting/diagramModel/index.js';
|
|
7
|
+
import { ViewTabStrip } from '../../charting/ViewTabStrip.js';
|
|
8
|
+
import { LocalJsonStore } from '../../charting/storage/LocalJsonStore.js';
|
|
9
|
+
import { EdgeStyleCascade } from '../../charting/edgeStyle/index.js';
|
|
10
|
+
import { DiagramViewPropertiesDialog } from '../../charting/DiagramViewPropertiesDialog.js';
|
|
11
|
+
|
|
12
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* DiagramEditorPart — a complete diagram editor as a workspace part: menu bar,
|
|
16
|
+
* palette, one tab per diagram and the canvas, over a DiagramModel.
|
|
17
|
+
*
|
|
18
|
+
* Spec (JSON): `templateType` (palette category), `tabOrientation`
|
|
19
|
+
* ('horizontal'|'vertical'), `diagramTitle` (first diagram), `toolStyleKey`
|
|
20
|
+
* (localStorage key for the tool-level line style; `null` keeps it in memory).
|
|
21
|
+
*
|
|
22
|
+
* Line styles cascade line → diagram (`view.edgeStyle`) → model
|
|
23
|
+
* (`model.edgeStyle`) → tool. Each diagram keeps its own undo history, so undo
|
|
24
|
+
* survives switching tabs. The diagrams' menu (new, duplicate, rename, delete)
|
|
25
|
+
* comes from EditorPart. Like ChartPart it needs the engine, so it is not in
|
|
26
|
+
* BUILTIN_PARTS: register it explicitly.
|
|
27
|
+
*/
|
|
28
|
+
export class DiagramEditorPart extends EditorPart {
|
|
29
|
+
static type = 'diagram-editor';
|
|
30
|
+
static viewNoun = 'diagram';
|
|
31
|
+
|
|
32
|
+
constructor(spec = {}) {
|
|
33
|
+
super(spec);
|
|
34
|
+
this.tabOrientation = spec.tabOrientation ?? 'horizontal';
|
|
35
|
+
// The editor is rebuilt with the canvas on every render, which follows every model change.
|
|
36
|
+
this._unsubscribe = this.model.onChange(() => { if (this.mounted) this.render(); });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
createModel(spec) {
|
|
40
|
+
const store = spec.toolStyleKey === null ? null
|
|
41
|
+
: new LocalJsonStore(spec.toolStyleKey ?? 'renderengine.diagramEditor.toolEdgeStyle');
|
|
42
|
+
this.toolEdgeStyle = store?.load({}) ?? {};
|
|
43
|
+
const model = new DiagramModel({
|
|
44
|
+
name: spec.name ?? '',
|
|
45
|
+
templateType: spec.templateType ?? 'generic',
|
|
46
|
+
// Tool-level style edits go through the diagram's undo history; persist whenever it moves.
|
|
47
|
+
historyOptions: { onChange: () => store?.save(this.toolEdgeStyle) },
|
|
48
|
+
});
|
|
49
|
+
model.createView(spec.diagramTitle ?? 'Diagram 1');
|
|
50
|
+
return model;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
render() {
|
|
54
|
+
const model = this.model;
|
|
55
|
+
const view = model.currentView;
|
|
56
|
+
const doc = this.document;
|
|
57
|
+
const canvasId = `diagram-canvas-${this.id}`;
|
|
58
|
+
|
|
59
|
+
const canvas = doc.createElementNS(SVG_NS, 'svg');
|
|
60
|
+
canvas.id = canvasId;
|
|
61
|
+
canvas.setAttribute('viewBox', '0 0 800 600');
|
|
62
|
+
canvas.style.cssText = 'flex: 1; width: 100%; height: 100%; display: block; background: #fff; border: 2px solid #333; box-sizing: border-box;';
|
|
63
|
+
const elements = doc.createElementNS(SVG_NS, 'g');
|
|
64
|
+
elements.id = 'elements';
|
|
65
|
+
canvas.appendChild(elements);
|
|
66
|
+
|
|
67
|
+
const tabs = new ViewTabStrip({
|
|
68
|
+
orientation: this.tabOrientation,
|
|
69
|
+
document: doc,
|
|
70
|
+
onSelect: (id) => model.switchTo(id),
|
|
71
|
+
menuItems: (id) => this.viewMenu.items(id),
|
|
72
|
+
}).render(model.views(), model.currentViewId);
|
|
73
|
+
|
|
74
|
+
// The shared workbench layout: toolbar, palette at the side, tabs + canvas in the main area.
|
|
75
|
+
const { toolbar: menuBarEl, side: palette, main } = new EditorShell({ document: doc, sideWidth: '200px' }).mount(this.container);
|
|
76
|
+
palette.classList.add('ws-diagram-palette');
|
|
77
|
+
Object.assign(palette.style, { padding: '8px', fontSize: '12px', display: 'flex', flexDirection: 'column', overflowY: 'hidden' });
|
|
78
|
+
main.style.flexDirection = this.tabOrientation === 'vertical' ? 'row' : 'column';
|
|
79
|
+
render(main, [tabs, { style: { flex: '1', display: 'flex', flexDirection: 'column', minHeight: '0' }, children: [canvas] }], doc);
|
|
80
|
+
|
|
81
|
+
// The editor is bound to the view's own maps and history: nothing to save back on switch.
|
|
82
|
+
const previous = this.editor;
|
|
83
|
+
previous?.dispose();
|
|
84
|
+
this.editor = new DiagramEditor({
|
|
85
|
+
diagramContainerId: canvasId,
|
|
86
|
+
model,
|
|
87
|
+
templateType: model.templateType,
|
|
88
|
+
onTemplateTypeChange: (type) => model.setTemplateType(type),
|
|
89
|
+
templates: view.templates,
|
|
90
|
+
edges: view.edges,
|
|
91
|
+
history: view.history,
|
|
92
|
+
arrangement: view.arrangement,
|
|
93
|
+
edgeStyleLevels: this.edgeStyleLevels(view),
|
|
94
|
+
});
|
|
95
|
+
if (previous) {
|
|
96
|
+
this.editor.showGrid = previous.showGrid;
|
|
97
|
+
this.editor.gridSize = previous.gridSize;
|
|
98
|
+
}
|
|
99
|
+
this.editor.initPaletteForDiagramType(palette, model.templateType);
|
|
100
|
+
this.menuBar?.dispose();
|
|
101
|
+
this.menuBar = new MenuBar({
|
|
102
|
+
container: menuBarEl,
|
|
103
|
+
editor: this.editor,
|
|
104
|
+
items: [this.fileMenu(), this.editMenu(), ...this.hostMenus(), ...this.editor.menuBarItems()],
|
|
105
|
+
}).render();
|
|
106
|
+
this.editor.render();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** A diagram's line-style levels, most specific first: the diagram, the model, the tool. */
|
|
110
|
+
edgeStyleLevels(view) {
|
|
111
|
+
return [
|
|
112
|
+
{ key: 'view', label: 'Diagram', style: view.edgeStyle },
|
|
113
|
+
{ key: 'model', label: 'Model', style: this.model.edgeStyle },
|
|
114
|
+
{ key: 'tool', label: 'Tool', style: this.toolEdgeStyle },
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** A diagram's properties: name, documentation and its line-style defaults. */
|
|
119
|
+
showViewProperties(view) {
|
|
120
|
+
return new DiagramViewPropertiesDialog({
|
|
121
|
+
model: this.model,
|
|
122
|
+
view,
|
|
123
|
+
document: this.document,
|
|
124
|
+
cascade: new EdgeStyleCascade(this.edgeStyleLevels(view)),
|
|
125
|
+
// The editor shows the current diagram; a style change there needs a redraw.
|
|
126
|
+
onStyleChange: () => { if (view === this.model.currentView) this.editor?.render(); },
|
|
127
|
+
}).open();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
dispose() {
|
|
131
|
+
this.menuBar?.dispose();
|
|
132
|
+
this._unsubscribe();
|
|
133
|
+
super.dispose();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -29,7 +29,9 @@ import { NAME_TRANSFORMS } from '../../charting/diagramModel/nameTransforms.js';
|
|
|
29
29
|
*
|
|
30
30
|
* Hosted by a ModelsPart (`ctx.models`: several models open at once, a tab
|
|
31
31
|
* each), its File menu also offers New model, Open… in a new tab and Close
|
|
32
|
-
* model,
|
|
32
|
+
* model, an import opens in a new tab rather than replacing this model, a
|
|
33
|
+
* Tools menu compares and merges models (ModelsPart#toolItems), and its tab
|
|
34
|
+
* follows its file's name and saved state.
|
|
33
35
|
*/
|
|
34
36
|
export class EditorPart extends Part {
|
|
35
37
|
/** What a view is called in this part's menus ('view', 'diagram'). */
|
|
@@ -113,6 +115,11 @@ export class EditorPart extends Part {
|
|
|
113
115
|
return new ToolbarMenu('File', () => this.fileMenuItems());
|
|
114
116
|
}
|
|
115
117
|
|
|
118
|
+
/** The menus beside File and Edit that work across models: Tools, with a ModelsPart. None without. */
|
|
119
|
+
hostMenus() {
|
|
120
|
+
return this.modelsHost ? [new ToolbarMenu('Tools', () => this.modelsHost.toolItems(this))] : [];
|
|
121
|
+
}
|
|
122
|
+
|
|
116
123
|
/** Edit menu: undo and redo (the editor's), then Find and replace… over the whole model. */
|
|
117
124
|
editMenu() {
|
|
118
125
|
return new ToolbarMenu('Edit', () => this.editMenuItems());
|
|
@@ -132,8 +139,8 @@ export class EditorPart extends Part {
|
|
|
132
139
|
];
|
|
133
140
|
}
|
|
134
141
|
|
|
135
|
-
/** Run a model-wide edit (null: nothing to do) as one undo step, then redraw.
|
|
136
|
-
|
|
142
|
+
/** Run a model-wide edit (null: nothing to do; a rename, a merge) as one undo step, then redraw. */
|
|
143
|
+
runModelEdit(command) {
|
|
137
144
|
if (!command) return null;
|
|
138
145
|
(this.editor?.history ?? this.model.history).execute(command);
|
|
139
146
|
if (this.mounted) this.render();
|
|
@@ -162,7 +169,7 @@ export class EditorPart extends Part {
|
|
|
162
169
|
|
|
163
170
|
/** Transform names (ModelSearch#transformCommand), one undo step. */
|
|
164
171
|
transformNames(transform, text = '') {
|
|
165
|
-
return this.
|
|
172
|
+
return this.runModelEdit(this.model.search().transformCommand(transform, text));
|
|
166
173
|
}
|
|
167
174
|
|
|
168
175
|
/** Rename property…: a property's name, everywhere in the model. One undo step. */
|
|
@@ -184,7 +191,7 @@ export class EditorPart extends Part {
|
|
|
184
191
|
|
|
185
192
|
/** Rename a property everywhere (ModelSearch#renamePropertyCommand), one undo step. */
|
|
186
193
|
renameProperty(from, to) {
|
|
187
|
-
return this.
|
|
194
|
+
return this.runModelEdit(this.model.search().renamePropertyCommand(from, to));
|
|
188
195
|
}
|
|
189
196
|
|
|
190
197
|
/**
|
|
@@ -260,18 +267,30 @@ export class EditorPart extends Part {
|
|
|
260
267
|
]);
|
|
261
268
|
}
|
|
262
269
|
|
|
263
|
-
/**
|
|
270
|
+
/**
|
|
271
|
+
* File > Import's items: each format that imports, then the subclass's own
|
|
272
|
+
* (ownImportItems). An imported model opens in a new tab with a ModelsPart
|
|
273
|
+
* (onImported), else replaces this one.
|
|
274
|
+
*/
|
|
264
275
|
importItems() {
|
|
265
276
|
return EditorPart.tidyMenu([
|
|
266
277
|
...modelFileMenuItems(this.fileFormats(), {
|
|
267
278
|
model: () => this.model, only: 'import',
|
|
268
|
-
onImport: (model) =>
|
|
279
|
+
onImport: (model) => this.onImported(model),
|
|
269
280
|
document: () => this.document,
|
|
270
281
|
}),
|
|
271
282
|
...this.ownImportItems(),
|
|
272
283
|
]);
|
|
273
284
|
}
|
|
274
285
|
|
|
286
|
+
/** A model imported from a file: in a new tab with a ModelsPart, else in place of this one. Returns the part showing it. */
|
|
287
|
+
onImported(model) {
|
|
288
|
+
if (this.modelsHost) return this.modelsHost.openModel(model);
|
|
289
|
+
this.replaceModel(model);
|
|
290
|
+
this.file.forget();
|
|
291
|
+
return this;
|
|
292
|
+
}
|
|
293
|
+
|
|
275
294
|
/** File > Export's items: each format, the subclass's own (ownExportItems), then the HTML report and the pictures. */
|
|
276
295
|
exportItems() {
|
|
277
296
|
return EditorPart.tidyMenu([
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Part } from '../Part.js';
|
|
2
2
|
import { WorkArea } from '../workArea.js';
|
|
3
3
|
import { el } from '../dom.js';
|
|
4
|
+
import { ModelCompareDialog, ModelMergeDialog } from '../../charting/ModelComparisonDialog.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* ModelsPart — several models open at once (as Archi's model tree holds
|
|
@@ -10,7 +11,10 @@ import { el } from '../dom.js';
|
|
|
10
11
|
* them as between any two editors.
|
|
11
12
|
*
|
|
12
13
|
* The editor parts it hosts find it as `ctx.models` and offer, in their File
|
|
13
|
-
* menu, New model, Open… in a new tab and Close model
|
|
14
|
+
* menu, New model, Open… in a new tab and Close model; a model imported from
|
|
15
|
+
* a file opens in a tab of its own. Their Tools menu works across the open
|
|
16
|
+
* models (toolItems): compare one with another, and merge from another into
|
|
17
|
+
* it (ModelComparison). A tab shows its file's
|
|
14
18
|
* name and • while it has unsaved changes; closing one with unsaved changes
|
|
15
19
|
* asks first; the page title follows the model in front. The last model
|
|
16
20
|
* closed leaves a new, empty one.
|
|
@@ -74,6 +78,59 @@ export class ModelsPart extends Part {
|
|
|
74
78
|
return part;
|
|
75
79
|
}
|
|
76
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Open a model made elsewhere (one imported from a file) in a new tab,
|
|
83
|
+
* as it is: nothing to undo, not yet saved to a file. Returns the part.
|
|
84
|
+
*/
|
|
85
|
+
openModel(model) {
|
|
86
|
+
const part = this.newModel();
|
|
87
|
+
part.replaceModel(model);
|
|
88
|
+
this.work.focus(part.id);
|
|
89
|
+
this.fileStateChanged(part);
|
|
90
|
+
return part;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The Tools menu's items for one open model (`part`): Compare with and
|
|
95
|
+
* Merge from, each a submenu of the other open models.
|
|
96
|
+
*/
|
|
97
|
+
toolItems(part) {
|
|
98
|
+
const others = this.models().filter(m => m !== part);
|
|
99
|
+
const each = (act) => (others.length
|
|
100
|
+
? others.map(other => ({ label: this.labelOf(other), action: () => act(part, other) }))
|
|
101
|
+
: [{ label: 'Open another model first', disabled: true }]);
|
|
102
|
+
return [
|
|
103
|
+
{ label: 'Compare with', items: each((a, b) => this.compare(a, b)) },
|
|
104
|
+
{ label: 'Merge from', items: each((a, b) => this.merge(a, b)) },
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** How `part`'s model differs from `other`'s, shown (ModelCompareDialog); its Merge… goes on to merge. */
|
|
109
|
+
compare(part, other) {
|
|
110
|
+
return new ModelCompareDialog({
|
|
111
|
+
...this._comparing(part, other),
|
|
112
|
+
onMerge: () => this.merge(part, other),
|
|
113
|
+
}).open();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Choose what to bring from `other`'s model into `part`'s, then bring it: one undo step there. */
|
|
117
|
+
merge(part, other) {
|
|
118
|
+
return new ModelMergeDialog({
|
|
119
|
+
...this._comparing(part, other),
|
|
120
|
+
onMerge: (command) => part.runModelEdit(command),
|
|
121
|
+
}).open();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** @private */
|
|
125
|
+
_comparing(part, other) {
|
|
126
|
+
return {
|
|
127
|
+
comparison: part.model.compareWith(other.model),
|
|
128
|
+
mineName: part.model.name || this.labelOf(part),
|
|
129
|
+
theirsName: other.model.name || this.labelOf(other),
|
|
130
|
+
document: this.container?.ownerDocument ?? globalThis.document,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
77
134
|
/**
|
|
78
135
|
* Ask for a file (as `from`'s Open… does) and open it in a new tab,
|
|
79
136
|
* leaving `from`'s model as it is. Returns the new part, or null.
|