@formicoidea/labre-framework-uml 0.59.0 → 0.60.0
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/dist/actions.d.ts +14 -13
- package/dist/actions.js +118 -258
- package/dist/component.d.ts +4 -4
- package/dist/component.js +4 -4
- package/dist/import.d.ts +7 -6
- package/dist/import.js +42 -141
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/interchange-headless.d.ts +1 -0
- package/dist/interchange-headless.js +3 -0
- package/dist/keywords.d.ts +26 -0
- package/dist/keywords.js +32 -0
- package/dist/presets.d.ts +91 -2
- package/dist/presets.js +178 -3
- package/dist/toolbox.d.ts +84 -0
- package/dist/toolbox.js +172 -0
- package/package.json +3 -3
package/dist/actions.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type UmlDiagramKind, UmlDiagramElementModel, type UmlFragmentOperator } from '@formicoidea/labre-core/model';
|
|
2
2
|
import type { BlockStdScope } from '@formicoidea/labre-core/std';
|
|
3
3
|
import { type GfxPrimitiveElementModel } from '@formicoidea/labre-core/std/gfx';
|
|
4
|
-
import {
|
|
4
|
+
import type { UmlEdgeRole } from './edge-styles.js';
|
|
5
5
|
/**
|
|
6
6
|
* Create a UML diagram frame centred on the viewport — the sheet one diagram is
|
|
7
7
|
* drawn on (Annex A).
|
|
@@ -164,14 +164,14 @@ export type UmlGlyphKind = 'package' | 'note' | 'actor' | 'use-case' | 'port' |
|
|
|
164
164
|
*
|
|
165
165
|
* ## `uml:name` for five of them, `uml:label` for the rest
|
|
166
166
|
*
|
|
167
|
-
* See
|
|
168
|
-
* the grammar parses a keyword off; a port, a ball and a
|
|
169
|
-
* Keeping them apart is what lets the exporter read a
|
|
170
|
-
* what shape it is under.
|
|
167
|
+
* See `NAMED_GLYPH_KINDS` (`presets.ts`): a package, a note and the three
|
|
168
|
+
* cubes carry a NAME the grammar parses a keyword off; a port, a ball and a
|
|
169
|
+
* socket carry a LABEL. Keeping them apart is what lets the exporter read a
|
|
170
|
+
* tier without first asking what shape it is under.
|
|
171
171
|
*
|
|
172
172
|
* ## The label is where the notation puts it, and that is not this file's call
|
|
173
173
|
*
|
|
174
|
-
* The box comes out of
|
|
174
|
+
* The box comes out of `umlCompartmentBoxes`, which is where every
|
|
175
175
|
* "beside the glyph" and "inside the front face" decision is written down: a
|
|
176
176
|
* port's label sits next to the square rather than on it (the square is 16
|
|
177
177
|
* units, and words do not fit in it), an interface mark's sits beside the ball
|
|
@@ -214,12 +214,13 @@ export declare function createUmlNode(std: BlockStdScope, kind: UmlGlyphKind): v
|
|
|
214
214
|
*
|
|
215
215
|
* ## How many tiers is the LAYOUT's answer, not this file's
|
|
216
216
|
*
|
|
217
|
-
* The
|
|
218
|
-
* for no other, which is what lets two kinds
|
|
219
|
-
* line changing here: a component and an
|
|
220
|
-
* the one body tier §11.6.4 and §19.3.4
|
|
221
|
-
*
|
|
222
|
-
* would be a second statement of the
|
|
217
|
+
* The assembly (`umlArtefactElements`) writes a tier for each box
|
|
218
|
+
* `umlCompartmentBoxes` returns and for no other, which is what lets two kinds
|
|
219
|
+
* be added to the notation without a line changing here: a component and an
|
|
220
|
+
* artifact arrive with the name tier and the one body tier §11.6.4 and §19.3.4
|
|
221
|
+
* draw them with, because that is what the layout hands over. The alternative
|
|
222
|
+
* — a table of tier counts in this file — would be a second statement of the
|
|
223
|
+
* geometry, and the first one to drift.
|
|
223
224
|
*
|
|
224
225
|
* ## Four or five elements, and the object is the four
|
|
225
226
|
*
|
|
@@ -264,7 +265,7 @@ export type { UmlEdgeRole };
|
|
|
264
265
|
* Because UML has twelve of them and they differ on three props out of a table
|
|
265
266
|
* that is otherwise identical — the same mode, the same ink, the same weight. A
|
|
266
267
|
* dozen near-identical functions would be a dozen places for a stroke width to
|
|
267
|
-
* drift, and
|
|
268
|
+
* drift, and `UML_EDGE_STYLE` is the notation itself, which is the thing
|
|
268
269
|
* worth reading. It is also what made phase 2 free: `deploy`, `manifest` and
|
|
269
270
|
* `communication-path` arm through this function without a line changing in it.
|
|
270
271
|
*
|
package/dist/actions.js
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import { DefaultTool, runInterchangeImportFile, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
2
|
import { ConnectorTool } from '@formicoidea/labre-core/gfx/connector';
|
|
3
|
-
import {
|
|
3
|
+
import { UmlDiagramElementModel, } from '@formicoidea/labre-core/model';
|
|
4
4
|
import { translateKey } from '@formicoidea/labre-core/shared/services';
|
|
5
5
|
import { downloadBlob } from '@formicoidea/labre-core/shared/utils';
|
|
6
|
-
import { Bound } from '@formicoidea/labre-core/global/gfx';
|
|
7
6
|
import { GfxControllerIdentifier, } from '@formicoidea/labre-core/std/gfx';
|
|
8
|
-
import {
|
|
9
|
-
import { UML_BODY_FONT_SIZE, UML_DIAGRAM_BOX, UML_EDGE_WIDTH, UML_FRAGMENT_BOX, UML_INK, UML_NAME_FONT_SIZE, UML_NODE_BOX, UML_PARTITION_BOX, UML_REGION_BOX, UML_SUBJECT_BOX, } from './consts.js';
|
|
7
|
+
import { UML_NODE_BOX } from './consts.js';
|
|
10
8
|
import { decodeDrawio } from './drawio-decode.js';
|
|
11
|
-
import { UML_EDGE_STYLE } from './edge-styles.js';
|
|
12
9
|
import { umlSafeFilename } from './filename.js';
|
|
13
10
|
import { UML_DRAWIO_IMPORT, UML_PLANTUML_EXPORT, UML_PLANTUML_IMPORT, UML_XMI_EXPORT, UML_XMI_IMPORT, } from './interchange.js';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
11
|
+
import { UML_NAME_SEED, UML_OPERATIONS_SEED, UML_OPERATIONS_SEED_KEY, UML_UNLABELLED_KINDS, umlBodySeed, umlSeedKey, } from './keywords.js';
|
|
12
|
+
import { umlConnectorProps, UML_FRAME_BOX, } from './presets.js';
|
|
13
|
+
import { UML_FRAME_SEED, umlFrameElement, umlNodeElements } from './toolbox.js';
|
|
17
14
|
/**
|
|
18
15
|
* Standalone creation/activation actions for the UML toolbox — the same shape
|
|
19
16
|
* BPMN's and C4's `actions.ts` have, and for the same reason: the menu is a pure
|
|
@@ -27,13 +24,64 @@ function finish(gfx, id) {
|
|
|
27
24
|
gfx.selection.set({ elements: [id], editing: false });
|
|
28
25
|
// Keep the palette open (native sub-menu behaviour).
|
|
29
26
|
}
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Write a toolbox builder's elements (`toolbox.ts`, `docs/adr/0045`) the way
|
|
29
|
+
* the toolbar does, and say what the gesture produced.
|
|
30
|
+
*
|
|
31
|
+
* What only an editor has stays here: the layer index, minted per element in
|
|
32
|
+
* painting order — a stability statement, since two elements sharing an index
|
|
33
|
+
* sort by nanoid — and the surface's own ids, which a group's `children` is
|
|
34
|
+
* rewritten to name. The group is LAST in a builder's list, so every member
|
|
35
|
+
* already has its id when the group is written.
|
|
36
|
+
*
|
|
37
|
+
* Returns the group's id when there is one, else the first element's: what a
|
|
38
|
+
* gesture selects.
|
|
39
|
+
*/
|
|
40
|
+
function place(gfx, elements) {
|
|
41
|
+
const surface = gfx.surface;
|
|
42
|
+
if (!surface)
|
|
43
|
+
return undefined;
|
|
44
|
+
const minted = new Map();
|
|
45
|
+
let first;
|
|
46
|
+
let group;
|
|
47
|
+
for (const { id: local, ...props } of elements) {
|
|
48
|
+
if (props.type === 'group') {
|
|
49
|
+
// A plain record is a legal `children`: the group's own `propsToY` takes
|
|
50
|
+
// the KEYS and forces every value to `true`.
|
|
51
|
+
props.children = Object.fromEntries(Object.keys(props.children).map(child => [
|
|
52
|
+
minted.get(child) ?? child,
|
|
53
|
+
true,
|
|
54
|
+
]));
|
|
55
|
+
}
|
|
56
|
+
const id = surface.addElement({
|
|
57
|
+
...props,
|
|
58
|
+
index: gfx.layer.generateIndex(),
|
|
59
|
+
});
|
|
60
|
+
minted.set(local, id);
|
|
61
|
+
first ??= id;
|
|
62
|
+
if (props.type === 'group')
|
|
63
|
+
group = id;
|
|
64
|
+
}
|
|
65
|
+
return group ?? first;
|
|
66
|
+
}
|
|
67
|
+
/** A frame centred on the viewport, written and selected. */
|
|
68
|
+
function createUmlFrame(std, type, name, options) {
|
|
69
|
+
const gfx = gfxOf(std);
|
|
70
|
+
const surface = gfx.surface;
|
|
71
|
+
if (!surface)
|
|
72
|
+
return;
|
|
73
|
+
const { w, h } = UML_FRAME_BOX[type];
|
|
74
|
+
const { centerX: cx, centerY: cy } = gfx.viewport;
|
|
75
|
+
const { id: _local, ...props } = umlFrameElement(type, { x: cx - w / 2, y: cy - h / 2 }, name, type, options);
|
|
76
|
+
finish(gfx, surface.addElement(props));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A frame's default name, resolved through the translation seam at placement
|
|
80
|
+
* and never again: content from the moment it lands (#183).
|
|
81
|
+
*/
|
|
82
|
+
function frameName(std, type) {
|
|
83
|
+
const [key, english] = UML_FRAME_SEED[type];
|
|
84
|
+
return translateKey(std, key, english);
|
|
37
85
|
}
|
|
38
86
|
/* ── The sheet and the frame on it ─────────────────────────────────────── */
|
|
39
87
|
/**
|
|
@@ -54,22 +102,10 @@ function addTier(surface, index, role, text, box, options) {
|
|
|
54
102
|
* once and never re-resolved.
|
|
55
103
|
*/
|
|
56
104
|
export function createUmlDiagram(std, kind = 'class') {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const { w, h } = UML_DIAGRAM_BOX;
|
|
62
|
-
const { centerX: cx, centerY: cy } = gfx.viewport;
|
|
63
|
-
const id = surface.addElement({
|
|
64
|
-
type: 'umlDiagram',
|
|
65
|
-
// The FRAME the elements are drawn on, and a role of its own: a rule written
|
|
66
|
-
// on the artefacts must never fall on the sheet holding them.
|
|
67
|
-
role: UML_ROLE.diagram,
|
|
68
|
-
name: translateKey(std, umlDiagramRoleKey, 'Diagram'),
|
|
69
|
-
kind,
|
|
70
|
-
xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
|
|
71
|
-
});
|
|
72
|
-
finish(gfx, id);
|
|
105
|
+
// The FRAME the elements are drawn on, with a role of its own
|
|
106
|
+
// (`umlFrameProps`): a rule written on the artefacts must never fall on the
|
|
107
|
+
// sheet holding them.
|
|
108
|
+
createUmlFrame(std, 'umlDiagram', frameName(std, 'umlDiagram'), { kind });
|
|
73
109
|
}
|
|
74
110
|
/**
|
|
75
111
|
* Create a use case SUBJECT centred on the viewport (§18.1.4) — the rectangle
|
|
@@ -79,30 +115,8 @@ export function createUmlDiagram(std, kind = 'class') {
|
|
|
79
115
|
* ONE name, so there is nothing for a discriminant to discriminate.
|
|
80
116
|
*/
|
|
81
117
|
export function createUmlSubject(std) {
|
|
82
|
-
|
|
83
|
-
const surface = gfx.surface;
|
|
84
|
-
if (!surface)
|
|
85
|
-
return;
|
|
86
|
-
const { w, h } = UML_SUBJECT_BOX;
|
|
87
|
-
const { centerX: cx, centerY: cy } = gfx.viewport;
|
|
88
|
-
const id = surface.addElement({
|
|
89
|
-
type: 'umlSubject',
|
|
90
|
-
role: UML_ROLE.subject,
|
|
91
|
-
name: translateKey(std, umlSubjectRoleKey, 'Subject'),
|
|
92
|
-
xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
|
|
93
|
-
});
|
|
94
|
-
finish(gfx, id);
|
|
118
|
+
createUmlFrame(std, 'umlSubject', frameName(std, 'umlSubject'));
|
|
95
119
|
}
|
|
96
|
-
/**
|
|
97
|
-
* The two behaviour BACKGROUNDS, named through the same seam the subject is.
|
|
98
|
-
*
|
|
99
|
-
* `umlRoleKey` rather than a `umlPartitionRoleKey` export, and it is the same
|
|
100
|
-
* string either way (`roles.ts` derives both from the role's local name): a
|
|
101
|
-
* default name is asked for ONCE, at creation, and it is the author's content
|
|
102
|
-
* from that moment on — so the call site needs the key and nothing else.
|
|
103
|
-
*/
|
|
104
|
-
const umlPartitionRoleKey = umlRoleKey('partition');
|
|
105
|
-
const umlRegionRoleKey = umlRoleKey('region');
|
|
106
120
|
/**
|
|
107
121
|
* Create an activity PARTITION — a swimlane (§15.6.4), the band an activity
|
|
108
122
|
* diagram draws round the steps one participant is responsible for.
|
|
@@ -127,19 +141,7 @@ const umlRegionRoleKey = umlRoleKey('region');
|
|
|
127
141
|
* turns a column into a row.
|
|
128
142
|
*/
|
|
129
143
|
export function createUmlPartition(std) {
|
|
130
|
-
|
|
131
|
-
const surface = gfx.surface;
|
|
132
|
-
if (!surface)
|
|
133
|
-
return;
|
|
134
|
-
const { w, h } = UML_PARTITION_BOX;
|
|
135
|
-
const { centerX: cx, centerY: cy } = gfx.viewport;
|
|
136
|
-
const id = surface.addElement({
|
|
137
|
-
type: 'umlPartition',
|
|
138
|
-
role: UML_ROLE.partition,
|
|
139
|
-
name: translateKey(std, umlPartitionRoleKey, 'Partition'),
|
|
140
|
-
xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
|
|
141
|
-
});
|
|
142
|
-
finish(gfx, id);
|
|
144
|
+
createUmlFrame(std, 'umlPartition', frameName(std, 'umlPartition'));
|
|
143
145
|
}
|
|
144
146
|
/**
|
|
145
147
|
* Create a REGION — the rounded, named rectangle a composite state is drawn as
|
|
@@ -155,19 +157,7 @@ export function createUmlPartition(std) {
|
|
|
155
157
|
* into.
|
|
156
158
|
*/
|
|
157
159
|
export function createUmlRegion(std) {
|
|
158
|
-
|
|
159
|
-
const surface = gfx.surface;
|
|
160
|
-
if (!surface)
|
|
161
|
-
return;
|
|
162
|
-
const { w, h } = UML_REGION_BOX;
|
|
163
|
-
const { centerX: cx, centerY: cy } = gfx.viewport;
|
|
164
|
-
const id = surface.addElement({
|
|
165
|
-
type: 'umlRegion',
|
|
166
|
-
role: UML_ROLE.region,
|
|
167
|
-
name: translateKey(std, umlRegionRoleKey, 'Region'),
|
|
168
|
-
xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
|
|
169
|
-
});
|
|
170
|
-
finish(gfx, id);
|
|
160
|
+
createUmlFrame(std, 'umlRegion', frameName(std, 'umlRegion'));
|
|
171
161
|
}
|
|
172
162
|
/* ── Phase 3: the combined fragment (§17.6.4) and the interaction use ───── */
|
|
173
163
|
/**
|
|
@@ -209,19 +199,7 @@ export function createUmlRegion(std) {
|
|
|
209
199
|
* putting a branch on the sheet nobody asked for.
|
|
210
200
|
*/
|
|
211
201
|
export function createUmlFragment(std, operator = 'alt') {
|
|
212
|
-
|
|
213
|
-
const surface = gfx.surface;
|
|
214
|
-
if (!surface)
|
|
215
|
-
return;
|
|
216
|
-
const { w, h } = UML_FRAGMENT_BOX;
|
|
217
|
-
const { centerX: cx, centerY: cy } = gfx.viewport;
|
|
218
|
-
const id = surface.addElement({
|
|
219
|
-
type: 'umlFragment',
|
|
220
|
-
role: UML_ROLE.fragment,
|
|
221
|
-
operator,
|
|
222
|
-
xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
|
|
223
|
-
});
|
|
224
|
-
finish(gfx, id);
|
|
202
|
+
createUmlFrame(std, 'umlFragment', undefined, { operator });
|
|
225
203
|
}
|
|
226
204
|
/**
|
|
227
205
|
* Create an INTERACTION USE — §17.7.4's `ref` frame, the box that says "and
|
|
@@ -245,42 +223,37 @@ export function createUmlInteractionUse(std) {
|
|
|
245
223
|
createUmlFragment(std, 'ref');
|
|
246
224
|
}
|
|
247
225
|
/**
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
* tidy: the morph's `afterMorph` rewrites `uml:name` and only that, so a cube
|
|
260
|
-
* labelled instead of named would morph into a device that never said so.
|
|
261
|
-
*
|
|
262
|
-
* Everything else here carries a LABEL: a port's word sits beside a 16-unit
|
|
263
|
-
* square and the two interface marks' beside a ball and a socket, none of them
|
|
264
|
-
* inside anything, and none of them ever carrying a keyword.
|
|
265
|
-
*/
|
|
266
|
-
const NAMED_GLYPHS = new Set([
|
|
267
|
-
'package',
|
|
268
|
-
'note',
|
|
269
|
-
'node',
|
|
270
|
-
'device',
|
|
271
|
-
'execution-environment',
|
|
272
|
-
]);
|
|
273
|
-
/**
|
|
274
|
-
* Where the viewport centre puts a node of this kind.
|
|
275
|
-
*
|
|
276
|
-
* Shared by the two creation functions so a class and a use case are centred by
|
|
277
|
-
* the same arithmetic — the geometry is the only thing the two gestures have in
|
|
278
|
-
* common, and it is worth not writing twice.
|
|
226
|
+
* Place an artefact centred on the viewport and select what the gesture
|
|
227
|
+
* produced: the GROUP when the kind has words, the SHAPE when it is a mark.
|
|
228
|
+
*
|
|
229
|
+
* The elements come from the ONE assembly a host with no editor and every
|
|
230
|
+
* importer also call (`umlNodeElements`, `toolbox.ts`, over
|
|
231
|
+
* `umlArtefactElements`, `presets.ts`, `docs/adr/0045`). What only an editor
|
|
232
|
+
* has stays here: the viewport centre, and every seed resolved through the
|
|
233
|
+
* translation SEAM — the words a gesture writes are persisted, so a host that
|
|
234
|
+
* has a catalogue must be asked before they land: a package dropped in a
|
|
235
|
+
* French editor is never called "Package" afterwards (R30). Resolved once,
|
|
236
|
+
* here; content from that moment on.
|
|
279
237
|
*/
|
|
280
|
-
function
|
|
238
|
+
function createUmlArtefact(std, kind) {
|
|
239
|
+
const gfx = gfxOf(std);
|
|
240
|
+
if (!gfx.surface)
|
|
241
|
+
return;
|
|
281
242
|
const { w, h } = UML_NODE_BOX[kind];
|
|
282
243
|
const { centerX: cx, centerY: cy } = gfx.viewport;
|
|
283
|
-
|
|
244
|
+
const [bodyKey, bodyText] = umlBodySeed(kind);
|
|
245
|
+
const elements = umlNodeElements(kind, { x: cx - w / 2, y: cy - h / 2 },
|
|
246
|
+
// A mark asks for no key: there is no word to translate.
|
|
247
|
+
UML_UNLABELLED_KINDS.has(kind)
|
|
248
|
+
? {}
|
|
249
|
+
: {
|
|
250
|
+
name: translateKey(std, umlSeedKey(kind), UML_NAME_SEED[kind]),
|
|
251
|
+
attributes: bodyKey ? translateKey(std, bodyKey, bodyText) : bodyText,
|
|
252
|
+
operations: translateKey(std, UML_OPERATIONS_SEED_KEY, UML_OPERATIONS_SEED),
|
|
253
|
+
});
|
|
254
|
+
const id = place(gfx, elements);
|
|
255
|
+
if (id)
|
|
256
|
+
finish(gfx, id);
|
|
284
257
|
}
|
|
285
258
|
/**
|
|
286
259
|
* Create one of the SINGLE-LABEL artefacts — a package, a note, an actor, a use
|
|
@@ -299,14 +272,14 @@ function centredBox(gfx, kind) {
|
|
|
299
272
|
*
|
|
300
273
|
* ## `uml:name` for five of them, `uml:label` for the rest
|
|
301
274
|
*
|
|
302
|
-
* See
|
|
303
|
-
* the grammar parses a keyword off; a port, a ball and a
|
|
304
|
-
* Keeping them apart is what lets the exporter read a
|
|
305
|
-
* what shape it is under.
|
|
275
|
+
* See `NAMED_GLYPH_KINDS` (`presets.ts`): a package, a note and the three
|
|
276
|
+
* cubes carry a NAME the grammar parses a keyword off; a port, a ball and a
|
|
277
|
+
* socket carry a LABEL. Keeping them apart is what lets the exporter read a
|
|
278
|
+
* tier without first asking what shape it is under.
|
|
306
279
|
*
|
|
307
280
|
* ## The label is where the notation puts it, and that is not this file's call
|
|
308
281
|
*
|
|
309
|
-
* The box comes out of
|
|
282
|
+
* The box comes out of `umlCompartmentBoxes`, which is where every
|
|
310
283
|
* "beside the glyph" and "inside the front face" decision is written down: a
|
|
311
284
|
* port's label sits next to the square rather than on it (the square is 16
|
|
312
285
|
* units, and words do not fit in it), an interface mark's sits beside the ball
|
|
@@ -342,83 +315,8 @@ function centredBox(gfx, kind) {
|
|
|
342
315
|
* rules off and the exporter reads.
|
|
343
316
|
*/
|
|
344
317
|
export function createUmlNode(std, kind) {
|
|
345
|
-
|
|
346
|
-
const surface = gfx.surface;
|
|
347
|
-
if (!surface)
|
|
348
|
-
return;
|
|
349
|
-
const { x, y, w, h } = centredBox(gfx, kind);
|
|
350
|
-
const shapeId = surface.addElement({
|
|
351
|
-
// Every prop the kind is worth, from the ONE table the morph also reads
|
|
352
|
-
// (`presets.ts`). NO `text`: the words are the child below, and the shape is
|
|
353
|
-
// a body and nothing else.
|
|
354
|
-
...umlNodeProps(kind, { xywh: new Bound(x, y, w, h).serialize() }),
|
|
355
|
-
index: gfx.layer.generateIndex(),
|
|
356
|
-
});
|
|
357
|
-
// The marks the notation draws without a word on them: the shape IS the
|
|
358
|
-
// artefact, so there is nothing to group it with and nothing to seed.
|
|
359
|
-
if (UML_UNLABELLED_KINDS.has(kind)) {
|
|
360
|
-
finish(gfx, shapeId);
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
const { name: box } = umlCompartmentBoxes(kind, x, y, w, h);
|
|
364
|
-
const isLabel = !NAMED_GLYPHS.has(kind);
|
|
365
|
-
const textId = addTier(surface, gfx.layer.generateIndex(),
|
|
366
|
-
// WHICH label role is the kind's own answer, not this file's: a lifeline's
|
|
367
|
-
// head carries `uml:lifeline-ident` and every other single word carries
|
|
368
|
-
// `uml:label` (`component.ts`, {@link umlLabelRoleOf}).
|
|
369
|
-
isLabel ? umlLabelRoleOf(kind) : UML_ROLE.name,
|
|
370
|
-
// Through the SEAM, like the frames above: the words a gesture writes are
|
|
371
|
-
// persisted, so a host that has a catalogue must be asked before they land
|
|
372
|
-
// — a package dropped in a French editor is never called "Package"
|
|
373
|
-
// afterwards (R30). Resolved once, here; content from that moment on.
|
|
374
|
-
translateKey(std, umlSeedKey(kind), UML_NAME_SEED[kind]), box, {
|
|
375
|
-
fontSize: UML_NAME_FONT_SIZE,
|
|
376
|
-
align: kind === 'note' ? TextAlign.Left : TextAlign.Center,
|
|
377
|
-
});
|
|
378
|
-
// The group LAST, so it is written after everything it holds: the elements are
|
|
379
|
-
// created in painting order — the shape first and the words above it — and the
|
|
380
|
-
// group's own `xywh` is derived from its children.
|
|
381
|
-
const groupId = surface.addElement({
|
|
382
|
-
type: 'group',
|
|
383
|
-
index: gfx.layer.generateIndex(),
|
|
384
|
-
// A plain record is a legal `children`: the group's own `propsToY` takes the
|
|
385
|
-
// KEYS and forces every value to `true`.
|
|
386
|
-
children: { [shapeId]: true, [textId]: true },
|
|
387
|
-
// No title, deliberately. The group renderer paints one only while the
|
|
388
|
-
// artefact is selected, and a package announcing itself as "Group 3" above
|
|
389
|
-
// its own name is a label nobody wrote.
|
|
390
|
-
});
|
|
391
|
-
finish(gfx, groupId);
|
|
318
|
+
createUmlArtefact(std, kind);
|
|
392
319
|
}
|
|
393
|
-
/**
|
|
394
|
-
* What the SECOND compartment says on a fresh artefact, where it is not the
|
|
395
|
-
* §9.5.4 attribute line every classifier gets.
|
|
396
|
-
*
|
|
397
|
-
* Two exceptions and a default, rather than three entries, because the default
|
|
398
|
-
* is the rule: `+ attribute : Type` is what a divided rectangle's body tier
|
|
399
|
-
* holds unless the kind's own clause says otherwise.
|
|
400
|
-
*
|
|
401
|
-
* - an **object**'s tier holds SLOTS (§9.8.4) — `attribute = value`, an
|
|
402
|
-
* instance giving its classifier's features values rather than redeclaring
|
|
403
|
-
* them;
|
|
404
|
-
* - a **state**'s holds INTERNAL ACTIVITIES (§14.2.4.4) and is seeded EMPTY.
|
|
405
|
-
* That is the one place in this pack where a blank tier is the right
|
|
406
|
-
* stencil, and it is the exporter that decides it: `model.ts` reads
|
|
407
|
-
* `entry / …`, `do / …` and `exit / …` off these lines and treats a label
|
|
408
|
-
* with nothing after it as no behaviour at all, so a seeded `entry / ` would
|
|
409
|
-
* be a prompt that reads as content and exports as nothing. §14.2.4's own
|
|
410
|
-
* figures draw exactly this — a named state with a ruled-off compartment
|
|
411
|
-
* waiting to be filled — and the author types the first line the grammar
|
|
412
|
-
* then reads back.
|
|
413
|
-
*
|
|
414
|
-
* Each is paired with the key it is the English default of, and the state's is
|
|
415
|
-
* `null` for the reason an unlabelled kind contributes none: there is no word
|
|
416
|
-
* to translate.
|
|
417
|
-
*/
|
|
418
|
-
const BODY_SEED = {
|
|
419
|
-
object: [UML_SLOTS_SEED_KEY, UML_SLOTS_SEED],
|
|
420
|
-
state: [null, ''],
|
|
421
|
-
};
|
|
422
320
|
/**
|
|
423
321
|
* Create one of the COMPARTMENTED artefacts — a class, an interface, an
|
|
424
322
|
* enumeration, an object, and since phase 2 a component, an artifact or a
|
|
@@ -426,12 +324,13 @@ const BODY_SEED = {
|
|
|
426
324
|
*
|
|
427
325
|
* ## How many tiers is the LAYOUT's answer, not this file's
|
|
428
326
|
*
|
|
429
|
-
* The
|
|
430
|
-
* for no other, which is what lets two kinds
|
|
431
|
-
* line changing here: a component and an
|
|
432
|
-
* the one body tier §11.6.4 and §19.3.4
|
|
433
|
-
*
|
|
434
|
-
* would be a second statement of the
|
|
327
|
+
* The assembly (`umlArtefactElements`) writes a tier for each box
|
|
328
|
+
* `umlCompartmentBoxes` returns and for no other, which is what lets two kinds
|
|
329
|
+
* be added to the notation without a line changing here: a component and an
|
|
330
|
+
* artifact arrive with the name tier and the one body tier §11.6.4 and §19.3.4
|
|
331
|
+
* draw them with, because that is what the layout hands over. The alternative
|
|
332
|
+
* — a table of tier counts in this file — would be a second statement of the
|
|
333
|
+
* geometry, and the first one to drift.
|
|
435
334
|
*
|
|
436
335
|
* ## Four or five elements, and the object is the four
|
|
437
336
|
*
|
|
@@ -460,41 +359,7 @@ const BODY_SEED = {
|
|
|
460
359
|
* visual cue UML gives for visibility (§9.5.4).
|
|
461
360
|
*/
|
|
462
361
|
export function createUmlClassifier(std, kind) {
|
|
463
|
-
|
|
464
|
-
const surface = gfx.surface;
|
|
465
|
-
if (!surface)
|
|
466
|
-
return;
|
|
467
|
-
const { x, y, w, h } = centredBox(gfx, kind);
|
|
468
|
-
const shapeId = surface.addElement({
|
|
469
|
-
...umlNodeProps(kind, { xywh: new Bound(x, y, w, h).serialize() }),
|
|
470
|
-
index: gfx.layer.generateIndex(),
|
|
471
|
-
});
|
|
472
|
-
const boxes = umlCompartmentBoxes(kind, x, y, w, h);
|
|
473
|
-
const children = { [shapeId]: true };
|
|
474
|
-
children[addTier(surface, gfx.layer.generateIndex(), UML_ROLE.name,
|
|
475
|
-
// Through the seam, exactly as {@link createUmlNode} writes its one tier.
|
|
476
|
-
translateKey(std, umlSeedKey(kind), UML_NAME_SEED[kind]), boxes.name, {
|
|
477
|
-
fontSize: UML_NAME_FONT_SIZE,
|
|
478
|
-
align: TextAlign.Center,
|
|
479
|
-
bold: true,
|
|
480
|
-
decoration: umlNameDecoration(kind),
|
|
481
|
-
})] = true;
|
|
482
|
-
if (boxes.attributes) {
|
|
483
|
-
const [bodyKey, bodyText] = BODY_SEED[kind] ?? [
|
|
484
|
-
UML_ATTRIBUTES_SEED_KEY,
|
|
485
|
-
UML_ATTRIBUTES_SEED,
|
|
486
|
-
];
|
|
487
|
-
children[addTier(surface, gfx.layer.generateIndex(), UML_ROLE.attributes, bodyKey ? translateKey(std, bodyKey, bodyText) : bodyText, boxes.attributes, { fontSize: UML_BODY_FONT_SIZE, align: TextAlign.Left })] = true;
|
|
488
|
-
}
|
|
489
|
-
if (boxes.operations) {
|
|
490
|
-
children[addTier(surface, gfx.layer.generateIndex(), UML_ROLE.operations, translateKey(std, UML_OPERATIONS_SEED_KEY, UML_OPERATIONS_SEED), boxes.operations, { fontSize: UML_BODY_FONT_SIZE, align: TextAlign.Left })] = true;
|
|
491
|
-
}
|
|
492
|
-
const groupId = surface.addElement({
|
|
493
|
-
type: 'group',
|
|
494
|
-
index: gfx.layer.generateIndex(),
|
|
495
|
-
children,
|
|
496
|
-
});
|
|
497
|
-
finish(gfx, groupId);
|
|
362
|
+
createUmlArtefact(std, kind);
|
|
498
363
|
}
|
|
499
364
|
/**
|
|
500
365
|
* Arm the native connector tool, pre-styled for one UML relationship.
|
|
@@ -504,7 +369,7 @@ export function createUmlClassifier(std, kind) {
|
|
|
504
369
|
* Because UML has twelve of them and they differ on three props out of a table
|
|
505
370
|
* that is otherwise identical — the same mode, the same ink, the same weight. A
|
|
506
371
|
* dozen near-identical functions would be a dozen places for a stroke width to
|
|
507
|
-
* drift, and
|
|
372
|
+
* drift, and `UML_EDGE_STYLE` is the notation itself, which is the thing
|
|
508
373
|
* worth reading. It is also what made phase 2 free: `deploy`, `manifest` and
|
|
509
374
|
* `communication-path` arm through this function without a line changing in it.
|
|
510
375
|
*
|
|
@@ -522,15 +387,10 @@ export function createUmlClassifier(std, kind) {
|
|
|
522
387
|
* keep the user's own style (#144 M1).
|
|
523
388
|
*/
|
|
524
389
|
export function activateUmlEdge(std, role) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
stroke: UML_INK,
|
|
530
|
-
strokeWidth: UML_EDGE_WIDTH,
|
|
531
|
-
...UML_EDGE_STYLE[role],
|
|
532
|
-
},
|
|
533
|
-
});
|
|
390
|
+
// The line a host's connector and an imported one are drawn in too
|
|
391
|
+
// (`umlConnectorProps`, `docs/adr/0045`): one table, read three ways.
|
|
392
|
+
const { type: _type, role: roleId, mode, ...style } = umlConnectorProps(role);
|
|
393
|
+
gfxOf(std).tool.setTool(ConnectorTool, { mode, role: roleId, style });
|
|
534
394
|
// Keep the palette open (native sub-menu behaviour).
|
|
535
395
|
}
|
|
536
396
|
/* ── The legend ────────────────────────────────────────────────────────── */
|
package/dist/component.d.ts
CHANGED
|
@@ -309,10 +309,10 @@ export declare const UML_HEAD_LABEL_KINDS: ReadonlySet<UmlNodeKind>;
|
|
|
309
309
|
* single role could only ever have asked them the same question (`roles.ts`).
|
|
310
310
|
*
|
|
311
311
|
* It does NOT answer for the compartmented kinds or for the five glyphs named
|
|
312
|
-
* rather than labelled (`
|
|
313
|
-
* which is a different statement — a tier a keyword may be written
|
|
314
|
-
* helper that folded all three answers together would be a second
|
|
315
|
-
* decide what the grammar reads.
|
|
312
|
+
* rather than labelled (`presets.ts`, `NAMED_GLYPH_KINDS`): those carry
|
|
313
|
+
* `uml:name`, which is a different statement — a tier a keyword may be written
|
|
314
|
+
* over — and a helper that folded all three answers together would be a second
|
|
315
|
+
* place to decide what the grammar reads.
|
|
316
316
|
*/
|
|
317
317
|
export declare function umlLabelRoleOf(kind: UmlNodeKind): UmlRoleId;
|
|
318
318
|
/**
|
package/dist/component.js
CHANGED
|
@@ -330,10 +330,10 @@ export const UML_HEAD_LABEL_KINDS = new Set(['lifeline']);
|
|
|
330
330
|
* single role could only ever have asked them the same question (`roles.ts`).
|
|
331
331
|
*
|
|
332
332
|
* It does NOT answer for the compartmented kinds or for the five glyphs named
|
|
333
|
-
* rather than labelled (`
|
|
334
|
-
* which is a different statement — a tier a keyword may be written
|
|
335
|
-
* helper that folded all three answers together would be a second
|
|
336
|
-
* decide what the grammar reads.
|
|
333
|
+
* rather than labelled (`presets.ts`, `NAMED_GLYPH_KINDS`): those carry
|
|
334
|
+
* `uml:name`, which is a different statement — a tier a keyword may be written
|
|
335
|
+
* over — and a helper that folded all three answers together would be a second
|
|
336
|
+
* place to decide what the grammar reads.
|
|
337
337
|
*/
|
|
338
338
|
export function umlLabelRoleOf(kind) {
|
|
339
339
|
return UML_HEAD_LABEL_KINDS.has(kind)
|
package/dist/import.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InterchangeNote, SerializedElementProps } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
-
import {
|
|
2
|
+
import type { UmlBox } from './component.js';
|
|
3
3
|
import { type UmlModel, type UmlNodeBase, type UmlRelation, type UmlRelationKind } from './model.js';
|
|
4
4
|
/**
|
|
5
5
|
* The **one materializer** every UML importer ends on — a model in, the
|
|
@@ -23,11 +23,12 @@ import { type UmlModel, type UmlNodeBase, type UmlRelation, type UmlRelationKind
|
|
|
23
23
|
*
|
|
24
24
|
* ## Never a size, never a colour, never a seed
|
|
25
25
|
*
|
|
26
|
-
* Every
|
|
27
|
-
* {@link
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* the
|
|
26
|
+
* Every artefact below comes out of {@link umlArtefactElements}, every frame
|
|
27
|
+
* out of {@link umlFrameProps} and every line out of {@link umlConnectorProps}
|
|
28
|
+
* — the very assembly the toolbar and the toolbox call (R17,
|
|
29
|
+
* `docs/adr/0045`). Not one measurement is restated here, so an imported class
|
|
30
|
+
* and a drawn one are the same element down to the stroke width, and a restyle
|
|
31
|
+
* that moves the pack moves the import with it.
|
|
31
32
|
*
|
|
32
33
|
* ## The two names an element carries, and they are not the same name
|
|
33
34
|
*
|