@formicoidea/labre-framework-wardley 0.23.0 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/consts.d.ts +72 -0
- package/{src/consts.ts → dist/consts.js} +63 -72
- package/dist/descriptor.d.ts +7 -0
- package/{src/descriptor.ts → dist/descriptor.js} +1 -1
- package/dist/effects.d.ts +10 -0
- package/dist/effects.js +7 -0
- package/dist/element-renderer.d.ts +15 -0
- package/dist/element-renderer.js +160 -0
- package/dist/element-view.d.ts +21 -0
- package/dist/element-view.js +122 -0
- package/dist/gradient.d.ts +18 -0
- package/dist/gradient.js +112 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/label-layout.d.ts +21 -0
- package/dist/label-layout.js +73 -0
- package/dist/legend.d.ts +12 -0
- package/dist/legend.js +333 -0
- package/dist/node/consts.d.ts +107 -0
- package/{src/node/consts.ts → dist/node/consts.js} +12 -20
- package/dist/node/label-editor.d.ts +28 -0
- package/dist/node/label-editor.js +216 -0
- package/dist/node/node-renderer.d.ts +17 -0
- package/dist/node/node-renderer.js +106 -0
- package/{src/node/node-view.ts → dist/node/node-view.d.ts} +3 -3
- package/dist/node/node-view.js +10 -0
- package/dist/templates/index.d.ts +3 -0
- package/dist/templates/index.js +172 -0
- package/dist/templates/maps.d.ts +3 -0
- package/dist/templates/maps.js +247 -0
- package/dist/toolbar/config.d.ts +75 -0
- package/dist/toolbar/config.js +206 -0
- package/dist/toolbar/icons.d.ts +31 -0
- package/{src/toolbar/icons.ts → dist/toolbar/icons.js} +51 -66
- package/dist/toolbar/node-config.d.ts +2 -0
- package/{src/toolbar/node-config.ts → dist/toolbar/node-config.js} +7 -14
- package/dist/toolbar/senior-tool.d.ts +2 -0
- package/{src/toolbar/senior-tool.ts → dist/toolbar/senior-tool.js} +5 -5
- package/dist/toolbar/wardley-menu.d.ts +53 -0
- package/dist/toolbar/wardley-menu.js +408 -0
- package/dist/toolbar/wardley-senior-button.d.ts +18 -0
- package/dist/toolbar/wardley-senior-button.js +146 -0
- package/dist/toolbar/wardley-tool-button.d.ts +10 -0
- package/dist/toolbar/wardley-tool-button.js +123 -0
- package/dist/view.d.ts +7 -0
- package/dist/view.js +36 -0
- package/package.json +15 -6
- package/src/effects.ts +0 -17
- package/src/element-renderer.ts +0 -242
- package/src/element-view.ts +0 -143
- package/src/gradient.ts +0 -137
- package/src/index.ts +0 -1
- package/src/label-layout.ts +0 -126
- package/src/legend.ts +0 -438
- package/src/node/node-renderer.ts +0 -142
- package/src/templates/index.ts +0 -236
- package/src/templates/maps.ts +0 -283
- package/src/toolbar/config.ts +0 -280
- package/src/toolbar/wardley-menu.ts +0 -552
- package/src/toolbar/wardley-senior-button.ts +0 -154
- package/src/view.ts +0 -39
package/dist/consts.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visual constants for the Wardley map background.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the relevant subset of
|
|
5
|
+
* `../wardley-map-renderer/src/blocks/wardley-map/wardley-map-consts.ts`
|
|
6
|
+
* and the validated mockup `../wardley-mockups/C-edgeless.html`.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: every size below is in **model units and FIXED** — fonts, margins,
|
|
9
|
+
* offsets and stroke widths do NOT scale with the element size. Only the plot
|
|
10
|
+
* interior (axis/divider positions, band rects) scales with the element's
|
|
11
|
+
* width/height. This keeps the labels readable at a constant size (18) no
|
|
12
|
+
* matter how large the background is enlarged.
|
|
13
|
+
*/
|
|
14
|
+
/** Reference / fallback element width (16:9 — the height is derived). */
|
|
15
|
+
export declare const REF_WIDTH = 1600;
|
|
16
|
+
/** Fixed inner margins (sized to hold the fixed-size labels). */
|
|
17
|
+
export declare const MARGIN: {
|
|
18
|
+
left: number;
|
|
19
|
+
right: number;
|
|
20
|
+
top: number;
|
|
21
|
+
bottom: number;
|
|
22
|
+
};
|
|
23
|
+
/** Fixed font sizes (the prominent labels are 18). */
|
|
24
|
+
export declare const FONTS: {
|
|
25
|
+
phase: number;
|
|
26
|
+
axis: number;
|
|
27
|
+
direction: number;
|
|
28
|
+
visibility: number;
|
|
29
|
+
};
|
|
30
|
+
/** Fixed label offsets relative to the plot edges. */
|
|
31
|
+
export declare const OFFSETS: {
|
|
32
|
+
phaseBaseline: number;
|
|
33
|
+
phasePad: number;
|
|
34
|
+
yHug: number;
|
|
35
|
+
directionTop: number;
|
|
36
|
+
directionPadLeft: number;
|
|
37
|
+
directionPadRight: number;
|
|
38
|
+
evolutionPadRight: number;
|
|
39
|
+
visibleTop: number;
|
|
40
|
+
invisibleBottom: number;
|
|
41
|
+
};
|
|
42
|
+
/** Fixed stroke widths + decorations. */
|
|
43
|
+
export declare const LINE: {
|
|
44
|
+
axis: number;
|
|
45
|
+
divider: number;
|
|
46
|
+
card: number;
|
|
47
|
+
};
|
|
48
|
+
export declare const ARROW = 11;
|
|
49
|
+
export declare const CARD_RADIUS = 10;
|
|
50
|
+
/** Evolution phase boundary ratios (interior dividers). */
|
|
51
|
+
export declare const EVOLUTION_BOUNDARIES: readonly [0.175, 0.4, 0.7];
|
|
52
|
+
/** Ordered phases: [label, startRatio]. */
|
|
53
|
+
export declare const EVOLUTION_PHASES: ReadonlyArray<readonly [string, number]>;
|
|
54
|
+
export declare const AXIS_LABELS: {
|
|
55
|
+
xAxis: string;
|
|
56
|
+
yAxis: string;
|
|
57
|
+
evolutionStart: string;
|
|
58
|
+
evolutionEnd: string;
|
|
59
|
+
visibilityHigh: string;
|
|
60
|
+
visibilityLow: string;
|
|
61
|
+
};
|
|
62
|
+
/** Colors (light theme, faithful to the reference renderer / mockup C). */
|
|
63
|
+
export declare const COLORS: {
|
|
64
|
+
card: string;
|
|
65
|
+
cardBorder: string;
|
|
66
|
+
axis: string;
|
|
67
|
+
divider: string;
|
|
68
|
+
label: string;
|
|
69
|
+
band: readonly ["#f7faff", "#eef4fb", "#e6eef8", "#dde8f4"];
|
|
70
|
+
};
|
|
71
|
+
export declare const FONT_FAMILY = "Inter, sans-serif";
|
|
72
|
+
//# sourceMappingURL=consts.d.ts.map
|
|
@@ -1,72 +1,63 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Visual constants for the Wardley map background.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors the relevant subset of
|
|
5
|
-
* `../wardley-map-renderer/src/blocks/wardley-map/wardley-map-consts.ts`
|
|
6
|
-
* and the validated mockup `../wardley-mockups/C-edgeless.html`.
|
|
7
|
-
*
|
|
8
|
-
* IMPORTANT: every size below is in **model units and FIXED** — fonts, margins,
|
|
9
|
-
* offsets and stroke widths do NOT scale with the element size. Only the plot
|
|
10
|
-
* interior (axis/divider positions, band rects) scales with the element's
|
|
11
|
-
* width/height. This keeps the labels readable at a constant size (18) no
|
|
12
|
-
* matter how large the background is enlarged.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
/** Fixed
|
|
19
|
-
export const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
export const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
card: '#ffffff',
|
|
65
|
-
cardBorder: '#e3e2e4',
|
|
66
|
-
axis: '#3b3d42',
|
|
67
|
-
divider: '#9aa0a6',
|
|
68
|
-
label: '#6b7280',
|
|
69
|
-
band: ['#f7faff', '#eef4fb', '#e6eef8', '#dde8f4'] as const,
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
export const FONT_FAMILY = 'Inter, sans-serif';
|
|
1
|
+
/**
|
|
2
|
+
* Visual constants for the Wardley map background.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the relevant subset of
|
|
5
|
+
* `../wardley-map-renderer/src/blocks/wardley-map/wardley-map-consts.ts`
|
|
6
|
+
* and the validated mockup `../wardley-mockups/C-edgeless.html`.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: every size below is in **model units and FIXED** — fonts, margins,
|
|
9
|
+
* offsets and stroke widths do NOT scale with the element size. Only the plot
|
|
10
|
+
* interior (axis/divider positions, band rects) scales with the element's
|
|
11
|
+
* width/height. This keeps the labels readable at a constant size (18) no
|
|
12
|
+
* matter how large the background is enlarged.
|
|
13
|
+
*/
|
|
14
|
+
/** Reference / fallback element width (16:9 — the height is derived). */
|
|
15
|
+
export const REF_WIDTH = 1600;
|
|
16
|
+
/** Fixed inner margins (sized to hold the fixed-size labels). */
|
|
17
|
+
export const MARGIN = { left: 40, right: 30, top: 30, bottom: 38 };
|
|
18
|
+
/** Fixed font sizes (the prominent labels are 18). */
|
|
19
|
+
export const FONTS = { phase: 18, axis: 18, direction: 13, visibility: 16 };
|
|
20
|
+
/** Fixed label offsets relative to the plot edges. */
|
|
21
|
+
export const OFFSETS = {
|
|
22
|
+
phaseBaseline: 22, // phase labels & "Evolution" baseline below the X axis
|
|
23
|
+
phasePad: 6, // gap from each zone start
|
|
24
|
+
yHug: 9, // rotated Y labels gap (matches the X labels' visual gap)
|
|
25
|
+
directionTop: 20, // Uncharted/Industrialized baseline below the top
|
|
26
|
+
directionPadLeft: 14,
|
|
27
|
+
directionPadRight: 6,
|
|
28
|
+
evolutionPadRight: 16,
|
|
29
|
+
visibleTop: 56, // "Visible" y below the top
|
|
30
|
+
invisibleBottom: 44, // "Invisible" y above the bottom
|
|
31
|
+
};
|
|
32
|
+
/** Fixed stroke widths + decorations. */
|
|
33
|
+
export const LINE = { axis: 2, divider: 1.2, card: 1.5 };
|
|
34
|
+
export const ARROW = 11;
|
|
35
|
+
export const CARD_RADIUS = 10;
|
|
36
|
+
/** Evolution phase boundary ratios (interior dividers). */
|
|
37
|
+
export const EVOLUTION_BOUNDARIES = [0.175, 0.4, 0.7];
|
|
38
|
+
/** Ordered phases: [label, startRatio]. */
|
|
39
|
+
export const EVOLUTION_PHASES = [
|
|
40
|
+
['Genesis', 0],
|
|
41
|
+
['Custom-Built', 0.175],
|
|
42
|
+
['Product (+Rental)', 0.4],
|
|
43
|
+
['Commodity (+Utility)', 0.7],
|
|
44
|
+
];
|
|
45
|
+
export const AXIS_LABELS = {
|
|
46
|
+
xAxis: 'Evolution',
|
|
47
|
+
yAxis: 'Value Chain',
|
|
48
|
+
evolutionStart: 'Uncharted',
|
|
49
|
+
evolutionEnd: 'Industrialized',
|
|
50
|
+
visibilityHigh: 'Visible',
|
|
51
|
+
visibilityLow: 'Invisible',
|
|
52
|
+
};
|
|
53
|
+
/** Colors (light theme, faithful to the reference renderer / mockup C). */
|
|
54
|
+
export const COLORS = {
|
|
55
|
+
card: '#ffffff',
|
|
56
|
+
cardBorder: '#e3e2e4',
|
|
57
|
+
axis: '#3b3d42',
|
|
58
|
+
divider: '#9aa0a6',
|
|
59
|
+
label: '#6b7280',
|
|
60
|
+
band: ['#f7faff', '#eef4fb', '#e6eef8', '#dde8f4'],
|
|
61
|
+
};
|
|
62
|
+
export const FONT_FAMILY = 'Inter, sans-serif';
|
|
63
|
+
//# sourceMappingURL=consts.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EdgelessWardleyMenu } from './toolbar/wardley-menu';
|
|
2
|
+
import { EdgelessWardleySeniorButton } from './toolbar/wardley-senior-button';
|
|
3
|
+
export declare function effects(): void;
|
|
4
|
+
declare global {
|
|
5
|
+
interface HTMLElementTagNameMap {
|
|
6
|
+
'edgeless-wardley-menu': EdgelessWardleyMenu;
|
|
7
|
+
'edgeless-wardley-senior-button': EdgelessWardleySeniorButton;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=effects.d.ts.map
|
package/dist/effects.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { EdgelessWardleyMenu } from './toolbar/wardley-menu';
|
|
2
|
+
import { EdgelessWardleySeniorButton } from './toolbar/wardley-senior-button';
|
|
3
|
+
export function effects() {
|
|
4
|
+
customElements.define('edgeless-wardley-menu', EdgelessWardleyMenu);
|
|
5
|
+
customElements.define('edgeless-wardley-senior-button', EdgelessWardleySeniorButton);
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=effects.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ElementRenderer } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import type { WardleyBackgroundElementModel } from '@formicoidea/labre-core/model';
|
|
3
|
+
/**
|
|
4
|
+
* Canvas renderer for the Wardley map background — reproduces mockup C:
|
|
5
|
+
* an L-shaped axes frame (no top/right border), dashed evolution dividers and
|
|
6
|
+
* the symmetric axis labels.
|
|
7
|
+
*
|
|
8
|
+
* All sizes (fonts, margins, offsets, strokes) are FIXED model units — they do
|
|
9
|
+
* not scale with the element size. Only the plot interior scales.
|
|
10
|
+
*/
|
|
11
|
+
export declare const wardley: ElementRenderer<WardleyBackgroundElementModel>;
|
|
12
|
+
export declare const WardleyElementRendererExtension: import("@formicoidea/labre-core/store").ExtensionType & {
|
|
13
|
+
identifier: import("@formicoidea/labre-core/global/di").ServiceIdentifier<ElementRenderer<WardleyBackgroundElementModel>>;
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=element-renderer.d.ts.map
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { ARROW, CARD_RADIUS, COLORS, EVOLUTION_BOUNDARIES, FONT_FAMILY, FONTS, LINE, MARGIN, OFFSETS, } from './consts';
|
|
3
|
+
import { BENEFIT_ZERO_FRAC, paintGradientBackground } from './gradient';
|
|
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
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Canvas renderer for the Wardley map background — reproduces mockup C:
|
|
16
|
+
* an L-shaped axes frame (no top/right border), dashed evolution dividers and
|
|
17
|
+
* the symmetric axis labels.
|
|
18
|
+
*
|
|
19
|
+
* All sizes (fonts, margins, offsets, strokes) are FIXED model units — they do
|
|
20
|
+
* not scale with the element size. Only the plot interior scales.
|
|
21
|
+
*/
|
|
22
|
+
export const wardley = (model, ctx, matrix) => {
|
|
23
|
+
const [, , w, h] = model.deserializedXYWH;
|
|
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
|
+
if (model.variant === 'benefit') {
|
|
64
|
+
const zy = py1 - BENEFIT_ZERO_FRAC * ph;
|
|
65
|
+
ctx.strokeStyle = COLORS.axis;
|
|
66
|
+
ctx.lineWidth = 1.2;
|
|
67
|
+
ctx.beginPath();
|
|
68
|
+
ctx.moveTo(px0, zy);
|
|
69
|
+
ctx.lineTo(px1, zy);
|
|
70
|
+
ctx.stroke();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// ── Optional evolution band tints ───────────────────────────────────
|
|
74
|
+
if (model.banded) {
|
|
75
|
+
const starts = [0, 0.175, 0.4, 0.7];
|
|
76
|
+
const ends = [0.175, 0.4, 0.7, 1];
|
|
77
|
+
for (let i = 0; i < 4; i++) {
|
|
78
|
+
ctx.fillStyle = COLORS.band[i];
|
|
79
|
+
ctx.fillRect(ex(starts[i]), py0, ex(ends[i]) - ex(starts[i]), ph);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// ── Evolution phase dividers (dashed) ───────────────────────────────
|
|
83
|
+
if (model.showColumnDividers) {
|
|
84
|
+
ctx.strokeStyle = COLORS.divider;
|
|
85
|
+
ctx.lineWidth = LINE.divider;
|
|
86
|
+
ctx.setLineDash([5, 5]);
|
|
87
|
+
for (const r of EVOLUTION_BOUNDARIES) {
|
|
88
|
+
line(ex(r), py0, ex(r), py1);
|
|
89
|
+
}
|
|
90
|
+
ctx.setLineDash([]);
|
|
91
|
+
}
|
|
92
|
+
// ── Axes (L shape) + arrowheads ─────────────────────────────────────
|
|
93
|
+
// X and Y axes are independently toggleable. Each line stops at the base of
|
|
94
|
+
// its arrowhead (1px overlap, hidden under the triangle) so the line never
|
|
95
|
+
// pokes past the tip on zoom.
|
|
96
|
+
ctx.strokeStyle = COLORS.axis;
|
|
97
|
+
ctx.lineWidth = LINE.axis;
|
|
98
|
+
ctx.fillStyle = COLORS.axis;
|
|
99
|
+
if (model.showXAxis) {
|
|
100
|
+
line(px0, py1, px1 - ARROW + 1, py1); // X axis (arrow tip at px1)
|
|
101
|
+
ctx.beginPath(); // X arrow (points right)
|
|
102
|
+
ctx.moveTo(px1, py1);
|
|
103
|
+
ctx.lineTo(px1 - ARROW, py1 - ARROW / 2);
|
|
104
|
+
ctx.lineTo(px1 - ARROW, py1 + ARROW / 2);
|
|
105
|
+
ctx.closePath();
|
|
106
|
+
ctx.fill();
|
|
107
|
+
}
|
|
108
|
+
if (model.showYAxis) {
|
|
109
|
+
line(px0, py1, px0, py0 + ARROW - 1); // Y axis (arrow tip at py0)
|
|
110
|
+
ctx.beginPath(); // Y arrow (points up)
|
|
111
|
+
ctx.moveTo(px0, py0);
|
|
112
|
+
ctx.lineTo(px0 - ARROW / 2, py0 + ARROW);
|
|
113
|
+
ctx.lineTo(px0 + ARROW / 2, py0 + ARROW);
|
|
114
|
+
ctx.closePath();
|
|
115
|
+
ctx.fill();
|
|
116
|
+
}
|
|
117
|
+
// ── Horizontal labels ───────────────────────────────────────────────
|
|
118
|
+
ctx.textBaseline = 'alphabetic';
|
|
119
|
+
// Phase (column) labels (left-aligned at each zone start)
|
|
120
|
+
if (model.showColumnLabels) {
|
|
121
|
+
ctx.font = `${FONTS.phase}px ${FONT_FAMILY}`;
|
|
122
|
+
ctx.fillStyle = COLORS.label;
|
|
123
|
+
ctx.textAlign = 'left';
|
|
124
|
+
const phases = [
|
|
125
|
+
[model.phase0, 0],
|
|
126
|
+
[model.phase1, 0.175],
|
|
127
|
+
[model.phase2, 0.4],
|
|
128
|
+
[model.phase3, 0.7],
|
|
129
|
+
];
|
|
130
|
+
for (const [label, start] of phases) {
|
|
131
|
+
ctx.fillText(label, ex(start) + OFFSETS.phasePad, py1 + OFFSETS.phaseBaseline);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// "Evolution" title near the X arrow (tied to the X axis)
|
|
135
|
+
if (model.showXAxis) {
|
|
136
|
+
ctx.font = `${FONTS.axis}px ${FONT_FAMILY}`;
|
|
137
|
+
ctx.fillStyle = COLORS.axis;
|
|
138
|
+
ctx.textAlign = 'right';
|
|
139
|
+
ctx.fillText(model.xAxisTitle, px1 - OFFSETS.evolutionPadRight, py1 + OFFSETS.phaseBaseline);
|
|
140
|
+
}
|
|
141
|
+
// Direction indicators (Uncharted / Industrialized, top corners)
|
|
142
|
+
if (model.showCornerLabels) {
|
|
143
|
+
ctx.font = `${FONTS.direction}px ${FONT_FAMILY}`;
|
|
144
|
+
ctx.fillStyle = COLORS.label;
|
|
145
|
+
ctx.textAlign = 'left';
|
|
146
|
+
ctx.fillText(model.evolutionStart, px0 + OFFSETS.directionPadLeft, py0 + OFFSETS.directionTop);
|
|
147
|
+
ctx.textAlign = 'right';
|
|
148
|
+
ctx.fillText(model.evolutionEnd, px1 - OFFSETS.directionPadRight, py0 + OFFSETS.directionTop);
|
|
149
|
+
}
|
|
150
|
+
// ── Rotated Y labels (hugging the axis, symmetric with the X labels) ─
|
|
151
|
+
if (model.showYAxis) {
|
|
152
|
+
vtext(model.yAxisTitle, px0 - OFFSETS.yHug, (py0 + py1) / 2, FONTS.axis, COLORS.axis);
|
|
153
|
+
}
|
|
154
|
+
if (model.showVisibilityLabels) {
|
|
155
|
+
vtext(model.visibilityHigh, px0 - OFFSETS.yHug, py0 + OFFSETS.visibleTop, FONTS.visibility, COLORS.label);
|
|
156
|
+
vtext(model.visibilityLow, px0 - OFFSETS.yHug, py1 - OFFSETS.invisibleBottom, FONTS.visibility, COLORS.label);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
export const WardleyElementRendererExtension = ElementRendererExtension('wardley', wardley);
|
|
160
|
+
//# sourceMappingURL=element-renderer.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { WardleyBackgroundElementModel } from '@formicoidea/labre-core/model';
|
|
2
|
+
import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
|
|
3
|
+
export declare class WardleyView extends GfxElementModelView<WardleyBackgroundElementModel> {
|
|
4
|
+
static type: string;
|
|
5
|
+
/** The in-place `<input>` used to edit a label, or null when idle. */
|
|
6
|
+
private _labelEditor;
|
|
7
|
+
onCreated(): void;
|
|
8
|
+
onDestroyed(): void;
|
|
9
|
+
/** Double-click on a label → edit its text in place. */
|
|
10
|
+
private _onDblClick;
|
|
11
|
+
private _openLabelEditor;
|
|
12
|
+
private _closeLabelEditor;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resize gating: the resize handles are hidden unless `model.resizeEnabled` is
|
|
16
|
+
* true. `beforeResize` is re-evaluated every time the allowed handles are
|
|
17
|
+
* computed (manager.ts), so toggling the field from the toolbar updates the
|
|
18
|
+
* handles reactively. Moving/selecting stays available throughout.
|
|
19
|
+
*/
|
|
20
|
+
export declare const WardleyInteraction: import("@formicoidea/labre-core/store").ExtensionType;
|
|
21
|
+
//# sourceMappingURL=element-view.d.ts.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { EdgelessCRUDIdentifier } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { rotatePoint } from '@formicoidea/labre-core/global/gfx';
|
|
3
|
+
import { GfxElementModelView, GfxViewInteractionExtension, } from '@formicoidea/labre-core/std/gfx';
|
|
4
|
+
import { getWardleyLabelHits, hitTestWardleyLabel, } from './label-layout';
|
|
5
|
+
export class WardleyView extends GfxElementModelView {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(...arguments);
|
|
8
|
+
/** The in-place `<input>` used to edit a label, or null when idle. */
|
|
9
|
+
this._labelEditor = null;
|
|
10
|
+
}
|
|
11
|
+
static { this.type = 'wardley'; }
|
|
12
|
+
onCreated() {
|
|
13
|
+
super.onCreated();
|
|
14
|
+
this.on('dblclick', e => this._onDblClick(e));
|
|
15
|
+
}
|
|
16
|
+
onDestroyed() {
|
|
17
|
+
this._closeLabelEditor();
|
|
18
|
+
super.onDestroyed();
|
|
19
|
+
}
|
|
20
|
+
/** Double-click on a label → edit its text in place. */
|
|
21
|
+
_onDblClick(e) {
|
|
22
|
+
if (this.model.isLocked())
|
|
23
|
+
return;
|
|
24
|
+
const [mx, my] = this.gfx.viewport.toModelCoord(e.x, e.y);
|
|
25
|
+
const [bx, by, w, h] = this.model.deserializedXYWH;
|
|
26
|
+
// Convert the model-space point into element-local coordinates, undoing the
|
|
27
|
+
// element rotation around its center.
|
|
28
|
+
let lx = mx - bx;
|
|
29
|
+
let ly = my - by;
|
|
30
|
+
const rot = this.model.rotate ?? 0;
|
|
31
|
+
if (rot) {
|
|
32
|
+
const center = [bx + w / 2, by + h / 2];
|
|
33
|
+
const [ux, uy] = rotatePoint([mx, my], center, -rot);
|
|
34
|
+
lx = ux - bx;
|
|
35
|
+
ly = uy - by;
|
|
36
|
+
}
|
|
37
|
+
const hit = hitTestWardleyLabel(getWardleyLabelHits(this.model, w, h), lx, ly);
|
|
38
|
+
if (!hit)
|
|
39
|
+
return;
|
|
40
|
+
this._openLabelEditor(hit.field, e);
|
|
41
|
+
}
|
|
42
|
+
_openLabelEditor(field, e) {
|
|
43
|
+
this._closeLabelEditor();
|
|
44
|
+
const input = document.createElement('input');
|
|
45
|
+
input.value = String(this.model[field] ?? '');
|
|
46
|
+
Object.assign(input.style, {
|
|
47
|
+
position: 'fixed',
|
|
48
|
+
left: `${e.raw.clientX}px`,
|
|
49
|
+
top: `${e.raw.clientY}px`,
|
|
50
|
+
transform: 'translate(-50%, -50%)',
|
|
51
|
+
zIndex: '10000',
|
|
52
|
+
minWidth: '140px',
|
|
53
|
+
padding: '3px 8px',
|
|
54
|
+
font: '14px Inter, sans-serif',
|
|
55
|
+
color: 'var(--affine-text-primary-color, #1f2328)',
|
|
56
|
+
background: 'var(--affine-background-overlay-panel-color, #ffffff)',
|
|
57
|
+
border: '1px solid var(--affine-primary-color, #1e96eb)',
|
|
58
|
+
borderRadius: '6px',
|
|
59
|
+
boxShadow: 'var(--affine-shadow-2, 0 2px 8px rgba(0,0,0,0.18))',
|
|
60
|
+
outline: 'none',
|
|
61
|
+
});
|
|
62
|
+
document.body.append(input);
|
|
63
|
+
this._labelEditor = input;
|
|
64
|
+
// Mark the element as "editing" so the global edgeless key handlers
|
|
65
|
+
// (delete, escape, etc.) don't act on it while the user types.
|
|
66
|
+
this.gfx.selection.set({ elements: [this.model.id], editing: true });
|
|
67
|
+
input.focus();
|
|
68
|
+
input.select();
|
|
69
|
+
const commit = () => {
|
|
70
|
+
// Guard against re-entrancy: removing the input fires `blur`, which would
|
|
71
|
+
// otherwise call `commit` a second time.
|
|
72
|
+
if (this._labelEditor !== input)
|
|
73
|
+
return;
|
|
74
|
+
const value = input.value;
|
|
75
|
+
this._closeLabelEditor();
|
|
76
|
+
this.gfx.std.store.captureSync();
|
|
77
|
+
this.gfx.std
|
|
78
|
+
.get(EdgelessCRUDIdentifier)
|
|
79
|
+
.updateElement(this.model.id, { [field]: value });
|
|
80
|
+
};
|
|
81
|
+
input.addEventListener('keydown', ev => {
|
|
82
|
+
ev.stopPropagation();
|
|
83
|
+
if (ev.key === 'Enter') {
|
|
84
|
+
ev.preventDefault();
|
|
85
|
+
commit();
|
|
86
|
+
}
|
|
87
|
+
else if (ev.key === 'Escape') {
|
|
88
|
+
ev.preventDefault();
|
|
89
|
+
this._closeLabelEditor();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
input.addEventListener('blur', commit);
|
|
93
|
+
}
|
|
94
|
+
_closeLabelEditor() {
|
|
95
|
+
if (!this._labelEditor)
|
|
96
|
+
return;
|
|
97
|
+
const input = this._labelEditor;
|
|
98
|
+
this._labelEditor = null;
|
|
99
|
+
input.remove();
|
|
100
|
+
if (this.isConnected) {
|
|
101
|
+
this.gfx.selection.set({ elements: [this.model.id], editing: false });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Resize gating: the resize handles are hidden unless `model.resizeEnabled` is
|
|
107
|
+
* true. `beforeResize` is re-evaluated every time the allowed handles are
|
|
108
|
+
* computed (manager.ts), so toggling the field from the toolbar updates the
|
|
109
|
+
* handles reactively. Moving/selecting stays available throughout.
|
|
110
|
+
*/
|
|
111
|
+
export const WardleyInteraction = GfxViewInteractionExtension(WardleyView.type, {
|
|
112
|
+
handleResize({ model }) {
|
|
113
|
+
return {
|
|
114
|
+
beforeResize({ set }) {
|
|
115
|
+
if (!model.resizeEnabled) {
|
|
116
|
+
set({ allowedHandlers: [] });
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
//# sourceMappingURL=element-view.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curve-driven gradient backgrounds (Slice C). Each analytic background is a
|
|
3
|
+
* smooth mathematical curve (piecewise asymmetric Gaussian bells); the gradient
|
|
4
|
+
* opacity at each evolution position X follows that curve, normalised between
|
|
5
|
+
* its own min and max — i.e. the gradient is strongest where the curve peaks and
|
|
6
|
+
* fades to nothing at its minimum. Validated against the reference images at
|
|
7
|
+
* `../wardley-mockups/gradient-backgrounds.html`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const GRADIENT_GREEN = "#1f9e4d";
|
|
10
|
+
export declare const GRADIENT_RED = "#d6455d";
|
|
11
|
+
/** Benefit/investment zero-line height (fraction of plot height from bottom). */
|
|
12
|
+
export declare const BENEFIT_ZERO_FRAC = 0.3;
|
|
13
|
+
/**
|
|
14
|
+
* Paint the curve-driven gradient over the plot rectangle [px0,px1]×[py0,py1]
|
|
15
|
+
* in element-local coordinates. `classic` paints nothing.
|
|
16
|
+
*/
|
|
17
|
+
export declare function paintGradientBackground(ctx: CanvasRenderingContext2D, variant: 'opportunity' | 'benefit' | 'evolution-gradient', px0: number, px1: number, py0: number, py1: number): void;
|
|
18
|
+
//# sourceMappingURL=gradient.d.ts.map
|