@formicoidea/labre-framework-wardley 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions.d.ts +63 -20
- package/dist/actions.js +190 -57
- package/dist/audit-criteria.d.ts +31 -0
- package/dist/audit-criteria.js +90 -0
- package/dist/background.d.ts +14 -0
- package/dist/background.js +338 -0
- package/dist/commands.d.ts +9 -0
- package/dist/commands.js +352 -0
- package/dist/consts.d.ts +10 -4
- package/dist/consts.js +10 -9
- package/dist/descriptor.d.ts +8 -3
- package/dist/descriptor.js +6 -3
- package/dist/element-renderer.d.ts +8 -5
- package/dist/element-renderer.js +12 -147
- package/dist/element-view.d.ts +8 -4
- package/dist/element-view.js +30 -22
- package/dist/export.d.ts +211 -0
- package/dist/export.js +655 -0
- package/dist/gradient.d.ts +6 -11
- package/dist/gradient.js +59 -48
- package/dist/import.d.ts +116 -0
- package/dist/import.js +905 -0
- package/dist/index.d.ts +27 -1
- package/dist/index.js +31 -1
- package/dist/interchange.d.ts +80 -0
- package/dist/interchange.js +138 -0
- package/dist/legend.js +8 -0
- package/dist/natures.d.ts +50 -0
- package/dist/natures.js +93 -0
- package/dist/node/node-renderer.js +1 -1
- package/dist/nudges.d.ts +41 -0
- package/dist/nudges.js +69 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +87 -0
- package/dist/reading.d.ts +3 -0
- package/dist/reading.js +129 -0
- package/dist/roles.d.ts +50 -0
- package/dist/roles.js +132 -0
- package/dist/rules.d.ts +2 -0
- package/dist/rules.js +286 -0
- package/dist/templates/index.js +66 -10
- package/dist/templates/maps.js +146 -22
- package/dist/toolbar/config.js +3 -1
- package/dist/toolbar/icons.d.ts +20 -0
- package/dist/toolbar/icons.js +34 -0
- package/dist/toolbar/senior-tool.js +1 -0
- package/dist/toolbar/wardley-menu.d.ts +8 -15
- package/dist/toolbar/wardley-menu.js +8 -136
- package/dist/toolbar/wardley-senior-button.js +12 -6
- package/dist/translations.d.ts +16 -0
- package/dist/translations.js +24 -0
- package/dist/view.d.ts +17 -0
- package/dist/view.js +127 -13
- package/package.json +2 -2
- package/dist/label-layout.d.ts +0 -20
- package/dist/label-layout.js +0 -72
- package/dist/shortcuts.d.ts +0 -2
- package/dist/shortcuts.js +0 -37
package/dist/commands.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { GfxControllerIdentifier } from '@formicoidea/labre-core/std/gfx';
|
|
2
|
+
import { activateWardleyConnector, createWardleyBackground, createWardleyInertia, createWardleyMarket, createWardleyNode, createWardleyPipeline, exportOwmFile, importOwmFile, importWardleySvgFile, wardleyMapsOnBoard, } from './actions.js';
|
|
3
|
+
import { WARDLEY_ROLE, WARDLEY_ROLES } from './roles.js';
|
|
4
|
+
import { wardleyAnchorIcon, wardleyArrowIcon, wardleyBackgroundIcon, wardleyBenefitIcon, wardleyComponentIcon, wardleyEcosystemIcon, wardleyEvolutionGradientIcon, wardleyExportOwmIcon, wardleyImportOwmIcon, wardleyImportSvgIcon, wardleyInertiaIcon, wardleyLinkIcon, wardleyMarketIcon, wardleyMethodIcon, wardleyOpportunityIcon, wardleyPipelineIcon, } from './toolbar/icons.js';
|
|
5
|
+
const SPECS = [
|
|
6
|
+
{
|
|
7
|
+
id: 'addBackground',
|
|
8
|
+
label: 'Wardley map background',
|
|
9
|
+
labelKey: 'com.labre.keyboardShortcuts.wardley.addBackground',
|
|
10
|
+
key: 'b',
|
|
11
|
+
iconKey: 'wardley.background',
|
|
12
|
+
category: 'backgrounds',
|
|
13
|
+
kind: 'artefact',
|
|
14
|
+
element: 'background:classic',
|
|
15
|
+
run: gfx => createWardleyBackground(gfx, 'classic'),
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'addOpportunityBackground',
|
|
19
|
+
label: 'Opportunity background (gradient)',
|
|
20
|
+
iconKey: 'wardley.background.opportunity',
|
|
21
|
+
category: 'backgrounds',
|
|
22
|
+
kind: 'artefact',
|
|
23
|
+
element: 'background:opportunity',
|
|
24
|
+
run: gfx => createWardleyBackground(gfx, 'opportunity'),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'addBenefitBackground',
|
|
28
|
+
label: 'Benefit / Investment background (gradient)',
|
|
29
|
+
iconKey: 'wardley.background.benefit',
|
|
30
|
+
category: 'backgrounds',
|
|
31
|
+
kind: 'artefact',
|
|
32
|
+
element: 'background:benefit',
|
|
33
|
+
run: gfx => createWardleyBackground(gfx, 'benefit'),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'addEvolutionBackground',
|
|
37
|
+
label: 'Evolution background (Wardley presentation)',
|
|
38
|
+
iconKey: 'wardley.background.evolution',
|
|
39
|
+
category: 'backgrounds',
|
|
40
|
+
kind: 'artefact',
|
|
41
|
+
element: 'background:evolution-gradient',
|
|
42
|
+
run: gfx => createWardleyBackground(gfx, 'evolution-gradient'),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'addComponent',
|
|
46
|
+
label: 'Component',
|
|
47
|
+
labelKey: 'com.labre.keyboardShortcuts.wardley.addComponent',
|
|
48
|
+
key: 'c',
|
|
49
|
+
iconKey: 'wardley.component',
|
|
50
|
+
category: 'nodes',
|
|
51
|
+
kind: 'artefact',
|
|
52
|
+
element: 'node:component',
|
|
53
|
+
run: gfx => createWardleyNode(gfx, 'component'),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'addMethod',
|
|
57
|
+
label: 'Component + method',
|
|
58
|
+
labelKey: 'com.labre.keyboardShortcuts.wardley.addMethod',
|
|
59
|
+
key: 'm',
|
|
60
|
+
iconKey: 'wardley.method',
|
|
61
|
+
category: 'nodes',
|
|
62
|
+
kind: 'artefact',
|
|
63
|
+
element: 'node:method',
|
|
64
|
+
run: gfx => createWardleyNode(gfx, 'method'),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'addMarket',
|
|
68
|
+
label: 'Market',
|
|
69
|
+
iconKey: 'wardley.market',
|
|
70
|
+
category: 'nodes',
|
|
71
|
+
kind: 'artefact',
|
|
72
|
+
element: 'node:market',
|
|
73
|
+
run: createWardleyMarket,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'addEcosystem',
|
|
77
|
+
label: 'Ecosystem',
|
|
78
|
+
iconKey: 'wardley.ecosystem',
|
|
79
|
+
category: 'nodes',
|
|
80
|
+
kind: 'artefact',
|
|
81
|
+
element: 'node:ecosystem',
|
|
82
|
+
run: gfx => createWardleyNode(gfx, 'ecosystem'),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'addAnchor',
|
|
86
|
+
label: 'Anchor',
|
|
87
|
+
iconKey: 'wardley.anchor',
|
|
88
|
+
category: 'nodes',
|
|
89
|
+
kind: 'artefact',
|
|
90
|
+
element: 'node:anchor',
|
|
91
|
+
run: gfx => createWardleyNode(gfx, 'anchor'),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'addPipeline',
|
|
95
|
+
label: 'Pipeline',
|
|
96
|
+
labelKey: 'com.labre.keyboardShortcuts.wardley.addPipeline',
|
|
97
|
+
key: 'p',
|
|
98
|
+
iconKey: 'wardley.pipeline',
|
|
99
|
+
category: 'nodes',
|
|
100
|
+
kind: 'artefact',
|
|
101
|
+
element: 'node:pipeline',
|
|
102
|
+
run: createWardleyPipeline,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'linkTool',
|
|
106
|
+
label: 'Link',
|
|
107
|
+
labelKey: 'com.labre.keyboardShortcuts.wardley.linkTool',
|
|
108
|
+
key: 'l',
|
|
109
|
+
iconKey: 'wardley.link',
|
|
110
|
+
category: 'connectors',
|
|
111
|
+
kind: 'tool',
|
|
112
|
+
element: 'connector:link',
|
|
113
|
+
edgeRole: WARDLEY_ROLE.dependency,
|
|
114
|
+
run: gfx => activateWardleyConnector(gfx, 'link'),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 'evolutionArrow',
|
|
118
|
+
label: 'Arrow (evolution)',
|
|
119
|
+
labelKey: 'com.labre.keyboardShortcuts.wardley.evolutionArrow',
|
|
120
|
+
key: 'a',
|
|
121
|
+
iconKey: 'wardley.arrow',
|
|
122
|
+
category: 'connectors',
|
|
123
|
+
kind: 'tool',
|
|
124
|
+
element: 'connector:arrow',
|
|
125
|
+
edgeRole: WARDLEY_ROLE.changeArrow,
|
|
126
|
+
run: gfx => activateWardleyConnector(gfx, 'arrow'),
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'addInertia',
|
|
130
|
+
label: 'Inertia',
|
|
131
|
+
labelKey: 'com.labre.keyboardShortcuts.wardley.addInertia',
|
|
132
|
+
key: 'i',
|
|
133
|
+
iconKey: 'wardley.inertia',
|
|
134
|
+
category: 'nodes',
|
|
135
|
+
kind: 'artefact',
|
|
136
|
+
element: 'node:inertia',
|
|
137
|
+
run: createWardleyInertia,
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
/**
|
|
141
|
+
* The sentence a tool shows under its label — the role's own gesture hint
|
|
142
|
+
* (M1 of `docs/adr/0010`), or nothing at all for a tool that draws no typed
|
|
143
|
+
* edge. Read off the vocabulary rather than written here twice.
|
|
144
|
+
*/
|
|
145
|
+
function gestureOf(spec) {
|
|
146
|
+
const direction = spec.edgeRole === undefined
|
|
147
|
+
? undefined
|
|
148
|
+
: WARDLEY_ROLES[spec.edgeRole]?.direction;
|
|
149
|
+
if (!direction?.gestureHintKey)
|
|
150
|
+
return {};
|
|
151
|
+
return {
|
|
152
|
+
descriptionKey: direction.gestureHintKey,
|
|
153
|
+
descriptionFallback: direction.gestureHintFallback,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const toolboxCommands = SPECS.map((spec, order) => ({
|
|
157
|
+
id: `wardley.${spec.id}`,
|
|
158
|
+
owner: 'wardley',
|
|
159
|
+
kind: spec.kind,
|
|
160
|
+
labelKey: spec.labelKey ?? `com.labre.commands.wardley.${spec.id}`,
|
|
161
|
+
labelFallback: spec.label,
|
|
162
|
+
...gestureOf(spec),
|
|
163
|
+
category: spec.category,
|
|
164
|
+
iconKey: spec.iconKey,
|
|
165
|
+
surfaces: ['senior-menu', 'catalogue', 'palette', 'agent'],
|
|
166
|
+
order,
|
|
167
|
+
scope: 'edgeless',
|
|
168
|
+
defaultKeys: spec.key
|
|
169
|
+
? { mac: ['w', spec.key], other: ['w', spec.key] }
|
|
170
|
+
: { mac: [], other: [] },
|
|
171
|
+
availability: 'always',
|
|
172
|
+
run: std => spec.run(std.get(GfxControllerIdentifier)),
|
|
173
|
+
telemetry: { framework: 'wardley', element: spec.element },
|
|
174
|
+
}));
|
|
175
|
+
/**
|
|
176
|
+
* The IMPORT — the first Wardley command whose subject is a whole map rather
|
|
177
|
+
* than something you draw, and the first that needs nothing on the board.
|
|
178
|
+
*
|
|
179
|
+
* ## `'senior-menu'`, per the PO decision of 2026-08-28
|
|
180
|
+
*
|
|
181
|
+
* An interpreted import lives in its framework's sub-menu. On an empty canvas
|
|
182
|
+
* the sub-menu is the first thing a user opens, and "start from the map
|
|
183
|
+
* somebody sent me" belongs in that row beside "start from a component" —
|
|
184
|
+
* asking them to find the catalogue sidepanel first is the friction the
|
|
185
|
+
* decision names. It is Wardley's fourteenth nomination, which is exactly the
|
|
186
|
+
* cap, so nothing about the sub-menu's arbitration changes and no button is
|
|
187
|
+
* pushed out of the row.
|
|
188
|
+
*
|
|
189
|
+
* No `'contextual-toolbar'`: a contextual toolbar is a statement about a
|
|
190
|
+
* SELECTION, and the moment this command is most wanted is on a board with
|
|
191
|
+
* nothing on it at all.
|
|
192
|
+
*
|
|
193
|
+
* ## `'editable'`
|
|
194
|
+
*
|
|
195
|
+
* An import needs no selection, but it WRITES, so a read-only document is one
|
|
196
|
+
* it cannot run on — and that is a precondition a catalogue has to be able to
|
|
197
|
+
* show. `'always'` would light the entry on a read-only board, do nothing when
|
|
198
|
+
* clicked, and put the same untruth into the manifest a host reads.
|
|
199
|
+
*/
|
|
200
|
+
const importCommand = {
|
|
201
|
+
id: 'wardley.importOwm',
|
|
202
|
+
owner: 'wardley',
|
|
203
|
+
kind: 'action',
|
|
204
|
+
labelKey: 'com.labre.commands.wardley.importOwm',
|
|
205
|
+
labelFallback: 'Import Wardley map (OWM)',
|
|
206
|
+
descriptionKey: 'com.labre.commands.wardley.importOwm.description',
|
|
207
|
+
descriptionFallback: 'Open an OnlineWardleyMaps .owm file as a map. What Labre cannot draw is kept in the document, and the import says what it was.',
|
|
208
|
+
category: 'interchange',
|
|
209
|
+
iconKey: 'wardley.import-owm',
|
|
210
|
+
surfaces: ['senior-menu', 'catalogue', 'palette', 'agent'],
|
|
211
|
+
order: SPECS.length,
|
|
212
|
+
scope: 'edgeless',
|
|
213
|
+
// Keyless by intent. The `w` chord already seats seven artefacts, and a
|
|
214
|
+
// framework binds past that by host override rather than by shipping a
|
|
215
|
+
// default — still bindable from Settings › Shortcuts.
|
|
216
|
+
defaultKeys: { mac: [], other: [] },
|
|
217
|
+
availability: 'editable',
|
|
218
|
+
run: std => void importOwmFile(std),
|
|
219
|
+
// `board:` and not `node:` — this one is launched with no map anywhere.
|
|
220
|
+
telemetry: { framework: 'wardley', element: 'board:import-owm' },
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* The EXPORT — the other direction of the same format.
|
|
224
|
+
*
|
|
225
|
+
* It declines `'senior-menu'`, and the asymmetry with the import above is the
|
|
226
|
+
* ruling BPMN already carries: the sub-menu is where a board COMES FROM, and an
|
|
227
|
+
* export is what you do to a board you already have. It keeps `'catalogue'`,
|
|
228
|
+
* which is the registry's own invariant rather than a category claim — a
|
|
229
|
+
* command missing from the catalogue is unreachable the moment its framework
|
|
230
|
+
* overflows the fourteen slots — plus the palette and the agent. No
|
|
231
|
+
* `'contextual-toolbar'` either, and that is a declaration rather than an
|
|
232
|
+
* oversight: a contextual-toolbar surface is rendered by an element's own
|
|
233
|
+
* `ToolbarModuleConfig`, and declaring one nothing invokes would put an entry
|
|
234
|
+
* in the manifest that no toolbar draws.
|
|
235
|
+
*
|
|
236
|
+
* `'always'` with a `when` on the BOARD, and the pair is deliberate. An export
|
|
237
|
+
* READS: it needs no selection, and it is offered on a locked map and on a
|
|
238
|
+
* read-only document — which is precisely the board somebody wants to take
|
|
239
|
+
* away. What it DOES need is a plot to measure coordinates against, and that is
|
|
240
|
+
* a fact about the surface rather than about the selection: a Wardley node has
|
|
241
|
+
* no `visibility` prop, so its position on the plot IS its coordinate, and with
|
|
242
|
+
* no map there is nothing to invert. `'selection'` would be BPMN's shape copied
|
|
243
|
+
* for the look of it — that command's precondition genuinely is a selected
|
|
244
|
+
* pool, and this one's is not.
|
|
245
|
+
*
|
|
246
|
+
* **v1 writes one map.** An OWM document is one map; a board holding several is
|
|
247
|
+
* written against the first in document order and the export says so out loud
|
|
248
|
+
* in its warnings.
|
|
249
|
+
*/
|
|
250
|
+
const exportCommand = {
|
|
251
|
+
id: 'wardley.exportOwm',
|
|
252
|
+
owner: 'wardley',
|
|
253
|
+
kind: 'action',
|
|
254
|
+
labelKey: 'com.labre.commands.wardley.exportOwm',
|
|
255
|
+
labelFallback: 'Export Wardley map (OWM)',
|
|
256
|
+
descriptionKey: 'com.labre.commands.wardley.exportOwm.description',
|
|
257
|
+
descriptionFallback: 'Download the map as an OnlineWardleyMaps .owm file, ready to open in any Wardley mapping tool.',
|
|
258
|
+
category: 'interchange',
|
|
259
|
+
iconKey: 'wardley.export-owm',
|
|
260
|
+
surfaces: ['catalogue', 'palette', 'agent'],
|
|
261
|
+
order: SPECS.length + 1,
|
|
262
|
+
scope: 'edgeless',
|
|
263
|
+
defaultKeys: { mac: [], other: [] },
|
|
264
|
+
availability: 'always',
|
|
265
|
+
run: exportOwmFile,
|
|
266
|
+
telemetry: { framework: 'wardley', element: 'board:export-owm' },
|
|
267
|
+
when: std => wardleyMapsOnBoard(std).length > 0,
|
|
268
|
+
};
|
|
269
|
+
/**
|
|
270
|
+
* The SVG FALLBACK import — the visual tier, named as such before the picker
|
|
271
|
+
* opens (`docs/adr/0012`, P2).
|
|
272
|
+
*
|
|
273
|
+
* ## Why the catalogue and not the senior row
|
|
274
|
+
*
|
|
275
|
+
* Because {@link importCommand} is already there. The sub-menu carries the
|
|
276
|
+
* framework's NATIVE format — the OWM DSL, which the ADR's roadmap calls the
|
|
277
|
+
* reference Wardley import — and that is the route a user should be pointed at:
|
|
278
|
+
* an `.owm` file carries `[visibility, evolution]` pairs, which ARE the map's
|
|
279
|
+
* meaning, so it round-trips. This one reads a picture. A fallback that
|
|
280
|
+
* outranked the real thing would be the platform offering the lossy door first,
|
|
281
|
+
* and Wardley's fourteen nominations are already exactly the cap — a fifteenth
|
|
282
|
+
* would push a button out of the row for the rarest thing anybody does to a map.
|
|
283
|
+
*
|
|
284
|
+
* So it lands one click away, in the artefact catalogue behind "More
|
|
285
|
+
* artefacts…", and keeps `'palette'` and `'agent'` so it stays findable by name
|
|
286
|
+
* and invocable by an agent.
|
|
287
|
+
*
|
|
288
|
+
* **Flagged for the PO** as a curation call rather than a technical one: it is
|
|
289
|
+
* a one-line change either way.
|
|
290
|
+
*
|
|
291
|
+
* ## The label names the tier before the file is read
|
|
292
|
+
*
|
|
293
|
+
* A map is coordinates, and this reader recovers none: it recognises circles
|
|
294
|
+
* and words. Saying so in the description is not modesty, it is the contract —
|
|
295
|
+
* "the import surface must name the tier before the file is read".
|
|
296
|
+
*/
|
|
297
|
+
const importSvgCommand = {
|
|
298
|
+
id: 'wardley.importSvg',
|
|
299
|
+
owner: 'wardley',
|
|
300
|
+
kind: 'action',
|
|
301
|
+
labelKey: 'com.labre.commands.wardley.importSvg',
|
|
302
|
+
labelFallback: 'Import SVG sketch',
|
|
303
|
+
descriptionKey: 'com.labre.commands.wardley.importSvg.description',
|
|
304
|
+
descriptionFallback: 'Best effort: recognises shapes and text, no round-trip. The axes and the evolution are not read — what arrives is a sketch you then promote.',
|
|
305
|
+
// The same section the two OWM directions are filed under, and the same one
|
|
306
|
+
// BPMN files its `.bpmn` pair under: a host that translated the header once
|
|
307
|
+
// has translated it for every framework.
|
|
308
|
+
category: 'interchange',
|
|
309
|
+
iconKey: 'wardley.import-svg',
|
|
310
|
+
surfaces: ['catalogue', 'palette', 'agent'],
|
|
311
|
+
// Last of the three interchange entries, which is also how the section reads
|
|
312
|
+
// for somebody scanning "what can I do with a file": the native format both
|
|
313
|
+
// ways, then the best-effort reader.
|
|
314
|
+
order: SPECS.length + 2,
|
|
315
|
+
scope: 'edgeless',
|
|
316
|
+
defaultKeys: { mac: [], other: [] },
|
|
317
|
+
// It WRITES, so a read-only document is one it cannot run on.
|
|
318
|
+
availability: 'editable',
|
|
319
|
+
run: importWardleySvgFile,
|
|
320
|
+
// `board:` and not `node:`: it is launched with nothing selected, and often
|
|
321
|
+
// with nothing on the canvas at all.
|
|
322
|
+
telemetry: { framework: 'wardley', element: 'board:import-svg' },
|
|
323
|
+
};
|
|
324
|
+
/**
|
|
325
|
+
* The Wardley registry: the thirteen toolbox entries, then the two directions
|
|
326
|
+
* of the OWM DSL and the SVG fallback (`docs/adr/0012`).
|
|
327
|
+
*/
|
|
328
|
+
export const wardleyCommands = [
|
|
329
|
+
...toolboxCommands,
|
|
330
|
+
importCommand,
|
|
331
|
+
exportCommand,
|
|
332
|
+
importSvgCommand,
|
|
333
|
+
];
|
|
334
|
+
/** `iconKey` → template. Never travels through either manifest (ADR 0008). */
|
|
335
|
+
export const wardleyCommandIcons = {
|
|
336
|
+
'wardley.background': wardleyBackgroundIcon,
|
|
337
|
+
'wardley.background.opportunity': wardleyOpportunityIcon,
|
|
338
|
+
'wardley.background.benefit': wardleyBenefitIcon,
|
|
339
|
+
'wardley.background.evolution': wardleyEvolutionGradientIcon,
|
|
340
|
+
'wardley.component': wardleyComponentIcon,
|
|
341
|
+
'wardley.method': wardleyMethodIcon,
|
|
342
|
+
'wardley.market': wardleyMarketIcon,
|
|
343
|
+
'wardley.ecosystem': wardleyEcosystemIcon,
|
|
344
|
+
'wardley.anchor': wardleyAnchorIcon,
|
|
345
|
+
'wardley.pipeline': wardleyPipelineIcon,
|
|
346
|
+
'wardley.link': wardleyLinkIcon,
|
|
347
|
+
'wardley.arrow': wardleyArrowIcon,
|
|
348
|
+
'wardley.inertia': wardleyInertiaIcon,
|
|
349
|
+
'wardley.import-owm': wardleyImportOwmIcon,
|
|
350
|
+
'wardley.export-owm': wardleyExportOwmIcon,
|
|
351
|
+
'wardley.import-svg': wardleyImportSvgIcon,
|
|
352
|
+
};
|
package/dist/consts.d.ts
CHANGED
|
@@ -47,10 +47,16 @@ export declare const LINE: {
|
|
|
47
47
|
};
|
|
48
48
|
export declare const ARROW = 11;
|
|
49
49
|
export declare const CARD_RADIUS = 10;
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Default phase wording. The zone RATIOS live in `./background.ts`, with the
|
|
52
|
+
* zones they delimit — the declaration is the single source of the map's shape.
|
|
53
|
+
*/
|
|
54
|
+
export declare const PHASE_LABELS: {
|
|
55
|
+
genesis: string;
|
|
56
|
+
customBuilt: string;
|
|
57
|
+
product: string;
|
|
58
|
+
commodity: string;
|
|
59
|
+
};
|
|
54
60
|
export declare const AXIS_LABELS: {
|
|
55
61
|
xAxis: string;
|
|
56
62
|
yAxis: string;
|
package/dist/consts.js
CHANGED
|
@@ -33,15 +33,16 @@ export const OFFSETS = {
|
|
|
33
33
|
export const LINE = { axis: 2, divider: 1.2, card: 1.5 };
|
|
34
34
|
export const ARROW = 11;
|
|
35
35
|
export const CARD_RADIUS = 10;
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Default phase wording. The zone RATIOS live in `./background.ts`, with the
|
|
38
|
+
* zones they delimit — the declaration is the single source of the map's shape.
|
|
39
|
+
*/
|
|
40
|
+
export const PHASE_LABELS = {
|
|
41
|
+
genesis: 'Genesis',
|
|
42
|
+
customBuilt: 'Custom-Built',
|
|
43
|
+
product: 'Product (+Rental)',
|
|
44
|
+
commodity: 'Commodity (+Utility)',
|
|
45
|
+
};
|
|
45
46
|
export const AXIS_LABELS = {
|
|
46
47
|
xAxis: 'Evolution',
|
|
47
48
|
yAxis: 'Value Chain',
|
package/dist/descriptor.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { WardleyViewExtension } from './view.js';
|
|
1
|
+
import { WardleyRenderViewExtension, WardleyViewExtension } from './view.js';
|
|
2
2
|
/** Host wiring for the wardley framework. */
|
|
3
3
|
export declare const wardleyFramework: {
|
|
4
4
|
readonly flag: "wardley";
|
|
5
|
-
readonly
|
|
6
|
-
readonly
|
|
5
|
+
readonly telemetryKey: "wardley";
|
|
6
|
+
readonly extensions: readonly [{
|
|
7
|
+
readonly viewExtension: typeof WardleyRenderViewExtension;
|
|
8
|
+
}, {
|
|
9
|
+
readonly flag: "wardley";
|
|
10
|
+
readonly viewExtension: typeof WardleyViewExtension;
|
|
11
|
+
}];
|
|
7
12
|
};
|
package/dist/descriptor.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { WardleyViewExtension } from './view.js';
|
|
1
|
+
import { WardleyRenderViewExtension, WardleyViewExtension } from './view.js';
|
|
2
2
|
/** Host wiring for the wardley framework. */
|
|
3
3
|
export const wardleyFramework = {
|
|
4
4
|
flag: 'wardley',
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
telemetryKey: 'wardley',
|
|
6
|
+
extensions: [
|
|
7
|
+
{ viewExtension: WardleyRenderViewExtension },
|
|
8
|
+
{ flag: 'wardley', viewExtension: WardleyViewExtension },
|
|
9
|
+
],
|
|
7
10
|
};
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { type ElementRenderer } from '@formicoidea/labre-core/blocks/surface';
|
|
2
2
|
import type { WardleyBackgroundElementModel } from '@formicoidea/labre-core/model';
|
|
3
3
|
/**
|
|
4
|
-
* Canvas renderer for the Wardley map background
|
|
5
|
-
* an L-shaped axes frame (no top/right border), dashed evolution dividers and
|
|
6
|
-
* the symmetric axis labels.
|
|
4
|
+
* Canvas renderer for the Wardley map background.
|
|
7
5
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* There is no Wardley drawing code any more (PF2.12): the map is an
|
|
7
|
+
* INSTANTIATION of the framework-background primitive, configured by the
|
|
8
|
+
* `WARDLEY_BACKGROUND` declaration. What used to be two hundred lines of
|
|
9
|
+
* `ctx.fillText` is now a declaration any other framework can write for itself.
|
|
10
|
+
*
|
|
11
|
+
* Exported as a function as well as an extension because the non-regression
|
|
12
|
+
* suite drives it directly with a canvas stub.
|
|
10
13
|
*/
|
|
11
14
|
export declare const wardley: ElementRenderer<WardleyBackgroundElementModel>;
|
|
12
15
|
export declare const WardleyElementRendererExtension: import("@formicoidea/labre-core/store").ExtensionType & {
|
package/dist/element-renderer.js
CHANGED
|
@@ -1,150 +1,15 @@
|
|
|
1
|
-
import { ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
-
import {
|
|
3
|
-
import { paintGradientBackground } from './gradient.js';
|
|
4
|
-
function roundRectPath(ctx, x, y, w, h, r) {
|
|
5
|
-
const rr = Math.min(r, w / 2, h / 2);
|
|
6
|
-
ctx.beginPath();
|
|
7
|
-
ctx.moveTo(x + rr, y);
|
|
8
|
-
ctx.arcTo(x + w, y, x + w, y + h, rr);
|
|
9
|
-
ctx.arcTo(x + w, y + h, x, y + h, rr);
|
|
10
|
-
ctx.arcTo(x, y + h, x, y, rr);
|
|
11
|
-
ctx.arcTo(x, y, x + w, y, rr);
|
|
12
|
-
ctx.closePath();
|
|
13
|
-
}
|
|
1
|
+
import { createFrameworkBackgroundRenderer, ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { WARDLEY_BACKGROUND } from './background.js';
|
|
14
3
|
/**
|
|
15
|
-
* Canvas renderer for the Wardley map background
|
|
16
|
-
* an L-shaped axes frame (no top/right border), dashed evolution dividers and
|
|
17
|
-
* the symmetric axis labels.
|
|
4
|
+
* Canvas renderer for the Wardley map background.
|
|
18
5
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
6
|
+
* There is no Wardley drawing code any more (PF2.12): the map is an
|
|
7
|
+
* INSTANTIATION of the framework-background primitive, configured by the
|
|
8
|
+
* `WARDLEY_BACKGROUND` declaration. What used to be two hundred lines of
|
|
9
|
+
* `ctx.fillText` is now a declaration any other framework can write for itself.
|
|
10
|
+
*
|
|
11
|
+
* Exported as a function as well as an extension because the non-regression
|
|
12
|
+
* suite drives it directly with a canvas stub.
|
|
21
13
|
*/
|
|
22
|
-
export const wardley = (
|
|
23
|
-
|
|
24
|
-
const cx = w / 2;
|
|
25
|
-
const cy = h / 2;
|
|
26
|
-
ctx.setTransform(matrix.translateSelf(cx, cy).rotateSelf(model.rotate).translateSelf(-cx, -cy));
|
|
27
|
-
const px0 = MARGIN.left;
|
|
28
|
-
const px1 = w - MARGIN.right;
|
|
29
|
-
const py0 = MARGIN.top;
|
|
30
|
-
const py1 = h - MARGIN.bottom;
|
|
31
|
-
const pw = px1 - px0;
|
|
32
|
-
const ph = py1 - py0;
|
|
33
|
-
const ex = (r) => px0 + r * pw;
|
|
34
|
-
const line = (x1, y1, x2, y2) => {
|
|
35
|
-
ctx.beginPath();
|
|
36
|
-
ctx.moveTo(x1, y1);
|
|
37
|
-
ctx.lineTo(x2, y2);
|
|
38
|
-
ctx.stroke();
|
|
39
|
-
};
|
|
40
|
-
const vtext = (text, x, y, fontSize, color) => {
|
|
41
|
-
ctx.save();
|
|
42
|
-
ctx.translate(x, y);
|
|
43
|
-
ctx.rotate(-Math.PI / 2);
|
|
44
|
-
ctx.font = `${fontSize}px ${FONT_FAMILY}`;
|
|
45
|
-
ctx.fillStyle = color;
|
|
46
|
-
ctx.textAlign = 'center';
|
|
47
|
-
ctx.textBaseline = 'alphabetic';
|
|
48
|
-
ctx.fillText(text, 0, 0);
|
|
49
|
-
ctx.restore();
|
|
50
|
-
};
|
|
51
|
-
// ── Card (element bounds) ───────────────────────────────────────────
|
|
52
|
-
const inset = LINE.card / 2;
|
|
53
|
-
roundRectPath(ctx, inset, inset, w - inset * 2, h - inset * 2, CARD_RADIUS);
|
|
54
|
-
ctx.fillStyle = COLORS.card;
|
|
55
|
-
ctx.fill();
|
|
56
|
-
ctx.strokeStyle = COLORS.cardBorder;
|
|
57
|
-
ctx.lineWidth = LINE.card;
|
|
58
|
-
ctx.stroke();
|
|
59
|
-
// ── Curve-driven gradient variants (inscribed in the frame) ─────────
|
|
60
|
-
// Hidden when `showGradient` is false → plain white background.
|
|
61
|
-
if (model.variant !== 'classic' && model.showGradient) {
|
|
62
|
-
paintGradientBackground(ctx, model.variant, px0, px1, py0, py1);
|
|
63
|
-
}
|
|
64
|
-
// ── Optional evolution band tints ───────────────────────────────────
|
|
65
|
-
if (model.banded) {
|
|
66
|
-
const starts = [0, 0.175, 0.4, 0.7];
|
|
67
|
-
const ends = [0.175, 0.4, 0.7, 1];
|
|
68
|
-
for (let i = 0; i < 4; i++) {
|
|
69
|
-
ctx.fillStyle = COLORS.band[i];
|
|
70
|
-
ctx.fillRect(ex(starts[i]), py0, ex(ends[i]) - ex(starts[i]), ph);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
// ── Evolution phase dividers (dashed) ───────────────────────────────
|
|
74
|
-
if (model.showColumnDividers) {
|
|
75
|
-
ctx.strokeStyle = COLORS.divider;
|
|
76
|
-
ctx.lineWidth = LINE.divider;
|
|
77
|
-
ctx.setLineDash([5, 5]);
|
|
78
|
-
for (const r of EVOLUTION_BOUNDARIES) {
|
|
79
|
-
line(ex(r), py0, ex(r), py1);
|
|
80
|
-
}
|
|
81
|
-
ctx.setLineDash([]);
|
|
82
|
-
}
|
|
83
|
-
// ── Axes (L shape) + arrowheads ─────────────────────────────────────
|
|
84
|
-
// X and Y axes are independently toggleable. Each line stops at the base of
|
|
85
|
-
// its arrowhead (1px overlap, hidden under the triangle) so the line never
|
|
86
|
-
// pokes past the tip on zoom.
|
|
87
|
-
ctx.strokeStyle = COLORS.axis;
|
|
88
|
-
ctx.lineWidth = LINE.axis;
|
|
89
|
-
ctx.fillStyle = COLORS.axis;
|
|
90
|
-
if (model.showXAxis) {
|
|
91
|
-
line(px0, py1, px1 - ARROW + 1, py1); // X axis (arrow tip at px1)
|
|
92
|
-
ctx.beginPath(); // X arrow (points right)
|
|
93
|
-
ctx.moveTo(px1, py1);
|
|
94
|
-
ctx.lineTo(px1 - ARROW, py1 - ARROW / 2);
|
|
95
|
-
ctx.lineTo(px1 - ARROW, py1 + ARROW / 2);
|
|
96
|
-
ctx.closePath();
|
|
97
|
-
ctx.fill();
|
|
98
|
-
}
|
|
99
|
-
if (model.showYAxis) {
|
|
100
|
-
line(px0, py1, px0, py0 + ARROW - 1); // Y axis (arrow tip at py0)
|
|
101
|
-
ctx.beginPath(); // Y arrow (points up)
|
|
102
|
-
ctx.moveTo(px0, py0);
|
|
103
|
-
ctx.lineTo(px0 - ARROW / 2, py0 + ARROW);
|
|
104
|
-
ctx.lineTo(px0 + ARROW / 2, py0 + ARROW);
|
|
105
|
-
ctx.closePath();
|
|
106
|
-
ctx.fill();
|
|
107
|
-
}
|
|
108
|
-
// ── Horizontal labels ───────────────────────────────────────────────
|
|
109
|
-
ctx.textBaseline = 'alphabetic';
|
|
110
|
-
// Phase (column) labels (left-aligned at each zone start)
|
|
111
|
-
if (model.showColumnLabels) {
|
|
112
|
-
ctx.font = `${FONTS.phase}px ${FONT_FAMILY}`;
|
|
113
|
-
ctx.fillStyle = COLORS.label;
|
|
114
|
-
ctx.textAlign = 'left';
|
|
115
|
-
const phases = [
|
|
116
|
-
[model.phase0, 0],
|
|
117
|
-
[model.phase1, 0.175],
|
|
118
|
-
[model.phase2, 0.4],
|
|
119
|
-
[model.phase3, 0.7],
|
|
120
|
-
];
|
|
121
|
-
for (const [label, start] of phases) {
|
|
122
|
-
ctx.fillText(label, ex(start) + OFFSETS.phasePad, py1 + OFFSETS.phaseBaseline);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// "Evolution" title near the X arrow (tied to the X axis)
|
|
126
|
-
if (model.showXAxis) {
|
|
127
|
-
ctx.font = `${FONTS.axis}px ${FONT_FAMILY}`;
|
|
128
|
-
ctx.fillStyle = COLORS.axis;
|
|
129
|
-
ctx.textAlign = 'right';
|
|
130
|
-
ctx.fillText(model.xAxisTitle, px1 - OFFSETS.evolutionPadRight, py1 + OFFSETS.phaseBaseline);
|
|
131
|
-
}
|
|
132
|
-
// Direction indicators (Uncharted / Industrialized, top corners)
|
|
133
|
-
if (model.showCornerLabels) {
|
|
134
|
-
ctx.font = `${FONTS.direction}px ${FONT_FAMILY}`;
|
|
135
|
-
ctx.fillStyle = COLORS.label;
|
|
136
|
-
ctx.textAlign = 'left';
|
|
137
|
-
ctx.fillText(model.evolutionStart, px0 + OFFSETS.directionPadLeft, py0 + OFFSETS.directionTop);
|
|
138
|
-
ctx.textAlign = 'right';
|
|
139
|
-
ctx.fillText(model.evolutionEnd, px1 - OFFSETS.directionPadRight, py0 + OFFSETS.directionTop);
|
|
140
|
-
}
|
|
141
|
-
// ── Rotated Y labels (hugging the axis, symmetric with the X labels) ─
|
|
142
|
-
if (model.showYAxis) {
|
|
143
|
-
vtext(model.yAxisTitle, px0 - OFFSETS.yHug, (py0 + py1) / 2, FONTS.axis, COLORS.axis);
|
|
144
|
-
}
|
|
145
|
-
if (model.showVisibilityLabels) {
|
|
146
|
-
vtext(model.visibilityHigh, px0 - OFFSETS.yHug, py0 + OFFSETS.visibleTop, FONTS.visibility, COLORS.label);
|
|
147
|
-
vtext(model.visibilityLow, px0 - OFFSETS.yHug, py1 - OFFSETS.invisibleBottom, FONTS.visibility, COLORS.label);
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
export const WardleyElementRendererExtension = ElementRendererExtension('wardley', wardley);
|
|
14
|
+
export const wardley = createFrameworkBackgroundRenderer(WARDLEY_BACKGROUND);
|
|
15
|
+
export const WardleyElementRendererExtension = ElementRendererExtension(WARDLEY_BACKGROUND.type, wardley);
|
package/dist/element-view.d.ts
CHANGED
|
@@ -8,13 +8,17 @@ export declare class WardleyView extends GfxElementModelView<WardleyBackgroundEl
|
|
|
8
8
|
onDestroyed(): void;
|
|
9
9
|
/** Double-click on a label → edit its text in place. */
|
|
10
10
|
private _onDblClick;
|
|
11
|
+
/**
|
|
12
|
+
* @param current the words currently DRAWN — which is the vocabulary, not
|
|
13
|
+
* `model[field]`, for a label the user has never renamed. Opening on the raw
|
|
14
|
+
* prop would show an empty box for a label that plainly reads "Evolution".
|
|
15
|
+
*/
|
|
11
16
|
private _openLabelEditor;
|
|
12
17
|
private _closeLabelEditor;
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
|
-
* Resize gating: the
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* handles reactively. Moving/selecting stays available throughout.
|
|
20
|
+
* Resize gating, from the primitive: the handles stay hidden until
|
|
21
|
+
* `resizeEnabled` is true — the runtime half of the declaration's
|
|
22
|
+
* `geometry.resizable`.
|
|
19
23
|
*/
|
|
20
24
|
export declare const WardleyInteraction: import("@formicoidea/labre-core/store").ExtensionType;
|