@formicoidea/labre-framework-edgy 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions.d.ts +39 -0
- package/dist/actions.js +193 -0
- package/dist/board-renderer.d.ts +12 -0
- package/dist/board-renderer.js +28 -0
- package/dist/board-view.d.ts +11 -0
- package/dist/board-view.js +20 -0
- package/dist/commands.d.ts +4 -0
- package/dist/commands.js +103 -0
- package/dist/consts.d.ts +35 -0
- package/dist/consts.js +46 -0
- package/dist/descriptor.d.ts +8 -3
- package/dist/descriptor.js +6 -3
- package/dist/element-renderer.js +24 -13
- package/dist/element-view.js +9 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- 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/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 +22 -1
- package/dist/templates/index.js +303 -52
- package/dist/toolbar/config.d.ts +33 -0
- package/dist/toolbar/config.js +82 -15
- package/dist/toolbar/edgy-menu.d.ts +7 -25
- package/dist/toolbar/edgy-menu.js +7 -186
- package/dist/toolbar/edgy-senior-button.js +8 -2
- package/dist/toolbar/icons.d.ts +12 -0
- package/dist/toolbar/icons.js +31 -0
- package/dist/toolbar/senior-tool.js +1 -0
- package/dist/translations.d.ts +24 -0
- package/dist/translations.js +29 -0
- package/dist/view.d.ts +18 -0
- package/dist/view.js +95 -8
- package/package.json +6 -3
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { BlockStdScope } from '@formicoidea/labre-core/std';
|
|
2
|
+
import { type GfxController } from '@formicoidea/labre-core/std/gfx';
|
|
3
|
+
export type EdgyBoxKind = 'outcome' | 'object' | 'activity';
|
|
4
|
+
/**
|
|
5
|
+
* Create the Enterprise Design Facets diagram centred on the viewport —
|
|
6
|
+
* cropped to the circles + facet labels (no dead margins around the Venn).
|
|
7
|
+
*/
|
|
8
|
+
export declare function createEdgyFacets(std: BlockStdScope): void;
|
|
9
|
+
/**
|
|
10
|
+
* Insert the "EDGY dynamic" template (facets background + the 12 elements
|
|
11
|
+
* linked by the 24 verbalised relations), then bring it into view.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createEdgyDynamic(std: BlockStdScope): Promise<void>;
|
|
14
|
+
/** Create a blank EDGY board (hover spotlight host) centred on the viewport. */
|
|
15
|
+
export declare function createEdgyBoard(std: BlockStdScope): void;
|
|
16
|
+
/** Create a box base element (outcome / object / activity) with inner text. */
|
|
17
|
+
export declare function createEdgyBox(std: BlockStdScope, kind: EdgyBoxKind): void;
|
|
18
|
+
/**
|
|
19
|
+
* Create the People base element: an (invisible) ellipse decorated with the
|
|
20
|
+
* person glyph by the renderer, plus a native text label below, grouped.
|
|
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
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { ConnectorTool } from '@formicoidea/labre-core/gfx/connector';
|
|
3
|
+
import { createGroupCommand } from '@formicoidea/labre-core/gfx/group';
|
|
4
|
+
import { createTemplateJob } from '@formicoidea/labre-core/gfx/template';
|
|
5
|
+
import { ConnectorMode, FontFamily, PointStyle, ShapeStyle, StrokeStyle, } from '@formicoidea/labre-core/model';
|
|
6
|
+
import { Bound } from '@formicoidea/labre-core/global/gfx';
|
|
7
|
+
import { GfxControllerIdentifier } from '@formicoidea/labre-core/std/gfx';
|
|
8
|
+
import { CROP_LABELED } from './consts.js';
|
|
9
|
+
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 { EDGY_ROLE } from './roles.js';
|
|
11
|
+
import { edgyDynamicTemplate } from './templates/index.js';
|
|
12
|
+
/** Stroke width the 24 template relations are drawn with. */
|
|
13
|
+
const RELATION_STROKE_WIDTH = 2;
|
|
14
|
+
/** Default facets-diagram size (REF aspect, scaled up so it reads on canvas). */
|
|
15
|
+
const FACETS_SCALE = 1.5;
|
|
16
|
+
/** Default blank-board size. */
|
|
17
|
+
const BOARD_W = 1600;
|
|
18
|
+
const BOARD_H = 1000;
|
|
19
|
+
/** Height of the native People free-text label. */
|
|
20
|
+
const LABEL_H = LABEL_FONT_SIZE + 8;
|
|
21
|
+
const gfxOf = (std) => std.get(GfxControllerIdentifier);
|
|
22
|
+
function finish(gfx, id) {
|
|
23
|
+
gfx.doc.captureSync();
|
|
24
|
+
gfx.tool.setTool(DefaultTool);
|
|
25
|
+
gfx.selection.set({ elements: [id], editing: false });
|
|
26
|
+
// Keep the palette open (native sub-menu behaviour).
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Shared props for an EDGY node shape.
|
|
30
|
+
*
|
|
31
|
+
* The `role` is the PERSISTED KIND and nothing more: somebody picking "Object"
|
|
32
|
+
* in the palette has said "this is an object", not "this is a Channel". The
|
|
33
|
+
* twelve official elements are named by the metamodel template, which knows
|
|
34
|
+
* which of them it is drawing; a base element created here specialises
|
|
35
|
+
* `edgy:element` through its kind and is judged by every rule written on the
|
|
36
|
+
* root — the overlap rule — and by none written on a leaf.
|
|
37
|
+
*/
|
|
38
|
+
const baseShapeProps = (kind) => ({
|
|
39
|
+
type: 'edgyNode',
|
|
40
|
+
kind,
|
|
41
|
+
role: EDGY_ROLE[kind],
|
|
42
|
+
filled: true,
|
|
43
|
+
fillColor: NODE_FILL,
|
|
44
|
+
strokeColor: NODE_STROKE,
|
|
45
|
+
shapeStyle: ShapeStyle.General,
|
|
46
|
+
roughness: 0,
|
|
47
|
+
});
|
|
48
|
+
/** Add a native free-text label (Inter), used for the People node. */
|
|
49
|
+
function addLabel(surface, text, x, y) {
|
|
50
|
+
return surface.addElement({
|
|
51
|
+
type: 'text',
|
|
52
|
+
text,
|
|
53
|
+
fontFamily: FontFamily.Inter,
|
|
54
|
+
fontSize: LABEL_FONT_SIZE,
|
|
55
|
+
color: NODE_STROKE,
|
|
56
|
+
textAlign: 'center',
|
|
57
|
+
xywh: new Bound(x, y, 120, LABEL_H).serialize(),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Create the Enterprise Design Facets diagram centred on the viewport —
|
|
62
|
+
* cropped to the circles + facet labels (no dead margins around the Venn).
|
|
63
|
+
*/
|
|
64
|
+
export function createEdgyFacets(std) {
|
|
65
|
+
const gfx = gfxOf(std);
|
|
66
|
+
if (!gfx.surface)
|
|
67
|
+
return;
|
|
68
|
+
const width = CROP_LABELED.w * FACETS_SCALE;
|
|
69
|
+
const height = CROP_LABELED.h * FACETS_SCALE;
|
|
70
|
+
const { centerX, centerY } = gfx.viewport;
|
|
71
|
+
const id = gfx.surface.addElement({
|
|
72
|
+
type: 'edgy',
|
|
73
|
+
cropToCircles: true,
|
|
74
|
+
// The frame: what makes this an EDGY board rather than three circles, and
|
|
75
|
+
// what a finding is attributed to.
|
|
76
|
+
role: EDGY_ROLE.facets,
|
|
77
|
+
xywh: new Bound(centerX - width / 2, centerY - height / 2, width, height).serialize(),
|
|
78
|
+
});
|
|
79
|
+
finish(gfx, id);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Insert the "EDGY dynamic" template (facets background + the 12 elements
|
|
83
|
+
* linked by the 24 verbalised relations), then bring it into view.
|
|
84
|
+
*/
|
|
85
|
+
export async function createEdgyDynamic(std) {
|
|
86
|
+
const gfx = gfxOf(std);
|
|
87
|
+
if (!gfx.surface)
|
|
88
|
+
return;
|
|
89
|
+
const job = createTemplateJob(std, 'template');
|
|
90
|
+
const bound = await job.insertTemplate(structuredClone(edgyDynamicTemplate.content));
|
|
91
|
+
if (bound) {
|
|
92
|
+
const padding = 20 / gfx.viewport.zoom;
|
|
93
|
+
gfx.viewport.setViewportByBound(bound, [padding, padding, padding, padding], true);
|
|
94
|
+
}
|
|
95
|
+
gfx.doc.captureSync();
|
|
96
|
+
gfx.tool.setTool(DefaultTool);
|
|
97
|
+
}
|
|
98
|
+
/** Create a blank EDGY board (hover spotlight host) centred on the viewport. */
|
|
99
|
+
export function createEdgyBoard(std) {
|
|
100
|
+
const gfx = gfxOf(std);
|
|
101
|
+
if (!gfx.surface)
|
|
102
|
+
return;
|
|
103
|
+
const { centerX, centerY } = gfx.viewport;
|
|
104
|
+
const id = gfx.surface.addElement({
|
|
105
|
+
type: 'edgyBoard',
|
|
106
|
+
// The blank board is a frame too: the same rules apply to what is drawn on
|
|
107
|
+
// it, and the same map-wide arbitration has to have somewhere to live.
|
|
108
|
+
role: EDGY_ROLE.board,
|
|
109
|
+
xywh: new Bound(centerX - BOARD_W / 2, centerY - BOARD_H / 2, BOARD_W, BOARD_H).serialize(),
|
|
110
|
+
});
|
|
111
|
+
finish(gfx, id);
|
|
112
|
+
}
|
|
113
|
+
/** Create a box base element (outcome / object / activity) with inner text. */
|
|
114
|
+
export function createEdgyBox(std, kind) {
|
|
115
|
+
const gfx = gfxOf(std);
|
|
116
|
+
const surface = gfx.surface;
|
|
117
|
+
if (!surface)
|
|
118
|
+
return;
|
|
119
|
+
const { w, h } = NODE_SIZE[kind];
|
|
120
|
+
const { centerX: cx, centerY: cy } = gfx.viewport;
|
|
121
|
+
const shapeType = kind === 'activity' ? 'polygon' : 'rect';
|
|
122
|
+
const id = surface.addElement({
|
|
123
|
+
...baseShapeProps(kind),
|
|
124
|
+
shapeType,
|
|
125
|
+
strokeWidth: NODE_STROKE_WIDTH,
|
|
126
|
+
radius: kind === 'outcome' ? OUTCOME_RADIUS : 0,
|
|
127
|
+
vertices: kind === 'activity' ? ACTIVITY_VERTICES : null,
|
|
128
|
+
text: NODE_LABEL[kind],
|
|
129
|
+
color: NODE_STROKE,
|
|
130
|
+
fontFamily: FontFamily.Inter,
|
|
131
|
+
fontSize: INNER_FONT_SIZE,
|
|
132
|
+
textAlign: 'center',
|
|
133
|
+
xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
|
|
134
|
+
});
|
|
135
|
+
finish(gfx, id);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Create the People base element: an (invisible) ellipse decorated with the
|
|
139
|
+
* person glyph by the renderer, plus a native text label below, grouped.
|
|
140
|
+
*/
|
|
141
|
+
export function createEdgyPeople(std) {
|
|
142
|
+
const gfx = gfxOf(std);
|
|
143
|
+
const surface = gfx.surface;
|
|
144
|
+
if (!surface)
|
|
145
|
+
return;
|
|
146
|
+
const { w, h } = NODE_SIZE.people;
|
|
147
|
+
const { centerX: cx, centerY: cy } = gfx.viewport;
|
|
148
|
+
const nodeId = surface.addElement({
|
|
149
|
+
...baseShapeProps('people'),
|
|
150
|
+
shapeType: 'ellipse',
|
|
151
|
+
// No visible outline — People is just the glyph; the ellipse is the bound.
|
|
152
|
+
strokeWidth: 0,
|
|
153
|
+
xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
|
|
154
|
+
});
|
|
155
|
+
const labelId = addLabel(surface, NODE_LABEL.people, cx - 60, cy + h / 2 + LABEL_GAP);
|
|
156
|
+
const [, result] = std.command.exec(createGroupCommand, {
|
|
157
|
+
elements: [nodeId, labelId],
|
|
158
|
+
});
|
|
159
|
+
finish(gfx, result.groupId || nodeId);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Activate the native connector tool for an EDGY RELATION: pre-styled like the
|
|
163
|
+
* 24 links of the metamodel template, and stamped with the GENERIC role
|
|
164
|
+
* `edgy:relation`.
|
|
165
|
+
*
|
|
166
|
+
* Generic on purpose, and this is the whole design (`./relation.ts`): the verb
|
|
167
|
+
* of an EDGY relation is determined by the ordered pair of elements it runs
|
|
168
|
+
* between — 24 rows, 24 distinct pairs — so there is nothing to ask the user
|
|
169
|
+
* and nothing to pick from a list of twenty-two. The tool arms the parent role,
|
|
170
|
+
* the user drags from the subject to the object, and `EdgyRelationResolver`
|
|
171
|
+
* writes the verb the metamodel gives that pair, as a role and as the label.
|
|
172
|
+
*
|
|
173
|
+
* The style is the template's, to the pixel: straight, `NODE_STROKE`, two units
|
|
174
|
+
* wide, and NO arrowhead at either end — EDGY's reference diagram draws its
|
|
175
|
+
* relations as bare lines and lets the verb say which way the sentence runs.
|
|
176
|
+
*/
|
|
177
|
+
export function activateEdgyRelation(gfx) {
|
|
178
|
+
gfx.tool.setTool(ConnectorTool, {
|
|
179
|
+
mode: ConnectorMode.Straight,
|
|
180
|
+
role: EDGY_ROLE.relation,
|
|
181
|
+
// The look rides on the activation, never through the last-props store:
|
|
182
|
+
// the plain connector tool must keep the user's own style (#144 M1).
|
|
183
|
+
style: {
|
|
184
|
+
stroke: NODE_STROKE,
|
|
185
|
+
strokeStyle: StrokeStyle.Solid,
|
|
186
|
+
strokeWidth: RELATION_STROKE_WIDTH,
|
|
187
|
+
frontEndpointStyle: PointStyle.None,
|
|
188
|
+
rearEndpointStyle: PointStyle.None,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
// Keep the palette open (native sub-menu behaviour): it only closes on
|
|
192
|
+
// re-click of the senior button, another senior tool, or Escape.
|
|
193
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ElementRenderer } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import type { EdgyBoardElementModel } from '@formicoidea/labre-core/model';
|
|
3
|
+
/**
|
|
4
|
+
* Canvas renderer for the blank EDGY board: a plain white rounded rectangle
|
|
5
|
+
* with a discreet border. The board only exists to host free-form EDGY
|
|
6
|
+
* modelling and grant the spotlight-on-hover behavior to the elements laid on
|
|
7
|
+
* top of it.
|
|
8
|
+
*/
|
|
9
|
+
export declare const edgyBoard: ElementRenderer<EdgyBoardElementModel>;
|
|
10
|
+
export declare const EdgyBoardRendererExtension: import("@formicoidea/labre-core/store").ExtensionType & {
|
|
11
|
+
identifier: import("@formicoidea/labre-core/_pkgs/global/di").ServiceIdentifier<ElementRenderer<EdgyBoardElementModel>>;
|
|
12
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
const BOARD_FILL = '#ffffff';
|
|
3
|
+
const BOARD_BORDER = '#e0e0e0';
|
|
4
|
+
const BOARD_RADIUS = 16;
|
|
5
|
+
const BOARD_BORDER_WIDTH = 2;
|
|
6
|
+
/**
|
|
7
|
+
* Canvas renderer for the blank EDGY board: a plain white rounded rectangle
|
|
8
|
+
* with a discreet border. The board only exists to host free-form EDGY
|
|
9
|
+
* modelling and grant the spotlight-on-hover behavior to the elements laid on
|
|
10
|
+
* top of it.
|
|
11
|
+
*/
|
|
12
|
+
export const edgyBoard = (model, ctx, matrix) => {
|
|
13
|
+
const [, , w, h] = model.deserializedXYWH;
|
|
14
|
+
const cx = w / 2;
|
|
15
|
+
const cy = h / 2;
|
|
16
|
+
ctx.setTransform(matrix
|
|
17
|
+
.translateSelf(cx, cy)
|
|
18
|
+
.rotateSelf(model.rotate)
|
|
19
|
+
.translateSelf(-cx, -cy));
|
|
20
|
+
ctx.beginPath();
|
|
21
|
+
ctx.roundRect(0, 0, w, h, BOARD_RADIUS);
|
|
22
|
+
ctx.fillStyle = BOARD_FILL;
|
|
23
|
+
ctx.fill();
|
|
24
|
+
ctx.strokeStyle = BOARD_BORDER;
|
|
25
|
+
ctx.lineWidth = BOARD_BORDER_WIDTH;
|
|
26
|
+
ctx.stroke();
|
|
27
|
+
};
|
|
28
|
+
export const EdgyBoardRendererExtension = ElementRendererExtension('edgyBoard', edgyBoard);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { EdgyBoardElementModel } from '@formicoidea/labre-core/model';
|
|
2
|
+
import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
|
|
3
|
+
export declare class EdgyBoardView extends GfxElementModelView<EdgyBoardElementModel> {
|
|
4
|
+
static type: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Resize gating: the resize handles are hidden unless `model.resizeEnabled` is
|
|
8
|
+
* true. Moving/selecting stays available throughout (same as the facets
|
|
9
|
+
* diagram).
|
|
10
|
+
*/
|
|
11
|
+
export declare const EdgyBoardInteraction: import("@formicoidea/labre-core/store").ExtensionType;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { GfxElementModelView, GfxViewInteractionExtension, } from '@formicoidea/labre-core/std/gfx';
|
|
2
|
+
export class EdgyBoardView extends GfxElementModelView {
|
|
3
|
+
static { this.type = 'edgyBoard'; }
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Resize gating: the resize handles are hidden unless `model.resizeEnabled` is
|
|
7
|
+
* true. Moving/selecting stays available throughout (same as the facets
|
|
8
|
+
* diagram).
|
|
9
|
+
*/
|
|
10
|
+
export const EdgyBoardInteraction = GfxViewInteractionExtension(EdgyBoardView.type, {
|
|
11
|
+
handleResize({ model }) {
|
|
12
|
+
return {
|
|
13
|
+
beforeResize({ set }) {
|
|
14
|
+
if (!model.resizeEnabled) {
|
|
15
|
+
set({ allowedHandlers: [] });
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
});
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
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';
|
|
4
|
+
const SPECS = [
|
|
5
|
+
{
|
|
6
|
+
id: 'addFacets',
|
|
7
|
+
label: 'Enterprise Design facets',
|
|
8
|
+
iconKey: 'edgy.facets',
|
|
9
|
+
category: 'diagrams',
|
|
10
|
+
element: 'facets',
|
|
11
|
+
run: createEdgyFacets,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: 'insertDynamic',
|
|
15
|
+
label: 'EDGY dynamic (elements & relations)',
|
|
16
|
+
iconKey: 'edgy.dynamic',
|
|
17
|
+
category: 'diagrams',
|
|
18
|
+
element: 'template:dynamic',
|
|
19
|
+
run: createEdgyDynamic,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'addBoard',
|
|
23
|
+
label: 'EDGY board (hover spotlight)',
|
|
24
|
+
iconKey: 'edgy.board',
|
|
25
|
+
category: 'diagrams',
|
|
26
|
+
element: 'board',
|
|
27
|
+
run: createEdgyBoard,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'addPeople',
|
|
31
|
+
label: 'People',
|
|
32
|
+
iconKey: 'edgy.people',
|
|
33
|
+
category: 'elements',
|
|
34
|
+
element: 'node:people',
|
|
35
|
+
run: createEdgyPeople,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'addOutcome',
|
|
39
|
+
label: 'Outcome',
|
|
40
|
+
iconKey: 'edgy.outcome',
|
|
41
|
+
category: 'elements',
|
|
42
|
+
element: 'node:outcome',
|
|
43
|
+
run: std => createEdgyBox(std, 'outcome'),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'addObject',
|
|
47
|
+
label: 'Object',
|
|
48
|
+
iconKey: 'edgy.object',
|
|
49
|
+
category: 'elements',
|
|
50
|
+
element: 'node:object',
|
|
51
|
+
run: std => createEdgyBox(std, 'object'),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'addActivity',
|
|
55
|
+
label: 'Activity',
|
|
56
|
+
iconKey: 'edgy.activity',
|
|
57
|
+
category: 'elements',
|
|
58
|
+
element: 'node:activity',
|
|
59
|
+
run: std => createEdgyBox(std, 'activity'),
|
|
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
|
+
},
|
|
71
|
+
];
|
|
72
|
+
export const edgyCommands = SPECS.map((spec, order) => ({
|
|
73
|
+
id: `edgy.${spec.id}`,
|
|
74
|
+
owner: 'edgy',
|
|
75
|
+
kind: spec.kind ?? 'artefact',
|
|
76
|
+
labelKey: `com.labre.commands.edgy.${spec.id}`,
|
|
77
|
+
labelFallback: spec.label,
|
|
78
|
+
...(spec.description === undefined
|
|
79
|
+
? {}
|
|
80
|
+
: {
|
|
81
|
+
descriptionKey: `com.labre.commands.edgy.${spec.id}.description`,
|
|
82
|
+
descriptionFallback: spec.description,
|
|
83
|
+
}),
|
|
84
|
+
category: spec.category,
|
|
85
|
+
iconKey: spec.iconKey,
|
|
86
|
+
surfaces: ['senior-menu', 'catalogue', 'palette', 'agent'],
|
|
87
|
+
order,
|
|
88
|
+
scope: 'edgeless',
|
|
89
|
+
defaultKeys: { mac: [], other: [] },
|
|
90
|
+
availability: 'always',
|
|
91
|
+
run: spec.run,
|
|
92
|
+
telemetry: { framework: 'edgy', element: spec.element },
|
|
93
|
+
}));
|
|
94
|
+
export const edgyCommandIcons = {
|
|
95
|
+
'edgy.facets': edgyFacetsIcon,
|
|
96
|
+
'edgy.dynamic': edgyDynamicIcon,
|
|
97
|
+
'edgy.board': edgyBoardIcon,
|
|
98
|
+
'edgy.people': edgyPeopleIcon,
|
|
99
|
+
'edgy.outcome': edgyOutcomeIcon,
|
|
100
|
+
'edgy.object': edgyObjectIcon,
|
|
101
|
+
'edgy.activity': edgyActivityIcon,
|
|
102
|
+
'edgy.relation': edgyRelationIcon,
|
|
103
|
+
};
|
package/dist/consts.d.ts
CHANGED
|
@@ -44,3 +44,38 @@ export declare function refScale(w: number, h: number): {
|
|
|
44
44
|
ox: number;
|
|
45
45
|
oy: number;
|
|
46
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* Bounding box of the three circles in reference coords (small padding). The
|
|
49
|
+
* REF margins around it only exist for the facet name labels — when a diagram
|
|
50
|
+
* hides them (`cropToCircles`), the renderer fits THIS box into the element
|
|
51
|
+
* bounds instead, so the background hugs the Venn.
|
|
52
|
+
*/
|
|
53
|
+
export declare const CROP: {
|
|
54
|
+
x: number;
|
|
55
|
+
y: number;
|
|
56
|
+
w: number;
|
|
57
|
+
h: number;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Crop box KEEPING room for the three facet name labels (drawn outside the
|
|
61
|
+
* circles): the circles box plus a fixed text allowance left/right and below.
|
|
62
|
+
* Used when a cropped diagram still shows its labels.
|
|
63
|
+
*/
|
|
64
|
+
export declare const CROP_LABELED: {
|
|
65
|
+
x: number;
|
|
66
|
+
y: number;
|
|
67
|
+
w: number;
|
|
68
|
+
h: number;
|
|
69
|
+
};
|
|
70
|
+
/** `refScale` variant fitting the cropped circles box (see {@link CROP}). */
|
|
71
|
+
export declare const cropScale: (w: number, h: number) => {
|
|
72
|
+
s: number;
|
|
73
|
+
ox: number;
|
|
74
|
+
oy: number;
|
|
75
|
+
};
|
|
76
|
+
/** `refScale` variant fitting the circles + facet labels box. */
|
|
77
|
+
export declare const cropLabeledScale: (w: number, h: number) => {
|
|
78
|
+
s: number;
|
|
79
|
+
ox: number;
|
|
80
|
+
oy: number;
|
|
81
|
+
};
|
package/dist/consts.js
CHANGED
|
@@ -43,3 +43,49 @@ export function refScale(w, h) {
|
|
|
43
43
|
const s = Math.min(w / REF_W, h / REF_H);
|
|
44
44
|
return { s, ox: (w - REF_W * s) / 2, oy: (h - REF_H * s) / 2 };
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Bounding box of the three circles in reference coords (small padding). The
|
|
48
|
+
* REF margins around it only exist for the facet name labels — when a diagram
|
|
49
|
+
* hides them (`cropToCircles`), the renderer fits THIS box into the element
|
|
50
|
+
* bounds instead, so the background hugs the Venn.
|
|
51
|
+
*/
|
|
52
|
+
export const CROP = (() => {
|
|
53
|
+
const pad = 8;
|
|
54
|
+
const ax = VENN.cx - 0.866 * VENN.r0; // Identity / Architecture centres
|
|
55
|
+
const abY = VENN.cy - 0.5 * VENN.r0;
|
|
56
|
+
const cY = VENN.cy + VENN.r0; // Experience centre
|
|
57
|
+
const minX = ax - VENN.R - pad;
|
|
58
|
+
const maxX = VENN.cx + 0.866 * VENN.r0 + VENN.R + pad;
|
|
59
|
+
const minY = abY - VENN.R - pad;
|
|
60
|
+
const maxY = cY + VENN.R + pad;
|
|
61
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
62
|
+
})();
|
|
63
|
+
/**
|
|
64
|
+
* Crop box KEEPING room for the three facet name labels (drawn outside the
|
|
65
|
+
* circles): the circles box plus a fixed text allowance left/right and below.
|
|
66
|
+
* Used when a cropped diagram still shows its labels.
|
|
67
|
+
*/
|
|
68
|
+
export const CROP_LABELED = (() => {
|
|
69
|
+
const sideAllowance = 140; // label offset (10) + text width headroom
|
|
70
|
+
const bottomAllowance = 35; // experience label below the bottom circle
|
|
71
|
+
return {
|
|
72
|
+
x: CROP.x - sideAllowance,
|
|
73
|
+
y: CROP.y,
|
|
74
|
+
w: CROP.w + 2 * sideAllowance,
|
|
75
|
+
h: CROP.h + bottomAllowance,
|
|
76
|
+
};
|
|
77
|
+
})();
|
|
78
|
+
function fitScale(box) {
|
|
79
|
+
return (w, h) => {
|
|
80
|
+
const s = Math.min(w / box.w, h / box.h);
|
|
81
|
+
return {
|
|
82
|
+
s,
|
|
83
|
+
ox: (w - box.w * s) / 2 - box.x * s,
|
|
84
|
+
oy: (h - box.h * s) / 2 - box.y * s,
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** `refScale` variant fitting the cropped circles box (see {@link CROP}). */
|
|
89
|
+
export const cropScale = fitScale(CROP);
|
|
90
|
+
/** `refScale` variant fitting the circles + facet labels box. */
|
|
91
|
+
export const cropLabeledScale = fitScale(CROP_LABELED);
|
package/dist/descriptor.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { EdgyViewExtension } from './view.js';
|
|
1
|
+
import { EdgyRenderViewExtension, EdgyViewExtension } from './view.js';
|
|
2
2
|
/** Host wiring for the edgy framework. */
|
|
3
3
|
export declare const edgyFramework: {
|
|
4
4
|
readonly flag: "edgy";
|
|
5
|
-
readonly
|
|
6
|
-
readonly
|
|
5
|
+
readonly telemetryKey: "edgy";
|
|
6
|
+
readonly extensions: readonly [{
|
|
7
|
+
readonly viewExtension: typeof EdgyRenderViewExtension;
|
|
8
|
+
}, {
|
|
9
|
+
readonly flag: "edgy";
|
|
10
|
+
readonly viewExtension: typeof EdgyViewExtension;
|
|
11
|
+
}];
|
|
7
12
|
};
|
package/dist/descriptor.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { EdgyViewExtension } from './view.js';
|
|
1
|
+
import { EdgyRenderViewExtension, EdgyViewExtension } from './view.js';
|
|
2
2
|
/** Host wiring for the edgy framework. */
|
|
3
3
|
export const edgyFramework = {
|
|
4
4
|
flag: 'edgy',
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
telemetryKey: 'edgy',
|
|
6
|
+
extensions: [
|
|
7
|
+
{ viewExtension: EdgyRenderViewExtension },
|
|
8
|
+
{ flag: 'edgy', viewExtension: EdgyViewExtension },
|
|
9
|
+
],
|
|
7
10
|
};
|
package/dist/element-renderer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
-
import { COLORS, 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';
|
|
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';
|
|
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,9 +15,18 @@ 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
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
ctx.setTransform(matrix
|
|
19
|
+
.translateSelf(cx, cy)
|
|
20
|
+
.rotateSelf(model.rotate)
|
|
21
|
+
.translateSelf(-cx, -cy));
|
|
22
|
+
// Uniform fit of the reference design, centered (letterboxed) — or, when
|
|
23
|
+
// the diagram is cropped, of the circles' bounding box (plus the facet
|
|
24
|
+
// label allowance if the labels are shown).
|
|
25
|
+
const { s, ox, oy } = model.cropToCircles
|
|
26
|
+
? model.showLabels
|
|
27
|
+
? cropLabeledScale(w, h)
|
|
28
|
+
: cropScale(w, h)
|
|
29
|
+
: refScale(w, h);
|
|
21
30
|
ctx.translate(ox, oy);
|
|
22
31
|
ctx.scale(s, s);
|
|
23
32
|
const A = CIRCLE_A;
|
|
@@ -149,14 +158,16 @@ export const edgy = (model, ctx, matrix) => {
|
|
|
149
158
|
ctx.lineTo(x, y + 12);
|
|
150
159
|
ctx.stroke();
|
|
151
160
|
};
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
161
|
+
if (model.showPictos) {
|
|
162
|
+
// Single-facet zone pictos.
|
|
163
|
+
lens(A.x - 30, A.y - 22);
|
|
164
|
+
house(B.x + 30, B.y - 22);
|
|
165
|
+
heart(C.x, C.y + 36);
|
|
166
|
+
// Intersection pictos at the validated sweet spots.
|
|
167
|
+
network(VENN.cx, 114);
|
|
168
|
+
sun(VENN.cx - 58, 206);
|
|
169
|
+
cube(VENN.cx + 58, 206);
|
|
170
|
+
}
|
|
160
171
|
// ── Labels (outside the circles) ────────────────────────────────────
|
|
161
172
|
if (model.showLabels) {
|
|
162
173
|
ctx.font = `500 ${LABEL_FONT_SIZE}px ${FONT_FAMILY}`;
|
package/dist/element-view.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EdgelessCRUDIdentifier } from '@formicoidea/labre-core/blocks/surface';
|
|
2
2
|
import { rotatePoint } from '@formicoidea/labre-core/global/gfx';
|
|
3
3
|
import { GfxElementModelView, GfxViewInteractionExtension, } from '@formicoidea/labre-core/std/gfx';
|
|
4
|
-
import { refScale } from './consts.js';
|
|
4
|
+
import { cropLabeledScale, refScale } from './consts.js';
|
|
5
5
|
import { getEdgyLabelHits, hitTestEdgyLabel, } from './label-layout.js';
|
|
6
6
|
export class EdgyView extends GfxElementModelView {
|
|
7
7
|
constructor() {
|
|
@@ -22,6 +22,9 @@ export class EdgyView extends GfxElementModelView {
|
|
|
22
22
|
_onDblClick(e) {
|
|
23
23
|
if (this.model.isLocked())
|
|
24
24
|
return;
|
|
25
|
+
// No labels shown → nothing to edit (and the cropped mapping differs).
|
|
26
|
+
if (!this.model.showLabels)
|
|
27
|
+
return;
|
|
25
28
|
const [mx, my] = this.gfx.viewport.toModelCoord(e.x, e.y);
|
|
26
29
|
const [bx, by, w, h] = this.model.deserializedXYWH;
|
|
27
30
|
// Convert the model-space point into element-local coordinates, undoing the
|
|
@@ -35,8 +38,11 @@ export class EdgyView extends GfxElementModelView {
|
|
|
35
38
|
lx = ux - bx;
|
|
36
39
|
ly = uy - by;
|
|
37
40
|
}
|
|
38
|
-
// Map element-local coords into the fixed reference space the labels live
|
|
39
|
-
|
|
41
|
+
// Map element-local coords into the fixed reference space the labels live
|
|
42
|
+
// in — with the SAME fit the renderer used (cropped or letterboxed).
|
|
43
|
+
const { s, ox, oy } = this.model.cropToCircles
|
|
44
|
+
? cropLabeledScale(w, h)
|
|
45
|
+
: refScale(w, h);
|
|
40
46
|
const rx = (lx - ox) / s;
|
|
41
47
|
const ry = (ly - oy) / s;
|
|
42
48
|
const hit = hitTestEdgyLabel(getEdgyLabelHits(this.model), rx, ry);
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export {};
|
|
1
|
+
export { edgyCommandIcons, edgyCommands } from './commands.js';
|
|
2
|
+
export { edgyTranslationEntries } from './translations.js';
|
package/dist/index.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export {};
|
|
1
|
+
export { edgyCommandIcons, edgyCommands } from './commands.js';
|
|
2
|
+
export { edgyTranslationEntries } from './translations.js';
|
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