@formicoidea/labre-framework-wardley 0.41.0 → 0.42.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.
@@ -1,147 +1,31 @@
1
- import { backgroundLabelHits, EdgelessCRUDIdentifier, FrameworkBackgroundInteractionExtension, hitTestBackgroundLabel, } from '@formicoidea/labre-core/blocks/surface';
2
- import { TranslationProvider } from '@formicoidea/labre-core/shared/services';
3
- import { rotatePoint } from '@formicoidea/labre-core/global/gfx';
4
- import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
5
- import { isWardleyLabelProp, WARDLEY_BACKGROUND, } from './background.js';
6
- export class WardleyView extends GfxElementModelView {
7
- constructor() {
8
- super(...arguments);
9
- /** The in-place `<input>` used to edit a label, or null when idle. */
10
- this._labelEditor = null;
11
- }
1
+ import { DeclaredBackgroundView, FrameworkBackgroundInteractionExtension, } from '@formicoidea/labre-core/blocks/surface';
2
+ import { isWardleyLabelProp, WARDLEY_BACKGROUND } from './background.js';
3
+ /**
4
+ * The Wardley map's view: the shared background gesture, aimed by the
5
+ * `WARDLEY_BACKGROUND` declaration.
6
+ *
7
+ * The in-place rename shipped here first and has moved to
8
+ * `DeclaredBackgroundView` (`@labre/affine-block-surface`) so that every
9
+ * framework background gets it rather than the two that got round to copying
10
+ * it — see issue #355. Nothing about the map's behaviour changed: the hit test
11
+ * reads the same declaration, through the same catalogue, and the guard below
12
+ * is the one thing this framework adds to it.
13
+ */
14
+ export class WardleyView extends DeclaredBackgroundView {
12
15
  static { this.type = 'wardley'; }
13
- onCreated() {
14
- super.onCreated();
15
- this.on('dblclick', e => this._onDblClick(e));
16
- }
17
- onDestroyed() {
18
- this._closeLabelEditor();
19
- super.onDestroyed();
16
+ get def() {
17
+ return WARDLEY_BACKGROUND;
20
18
  }
21
19
  /**
22
- * The editable label under a MODEL-space point, or null.
20
+ * The declaration names the prop; this decides whether it may be WRITTEN.
23
21
  *
24
- * Which labels exist, where they sit, what they SAY and which are editable
25
- * all come from the declaration the renderer paints — one source, resolved
26
- * through the same catalogue, so a label can never be drawn in one place
27
- * and clicked in another, nor read one thing and open on another.
22
+ * A closed list, because since #73 an element preserves keys it does not
23
+ * declare: a typo in the declaration would otherwise persist a junk key onto
24
+ * every map it was double-clicked on.
28
25
  */
29
- _labelAt(mx, my) {
30
- const [bx, by, w, h] = this.model.deserializedXYWH;
31
- // Convert the model-space point into element-local coordinates, undoing the
32
- // element rotation around its center.
33
- let lx = mx - bx;
34
- let ly = my - by;
35
- const rot = this.model.rotate ?? 0;
36
- if (rot) {
37
- const center = [bx + w / 2, by + h / 2];
38
- const [ux, uy] = rotatePoint([mx, my], center, -rot);
39
- lx = ux - bx;
40
- ly = uy - by;
41
- }
42
- const hit = hitTestBackgroundLabel(backgroundLabelHits(WARDLEY_BACKGROUND, this.model, w, h, this.gfx.std.getOptional(TranslationProvider)), lx, ly);
43
- // The declaration names the prop; this decides whether it may be written.
44
- if (!hit || !isWardleyLabelProp(hit.prop))
45
- return null;
46
- return { prop: hit.prop, text: hit.text };
47
- }
48
- /**
49
- * The map is SELECTED by its border (`WardleyBackgroundElementModel`), but
50
- * its axis labels must still receive the double-click that renames them.
51
- *
52
- * So the two areas differ, and this is where they are allowed to: the pointer
53
- * router asks the VIEW (`GfxViewEventManager`), and the view adds the zones
54
- * the declaration draws its labels in. Picking is unaffected —
55
- * `getElementByPoint` still asks the model, so a click in the middle of the
56
- * map still goes to whatever the user put there.
57
- */
58
- includesPoint(x, y, options, host) {
59
- if (super.includesPoint(x, y, options, host))
60
- return true;
61
- return this._labelAt(x, y) !== null;
62
- }
63
- /** Double-click on a label → edit its text in place. */
64
- _onDblClick(e) {
65
- if (this.model.isLocked())
66
- return;
67
- const [mx, my] = this.gfx.viewport.toModelCoord(e.x, e.y);
68
- const hit = this._labelAt(mx, my);
69
- if (!hit)
70
- return;
71
- this._openLabelEditor(hit.prop, hit.text, e);
72
- }
73
- /**
74
- * @param current the words currently DRAWN — which is the vocabulary, not
75
- * `model[field]`, for a label the user has never renamed. Opening on the raw
76
- * prop would show an empty box for a label that plainly reads "Evolution".
77
- */
78
- _openLabelEditor(field, current, e) {
79
- this._closeLabelEditor();
80
- const input = document.createElement('input');
81
- input.value = current;
82
- Object.assign(input.style, {
83
- position: 'fixed',
84
- left: `${e.raw.clientX}px`,
85
- top: `${e.raw.clientY}px`,
86
- transform: 'translate(-50%, -50%)',
87
- zIndex: '10000',
88
- minWidth: '140px',
89
- padding: '3px 8px',
90
- font: '14px Inter, sans-serif',
91
- color: 'var(--affine-text-primary-color, #1f2328)',
92
- background: 'var(--affine-background-overlay-panel-color, #ffffff)',
93
- border: '1px solid var(--affine-primary-color, #1e96eb)',
94
- borderRadius: '6px',
95
- boxShadow: 'var(--affine-shadow-2, 0 2px 8px rgba(0,0,0,0.18))',
96
- outline: 'none',
97
- });
98
- document.body.append(input);
99
- this._labelEditor = input;
100
- // Mark the element as "editing" so the global edgeless key handlers
101
- // (delete, escape, etc.) don't act on it while the user types.
102
- this.gfx.selection.set({ elements: [this.model.id], editing: true });
103
- input.focus();
104
- input.select();
105
- const commit = () => {
106
- // Guard against re-entrancy: removing the input fires `blur`, which would
107
- // otherwise call `commit` a second time.
108
- if (this._labelEditor !== input)
109
- return;
110
- const value = input.value;
111
- this._closeLabelEditor();
112
- // Opening an editor is not renaming. Writing back an untouched value
113
- // would persist the resolved VOCABULARY as the user's own text, freezing
114
- // the label in whatever language it was read in and putting it beyond
115
- // any catalogue for good — and it would push an empty entry onto undo.
116
- if (value === current)
117
- return;
118
- this.gfx.std.store.captureSync();
119
- this.gfx.std
120
- .get(EdgelessCRUDIdentifier)
121
- .updateElement(this.model.id, { [field]: value });
122
- };
123
- input.addEventListener('keydown', ev => {
124
- ev.stopPropagation();
125
- if (ev.key === 'Enter') {
126
- ev.preventDefault();
127
- commit();
128
- }
129
- else if (ev.key === 'Escape') {
130
- ev.preventDefault();
131
- this._closeLabelEditor();
132
- }
133
- });
134
- input.addEventListener('blur', commit);
135
- }
136
- _closeLabelEditor() {
137
- if (!this._labelEditor)
138
- return;
139
- const input = this._labelEditor;
140
- this._labelEditor = null;
141
- input.remove();
142
- if (this.isConnected) {
143
- this.gfx.selection.set({ elements: [this.model.id], editing: false });
144
- }
26
+ labelAt(lx, ly, w, h) {
27
+ const hit = super.labelAt(lx, ly, w, h);
28
+ return hit && isWardleyLabelProp(hit.prop) ? hit : null;
145
29
  }
146
30
  }
147
31
  /**
package/dist/index.d.ts CHANGED
@@ -27,4 +27,5 @@ export { WARDLEY_BACKGROUND } from './background.js';
27
27
  */
28
28
  export { exportWardleyOwm, exportWardleyOwmWithWarnings, OWM_SCOPE, type OwmPlot, owmCoordsOf, owmPlotOf, owmPointOf, WARDLEY_OWM_FORMAT_ID, type WardleyExportBoard, wardleyBoardFrom, wardleySafeFilename, } from './export.js';
29
29
  export { importWardleyOwm } from './import.js';
30
+ export { wardleyBoardOf, wardleyExportElementsOf, wardleyMapsSelected, } from './actions.js';
30
31
  export { WARDLEY_INTERCHANGE, WARDLEY_OWM_EXPORT, WARDLEY_OWM_EXTENSION, WARDLEY_OWM_FORMAT, WARDLEY_OWM_IMPORT, WARDLEY_OWM_MIME, WARDLEY_SVG_FORMAT, WARDLEY_SVG_IMPORT, } from './interchange.js';
package/dist/index.js CHANGED
@@ -39,6 +39,10 @@ export { WARDLEY_BACKGROUND } from './background.js';
39
39
  */
40
40
  export { exportWardleyOwm, exportWardleyOwmWithWarnings, OWM_SCOPE, owmCoordsOf, owmPlotOf, owmPointOf, WARDLEY_OWM_FORMAT_ID, wardleyBoardFrom, wardleySafeFilename, } from './export.js';
41
41
  export { importWardleyOwm } from './import.js';
42
+ // The half of the export that needs an editor, kept apart from the half that
43
+ // does not — the same split C4's and BPMN's indexes make for the same reason:
44
+ // what the selection scopes the file to lives here, the serializer next door.
45
+ export { wardleyBoardOf, wardleyExportElementsOf, wardleyMapsSelected, } from './actions.js';
42
46
  // Wardley's entries in the interchange registry (`docs/adr/0012`) — the OWM
43
47
  // DSL both ways, and the visual-tier SVG fallback. Exported whole so a host can
44
48
  // ask what Wardley can read and write without mounting an editor, and call it
package/dist/legend.d.ts CHANGED
@@ -1,12 +1,93 @@
1
- import { type WardleyBackgroundElementModel } from '@formicoidea/labre-core/model';
2
1
  import { type ChromeWording } from '@formicoidea/labre-core/shared/services';
3
- import type { BlockStdScope } from '@formicoidea/labre-core/std';
2
+ import type { BlockStdScope, CommandLegendBox, CommandLegendEntry, CommandLegendRow, CommandLegendSurface } from '@formicoidea/labre-core/std';
3
+ /** The legend box's own title. */
4
+ export declare const WARDLEY_LEGEND_TITLE: ChromeWording;
5
+ /**
6
+ * The market: a circle, three neutral dots and the triangle wiring them.
7
+ *
8
+ * A `custom` swatch and not a `glyph`, for the reason the pipeline and the
9
+ * porter below give: the drawing is SEVERAL elements, and the three connectors
10
+ * are anchored by id so the triangle follows its dots.
11
+ */
12
+ declare function drawMarket(surface: CommandLegendSurface, box: {
13
+ x: number;
14
+ y: number;
15
+ w: number;
16
+ h: number;
17
+ }): string[];
18
+ /** The pipeline: the body, and the handle astride its top edge. */
19
+ declare function drawPipeline(surface: CommandLegendSurface, box: {
20
+ x: number;
21
+ y: number;
22
+ w: number;
23
+ h: number;
24
+ }): string[];
25
+ /**
26
+ * The Porter rose at the row's scale: the circle, the four DERIVED arrows and
27
+ * the notation letter.
28
+ *
29
+ * Radius 6 and not the 8 the other circles get, because this glyph is the only
30
+ * one wider than its own circle: arrows included it spans
31
+ * `2 * (R + (gap + length) * R / 30)`, which at 8 would overrun a 30-unit row.
32
+ * It is a RATIO, so the PO's doubling of the map glyph left this row where it
33
+ * was.
34
+ *
35
+ * The letter is a SEPARATE text and not the circle's inner text (recette v2): a
36
+ * shape lays its text out inside a padding larger than this 12-unit box, so at
37
+ * font size 8 the character was pushed out under the circle.
38
+ */
39
+ declare function drawPorter(surface: CommandLegendSurface, box: {
40
+ x: number;
41
+ y: number;
42
+ w: number;
43
+ h: number;
44
+ }, std: BlockStdScope): string[];
45
+ /**
46
+ * The swatch each of the thirteen rows is pictured by, keyed by the ROLE it
47
+ * stands for — eight real artefacts at their notation size, and five samples
48
+ * the framework draws itself: the two typed connectors, at the endpoints that
49
+ * tell them apart, and the three composites that are several elements.
50
+ */
51
+ export declare const WARDLEY_LEGEND_ROWS: {
52
+ "wardley:component": CommandLegendRow;
53
+ "wardley:anchor": CommandLegendRow;
54
+ "wardley:market": {
55
+ swatch: "custom";
56
+ color: string;
57
+ draw: typeof drawMarket;
58
+ };
59
+ "wardley:ecosystem": CommandLegendRow;
60
+ "wardley:method": CommandLegendRow;
61
+ "wardley:pipeline": {
62
+ swatch: "custom";
63
+ color: string;
64
+ draw: typeof drawPipeline;
65
+ };
66
+ "wardley:dependency": CommandLegendRow;
67
+ "wardley:change-arrow": CommandLegendRow;
68
+ "wardley:inertia": CommandLegendRow;
69
+ "wardley:porter": {
70
+ swatch: "custom";
71
+ color: string;
72
+ draw: typeof drawPorter;
73
+ };
74
+ "wardley:accelerator": CommandLegendRow;
75
+ "wardley:decelerator": CommandLegendRow;
76
+ "wardley:area": CommandLegendRow;
77
+ };
78
+ /** A role Wardley pictures in its legend. */
79
+ export type WardleyLegendRole = keyof typeof WARDLEY_LEGEND_ROWS;
4
80
  /** Every {@link LEGEND_DESC} wording, for `translations.ts`'s manifest. */
5
81
  export declare const WARDLEY_LEGEND_DESC_WORDINGS: readonly ChromeWording[];
82
+ /**
83
+ * The legend line one command subscribes — the swatch of the role it stamps,
84
+ * and the sentence that explains it. Its sub-title comes from the command's own
85
+ * catalogue category (Nodes, Connectors, Areas), so a section is declared by
86
+ * filing a command rather than by a second list.
87
+ */
88
+ export declare function wardleyLegendEntry(role: WardleyLegendRole): CommandLegendEntry;
6
89
  /** Every {@link LEGEND_GRADIENT} caption, for `translations.ts`'s manifest. */
7
90
  export declare const WARDLEY_LEGEND_GRADIENT_WORDINGS: readonly ChromeWording[];
8
- /** The legend box's own title. */
9
- export declare const WARDLEY_LEGEND_TITLE: ChromeWording;
10
91
  /** The five-forces panel's own title. */
11
92
  export declare const WARDLEY_LEGEND_PORTER_TITLE: ChromeWording;
12
93
  /** Every {@link PORTER_FORCES} wording, plus the caption, for the manifest. */
@@ -36,20 +117,16 @@ interface PorterPanelLayout {
36
117
  */
37
118
  export declare function porterPanelLayout(w: number): PorterPanelLayout;
38
119
  /**
39
- * Every wording this file writes onto the canvas, for `translations.ts`'s
40
- * manifest contribution — the auto-legend's own title, its rows' captions,
41
- * the gradient blocks and the Porter panel, all as `seed` (written into the
42
- * document once, at the moment a legend is generated).
120
+ * How a Wardley map's legend box is laid out, and the two blocks it adds under
121
+ * the rows. Declared on the FIRST background command (`commands.ts`), which is
122
+ * the sheet the legend is drawn on.
43
123
  */
44
- export declare const WARDLEY_LEGEND_WORDINGS: readonly ChromeWording[];
124
+ export declare const WARDLEY_LEGEND_BOX: CommandLegendBox;
45
125
  /**
46
- * Build a "Legend" group from real, editable elements (white rect frame +
47
- * "Legend" text + one row of [real component glyph + description text] per
48
- * Wardley component TYPE present inside the background's perimeter + a
49
- * gradient-meaning block when the background is a gradient variant + the
50
- * five-forces panel when a Porter's-forces glyph is on the map). A snapshot
51
- * is created on each call; everything is grouped so it can be moved / resized /
52
- * edited and is dropped bottom-left of the background.
126
+ * Every wording this file writes onto the canvas, for `translations.ts`'s
127
+ * manifest contribution — the box's own title, its rows' captions, the gradient
128
+ * blocks and the Porter panel, all as `seed` (written into the document once,
129
+ * at the moment a legend is generated).
53
130
  */
54
- export declare function createWardleyLegend(std: BlockStdScope, bg: WardleyBackgroundElementModel): void;
131
+ export declare const WARDLEY_LEGEND_WORDINGS: readonly ChromeWording[];
55
132
  export {};