@formicoidea/labre-framework-wardley 0.41.0 → 0.43.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/LICENSE +384 -0
- package/dist/actions.d.ts +51 -13
- package/dist/actions.js +111 -54
- package/dist/commands.js +52 -31
- package/dist/element-view.d.ts +20 -34
- package/dist/element-view.js +23 -139
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/legend.d.ts +94 -17
- package/dist/legend.js +409 -484
- package/dist/morph.d.ts +3 -1
- package/dist/morph.js +66 -2
- package/dist/node/consts.d.ts +15 -13
- package/dist/node/consts.js +15 -13
- package/dist/node/node-renderer.js +12 -1
- package/dist/presets.d.ts +50 -1
- package/dist/presets.js +62 -1
- package/dist/roles.js +15 -6
- package/dist/templates/index.d.ts +5 -4
- package/dist/templates/index.js +24 -9
- package/dist/templates/maps.d.ts +1 -1
- package/dist/templates/maps.js +2 -2
- package/dist/toolbar/config.d.ts +32 -7
- package/dist/toolbar/config.js +63 -27
- package/dist/toolbar/node-config.d.ts +9 -1
- package/dist/toolbar/node-config.js +12 -0
- package/dist/toolbar/wardley-senior-button.d.ts +8 -0
- package/dist/toolbar/wardley-senior-button.js +12 -2
- package/dist/translations.js +5 -4
- package/dist/view.js +19 -15
- package/package.json +2 -2
package/dist/morph.d.ts
CHANGED
|
@@ -108,7 +108,9 @@ export declare function wardleyMorphedLabel(from: WardleyMorphKind, to: WardleyM
|
|
|
108
108
|
* dots crammed into it. So the target's canonical size is applied, CENTRED on
|
|
109
109
|
* where the artefact already stands: the thing does not move, it becomes the
|
|
110
110
|
* size its notation says it is. The label keeps its own place, which is the
|
|
111
|
-
* user's
|
|
111
|
+
* user's — with the one exception {@link relocateLabel} states: a name still
|
|
112
|
+
* standing where creation put it follows the target kind when that kind carries
|
|
113
|
+
* its name somewhere else, or the new drawing lands on top of the old words.
|
|
112
114
|
*
|
|
113
115
|
* ## And why the rest is here rather than in the patch
|
|
114
116
|
*
|
package/dist/morph.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { morphLabel, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
2
|
import { ConnectorElementModel, GroupElementModel, TextElementModel, WardleyNodeElementModel, } from '@formicoidea/labre-core/model';
|
|
3
3
|
import { translateKey } from '@formicoidea/labre-core/shared/services';
|
|
4
|
+
import { Bound } from '@formicoidea/labre-core/global/gfx';
|
|
4
5
|
import { wardleyCommandIcons, wardleyCommands } from './commands.js';
|
|
5
|
-
import { wardleyCanonicalBox, wardleyHandleBox, wardleyHandleProps, wardleyMarketDotBoxes, wardleyMarketDotProps, wardleyMarketLinkPairs, wardleyMarketLinkProps, WARDLEY_NODE_LABEL, wardleyMorphClears, wardleyMorphProps, wardleyNodeLabelKey, } from './presets.js';
|
|
6
|
+
import { wardleyCanonicalBox, wardleyHandleBox, wardleyHandleProps, WARDLEY_LABEL_H, WARDLEY_LABEL_W, wardleyLabelBoxFor, wardleyMarketDotBoxes, wardleyMarketDotProps, wardleyMarketLinkPairs, wardleyMarketLinkProps, WARDLEY_NODE_LABEL, wardleyMorphClears, wardleyMorphProps, wardleyNodeLabelKey, } from './presets.js';
|
|
6
7
|
import { WARDLEY_ROLE } from './roles.js';
|
|
7
8
|
/**
|
|
8
9
|
* What a Wardley artefact may BECOME — the declaration behind the "Change type"
|
|
@@ -338,6 +339,66 @@ function rewriteLabel(group, from, to, std) {
|
|
|
338
339
|
label.text.insert(0, next);
|
|
339
340
|
});
|
|
340
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* The offset from a label box's left edge to the point its alignment holds
|
|
344
|
+
* fixed — the edge the words grow from.
|
|
345
|
+
*/
|
|
346
|
+
function anchorOffset(w, textAlign) {
|
|
347
|
+
return textAlign === 'center' ? w / 2 : textAlign === 'right' ? w : 0;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Carry the name to where the TARGET kind holds it — and only when it is still
|
|
351
|
+
* where the SOURCE kind's creation put it.
|
|
352
|
+
*
|
|
353
|
+
* ## The bug this repairs
|
|
354
|
+
*
|
|
355
|
+
* A component wears its name to the right of an 18-pixel circle. A pipeline is
|
|
356
|
+
* a 120-wide bar: written onto the same artefact, it is drawn straight OVER
|
|
357
|
+
* those words, and a filled shape answers the pointer on its whole interior
|
|
358
|
+
* while a text answers only on its box — so which of the two got the click
|
|
359
|
+
* depended on where in the label you aimed. The name could not be edited, and
|
|
360
|
+
* dragging it moved the pipeline. Rule W3 forbids precisely that overlap.
|
|
361
|
+
*
|
|
362
|
+
* ## Why only across a change of PLACE
|
|
363
|
+
*
|
|
364
|
+
* The timid rule {@link rewriteLabel} states about the words, applied to the
|
|
365
|
+
* placement: what the author put somewhere is theirs. The three circles all
|
|
366
|
+
* carry their name beside them, so a component growing into a market leaves the
|
|
367
|
+
* words exactly where they were — the circle grows underneath them and that is
|
|
368
|
+
* the documented behaviour. Only the crossing to or from the pipeline changes
|
|
369
|
+
* the PLACE, and only that moves anything.
|
|
370
|
+
*
|
|
371
|
+
* The test is on the point the label's own alignment keeps fixed rather than on
|
|
372
|
+
* its top-left, because typing into a label resizes its box around exactly that
|
|
373
|
+
* point (`edgeless-text-editor`): a renamed label is still where creation put
|
|
374
|
+
* it, and a MOVED one is not.
|
|
375
|
+
*/
|
|
376
|
+
function relocateLabel(group, from, to, cx, cy) {
|
|
377
|
+
const source = wardleyLabelBoxFor(from, cx, cy);
|
|
378
|
+
const target = wardleyLabelBoxFor(to, cx, cy);
|
|
379
|
+
if (source.place === target.place)
|
|
380
|
+
return;
|
|
381
|
+
const label = labelOfComposite(group);
|
|
382
|
+
if (!label || label.isLocked())
|
|
383
|
+
return;
|
|
384
|
+
const [x, y, w, h] = label.deserializedXYWH;
|
|
385
|
+
const sourceAnchorX = source.x + anchorOffset(WARDLEY_LABEL_W, source.textAlign);
|
|
386
|
+
// A pixel of tolerance on each axis: a renamed label's line is measured from
|
|
387
|
+
// the font and lands within a hair of the canonical height this compares to.
|
|
388
|
+
if (Math.abs(x + anchorOffset(w, source.textAlign) - sourceAnchorX) > 1) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (Math.abs(y + h / 2 - (source.y + WARDLEY_LABEL_H / 2)) > 1)
|
|
392
|
+
return;
|
|
393
|
+
label.surface.updateElement(label.id, {
|
|
394
|
+
// The BOX keeps its own size — the words in it may be the author's, and one
|
|
395
|
+
// shrunk back to the canonical width would clip them.
|
|
396
|
+
xywh: new Bound(target.x +
|
|
397
|
+
anchorOffset(WARDLEY_LABEL_W, target.textAlign) -
|
|
398
|
+
anchorOffset(w, target.textAlign), target.y + WARDLEY_LABEL_H / 2 - h / 2, w, h).serialize(),
|
|
399
|
+
textAlign: target.textAlign,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
341
402
|
/**
|
|
342
403
|
* Everything the morph owes a Wardley composite beyond the props patch — the
|
|
343
404
|
* `afterMorph` half of the spec, run inside the generic module's one
|
|
@@ -353,7 +414,9 @@ function rewriteLabel(group, from, to, std) {
|
|
|
353
414
|
* dots crammed into it. So the target's canonical size is applied, CENTRED on
|
|
354
415
|
* where the artefact already stands: the thing does not move, it becomes the
|
|
355
416
|
* size its notation says it is. The label keeps its own place, which is the
|
|
356
|
-
* user's
|
|
417
|
+
* user's — with the one exception {@link relocateLabel} states: a name still
|
|
418
|
+
* standing where creation put it follows the target kind when that kind carries
|
|
419
|
+
* its name somewhere else, or the new drawing lands on top of the old words.
|
|
357
420
|
*
|
|
358
421
|
* ## And why the rest is here rather than in the patch
|
|
359
422
|
*
|
|
@@ -383,6 +446,7 @@ export function wardleyMorphComposite(selected, from, to, std) {
|
|
|
383
446
|
addMarketGlyph(selected, cx, cy);
|
|
384
447
|
if (to === 'pipeline')
|
|
385
448
|
addHandle(selected, carrier, cx, cy);
|
|
449
|
+
relocateLabel(selected, from, to, cx, cy);
|
|
386
450
|
rewriteLabel(selected, from, to, std);
|
|
387
451
|
}
|
|
388
452
|
/**
|
package/dist/node/consts.d.ts
CHANGED
|
@@ -57,29 +57,31 @@ export declare const LINK_STROKE_WIDTH = 2;
|
|
|
57
57
|
/**
|
|
58
58
|
* Wardley red ("future"/evolution) — matches the validated arrow icon.
|
|
59
59
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
60
|
+
* A STORED colour: every evolution arrow ever drawn carries this exact value,
|
|
61
|
+
* so it stays a literal and must never be derived from `NOTATION_NEUTRALS` or
|
|
62
|
+
* any other scale — a change of value would leave the maps already drawn with
|
|
63
|
+
* it a different red from the ones drawn after. (It is no longer how an arrow
|
|
64
|
+
* is RECOGNISED: the legend and the rules read `wardley:change-arrow`, so a
|
|
65
|
+
* connector somebody restyled red is not one.)
|
|
64
66
|
*/
|
|
65
67
|
export declare const WARDLEY_RED = "#d6455d";
|
|
66
68
|
/**
|
|
67
69
|
* Dependency link grey.
|
|
68
70
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* be derived from `NOTATION_NEUTRALS
|
|
72
|
-
*
|
|
71
|
+
* A STORED colour, like {@link WARDLEY_RED} above and for its reason: every
|
|
72
|
+
* dependency link ever drawn carries this exact value, so it stays a literal
|
|
73
|
+
* and must never be derived from `NOTATION_NEUTRALS`. What MAKES a connector a
|
|
74
|
+
* dependency is its role, not its grey.
|
|
73
75
|
*/
|
|
74
76
|
export declare const LINK_GREY = "#666666";
|
|
75
77
|
/**
|
|
76
78
|
* Inertia bar color + size.
|
|
77
79
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
80
|
+
* A STORED colour, like the two above: every inertia bar ever drawn carries
|
|
81
|
+
* this exact value. It happens to equal the scale's ink today, but it stays a
|
|
82
|
+
* literal and must never be derived from `NOTATION_NEUTRALS` — a change of the
|
|
83
|
+
* shared ink would repaint the bars already on a map. A rect somebody tinted
|
|
84
|
+
* `#1f2328` is NOT an inertia: `wardley:inertia` is.
|
|
83
85
|
*/
|
|
84
86
|
export declare const INERTIA_COLOR = "#1f2328";
|
|
85
87
|
export declare const INERTIA_SIZE: {
|
package/dist/node/consts.js
CHANGED
|
@@ -55,29 +55,31 @@ export const LINK_STROKE_WIDTH = 2;
|
|
|
55
55
|
/**
|
|
56
56
|
* Wardley red ("future"/evolution) — matches the validated arrow icon.
|
|
57
57
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
58
|
+
* A STORED colour: every evolution arrow ever drawn carries this exact value,
|
|
59
|
+
* so it stays a literal and must never be derived from `NOTATION_NEUTRALS` or
|
|
60
|
+
* any other scale — a change of value would leave the maps already drawn with
|
|
61
|
+
* it a different red from the ones drawn after. (It is no longer how an arrow
|
|
62
|
+
* is RECOGNISED: the legend and the rules read `wardley:change-arrow`, so a
|
|
63
|
+
* connector somebody restyled red is not one.)
|
|
62
64
|
*/
|
|
63
65
|
export const WARDLEY_RED = '#d6455d';
|
|
64
66
|
/**
|
|
65
67
|
* Dependency link grey.
|
|
66
68
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* be derived from `NOTATION_NEUTRALS
|
|
70
|
-
*
|
|
69
|
+
* A STORED colour, like {@link WARDLEY_RED} above and for its reason: every
|
|
70
|
+
* dependency link ever drawn carries this exact value, so it stays a literal
|
|
71
|
+
* and must never be derived from `NOTATION_NEUTRALS`. What MAKES a connector a
|
|
72
|
+
* dependency is its role, not its grey.
|
|
71
73
|
*/
|
|
72
74
|
export const LINK_GREY = '#666666';
|
|
73
75
|
/**
|
|
74
76
|
* Inertia bar color + size.
|
|
75
77
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
78
|
+
* A STORED colour, like the two above: every inertia bar ever drawn carries
|
|
79
|
+
* this exact value. It happens to equal the scale's ink today, but it stays a
|
|
80
|
+
* literal and must never be derived from `NOTATION_NEUTRALS` — a change of the
|
|
81
|
+
* shared ink would repaint the bars already on a map. A rect somebody tinted
|
|
82
|
+
* `#1f2328` is NOT an inertia: `wardley:inertia` is.
|
|
81
83
|
*/
|
|
82
84
|
export const INERTIA_COLOR = '#1f2328';
|
|
83
85
|
export const INERTIA_SIZE = { w: 8, h: 44 };
|
|
@@ -30,13 +30,24 @@ export const wardleyNode = (model, ctx, matrix, renderer, rc, bound) => {
|
|
|
30
30
|
return;
|
|
31
31
|
const strokeWidth = model.strokeWidth || 1;
|
|
32
32
|
const R = Math.min(w, h) / 2 - strokeWidth / 2;
|
|
33
|
+
// A node dragged smaller than its own stroke leaves the glyph no room: every
|
|
34
|
+
// radius below is a fraction of `R`, and `arc` throws `IndexSizeError` on a
|
|
35
|
+
// negative one rather than clamping — which, since the surface render loop
|
|
36
|
+
// wraps no renderer in a `try`, aborts the rest of the frame. The hatch step
|
|
37
|
+
// is a fraction of `R` too, so `R === 0` would also spin its loop forever.
|
|
38
|
+
// The native circle underneath is already painted; the glyph simply stops.
|
|
39
|
+
if (!(R > 0))
|
|
40
|
+
return;
|
|
33
41
|
const color = renderer.getColorValue(model.strokeColor, DefaultTheme.shapeStrokeColor, true);
|
|
34
42
|
ctx.setTransform(glyphMatrix);
|
|
35
43
|
// ── Anchor: person glyph (clipped to the circle) ────────────────────
|
|
36
44
|
if (model.kind === 'anchor') {
|
|
37
45
|
ctx.save();
|
|
38
46
|
ctx.beginPath();
|
|
39
|
-
|
|
47
|
+
// The clip is inset by half a stroke again, which can land under zero on a
|
|
48
|
+
// node barely wider than its border: clipped to nothing, the glyph paints
|
|
49
|
+
// nothing, which is the honest picture at that size.
|
|
50
|
+
ctx.arc(cx, cy, Math.max(0, R - strokeWidth / 2), 0, Math.PI * 2);
|
|
40
51
|
ctx.clip();
|
|
41
52
|
ctx.lineWidth = strokeWidth;
|
|
42
53
|
ctx.strokeStyle = color;
|
package/dist/presets.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FontWeight, type WardleyNodeKind } from '@formicoidea/labre-core/model';
|
|
1
|
+
import { FontWeight, PointStyle, StrokeStyle, type WardleyNodeKind } from '@formicoidea/labre-core/model';
|
|
2
2
|
/**
|
|
3
3
|
* What a Wardley artefact IS, as props — the ONE description the creation sites
|
|
4
4
|
* and the morph both read.
|
|
@@ -129,6 +129,29 @@ export declare const WARDLEY_LABEL_H: number;
|
|
|
129
129
|
* the words end on a given edge is to start the box a full width before it.
|
|
130
130
|
*/
|
|
131
131
|
export declare const WARDLEY_LABEL_W = 120;
|
|
132
|
+
/**
|
|
133
|
+
* Where a kind carries its NAME, and how the words sit in the box.
|
|
134
|
+
*
|
|
135
|
+
* The one description of a placement the four creation sites and the morph all
|
|
136
|
+
* read. Restated at each site it had already started to drift — and a morph
|
|
137
|
+
* that rewrites the BOX of an artefact without knowing where that kind holds
|
|
138
|
+
* its name leaves the words under the new drawing, which is W3's overlap and a
|
|
139
|
+
* label the pointer can no longer reach.
|
|
140
|
+
*
|
|
141
|
+
* `place` is the coarse half of the answer, and the only one a morph acts on:
|
|
142
|
+
* the three circles and the two arrows all wear their name BESIDE them, so an
|
|
143
|
+
* artefact that grows from one to the next leaves the words exactly where they
|
|
144
|
+
* are; the pipeline is the one kind that holds it ABOVE, and crossing between
|
|
145
|
+
* the two is what has to move something.
|
|
146
|
+
*/
|
|
147
|
+
export type WardleyLabelPlacement = {
|
|
148
|
+
x: number;
|
|
149
|
+
y: number;
|
|
150
|
+
textAlign: 'left' | 'center' | 'right';
|
|
151
|
+
place: 'beside' | 'above';
|
|
152
|
+
};
|
|
153
|
+
/** Where a node of one kind, centred on (cx, cy), carries its label. */
|
|
154
|
+
export declare function wardleyLabelBoxFor(kind: WardleyLabelledKind, cx: number, cy: number): WardleyLabelPlacement;
|
|
132
155
|
/**
|
|
133
156
|
* The NAME beside an artefact, as props — a native free-text element.
|
|
134
157
|
*
|
|
@@ -228,6 +251,32 @@ export declare function wardleyMarketLinkProps(sourceId: string, targetId: strin
|
|
|
228
251
|
};
|
|
229
252
|
/** The pairs the triangle wires, given the three dots in creation order. */
|
|
230
253
|
export declare function wardleyMarketLinkPairs<T>(dots: readonly T[]): [T, T][];
|
|
254
|
+
/**
|
|
255
|
+
* How a Wardley connector LOOKS, per tool: the value-chain link is grey, solid
|
|
256
|
+
* and headless, the evolution arrow red, dashed and tipped with a filled
|
|
257
|
+
* triangle.
|
|
258
|
+
*
|
|
259
|
+
* Here beside the node presets, and for their reason: the style rides on the
|
|
260
|
+
* tool activation (`activateWardleyConnector`) and is pictured by the legend
|
|
261
|
+
* row that tool subscribes, so restating it in either place would be two
|
|
262
|
+
* descriptions of one notation that agreed the day they were written.
|
|
263
|
+
*/
|
|
264
|
+
export declare const WARDLEY_EDGE_STYLE: {
|
|
265
|
+
readonly link: {
|
|
266
|
+
readonly stroke: "#666666";
|
|
267
|
+
readonly strokeStyle: StrokeStyle.Solid;
|
|
268
|
+
readonly strokeWidth: 2;
|
|
269
|
+
readonly frontEndpointStyle: PointStyle.None;
|
|
270
|
+
readonly rearEndpointStyle: PointStyle.None;
|
|
271
|
+
};
|
|
272
|
+
readonly arrow: {
|
|
273
|
+
readonly stroke: "#d6455d";
|
|
274
|
+
readonly strokeStyle: StrokeStyle.Dash;
|
|
275
|
+
readonly strokeWidth: 2;
|
|
276
|
+
readonly frontEndpointStyle: PointStyle.None;
|
|
277
|
+
readonly rearEndpointStyle: PointStyle.Triangle;
|
|
278
|
+
};
|
|
279
|
+
};
|
|
231
280
|
/** One of the four arrows: its own axis-aligned box, and the outline in it. */
|
|
232
281
|
export interface WardleyPorterArrow {
|
|
233
282
|
/** Serialized `xywh` of the box the polygon is drawn inside. */
|
package/dist/presets.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ConnectorMode, DEFAULT_POLYGON_VERTICES, FontFamily, FontWeight, PointStyle, ShapeStyle, StrokeStyle, TextFitMode, } from '@formicoidea/labre-core/model';
|
|
2
2
|
import { Bound } from '@formicoidea/labre-core/global/gfx';
|
|
3
|
-
import { ACCELERATOR_FILL, ACCELERATOR_LABEL, ACCELERATOR_SIZE, ACCELERATOR_STROKE_WIDTH, ACCELERATOR_VERTICES, AREA_FILL, AREA_POLYGON_SIZE, AREA_RECT_SIZE, AREA_STROKE, AREA_STROKE_WIDTH, DECELERATOR_LABEL, DECELERATOR_VERTICES, ECOSYSTEM_LABEL, ECOSYSTEM_SIZE, HANDLE_SIZE, INERTIA_COLOR, LABEL_DEFAULT, LABEL_FONT_SIZE, MARKET_DOT_RING, MARKET_DOT_SIZE, MARKET_DOT_STROKE_WIDTH, MARKET_LABEL, MARKET_LINK_COLOR, MARKET_LINK_WIDTH, MARKET_SIZE, METHOD_FILL, METHOD_LABEL, METHOD_SIZE, NODE_FILL, NODE_SIZE, NODE_STROKE, NODE_STROKE_WIDTH, PIPELINE_FILL, PIPELINE_HEIGHT, PIPELINE_LABEL, PIPELINE_WIDTH, PORTER_ARROW, PORTER_LETTER_FONT_SIZE, PORTER_SIZE, WARDLEY_RED, } from './node/consts.js';
|
|
3
|
+
import { ACCELERATOR_FILL, ACCELERATOR_LABEL, ACCELERATOR_SIZE, ACCELERATOR_STROKE_WIDTH, ACCELERATOR_VERTICES, AREA_FILL, AREA_POLYGON_SIZE, AREA_RECT_SIZE, AREA_STROKE, AREA_STROKE_WIDTH, DECELERATOR_LABEL, DECELERATOR_VERTICES, ECOSYSTEM_LABEL, ECOSYSTEM_SIZE, HANDLE_SIZE, INERTIA_COLOR, LABEL_DEFAULT, LABEL_FONT_SIZE, LABEL_GAP, LINK_GREY, LINK_STROKE_WIDTH, MARKET_DOT_RING, MARKET_DOT_SIZE, MARKET_DOT_STROKE_WIDTH, MARKET_LABEL, MARKET_LINK_COLOR, MARKET_LINK_WIDTH, MARKET_SIZE, METHOD_FILL, METHOD_LABEL, METHOD_SIZE, NODE_FILL, NODE_SIZE, NODE_STROKE, NODE_STROKE_WIDTH, PIPELINE_FILL, PIPELINE_HEIGHT, PIPELINE_LABEL, PIPELINE_WIDTH, PORTER_ARROW, PORTER_LETTER_FONT_SIZE, PORTER_SIZE, WARDLEY_RED, } from './node/consts.js';
|
|
4
4
|
import { WARDLEY_ROLE } from './roles.js';
|
|
5
5
|
/**
|
|
6
6
|
* The size a kind is BORN at, which for Wardley is the notation rather than a
|
|
@@ -198,6 +198,40 @@ export const WARDLEY_LABEL_H = LABEL_FONT_SIZE + 8;
|
|
|
198
198
|
* the words end on a given edge is to start the box a full width before it.
|
|
199
199
|
*/
|
|
200
200
|
export const WARDLEY_LABEL_W = 120;
|
|
201
|
+
/** Where a node of one kind, centred on (cx, cy), carries its label. */
|
|
202
|
+
export function wardleyLabelBoxFor(kind, cx, cy) {
|
|
203
|
+
const { w } = WARDLEY_NODE_SIZE[kind];
|
|
204
|
+
// A pipeline is a 120-wide bar, and a name written to the right of it would
|
|
205
|
+
// stand a bar's length away from the thing it names. So the words go above
|
|
206
|
+
// the handle, centred on the body.
|
|
207
|
+
if (kind === 'pipeline') {
|
|
208
|
+
const top = cy - WARDLEY_NODE_SIZE.pipeline.h / 2;
|
|
209
|
+
return {
|
|
210
|
+
x: cx - WARDLEY_LABEL_W / 2,
|
|
211
|
+
y: top - HANDLE_SIZE / 2 - WARDLEY_LABEL_H - LABEL_GAP,
|
|
212
|
+
textAlign: 'center',
|
|
213
|
+
place: 'above',
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
// A decelerator points LEFT, and the reference puts the words on the side the
|
|
217
|
+
// arrow's shaft is on so the reading runs into the arrow rather than across
|
|
218
|
+
// it. A label box is a fixed width whatever it reads, so a right-aligned one
|
|
219
|
+
// has to start a box-width before the edge its words must end on.
|
|
220
|
+
if (kind === 'decelerator') {
|
|
221
|
+
return {
|
|
222
|
+
x: cx - w / 2 - LABEL_GAP - WARDLEY_LABEL_W,
|
|
223
|
+
y: cy - WARDLEY_LABEL_H / 2,
|
|
224
|
+
textAlign: 'right',
|
|
225
|
+
place: 'beside',
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
x: cx + w / 2 + LABEL_GAP,
|
|
230
|
+
y: cy - WARDLEY_LABEL_H / 2,
|
|
231
|
+
textAlign: 'left',
|
|
232
|
+
place: 'beside',
|
|
233
|
+
};
|
|
234
|
+
}
|
|
201
235
|
/**
|
|
202
236
|
* The NAME beside an artefact, as props — a native free-text element.
|
|
203
237
|
*
|
|
@@ -402,6 +436,33 @@ export function wardleyMarketLinkPairs(dots) {
|
|
|
402
436
|
[dots[2], dots[0]],
|
|
403
437
|
];
|
|
404
438
|
}
|
|
439
|
+
/* ── The two typed edges ──────────────────────────────────────────────── */
|
|
440
|
+
/**
|
|
441
|
+
* How a Wardley connector LOOKS, per tool: the value-chain link is grey, solid
|
|
442
|
+
* and headless, the evolution arrow red, dashed and tipped with a filled
|
|
443
|
+
* triangle.
|
|
444
|
+
*
|
|
445
|
+
* Here beside the node presets, and for their reason: the style rides on the
|
|
446
|
+
* tool activation (`activateWardleyConnector`) and is pictured by the legend
|
|
447
|
+
* row that tool subscribes, so restating it in either place would be two
|
|
448
|
+
* descriptions of one notation that agreed the day they were written.
|
|
449
|
+
*/
|
|
450
|
+
export const WARDLEY_EDGE_STYLE = {
|
|
451
|
+
link: {
|
|
452
|
+
stroke: LINK_GREY,
|
|
453
|
+
strokeStyle: StrokeStyle.Solid,
|
|
454
|
+
strokeWidth: LINK_STROKE_WIDTH,
|
|
455
|
+
frontEndpointStyle: PointStyle.None,
|
|
456
|
+
rearEndpointStyle: PointStyle.None,
|
|
457
|
+
},
|
|
458
|
+
arrow: {
|
|
459
|
+
stroke: WARDLEY_RED,
|
|
460
|
+
strokeStyle: StrokeStyle.Dash,
|
|
461
|
+
strokeWidth: LINK_STROKE_WIDTH,
|
|
462
|
+
frontEndpointStyle: PointStyle.None,
|
|
463
|
+
rearEndpointStyle: PointStyle.Triangle,
|
|
464
|
+
},
|
|
465
|
+
};
|
|
405
466
|
/**
|
|
406
467
|
* The four arrow outlines, north first and then clockwise.
|
|
407
468
|
*
|
package/dist/roles.js
CHANGED
|
@@ -148,12 +148,21 @@ const DEFS = [
|
|
|
148
148
|
verbFallback: 'needs',
|
|
149
149
|
gestureHintKey: 'com.labre.wardley.role.dependency.gesture',
|
|
150
150
|
gestureHintFallback: 'Drag from the component that has the need to what it needs.',
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
|
|
151
|
+
// No `chipColor`. This role used to declare one (PR #203), so that the
|
|
152
|
+
// relation carrying the value chain would be unmistakable — and the
|
|
153
|
+
// result, on one map, was two chips in two different blues: this one in
|
|
154
|
+
// the colour it asked for, `wardley:change-arrow` in the mechanism's
|
|
155
|
+
// default. Neither had been compared against the other, because the pin
|
|
156
|
+
// that watched this field only ever looked at Wardley.
|
|
157
|
+
//
|
|
158
|
+
// The chip is chrome — the tool talking about the drawing, not part of
|
|
159
|
+
// it — so it follows the theme's accent token like every other
|
|
160
|
+
// affordance, and both relations of a map now wear the same blue
|
|
161
|
+
// (`EDGE_DIRECTION_COLOR`, `gfx/connector`). `chipColor` stays on the
|
|
162
|
+
// role contract as the extension point it was meant to be: a framework
|
|
163
|
+
// MAY claim its own, and the cross-framework guard in
|
|
164
|
+
// `affine/all/src/__tests__/direction-chip-accent.unit.spec.ts` is what
|
|
165
|
+
// keeps a second accent from creeping back in unnoticed.
|
|
157
166
|
},
|
|
158
167
|
},
|
|
159
168
|
// The map itself: the axes frame the other roles are positioned against.
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type TemplateCategory } from '@formicoidea/labre-core/gfx/template';
|
|
2
2
|
import type { ChromeWording } from '@formicoidea/labre-core/shared/services';
|
|
3
3
|
/**
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
* activate a tool rather than draw anything (see `tpl` below
|
|
7
|
-
* docstring).
|
|
4
|
+
* The three hand-drawn palette swatches' own tile names (`Template.nameKey`):
|
|
5
|
+
* none derives from a command, since `linkTool` / `evolutionArrow` /
|
|
6
|
+
* `addAreaPolygon` activate a tool rather than draw anything (see `tpl` below
|
|
7
|
+
* and its docstring).
|
|
8
8
|
*/
|
|
9
9
|
export declare const WARDLEY_TEMPLATE_NAME_LINK: ChromeWording;
|
|
10
10
|
export declare const WARDLEY_TEMPLATE_NAME_EVOLUTION_ARROW: ChromeWording;
|
|
11
|
+
export declare const WARDLEY_TEMPLATE_NAME_AREA_POLYGON: ChromeWording;
|
|
11
12
|
export declare const wardleyTemplateCategory: TemplateCategory;
|
package/dist/templates/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { lowerWardleyArea } from '../actions.js';
|
|
|
5
5
|
import { wardleyCommands } from '../commands.js';
|
|
6
6
|
import { COLORS } from '../consts.js';
|
|
7
7
|
import { INERTIA_COLOR, LINK_GREY, LINK_STROKE_WIDTH, NODE_FILL, NODE_STROKE, WARDLEY_RED, } from '../node/consts.js';
|
|
8
|
+
import { WARDLEY_AREA_SIZE, wardleyAreaProps } from '../presets.js';
|
|
8
9
|
import { WARDLEY_ROLE } from '../roles.js';
|
|
9
10
|
import { wardleyMaps } from './maps.js';
|
|
10
11
|
/**
|
|
@@ -74,10 +75,10 @@ const arrowPreview = (rightwards) => `<svg ${ATTRS} fill="none"><path d="${right
|
|
|
74
75
|
/** The zone, in its Peace wash — the pair differs on the outline and on nothing else. */
|
|
75
76
|
const areaPreview = (outline) => `<svg ${ATTRS} fill="none">${outline}</svg>`;
|
|
76
77
|
/**
|
|
77
|
-
* The
|
|
78
|
-
*
|
|
79
|
-
* activate a tool rather than draw anything (see `tpl` below
|
|
80
|
-
* docstring).
|
|
78
|
+
* The three hand-drawn palette swatches' own tile names (`Template.nameKey`):
|
|
79
|
+
* none derives from a command, since `linkTool` / `evolutionArrow` /
|
|
80
|
+
* `addAreaPolygon` activate a tool rather than draw anything (see `tpl` below
|
|
81
|
+
* and its docstring).
|
|
81
82
|
*/
|
|
82
83
|
export const WARDLEY_TEMPLATE_NAME_LINK = [
|
|
83
84
|
'com.labre.wardley.template.link',
|
|
@@ -87,6 +88,10 @@ export const WARDLEY_TEMPLATE_NAME_EVOLUTION_ARROW = [
|
|
|
87
88
|
'com.labre.wardley.template.evolution-arrow',
|
|
88
89
|
'Evolution arrow',
|
|
89
90
|
];
|
|
91
|
+
export const WARDLEY_TEMPLATE_NAME_AREA_POLYGON = [
|
|
92
|
+
'com.labre.wardley.template.area-polygon',
|
|
93
|
+
'Area (polygon)',
|
|
94
|
+
];
|
|
90
95
|
/** A hand-composed template — what is left once the artefacts are derived. */
|
|
91
96
|
function tpl(name, preview, elements, nameKey) {
|
|
92
97
|
return {
|
|
@@ -98,7 +103,7 @@ function tpl(name, preview, elements, nameKey) {
|
|
|
98
103
|
};
|
|
99
104
|
}
|
|
100
105
|
/**
|
|
101
|
-
* A zone, plus the one thing a snapshot cannot say.
|
|
106
|
+
* A zone card, plus the one thing a snapshot cannot say.
|
|
102
107
|
*
|
|
103
108
|
* `createWardleyArea` lowers the zone the moment it exists, to just above the
|
|
104
109
|
* framework backgrounds it covers — otherwise the wash sits on top of every
|
|
@@ -106,9 +111,9 @@ function tpl(name, preview, elements, nameKey) {
|
|
|
106
111
|
* ALREADY ON THE BOARD, which a snapshot knows nothing about, so the panel
|
|
107
112
|
* replays it on the freshly inserted element instead.
|
|
108
113
|
*/
|
|
109
|
-
function areaTemplate(
|
|
114
|
+
function areaTemplate(template) {
|
|
110
115
|
return {
|
|
111
|
-
...
|
|
116
|
+
...template,
|
|
112
117
|
afterInsert: (std, insertedIds) => lowerWardleyArea(std.get(GfxControllerIdentifier), insertedIds[0]),
|
|
113
118
|
};
|
|
114
119
|
}
|
|
@@ -147,7 +152,17 @@ export const wardleyTemplateCategory = {
|
|
|
147
152
|
tpl('Evolution arrow', `<svg ${ATTRS} fill="none"><path d="M24 40 H100" stroke="${WARDLEY_RED}" stroke-width="2.4" stroke-dasharray="6 4"/><path d="M98 33 L112 40 L98 47 Z" fill="${WARDLEY_RED}"/></svg>`, {
|
|
148
153
|
a: connect({ position: [0, 0] }, { position: [160, 0] }, { evolution: true, typed: false }),
|
|
149
154
|
}, WARDLEY_TEMPLATE_NAME_EVOLUTION_ARROW[0]),
|
|
150
|
-
areaTemplate('wardley.addAreaRect', areaPreview('<rect x="24" y="16" width="87" height="48" rx="2" fill="#c6dbfc" fill-opacity="0.25" stroke="#5b9cf6" stroke-width="1.5"/>')),
|
|
151
|
-
|
|
155
|
+
areaTemplate(templateFromCommand(byId('wardley.addAreaRect'), areaPreview('<rect x="24" y="16" width="87" height="48" rx="2" fill="#c6dbfc" fill-opacity="0.25" stroke="#5b9cf6" stroke-width="1.5"/>'))),
|
|
156
|
+
// Hand-composed, like the two connector swatches and for the same reason:
|
|
157
|
+
// `wardley.addAreaPolygon` arms the polygon tool (#343) and draws nothing,
|
|
158
|
+
// so there is no action to record. A panel card cannot arm a tool — it
|
|
159
|
+
// inserts a snapshot — so the card keeps offering the zone ready-made, and
|
|
160
|
+
// the author who wants to place the corners uses the sub-menu. The props
|
|
161
|
+
// still come from the pack, so the two zones cannot disagree.
|
|
162
|
+
areaTemplate(tpl(WARDLEY_TEMPLATE_NAME_AREA_POLYGON[1], areaPreview('<path d="M67 12 L110 43 L94 68 H40 L24 43 Z" fill="#c6dbfc" fill-opacity="0.25" stroke="#5b9cf6" stroke-width="1.5" stroke-linejoin="round"/>'), {
|
|
163
|
+
area: wardleyAreaProps('polygon', {
|
|
164
|
+
xywh: `[0,0,${WARDLEY_AREA_SIZE.polygon.w},${WARDLEY_AREA_SIZE.polygon.h}]`,
|
|
165
|
+
}),
|
|
166
|
+
}, WARDLEY_TEMPLATE_NAME_AREA_POLYGON[0])),
|
|
152
167
|
],
|
|
153
168
|
};
|
package/dist/templates/maps.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { type ChromeWording } from '@formicoidea/labre-core/shared/services';
|
|
|
4
4
|
* The seeds the two worked maps write — declared once so the builders below
|
|
5
5
|
* and the manifest (`../translations.ts`) read the very same fallback, never
|
|
6
6
|
* a restated literal. `com.labre.wardley.example.<map>.<slug>`, per
|
|
7
|
-
* `docs/adr/
|
|
7
|
+
* `docs/adr/0023`.
|
|
8
8
|
*/
|
|
9
9
|
export declare const TEA_SHOP_SEED: {
|
|
10
10
|
readonly title: {
|
package/dist/templates/maps.js
CHANGED
|
@@ -222,7 +222,7 @@ const mapPreview = (extra) => `<svg ${ATTRS} fill="none"><path d="M22 12 V64 H12
|
|
|
222
222
|
* The seeds the two worked maps write — declared once so the builders below
|
|
223
223
|
* and the manifest (`../translations.ts`) read the very same fallback, never
|
|
224
224
|
* a restated literal. `com.labre.wardley.example.<map>.<slug>`, per
|
|
225
|
-
* `docs/adr/
|
|
225
|
+
* `docs/adr/0023`.
|
|
226
226
|
*/
|
|
227
227
|
export const TEA_SHOP_SEED = {
|
|
228
228
|
title: {
|
|
@@ -310,7 +310,7 @@ function seedText(std, def) {
|
|
|
310
310
|
* `build` takes the OPTIONAL inserting editor: called with none, at module
|
|
311
311
|
* load, for the English `content`; called again with the real `std` as
|
|
312
312
|
* {@link Template.localize}, so the two builds read the very same layout and
|
|
313
|
-
* differ only in the words a seed resolves to (`docs/adr/
|
|
313
|
+
* differ only in the words a seed resolves to (`docs/adr/0023`).
|
|
314
314
|
*/
|
|
315
315
|
function tpl(name, preview, build, nameKey) {
|
|
316
316
|
return {
|
package/dist/toolbar/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ChromeWording, type ToolbarContext } from '@formicoidea/labre-core/shared/services';
|
|
1
|
+
import { type ChromeWording, type ToolbarContext, type ToolbarModuleConfig } from '@formicoidea/labre-core/shared/services';
|
|
2
2
|
import { type TemplateResult } from 'lit';
|
|
3
3
|
/** Every wording above, for `translations.ts`'s manifest contribution. */
|
|
4
4
|
export declare const WARDLEY_TOOLBAR_WORDINGS: readonly ChromeWording[];
|
|
@@ -73,12 +73,37 @@ export declare const wardleyToolbarConfig: {
|
|
|
73
73
|
active(ctx: ToolbarContext): boolean;
|
|
74
74
|
run(ctx: ToolbarContext): void;
|
|
75
75
|
}];
|
|
76
|
-
},
|
|
77
|
-
readonly id: "d.legend";
|
|
78
|
-
readonly tooltipWording: ChromeWording;
|
|
79
|
-
readonly icon: TemplateResult<2>;
|
|
80
|
-
readonly run: (ctx: ToolbarContext) => void;
|
|
81
|
-
}];
|
|
76
|
+
}, import("@formicoidea/labre-core/shared/services").ToolbarActionGenerator];
|
|
82
77
|
readonly when: (ctx: ToolbarContext) => boolean;
|
|
83
78
|
};
|
|
84
79
|
export declare const wardleyToolbarExtension: import("@formicoidea/labre-core/store").ExtensionType;
|
|
80
|
+
/**
|
|
81
|
+
* The map's flag-gated row, WHOLE: the Legend button and the Validation
|
|
82
|
+
* dropdown, in one module.
|
|
83
|
+
*
|
|
84
|
+
* ## Why they cannot be two modules
|
|
85
|
+
*
|
|
86
|
+
* `renderToolbar` merges exactly four slots per element — `<flavour>`,
|
|
87
|
+
* `custom:<flavour>` and the two `affine:surface:*` wildcards — and
|
|
88
|
+
* `ToolbarModuleExtension` binds by DI variant, so a second module claiming
|
|
89
|
+
* `custom:affine:surface:wardley` would throw
|
|
90
|
+
* `DuplicateServiceDefinitionError` before the editor finished setting up. Two
|
|
91
|
+
* slots, and Wardley has three things to put on a selected map: the axis and
|
|
92
|
+
* label toggles (always-on, `<flavour>`, above), the legend and the level of
|
|
93
|
+
* requirement. The precedent is C4's `c4BoardToolingToolbarConfig`.
|
|
94
|
+
*
|
|
95
|
+
* ## Why the legend moved OUT of the always-on half
|
|
96
|
+
*
|
|
97
|
+
* Because GENERATING one is tooling, and `docs/adr/0009` gates tooling: the
|
|
98
|
+
* flag off takes away the gesture that writes legend elements, exactly as it
|
|
99
|
+
* takes away the sub-menu that draws components. What the gesture already
|
|
100
|
+
* wrote is document CONTENT and keeps being painted — the box is plain shapes,
|
|
101
|
+
* text and `wardleyNode`s with no role, all drawn by the always-on renderers.
|
|
102
|
+
* The axis and label toggles stay always-on for the opposite reason: a stored
|
|
103
|
+
* map must keep its axes whatever the flag says.
|
|
104
|
+
*
|
|
105
|
+
* Sorting keeps the row readable across the merge — `a.` … `d.legend` from the
|
|
106
|
+
* two modules, then `z.validation` — whatever order they were registered in.
|
|
107
|
+
*/
|
|
108
|
+
export declare const wardleyBoardToolingToolbarConfig: ToolbarModuleConfig;
|
|
109
|
+
export declare const wardleyBoardToolingToolbarExtension: import("@formicoidea/labre-core/store").ExtensionType;
|