@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.
@@ -0,0 +1,84 @@
1
+ import type { FrameworkToolbox, SerializedElementProps, ToolboxPoint } from '@formicoidea/labre-core/interchange';
2
+ import type { UmlDiagramKind, UmlFragmentOperator, UmlNodeKind } from '@formicoidea/labre-core/model';
3
+ import { type UmlEdgeRole } from './edge-styles.js';
4
+ import { type UmlFrameType } from './presets.js';
5
+ /**
6
+ * UML's toolbar as pure builders (`docs/adr/0045`): what `createUmlNode`,
7
+ * `createUmlClassifier`, the five frame actions and the twenty edge tools
8
+ * place, with no editor behind them. The actions call these, and the
9
+ * importers write an artefact through the same assembly
10
+ * (`umlArtefactElements`, `presets.ts`), so a class a host places, one drawn
11
+ * from the toolbar and one read out of a `.puml` file are the same elements.
12
+ *
13
+ * ## Data, not sixty-four builders
14
+ *
15
+ * {@link UML_TOOLBOX} is walked off the tables that already exist: the
16
+ * toolbar's own rows (`umlCommandsManifest`, in the toolbar's order), the
17
+ * node kinds (`UML_NODE_BOX`), the edge styles (`UML_EDGE_STYLE`) and six
18
+ * frame commands. A node command is `uml.add<Kind>` and an edge tool
19
+ * `uml.<role>Tool`, so a kind or a relationship added to its table and to the
20
+ * toolbar arrives here without a line changing; `toolbox.unit.spec.ts` says
21
+ * which rows are left out — the five interchange commands (export PlantUML,
22
+ * export XMI, import XMI, import PlantUML, import draw.io), which read or
23
+ * write a file and place nothing a builder could.
24
+ */
25
+ /** The text slots of an artefact. */
26
+ export interface UmlNodeTexts {
27
+ /** The name, or a glyph's one word; the kind's English seed when absent. */
28
+ name?: string;
29
+ /**
30
+ * A divided box's second compartment, one member per line (an object's
31
+ * slots, a state's internal activities); the kind's seed when absent, and
32
+ * empty when given empty.
33
+ */
34
+ attributes?: string;
35
+ /** A class's, an interface's or an enumeration's operations, likewise. */
36
+ operations?: string;
37
+ }
38
+ /**
39
+ * A UML artefact at `at` (its top-left), at the size the toolbar draws it: the
40
+ * shape, its tiers and the group LAST (`umlArtefactElements`), with ids
41
+ * `<local>:shape`, `<local>:name`, `<local>:attributes`, `<local>:operations`
42
+ * and `<local>:group`. A relationship attaches to `<local>:shape`. A mark the
43
+ * notation draws no word on (an initial node, a fork…) is the shape alone.
44
+ *
45
+ * A port's, an interface mark's and a time event's word is written BESIDE
46
+ * the glyph (`UML_BESIDE_LABEL_KINDS`): the group is wider than the shape's
47
+ * box there, and only there.
48
+ */
49
+ export declare function umlNodeElements(kind: UmlNodeKind, at: ToolboxPoint, texts?: UmlNodeTexts, local?: string): SerializedElementProps[];
50
+ /**
51
+ * A frame's name as `[key, English]`: the key the toolbar resolves at
52
+ * placement (`docs/adr/0023`), the English a host with no catalogue writes.
53
+ * The key is the frame ROLE's: the two are the same noun, and a second key
54
+ * would ask a host to word it twice. A combined fragment has none: its `name`
55
+ * is a guard, and the notation draws a guard only where there is one.
56
+ */
57
+ export declare const UML_FRAME_SEED: {
58
+ readonly umlDiagram: readonly [string, "Diagram"];
59
+ readonly umlSubject: readonly [string, "Subject"];
60
+ readonly umlPartition: readonly [string, "Partition"];
61
+ readonly umlRegion: readonly [string, "Region"];
62
+ };
63
+ /** What a frame is born as, beyond its name. */
64
+ export interface UmlFrameOptions {
65
+ /** A diagram's kind, the word in its heading tag (Annex A); `class` when absent. */
66
+ kind?: UmlDiagramKind;
67
+ /** A fragment's interaction operator (§17.6.4); `alt` when absent. */
68
+ operator?: UmlFragmentOperator;
69
+ }
70
+ /**
71
+ * A frame at `at`, at the size the toolbar draws it, with id `local`.
72
+ *
73
+ * A diagram, a subject, a partition and a region take `name`, or their
74
+ * English seed. A combined fragment is born unnamed, as the toolbar draws it:
75
+ * its `name` is the guard of its first operand (`[x > 0]`), or under `ref` the
76
+ * name of the interaction it refers to (§17.7.4), and is written only when
77
+ * given. A partition is born vertical (the model's default, never restated)
78
+ * and a fragment with one operand and no `operands` (the model's promise).
79
+ */
80
+ export declare function umlFrameElement(type: UmlFrameType, at: ToolboxPoint, name?: string, local?: string, options?: UmlFrameOptions): SerializedElementProps;
81
+ /** The connector an edge tool is armed with, between two ends. */
82
+ export declare function umlRelationElement(edge: UmlEdgeRole, from: string, to: string, label?: string): SerializedElementProps;
83
+ /** Every item of the UML toolbar that places elements, in the toolbar's order. */
84
+ export declare const UML_TOOLBOX: FrameworkToolbox;
@@ -0,0 +1,172 @@
1
+ import { Bound } from '@formicoidea/labre-core/global/gfx';
2
+ import { umlCommandsManifest } from './commands-manifest.js';
3
+ import { umlCompartmentBoxes } from './component.js';
4
+ import { UML_NODE_BOX } from './consts.js';
5
+ import { UML_EDGE_STYLE } from './edge-styles.js';
6
+ import { UML_NAME_SEED, UML_OPERATIONS_SEED, UML_UNLABELLED_KINDS, umlBodySeed, } from './keywords.js';
7
+ import { UML_FRAME_BOX, umlArtefactElements, umlConnectorProps, umlFrameProps, } from './presets.js';
8
+ import { umlDiagramRoleKey, umlRoleKey, umlSubjectRoleKey } from './roles.js';
9
+ /**
10
+ * A UML artefact at `at` (its top-left), at the size the toolbar draws it: the
11
+ * shape, its tiers and the group LAST (`umlArtefactElements`), with ids
12
+ * `<local>:shape`, `<local>:name`, `<local>:attributes`, `<local>:operations`
13
+ * and `<local>:group`. A relationship attaches to `<local>:shape`. A mark the
14
+ * notation draws no word on (an initial node, a fork…) is the shape alone.
15
+ *
16
+ * A port's, an interface mark's and a time event's word is written BESIDE
17
+ * the glyph (`UML_BESIDE_LABEL_KINDS`): the group is wider than the shape's
18
+ * box there, and only there.
19
+ */
20
+ export function umlNodeElements(kind, at, texts = {}, local = kind) {
21
+ const { w, h } = UML_NODE_BOX[kind];
22
+ return umlArtefactElements(kind, { x: at.x, y: at.y, w, h }, {
23
+ name: texts.name?.trim() || UML_NAME_SEED[kind],
24
+ // `??` and not `||`: an empty compartment is a statement (a class with
25
+ // no operations yet), where an empty name never is.
26
+ attributes: texts.attributes?.trimEnd() ?? umlBodySeed(kind)[1],
27
+ operations: texts.operations?.trimEnd() ?? UML_OPERATIONS_SEED,
28
+ }, part => `${local}:${part}`);
29
+ }
30
+ /**
31
+ * A frame's name as `[key, English]`: the key the toolbar resolves at
32
+ * placement (`docs/adr/0023`), the English a host with no catalogue writes.
33
+ * The key is the frame ROLE's: the two are the same noun, and a second key
34
+ * would ask a host to word it twice. A combined fragment has none: its `name`
35
+ * is a guard, and the notation draws a guard only where there is one.
36
+ */
37
+ export const UML_FRAME_SEED = {
38
+ umlDiagram: [umlDiagramRoleKey, 'Diagram'],
39
+ umlSubject: [umlSubjectRoleKey, 'Subject'],
40
+ umlPartition: [umlRoleKey('partition'), 'Partition'],
41
+ umlRegion: [umlRoleKey('region'), 'Region'],
42
+ };
43
+ /**
44
+ * A frame at `at`, at the size the toolbar draws it, with id `local`.
45
+ *
46
+ * A diagram, a subject, a partition and a region take `name`, or their
47
+ * English seed. A combined fragment is born unnamed, as the toolbar draws it:
48
+ * its `name` is the guard of its first operand (`[x > 0]`), or under `ref` the
49
+ * name of the interaction it refers to (§17.7.4), and is written only when
50
+ * given. A partition is born vertical (the model's default, never restated)
51
+ * and a fragment with one operand and no `operands` (the model's promise).
52
+ */
53
+ export function umlFrameElement(type, at, name, local = type, options = {}) {
54
+ const { w, h } = UML_FRAME_BOX[type];
55
+ const stated = name?.trim();
56
+ const fields = type === 'umlFragment'
57
+ ? {
58
+ // The operator IS the fragment: written even at the model's default,
59
+ // so the pentagon never depends on a default nobody can see.
60
+ operator: options.operator ?? 'alt',
61
+ ...(stated ? { name: stated } : {}),
62
+ }
63
+ : {
64
+ name: stated || UML_FRAME_SEED[type][1],
65
+ // Annex A's heading is `<kind> <name>`: a frame with no kind has
66
+ // nothing to write in its tag.
67
+ ...(type === 'umlDiagram' ? { kind: options.kind ?? 'class' } : {}),
68
+ };
69
+ return {
70
+ ...umlFrameProps(type, { xywh: new Bound(at.x, at.y, w, h).serialize() }, fields),
71
+ id: local,
72
+ };
73
+ }
74
+ /** The connector an edge tool is armed with, between two ends. */
75
+ export function umlRelationElement(edge, from, to, label) {
76
+ return {
77
+ ...umlConnectorProps(edge),
78
+ source: { id: from },
79
+ target: { id: to },
80
+ ...(label?.trim() ? { text: label.trim() } : {}),
81
+ };
82
+ }
83
+ /* ── The list, walked off the tables ──────────────────────────────────── */
84
+ const camel = (kebab) => kebab.replace(/-(\w)/g, (_, letter) => letter.toUpperCase());
85
+ const pascal = (kebab) => {
86
+ const word = camel(kebab);
87
+ return word.charAt(0).toUpperCase() + word.slice(1);
88
+ };
89
+ /** `uml.addUseCase` → `use-case`, for every kind the model draws. */
90
+ const NODE_OF_COMMAND = new Map(Object.keys(UML_NODE_BOX).map(kind => [
91
+ `uml.add${pascal(kind)}`,
92
+ kind,
93
+ ]));
94
+ /** `uml.communicationPathTool` → `communication-path`, for every edge style. */
95
+ const EDGE_OF_COMMAND = new Map(Object.keys(UML_EDGE_STYLE).map(edge => [
96
+ `uml.${camel(edge)}Tool`,
97
+ edge,
98
+ ]));
99
+ /**
100
+ * The six frame commands. The interaction use is a fragment under `ref`
101
+ * (§17.7.4): the same element, a command of its own because pointing at
102
+ * another diagram is a different modelling act from branching this one.
103
+ */
104
+ const FRAME_OF_COMMAND = {
105
+ 'uml.addDiagram': { id: 'diagram', type: 'umlDiagram' },
106
+ 'uml.addSubject': { id: 'subject', type: 'umlSubject' },
107
+ 'uml.addPartition': { id: 'partition', type: 'umlPartition' },
108
+ 'uml.addRegion': { id: 'region', type: 'umlRegion' },
109
+ 'uml.addFragment': { id: 'fragment', type: 'umlFragment' },
110
+ 'uml.addInteractionUse': {
111
+ id: 'interaction-use',
112
+ type: 'umlFragment',
113
+ options: { operator: 'ref' },
114
+ },
115
+ };
116
+ /** The slots a kind's builder fills: the tiers its layout gives it. */
117
+ function slotsOf(kind) {
118
+ if (UML_UNLABELLED_KINDS.has(kind))
119
+ return [];
120
+ const { w, h } = UML_NODE_BOX[kind];
121
+ const boxes = umlCompartmentBoxes(kind, 0, 0, w, h);
122
+ return [
123
+ 'name',
124
+ ...(boxes.attributes ? ['attributes'] : []),
125
+ ...(boxes.operations ? ['operations'] : []),
126
+ ];
127
+ }
128
+ function toolbox() {
129
+ const items = [];
130
+ const links = [];
131
+ for (const row of umlCommandsManifest) {
132
+ const label = {
133
+ labelKey: row.labelKey,
134
+ labelFallback: row.labelFallback ?? row.id,
135
+ };
136
+ const kind = NODE_OF_COMMAND.get(row.id);
137
+ if (kind) {
138
+ items.push({
139
+ id: kind,
140
+ ...label,
141
+ texts: slotsOf(kind),
142
+ size: UML_NODE_BOX[kind],
143
+ build: (at, texts, local) => umlNodeElements(kind, at, texts, local),
144
+ });
145
+ continue;
146
+ }
147
+ const frame = FRAME_OF_COMMAND[row.id];
148
+ if (frame) {
149
+ items.push({
150
+ id: frame.id,
151
+ ...label,
152
+ texts: ['name'],
153
+ size: UML_FRAME_BOX[frame.type],
154
+ build: (at, texts, local) => [
155
+ umlFrameElement(frame.type, at, texts?.name, local ?? frame.id, frame.options),
156
+ ],
157
+ });
158
+ continue;
159
+ }
160
+ const edge = EDGE_OF_COMMAND.get(row.id);
161
+ if (edge) {
162
+ links.push({
163
+ id: edge,
164
+ ...label,
165
+ build: (from, to, text) => umlRelationElement(edge, from, to, text),
166
+ });
167
+ }
168
+ }
169
+ return { framework: 'uml', items, links };
170
+ }
171
+ /** Every item of the UML toolbar that places elements, in the toolbar's order. */
172
+ export const UML_TOOLBOX = toolbox();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@formicoidea/labre-framework-uml",
3
3
  "description": "Labre uml framework for @formicoidea/labre-core.",
4
- "version": "0.59.0",
4
+ "version": "0.60.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "author": "lajola",
@@ -35,8 +35,8 @@
35
35
  "dist"
36
36
  ],
37
37
  "dependencies": {
38
- "@formicoidea/labre-core": "0.59.0",
39
- "@formicoidea/labre-ddd-shared": "0.59.0",
38
+ "@formicoidea/labre-core": "0.60.0",
39
+ "@formicoidea/labre-ddd-shared": "0.60.0",
40
40
  "lit": "^3.2.0"
41
41
  }
42
42
  }