@formicoidea/labre-framework-wardley 0.35.0 → 0.36.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 +108 -6
- package/dist/actions.js +172 -11
- package/dist/commands-manifest.js +40 -0
- package/dist/commands.d.ts +18 -1
- package/dist/commands.js +96 -7
- package/dist/export.js +50 -2
- package/dist/import.d.ts +5 -3
- package/dist/import.js +53 -8
- package/dist/index.d.ts +3 -2
- package/dist/index.js +7 -2
- package/dist/legend.d.ts +27 -1
- package/dist/legend.js +277 -3
- package/dist/natures.d.ts +31 -0
- package/dist/natures.js +64 -4
- package/dist/node/consts.d.ts +123 -0
- package/dist/node/consts.js +122 -0
- package/dist/node/node-view.d.ts +82 -4
- package/dist/node/node-view.js +108 -4
- package/dist/node/porter-watcher.d.ts +119 -0
- package/dist/node/porter-watcher.js +255 -0
- package/dist/porter-competition.d.ts +36 -0
- package/dist/porter-competition.js +50 -0
- package/dist/presets.d.ts +151 -5
- package/dist/presets.js +336 -12
- package/dist/roles.d.ts +4 -0
- package/dist/roles.js +67 -0
- package/dist/toolbar/icons.d.ts +36 -0
- package/dist/toolbar/icons.js +58 -0
- package/dist/toolbar/node-config.d.ts +20 -0
- package/dist/toolbar/node-config.js +100 -7
- package/dist/view.js +10 -0
- package/package.json +2 -2
package/dist/node/consts.js
CHANGED
|
@@ -98,3 +98,125 @@ export const METHOD = {
|
|
|
98
98
|
centerRatio: 0.5, // inner white component radius / R
|
|
99
99
|
};
|
|
100
100
|
export const METHOD_LABEL = 'Component';
|
|
101
|
+
/**
|
|
102
|
+
* Porter's forces (composite): a large white circle carrying ONE letter, pushed
|
|
103
|
+
* on from the four cardinal directions by solid red arrows.
|
|
104
|
+
*
|
|
105
|
+
* Twice the market's diameter, at the PO's recette of #210: this is not a link
|
|
106
|
+
* in the value chain drawn a little differently, it is a pressure bearing on the
|
|
107
|
+
* whole map, and it has to read as one at the zoom an architect actually works
|
|
108
|
+
* at. Every other number here is proportional to it.
|
|
109
|
+
*
|
|
110
|
+
* The letter IS the notation — **R** relative competition, **L** struggle for
|
|
111
|
+
* survival, **E** struggle to establish — which is why it is written as the
|
|
112
|
+
* shape's own inner text rather than as a label beside it: a double-click opens
|
|
113
|
+
* the native shape editor on the circle, and typing another letter is the whole
|
|
114
|
+
* of the edit. It is also why this glyph carries no label at all: the three
|
|
115
|
+
* words behind the three letters are the map's vocabulary, not a name the
|
|
116
|
+
* author gives one force.
|
|
117
|
+
*/
|
|
118
|
+
export const PORTER_SIZE = 60;
|
|
119
|
+
/**
|
|
120
|
+
* The four arrows, read by `wardleyPorterArrows`: each starts `gap` outside the
|
|
121
|
+
* circle's rim and runs `length` further out, a `width`-wide shaft carrying a
|
|
122
|
+
* head `headLength` long and `headWidth` across.
|
|
123
|
+
*
|
|
124
|
+
* They are drawn as filled POLYGONS rather than as connectors with a triangle
|
|
125
|
+
* endpoint, and the recette of #210 is why: a connector sizes its head off its
|
|
126
|
+
* STROKE WIDTH (`DEFAULT_ARROW_SIZE` × strokeWidth / 2), which is a number about
|
|
127
|
+
* a line rather than about this notation — the head came out longer than the
|
|
128
|
+
* arrow, swallowed the circle, the letter and the double-click, and the glyph
|
|
129
|
+
* rendered as a solid red star. A polygon's head is the number written here and
|
|
130
|
+
* nothing else, at any size.
|
|
131
|
+
*/
|
|
132
|
+
export const PORTER_ARROW = {
|
|
133
|
+
gap: 6,
|
|
134
|
+
length: 40,
|
|
135
|
+
/** Shaft width. */
|
|
136
|
+
width: 8,
|
|
137
|
+
headLength: 16,
|
|
138
|
+
headWidth: 24,
|
|
139
|
+
};
|
|
140
|
+
/** The letter, sized to sit inside the circle without touching the rim. */
|
|
141
|
+
export const PORTER_LETTER_FONT_SIZE = 28;
|
|
142
|
+
/** The letter a fresh glyph opens on: relative competition, the commonest read. */
|
|
143
|
+
export const PORTER_DEFAULT_LETTER = 'R';
|
|
144
|
+
/**
|
|
145
|
+
* Accelerators and decelerators — the climate annotations.
|
|
146
|
+
*
|
|
147
|
+
* A fat solid arrow with a flat grey fill and a thick dark border: as close as
|
|
148
|
+
* a native polygon gets to the reference's solid arrow, because this canvas has
|
|
149
|
+
* no gradient fill. The two are the same drawing mirrored — an accelerator
|
|
150
|
+
* points RIGHT, towards the commodity end of the evolution axis, a decelerator
|
|
151
|
+
* points LEFT, back towards genesis — and that direction IS the notation, which
|
|
152
|
+
* is why the outlines are written out below rather than expressed as one shape
|
|
153
|
+
* plus a `rotate`: a rotated element is one the selection, the resize handles
|
|
154
|
+
* and every bounding-box reader then have to de-rotate.
|
|
155
|
+
*
|
|
156
|
+
* Neither is a link in the value chain, so neither carries a component role
|
|
157
|
+
* (see `roles.ts`): they say something about the CLIMATE the map is drawn in.
|
|
158
|
+
*/
|
|
159
|
+
export const ACCELERATOR_SIZE = { w: 48, h: 40 };
|
|
160
|
+
/** Flat grey: the arrow is a annotation, and must not out-shout the chain. */
|
|
161
|
+
export const ACCELERATOR_FILL = '#bfbfbf';
|
|
162
|
+
/** A thick dark border — `NODE_STROKE`, like every other artefact's rim. */
|
|
163
|
+
export const ACCELERATOR_STROKE_WIDTH = 2;
|
|
164
|
+
/**
|
|
165
|
+
* The accelerator's outline, normalized to its box, pointing RIGHT.
|
|
166
|
+
*
|
|
167
|
+
* Seven points: the shaft's two left corners, the head's upper barb, the tip,
|
|
168
|
+
* the head's lower barb, and back along the shaft. `0.55` is where the head
|
|
169
|
+
* starts, `0.28 / 0.72` the shaft's own edges.
|
|
170
|
+
*/
|
|
171
|
+
export const ACCELERATOR_VERTICES = [
|
|
172
|
+
[0, 0.28],
|
|
173
|
+
[0.55, 0.28],
|
|
174
|
+
[0.55, 0],
|
|
175
|
+
[1, 0.5],
|
|
176
|
+
[0.55, 1],
|
|
177
|
+
[0.55, 0.72],
|
|
178
|
+
[0, 0.72],
|
|
179
|
+
];
|
|
180
|
+
/** The decelerator: the same outline mirrored `x → 1 − x`, pointing LEFT. */
|
|
181
|
+
export const DECELERATOR_VERTICES = ACCELERATOR_VERTICES.map(([x, y]) => [1 - x, y]);
|
|
182
|
+
export const ACCELERATOR_LABEL = 'Accelerator';
|
|
183
|
+
export const DECELERATOR_LABEL = 'Decelerator';
|
|
184
|
+
/**
|
|
185
|
+
* Areas — the zones of the map.
|
|
186
|
+
*
|
|
187
|
+
* A ring you draw AROUND components to say "all of this is one thing": a
|
|
188
|
+
* business unit, a team's territory, a scope under discussion. It is not a link
|
|
189
|
+
* in the value chain and not an annotation of the climate either, which is why
|
|
190
|
+
* it carries a role with no parent and sits in a category of its own in the
|
|
191
|
+
* toolbox.
|
|
192
|
+
*
|
|
193
|
+
* Two shapes, one kind: a RECT for the common case and a POLYGON for a zone
|
|
194
|
+
* whose outline has to follow the components it groups (its corners are then
|
|
195
|
+
* moved from the shape toolbar's vertex editor). The `shapeType` is what says
|
|
196
|
+
* which, so nothing about an area is a second kind to keep in step.
|
|
197
|
+
*
|
|
198
|
+
* A zone is drawn OVER the map it groups, so the fill is the load-bearing
|
|
199
|
+
* number here: Peace light at ~25 % opacity, which is a wash the components
|
|
200
|
+
* underneath still read through. The border is the saturated Peace of the same
|
|
201
|
+
* swatch, thin like every other artefact's rim — the zone must frame the map,
|
|
202
|
+
* never compete with it.
|
|
203
|
+
*/
|
|
204
|
+
export const AREA_RECT_SIZE = { w: 240, h: 160 };
|
|
205
|
+
/** The polygon is born square: its default outline is a regular pentagon. */
|
|
206
|
+
export const AREA_POLYGON_SIZE = { w: 200, h: 200 };
|
|
207
|
+
/** Peace light at ~25 % opacity — an 8-digit hex, exactly as `PIPELINE_FILL`. */
|
|
208
|
+
export const AREA_FILL = '#c6dbfc40';
|
|
209
|
+
/** Peace, the saturated shade of the same swatch. */
|
|
210
|
+
export const AREA_STROKE = '#5b9cf6';
|
|
211
|
+
export const AREA_STROKE_WIDTH = 1;
|
|
212
|
+
/**
|
|
213
|
+
* The word the legend describes an area by.
|
|
214
|
+
*
|
|
215
|
+
* NOT written into the document: an area is created NAMELESS, so the editor
|
|
216
|
+
* that a double-click opens starts on an empty line and the first thing typed
|
|
217
|
+
* is the zone's own name. The other artefacts carry a placeholder because their
|
|
218
|
+
* name is a text element beside them and an empty one would be invisible; an
|
|
219
|
+
* area's name is its own inner text, and a placeholder there would be a word
|
|
220
|
+
* the author has to delete before writing theirs.
|
|
221
|
+
*/
|
|
222
|
+
export const AREA_LABEL = 'Area';
|
package/dist/node/node-view.d.ts
CHANGED
|
@@ -1,10 +1,88 @@
|
|
|
1
|
+
import { ShapeElementView } from '@formicoidea/labre-core/gfx/shape';
|
|
1
2
|
import type { WardleyNodeElementModel } from '@formicoidea/labre-core/model';
|
|
2
|
-
import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
|
|
3
3
|
/**
|
|
4
4
|
* View for a Wardley node. Registering it ensures `gfx.view.get(model)` returns
|
|
5
|
-
* a view (required so move / select / connector interactions work).
|
|
6
|
-
*
|
|
5
|
+
* a view (required so move / select / connector interactions work).
|
|
6
|
+
*
|
|
7
|
+
* ## It extends `ShapeElementView`, and that is what makes "Edit vertices" work
|
|
8
|
+
*
|
|
9
|
+
* A Wardley node IS a `ShapeElementModel`, and the toolbar's vertex-editing
|
|
10
|
+
* action ends in `if (view instanceof ShapeElementView) view.enterVertexEditingMode()`.
|
|
11
|
+
* While this view extended `GfxElementModelView` directly, that check failed
|
|
12
|
+
* silently: the button was on the row, it was enabled, and clicking it did
|
|
13
|
+
* nothing at all — found by the recette of #213 on an area polygon, whose
|
|
14
|
+
* corners are the whole reason to draw one.
|
|
15
|
+
*
|
|
16
|
+
* Extending the shape view brings TWO wirings, and this framework wants exactly
|
|
17
|
+
* one of each. Both are hooks on the base rather than things to dodge by
|
|
18
|
+
* skipping `onCreated`, because skipping it would drop the wiring that is
|
|
19
|
+
* wanted along with the wiring that is not.
|
|
20
|
+
*
|
|
21
|
+
* ## Double-clicking edits the inner text — on the TWO kinds that have one
|
|
22
|
+
*
|
|
23
|
+
* Every other Wardley artefact wears its name as a SEPARATE canvas text element
|
|
24
|
+
* grouped beside it, so a double-click on the circle has nothing of its own to
|
|
25
|
+
* open: the words live next door and are edited by double-clicking them, which
|
|
26
|
+
* is `TextElementView`'s job and not this one's. Those kinds are deliberately
|
|
27
|
+
* left alone.
|
|
28
|
+
*
|
|
29
|
+
* The `porter` is the first exception, and it is the notation that makes it
|
|
30
|
+
* one. A Porter's-forces glyph carries a LETTER — R relative competition, L
|
|
31
|
+
* struggle for survival, E struggle to establish — and that letter is the
|
|
32
|
+
* shape's own inner text, because it is one character in the middle of the
|
|
33
|
+
* circle rather than a name written alongside it. So the gesture everybody in
|
|
34
|
+
* this editor already makes has to land: `mountShapeTextEditor`, the same call
|
|
35
|
+
* EDGY's and BPMN's node views make on their own inner text.
|
|
36
|
+
*
|
|
37
|
+
* Found by the recette of #210: the view had no handler at all and the node
|
|
38
|
+
* toolbar offers no "add text" action, which left the letter unreachable — a
|
|
39
|
+
* glyph whose whole meaning is one character nobody could change. The four
|
|
40
|
+
* arrows are polygons OUTSIDE the rim (`presets.ts`), so the double-click at
|
|
41
|
+
* the centre reaches this circle rather than a connector's oversized head.
|
|
42
|
+
*
|
|
43
|
+
* The `area` is the second, and for a different reason: a zone's name belongs
|
|
44
|
+
* INSIDE the zone rather than beside it — a label parked outside a boundary
|
|
45
|
+
* would name whatever else happens to be there. It is created with no text at
|
|
46
|
+
* all, so the double-click opens the editor on an empty line and the first
|
|
47
|
+
* thing typed is the zone's name (`mountShapeTextEditor` mints the `Y.Text`).
|
|
48
|
+
*
|
|
49
|
+
* ## …and only on the artefact, never on its wiring
|
|
50
|
+
*
|
|
51
|
+
* The four arrows are `wardleyNode`s too — `kind: 'porter'`, ROLE-LESS, the
|
|
52
|
+
* market's three inner dots exactly — so `kind` alone would hand each of them
|
|
53
|
+
* the same editor. The role is what separates the artefact the author placed
|
|
54
|
+
* from the glyph's own wiring, so it is the second half of the gate, and it is
|
|
55
|
+
* the half that also makes the gate safe to extend: a kind is admitted with the
|
|
56
|
+
* role that proves the element is the artefact rather than a part of one.
|
|
57
|
+
*
|
|
58
|
+
* That pair is also why they are `wardleyNode`s at all. Recette v2 found them
|
|
59
|
+
* as plain shapes, and `ShapeElementView` gives EVERY plain shape a
|
|
60
|
+
* double-click that mounts the inner-text editor: an arrow grew from 24 units
|
|
61
|
+
* high to 44 the moment one was opened, and the deformation survived Escape. An
|
|
62
|
+
* arrow is not a thing you write in, and this view is where that is said.
|
|
63
|
+
*
|
|
64
|
+
* ## Vertices move on the AREA and nowhere else
|
|
65
|
+
*
|
|
66
|
+
* The other wiring. Every other polygon this framework draws has an outline
|
|
67
|
+
* that IS the notation — an accelerator points right and a decelerator points
|
|
68
|
+
* left, and a porter's arrow is the glyph's own wiring — so a handle on one can
|
|
69
|
+
* only turn a statement into a blob. A zone's outline is the opposite: it
|
|
70
|
+
* exists to follow the components it groups. Declining the hook on every other
|
|
71
|
+
* kind skips the overlay, the pointer wiring and the toolbar entry together,
|
|
72
|
+
* which is the same answer the toolbar's own `when` gives.
|
|
7
73
|
*/
|
|
8
|
-
export declare class WardleyNodeView extends
|
|
74
|
+
export declare class WardleyNodeView extends ShapeElementView<WardleyNodeElementModel> {
|
|
9
75
|
static type: string;
|
|
76
|
+
/**
|
|
77
|
+
* Only the porter CIRCLE and the area. The kind alone is not enough: the
|
|
78
|
+
* porter's four arrows share its kind and are told apart by carrying no role,
|
|
79
|
+
* exactly as the market's inner dots are — hence a table of kind → the role
|
|
80
|
+
* the ARTEFACT carries, and an element that fails to match is either a glyph's
|
|
81
|
+
* own wiring or a kind whose name is the text element beside it. Opening the
|
|
82
|
+
* shape's own editor on those would write a second, invisible name into a
|
|
83
|
+
* field that must stay empty.
|
|
84
|
+
*/
|
|
85
|
+
protected editsTextOnDblClick(): boolean;
|
|
86
|
+
/** The zone, and nothing else this framework draws as a polygon. */
|
|
87
|
+
protected editsVerticesOnSelection(): boolean;
|
|
10
88
|
}
|
package/dist/node/node-view.js
CHANGED
|
@@ -1,9 +1,113 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ShapeElementView } from '@formicoidea/labre-core/gfx/shape';
|
|
2
|
+
import { WARDLEY_ROLE } from '../roles.js';
|
|
3
|
+
/**
|
|
4
|
+
* The kinds whose NAME is the shape's own inner text, each paired with the role
|
|
5
|
+
* an element of that kind carries when it is the artefact itself.
|
|
6
|
+
*
|
|
7
|
+
* A pair and not a list, because `kind` alone admits too much: the porter's
|
|
8
|
+
* four arrows are `wardleyNode`s of kind `porter` carrying no role at all, and
|
|
9
|
+
* a gate on the kind would hand each of them an editor for text they must never
|
|
10
|
+
* have. Anything not in this table is a kind that wears its name as a text
|
|
11
|
+
* element beside it, and its own inner text must stay empty.
|
|
12
|
+
*/
|
|
13
|
+
const NAMES_ITSELF = {
|
|
14
|
+
porter: WARDLEY_ROLE.porter,
|
|
15
|
+
area: WARDLEY_ROLE.area,
|
|
16
|
+
};
|
|
2
17
|
/**
|
|
3
18
|
* View for a Wardley node. Registering it ensures `gfx.view.get(model)` returns
|
|
4
|
-
* a view (required so move / select / connector interactions work).
|
|
5
|
-
*
|
|
19
|
+
* a view (required so move / select / connector interactions work).
|
|
20
|
+
*
|
|
21
|
+
* ## It extends `ShapeElementView`, and that is what makes "Edit vertices" work
|
|
22
|
+
*
|
|
23
|
+
* A Wardley node IS a `ShapeElementModel`, and the toolbar's vertex-editing
|
|
24
|
+
* action ends in `if (view instanceof ShapeElementView) view.enterVertexEditingMode()`.
|
|
25
|
+
* While this view extended `GfxElementModelView` directly, that check failed
|
|
26
|
+
* silently: the button was on the row, it was enabled, and clicking it did
|
|
27
|
+
* nothing at all — found by the recette of #213 on an area polygon, whose
|
|
28
|
+
* corners are the whole reason to draw one.
|
|
29
|
+
*
|
|
30
|
+
* Extending the shape view brings TWO wirings, and this framework wants exactly
|
|
31
|
+
* one of each. Both are hooks on the base rather than things to dodge by
|
|
32
|
+
* skipping `onCreated`, because skipping it would drop the wiring that is
|
|
33
|
+
* wanted along with the wiring that is not.
|
|
34
|
+
*
|
|
35
|
+
* ## Double-clicking edits the inner text — on the TWO kinds that have one
|
|
36
|
+
*
|
|
37
|
+
* Every other Wardley artefact wears its name as a SEPARATE canvas text element
|
|
38
|
+
* grouped beside it, so a double-click on the circle has nothing of its own to
|
|
39
|
+
* open: the words live next door and are edited by double-clicking them, which
|
|
40
|
+
* is `TextElementView`'s job and not this one's. Those kinds are deliberately
|
|
41
|
+
* left alone.
|
|
42
|
+
*
|
|
43
|
+
* The `porter` is the first exception, and it is the notation that makes it
|
|
44
|
+
* one. A Porter's-forces glyph carries a LETTER — R relative competition, L
|
|
45
|
+
* struggle for survival, E struggle to establish — and that letter is the
|
|
46
|
+
* shape's own inner text, because it is one character in the middle of the
|
|
47
|
+
* circle rather than a name written alongside it. So the gesture everybody in
|
|
48
|
+
* this editor already makes has to land: `mountShapeTextEditor`, the same call
|
|
49
|
+
* EDGY's and BPMN's node views make on their own inner text.
|
|
50
|
+
*
|
|
51
|
+
* Found by the recette of #210: the view had no handler at all and the node
|
|
52
|
+
* toolbar offers no "add text" action, which left the letter unreachable — a
|
|
53
|
+
* glyph whose whole meaning is one character nobody could change. The four
|
|
54
|
+
* arrows are polygons OUTSIDE the rim (`presets.ts`), so the double-click at
|
|
55
|
+
* the centre reaches this circle rather than a connector's oversized head.
|
|
56
|
+
*
|
|
57
|
+
* The `area` is the second, and for a different reason: a zone's name belongs
|
|
58
|
+
* INSIDE the zone rather than beside it — a label parked outside a boundary
|
|
59
|
+
* would name whatever else happens to be there. It is created with no text at
|
|
60
|
+
* all, so the double-click opens the editor on an empty line and the first
|
|
61
|
+
* thing typed is the zone's name (`mountShapeTextEditor` mints the `Y.Text`).
|
|
62
|
+
*
|
|
63
|
+
* ## …and only on the artefact, never on its wiring
|
|
64
|
+
*
|
|
65
|
+
* The four arrows are `wardleyNode`s too — `kind: 'porter'`, ROLE-LESS, the
|
|
66
|
+
* market's three inner dots exactly — so `kind` alone would hand each of them
|
|
67
|
+
* the same editor. The role is what separates the artefact the author placed
|
|
68
|
+
* from the glyph's own wiring, so it is the second half of the gate, and it is
|
|
69
|
+
* the half that also makes the gate safe to extend: a kind is admitted with the
|
|
70
|
+
* role that proves the element is the artefact rather than a part of one.
|
|
71
|
+
*
|
|
72
|
+
* That pair is also why they are `wardleyNode`s at all. Recette v2 found them
|
|
73
|
+
* as plain shapes, and `ShapeElementView` gives EVERY plain shape a
|
|
74
|
+
* double-click that mounts the inner-text editor: an arrow grew from 24 units
|
|
75
|
+
* high to 44 the moment one was opened, and the deformation survived Escape. An
|
|
76
|
+
* arrow is not a thing you write in, and this view is where that is said.
|
|
77
|
+
*
|
|
78
|
+
* ## Vertices move on the AREA and nowhere else
|
|
79
|
+
*
|
|
80
|
+
* The other wiring. Every other polygon this framework draws has an outline
|
|
81
|
+
* that IS the notation — an accelerator points right and a decelerator points
|
|
82
|
+
* left, and a porter's arrow is the glyph's own wiring — so a handle on one can
|
|
83
|
+
* only turn a statement into a blob. A zone's outline is the opposite: it
|
|
84
|
+
* exists to follow the components it groups. Declining the hook on every other
|
|
85
|
+
* kind skips the overlay, the pointer wiring and the toolbar entry together,
|
|
86
|
+
* which is the same answer the toolbar's own `when` gives.
|
|
6
87
|
*/
|
|
7
|
-
export class WardleyNodeView extends
|
|
88
|
+
export class WardleyNodeView extends ShapeElementView {
|
|
8
89
|
static { this.type = 'wardleyNode'; }
|
|
90
|
+
/**
|
|
91
|
+
* Only the porter CIRCLE and the area. The kind alone is not enough: the
|
|
92
|
+
* porter's four arrows share its kind and are told apart by carrying no role,
|
|
93
|
+
* exactly as the market's inner dots are — hence a table of kind → the role
|
|
94
|
+
* the ARTEFACT carries, and an element that fails to match is either a glyph's
|
|
95
|
+
* own wiring or a kind whose name is the text element beside it. Opening the
|
|
96
|
+
* shape's own editor on those would write a second, invisible name into a
|
|
97
|
+
* field that must stay empty.
|
|
98
|
+
*/
|
|
99
|
+
editsTextOnDblClick() {
|
|
100
|
+
// A read-only document is one nobody names anything on. The base view has
|
|
101
|
+
// no such check — a plain shape's editor is harmless there — but this gate
|
|
102
|
+
// is the whole of what a Wardley node offers, so it keeps the guard the
|
|
103
|
+
// hand-written handler carried.
|
|
104
|
+
if (this.std.store.readonly)
|
|
105
|
+
return false;
|
|
106
|
+
const named = NAMES_ITSELF[this.model.kind];
|
|
107
|
+
return named !== undefined && this.model.role === named;
|
|
108
|
+
}
|
|
109
|
+
/** The zone, and nothing else this framework draws as a polygon. */
|
|
110
|
+
editsVerticesOnSelection() {
|
|
111
|
+
return this.model.kind === 'area';
|
|
112
|
+
}
|
|
9
113
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { InteractivityExtension } from '@formicoidea/labre-core/std/gfx';
|
|
2
|
+
/**
|
|
3
|
+
* Keeps a Porter's-forces glyph saying ONE thing: the letter in the circle and
|
|
4
|
+
* the `wardley:competition` tag on it are two spellings of the same statement.
|
|
5
|
+
*
|
|
6
|
+
* ## Why there are two spellings at all
|
|
7
|
+
*
|
|
8
|
+
* The letter is the NOTATION — an architect reads `L` off the map and knows
|
|
9
|
+
* what it says, and typing another letter is the whole of the edit
|
|
10
|
+
* (`node-view.ts`). The tag is the FACT — the half a rule, a reading or a host
|
|
11
|
+
* report can query, because nothing can ask "which forces does this map name?"
|
|
12
|
+
* of a `Y.Text` holding one character. Dropping either would cost something
|
|
13
|
+
* real, so both exist.
|
|
14
|
+
*
|
|
15
|
+
* ## The invariant: the tag is DERIVED from the letter
|
|
16
|
+
*
|
|
17
|
+
* The two are not symmetric, and the recette of #211 is what made the asymmetry
|
|
18
|
+
* necessary. The letter is what the author writes; the tag is a reading of it,
|
|
19
|
+
* recomputed on EVERY local change of the text and written as a consequence of
|
|
20
|
+
* that change rather than as a step of its own. So `tag = f(text)` holds at
|
|
21
|
+
* every instant, including at every instant an undo or a redo passes through.
|
|
22
|
+
*
|
|
23
|
+
* That is the fix for the defect the recette found. Reconciling only when the
|
|
24
|
+
* editor closed left the two able to disagree: the inline editor writes a
|
|
25
|
+
* select-all-and-retype as several Yjs operations, each its own undo entry,
|
|
26
|
+
* while the tag was one further entry captured after all of them. Undoing then
|
|
27
|
+
* landed on an intermediate text — `RR` — carrying the tag of the text that
|
|
28
|
+
* came after it, and redoing past the tag's own entry re-ran the reconciliation
|
|
29
|
+
* against a text that had already moved on, so the document never returned to
|
|
30
|
+
* where it started.
|
|
31
|
+
*
|
|
32
|
+
* The reading is taken on a MICROTASK after the change, and written UNTRACKED
|
|
33
|
+
* (`Store.withoutTransact`, an origin the undo manager ignores). So the undo
|
|
34
|
+
* stack holds the author's keystrokes and nothing else, and whatever text a
|
|
35
|
+
* step lands on — `E`, the intermediate `RR`, the original `R` — the tag is
|
|
36
|
+
* recomputed from it a tick later. There is no entry to get out of order,
|
|
37
|
+
* because the qualification is not an entry.
|
|
38
|
+
*
|
|
39
|
+
* ## Tag → letter, and why it is the quiet direction
|
|
40
|
+
*
|
|
41
|
+
* A menu pick is the one gesture that starts from the tag, so it — and only it
|
|
42
|
+
* — redraws the letter. Two guards keep that from becoming a second author:
|
|
43
|
+
*
|
|
44
|
+
* - It compares the two SEMANTICALLY (`competitionOfPorterLetter(raw)`), never
|
|
45
|
+
* character by character. A circle reading `e` already says "struggle to
|
|
46
|
+
* establish", so a tag derived from it must not turn round and rewrite it to
|
|
47
|
+
* `E`. Without this the two directions would trade writes on every keystroke.
|
|
48
|
+
* - It never runs while the id is in the EDITING set. That is C4's lesson
|
|
49
|
+
* (`C4TypeLineWatcher`): rewriting the text an author has their caret in
|
|
50
|
+
* throws that caret, and no letter is worth it.
|
|
51
|
+
*
|
|
52
|
+
* The one deliberate asymmetry: a CLEARED tag leaves the circle alone. Removing
|
|
53
|
+
* a value from the menu is a statement about the qualification — "I no longer
|
|
54
|
+
* claim which force this is" — and not about a map somebody is still reading.
|
|
55
|
+
* The tie is re-tied by the next edit of the letter, which is the only gesture
|
|
56
|
+
* that ever asserts one.
|
|
57
|
+
*
|
|
58
|
+
* ## `local`, and always-on
|
|
59
|
+
*
|
|
60
|
+
* Both directions are LOCAL-only, for `EdgyRelationResolver`'s reason: without
|
|
61
|
+
* it every peer on a shared board would derive the same tag the moment a letter
|
|
62
|
+
* synced, giving N−1 of them an entry for a gesture they did not make.
|
|
63
|
+
*
|
|
64
|
+
* Registered by the RENDER half rather than the flag-gated one
|
|
65
|
+
* (`docs/adr/0009`): this creates no element, offers no button and adds no
|
|
66
|
+
* artefact to any map. It keeps an element ALREADY in the document coherent
|
|
67
|
+
* with itself, which a force drawn while the Wardley button was on must stay
|
|
68
|
+
* when the button goes off.
|
|
69
|
+
*/
|
|
70
|
+
export declare class WardleyPorterWatcher extends InteractivityExtension {
|
|
71
|
+
static key: string;
|
|
72
|
+
private _selection;
|
|
73
|
+
private _updated;
|
|
74
|
+
private _disposeSurface;
|
|
75
|
+
private _editing;
|
|
76
|
+
private _pending;
|
|
77
|
+
private _scheduled;
|
|
78
|
+
private _mounted;
|
|
79
|
+
mounted(): void;
|
|
80
|
+
unmounted(): void;
|
|
81
|
+
/** The porter this id names, when it is one this peer may rewrite. */
|
|
82
|
+
private _porter;
|
|
83
|
+
/**
|
|
84
|
+
* Queue a re-reading of the letter, for the end of the current task.
|
|
85
|
+
*
|
|
86
|
+
* A MICROTASK, and it is load-bearing rather than cosmetic. `watchText` fires
|
|
87
|
+
* from inside the Yjs transaction that changed the text, and at that instant
|
|
88
|
+
* the element's own `Y.Map` observer may not have run yet — so `element.tags`
|
|
89
|
+
* can still resolve, through `_preserved`, to a nested map the very same
|
|
90
|
+
* transaction has just deleted. A tag written into that map goes nowhere at
|
|
91
|
+
* all: the recette of #211 saw exactly that, as an undo landing on a letter
|
|
92
|
+
* whose force had silently failed to follow it.
|
|
93
|
+
*
|
|
94
|
+
* Deferring past the transaction settles two things at once. The document is
|
|
95
|
+
* consistent when the reading is taken, and the write lands OUTSIDE any
|
|
96
|
+
* tracked transaction — so {@link Store.withoutTransact} really does keep it
|
|
97
|
+
* off the undo stack, and the tag becomes what it claims to be: a pure
|
|
98
|
+
* function of the text, recomputed after every change of it, in either
|
|
99
|
+
* direction of history, and never a step an author has to undo.
|
|
100
|
+
*/
|
|
101
|
+
private _scheduleDerive;
|
|
102
|
+
/** The letter as it now stands, written down as the fact it states. */
|
|
103
|
+
private _deriveTagFromText;
|
|
104
|
+
/** The value just picked from the menu, drawn as the letter it stands for. */
|
|
105
|
+
private _drawLetterFromTag;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The ids currently being EDITED, out of a selection update.
|
|
109
|
+
*
|
|
110
|
+
* A pure function because it is the whole of what the watcher needs to know
|
|
111
|
+
* about the selection: a text editor holds an editing selection for as long as
|
|
112
|
+
* it is mounted and drops it when it closes, so this set is "whose caret is in
|
|
113
|
+
* something right now" and nothing else. Total over an empty selection, which
|
|
114
|
+
* is what arrives when an editor closes onto bare canvas.
|
|
115
|
+
*/
|
|
116
|
+
export declare function wardleyEditingIds(selections: readonly {
|
|
117
|
+
elements: readonly string[];
|
|
118
|
+
editing?: boolean;
|
|
119
|
+
}[]): Set<string>;
|