@formicoidea/labre-framework-bpmn 0.32.0 → 0.34.1
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 +202 -6
- package/dist/actions.js +427 -43
- package/dist/background.d.ts +2 -0
- package/dist/background.js +158 -0
- package/dist/commands-manifest.d.ts +18 -0
- package/dist/commands-manifest.js +226 -0
- package/dist/commands.js +496 -5
- package/dist/consts.d.ts +195 -4
- package/dist/consts.js +230 -4
- package/dist/element-renderer.d.ts +10 -4
- package/dist/element-renderer.js +14 -55
- package/dist/element-view.d.ts +119 -8
- package/dist/element-view.js +274 -30
- package/dist/export.d.ts +277 -0
- package/dist/export.js +1802 -0
- package/dist/facts.d.ts +48 -0
- package/dist/facts.js +127 -0
- package/dist/import.d.ts +69 -0
- package/dist/import.js +1476 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +47 -0
- package/dist/interchange.d.ts +109 -0
- package/dist/interchange.js +191 -0
- package/dist/morph.d.ts +61 -0
- package/dist/morph.js +118 -0
- package/dist/node/node-renderer.d.ts +0 -9
- package/dist/node/node-renderer.js +294 -17
- package/dist/pool-hit.d.ts +98 -0
- package/dist/pool-hit.js +130 -0
- package/dist/presets.d.ts +168 -0
- package/dist/presets.js +327 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +189 -0
- package/dist/roles.d.ts +96 -0
- package/dist/roles.js +410 -0
- package/dist/rules.d.ts +199 -0
- package/dist/rules.js +1539 -0
- package/dist/templates/index.js +116 -9
- package/dist/toolbar/bpmn-senior-button.js +8 -2
- package/dist/toolbar/config.d.ts +28 -2
- package/dist/toolbar/config.js +93 -4
- package/dist/toolbar/icons.d.ts +67 -0
- package/dist/toolbar/icons.js +141 -0
- package/dist/toolbar/senior-tool.js +1 -0
- package/dist/translations.d.ts +3 -1
- package/dist/translations.js +38 -3
- package/dist/view.d.ts +6 -2
- package/dist/view.js +68 -5
- package/package.json +6 -2
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { BpmnNodeKind } from '@formicoidea/labre-core/model';
|
|
2
|
+
import { StrokeStyle, TextAlign, TextFitMode, TextVerticalAlign } from '@formicoidea/labre-core/model';
|
|
3
|
+
/**
|
|
4
|
+
* What a BPMN artefact is BORN as — the one description of a node's props, read
|
|
5
|
+
* by every site that creates one.
|
|
6
|
+
*
|
|
7
|
+
* Lifted out of `actions.ts` when the importer arrived, and lifted rather than
|
|
8
|
+
* copied for the reason the copy would have failed: a task read out of a
|
|
9
|
+
* `.bpmn` file and a task drawn from the palette must be the same element in
|
|
10
|
+
* the document, down to the stroke width and the text fit mode. Two builders
|
|
11
|
+
* would agree on the day they were written and drift on the first restyle,
|
|
12
|
+
* and the drift would show up as an imported board that looks subtly unlike a
|
|
13
|
+
* drawn one — the kind of difference nobody reports and everybody notices.
|
|
14
|
+
*
|
|
15
|
+
* Pure data and one pure function: no `BlockStdScope`, no surface, no viewport.
|
|
16
|
+
* The creation gesture supplies the box, the importer supplies the box the file
|
|
17
|
+
* gave it, and neither knows anything the other does not.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* The native shape and accent a kind is born with (style C).
|
|
21
|
+
*
|
|
22
|
+
* `glyphBody` is the one non-obvious field: the three data/artifact shapes have
|
|
23
|
+
* a silhouette a native rect cannot make — a folded page, a cylinder, an open
|
|
24
|
+
* bracket — so for those the renderer's glyph paints the BODY as well as the
|
|
25
|
+
* decoration, and the native shape is created unfilled and unstroked. It still
|
|
26
|
+
* earns its keep: it is what carries the inner text, the selection bounds, the
|
|
27
|
+
* resize handles and the connector anchors.
|
|
28
|
+
*/
|
|
29
|
+
export interface BpmnNodePreset {
|
|
30
|
+
shapeType: 'ellipse' | 'rect' | 'diamond';
|
|
31
|
+
stroke: string;
|
|
32
|
+
width: number;
|
|
33
|
+
/** Corner radius, `rect` only. Absent means a square corner. */
|
|
34
|
+
radius?: number;
|
|
35
|
+
/** Border style. Absent means a solid line, which is what BPMN mostly draws. */
|
|
36
|
+
strokeStyle?: StrokeStyle;
|
|
37
|
+
/**
|
|
38
|
+
* No fill — an OUTLINE, not a body. It also decides what the artefact does to
|
|
39
|
+
* a click: an unfilled shape is hit near its border and on its label only, so
|
|
40
|
+
* the group never steals a click from the work it encloses.
|
|
41
|
+
*/
|
|
42
|
+
hollow?: true;
|
|
43
|
+
/** The glyph draws the body; the native shape paints nothing. */
|
|
44
|
+
glyphBody?: true;
|
|
45
|
+
/**
|
|
46
|
+
* Where the inner text sits. Absent means centred, which is what every
|
|
47
|
+
* artefact whose label names the artefact itself wants. The group is the
|
|
48
|
+
* exception: its label names a region, so it goes in the corner of it.
|
|
49
|
+
*/
|
|
50
|
+
textAlign?: TextAlign;
|
|
51
|
+
textVerticalAlign?: TextVerticalAlign;
|
|
52
|
+
}
|
|
53
|
+
/** Per-kind native shape + accent presets (style C). */
|
|
54
|
+
export declare const NODE_PRESETS: Record<BpmnNodeKind, BpmnNodePreset>;
|
|
55
|
+
/** The label typography a foreign box asks for. See {@link bpmnLabelFit}. */
|
|
56
|
+
export interface BpmnLabelFit {
|
|
57
|
+
fontSize: number;
|
|
58
|
+
/** `[vertical, horizontal]`, as `ShapeElementModel.padding` is written. */
|
|
59
|
+
padding: [number, number];
|
|
60
|
+
textFitMode: TextFitMode;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The typography a label needs to sit INSIDE a box this pack did not choose —
|
|
64
|
+
* which in practice means a box an interchange file chose (`import.ts`).
|
|
65
|
+
*
|
|
66
|
+
* ## Why an imported label misbehaves and a drawn one does not
|
|
67
|
+
*
|
|
68
|
+
* Every value the creation builder writes is calibrated against
|
|
69
|
+
* {@link NODE_SIZE}: an 18-unit font and the shape's native 20-unit horizontal
|
|
70
|
+
* inset are comfortable in a 120-unit task, which is what the palette draws.
|
|
71
|
+
* A file draws to its author's scale, and bpmn.io's normative sizes — the ones
|
|
72
|
+
* nearly every `.bpmn` in the wild carries — are a 100×80 task and a 36-unit
|
|
73
|
+
* event. In a 100-unit task the native inset leaves 60 units of line, which is
|
|
74
|
+
* less than the word "Étudier" is wide at 18 units, so the label breaks in the
|
|
75
|
+
* middle of a word; in a 36-unit event it leaves NEGATIVE room, and the name
|
|
76
|
+
* sprawls across the canvas. Both are issue #184, and neither is reachable from
|
|
77
|
+
* the palette.
|
|
78
|
+
*
|
|
79
|
+
* So the three props that decide how a label sits are derived from the box:
|
|
80
|
+
*
|
|
81
|
+
* - **`fontSize`** shrinks with the box, proportionally, and never grows past
|
|
82
|
+
* {@link INNER_FONT_SIZE} — a file that draws BIGGER than this pack gets the
|
|
83
|
+
* pack's own type, not inflated type — with {@link LABEL_MIN_FONT_SIZE} as
|
|
84
|
+
* the floor;
|
|
85
|
+
* - **`padding`** becomes {@link LABEL_INSET_RATIO} of the box, capped at the
|
|
86
|
+
* shape's native inset, so the margin follows the artefact down instead of
|
|
87
|
+
* eating it;
|
|
88
|
+
* - **`textFitMode`** becomes `Contained`, which is the honest statement of
|
|
89
|
+
* what an imported artefact IS: the file fixed the box — re-exporting a
|
|
90
|
+
* different one would rewrite the author's diagram — so the TEXT is what
|
|
91
|
+
* yields. The renderer then shrinks the font further, per frame, whenever a
|
|
92
|
+
* long name still does not fit.
|
|
93
|
+
*
|
|
94
|
+
* Pure arithmetic on two numbers: no measuring, no canvas, no renderer. The
|
|
95
|
+
* fit does not have to be exact, because `Contained` finishes it at paint time
|
|
96
|
+
* with the real font metrics; what this has to do is stop asking for type the
|
|
97
|
+
* box was never going to hold.
|
|
98
|
+
*/
|
|
99
|
+
export declare function bpmnLabelFit(kind: BpmnNodeKind, w: number, h: number): BpmnLabelFit;
|
|
100
|
+
/**
|
|
101
|
+
* One BPMN node, as the props `surface.addElement` takes.
|
|
102
|
+
*
|
|
103
|
+
* The single description of what a `bpmnNode` IS in a document: the palette
|
|
104
|
+
* hands it a box centred on the viewport, the importer hands it the box the
|
|
105
|
+
* file's `dc:Bounds` gave, and the element that lands is the same either way.
|
|
106
|
+
*
|
|
107
|
+
* `text` is passed through unchanged, `undefined` included — an artefact whose
|
|
108
|
+
* label the source left empty gets no `text` key at all rather than an empty
|
|
109
|
+
* one, which is what keeps an imported node byte-comparable with a drawn one
|
|
110
|
+
* that was never typed into.
|
|
111
|
+
*
|
|
112
|
+
* `fitLabel` says the box is NOT the pack's own — the caller took it from a
|
|
113
|
+
* file it may not rewrite — so the label is fitted to it
|
|
114
|
+
* ({@link bpmnLabelFit}). It is asked for explicitly rather than inferred from
|
|
115
|
+
* the box's size for two reasons: an importer knows whether it read a
|
|
116
|
+
* `dc:Bounds` and a guess never would, and {@link bpmnMorphProps} calls this
|
|
117
|
+
* builder with a throwaway box whose numbers must not be allowed to mean
|
|
118
|
+
* anything.
|
|
119
|
+
*/
|
|
120
|
+
export declare function bpmnNodeProps(kind: BpmnNodeKind, box: {
|
|
121
|
+
xywh: string;
|
|
122
|
+
text?: string;
|
|
123
|
+
fitLabel?: boolean;
|
|
124
|
+
}): Record<string, unknown> & {
|
|
125
|
+
type: string;
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* What a node's kind is worth to an element that ALREADY EXISTS — the same
|
|
129
|
+
* description as {@link bpmnNodeProps}, minus the three things a morph has no
|
|
130
|
+
* business touching.
|
|
131
|
+
*
|
|
132
|
+
* Derived from the creation builder rather than restated beside it, and that is
|
|
133
|
+
* the whole point of the function.
|
|
134
|
+
*
|
|
135
|
+
* ## Why a `{kind, role}` patch is not enough
|
|
136
|
+
*
|
|
137
|
+
* Because the appearance of a BPMN artefact lives in props the CREATING kind's
|
|
138
|
+
* preset wrote, and nothing else ever rewrites them. One shipped morph pair
|
|
139
|
+
* shows it today: `subProcess` and `callActivity` are the same rounded
|
|
140
|
+
* rectangle and differ only in `strokeWidth` — 2 against 4 — and that thick
|
|
141
|
+
* border IS how a reader tells "this box stands for a process defined
|
|
142
|
+
* elsewhere" from "this box stands for one defined inline". Morph between them
|
|
143
|
+
* with two keys and the call activity arrives wearing the sub-process's thin
|
|
144
|
+
* border, which is a drawing that says the wrong thing.
|
|
145
|
+
*
|
|
146
|
+
* Every other family declared in `./morph.ts` currently shares one preset
|
|
147
|
+
* across its members, so for those the full patch changes nothing — and that is
|
|
148
|
+
* the second reason to write it this way rather than to trim it. A family is
|
|
149
|
+
* DATA (`BPMN_MORPH_FAMILIES`) and grows by declaration, with no code change to
|
|
150
|
+
* prompt anyone to ask whether the presets still agree; deriving the patch from
|
|
151
|
+
* the creation builder means the answer is right in advance. It is also what
|
|
152
|
+
* guarantees that a morphed artefact and one drawn fresh from the palette are
|
|
153
|
+
* the same element — two builders would agree the day they were written and
|
|
154
|
+
* drift on the first restyle, which is the argument this file already makes for
|
|
155
|
+
* having one creation builder at all.
|
|
156
|
+
*/
|
|
157
|
+
export declare function bpmnMorphProps(kind: BpmnNodeKind): Record<string, unknown>;
|
|
158
|
+
/**
|
|
159
|
+
* The fields to DELETE from an element after morphing it to `kind` — the keys
|
|
160
|
+
* some other kind writes and this one does not.
|
|
161
|
+
*
|
|
162
|
+
* A patch cannot express absence. `textVerticalAlign` is spread conditionally
|
|
163
|
+
* (see {@link bpmnNodeProps}), so morphing away from the group would leave
|
|
164
|
+
* `Top` sitting in the Y.Map and silently in force over a preset that means
|
|
165
|
+
* "centred". `clearField` removes the key, which is the same call `writeLanes`
|
|
166
|
+
* makes when a pool loses its last lane.
|
|
167
|
+
*/
|
|
168
|
+
export declare function bpmnMorphClears(kind: BpmnNodeKind): readonly string[];
|
package/dist/presets.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { FontFamily, SHAPE_TEXT_PADDING, SHAPE_TEXT_VERTICAL_PADDING, ShapeStyle, StrokeStyle, TextAlign, TextFitMode, TextVerticalAlign, } from '@formicoidea/labre-core/model';
|
|
2
|
+
import { Bound } from '@formicoidea/labre-core/global/gfx';
|
|
3
|
+
import { CALL_ACTIVITY_WIDTH, END_WIDTH, EVENT_END, EVENT_START, GROUP_RADIUS, GROUP_STROKE, INNER_FONT_SIZE, LABEL_INSET_RATIO, LABEL_MIN_FONT_SIZE, NEUTRAL_STROKE, NODE_FILL, NODE_SIZE, NODE_STROKE_WIDTH, START_WIDTH, TASK_RADIUS, } from './consts.js';
|
|
4
|
+
import { BPMN_ROLE_OF_KIND } from './roles.js';
|
|
5
|
+
/** Per-kind native shape + accent presets (style C). */
|
|
6
|
+
export const NODE_PRESETS = {
|
|
7
|
+
// Events: one ellipse, and the ring weight says start or end. The message and
|
|
8
|
+
// timer variants keep their family's ring exactly — a message START is a thin
|
|
9
|
+
// green ring with an envelope in it, and a message END the thick red one.
|
|
10
|
+
startEvent: { shapeType: 'ellipse', stroke: EVENT_START, width: START_WIDTH },
|
|
11
|
+
startEventMessage: {
|
|
12
|
+
shapeType: 'ellipse',
|
|
13
|
+
stroke: EVENT_START,
|
|
14
|
+
width: START_WIDTH,
|
|
15
|
+
},
|
|
16
|
+
startEventTimer: {
|
|
17
|
+
shapeType: 'ellipse',
|
|
18
|
+
stroke: EVENT_START,
|
|
19
|
+
width: START_WIDTH,
|
|
20
|
+
},
|
|
21
|
+
endEvent: { shapeType: 'ellipse', stroke: EVENT_END, width: END_WIDTH },
|
|
22
|
+
endEventMessage: {
|
|
23
|
+
shapeType: 'ellipse',
|
|
24
|
+
stroke: EVENT_END,
|
|
25
|
+
width: END_WIDTH,
|
|
26
|
+
},
|
|
27
|
+
endEventTerminate: {
|
|
28
|
+
shapeType: 'ellipse',
|
|
29
|
+
stroke: EVENT_END,
|
|
30
|
+
width: END_WIDTH,
|
|
31
|
+
},
|
|
32
|
+
// Activities: the same rounded rectangle, and a marker tells them apart —
|
|
33
|
+
// except the call activity, whose thick border IS the distinction (it carries
|
|
34
|
+
// the same `+` as the sub-process).
|
|
35
|
+
task: {
|
|
36
|
+
shapeType: 'rect',
|
|
37
|
+
stroke: NEUTRAL_STROKE,
|
|
38
|
+
width: NODE_STROKE_WIDTH,
|
|
39
|
+
radius: TASK_RADIUS,
|
|
40
|
+
},
|
|
41
|
+
taskUser: {
|
|
42
|
+
shapeType: 'rect',
|
|
43
|
+
stroke: NEUTRAL_STROKE,
|
|
44
|
+
width: NODE_STROKE_WIDTH,
|
|
45
|
+
radius: TASK_RADIUS,
|
|
46
|
+
},
|
|
47
|
+
taskService: {
|
|
48
|
+
shapeType: 'rect',
|
|
49
|
+
stroke: NEUTRAL_STROKE,
|
|
50
|
+
width: NODE_STROKE_WIDTH,
|
|
51
|
+
radius: TASK_RADIUS,
|
|
52
|
+
},
|
|
53
|
+
subProcess: {
|
|
54
|
+
shapeType: 'rect',
|
|
55
|
+
stroke: NEUTRAL_STROKE,
|
|
56
|
+
width: NODE_STROKE_WIDTH,
|
|
57
|
+
radius: TASK_RADIUS,
|
|
58
|
+
},
|
|
59
|
+
callActivity: {
|
|
60
|
+
shapeType: 'rect',
|
|
61
|
+
stroke: NEUTRAL_STROKE,
|
|
62
|
+
width: CALL_ACTIVITY_WIDTH,
|
|
63
|
+
radius: TASK_RADIUS,
|
|
64
|
+
},
|
|
65
|
+
// Gateways: one diamond, one marker each.
|
|
66
|
+
gatewayExclusive: {
|
|
67
|
+
shapeType: 'diamond',
|
|
68
|
+
stroke: NEUTRAL_STROKE,
|
|
69
|
+
width: NODE_STROKE_WIDTH,
|
|
70
|
+
},
|
|
71
|
+
gatewayParallel: {
|
|
72
|
+
shapeType: 'diamond',
|
|
73
|
+
stroke: NEUTRAL_STROKE,
|
|
74
|
+
width: NODE_STROKE_WIDTH,
|
|
75
|
+
},
|
|
76
|
+
// Data and artifacts: body drawn by the glyph (see {@link BpmnNodePreset}).
|
|
77
|
+
dataObject: {
|
|
78
|
+
shapeType: 'rect',
|
|
79
|
+
stroke: NEUTRAL_STROKE,
|
|
80
|
+
width: NODE_STROKE_WIDTH,
|
|
81
|
+
glyphBody: true,
|
|
82
|
+
},
|
|
83
|
+
dataStore: {
|
|
84
|
+
shapeType: 'rect',
|
|
85
|
+
stroke: NEUTRAL_STROKE,
|
|
86
|
+
width: NODE_STROKE_WIDTH,
|
|
87
|
+
glyphBody: true,
|
|
88
|
+
},
|
|
89
|
+
textAnnotation: {
|
|
90
|
+
shapeType: 'rect',
|
|
91
|
+
stroke: NEUTRAL_STROKE,
|
|
92
|
+
width: NODE_STROKE_WIDTH,
|
|
93
|
+
glyphBody: true,
|
|
94
|
+
},
|
|
95
|
+
// The group: a dashed grey outline round part of the picture. Entirely a
|
|
96
|
+
// native shape — no glyph, nothing for the renderer to do — because the
|
|
97
|
+
// notation asks for exactly what `strokeStyle: dash` already draws.
|
|
98
|
+
group: {
|
|
99
|
+
shapeType: 'rect',
|
|
100
|
+
stroke: GROUP_STROKE,
|
|
101
|
+
width: NODE_STROKE_WIDTH,
|
|
102
|
+
radius: GROUP_RADIUS,
|
|
103
|
+
strokeStyle: StrokeStyle.Dash,
|
|
104
|
+
hollow: true,
|
|
105
|
+
textAlign: TextAlign.Left,
|
|
106
|
+
textVerticalAlign: TextVerticalAlign.Top,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* `[w, h]` off a serialized box.
|
|
111
|
+
*
|
|
112
|
+
* A `Bound` and not a hand-rolled `split(',')`: the same deserializer the store
|
|
113
|
+
* reads an element's geometry with, so a box this builder measures and a box
|
|
114
|
+
* the canvas draws can never be two different rectangles.
|
|
115
|
+
*/
|
|
116
|
+
function boxExtent(xywh) {
|
|
117
|
+
const bound = Bound.deserialize(xywh);
|
|
118
|
+
return [bound.w, bound.h];
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* How big a box is against the pack's own — and `1`, meaning "the pack's own",
|
|
122
|
+
* for an extent that says nothing.
|
|
123
|
+
*
|
|
124
|
+
* A `dc:Bounds` of zeros, of negatives or of `NaN` (which is what a
|
|
125
|
+
* non-numeric attribute parses to) reaches an importer on its first afternoon
|
|
126
|
+
* in the wild. The answer for a box that says nothing is the DRAWN artefact's
|
|
127
|
+
* type, not a shrunken guess at a size nobody gave — and never `NaN`, which is
|
|
128
|
+
* not a number the store can hold.
|
|
129
|
+
*/
|
|
130
|
+
const ratioOf = (extent, standard) => Number.isFinite(extent) && extent > 0 ? extent / standard : 1;
|
|
131
|
+
/** One side's margin: the ratio of the extent, never more than the native inset. */
|
|
132
|
+
const insetOf = (extent, native) => Number.isFinite(extent) && extent > 0
|
|
133
|
+
? Math.round(Math.min(native, extent * LABEL_INSET_RATIO))
|
|
134
|
+
: native;
|
|
135
|
+
/**
|
|
136
|
+
* The typography a label needs to sit INSIDE a box this pack did not choose —
|
|
137
|
+
* which in practice means a box an interchange file chose (`import.ts`).
|
|
138
|
+
*
|
|
139
|
+
* ## Why an imported label misbehaves and a drawn one does not
|
|
140
|
+
*
|
|
141
|
+
* Every value the creation builder writes is calibrated against
|
|
142
|
+
* {@link NODE_SIZE}: an 18-unit font and the shape's native 20-unit horizontal
|
|
143
|
+
* inset are comfortable in a 120-unit task, which is what the palette draws.
|
|
144
|
+
* A file draws to its author's scale, and bpmn.io's normative sizes — the ones
|
|
145
|
+
* nearly every `.bpmn` in the wild carries — are a 100×80 task and a 36-unit
|
|
146
|
+
* event. In a 100-unit task the native inset leaves 60 units of line, which is
|
|
147
|
+
* less than the word "Étudier" is wide at 18 units, so the label breaks in the
|
|
148
|
+
* middle of a word; in a 36-unit event it leaves NEGATIVE room, and the name
|
|
149
|
+
* sprawls across the canvas. Both are issue #184, and neither is reachable from
|
|
150
|
+
* the palette.
|
|
151
|
+
*
|
|
152
|
+
* So the three props that decide how a label sits are derived from the box:
|
|
153
|
+
*
|
|
154
|
+
* - **`fontSize`** shrinks with the box, proportionally, and never grows past
|
|
155
|
+
* {@link INNER_FONT_SIZE} — a file that draws BIGGER than this pack gets the
|
|
156
|
+
* pack's own type, not inflated type — with {@link LABEL_MIN_FONT_SIZE} as
|
|
157
|
+
* the floor;
|
|
158
|
+
* - **`padding`** becomes {@link LABEL_INSET_RATIO} of the box, capped at the
|
|
159
|
+
* shape's native inset, so the margin follows the artefact down instead of
|
|
160
|
+
* eating it;
|
|
161
|
+
* - **`textFitMode`** becomes `Contained`, which is the honest statement of
|
|
162
|
+
* what an imported artefact IS: the file fixed the box — re-exporting a
|
|
163
|
+
* different one would rewrite the author's diagram — so the TEXT is what
|
|
164
|
+
* yields. The renderer then shrinks the font further, per frame, whenever a
|
|
165
|
+
* long name still does not fit.
|
|
166
|
+
*
|
|
167
|
+
* Pure arithmetic on two numbers: no measuring, no canvas, no renderer. The
|
|
168
|
+
* fit does not have to be exact, because `Contained` finishes it at paint time
|
|
169
|
+
* with the real font metrics; what this has to do is stop asking for type the
|
|
170
|
+
* box was never going to hold.
|
|
171
|
+
*/
|
|
172
|
+
export function bpmnLabelFit(kind, w, h) {
|
|
173
|
+
const size = NODE_SIZE[kind];
|
|
174
|
+
const scale = Math.min(1, ratioOf(w, size.w), ratioOf(h, size.h));
|
|
175
|
+
return {
|
|
176
|
+
fontSize: Math.max(LABEL_MIN_FONT_SIZE, Math.round(INNER_FONT_SIZE * scale)),
|
|
177
|
+
padding: [
|
|
178
|
+
insetOf(h, SHAPE_TEXT_VERTICAL_PADDING),
|
|
179
|
+
insetOf(w, SHAPE_TEXT_PADDING),
|
|
180
|
+
],
|
|
181
|
+
textFitMode: TextFitMode.Contained,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* One BPMN node, as the props `surface.addElement` takes.
|
|
186
|
+
*
|
|
187
|
+
* The single description of what a `bpmnNode` IS in a document: the palette
|
|
188
|
+
* hands it a box centred on the viewport, the importer hands it the box the
|
|
189
|
+
* file's `dc:Bounds` gave, and the element that lands is the same either way.
|
|
190
|
+
*
|
|
191
|
+
* `text` is passed through unchanged, `undefined` included — an artefact whose
|
|
192
|
+
* label the source left empty gets no `text` key at all rather than an empty
|
|
193
|
+
* one, which is what keeps an imported node byte-comparable with a drawn one
|
|
194
|
+
* that was never typed into.
|
|
195
|
+
*
|
|
196
|
+
* `fitLabel` says the box is NOT the pack's own — the caller took it from a
|
|
197
|
+
* file it may not rewrite — so the label is fitted to it
|
|
198
|
+
* ({@link bpmnLabelFit}). It is asked for explicitly rather than inferred from
|
|
199
|
+
* the box's size for two reasons: an importer knows whether it read a
|
|
200
|
+
* `dc:Bounds` and a guess never would, and {@link bpmnMorphProps} calls this
|
|
201
|
+
* builder with a throwaway box whose numbers must not be allowed to mean
|
|
202
|
+
* anything.
|
|
203
|
+
*/
|
|
204
|
+
export function bpmnNodeProps(kind, box) {
|
|
205
|
+
const preset = NODE_PRESETS[kind];
|
|
206
|
+
// Nothing to fit without a label: an artefact whose name the file left empty
|
|
207
|
+
// stays byte-identical to a drawn one, which is what `presets.ts` is for.
|
|
208
|
+
const fit = box.fitLabel && box.text
|
|
209
|
+
? bpmnLabelFit(kind, ...boxExtent(box.xywh))
|
|
210
|
+
: undefined;
|
|
211
|
+
return {
|
|
212
|
+
type: 'bpmnNode',
|
|
213
|
+
kind,
|
|
214
|
+
// Semantic identity (B1): posted next to `kind`, which stays untouched and
|
|
215
|
+
// keeps driving the rendering. The role is the authority on what the node
|
|
216
|
+
// MEANS — see the table in `./roles.ts`.
|
|
217
|
+
role: BPMN_ROLE_OF_KIND[kind],
|
|
218
|
+
shapeType: preset.shapeType,
|
|
219
|
+
// A glyph-bodied artefact paints nothing natively: the folded page, the
|
|
220
|
+
// cylinder and the bracket are drawn by the renderer, which reads
|
|
221
|
+
// `fillColor` / `strokeColor` off this same model — so both stay editable
|
|
222
|
+
// from the shape toolbar exactly like every other node's. A `hollow` one
|
|
223
|
+
// paints natively and simply has no body: the group is an outline.
|
|
224
|
+
filled: !preset.glyphBody && !preset.hollow,
|
|
225
|
+
fillColor: NODE_FILL,
|
|
226
|
+
strokeColor: preset.stroke,
|
|
227
|
+
strokeWidth: preset.width,
|
|
228
|
+
strokeStyle: preset.glyphBody
|
|
229
|
+
? StrokeStyle.None
|
|
230
|
+
: (preset.strokeStyle ?? StrokeStyle.Solid),
|
|
231
|
+
shapeStyle: ShapeStyle.General,
|
|
232
|
+
roughness: 0,
|
|
233
|
+
radius: preset.radius ?? 0,
|
|
234
|
+
text: box.text,
|
|
235
|
+
color: NEUTRAL_STROKE,
|
|
236
|
+
fontFamily: FontFamily.Inter,
|
|
237
|
+
fontSize: fit?.fontSize ?? INNER_FONT_SIZE,
|
|
238
|
+
textAlign: preset.textAlign ?? TextAlign.Center,
|
|
239
|
+
// Spread, never a defaulted key: the model's own default is already
|
|
240
|
+
// `Center`, so writing it here would put a new key in the Y.Map of every
|
|
241
|
+
// artefact that does not ask for one — the same avoidable payload change
|
|
242
|
+
// review caught on `strokeStyle`.
|
|
243
|
+
...(preset.textVerticalAlign
|
|
244
|
+
? { textVerticalAlign: preset.textVerticalAlign }
|
|
245
|
+
: {}),
|
|
246
|
+
// BPMN symbols have normative sizes: a long label overflows rather
|
|
247
|
+
// than deforming the node. An imported artefact is the one case where that
|
|
248
|
+
// is not enough — its box is the FILE's and its label is the file's too, so
|
|
249
|
+
// the text is fitted to the box instead of painted past it (#184).
|
|
250
|
+
textFitMode: fit?.textFitMode ?? TextFitMode.Overflow,
|
|
251
|
+
// Spread, never a defaulted key, for the reason `textVerticalAlign` is: a
|
|
252
|
+
// drawn artefact keeps the shape's own inset and puts nothing of its own in
|
|
253
|
+
// the Y.Map.
|
|
254
|
+
...(fit ? { padding: fit.padding } : {}),
|
|
255
|
+
xywh: box.xywh,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* The box {@link bpmnMorphProps} hands {@link bpmnNodeProps} and then throws
|
|
260
|
+
* away. Never written to a document: a morph keeps the geometry the element
|
|
261
|
+
* already has, and this exists only because the one builder takes a box.
|
|
262
|
+
*/
|
|
263
|
+
const DISCARDED_BOX = '[0,0,0,0]';
|
|
264
|
+
/** What a morph must never rewrite: identity, geometry, and the user's words. */
|
|
265
|
+
const NOT_A_MORPH = ['type', 'xywh', 'text'];
|
|
266
|
+
/**
|
|
267
|
+
* What a node's kind is worth to an element that ALREADY EXISTS — the same
|
|
268
|
+
* description as {@link bpmnNodeProps}, minus the three things a morph has no
|
|
269
|
+
* business touching.
|
|
270
|
+
*
|
|
271
|
+
* Derived from the creation builder rather than restated beside it, and that is
|
|
272
|
+
* the whole point of the function.
|
|
273
|
+
*
|
|
274
|
+
* ## Why a `{kind, role}` patch is not enough
|
|
275
|
+
*
|
|
276
|
+
* Because the appearance of a BPMN artefact lives in props the CREATING kind's
|
|
277
|
+
* preset wrote, and nothing else ever rewrites them. One shipped morph pair
|
|
278
|
+
* shows it today: `subProcess` and `callActivity` are the same rounded
|
|
279
|
+
* rectangle and differ only in `strokeWidth` — 2 against 4 — and that thick
|
|
280
|
+
* border IS how a reader tells "this box stands for a process defined
|
|
281
|
+
* elsewhere" from "this box stands for one defined inline". Morph between them
|
|
282
|
+
* with two keys and the call activity arrives wearing the sub-process's thin
|
|
283
|
+
* border, which is a drawing that says the wrong thing.
|
|
284
|
+
*
|
|
285
|
+
* Every other family declared in `./morph.ts` currently shares one preset
|
|
286
|
+
* across its members, so for those the full patch changes nothing — and that is
|
|
287
|
+
* the second reason to write it this way rather than to trim it. A family is
|
|
288
|
+
* DATA (`BPMN_MORPH_FAMILIES`) and grows by declaration, with no code change to
|
|
289
|
+
* prompt anyone to ask whether the presets still agree; deriving the patch from
|
|
290
|
+
* the creation builder means the answer is right in advance. It is also what
|
|
291
|
+
* guarantees that a morphed artefact and one drawn fresh from the palette are
|
|
292
|
+
* the same element — two builders would agree the day they were written and
|
|
293
|
+
* drift on the first restyle, which is the argument this file already makes for
|
|
294
|
+
* having one creation builder at all.
|
|
295
|
+
*/
|
|
296
|
+
export function bpmnMorphProps(kind) {
|
|
297
|
+
// Widened to the plain record on the way in: `type` is required on what the
|
|
298
|
+
// creation builder returns, and `delete` may only take an optional key.
|
|
299
|
+
const props = {
|
|
300
|
+
...bpmnNodeProps(kind, { xywh: DISCARDED_BOX }),
|
|
301
|
+
};
|
|
302
|
+
for (const key of NOT_A_MORPH)
|
|
303
|
+
delete props[key];
|
|
304
|
+
return props;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Every key ANY kind's props may carry — the union over the whole pack.
|
|
308
|
+
*
|
|
309
|
+
* Computed rather than listed, so a preset that starts spreading a second
|
|
310
|
+
* conditional key is covered on the day it is added rather than on the day
|
|
311
|
+
* somebody notices.
|
|
312
|
+
*/
|
|
313
|
+
const EVERY_MORPH_KEY = new Set(Object.keys(NODE_PRESETS).flatMap(kind => Object.keys(bpmnMorphProps(kind))));
|
|
314
|
+
/**
|
|
315
|
+
* The fields to DELETE from an element after morphing it to `kind` — the keys
|
|
316
|
+
* some other kind writes and this one does not.
|
|
317
|
+
*
|
|
318
|
+
* A patch cannot express absence. `textVerticalAlign` is spread conditionally
|
|
319
|
+
* (see {@link bpmnNodeProps}), so morphing away from the group would leave
|
|
320
|
+
* `Top` sitting in the Y.Map and silently in force over a preset that means
|
|
321
|
+
* "centred". `clearField` removes the key, which is the same call `writeLanes`
|
|
322
|
+
* makes when a pool loses its last lane.
|
|
323
|
+
*/
|
|
324
|
+
export function bpmnMorphClears(kind) {
|
|
325
|
+
const present = new Set(Object.keys(bpmnMorphProps(kind)));
|
|
326
|
+
return [...EVERY_MORPH_KEY].filter(key => !present.has(key));
|
|
327
|
+
}
|