@formicoidea/labre-framework-edgy 0.59.0 → 0.61.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.js +51 -50
- package/dist/consts.d.ts +1 -65
- package/dist/consts.js +5 -129
- package/dist/facets-crop.d.ts +75 -0
- package/dist/facets-crop.js +139 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/interchange-headless.d.ts +10 -0
- package/dist/interchange-headless.js +10 -0
- package/dist/node/consts.js +1 -1
- package/dist/presets.d.ts +9 -1
- package/dist/presets.js +8 -1
- package/dist/toolbox.d.ts +67 -0
- package/dist/toolbox.js +190 -0
- package/package.json +6 -2
package/dist/actions.js
CHANGED
|
@@ -4,25 +4,24 @@ import { createGroupCommand } from '@formicoidea/labre-core/gfx/group';
|
|
|
4
4
|
import { createTemplateJob } from '@formicoidea/labre-core/gfx/template';
|
|
5
5
|
import { ConnectorMode, PointStyle, StrokeStyle } from '@formicoidea/labre-core/model';
|
|
6
6
|
import { translateKey } from '@formicoidea/labre-core/shared/services';
|
|
7
|
-
import { Bound } from '@formicoidea/labre-core/global/gfx';
|
|
8
7
|
import { GfxControllerIdentifier } from '@formicoidea/labre-core/std/gfx';
|
|
9
|
-
import { CROP_LABELED } from './consts.js';
|
|
10
8
|
import { edgyElementLabel, edgyElementLabelKey } from './metamodel.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
9
|
+
import { NODE_LABEL, nodeLabelKey, NODE_SIZE, NODE_STROKE, } from './node/consts.js';
|
|
10
|
+
import { EDGY_BOARD_SIZE, RELATION_STROKE_WIDTH, } from './presets.js';
|
|
13
11
|
import { EDGY_ROLE } from './roles.js';
|
|
14
12
|
import { edgyDynamicTemplate } from './templates/dynamic.js';
|
|
15
|
-
|
|
16
|
-
const RELATION_STROKE_WIDTH = 2;
|
|
13
|
+
import { EDGY_FACETS_SIZE, EDGY_PEOPLE_SIZE, edgyBoardElement, edgyBoxElement, edgyFacetsElement, edgyPeopleElements, } from './toolbox.js';
|
|
17
14
|
/**
|
|
18
15
|
* Standalone creation actions for the EDGY toolbox — the behaviour layer,
|
|
19
16
|
* lifted out of `toolbar/edgy-menu.ts` by PF3 so the menu can become a pure
|
|
20
17
|
* renderer over the command registry. They emit no telemetry: the single
|
|
21
18
|
* emission point is `runCommand` (`docs/adr/0008`).
|
|
19
|
+
*
|
|
20
|
+
* What they place comes from the ONE assembly a host with no editor also
|
|
21
|
+
* calls (`toolbox.ts`, `docs/adr/0045`); what stays here is what only an
|
|
22
|
+
* editor has: the viewport centre, the seeds resolved in the host's language
|
|
23
|
+
* (`docs/adr/0023`), the group command and the selection.
|
|
22
24
|
*/
|
|
23
|
-
/** Default blank-board size. */
|
|
24
|
-
const BOARD_W = 1600;
|
|
25
|
-
const BOARD_H = 1000;
|
|
26
25
|
const gfxOf = (std) => std.get(GfxControllerIdentifier);
|
|
27
26
|
function finish(gfx, id) {
|
|
28
27
|
gfx.doc.captureSync();
|
|
@@ -38,27 +37,22 @@ export function createEdgyFacets(std) {
|
|
|
38
37
|
const gfx = gfxOf(std);
|
|
39
38
|
if (!gfx.surface)
|
|
40
39
|
return;
|
|
41
|
-
const
|
|
42
|
-
const height = CROP_LABELED.h * FACETS_SCALE;
|
|
40
|
+
const { w, h } = EDGY_FACETS_SIZE;
|
|
43
41
|
const { centerX, centerY } = gfx.viewport;
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
identityLabel: translateKey(std, edgyElementLabelKey('identity'), edgyElementLabel('identity')),
|
|
57
|
-
architectureLabel: translateKey(std, edgyElementLabelKey('architecture'), edgyElementLabel('architecture')),
|
|
58
|
-
experienceLabel: translateKey(std, edgyElementLabelKey('experience'), edgyElementLabel('experience')),
|
|
59
|
-
xywh: new Bound(centerX - width / 2, centerY - height / 2, width, height).serialize(),
|
|
42
|
+
const zone = (name) => translateKey(std, edgyElementLabelKey(name), edgyElementLabel(name));
|
|
43
|
+
const { id: _local, ...props } = edgyFacetsElement({ x: centerX - w / 2, y: centerY - h / 2 },
|
|
44
|
+
// The three facet names, written HERE rather than left to the model's
|
|
45
|
+
// defaults (`EdgyFacetsElementModel.identityLabel` etc — a red-zone
|
|
46
|
+
// field: a document created before this key existed keeps its literal
|
|
47
|
+
// 'Identity' / 'Architecture' / 'Experience' verbatim). Shares its key
|
|
48
|
+
// with the very same words wherever the metamodel names a zone
|
|
49
|
+
// (`edgyElementLabelKey`, `templates/dynamic.ts`, `templates/index.ts`).
|
|
50
|
+
{
|
|
51
|
+
identity: zone('identity'),
|
|
52
|
+
architecture: zone('architecture'),
|
|
53
|
+
experience: zone('experience'),
|
|
60
54
|
});
|
|
61
|
-
finish(gfx,
|
|
55
|
+
finish(gfx, gfx.surface.addElement(props));
|
|
62
56
|
}
|
|
63
57
|
/**
|
|
64
58
|
* Insert the "EDGY dynamic" template (facets background + the 12 elements
|
|
@@ -90,15 +84,13 @@ export function createEdgyBoard(std) {
|
|
|
90
84
|
const gfx = gfxOf(std);
|
|
91
85
|
if (!gfx.surface)
|
|
92
86
|
return;
|
|
87
|
+
const { w, h } = EDGY_BOARD_SIZE;
|
|
93
88
|
const { centerX, centerY } = gfx.viewport;
|
|
94
|
-
const id =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// it, and the same map-wide arbitration has to have somewhere to live.
|
|
98
|
-
role: EDGY_ROLE.board,
|
|
99
|
-
xywh: new Bound(centerX - BOARD_W / 2, centerY - BOARD_H / 2, BOARD_W, BOARD_H).serialize(),
|
|
89
|
+
const { id: _local, ...props } = edgyBoardElement({
|
|
90
|
+
x: centerX - w / 2,
|
|
91
|
+
y: centerY - h / 2,
|
|
100
92
|
});
|
|
101
|
-
finish(gfx,
|
|
93
|
+
finish(gfx, gfx.surface.addElement(props));
|
|
102
94
|
}
|
|
103
95
|
/** Create a box base element (outcome / object / activity) with inner text. */
|
|
104
96
|
export function createEdgyBox(std, kind) {
|
|
@@ -108,15 +100,11 @@ export function createEdgyBox(std, kind) {
|
|
|
108
100
|
return;
|
|
109
101
|
const { w, h } = NODE_SIZE[kind];
|
|
110
102
|
const { centerX: cx, centerY: cy } = gfx.viewport;
|
|
111
|
-
const id =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// document content the moment it lands (`nodeLabelKey`).
|
|
117
|
-
...edgyNodeTextProps(translateKey(std, nodeLabelKey(kind), NODE_LABEL[kind])),
|
|
118
|
-
});
|
|
119
|
-
finish(gfx, id);
|
|
103
|
+
const { id: _local, ...props } = edgyBoxElement(kind, { x: cx - w / 2, y: cy - h / 2 },
|
|
104
|
+
// Translated HERE and once: what a gesture writes onto the canvas is
|
|
105
|
+
// document content the moment it lands (`nodeLabelKey`).
|
|
106
|
+
translateKey(std, nodeLabelKey(kind), NODE_LABEL[kind]));
|
|
107
|
+
finish(gfx, surface.addElement(props));
|
|
120
108
|
}
|
|
121
109
|
/**
|
|
122
110
|
* Create the People base element: an (invisible) ellipse decorated with the
|
|
@@ -127,14 +115,27 @@ export function createEdgyPeople(std) {
|
|
|
127
115
|
const surface = gfx.surface;
|
|
128
116
|
if (!surface)
|
|
129
117
|
return;
|
|
130
|
-
const { w, h } = NODE_SIZE.people;
|
|
131
118
|
const { centerX: cx, centerY: cy } = gfx.viewport;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
119
|
+
// The GLYPH, not the whole item, sits on the viewport centre, as it always
|
|
120
|
+
// has: the item's box is as wide as the name and starts at the glyph's top.
|
|
121
|
+
const elements = edgyPeopleElements({
|
|
122
|
+
x: cx - EDGY_PEOPLE_SIZE.w / 2,
|
|
123
|
+
y: cy - NODE_SIZE.people.h / 2,
|
|
124
|
+
}, translateKey(std, nodeLabelKey('people'), NODE_LABEL.people));
|
|
125
|
+
const members = [];
|
|
126
|
+
for (const { id: _local, ...props } of elements) {
|
|
127
|
+
// The builder's group is for a host with no editor. Here the editor's own
|
|
128
|
+
// group command makes it, because it writes what only an editor has: the
|
|
129
|
+
// numbered "Group N" title from the host's catalogue and the user layer.
|
|
130
|
+
if (props.type === 'group')
|
|
131
|
+
continue;
|
|
132
|
+
members.push(surface.addElement(props));
|
|
133
|
+
}
|
|
134
|
+
const nodeId = members[0];
|
|
135
|
+
if (!nodeId)
|
|
136
|
+
return;
|
|
136
137
|
const [, result] = std.command.exec(createGroupCommand, {
|
|
137
|
-
elements:
|
|
138
|
+
elements: members,
|
|
138
139
|
});
|
|
139
140
|
finish(gfx, result.groupId || nodeId);
|
|
140
141
|
}
|
package/dist/consts.d.ts
CHANGED
|
@@ -6,18 +6,7 @@
|
|
|
6
6
|
* uniformly to the element bounds — so the three circles always stay circular
|
|
7
7
|
* and the pictos/labels keep their relative proportions at any size.
|
|
8
8
|
*/
|
|
9
|
-
|
|
10
|
-
export declare const REF_W = 680;
|
|
11
|
-
export declare const REF_H = 400;
|
|
12
|
-
/** Circle geometry in reference coords. */
|
|
13
|
-
export declare const VENN: {
|
|
14
|
-
readonly cx: number;
|
|
15
|
-
readonly cy: 176;
|
|
16
|
-
/** Circle radius. */
|
|
17
|
-
readonly R: 95;
|
|
18
|
-
/** Distance of each circle centre from the diagram centre. */
|
|
19
|
-
readonly r0: 56;
|
|
20
|
-
};
|
|
9
|
+
export { CROP, CROP_LABELED, cropLabeledScale, cropScale, REF_H, REF_W, VENN, } from './facets-crop.js';
|
|
21
10
|
/** Saturated facet + intersection colours (faithful to the official icons). */
|
|
22
11
|
export declare const COLORS: {
|
|
23
12
|
readonly identity: "#00ea4e";
|
|
@@ -43,56 +32,3 @@ export declare const FONT_FAMILY = "Inter, sans-serif";
|
|
|
43
32
|
* reference size is this framework's business.
|
|
44
33
|
*/
|
|
45
34
|
export declare function refScale(w: number, h: number): import("@formicoidea/labre-core/_pkgs/affine-block-surface").RefFit;
|
|
46
|
-
/**
|
|
47
|
-
* Bounding box of the three circles in reference coords, with just enough
|
|
48
|
-
* padding to see their outline. When a diagram hides its facet names
|
|
49
|
-
* (`cropToCircles` without `showLabels`) the renderer fits THIS box into the
|
|
50
|
-
* element bounds, so the background hugs the Venn.
|
|
51
|
-
*
|
|
52
|
-
* ## Why the padding is 2.5 and not the 8 it was
|
|
53
|
-
*
|
|
54
|
-
* The padding is the ONLY empty band such a board has, and it is spent at the
|
|
55
|
-
* element's scale, not at the reference one. The EDGY dynamic template draws
|
|
56
|
-
* its board at `DYN_SCALE = 4.8`, so eight reference units of padding were
|
|
57
|
-
* **32.4 model units** of nothing on each of the four sides — the board a user
|
|
58
|
-
* has to catch by its border (R37) sat a good inch away from the circles, which
|
|
59
|
-
* is what the recette of 23/09/2026 reported. 2.5 leaves 1.25 past the circles'
|
|
60
|
-
* own 2.5-wide outline, i.e. 6 model units at the dynamic board's scale and
|
|
61
|
-
* under 2 at a facets board's.
|
|
62
|
-
*
|
|
63
|
-
* `templates/dynamic.ts` places every element of the dynamic board in
|
|
64
|
-
* coordinates relative to this box (`dynToModel`) and sizes the board from it,
|
|
65
|
-
* so the template follows in the same breath: both are derived here, and a
|
|
66
|
-
* freshly inserted template is aligned by construction. A board built from the
|
|
67
|
-
* OLD template keeps its `xywh` and sees the Venn grow 3.8 % inside it — see
|
|
68
|
-
* the changeset.
|
|
69
|
-
*/
|
|
70
|
-
export declare const CROP: {
|
|
71
|
-
x: number;
|
|
72
|
-
y: number;
|
|
73
|
-
w: number;
|
|
74
|
-
h: number;
|
|
75
|
-
};
|
|
76
|
-
/**
|
|
77
|
-
* Crop box KEEPING room for the three facet name labels (drawn outside the
|
|
78
|
-
* circles): the circles plus {@link LABEL_ALLOWANCE}, which is asymmetric
|
|
79
|
-
* because the words are. Used when a cropped diagram still shows its labels.
|
|
80
|
-
*/
|
|
81
|
-
export declare const CROP_LABELED: {
|
|
82
|
-
x: number;
|
|
83
|
-
y: number;
|
|
84
|
-
w: number;
|
|
85
|
-
h: number;
|
|
86
|
-
};
|
|
87
|
-
/** `refScale` variant fitting the cropped circles box (see {@link CROP}). */
|
|
88
|
-
export declare const cropScale: (w: number, h: number) => {
|
|
89
|
-
s: number;
|
|
90
|
-
ox: number;
|
|
91
|
-
oy: number;
|
|
92
|
-
};
|
|
93
|
-
/** `refScale` variant fitting the circles + facet labels box. */
|
|
94
|
-
export declare const cropLabeledScale: (w: number, h: number) => {
|
|
95
|
-
s: number;
|
|
96
|
-
ox: number;
|
|
97
|
-
oy: number;
|
|
98
|
-
};
|
package/dist/consts.js
CHANGED
|
@@ -7,18 +7,11 @@
|
|
|
7
7
|
* and the pictos/labels keep their relative proportions at any size.
|
|
8
8
|
*/
|
|
9
9
|
import { refScale as fitReference } from '@formicoidea/labre-core/blocks/surface';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export
|
|
15
|
-
cx: REF_W / 2,
|
|
16
|
-
cy: 176,
|
|
17
|
-
/** Circle radius. */
|
|
18
|
-
R: 95,
|
|
19
|
-
/** Distance of each circle centre from the diagram centre. */
|
|
20
|
-
r0: 56,
|
|
21
|
-
};
|
|
10
|
+
import { REF_H, REF_W } from './facets-crop.js';
|
|
11
|
+
// The reference geometry lives in an editor-free file (`facets-crop.ts`) so the
|
|
12
|
+
// toolbox can size a facets background without the surface index; every
|
|
13
|
+
// binding is re-exported here unchanged.
|
|
14
|
+
export { CROP, CROP_LABELED, cropLabeledScale, cropScale, REF_H, REF_W, VENN, } from './facets-crop.js';
|
|
22
15
|
/** Saturated facet + intersection colours (faithful to the official icons). */
|
|
23
16
|
export const COLORS = {
|
|
24
17
|
identity: '#00ea4e',
|
|
@@ -46,120 +39,3 @@ export const FONT_FAMILY = 'Inter, sans-serif';
|
|
|
46
39
|
export function refScale(w, h) {
|
|
47
40
|
return fitReference(w, h, REF_W, REF_H);
|
|
48
41
|
}
|
|
49
|
-
/**
|
|
50
|
-
* The three circles, and nothing else: their exact bounding box in reference
|
|
51
|
-
* coords. Both crop boxes below are this box plus what each of them has to make
|
|
52
|
-
* room for, so the two can never disagree about where the Venn is.
|
|
53
|
-
*/
|
|
54
|
-
const CIRCLES = (() => {
|
|
55
|
-
const ax = VENN.cx - 0.866 * VENN.r0; // Identity / Architecture centres
|
|
56
|
-
const abY = VENN.cy - 0.5 * VENN.r0;
|
|
57
|
-
const cY = VENN.cy + VENN.r0; // Experience centre
|
|
58
|
-
return {
|
|
59
|
-
minX: ax - VENN.R,
|
|
60
|
-
maxX: VENN.cx + 0.866 * VENN.r0 + VENN.R,
|
|
61
|
-
minY: abY - VENN.R,
|
|
62
|
-
maxY: cY + VENN.R,
|
|
63
|
-
};
|
|
64
|
-
})();
|
|
65
|
-
/**
|
|
66
|
-
* Bounding box of the three circles in reference coords, with just enough
|
|
67
|
-
* padding to see their outline. When a diagram hides its facet names
|
|
68
|
-
* (`cropToCircles` without `showLabels`) the renderer fits THIS box into the
|
|
69
|
-
* element bounds, so the background hugs the Venn.
|
|
70
|
-
*
|
|
71
|
-
* ## Why the padding is 2.5 and not the 8 it was
|
|
72
|
-
*
|
|
73
|
-
* The padding is the ONLY empty band such a board has, and it is spent at the
|
|
74
|
-
* element's scale, not at the reference one. The EDGY dynamic template draws
|
|
75
|
-
* its board at `DYN_SCALE = 4.8`, so eight reference units of padding were
|
|
76
|
-
* **32.4 model units** of nothing on each of the four sides — the board a user
|
|
77
|
-
* has to catch by its border (R37) sat a good inch away from the circles, which
|
|
78
|
-
* is what the recette of 23/09/2026 reported. 2.5 leaves 1.25 past the circles'
|
|
79
|
-
* own 2.5-wide outline, i.e. 6 model units at the dynamic board's scale and
|
|
80
|
-
* under 2 at a facets board's.
|
|
81
|
-
*
|
|
82
|
-
* `templates/dynamic.ts` places every element of the dynamic board in
|
|
83
|
-
* coordinates relative to this box (`dynToModel`) and sizes the board from it,
|
|
84
|
-
* so the template follows in the same breath: both are derived here, and a
|
|
85
|
-
* freshly inserted template is aligned by construction. A board built from the
|
|
86
|
-
* OLD template keeps its `xywh` and sees the Venn grow 3.8 % inside it — see
|
|
87
|
-
* the changeset.
|
|
88
|
-
*/
|
|
89
|
-
export const CROP = (() => {
|
|
90
|
-
const pad = 2.5;
|
|
91
|
-
return {
|
|
92
|
-
x: CIRCLES.minX - pad,
|
|
93
|
-
y: CIRCLES.minY - pad,
|
|
94
|
-
w: CIRCLES.maxX - CIRCLES.minX + 2 * pad,
|
|
95
|
-
h: CIRCLES.maxY - CIRCLES.minY + 2 * pad,
|
|
96
|
-
};
|
|
97
|
-
})();
|
|
98
|
-
/**
|
|
99
|
-
* How far past the circles each side of the labelled box has to reach, in REF
|
|
100
|
-
* units. MEASURED on the canvas, in the shipped wording, at
|
|
101
|
-
* {@link LABEL_FONT_SIZE}; each number is that reach plus 4 or 5 units of
|
|
102
|
-
* gutter.
|
|
103
|
-
*
|
|
104
|
-
* It used to be one symmetric number — `sideAllowance = 140` either side of the
|
|
105
|
-
* padded box, `bottomAllowance = 35` under it — and the three words are not
|
|
106
|
-
* symmetric at all. "Identity" is written right-aligned 10 left of the left
|
|
107
|
-
* circle and reaches 63.6 past the circles; "Architecture" is written
|
|
108
|
-
* left-aligned 10 right of the right circle and reaches 98.1; "Experience"
|
|
109
|
-
* hangs 29.2 below the bottom one (a `middle` baseline plus the descender of
|
|
110
|
-
* its "p"); and above them there is no word at all, only the 1.25 of circle
|
|
111
|
-
* stroke. So a facets diagram was born inside 126.6 model units of nothing on
|
|
112
|
-
* its left and 74.8 on its right — and a framework background is caught by its
|
|
113
|
-
* border and by nothing else
|
|
114
|
-
* (`model/src/elements/framework-background/hit-test.ts`), which put the
|
|
115
|
-
* catchable edge a seventh of the board away from anything drawn.
|
|
116
|
-
*
|
|
117
|
-
* ## Why constants rather than a measurement at paint time
|
|
118
|
-
*
|
|
119
|
-
* Because the three names are EDITABLE props of the element (`identityLabel` &
|
|
120
|
-
* co) and `cropLabeledScale` is not only what the renderer draws through —
|
|
121
|
-
* `element-view.ts` maps every click back through it too. Deriving the box from
|
|
122
|
-
* the current text would rescale and re-centre the whole Venn on each
|
|
123
|
-
* keystroke, and move the click mapping under the user's finger while they
|
|
124
|
-
* type; two boards of the same size would also draw their circles at two
|
|
125
|
-
* different sizes. The allowance is furniture: it is measured once, here.
|
|
126
|
-
*
|
|
127
|
-
* A name rewritten to something much longer than the vocabulary therefore
|
|
128
|
-
* reaches past the element's edge. It paints (the surface clips nothing); only
|
|
129
|
-
* the SVG export, which crops to the `xywh`, would cut it.
|
|
130
|
-
*/
|
|
131
|
-
const LABEL_ALLOWANCE = {
|
|
132
|
-
/** No word up there: the circle's own stroke, and room to see it. */
|
|
133
|
-
top: 5,
|
|
134
|
-
/** "Identity", right-aligned, reaching left of the circles. */
|
|
135
|
-
left: 68,
|
|
136
|
-
/** "Architecture", left-aligned, reaching right of them. */
|
|
137
|
-
right: 103,
|
|
138
|
-
/** "Experience", under the bottom circle, descender included. */
|
|
139
|
-
bottom: 32,
|
|
140
|
-
};
|
|
141
|
-
/**
|
|
142
|
-
* Crop box KEEPING room for the three facet name labels (drawn outside the
|
|
143
|
-
* circles): the circles plus {@link LABEL_ALLOWANCE}, which is asymmetric
|
|
144
|
-
* because the words are. Used when a cropped diagram still shows its labels.
|
|
145
|
-
*/
|
|
146
|
-
export const CROP_LABELED = {
|
|
147
|
-
x: CIRCLES.minX - LABEL_ALLOWANCE.left,
|
|
148
|
-
y: CIRCLES.minY - LABEL_ALLOWANCE.top,
|
|
149
|
-
w: CIRCLES.maxX - CIRCLES.minX + LABEL_ALLOWANCE.left + LABEL_ALLOWANCE.right,
|
|
150
|
-
h: CIRCLES.maxY - CIRCLES.minY + LABEL_ALLOWANCE.top + LABEL_ALLOWANCE.bottom,
|
|
151
|
-
};
|
|
152
|
-
function fitScale(box) {
|
|
153
|
-
return (w, h) => {
|
|
154
|
-
const s = Math.min(w / box.w, h / box.h);
|
|
155
|
-
return {
|
|
156
|
-
s,
|
|
157
|
-
ox: (w - box.w * s) / 2 - box.x * s,
|
|
158
|
-
oy: (h - box.h * s) / 2 - box.y * s,
|
|
159
|
-
};
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
/** `refScale` variant fitting the cropped circles box (see {@link CROP}). */
|
|
163
|
-
export const cropScale = fitScale(CROP);
|
|
164
|
-
/** `refScale` variant fitting the circles + facet labels box. */
|
|
165
|
-
export const cropLabeledScale = fitScale(CROP_LABELED);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The facets diagram's reference geometry: the fixed design space, the Venn
|
|
3
|
+
* and the two crop boxes, as plain numbers.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `consts.ts`, which re-exports every binding unchanged, because
|
|
6
|
+
* that file also reads the library's fit from the surface package index: the
|
|
7
|
+
* toolbox (`toolbox.ts`, `docs/adr/0045`) sizes a facets background from
|
|
8
|
+
* {@link CROP_LABELED} on the editor-free `./interchange` entry, which must not
|
|
9
|
+
* load the editor graph that index brings.
|
|
10
|
+
*/
|
|
11
|
+
/** Reference design size (the validated mockup canvas). */
|
|
12
|
+
export declare const REF_W = 680;
|
|
13
|
+
export declare const REF_H = 400;
|
|
14
|
+
/** Circle geometry in reference coords. */
|
|
15
|
+
export declare const VENN: {
|
|
16
|
+
readonly cx: number;
|
|
17
|
+
readonly cy: 176;
|
|
18
|
+
/** Circle radius. */
|
|
19
|
+
readonly R: 95;
|
|
20
|
+
/** Distance of each circle centre from the diagram centre. */
|
|
21
|
+
readonly r0: 56;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Bounding box of the three circles in reference coords, with just enough
|
|
25
|
+
* padding to see their outline. When a diagram hides its facet names
|
|
26
|
+
* (`cropToCircles` without `showLabels`) the renderer fits THIS box into the
|
|
27
|
+
* element bounds, so the background hugs the Venn.
|
|
28
|
+
*
|
|
29
|
+
* ## Why the padding is 2.5 and not the 8 it was
|
|
30
|
+
*
|
|
31
|
+
* The padding is the ONLY empty band such a board has, and it is spent at the
|
|
32
|
+
* element's scale, not at the reference one. The EDGY dynamic template draws
|
|
33
|
+
* its board at `DYN_SCALE = 4.8`, so eight reference units of padding were
|
|
34
|
+
* **32.4 model units** of nothing on each of the four sides — the board a user
|
|
35
|
+
* has to catch by its border (R37) sat a good inch away from the circles, which
|
|
36
|
+
* is what the recette of 23/09/2026 reported. 2.5 leaves 1.25 past the circles'
|
|
37
|
+
* own 2.5-wide outline, i.e. 6 model units at the dynamic board's scale and
|
|
38
|
+
* under 2 at a facets board's.
|
|
39
|
+
*
|
|
40
|
+
* `templates/dynamic.ts` places every element of the dynamic board in
|
|
41
|
+
* coordinates relative to this box (`dynToModel`) and sizes the board from it,
|
|
42
|
+
* so the template follows in the same breath: both are derived here, and a
|
|
43
|
+
* freshly inserted template is aligned by construction. A board built from the
|
|
44
|
+
* OLD template keeps its `xywh` and sees the Venn grow 3.8 % inside it — see
|
|
45
|
+
* the changeset.
|
|
46
|
+
*/
|
|
47
|
+
export declare const CROP: {
|
|
48
|
+
x: number;
|
|
49
|
+
y: number;
|
|
50
|
+
w: number;
|
|
51
|
+
h: number;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Crop box KEEPING room for the three facet name labels (drawn outside the
|
|
55
|
+
* circles): the circles plus {@link LABEL_ALLOWANCE}, which is asymmetric
|
|
56
|
+
* because the words are. Used when a cropped diagram still shows its labels.
|
|
57
|
+
*/
|
|
58
|
+
export declare const CROP_LABELED: {
|
|
59
|
+
x: number;
|
|
60
|
+
y: number;
|
|
61
|
+
w: number;
|
|
62
|
+
h: number;
|
|
63
|
+
};
|
|
64
|
+
/** `refScale` variant fitting the cropped circles box (see {@link CROP}). */
|
|
65
|
+
export declare const cropScale: (w: number, h: number) => {
|
|
66
|
+
s: number;
|
|
67
|
+
ox: number;
|
|
68
|
+
oy: number;
|
|
69
|
+
};
|
|
70
|
+
/** `refScale` variant fitting the circles + facet labels box. */
|
|
71
|
+
export declare const cropLabeledScale: (w: number, h: number) => {
|
|
72
|
+
s: number;
|
|
73
|
+
ox: number;
|
|
74
|
+
oy: number;
|
|
75
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The facets diagram's reference geometry: the fixed design space, the Venn
|
|
3
|
+
* and the two crop boxes, as plain numbers.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `consts.ts`, which re-exports every binding unchanged, because
|
|
6
|
+
* that file also reads the library's fit from the surface package index: the
|
|
7
|
+
* toolbox (`toolbox.ts`, `docs/adr/0045`) sizes a facets background from
|
|
8
|
+
* {@link CROP_LABELED} on the editor-free `./interchange` entry, which must not
|
|
9
|
+
* load the editor graph that index brings.
|
|
10
|
+
*/
|
|
11
|
+
/** Reference design size (the validated mockup canvas). */
|
|
12
|
+
export const REF_W = 680;
|
|
13
|
+
export const REF_H = 400;
|
|
14
|
+
/** Circle geometry in reference coords. */
|
|
15
|
+
export const VENN = {
|
|
16
|
+
cx: REF_W / 2,
|
|
17
|
+
cy: 176,
|
|
18
|
+
/** Circle radius. */
|
|
19
|
+
R: 95,
|
|
20
|
+
/** Distance of each circle centre from the diagram centre. */
|
|
21
|
+
r0: 56,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The three circles, and nothing else: their exact bounding box in reference
|
|
25
|
+
* coords. Both crop boxes below are this box plus what each of them has to make
|
|
26
|
+
* room for, so the two can never disagree about where the Venn is.
|
|
27
|
+
*/
|
|
28
|
+
const CIRCLES = (() => {
|
|
29
|
+
const ax = VENN.cx - 0.866 * VENN.r0; // Identity / Architecture centres
|
|
30
|
+
const abY = VENN.cy - 0.5 * VENN.r0;
|
|
31
|
+
const cY = VENN.cy + VENN.r0; // Experience centre
|
|
32
|
+
return {
|
|
33
|
+
minX: ax - VENN.R,
|
|
34
|
+
maxX: VENN.cx + 0.866 * VENN.r0 + VENN.R,
|
|
35
|
+
minY: abY - VENN.R,
|
|
36
|
+
maxY: cY + VENN.R,
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
/**
|
|
40
|
+
* Bounding box of the three circles in reference coords, with just enough
|
|
41
|
+
* padding to see their outline. When a diagram hides its facet names
|
|
42
|
+
* (`cropToCircles` without `showLabels`) the renderer fits THIS box into the
|
|
43
|
+
* element bounds, so the background hugs the Venn.
|
|
44
|
+
*
|
|
45
|
+
* ## Why the padding is 2.5 and not the 8 it was
|
|
46
|
+
*
|
|
47
|
+
* The padding is the ONLY empty band such a board has, and it is spent at the
|
|
48
|
+
* element's scale, not at the reference one. The EDGY dynamic template draws
|
|
49
|
+
* its board at `DYN_SCALE = 4.8`, so eight reference units of padding were
|
|
50
|
+
* **32.4 model units** of nothing on each of the four sides — the board a user
|
|
51
|
+
* has to catch by its border (R37) sat a good inch away from the circles, which
|
|
52
|
+
* is what the recette of 23/09/2026 reported. 2.5 leaves 1.25 past the circles'
|
|
53
|
+
* own 2.5-wide outline, i.e. 6 model units at the dynamic board's scale and
|
|
54
|
+
* under 2 at a facets board's.
|
|
55
|
+
*
|
|
56
|
+
* `templates/dynamic.ts` places every element of the dynamic board in
|
|
57
|
+
* coordinates relative to this box (`dynToModel`) and sizes the board from it,
|
|
58
|
+
* so the template follows in the same breath: both are derived here, and a
|
|
59
|
+
* freshly inserted template is aligned by construction. A board built from the
|
|
60
|
+
* OLD template keeps its `xywh` and sees the Venn grow 3.8 % inside it — see
|
|
61
|
+
* the changeset.
|
|
62
|
+
*/
|
|
63
|
+
export const CROP = (() => {
|
|
64
|
+
const pad = 2.5;
|
|
65
|
+
return {
|
|
66
|
+
x: CIRCLES.minX - pad,
|
|
67
|
+
y: CIRCLES.minY - pad,
|
|
68
|
+
w: CIRCLES.maxX - CIRCLES.minX + 2 * pad,
|
|
69
|
+
h: CIRCLES.maxY - CIRCLES.minY + 2 * pad,
|
|
70
|
+
};
|
|
71
|
+
})();
|
|
72
|
+
/**
|
|
73
|
+
* How far past the circles each side of the labelled box has to reach, in REF
|
|
74
|
+
* units. MEASURED on the canvas, in the shipped wording, at
|
|
75
|
+
* `LABEL_FONT_SIZE` (`consts.ts`); each number is that reach plus 4 or 5 units of
|
|
76
|
+
* gutter.
|
|
77
|
+
*
|
|
78
|
+
* It used to be one symmetric number — `sideAllowance = 140` either side of the
|
|
79
|
+
* padded box, `bottomAllowance = 35` under it — and the three words are not
|
|
80
|
+
* symmetric at all. "Identity" is written right-aligned 10 left of the left
|
|
81
|
+
* circle and reaches 63.6 past the circles; "Architecture" is written
|
|
82
|
+
* left-aligned 10 right of the right circle and reaches 98.1; "Experience"
|
|
83
|
+
* hangs 29.2 below the bottom one (a `middle` baseline plus the descender of
|
|
84
|
+
* its "p"); and above them there is no word at all, only the 1.25 of circle
|
|
85
|
+
* stroke. So a facets diagram was born inside 126.6 model units of nothing on
|
|
86
|
+
* its left and 74.8 on its right — and a framework background is caught by its
|
|
87
|
+
* border and by nothing else
|
|
88
|
+
* (`model/src/elements/framework-background/hit-test.ts`), which put the
|
|
89
|
+
* catchable edge a seventh of the board away from anything drawn.
|
|
90
|
+
*
|
|
91
|
+
* ## Why constants rather than a measurement at paint time
|
|
92
|
+
*
|
|
93
|
+
* Because the three names are EDITABLE props of the element (`identityLabel` &
|
|
94
|
+
* co) and `cropLabeledScale` is not only what the renderer draws through —
|
|
95
|
+
* `element-view.ts` maps every click back through it too. Deriving the box from
|
|
96
|
+
* the current text would rescale and re-centre the whole Venn on each
|
|
97
|
+
* keystroke, and move the click mapping under the user's finger while they
|
|
98
|
+
* type; two boards of the same size would also draw their circles at two
|
|
99
|
+
* different sizes. The allowance is furniture: it is measured once, here.
|
|
100
|
+
*
|
|
101
|
+
* A name rewritten to something much longer than the vocabulary therefore
|
|
102
|
+
* reaches past the element's edge. It paints (the surface clips nothing); only
|
|
103
|
+
* the SVG export, which crops to the `xywh`, would cut it.
|
|
104
|
+
*/
|
|
105
|
+
const LABEL_ALLOWANCE = {
|
|
106
|
+
/** No word up there: the circle's own stroke, and room to see it. */
|
|
107
|
+
top: 5,
|
|
108
|
+
/** "Identity", right-aligned, reaching left of the circles. */
|
|
109
|
+
left: 68,
|
|
110
|
+
/** "Architecture", left-aligned, reaching right of them. */
|
|
111
|
+
right: 103,
|
|
112
|
+
/** "Experience", under the bottom circle, descender included. */
|
|
113
|
+
bottom: 32,
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Crop box KEEPING room for the three facet name labels (drawn outside the
|
|
117
|
+
* circles): the circles plus {@link LABEL_ALLOWANCE}, which is asymmetric
|
|
118
|
+
* because the words are. Used when a cropped diagram still shows its labels.
|
|
119
|
+
*/
|
|
120
|
+
export const CROP_LABELED = {
|
|
121
|
+
x: CIRCLES.minX - LABEL_ALLOWANCE.left,
|
|
122
|
+
y: CIRCLES.minY - LABEL_ALLOWANCE.top,
|
|
123
|
+
w: CIRCLES.maxX - CIRCLES.minX + LABEL_ALLOWANCE.left + LABEL_ALLOWANCE.right,
|
|
124
|
+
h: CIRCLES.maxY - CIRCLES.minY + LABEL_ALLOWANCE.top + LABEL_ALLOWANCE.bottom,
|
|
125
|
+
};
|
|
126
|
+
function fitScale(box) {
|
|
127
|
+
return (w, h) => {
|
|
128
|
+
const s = Math.min(w / box.w, h / box.h);
|
|
129
|
+
return {
|
|
130
|
+
s,
|
|
131
|
+
ox: (w - box.w * s) / 2 - box.x * s,
|
|
132
|
+
oy: (h - box.h * s) / 2 - box.y * s,
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** `refScale` variant fitting the cropped circles box (see {@link CROP}). */
|
|
137
|
+
export const cropScale = fitScale(CROP);
|
|
138
|
+
/** `refScale` variant fitting the circles + facet labels box. */
|
|
139
|
+
export const cropLabeledScale = fitScale(CROP_LABELED);
|
package/dist/index.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export { edgyTranslationEntries } from './translations.js';
|
|
|
3
3
|
export { EDGY_ROLE, EDGY_ROLES, type EdgyRoleId } from './roles.js';
|
|
4
4
|
export { EDGY_READING } from './reading.js';
|
|
5
5
|
export { EDGY_RULES } from './rules.js';
|
|
6
|
+
export { EDGY_FACETS_SIZE, EDGY_PEOPLE_SIZE, EDGY_TOOLBOX, edgyBoardElement, edgyBoxElement, edgyFacetsElement, type EdgyFacetsTexts, edgyPeopleElements, edgyRelationElement, } from './toolbox.js';
|
package/dist/index.js
CHANGED
|
@@ -5,3 +5,5 @@ export { edgyTranslationEntries } from './translations.js';
|
|
|
5
5
|
export { EDGY_ROLE, EDGY_ROLES } from './roles.js';
|
|
6
6
|
export { EDGY_READING } from './reading.js';
|
|
7
7
|
export { EDGY_RULES } from './rules.js';
|
|
8
|
+
// The toolbar as pure builders (`docs/adr/0045`), also on `./interchange`.
|
|
9
|
+
export { EDGY_FACETS_SIZE, EDGY_PEOPLE_SIZE, EDGY_TOOLBOX, edgyBoardElement, edgyBoxElement, edgyFacetsElement, edgyPeopleElements, edgyRelationElement, } from './toolbox.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@labre/affine-gfx-edgy/interchange` — EDGY with no editor behind it.
|
|
3
|
+
*
|
|
4
|
+
* EDGY declares no file format. What a host without an editor uses is its
|
|
5
|
+
* toolbar as pure builders (`docs/adr/0045`): the elements a user places from
|
|
6
|
+
* the toolbar, for a server or an MCP tool to place the same ones.
|
|
7
|
+
*
|
|
8
|
+
* Held by `interchange-headless.unit.spec.ts` in `packages/affine/all`.
|
|
9
|
+
*/
|
|
10
|
+
export { EDGY_FACETS_SIZE, EDGY_PEOPLE_SIZE, EDGY_TOOLBOX, edgyBoardElement, edgyBoxElement, edgyFacetsElement, type EdgyFacetsTexts, edgyPeopleElements, edgyRelationElement, } from './toolbox.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@labre/affine-gfx-edgy/interchange` — EDGY with no editor behind it.
|
|
3
|
+
*
|
|
4
|
+
* EDGY declares no file format. What a host without an editor uses is its
|
|
5
|
+
* toolbar as pure builders (`docs/adr/0045`): the elements a user places from
|
|
6
|
+
* the toolbar, for a server or an MCP tool to place the same ones.
|
|
7
|
+
*
|
|
8
|
+
* Held by `interchange-headless.unit.spec.ts` in `packages/affine/all`.
|
|
9
|
+
*/
|
|
10
|
+
export { EDGY_FACETS_SIZE, EDGY_PEOPLE_SIZE, EDGY_TOOLBOX, edgyBoardElement, edgyBoxElement, edgyFacetsElement, edgyPeopleElements, edgyRelationElement, } from './toolbox.js';
|
package/dist/node/consts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NOTATION_NEUTRALS } from '@formicoidea/labre-core/shared/
|
|
1
|
+
import { NOTATION_NEUTRALS } from '@formicoidea/labre-core/shared/headless';
|
|
2
2
|
/**
|
|
3
3
|
* Visual constants for the four EDGY base-element nodes. Each node is a NATIVE
|
|
4
4
|
* shape (ShapeElementModel-derived) so its stroke / fill / inner text are
|
package/dist/presets.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { EdgyNodeKind } from '@formicoidea/labre-core/model';
|
|
2
|
+
import { FontFamily, TextAlign } from '@formicoidea/labre-core/model/headless';
|
|
2
3
|
/**
|
|
3
4
|
* What an EDGY artefact IS, as props — the ONE description the creation sites
|
|
4
5
|
* and the templates both read.
|
|
@@ -23,6 +24,13 @@ export type EdgyBoxKind = 'outcome' | 'object' | 'activity';
|
|
|
23
24
|
* used to spell the same `1.5` out a second time.
|
|
24
25
|
*/
|
|
25
26
|
export declare const FACETS_SCALE = 1.5;
|
|
27
|
+
/** Default blank-board size. */
|
|
28
|
+
export declare const EDGY_BOARD_SIZE: {
|
|
29
|
+
readonly w: 1600;
|
|
30
|
+
readonly h: 1000;
|
|
31
|
+
};
|
|
32
|
+
/** Stroke width the 24 template relations are drawn with. */
|
|
33
|
+
export declare const RELATION_STROKE_WIDTH = 2;
|
|
26
34
|
/**
|
|
27
35
|
* An EDGY node, as props.
|
|
28
36
|
*
|
package/dist/presets.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
// Values from the headless entry, never the model index: the toolbox
|
|
2
|
+
// (`toolbox.ts`, `docs/adr/0045`) builds on these presets from the
|
|
3
|
+
// editor-free `./interchange` entry.
|
|
4
|
+
import { FontFamily, ShapeStyle, TextAlign, } from '@formicoidea/labre-core/model/headless';
|
|
2
5
|
import { Bound } from '@formicoidea/labre-core/global/gfx';
|
|
3
6
|
import { ACTIVITY_VERTICES, INNER_FONT_SIZE, LABEL_FONT_SIZE, NODE_FILL, NODE_STROKE, NODE_STROKE_WIDTH, OUTCOME_RADIUS, } from './node/consts.js';
|
|
4
7
|
import { EDGY_ROLE } from './roles.js';
|
|
@@ -9,6 +12,10 @@ import { EDGY_ROLE } from './roles.js';
|
|
|
9
12
|
* used to spell the same `1.5` out a second time.
|
|
10
13
|
*/
|
|
11
14
|
export const FACETS_SCALE = 1.5;
|
|
15
|
+
/** Default blank-board size. */
|
|
16
|
+
export const EDGY_BOARD_SIZE = { w: 1600, h: 1000 };
|
|
17
|
+
/** Stroke width the 24 template relations are drawn with. */
|
|
18
|
+
export const RELATION_STROKE_WIDTH = 2;
|
|
12
19
|
/**
|
|
13
20
|
* An EDGY node, as props.
|
|
14
21
|
*
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { FrameworkToolbox, SerializedElementProps, ToolboxPoint } from '@formicoidea/labre-core/interchange';
|
|
2
|
+
import { type EdgyBoxKind } from './presets.js';
|
|
3
|
+
/**
|
|
4
|
+
* EDGY's toolbar as pure builders (`docs/adr/0045`): what `createEdgyFacets`,
|
|
5
|
+
* `createEdgyBoard`, `createEdgyPeople`, `createEdgyBox` and the relation tool
|
|
6
|
+
* place, with no editor behind them. The actions call these, so an element
|
|
7
|
+
* built here and one placed from the toolbar are the same element.
|
|
8
|
+
*
|
|
9
|
+
* "EDGY dynamic" is NOT an item: it inserts a template snapshot through the
|
|
10
|
+
* template job (`createEdgyDynamic`), not elements a builder can return.
|
|
11
|
+
*/
|
|
12
|
+
/** The facets background is drawn cropped to its circles and their names. */
|
|
13
|
+
export declare const EDGY_FACETS_SIZE: {
|
|
14
|
+
readonly w: number;
|
|
15
|
+
readonly h: number;
|
|
16
|
+
};
|
|
17
|
+
/** People's box: the glyph on top, its name centred under it. */
|
|
18
|
+
export declare const EDGY_PEOPLE_SIZE: {
|
|
19
|
+
readonly w: number;
|
|
20
|
+
readonly h: number;
|
|
21
|
+
};
|
|
22
|
+
/** The three facet names a facets background writes. */
|
|
23
|
+
export interface EdgyFacetsTexts {
|
|
24
|
+
identity?: string;
|
|
25
|
+
architecture?: string;
|
|
26
|
+
experience?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The Enterprise Design facets background at `at`, cropped to the circles and
|
|
30
|
+
* the facet names. The three names are WRITTEN on the element rather than left
|
|
31
|
+
* to the model's defaults, as `createEdgyFacets` requires: the action passes
|
|
32
|
+
* them resolved in the host's language, a host passes its own or gets the
|
|
33
|
+
* English seed.
|
|
34
|
+
*/
|
|
35
|
+
export declare function edgyFacetsElement(at: ToolboxPoint, texts?: EdgyFacetsTexts, local?: string): SerializedElementProps;
|
|
36
|
+
/** A blank EDGY board (the hover spotlight host) at `at`. */
|
|
37
|
+
export declare function edgyBoardElement(at: ToolboxPoint, local?: string): SerializedElementProps;
|
|
38
|
+
/**
|
|
39
|
+
* An Outcome, Object or Activity at `at`: one node carrying its name as the
|
|
40
|
+
* shape's own inner text.
|
|
41
|
+
*/
|
|
42
|
+
export declare function edgyBoxElement(kind: EdgyBoxKind, at: ToolboxPoint, name?: string, local?: string): SerializedElementProps;
|
|
43
|
+
/**
|
|
44
|
+
* People at `at`, the top-left of {@link EDGY_PEOPLE_SIZE}: the glyph's
|
|
45
|
+
* ellipse centred on top, its name as a free text under it, and the group
|
|
46
|
+
* that makes the two one thing — last, holding both.
|
|
47
|
+
*
|
|
48
|
+
* The group carries no title: the numbered "Group N" the toolbar's group
|
|
49
|
+
* command writes counts the document's groups and asks the host's catalogue,
|
|
50
|
+
* which only an editor has (`createEdgyPeople`).
|
|
51
|
+
*/
|
|
52
|
+
export declare function edgyPeopleElements(at: ToolboxPoint, name?: string, local?: string): SerializedElementProps[];
|
|
53
|
+
/**
|
|
54
|
+
* The connector the relation tool is armed with, between two ends: straight,
|
|
55
|
+
* the stencil ink, two units wide, no arrowhead, and the GENERIC role.
|
|
56
|
+
*
|
|
57
|
+
* The verb is not the builder's: in the editor, `EdgyRelationResolver` names a
|
|
58
|
+
* generic relation from the roles of the two elements it joins, as a verb role
|
|
59
|
+
* and a label. A `label` given here is written as the link's text and leaves
|
|
60
|
+
* the role generic.
|
|
61
|
+
*/
|
|
62
|
+
export declare function edgyRelationElement(from: string, to: string, label?: string): SerializedElementProps;
|
|
63
|
+
/**
|
|
64
|
+
* Every item of the EDGY toolbar that places elements, in the toolbar's order,
|
|
65
|
+
* "EDGY dynamic" excepted (see the note at the top of this file).
|
|
66
|
+
*/
|
|
67
|
+
export declare const EDGY_TOOLBOX: FrameworkToolbox;
|
package/dist/toolbox.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { ConnectorMode, PointStyle, StrokeStyle, } from '@formicoidea/labre-core/model/headless';
|
|
2
|
+
import { Bound } from '@formicoidea/labre-core/global/gfx';
|
|
3
|
+
import { edgyCommandsManifest } from './commands-manifest.js';
|
|
4
|
+
import { CROP_LABELED } from './facets-crop.js';
|
|
5
|
+
import { edgyElementLabel } from './metamodel.js';
|
|
6
|
+
import { LABEL_GAP, NODE_LABEL, NODE_SIZE, NODE_STROKE } from './node/consts.js';
|
|
7
|
+
import { EDGY_BOARD_SIZE, EDGY_LABEL_H, EDGY_LABEL_W, edgyLabelProps, edgyNodeProps, edgyNodeTextProps, FACETS_SCALE, RELATION_STROKE_WIDTH, } from './presets.js';
|
|
8
|
+
import { EDGY_ROLE } from './roles.js';
|
|
9
|
+
/**
|
|
10
|
+
* EDGY's toolbar as pure builders (`docs/adr/0045`): what `createEdgyFacets`,
|
|
11
|
+
* `createEdgyBoard`, `createEdgyPeople`, `createEdgyBox` and the relation tool
|
|
12
|
+
* place, with no editor behind them. The actions call these, so an element
|
|
13
|
+
* built here and one placed from the toolbar are the same element.
|
|
14
|
+
*
|
|
15
|
+
* "EDGY dynamic" is NOT an item: it inserts a template snapshot through the
|
|
16
|
+
* template job (`createEdgyDynamic`), not elements a builder can return.
|
|
17
|
+
*/
|
|
18
|
+
/** The facets background is drawn cropped to its circles and their names. */
|
|
19
|
+
export const EDGY_FACETS_SIZE = {
|
|
20
|
+
w: CROP_LABELED.w * FACETS_SCALE,
|
|
21
|
+
h: CROP_LABELED.h * FACETS_SCALE,
|
|
22
|
+
};
|
|
23
|
+
/** People's box: the glyph on top, its name centred under it. */
|
|
24
|
+
export const EDGY_PEOPLE_SIZE = {
|
|
25
|
+
w: Math.max(NODE_SIZE.people.w, EDGY_LABEL_W),
|
|
26
|
+
h: NODE_SIZE.people.h + LABEL_GAP + EDGY_LABEL_H,
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The Enterprise Design facets background at `at`, cropped to the circles and
|
|
30
|
+
* the facet names. The three names are WRITTEN on the element rather than left
|
|
31
|
+
* to the model's defaults, as `createEdgyFacets` requires: the action passes
|
|
32
|
+
* them resolved in the host's language, a host passes its own or gets the
|
|
33
|
+
* English seed.
|
|
34
|
+
*/
|
|
35
|
+
export function edgyFacetsElement(at, texts = {}, local = 'facets') {
|
|
36
|
+
const name = (zone) => texts[zone]?.trim() || edgyElementLabel(zone);
|
|
37
|
+
return {
|
|
38
|
+
type: 'edgy',
|
|
39
|
+
id: local,
|
|
40
|
+
cropToCircles: true,
|
|
41
|
+
// The frame: what makes this an EDGY board rather than three circles, and
|
|
42
|
+
// what a finding is attributed to.
|
|
43
|
+
role: EDGY_ROLE.facets,
|
|
44
|
+
identityLabel: name('identity'),
|
|
45
|
+
architectureLabel: name('architecture'),
|
|
46
|
+
experienceLabel: name('experience'),
|
|
47
|
+
xywh: new Bound(at.x, at.y, EDGY_FACETS_SIZE.w, EDGY_FACETS_SIZE.h).serialize(),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** A blank EDGY board (the hover spotlight host) at `at`. */
|
|
51
|
+
export function edgyBoardElement(at, local = 'board') {
|
|
52
|
+
return {
|
|
53
|
+
type: 'edgyBoard',
|
|
54
|
+
id: local,
|
|
55
|
+
// The blank board is a frame too: the same rules apply to what is drawn on
|
|
56
|
+
// it, and the same map-wide arbitration has to have somewhere to live.
|
|
57
|
+
role: EDGY_ROLE.board,
|
|
58
|
+
xywh: new Bound(at.x, at.y, EDGY_BOARD_SIZE.w, EDGY_BOARD_SIZE.h).serialize(),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* An Outcome, Object or Activity at `at`: one node carrying its name as the
|
|
63
|
+
* shape's own inner text.
|
|
64
|
+
*/
|
|
65
|
+
export function edgyBoxElement(kind, at, name, local = kind) {
|
|
66
|
+
const { w, h } = NODE_SIZE[kind];
|
|
67
|
+
return {
|
|
68
|
+
...edgyNodeProps(kind, {
|
|
69
|
+
xywh: new Bound(at.x, at.y, w, h).serialize(),
|
|
70
|
+
}),
|
|
71
|
+
...edgyNodeTextProps(name?.trim() || NODE_LABEL[kind]),
|
|
72
|
+
id: local,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* People at `at`, the top-left of {@link EDGY_PEOPLE_SIZE}: the glyph's
|
|
77
|
+
* ellipse centred on top, its name as a free text under it, and the group
|
|
78
|
+
* that makes the two one thing — last, holding both.
|
|
79
|
+
*
|
|
80
|
+
* The group carries no title: the numbered "Group N" the toolbar's group
|
|
81
|
+
* command writes counts the document's groups and asks the host's catalogue,
|
|
82
|
+
* which only an editor has (`createEdgyPeople`).
|
|
83
|
+
*/
|
|
84
|
+
export function edgyPeopleElements(at, name, local = 'people') {
|
|
85
|
+
const { w, h } = NODE_SIZE.people;
|
|
86
|
+
const id = (part) => `${local}:${part}`;
|
|
87
|
+
const members = [
|
|
88
|
+
{
|
|
89
|
+
...edgyNodeProps('people', {
|
|
90
|
+
xywh: new Bound(at.x + (EDGY_PEOPLE_SIZE.w - w) / 2, at.y, w, h).serialize(),
|
|
91
|
+
}),
|
|
92
|
+
id: id('node'),
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
...edgyLabelProps(name?.trim() || NODE_LABEL.people, at.x + (EDGY_PEOPLE_SIZE.w - EDGY_LABEL_W) / 2, at.y + h + LABEL_GAP),
|
|
96
|
+
id: id('label'),
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
return [
|
|
100
|
+
...members,
|
|
101
|
+
{
|
|
102
|
+
type: 'group',
|
|
103
|
+
id: id('group'),
|
|
104
|
+
children: Object.fromEntries(members.map(m => [m.id, true])),
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The connector the relation tool is armed with, between two ends: straight,
|
|
110
|
+
* the stencil ink, two units wide, no arrowhead, and the GENERIC role.
|
|
111
|
+
*
|
|
112
|
+
* The verb is not the builder's: in the editor, `EdgyRelationResolver` names a
|
|
113
|
+
* generic relation from the roles of the two elements it joins, as a verb role
|
|
114
|
+
* and a label. A `label` given here is written as the link's text and leaves
|
|
115
|
+
* the role generic.
|
|
116
|
+
*/
|
|
117
|
+
export function edgyRelationElement(from, to, label) {
|
|
118
|
+
return {
|
|
119
|
+
type: 'connector',
|
|
120
|
+
mode: ConnectorMode.Straight,
|
|
121
|
+
role: EDGY_ROLE.relation,
|
|
122
|
+
stroke: NODE_STROKE,
|
|
123
|
+
strokeStyle: StrokeStyle.Solid,
|
|
124
|
+
strokeWidth: RELATION_STROKE_WIDTH,
|
|
125
|
+
frontEndpointStyle: PointStyle.None,
|
|
126
|
+
rearEndpointStyle: PointStyle.None,
|
|
127
|
+
source: { id: from },
|
|
128
|
+
target: { id: to },
|
|
129
|
+
...(label?.trim() ? { text: label.trim() } : {}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** The toolbar command's label, so a host names an item as the toolbar does. */
|
|
133
|
+
function label(commandId) {
|
|
134
|
+
const row = edgyCommandsManifest.find(r => r.id === commandId);
|
|
135
|
+
return {
|
|
136
|
+
labelKey: row?.labelKey ?? commandId,
|
|
137
|
+
labelFallback: row?.labelFallback ?? commandId,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const BOX_COMMANDS = {
|
|
141
|
+
outcome: 'edgy.addOutcome',
|
|
142
|
+
object: 'edgy.addObject',
|
|
143
|
+
activity: 'edgy.addActivity',
|
|
144
|
+
};
|
|
145
|
+
const boxItem = (kind) => ({
|
|
146
|
+
id: kind,
|
|
147
|
+
...label(BOX_COMMANDS[kind]),
|
|
148
|
+
texts: ['name'],
|
|
149
|
+
size: NODE_SIZE[kind],
|
|
150
|
+
build: (at, texts, local) => [edgyBoxElement(kind, at, texts?.name, local)],
|
|
151
|
+
});
|
|
152
|
+
const relation = {
|
|
153
|
+
id: 'relation',
|
|
154
|
+
...label('edgy.addRelation'),
|
|
155
|
+
build: edgyRelationElement,
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Every item of the EDGY toolbar that places elements, in the toolbar's order,
|
|
159
|
+
* "EDGY dynamic" excepted (see the note at the top of this file).
|
|
160
|
+
*/
|
|
161
|
+
export const EDGY_TOOLBOX = {
|
|
162
|
+
framework: 'edgy',
|
|
163
|
+
items: [
|
|
164
|
+
{
|
|
165
|
+
id: 'facets',
|
|
166
|
+
...label('edgy.addFacets'),
|
|
167
|
+
texts: ['identity', 'architecture', 'experience'],
|
|
168
|
+
size: EDGY_FACETS_SIZE,
|
|
169
|
+
build: (at, texts, local) => [edgyFacetsElement(at, texts, local)],
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: 'board',
|
|
173
|
+
...label('edgy.addBoard'),
|
|
174
|
+
texts: [],
|
|
175
|
+
size: EDGY_BOARD_SIZE,
|
|
176
|
+
build: (at, _texts, local) => [edgyBoardElement(at, local)],
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 'people',
|
|
180
|
+
...label('edgy.addPeople'),
|
|
181
|
+
texts: ['name'],
|
|
182
|
+
size: EDGY_PEOPLE_SIZE,
|
|
183
|
+
build: (at, texts, local) => edgyPeopleElements(at, texts?.name, local),
|
|
184
|
+
},
|
|
185
|
+
boxItem('outcome'),
|
|
186
|
+
boxItem('object'),
|
|
187
|
+
boxItem('activity'),
|
|
188
|
+
],
|
|
189
|
+
links: [relation],
|
|
190
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formicoidea/labre-framework-edgy",
|
|
3
3
|
"description": "Labre edgy framework for @formicoidea/labre-core.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.61.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"author": "lajola",
|
|
@@ -25,13 +25,17 @@
|
|
|
25
25
|
"./commands-manifest": {
|
|
26
26
|
"types": "./dist/commands-manifest.d.ts",
|
|
27
27
|
"import": "./dist/commands-manifest.js"
|
|
28
|
+
},
|
|
29
|
+
"./interchange": {
|
|
30
|
+
"types": "./dist/interchange-headless.d.ts",
|
|
31
|
+
"import": "./dist/interchange-headless.js"
|
|
28
32
|
}
|
|
29
33
|
},
|
|
30
34
|
"files": [
|
|
31
35
|
"dist"
|
|
32
36
|
],
|
|
33
37
|
"dependencies": {
|
|
34
|
-
"@formicoidea/labre-core": "0.
|
|
38
|
+
"@formicoidea/labre-core": "0.61.0",
|
|
35
39
|
"@preact/signals-core": "^1.8.0",
|
|
36
40
|
"lit": "^3.2.0",
|
|
37
41
|
"yjs": "^13.6.21"
|