@formicoidea/labre-framework-wardley 0.23.1 → 0.23.2
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/package.json +6 -15
- package/{dist/consts.js → src/consts.ts} +72 -63
- package/{dist/descriptor.js → src/descriptor.ts} +1 -1
- package/src/effects.ts +17 -0
- package/src/element-renderer.ts +242 -0
- package/src/element-view.ts +143 -0
- package/src/gradient.ts +137 -0
- package/src/index.ts +1 -0
- package/src/label-layout.ts +126 -0
- package/src/legend.ts +438 -0
- package/{dist/node/consts.js → src/node/consts.ts} +109 -101
- package/src/node/node-renderer.ts +142 -0
- package/{dist/node/node-view.d.ts → src/node/node-view.ts} +11 -11
- package/src/templates/index.ts +236 -0
- package/src/templates/maps.ts +283 -0
- package/src/toolbar/config.ts +280 -0
- package/{dist/toolbar/icons.js → src/toolbar/icons.ts} +66 -51
- package/{dist/toolbar/node-config.js → src/toolbar/node-config.ts} +28 -21
- package/{dist/toolbar/senior-tool.js → src/toolbar/senior-tool.ts} +11 -11
- package/src/toolbar/wardley-menu.ts +552 -0
- package/src/toolbar/wardley-senior-button.ts +154 -0
- package/src/view.ts +39 -0
- package/dist/consts.d.ts +0 -72
- package/dist/descriptor.d.ts +0 -7
- package/dist/effects.d.ts +0 -10
- package/dist/effects.js +0 -7
- package/dist/element-renderer.d.ts +0 -15
- package/dist/element-renderer.js +0 -160
- package/dist/element-view.d.ts +0 -21
- package/dist/element-view.js +0 -122
- package/dist/gradient.d.ts +0 -18
- package/dist/gradient.js +0 -112
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/dist/label-layout.d.ts +0 -21
- package/dist/label-layout.js +0 -73
- package/dist/legend.d.ts +0 -12
- package/dist/legend.js +0 -333
- package/dist/node/consts.d.ts +0 -107
- package/dist/node/label-editor.d.ts +0 -28
- package/dist/node/label-editor.js +0 -216
- package/dist/node/node-renderer.d.ts +0 -17
- package/dist/node/node-renderer.js +0 -106
- package/dist/node/node-view.js +0 -10
- package/dist/templates/index.d.ts +0 -3
- package/dist/templates/index.js +0 -172
- package/dist/templates/maps.d.ts +0 -3
- package/dist/templates/maps.js +0 -247
- package/dist/toolbar/config.d.ts +0 -75
- package/dist/toolbar/config.js +0 -206
- package/dist/toolbar/icons.d.ts +0 -31
- package/dist/toolbar/node-config.d.ts +0 -2
- package/dist/toolbar/senior-tool.d.ts +0 -2
- package/dist/toolbar/wardley-menu.d.ts +0 -53
- package/dist/toolbar/wardley-menu.js +0 -408
- package/dist/toolbar/wardley-senior-button.d.ts +0 -18
- package/dist/toolbar/wardley-senior-button.js +0 -146
- package/dist/toolbar/wardley-tool-button.d.ts +0 -10
- package/dist/toolbar/wardley-tool-button.js +0 -123
- package/dist/view.d.ts +0 -7
- package/dist/view.js +0 -36
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import {
|
|
2
|
+
makeTemplateSnapshot,
|
|
3
|
+
type SurfaceElementsJSON,
|
|
4
|
+
surfaceText,
|
|
5
|
+
type Template,
|
|
6
|
+
} from '@formicoidea/labre-core/gfx/template';
|
|
7
|
+
import {
|
|
8
|
+
ConnectorMode,
|
|
9
|
+
FontFamily,
|
|
10
|
+
PointStyle,
|
|
11
|
+
ShapeStyle,
|
|
12
|
+
StrokeStyle,
|
|
13
|
+
TextAlign,
|
|
14
|
+
} from '@formicoidea/labre-core/model';
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
INERTIA_COLOR,
|
|
18
|
+
LABEL_FONT_SIZE,
|
|
19
|
+
LINK_GREY,
|
|
20
|
+
LINK_STROKE_WIDTH,
|
|
21
|
+
NODE_FILL,
|
|
22
|
+
NODE_SIZE,
|
|
23
|
+
NODE_STROKE,
|
|
24
|
+
WARDLEY_RED,
|
|
25
|
+
} from '../node/consts';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Authoring kit for canonical Wardley maps. Positions are given as
|
|
29
|
+
* (evolution 0..1, value 0..1) and mapped into the plot interior of a fixed
|
|
30
|
+
* 1600x900 background. Per the composition principle, every glyph reuses an
|
|
31
|
+
* existing shape: stakeholders/users are anchor (person) nodes, "needs" are
|
|
32
|
+
* thick-stroked component nodes, capabilities are component nodes, notes are
|
|
33
|
+
* native rects + text, inertia is the inertia bar, and the future / evolution
|
|
34
|
+
* arrow is the red dashed connector. Legends are produced by the editor's
|
|
35
|
+
* auto-legend action rather than baked into the template.
|
|
36
|
+
*/
|
|
37
|
+
const W = 1600;
|
|
38
|
+
const H = 900;
|
|
39
|
+
const PL = { x: 70, y: 56, w: 1470, h: 786 };
|
|
40
|
+
const ex = (e: number) => PL.x + e * PL.w;
|
|
41
|
+
const vy = (v: number) => PL.y + (1 - v) * PL.h;
|
|
42
|
+
const D = NODE_SIZE; // 18
|
|
43
|
+
|
|
44
|
+
const bg = (variant = 'classic') => ({ type: 'wardley', variant, xywh: `[0,0,${W},${H}]` });
|
|
45
|
+
|
|
46
|
+
function dot(e: number, v: number, sw: number, stroke = NODE_STROKE, fill = NODE_FILL) {
|
|
47
|
+
const cx = ex(e);
|
|
48
|
+
const cy = vy(v);
|
|
49
|
+
return {
|
|
50
|
+
type: 'wardleyNode',
|
|
51
|
+
kind: 'component',
|
|
52
|
+
shapeType: 'ellipse',
|
|
53
|
+
filled: true,
|
|
54
|
+
fillColor: fill,
|
|
55
|
+
strokeColor: stroke,
|
|
56
|
+
strokeWidth: sw,
|
|
57
|
+
shapeStyle: ShapeStyle.General,
|
|
58
|
+
roughness: 0,
|
|
59
|
+
xywh: `[${cx - D / 2},${cy - D / 2},${D},${D}]`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const comp = (e: number, v: number) => dot(e, v, 1);
|
|
63
|
+
const future = (e: number, v: number) => dot(e, v, 2, WARDLEY_RED);
|
|
64
|
+
function stake(e: number, v: number) {
|
|
65
|
+
const cx = ex(e);
|
|
66
|
+
const cy = vy(v);
|
|
67
|
+
const d = 24;
|
|
68
|
+
return {
|
|
69
|
+
type: 'wardleyNode',
|
|
70
|
+
kind: 'anchor',
|
|
71
|
+
shapeType: 'ellipse',
|
|
72
|
+
filled: true,
|
|
73
|
+
fillColor: NODE_FILL,
|
|
74
|
+
strokeColor: NODE_STROKE,
|
|
75
|
+
strokeWidth: 1,
|
|
76
|
+
shapeStyle: ShapeStyle.General,
|
|
77
|
+
roughness: 0,
|
|
78
|
+
xywh: `[${cx - d / 2},${cy - d / 2},${d},${d}]`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type LblOpts = { dx?: number; dy?: number; align?: 'left' | 'right' | 'center'; color?: string; size?: number; w?: number };
|
|
83
|
+
function lbl(e: number, v: number, text: string, o: LblOpts = {}) {
|
|
84
|
+
const cx = ex(e);
|
|
85
|
+
const cy = vy(v);
|
|
86
|
+
const w = o.w ?? 200;
|
|
87
|
+
const dx = o.dx ?? 12;
|
|
88
|
+
const dy = o.dy ?? -10;
|
|
89
|
+
const align = o.align ?? 'left';
|
|
90
|
+
const x = align === 'right' ? cx - w - dx : align === 'center' ? cx - w / 2 : cx + dx;
|
|
91
|
+
return {
|
|
92
|
+
type: 'text',
|
|
93
|
+
text: surfaceText(text),
|
|
94
|
+
color: o.color ?? NODE_STROKE,
|
|
95
|
+
fontFamily: FontFamily.Inter,
|
|
96
|
+
fontSize: o.size ?? LABEL_FONT_SIZE,
|
|
97
|
+
textAlign: align === 'right' ? TextAlign.Right : align === 'center' ? TextAlign.Center : TextAlign.Left,
|
|
98
|
+
xywh: `[${x},${cy + dy},${w},26]`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function link(a: string, b: string, o: { red?: boolean; arrow?: boolean } = {}) {
|
|
103
|
+
return {
|
|
104
|
+
type: 'connector',
|
|
105
|
+
mode: ConnectorMode.Straight,
|
|
106
|
+
stroke: o.red ? WARDLEY_RED : LINK_GREY,
|
|
107
|
+
strokeStyle: o.arrow ? StrokeStyle.Dash : StrokeStyle.Solid,
|
|
108
|
+
strokeWidth: LINK_STROKE_WIDTH,
|
|
109
|
+
frontEndpointStyle: PointStyle.None,
|
|
110
|
+
rearEndpointStyle: o.arrow ? PointStyle.Triangle : PointStyle.None,
|
|
111
|
+
source: { id: a },
|
|
112
|
+
target: { id: b },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function inertia(e: number, v: number) {
|
|
117
|
+
const cx = ex(e);
|
|
118
|
+
const cy = vy(v);
|
|
119
|
+
return {
|
|
120
|
+
type: 'shape',
|
|
121
|
+
shapeType: 'rect',
|
|
122
|
+
filled: true,
|
|
123
|
+
fillColor: INERTIA_COLOR,
|
|
124
|
+
strokeColor: INERTIA_COLOR,
|
|
125
|
+
strokeWidth: 0,
|
|
126
|
+
shapeStyle: ShapeStyle.General,
|
|
127
|
+
roughness: 0,
|
|
128
|
+
radius: 0,
|
|
129
|
+
xywh: `[${cx - 4},${cy - 22},8,44]`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function panel(x: number, y: number, w: number, h: number) {
|
|
134
|
+
return {
|
|
135
|
+
type: 'shape',
|
|
136
|
+
shapeType: 'rect',
|
|
137
|
+
filled: true,
|
|
138
|
+
fillColor: '#ffffff',
|
|
139
|
+
strokeColor: NODE_STROKE,
|
|
140
|
+
strokeWidth: 1.2,
|
|
141
|
+
shapeStyle: ShapeStyle.General,
|
|
142
|
+
roughness: 0,
|
|
143
|
+
radius: 0,
|
|
144
|
+
xywh: `[${x},${y},${w},${h}]`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function freeText(x: number, y: number, w: number, str: string, size = 16, color = NODE_STROKE) {
|
|
148
|
+
return {
|
|
149
|
+
type: 'text',
|
|
150
|
+
text: surfaceText(str),
|
|
151
|
+
color,
|
|
152
|
+
fontFamily: FontFamily.Inter,
|
|
153
|
+
fontSize: size,
|
|
154
|
+
textAlign: TextAlign.Left,
|
|
155
|
+
xywh: `[${x},${y},${w},26]`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** Centred, enlarged map title spanning the plot width. */
|
|
159
|
+
function title(str: string) {
|
|
160
|
+
return {
|
|
161
|
+
type: 'text',
|
|
162
|
+
text: surfaceText(str),
|
|
163
|
+
color: NODE_STROKE,
|
|
164
|
+
fontFamily: FontFamily.Inter,
|
|
165
|
+
fontSize: 28,
|
|
166
|
+
textAlign: TextAlign.Center,
|
|
167
|
+
xywh: `[${W / 2 - 500},12,1000,40]`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const ATTRS = 'width="100%" height="100%" viewBox="0 0 135 80" xmlns="http://www.w3.org/2000/svg"';
|
|
172
|
+
const mapPreview = (extra: string) =>
|
|
173
|
+
`<svg ${ATTRS} fill="none"><path d="M22 12 V64 H120" stroke="#3b3d42" stroke-width="2"/>${extra}</svg>`;
|
|
174
|
+
|
|
175
|
+
function tpl(name: string, preview: string, elements: SurfaceElementsJSON): Template {
|
|
176
|
+
return { name, type: 'template', preview, content: makeTemplateSnapshot(elements, name) };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function ann(e: number, v: number) {
|
|
180
|
+
const cx = ex(e);
|
|
181
|
+
const cy = vy(v);
|
|
182
|
+
return {
|
|
183
|
+
type: 'shape',
|
|
184
|
+
shapeType: 'ellipse',
|
|
185
|
+
filled: true,
|
|
186
|
+
fillColor: '#ffffff',
|
|
187
|
+
strokeColor: NODE_STROKE,
|
|
188
|
+
strokeWidth: 1.5,
|
|
189
|
+
shapeStyle: ShapeStyle.General,
|
|
190
|
+
roughness: 0,
|
|
191
|
+
xywh: `[${cx - 14},${cy - 14},28,28]`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function annTxt(e: number, v: number, n: string) {
|
|
195
|
+
const cx = ex(e);
|
|
196
|
+
const cy = vy(v);
|
|
197
|
+
return {
|
|
198
|
+
type: 'text',
|
|
199
|
+
text: surfaceText(n),
|
|
200
|
+
color: NODE_STROKE,
|
|
201
|
+
fontFamily: FontFamily.Inter,
|
|
202
|
+
fontSize: 14,
|
|
203
|
+
textAlign: TextAlign.Center,
|
|
204
|
+
xywh: `[${cx - 14},${cy - 9},28,20]`,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ── Tea Shop (the canonical map) ──────────────────────────────────────
|
|
209
|
+
function teaShop(): SurfaceElementsJSON {
|
|
210
|
+
return {
|
|
211
|
+
bg: bg(),
|
|
212
|
+
title: title('Tea Shop'),
|
|
213
|
+
annBox: panel(120, 200, 420, 64),
|
|
214
|
+
annText: freeText(132, 208, 400, 'Annotations:\n1. Standardising power lets kettles evolve faster\n2. Hot water is obvious and well known', 13),
|
|
215
|
+
business: stake(0.62, 0.93),
|
|
216
|
+
businessL: lbl(0.62, 0.93, 'Business', { align: 'center', dy: -28, w: 120 }),
|
|
217
|
+
public: stake(0.78, 0.93),
|
|
218
|
+
publicL: lbl(0.78, 0.93, 'Public', { align: 'center', dy: -28, w: 120 }),
|
|
219
|
+
cupOfTea: comp(0.62, 0.74),
|
|
220
|
+
cupOfTeaL: lbl(0.62, 0.74, 'Cup of Tea', { align: 'right' }),
|
|
221
|
+
cup: comp(0.8, 0.7),
|
|
222
|
+
cupL: lbl(0.8, 0.7, 'Cup'),
|
|
223
|
+
tea: comp(0.83, 0.6),
|
|
224
|
+
teaL: lbl(0.83, 0.6, 'Tea'),
|
|
225
|
+
hotWater: comp(0.8, 0.47),
|
|
226
|
+
hotWaterL: lbl(0.8, 0.47, 'Hot Water'),
|
|
227
|
+
water: comp(0.81, 0.34),
|
|
228
|
+
waterL: lbl(0.81, 0.34, 'Water'),
|
|
229
|
+
kettle: comp(0.36, 0.38),
|
|
230
|
+
kettleL: lbl(0.36, 0.38, 'Kettle', { align: 'right', dy: 6 }),
|
|
231
|
+
electric: future(0.56, 0.38),
|
|
232
|
+
electricL: lbl(0.56, 0.38, 'Electric Kettle'),
|
|
233
|
+
power: comp(0.7, 0.1),
|
|
234
|
+
powerL: lbl(0.7, 0.1, 'Power', { align: 'right', dy: 6 }),
|
|
235
|
+
powerFut: future(0.88, 0.1),
|
|
236
|
+
powerFutL: lbl(0.88, 0.1, 'Power'),
|
|
237
|
+
limitedBy: lbl(0.56, 0.43, 'limited by', { align: 'center', w: 120, size: 13 }),
|
|
238
|
+
ann1a: ann(0.5, 0.385), ann1t: annTxt(0.5, 0.385, '1'),
|
|
239
|
+
ann2a: ann(0.84, 0.45), ann2t: annTxt(0.84, 0.45, '2'),
|
|
240
|
+
l1: link('business', 'cupOfTea'),
|
|
241
|
+
l2: link('public', 'cupOfTea'),
|
|
242
|
+
l3: link('cupOfTea', 'cup'),
|
|
243
|
+
l4: link('cupOfTea', 'tea'),
|
|
244
|
+
l5: link('cupOfTea', 'hotWater'),
|
|
245
|
+
l6: link('hotWater', 'water'),
|
|
246
|
+
l7: link('hotWater', 'kettle'),
|
|
247
|
+
l8: link('kettle', 'power'),
|
|
248
|
+
a1: link('kettle', 'electric', { red: true, arrow: true }),
|
|
249
|
+
a2: link('power', 'powerFut', { red: true, arrow: true }),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ── Kodak inertia (2005) ──────────────────────────────────────────────
|
|
254
|
+
function kodak(): SurfaceElementsJSON {
|
|
255
|
+
return {
|
|
256
|
+
bg: bg(),
|
|
257
|
+
title: title("Wardley map of Kodak's 2005 inertia to digital"),
|
|
258
|
+
user: stake(0.54, 0.92),
|
|
259
|
+
userL: lbl(0.54, 0.92, 'User'),
|
|
260
|
+
capture: dot(0.53, 0.8, 3),
|
|
261
|
+
captureL: lbl(0.53, 0.8, 'Capture a moment'),
|
|
262
|
+
film: comp(0.52, 0.62),
|
|
263
|
+
filmL: lbl(0.52, 0.62, 'Film camera', { align: 'right' }),
|
|
264
|
+
digital: future(0.74, 0.62),
|
|
265
|
+
digitalL: lbl(0.74, 0.62, 'Digital camera', { color: WARDLEY_RED }),
|
|
266
|
+
roll: comp(0.52, 0.4),
|
|
267
|
+
rollL: lbl(0.52, 0.4, 'Photographic film', { align: 'right' }),
|
|
268
|
+
storage: future(0.84, 0.4),
|
|
269
|
+
storageL: lbl(0.84, 0.4, 'Digital storage', { color: WARDLEY_RED }),
|
|
270
|
+
inertiaBar: inertia(0.78, 0.4),
|
|
271
|
+
l1: link('user', 'capture'),
|
|
272
|
+
l2: link('capture', 'film'),
|
|
273
|
+
l3: link('film', 'roll'),
|
|
274
|
+
r1: link('capture', 'storage', { red: true }),
|
|
275
|
+
a1: link('film', 'digital', { red: true, arrow: true }),
|
|
276
|
+
a2: link('roll', 'storage', { red: true, arrow: true }),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export const wardleyMaps: Template[] = [
|
|
281
|
+
tpl('Tea Shop', mapPreview('<circle cx="78" cy="24" r="3" fill="#fff" stroke="#1f2328"/><circle cx="50" cy="44" r="3" fill="#fff" stroke="#1f2328"/><circle cx="86" cy="40" r="3" fill="#fff" stroke="#1f2328"/><circle cx="92" cy="58" r="3" fill="#fff" stroke="#1f2328"/><path d="M78 24 L50 44 M78 24 L86 40 L92 58" stroke="#666"/><path d="M50 44 h22" stroke="#d6455d" stroke-dasharray="3 2"/>'), teaShop()),
|
|
282
|
+
tpl('Kodak inertia', mapPreview('<circle cx="56" cy="22" r="3" fill="#fff" stroke="#1f2328"/><circle cx="54" cy="40" r="3" fill="#fff" stroke="#1f2328"/><circle cx="86" cy="40" r="3" fill="#fff" stroke="#d6455d"/><rect x="76" y="35" width="2.5" height="11" fill="#1f2328"/><path d="M57 40 h17" stroke="#d6455d" stroke-dasharray="3 2"/>'), kodak()),
|
|
283
|
+
];
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { EdgelessCRUDIdentifier } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { WardleyBackgroundElementModel } from '@formicoidea/labre-core/model';
|
|
3
|
+
import {
|
|
4
|
+
TelemetryProvider,
|
|
5
|
+
type ToolbarContext,
|
|
6
|
+
type ToolbarModuleConfig,
|
|
7
|
+
ToolbarModuleExtension,
|
|
8
|
+
} from '@formicoidea/labre-core/shared/services';
|
|
9
|
+
import { BlockFlavourIdentifier } from '@formicoidea/labre-core/std';
|
|
10
|
+
import { html, type TemplateResult } from 'lit';
|
|
11
|
+
|
|
12
|
+
import { createWardleyLegend } from '../legend';
|
|
13
|
+
import { wardleyLegendIcon } from './icons';
|
|
14
|
+
|
|
15
|
+
const ResizeIcon = html`<svg
|
|
16
|
+
width="24"
|
|
17
|
+
height="24"
|
|
18
|
+
viewBox="0 0 24 24"
|
|
19
|
+
fill="none"
|
|
20
|
+
stroke="currentColor"
|
|
21
|
+
stroke-width="1.6"
|
|
22
|
+
stroke-linecap="round"
|
|
23
|
+
stroke-linejoin="round"
|
|
24
|
+
>
|
|
25
|
+
<path d="M9 5H5v4M15 19h4v-4" />
|
|
26
|
+
<path d="M5 5l6 6M19 19l-6-6" />
|
|
27
|
+
</svg>`;
|
|
28
|
+
|
|
29
|
+
/** Gradient swatch — show/hide the variant gradient. */
|
|
30
|
+
const GradientIcon = html`<svg
|
|
31
|
+
width="24"
|
|
32
|
+
height="24"
|
|
33
|
+
viewBox="0 0 24 24"
|
|
34
|
+
fill="none"
|
|
35
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
36
|
+
>
|
|
37
|
+
<defs>
|
|
38
|
+
<linearGradient id="wardleyToolbarGrad" x1="0" y1="0" x2="1" y2="0">
|
|
39
|
+
<stop offset="0" stop-color="currentColor" stop-opacity="0.85" />
|
|
40
|
+
<stop offset="1" stop-color="currentColor" stop-opacity="0.1" />
|
|
41
|
+
</linearGradient>
|
|
42
|
+
</defs>
|
|
43
|
+
<rect
|
|
44
|
+
x="4"
|
|
45
|
+
y="5"
|
|
46
|
+
width="16"
|
|
47
|
+
height="14"
|
|
48
|
+
rx="2"
|
|
49
|
+
fill="url(#wardleyToolbarGrad)"
|
|
50
|
+
stroke="currentColor"
|
|
51
|
+
stroke-width="1.4"
|
|
52
|
+
/>
|
|
53
|
+
</svg>`;
|
|
54
|
+
|
|
55
|
+
/** Horizontal axis with a right-pointing arrow (Evolution / X). */
|
|
56
|
+
const XAxisIcon = html`<svg
|
|
57
|
+
width="24"
|
|
58
|
+
height="24"
|
|
59
|
+
viewBox="0 0 24 24"
|
|
60
|
+
fill="none"
|
|
61
|
+
stroke="currentColor"
|
|
62
|
+
stroke-width="1.6"
|
|
63
|
+
stroke-linecap="round"
|
|
64
|
+
stroke-linejoin="round"
|
|
65
|
+
>
|
|
66
|
+
<path d="M3 17h15" />
|
|
67
|
+
<path d="M15 14l3 3-3 3" />
|
|
68
|
+
</svg>`;
|
|
69
|
+
|
|
70
|
+
/** Vertical axis with an up-pointing arrow (Value Chain / Y). */
|
|
71
|
+
const YAxisIcon = html`<svg
|
|
72
|
+
width="24"
|
|
73
|
+
height="24"
|
|
74
|
+
viewBox="0 0 24 24"
|
|
75
|
+
fill="none"
|
|
76
|
+
stroke="currentColor"
|
|
77
|
+
stroke-width="1.6"
|
|
78
|
+
stroke-linecap="round"
|
|
79
|
+
stroke-linejoin="round"
|
|
80
|
+
>
|
|
81
|
+
<path d="M7 21V6" />
|
|
82
|
+
<path d="M4 9l3-3 3 3" />
|
|
83
|
+
</svg>`;
|
|
84
|
+
|
|
85
|
+
/** Dashed vertical column dividers. */
|
|
86
|
+
const ColumnsIcon = html`<svg
|
|
87
|
+
width="24"
|
|
88
|
+
height="24"
|
|
89
|
+
viewBox="0 0 24 24"
|
|
90
|
+
fill="none"
|
|
91
|
+
stroke="currentColor"
|
|
92
|
+
stroke-width="1.6"
|
|
93
|
+
stroke-linecap="round"
|
|
94
|
+
stroke-dasharray="3 3"
|
|
95
|
+
>
|
|
96
|
+
<path d="M9 4v16M15 4v16" />
|
|
97
|
+
</svg>`;
|
|
98
|
+
|
|
99
|
+
/** Column labels (short bars under the columns). */
|
|
100
|
+
const ColumnLabelsIcon = html`<svg
|
|
101
|
+
width="24"
|
|
102
|
+
height="24"
|
|
103
|
+
viewBox="0 0 24 24"
|
|
104
|
+
fill="currentColor"
|
|
105
|
+
stroke="none"
|
|
106
|
+
>
|
|
107
|
+
<rect x="3" y="15" width="4.5" height="3" rx="1" />
|
|
108
|
+
<rect x="9.75" y="15" width="4.5" height="3" rx="1" />
|
|
109
|
+
<rect x="16.5" y="15" width="4.5" height="3" rx="1" />
|
|
110
|
+
</svg>`;
|
|
111
|
+
|
|
112
|
+
/** Corner labels (Uncharted / Industrialized, top corners). */
|
|
113
|
+
const CornerLabelsIcon = html`<svg
|
|
114
|
+
width="24"
|
|
115
|
+
height="24"
|
|
116
|
+
viewBox="0 0 24 24"
|
|
117
|
+
fill="currentColor"
|
|
118
|
+
stroke="none"
|
|
119
|
+
>
|
|
120
|
+
<rect x="3" y="5" width="6" height="3" rx="1" />
|
|
121
|
+
<rect x="15" y="5" width="6" height="3" rx="1" />
|
|
122
|
+
</svg>`;
|
|
123
|
+
|
|
124
|
+
/** Visibility labels (Visible / Invisible) — an eye. */
|
|
125
|
+
const VisibilityIcon = html`<svg
|
|
126
|
+
width="24"
|
|
127
|
+
height="24"
|
|
128
|
+
viewBox="0 0 24 24"
|
|
129
|
+
fill="none"
|
|
130
|
+
stroke="currentColor"
|
|
131
|
+
stroke-width="1.6"
|
|
132
|
+
stroke-linecap="round"
|
|
133
|
+
stroke-linejoin="round"
|
|
134
|
+
>
|
|
135
|
+
<path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12z" />
|
|
136
|
+
<circle cx="12" cy="12" r="2.4" />
|
|
137
|
+
</svg>`;
|
|
138
|
+
|
|
139
|
+
type WardleyToggleProp =
|
|
140
|
+
| 'resizeEnabled'
|
|
141
|
+
| 'showGradient'
|
|
142
|
+
| 'showXAxis'
|
|
143
|
+
| 'showYAxis'
|
|
144
|
+
| 'showColumnDividers'
|
|
145
|
+
| 'showColumnLabels'
|
|
146
|
+
| 'showCornerLabels'
|
|
147
|
+
| 'showVisibilityLabels';
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Build a toolbar toggle that flips a boolean flag on every selected Wardley
|
|
151
|
+
* background: `active` reflects the current state, `run` flips it (with an
|
|
152
|
+
* undo checkpoint). An optional `when` predicate hides the button.
|
|
153
|
+
*/
|
|
154
|
+
function booleanToggle(
|
|
155
|
+
id: string,
|
|
156
|
+
tooltip: string,
|
|
157
|
+
icon: TemplateResult,
|
|
158
|
+
prop: WardleyToggleProp,
|
|
159
|
+
when?: (ctx: ToolbarContext) => boolean
|
|
160
|
+
) {
|
|
161
|
+
return {
|
|
162
|
+
id,
|
|
163
|
+
tooltip,
|
|
164
|
+
icon,
|
|
165
|
+
when: when ?? true,
|
|
166
|
+
active(ctx: ToolbarContext) {
|
|
167
|
+
const models = ctx.getSurfaceModelsByType(WardleyBackgroundElementModel);
|
|
168
|
+
return models.length > 0 && models.every(model => model[prop]);
|
|
169
|
+
},
|
|
170
|
+
run(ctx: ToolbarContext) {
|
|
171
|
+
const models = ctx.getSurfaceModelsByType(WardleyBackgroundElementModel);
|
|
172
|
+
if (!models.length) return;
|
|
173
|
+
|
|
174
|
+
const enable = !models.every(model => model[prop]);
|
|
175
|
+
ctx.std.store.captureSync();
|
|
176
|
+
const crud = ctx.std.get(EdgelessCRUDIdentifier);
|
|
177
|
+
for (const model of models) {
|
|
178
|
+
crud.updateElement(model.id, { [prop]: enable });
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const wardleyToolbarConfig = {
|
|
185
|
+
actions: [
|
|
186
|
+
booleanToggle(
|
|
187
|
+
'a.toggle-resize',
|
|
188
|
+
'Enable / lock resizing',
|
|
189
|
+
ResizeIcon,
|
|
190
|
+
'resizeEnabled'
|
|
191
|
+
),
|
|
192
|
+
// Group 1 — evolution (X) side: axis, phase labels, columns, corner
|
|
193
|
+
// labels, and the gradient toggle.
|
|
194
|
+
{
|
|
195
|
+
id: 'b.evolution',
|
|
196
|
+
actions: [
|
|
197
|
+
booleanToggle(
|
|
198
|
+
'b.1-axis-x',
|
|
199
|
+
'Evolution axis (X)',
|
|
200
|
+
XAxisIcon,
|
|
201
|
+
'showXAxis'
|
|
202
|
+
),
|
|
203
|
+
booleanToggle(
|
|
204
|
+
'b.2-column-labels',
|
|
205
|
+
'Evolution phase labels',
|
|
206
|
+
ColumnLabelsIcon,
|
|
207
|
+
'showColumnLabels'
|
|
208
|
+
),
|
|
209
|
+
booleanToggle(
|
|
210
|
+
'b.3-columns',
|
|
211
|
+
'Columns (dividers)',
|
|
212
|
+
ColumnsIcon,
|
|
213
|
+
'showColumnDividers'
|
|
214
|
+
),
|
|
215
|
+
booleanToggle(
|
|
216
|
+
'b.4-corner-labels',
|
|
217
|
+
'Labels Uncharted / Industrialized',
|
|
218
|
+
CornerLabelsIcon,
|
|
219
|
+
'showCornerLabels'
|
|
220
|
+
),
|
|
221
|
+
// Only relevant when the selection has a gradient variant.
|
|
222
|
+
booleanToggle(
|
|
223
|
+
'b.5-gradient',
|
|
224
|
+
'Show / hide the gradient',
|
|
225
|
+
GradientIcon,
|
|
226
|
+
'showGradient',
|
|
227
|
+
ctx =>
|
|
228
|
+
ctx
|
|
229
|
+
.getSurfaceModelsByType(WardleyBackgroundElementModel)
|
|
230
|
+
.some(model => model.variant !== 'classic')
|
|
231
|
+
),
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
// Group 2 — value-chain (Y) side: axis and Visible/Invisible labels.
|
|
235
|
+
{
|
|
236
|
+
id: 'c.value-chain',
|
|
237
|
+
actions: [
|
|
238
|
+
booleanToggle(
|
|
239
|
+
'c.1-axis-y',
|
|
240
|
+
'Value Chain axis (Y)',
|
|
241
|
+
YAxisIcon,
|
|
242
|
+
'showYAxis'
|
|
243
|
+
),
|
|
244
|
+
booleanToggle(
|
|
245
|
+
'c.2-visibility-labels',
|
|
246
|
+
'Labels Visible / Invisible',
|
|
247
|
+
VisibilityIcon,
|
|
248
|
+
'showVisibilityLabels'
|
|
249
|
+
),
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
// Generate the auto-legend from the components present inside this
|
|
253
|
+
// background's perimeter (+ a gradient-meaning block for gradient variants).
|
|
254
|
+
{
|
|
255
|
+
id: 'd.legend',
|
|
256
|
+
tooltip: 'Generate the legend (components present)',
|
|
257
|
+
icon: wardleyLegendIcon,
|
|
258
|
+
run(ctx) {
|
|
259
|
+
const models = ctx.getSurfaceModelsByType(WardleyBackgroundElementModel);
|
|
260
|
+
const bg = models[0];
|
|
261
|
+
if (!bg) return;
|
|
262
|
+
createWardleyLegend(ctx.std, bg);
|
|
263
|
+
ctx.std.getOptional(TelemetryProvider)?.track('FrameworkLegendCreated', {
|
|
264
|
+
framework: 'wardley',
|
|
265
|
+
element: 'legend',
|
|
266
|
+
page: 'whiteboard editor',
|
|
267
|
+
segment: 'element toolbar',
|
|
268
|
+
module: 'wardley toolbar',
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
when: ctx =>
|
|
274
|
+
ctx.getSurfaceModelsByType(WardleyBackgroundElementModel).length > 0,
|
|
275
|
+
} as const satisfies ToolbarModuleConfig;
|
|
276
|
+
|
|
277
|
+
export const wardleyToolbarExtension = ToolbarModuleExtension({
|
|
278
|
+
id: BlockFlavourIdentifier('affine:surface:wardley'),
|
|
279
|
+
config: wardleyToolbarConfig,
|
|
280
|
+
});
|