@formicoidea/labre-framework-edgy 0.32.0 → 0.34.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/actions.d.ts +18 -0
- package/dist/actions.js +61 -5
- package/dist/board-renderer.js +4 -1
- package/dist/commands-manifest.d.ts +18 -0
- package/dist/commands-manifest.js +82 -0
- package/dist/commands.js +21 -3
- package/dist/element-renderer.js +5 -2
- package/dist/element-view.d.ts +14 -0
- package/dist/element-view.js +25 -7
- package/dist/label-layout.js +8 -2
- package/dist/legend.d.ts +2 -0
- package/dist/legend.js +109 -0
- package/dist/metamodel.d.ts +96 -0
- package/dist/metamodel.js +128 -0
- package/dist/node/consts.d.ts +12 -0
- package/dist/node/consts.js +12 -0
- package/dist/nudges.d.ts +26 -0
- package/dist/nudges.js +54 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +66 -0
- package/dist/relation-resolver.d.ts +32 -0
- package/dist/relation-resolver.js +101 -0
- package/dist/relation.d.ts +142 -0
- package/dist/relation.js +188 -0
- package/dist/roles.d.ts +108 -0
- package/dist/roles.js +182 -0
- package/dist/rules.d.ts +43 -0
- package/dist/rules.js +153 -0
- package/dist/templates/index.d.ts +5 -13
- package/dist/templates/index.js +210 -107
- package/dist/toolbar/config.d.ts +12 -4
- package/dist/toolbar/config.js +48 -5
- package/dist/toolbar/edgy-senior-button.js +8 -2
- package/dist/toolbar/icons.d.ts +8 -0
- package/dist/toolbar/icons.js +13 -0
- package/dist/toolbar/senior-tool.js +1 -0
- package/dist/translations.d.ts +17 -7
- package/dist/translations.js +38 -9
- package/dist/view.d.ts +8 -5
- package/dist/view.js +60 -10
- package/package.json +10 -3
package/dist/actions.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BlockStdScope } from '@formicoidea/labre-core/std';
|
|
2
|
+
import { type GfxController } from '@formicoidea/labre-core/std/gfx';
|
|
2
3
|
export type EdgyBoxKind = 'outcome' | 'object' | 'activity';
|
|
3
4
|
/**
|
|
4
5
|
* Create the Enterprise Design Facets diagram centred on the viewport —
|
|
@@ -19,3 +20,20 @@ export declare function createEdgyBox(std: BlockStdScope, kind: EdgyBoxKind): vo
|
|
|
19
20
|
* person glyph by the renderer, plus a native text label below, grouped.
|
|
20
21
|
*/
|
|
21
22
|
export declare function createEdgyPeople(std: BlockStdScope): void;
|
|
23
|
+
/**
|
|
24
|
+
* Activate the native connector tool for an EDGY RELATION: pre-styled like the
|
|
25
|
+
* 24 links of the metamodel template, and stamped with the GENERIC role
|
|
26
|
+
* `edgy:relation`.
|
|
27
|
+
*
|
|
28
|
+
* Generic on purpose, and this is the whole design (`./relation.ts`): the verb
|
|
29
|
+
* of an EDGY relation is determined by the ordered pair of elements it runs
|
|
30
|
+
* between — 24 rows, 24 distinct pairs — so there is nothing to ask the user
|
|
31
|
+
* and nothing to pick from a list of twenty-two. The tool arms the parent role,
|
|
32
|
+
* the user drags from the subject to the object, and `EdgyRelationResolver`
|
|
33
|
+
* writes the verb the metamodel gives that pair, as a role and as the label.
|
|
34
|
+
*
|
|
35
|
+
* The style is the template's, to the pixel: straight, `NODE_STROKE`, two units
|
|
36
|
+
* wide, and NO arrowhead at either end — EDGY's reference diagram draws its
|
|
37
|
+
* relations as bare lines and lets the verb say which way the sentence runs.
|
|
38
|
+
*/
|
|
39
|
+
export declare function activateEdgyRelation(gfx: GfxController): void;
|
package/dist/actions.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { ConnectorTool } from '@formicoidea/labre-core/gfx/connector';
|
|
2
3
|
import { createGroupCommand } from '@formicoidea/labre-core/gfx/group';
|
|
3
4
|
import { createTemplateJob } from '@formicoidea/labre-core/gfx/template';
|
|
4
|
-
import { FontFamily, ShapeStyle } from '@formicoidea/labre-core/model';
|
|
5
|
+
import { ConnectorMode, FontFamily, PointStyle, ShapeStyle, StrokeStyle, } from '@formicoidea/labre-core/model';
|
|
6
|
+
import { translateKey } from '@formicoidea/labre-core/shared/services';
|
|
5
7
|
import { Bound } from '@formicoidea/labre-core/global/gfx';
|
|
6
8
|
import { GfxControllerIdentifier } from '@formicoidea/labre-core/std/gfx';
|
|
7
9
|
import { CROP_LABELED } from './consts.js';
|
|
8
|
-
import { ACTIVITY_VERTICES, INNER_FONT_SIZE, LABEL_FONT_SIZE, LABEL_GAP, NODE_FILL, NODE_LABEL, NODE_SIZE, NODE_STROKE, NODE_STROKE_WIDTH, OUTCOME_RADIUS, } from './node/consts.js';
|
|
10
|
+
import { ACTIVITY_VERTICES, INNER_FONT_SIZE, LABEL_FONT_SIZE, LABEL_GAP, NODE_FILL, NODE_LABEL, nodeLabelKey, NODE_SIZE, NODE_STROKE, NODE_STROKE_WIDTH, OUTCOME_RADIUS, } from './node/consts.js';
|
|
11
|
+
import { EDGY_ROLE } from './roles.js';
|
|
9
12
|
import { edgyDynamicTemplate } from './templates/index.js';
|
|
13
|
+
/** Stroke width the 24 template relations are drawn with. */
|
|
14
|
+
const RELATION_STROKE_WIDTH = 2;
|
|
10
15
|
/** Default facets-diagram size (REF aspect, scaled up so it reads on canvas). */
|
|
11
16
|
const FACETS_SCALE = 1.5;
|
|
12
17
|
/** Default blank-board size. */
|
|
@@ -21,10 +26,20 @@ function finish(gfx, id) {
|
|
|
21
26
|
gfx.selection.set({ elements: [id], editing: false });
|
|
22
27
|
// Keep the palette open (native sub-menu behaviour).
|
|
23
28
|
}
|
|
24
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Shared props for an EDGY node shape.
|
|
31
|
+
*
|
|
32
|
+
* The `role` is the PERSISTED KIND and nothing more: somebody picking "Object"
|
|
33
|
+
* in the palette has said "this is an object", not "this is a Channel". The
|
|
34
|
+
* twelve official elements are named by the metamodel template, which knows
|
|
35
|
+
* which of them it is drawing; a base element created here specialises
|
|
36
|
+
* `edgy:element` through its kind and is judged by every rule written on the
|
|
37
|
+
* root — the overlap rule — and by none written on a leaf.
|
|
38
|
+
*/
|
|
25
39
|
const baseShapeProps = (kind) => ({
|
|
26
40
|
type: 'edgyNode',
|
|
27
41
|
kind,
|
|
42
|
+
role: EDGY_ROLE[kind],
|
|
28
43
|
filled: true,
|
|
29
44
|
fillColor: NODE_FILL,
|
|
30
45
|
strokeColor: NODE_STROKE,
|
|
@@ -57,6 +72,9 @@ export function createEdgyFacets(std) {
|
|
|
57
72
|
const id = gfx.surface.addElement({
|
|
58
73
|
type: 'edgy',
|
|
59
74
|
cropToCircles: true,
|
|
75
|
+
// The frame: what makes this an EDGY board rather than three circles, and
|
|
76
|
+
// what a finding is attributed to.
|
|
77
|
+
role: EDGY_ROLE.facets,
|
|
60
78
|
xywh: new Bound(centerX - width / 2, centerY - height / 2, width, height).serialize(),
|
|
61
79
|
});
|
|
62
80
|
finish(gfx, id);
|
|
@@ -86,6 +104,9 @@ export function createEdgyBoard(std) {
|
|
|
86
104
|
const { centerX, centerY } = gfx.viewport;
|
|
87
105
|
const id = gfx.surface.addElement({
|
|
88
106
|
type: 'edgyBoard',
|
|
107
|
+
// The blank board is a frame too: the same rules apply to what is drawn on
|
|
108
|
+
// it, and the same map-wide arbitration has to have somewhere to live.
|
|
109
|
+
role: EDGY_ROLE.board,
|
|
89
110
|
xywh: new Bound(centerX - BOARD_W / 2, centerY - BOARD_H / 2, BOARD_W, BOARD_H).serialize(),
|
|
90
111
|
});
|
|
91
112
|
finish(gfx, id);
|
|
@@ -105,7 +126,9 @@ export function createEdgyBox(std, kind) {
|
|
|
105
126
|
strokeWidth: NODE_STROKE_WIDTH,
|
|
106
127
|
radius: kind === 'outcome' ? OUTCOME_RADIUS : 0,
|
|
107
128
|
vertices: kind === 'activity' ? ACTIVITY_VERTICES : null,
|
|
108
|
-
|
|
129
|
+
// Translated HERE and once: what a gesture writes onto the canvas is
|
|
130
|
+
// document content the moment it lands (`nodeLabelKey`).
|
|
131
|
+
text: translateKey(std, nodeLabelKey(kind), NODE_LABEL[kind]),
|
|
109
132
|
color: NODE_STROKE,
|
|
110
133
|
fontFamily: FontFamily.Inter,
|
|
111
134
|
fontSize: INNER_FONT_SIZE,
|
|
@@ -132,9 +155,42 @@ export function createEdgyPeople(std) {
|
|
|
132
155
|
strokeWidth: 0,
|
|
133
156
|
xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
|
|
134
157
|
});
|
|
135
|
-
const labelId = addLabel(surface, NODE_LABEL.people, cx - 60, cy + h / 2 + LABEL_GAP);
|
|
158
|
+
const labelId = addLabel(surface, translateKey(std, nodeLabelKey('people'), NODE_LABEL.people), cx - 60, cy + h / 2 + LABEL_GAP);
|
|
136
159
|
const [, result] = std.command.exec(createGroupCommand, {
|
|
137
160
|
elements: [nodeId, labelId],
|
|
138
161
|
});
|
|
139
162
|
finish(gfx, result.groupId || nodeId);
|
|
140
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Activate the native connector tool for an EDGY RELATION: pre-styled like the
|
|
166
|
+
* 24 links of the metamodel template, and stamped with the GENERIC role
|
|
167
|
+
* `edgy:relation`.
|
|
168
|
+
*
|
|
169
|
+
* Generic on purpose, and this is the whole design (`./relation.ts`): the verb
|
|
170
|
+
* of an EDGY relation is determined by the ordered pair of elements it runs
|
|
171
|
+
* between — 24 rows, 24 distinct pairs — so there is nothing to ask the user
|
|
172
|
+
* and nothing to pick from a list of twenty-two. The tool arms the parent role,
|
|
173
|
+
* the user drags from the subject to the object, and `EdgyRelationResolver`
|
|
174
|
+
* writes the verb the metamodel gives that pair, as a role and as the label.
|
|
175
|
+
*
|
|
176
|
+
* The style is the template's, to the pixel: straight, `NODE_STROKE`, two units
|
|
177
|
+
* wide, and NO arrowhead at either end — EDGY's reference diagram draws its
|
|
178
|
+
* relations as bare lines and lets the verb say which way the sentence runs.
|
|
179
|
+
*/
|
|
180
|
+
export function activateEdgyRelation(gfx) {
|
|
181
|
+
gfx.tool.setTool(ConnectorTool, {
|
|
182
|
+
mode: ConnectorMode.Straight,
|
|
183
|
+
role: EDGY_ROLE.relation,
|
|
184
|
+
// The look rides on the activation, never through the last-props store:
|
|
185
|
+
// the plain connector tool must keep the user's own style (#144 M1).
|
|
186
|
+
style: {
|
|
187
|
+
stroke: NODE_STROKE,
|
|
188
|
+
strokeStyle: StrokeStyle.Solid,
|
|
189
|
+
strokeWidth: RELATION_STROKE_WIDTH,
|
|
190
|
+
frontEndpointStyle: PointStyle.None,
|
|
191
|
+
rearEndpointStyle: PointStyle.None,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
// Keep the palette open (native sub-menu behaviour): it only closes on
|
|
195
|
+
// re-click of the senior button, another senior tool, or Escape.
|
|
196
|
+
}
|
package/dist/board-renderer.js
CHANGED
|
@@ -13,7 +13,10 @@ export const edgyBoard = (model, ctx, matrix) => {
|
|
|
13
13
|
const [, , w, h] = model.deserializedXYWH;
|
|
14
14
|
const cx = w / 2;
|
|
15
15
|
const cy = h / 2;
|
|
16
|
-
ctx.setTransform(matrix
|
|
16
|
+
ctx.setTransform(matrix
|
|
17
|
+
.translateSelf(cx, cy)
|
|
18
|
+
.rotateSelf(model.rotate)
|
|
19
|
+
.translateSelf(-cx, -cy));
|
|
17
20
|
ctx.beginPath();
|
|
18
21
|
ctx.roundRect(0, 0, w, h, BOARD_RADIUS);
|
|
19
22
|
ctx.fillStyle = BOARD_FILL;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ShortcutManifestEntry } from '@formicoidea/labre-core/std';
|
|
2
|
+
/**
|
|
3
|
+
* The EDGY commands as SHORTCUT-MANIFEST rows — id, label, chord, scope,
|
|
4
|
+
* owner — and nothing else.
|
|
5
|
+
*
|
|
6
|
+
* DATA ONLY, and that is the whole point (`docs/adr/0008` § Packaging). A
|
|
7
|
+
* `CommandDescriptor` carries its `run`, so a host settings pane that imports
|
|
8
|
+
* the package entry to list names and chords drags the entire action graph —
|
|
9
|
+
* the import/export machinery, the surface and gfx deep paths — into its
|
|
10
|
+
* chunk. This module has type-only imports, so the published bundle exposes it
|
|
11
|
+
* as `./commands-manifest`: a few hundred bytes that reference nothing.
|
|
12
|
+
*
|
|
13
|
+
* GENERATED-SHAPED, hand-committed: `commands-manifest.unit.spec.ts` asserts
|
|
14
|
+
* row-for-row equality with `toShortcutManifestEntry` over {@link edgyCommands},
|
|
15
|
+
* so this file cannot drift from the commands it projects. Add a command and
|
|
16
|
+
* the test says exactly what to add here.
|
|
17
|
+
*/
|
|
18
|
+
export declare const edgyCommandsManifest: ShortcutManifestEntry[];
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The EDGY commands as SHORTCUT-MANIFEST rows — id, label, chord, scope,
|
|
3
|
+
* owner — and nothing else.
|
|
4
|
+
*
|
|
5
|
+
* DATA ONLY, and that is the whole point (`docs/adr/0008` § Packaging). A
|
|
6
|
+
* `CommandDescriptor` carries its `run`, so a host settings pane that imports
|
|
7
|
+
* the package entry to list names and chords drags the entire action graph —
|
|
8
|
+
* the import/export machinery, the surface and gfx deep paths — into its
|
|
9
|
+
* chunk. This module has type-only imports, so the published bundle exposes it
|
|
10
|
+
* as `./commands-manifest`: a few hundred bytes that reference nothing.
|
|
11
|
+
*
|
|
12
|
+
* GENERATED-SHAPED, hand-committed: `commands-manifest.unit.spec.ts` asserts
|
|
13
|
+
* row-for-row equality with `toShortcutManifestEntry` over {@link edgyCommands},
|
|
14
|
+
* so this file cannot drift from the commands it projects. Add a command and
|
|
15
|
+
* the test says exactly what to add here.
|
|
16
|
+
*/
|
|
17
|
+
export const edgyCommandsManifest = [
|
|
18
|
+
{
|
|
19
|
+
id: 'edgy.addFacets',
|
|
20
|
+
owner: 'edgy',
|
|
21
|
+
labelKey: 'com.labre.commands.edgy.addFacets',
|
|
22
|
+
labelFallback: 'Enterprise Design facets',
|
|
23
|
+
scope: 'edgeless',
|
|
24
|
+
defaultKeys: { mac: [], other: [] },
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'edgy.insertDynamic',
|
|
28
|
+
owner: 'edgy',
|
|
29
|
+
labelKey: 'com.labre.commands.edgy.insertDynamic',
|
|
30
|
+
labelFallback: 'EDGY dynamic (elements & relations)',
|
|
31
|
+
scope: 'edgeless',
|
|
32
|
+
defaultKeys: { mac: [], other: [] },
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'edgy.addBoard',
|
|
36
|
+
owner: 'edgy',
|
|
37
|
+
labelKey: 'com.labre.commands.edgy.addBoard',
|
|
38
|
+
labelFallback: 'EDGY board (hover spotlight)',
|
|
39
|
+
scope: 'edgeless',
|
|
40
|
+
defaultKeys: { mac: [], other: [] },
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'edgy.addPeople',
|
|
44
|
+
owner: 'edgy',
|
|
45
|
+
labelKey: 'com.labre.commands.edgy.addPeople',
|
|
46
|
+
labelFallback: 'People',
|
|
47
|
+
scope: 'edgeless',
|
|
48
|
+
defaultKeys: { mac: [], other: [] },
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'edgy.addOutcome',
|
|
52
|
+
owner: 'edgy',
|
|
53
|
+
labelKey: 'com.labre.commands.edgy.addOutcome',
|
|
54
|
+
labelFallback: 'Outcome',
|
|
55
|
+
scope: 'edgeless',
|
|
56
|
+
defaultKeys: { mac: [], other: [] },
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'edgy.addObject',
|
|
60
|
+
owner: 'edgy',
|
|
61
|
+
labelKey: 'com.labre.commands.edgy.addObject',
|
|
62
|
+
labelFallback: 'Object',
|
|
63
|
+
scope: 'edgeless',
|
|
64
|
+
defaultKeys: { mac: [], other: [] },
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'edgy.addActivity',
|
|
68
|
+
owner: 'edgy',
|
|
69
|
+
labelKey: 'com.labre.commands.edgy.addActivity',
|
|
70
|
+
labelFallback: 'Activity',
|
|
71
|
+
scope: 'edgeless',
|
|
72
|
+
defaultKeys: { mac: [], other: [] },
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'edgy.addRelation',
|
|
76
|
+
owner: 'edgy',
|
|
77
|
+
labelKey: 'com.labre.commands.edgy.addRelation',
|
|
78
|
+
labelFallback: 'Relation',
|
|
79
|
+
scope: 'edgeless',
|
|
80
|
+
defaultKeys: { mac: [], other: [] },
|
|
81
|
+
},
|
|
82
|
+
];
|
package/dist/commands.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { GfxControllerIdentifier } from '@formicoidea/labre-core/std/gfx';
|
|
2
|
+
import { activateEdgyRelation, createEdgyBoard, createEdgyBox, createEdgyDynamic, createEdgyFacets, createEdgyPeople, } from './actions.js';
|
|
3
|
+
import { edgyActivityIcon, edgyBoardIcon, edgyDynamicIcon, edgyFacetsIcon, edgyObjectIcon, edgyOutcomeIcon, edgyPeopleIcon, edgyRelationIcon, } from './toolbar/icons.js';
|
|
3
4
|
const SPECS = [
|
|
4
5
|
{
|
|
5
6
|
id: 'addFacets',
|
|
@@ -57,13 +58,29 @@ const SPECS = [
|
|
|
57
58
|
element: 'node:activity',
|
|
58
59
|
run: std => createEdgyBox(std, 'activity'),
|
|
59
60
|
},
|
|
61
|
+
{
|
|
62
|
+
id: 'addRelation',
|
|
63
|
+
label: 'Relation',
|
|
64
|
+
description: 'Drag from the element that is the subject of the relation to the one it is about; EDGY names the link itself.',
|
|
65
|
+
iconKey: 'edgy.relation',
|
|
66
|
+
category: 'relations',
|
|
67
|
+
kind: 'tool',
|
|
68
|
+
element: 'connector:relation',
|
|
69
|
+
run: std => activateEdgyRelation(std.get(GfxControllerIdentifier)),
|
|
70
|
+
},
|
|
60
71
|
];
|
|
61
72
|
export const edgyCommands = SPECS.map((spec, order) => ({
|
|
62
73
|
id: `edgy.${spec.id}`,
|
|
63
74
|
owner: 'edgy',
|
|
64
|
-
kind: 'artefact',
|
|
75
|
+
kind: spec.kind ?? 'artefact',
|
|
65
76
|
labelKey: `com.labre.commands.edgy.${spec.id}`,
|
|
66
77
|
labelFallback: spec.label,
|
|
78
|
+
...(spec.description === undefined
|
|
79
|
+
? {}
|
|
80
|
+
: {
|
|
81
|
+
descriptionKey: `com.labre.commands.edgy.${spec.id}.description`,
|
|
82
|
+
descriptionFallback: spec.description,
|
|
83
|
+
}),
|
|
67
84
|
category: spec.category,
|
|
68
85
|
iconKey: spec.iconKey,
|
|
69
86
|
surfaces: ['senior-menu', 'catalogue', 'palette', 'agent'],
|
|
@@ -82,4 +99,5 @@ export const edgyCommandIcons = {
|
|
|
82
99
|
'edgy.outcome': edgyOutcomeIcon,
|
|
83
100
|
'edgy.object': edgyObjectIcon,
|
|
84
101
|
'edgy.activity': edgyActivityIcon,
|
|
102
|
+
'edgy.relation': edgyRelationIcon,
|
|
85
103
|
};
|
package/dist/element-renderer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
2
|
import { COLORS, cropLabeledScale, cropScale, FONT_FAMILY, LABEL_FONT_SIZE, PICTO_STROKE, REF_H, REF_W, refScale, VENN, } from './consts.js';
|
|
3
|
-
import { CIRCLE_A, CIRCLE_B, CIRCLE_C, facetLabelAnchors } from './label-layout.js';
|
|
3
|
+
import { CIRCLE_A, CIRCLE_B, CIRCLE_C, facetLabelAnchors, } from './label-layout.js';
|
|
4
4
|
/**
|
|
5
5
|
* Canvas renderer for the EDGY Enterprise Design Facets diagram — reproduces the
|
|
6
6
|
* validated mockup: three overlapping circles (Identity / Architecture /
|
|
@@ -15,7 +15,10 @@ export const edgy = (model, ctx, matrix) => {
|
|
|
15
15
|
const [, , w, h] = model.deserializedXYWH;
|
|
16
16
|
const cx = w / 2;
|
|
17
17
|
const cy = h / 2;
|
|
18
|
-
ctx.setTransform(matrix
|
|
18
|
+
ctx.setTransform(matrix
|
|
19
|
+
.translateSelf(cx, cy)
|
|
20
|
+
.rotateSelf(model.rotate)
|
|
21
|
+
.translateSelf(-cx, -cy));
|
|
19
22
|
// Uniform fit of the reference design, centered (letterboxed) — or, when
|
|
20
23
|
// the diagram is cropped, of the circles' bounding box (plus the facet
|
|
21
24
|
// label allowance if the labels are shown).
|
package/dist/element-view.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { EdgyFacetsElementModel } from '@formicoidea/labre-core/model';
|
|
2
|
+
import type { EditorHost } from '@formicoidea/labre-core/std';
|
|
3
|
+
import type { PointTestOptions } from '@formicoidea/labre-core/std/gfx';
|
|
2
4
|
import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
|
|
3
5
|
export declare class EdgyView extends GfxElementModelView<EdgyFacetsElementModel> {
|
|
4
6
|
static type: string;
|
|
@@ -6,6 +8,18 @@ export declare class EdgyView extends GfxElementModelView<EdgyFacetsElementModel
|
|
|
6
8
|
private _labelEditor;
|
|
7
9
|
onCreated(): void;
|
|
8
10
|
onDestroyed(): void;
|
|
11
|
+
/** The editable facet label under a MODEL-space point, or null. */
|
|
12
|
+
private _labelAt;
|
|
13
|
+
/**
|
|
14
|
+
* The diagram is SELECTED by its border (`EdgyFacetsElementModel`), but its
|
|
15
|
+
* three facet labels must still receive the double-click that renames them.
|
|
16
|
+
*
|
|
17
|
+
* Same seam Wardley uses, for the same reason: the pointer router asks the
|
|
18
|
+
* VIEW, so a framework declares its own gesture zones next to the code that
|
|
19
|
+
* draws them, and the model layer keeps a hit test that only says what a
|
|
20
|
+
* click SELECTS.
|
|
21
|
+
*/
|
|
22
|
+
includesPoint(x: number, y: number, options: PointTestOptions, host: EditorHost): boolean;
|
|
9
23
|
/** Double-click on a label → edit its text in place. */
|
|
10
24
|
private _onDblClick;
|
|
11
25
|
private _openLabelEditor;
|
package/dist/element-view.js
CHANGED
|
@@ -18,14 +18,11 @@ export class EdgyView extends GfxElementModelView {
|
|
|
18
18
|
this._closeLabelEditor();
|
|
19
19
|
super.onDestroyed();
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
if (this.model.isLocked())
|
|
24
|
-
return;
|
|
21
|
+
/** The editable facet label under a MODEL-space point, or null. */
|
|
22
|
+
_labelAt(mx, my) {
|
|
25
23
|
// No labels shown → nothing to edit (and the cropped mapping differs).
|
|
26
24
|
if (!this.model.showLabels)
|
|
27
|
-
return;
|
|
28
|
-
const [mx, my] = this.gfx.viewport.toModelCoord(e.x, e.y);
|
|
25
|
+
return null;
|
|
29
26
|
const [bx, by, w, h] = this.model.deserializedXYWH;
|
|
30
27
|
// Convert the model-space point into element-local coordinates, undoing the
|
|
31
28
|
// element rotation around its center.
|
|
@@ -45,7 +42,28 @@ export class EdgyView extends GfxElementModelView {
|
|
|
45
42
|
: refScale(w, h);
|
|
46
43
|
const rx = (lx - ox) / s;
|
|
47
44
|
const ry = (ly - oy) / s;
|
|
48
|
-
|
|
45
|
+
return hitTestEdgyLabel(getEdgyLabelHits(this.model), rx, ry);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The diagram is SELECTED by its border (`EdgyFacetsElementModel`), but its
|
|
49
|
+
* three facet labels must still receive the double-click that renames them.
|
|
50
|
+
*
|
|
51
|
+
* Same seam Wardley uses, for the same reason: the pointer router asks the
|
|
52
|
+
* VIEW, so a framework declares its own gesture zones next to the code that
|
|
53
|
+
* draws them, and the model layer keeps a hit test that only says what a
|
|
54
|
+
* click SELECTS.
|
|
55
|
+
*/
|
|
56
|
+
includesPoint(x, y, options, host) {
|
|
57
|
+
if (super.includesPoint(x, y, options, host))
|
|
58
|
+
return true;
|
|
59
|
+
return this._labelAt(x, y) !== null;
|
|
60
|
+
}
|
|
61
|
+
/** Double-click on a label → edit its text in place. */
|
|
62
|
+
_onDblClick(e) {
|
|
63
|
+
if (this.model.isLocked())
|
|
64
|
+
return;
|
|
65
|
+
const [mx, my] = this.gfx.viewport.toModelCoord(e.x, e.y);
|
|
66
|
+
const hit = this._labelAt(mx, my);
|
|
49
67
|
if (!hit)
|
|
50
68
|
return;
|
|
51
69
|
this._openLabelEditor(hit.field, e);
|
package/dist/label-layout.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { LABEL_FONT_SIZE, VENN } from './consts.js';
|
|
2
2
|
/** The three circle centres in reference coords. */
|
|
3
|
-
export const CIRCLE_A = {
|
|
4
|
-
|
|
3
|
+
export const CIRCLE_A = {
|
|
4
|
+
x: VENN.cx - 0.866 * VENN.r0,
|
|
5
|
+
y: VENN.cy - 0.5 * VENN.r0,
|
|
6
|
+
}; // Identity
|
|
7
|
+
export const CIRCLE_B = {
|
|
8
|
+
x: VENN.cx + 0.866 * VENN.r0,
|
|
9
|
+
y: VENN.cy - 0.5 * VENN.r0,
|
|
10
|
+
}; // Architecture
|
|
5
11
|
export const CIRCLE_C = { x: VENN.cx, y: VENN.cy + VENN.r0 }; // Experience
|
|
6
12
|
/**
|
|
7
13
|
* The three facet name anchors in reference coords, positioned fully outside
|
package/dist/legend.d.ts
ADDED
package/dist/legend.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { roleLabel, } from '@formicoidea/labre-ddd-shared';
|
|
2
|
+
import { EDGY_DYNAMIC_NODES, EDGY_ZONE_FILL, EDGY_ZONES, edgyElementLabel, } from './metamodel.js';
|
|
3
|
+
import { NODE_FILL, NODE_STROKE } from './node/consts.js';
|
|
4
|
+
import { EDGY_ROLE, EDGY_ROLES } from './roles.js';
|
|
5
|
+
/**
|
|
6
|
+
* What an EDGY background's automatic legend can say — a TABLE, and nothing
|
|
7
|
+
* else: the scan, the placement and the box are `createAutoLegend`'s job, the
|
|
8
|
+
* same generic the three DDD backgrounds were given
|
|
9
|
+
* (`@labre/affine-gfx-ddd-shared`, `shared/legend-auto.ts`). EDGY takes a
|
|
10
|
+
* dependency on that package for the generic and the box prefab alone; moving
|
|
11
|
+
* them to a neutral home would be the tidier answer and is not worth the churn
|
|
12
|
+
* of this fix.
|
|
13
|
+
*
|
|
14
|
+
* Every row is DERIVED — from the metamodel (`./metamodel.ts`: which elements
|
|
15
|
+
* exist, which zone each belongs to, what colour that zone is drawn in) and
|
|
16
|
+
* from the role vocabulary's own labels (`./roles.ts`). Nothing here is
|
|
17
|
+
* restated: a thirteenth element added to the metamodel gets its legend row the
|
|
18
|
+
* same way it gets its role, with no edit to this file.
|
|
19
|
+
*
|
|
20
|
+
* ## The order
|
|
21
|
+
*
|
|
22
|
+
* By ZONE, and the order of the table carries it: the three facets first
|
|
23
|
+
* (Identity, Architecture, Experience), then the three intersections in one
|
|
24
|
+
* section, then the four base elements, then the relations — which is the
|
|
25
|
+
* reading order of {@link EDGY_ZONES} and, before it, of the Venn itself.
|
|
26
|
+
*/
|
|
27
|
+
/** One row per official element of `zone`, in the metamodel's own order. */
|
|
28
|
+
function zoneEntries(zone) {
|
|
29
|
+
return Object.entries(EDGY_DYNAMIC_NODES)
|
|
30
|
+
.filter(([, node]) => node.zone === zone)
|
|
31
|
+
.map(([name]) => ({
|
|
32
|
+
role: EDGY_ROLE[name],
|
|
33
|
+
row: {
|
|
34
|
+
swatch: 'square',
|
|
35
|
+
// The zone's fill, which IS what the diagram paints the element with.
|
|
36
|
+
color: EDGY_ZONE_FILL[zone],
|
|
37
|
+
label: roleLabel(EDGY_ROLES, EDGY_ROLE[name]),
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
const FACET_SECTIONS = EDGY_ZONES.filter(zone => zone.group === 'facet').map(zone => ({
|
|
42
|
+
title: edgyElementLabel(zone.id),
|
|
43
|
+
entries: zoneEntries(zone.id),
|
|
44
|
+
}));
|
|
45
|
+
/**
|
|
46
|
+
* The three intersection elements in ONE section: Organisation, Product and
|
|
47
|
+
* Brand are not three facets, they are the three lenses between them, and the
|
|
48
|
+
* diagram says so by drawing them in the overlaps. Each keeps its own colour.
|
|
49
|
+
*/
|
|
50
|
+
const INTERSECTIONS_SECTION = {
|
|
51
|
+
title: 'Intersections',
|
|
52
|
+
entries: EDGY_ZONES.filter(zone => zone.group === 'intersection').flatMap(zone => zoneEntries(zone.id)),
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* The four base elements, listed only when one is on the board BARE — an
|
|
56
|
+
* element the user dropped from the toolbox and left as a People, an Outcome,
|
|
57
|
+
* an Object or an Activity, without saying which of the twelve it is. White,
|
|
58
|
+
* because that is the fill the palette gives them (`node/consts.ts`).
|
|
59
|
+
*
|
|
60
|
+
* `exact` is what makes "bare" true. Detection is otherwise an ancestor walk,
|
|
61
|
+
* and the twelve official elements specialise these four — Content is an
|
|
62
|
+
* object, Story an activity — so an "Object" row would appear on a board
|
|
63
|
+
* carrying nothing but Contents, keyed to a white swatch that is drawn nowhere
|
|
64
|
+
* on it. The relation entry below keeps the walk, because there the parent row
|
|
65
|
+
* IS the fair summary: a board carrying `edgy:expresses` carries a relation.
|
|
66
|
+
*/
|
|
67
|
+
const BASE_SECTION = {
|
|
68
|
+
title: 'Base elements',
|
|
69
|
+
entries: ['people', 'outcome', 'object', 'activity'].map(kind => ({
|
|
70
|
+
role: EDGY_ROLE[kind],
|
|
71
|
+
exact: true,
|
|
72
|
+
row: {
|
|
73
|
+
swatch: 'square',
|
|
74
|
+
color: NODE_FILL,
|
|
75
|
+
label: roleLabel(EDGY_ROLES, EDGY_ROLE[kind]),
|
|
76
|
+
},
|
|
77
|
+
})),
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* ONE row for the whole relation family, keyed on the PARENT role: a board
|
|
81
|
+
* carrying "content expresses purpose" carries `edgy:expresses`, which is a
|
|
82
|
+
* relation, and that is all the legend has to say. Twenty-two rows naming the
|
|
83
|
+
* verbs would restate the metamodel rather than document the drawing — and the
|
|
84
|
+
* verb travels on the link itself, visible, where it belongs.
|
|
85
|
+
*/
|
|
86
|
+
const RELATIONS_SECTION = {
|
|
87
|
+
title: 'Relations',
|
|
88
|
+
entries: [
|
|
89
|
+
{
|
|
90
|
+
role: EDGY_ROLE.relation,
|
|
91
|
+
row: {
|
|
92
|
+
swatch: 'line',
|
|
93
|
+
// The stroke `activateEdgyRelation` arms the connector tool with.
|
|
94
|
+
color: NODE_STROKE,
|
|
95
|
+
label: roleLabel(EDGY_ROLES, EDGY_ROLE.relation),
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
export const EDGY_AUTO_LEGEND = {
|
|
101
|
+
title: 'Legend',
|
|
102
|
+
roles: EDGY_ROLES,
|
|
103
|
+
sections: [
|
|
104
|
+
...FACET_SECTIONS,
|
|
105
|
+
INTERSECTIONS_SECTION,
|
|
106
|
+
BASE_SECTION,
|
|
107
|
+
RELATIONS_SECTION,
|
|
108
|
+
],
|
|
109
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The EDGY metamodel, as DATA — the 12 official elements and the 24 canonical
|
|
3
|
+
* relations that link them.
|
|
4
|
+
*
|
|
5
|
+
* It used to live in `templates/index.ts`, where it was drawn. It moved here
|
|
6
|
+
* the day the roles started being DERIVED from it (`./roles.ts`): the template
|
|
7
|
+
* needs the vocabulary to stamp its elements, and the vocabulary needs the
|
|
8
|
+
* metamodel to know which verbs exist, so a single module holding both would be
|
|
9
|
+
* a cycle. Nothing was renamed — `templates/index.ts` re-exports both tables
|
|
10
|
+
* under the names they always had.
|
|
11
|
+
*
|
|
12
|
+
* This file is the ONE place the metamodel is written down. `roles.ts` derives
|
|
13
|
+
* an edge role per verb from it, `rules.ts` derives the sanctioned sentences of
|
|
14
|
+
* `edgy.non-canonical-link` from it, and the template draws it. Restating the
|
|
15
|
+
* matrix anywhere else would be inviting the three to disagree.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Official pastel fills per zone (the `pictograms/Shape-*.svg` colors) — the
|
|
19
|
+
* colour an element of that zone is DRAWN with, on the diagram and therefore in
|
|
20
|
+
* the legend that documents it.
|
|
21
|
+
*
|
|
22
|
+
* Exported because the legend needs the same swatch the board uses: a swatch
|
|
23
|
+
* restated as a second literal would be a colour free to drift away from the
|
|
24
|
+
* one the eye sees.
|
|
25
|
+
*/
|
|
26
|
+
export declare const EDGY_ZONE_FILL: {
|
|
27
|
+
readonly identity: "#80ffb7";
|
|
28
|
+
readonly architecture: "#a6c0ff";
|
|
29
|
+
readonly experience: "#ff99bd";
|
|
30
|
+
readonly organisation: "#80eaff";
|
|
31
|
+
readonly brand: "#ffd580";
|
|
32
|
+
readonly product: "#e599ff";
|
|
33
|
+
};
|
|
34
|
+
/** One of the six regions of the Venn: three facets, three intersections. */
|
|
35
|
+
export type EdgyZone = keyof typeof EDGY_ZONE_FILL;
|
|
36
|
+
/**
|
|
37
|
+
* The six zones, in reading order — the three FACETS first, then the three
|
|
38
|
+
* INTERSECTIONS, each named by the pair of circles it is the lens of (the very
|
|
39
|
+
* pairs `element-renderer.ts` clips: A∩B, B∩C, A∩C).
|
|
40
|
+
*
|
|
41
|
+
* Written down as data because the split is otherwise readable only inside the
|
|
42
|
+
* renderer's paint calls, and the legend has to group the twelve elements by
|
|
43
|
+
* it. `group` is the only thing here a reader could not already derive from
|
|
44
|
+
* {@link EDGY_DYNAMIC_NODES}.
|
|
45
|
+
*/
|
|
46
|
+
export declare const EDGY_ZONES: readonly {
|
|
47
|
+
id: EdgyZone;
|
|
48
|
+
group: 'facet' | 'intersection';
|
|
49
|
+
}[];
|
|
50
|
+
/**
|
|
51
|
+
* The 24 canonical EDGY relations (source, target, verb, label position along
|
|
52
|
+
* the link) — exported for the unit tests. 7 per facet + 3 between the
|
|
53
|
+
* intersections. The optional 4th member mirrors the reference diagram's
|
|
54
|
+
* placements: verbs of intersection-outgoing links sit near the far element
|
|
55
|
+
* (`labelOffset.distance` ≈ .75), short peer links keep the middle.
|
|
56
|
+
*
|
|
57
|
+
* Read the way `docs/adr/0010` reads every typed edge: the SOURCE is the
|
|
58
|
+
* subject of the verb and the TARGET its object, so each row is one sentence a
|
|
59
|
+
* practitioner would say out loud — "content expresses purpose". That is what
|
|
60
|
+
* makes the row usable as an {@link EndpointTriplet} without a single
|
|
61
|
+
* coordinate taking part.
|
|
62
|
+
*/
|
|
63
|
+
export declare const EDGY_DYNAMIC_RELATIONS: [string, string, string, number?][];
|
|
64
|
+
/**
|
|
65
|
+
* The 12 elements, centred coordinates in REFERENCE coords (the fixed space
|
|
66
|
+
* of consts.ts — the same space as `VENN`), laid out like the reference
|
|
67
|
+
* "elements & relations" diagram: aligned top row, Story/Capability flanks,
|
|
68
|
+
* Brand/Product astride the white centre, Task/Journey/Channel triangle.
|
|
69
|
+
* Exported (with `dynToModel`) for the containment test.
|
|
70
|
+
*
|
|
71
|
+
* `kind` is the OFFICIAL kind of each element — the base shape EDGY draws it
|
|
72
|
+
* with — and `roles.ts` reads it to give each of the twelve leaf roles its
|
|
73
|
+
* parent. Written once, here, rather than restated as a second table nobody
|
|
74
|
+
* would think to keep in step.
|
|
75
|
+
*
|
|
76
|
+
* `zone` is the region of the Venn the element belongs to, and it REPLACES the
|
|
77
|
+
* fill this table used to carry: the fill is `EDGY_ZONE_FILL[zone]` and nothing
|
|
78
|
+
* else, so an element cannot be filed under one facet and painted another's
|
|
79
|
+
* colour. The template reads it to draw, the legend reads it to group and to
|
|
80
|
+
* swatch.
|
|
81
|
+
*/
|
|
82
|
+
export type EdgyElementName = 'content' | 'purpose' | 'organisation' | 'process' | 'asset' | 'story' | 'capability' | 'brand' | 'product' | 'task' | 'journey' | 'channel';
|
|
83
|
+
/**
|
|
84
|
+
* The metamodel's own wording for one of its lowercase names — one of the
|
|
85
|
+
* twelve elements, or one of the six zones. It is the name written on the
|
|
86
|
+
* diagram, so it is also what the vocabulary falls back to and what the legend
|
|
87
|
+
* prints as a row label or a section title. One expression, one wording.
|
|
88
|
+
*/
|
|
89
|
+
export declare const edgyElementLabel: (name: EdgyElementName | EdgyZone) => string;
|
|
90
|
+
export declare const EDGY_DYNAMIC_NODES: Record<EdgyElementName, {
|
|
91
|
+
kind: 'outcome' | 'object' | 'activity';
|
|
92
|
+
cx: number;
|
|
93
|
+
cy: number;
|
|
94
|
+
w?: number;
|
|
95
|
+
zone: EdgyZone;
|
|
96
|
+
}>;
|