@formicoidea/labre-framework-edgy 0.32.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 +18 -0
- package/dist/actions.js +55 -2
- package/dist/board-renderer.js +4 -1
- package/dist/commands.js +21 -3
- package/dist/element-renderer.js +5 -2
- 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 +5 -13
- package/dist/templates/index.js +210 -107
- package/dist/toolbar/config.d.ts +10 -0
- package/dist/toolbar/config.js +38 -1
- 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 +23 -9
- package/dist/view.d.ts +5 -2
- package/dist/view.js +54 -5
- package/package.json +6 -3
package/dist/templates/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { makeTemplateSnapshot, surfaceText, } from '@formicoidea/labre-core/gfx/template';
|
|
2
2
|
import { ConnectorMode, FontFamily, PointStyle, ShapeStyle, StrokeStyle, TextAlign, } from '@formicoidea/labre-core/model';
|
|
3
3
|
import { CROP, CROP_LABELED } from '../consts.js';
|
|
4
|
+
import { EDGY_DYNAMIC_NODES, EDGY_DYNAMIC_RELATIONS, EDGY_ZONE_FILL, edgyElementLabel, } from '../metamodel.js';
|
|
5
|
+
import { EDGY_RELATION_LABEL_DISTANCE, edgyVerbLabelXYWH } from '../relation.js';
|
|
6
|
+
import { EDGY_ROLE, EDGY_VERB_ROLE } from '../roles.js';
|
|
4
7
|
import { ACTIVITY_VERTICES, INNER_FONT_SIZE, NODE_FILL, NODE_SIZE, NODE_STROKE, NODE_STROKE_WIDTH, OUTCOME_RADIUS, } from '../node/consts.js';
|
|
5
8
|
// EDGY facet palette (header / pale sub-card).
|
|
6
9
|
const C = {
|
|
@@ -35,7 +38,15 @@ function rect(x, y, w, h, opts = {}) {
|
|
|
35
38
|
}
|
|
36
39
|
return el;
|
|
37
40
|
}
|
|
38
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* An EDGY node (kind drives the native shape): outcome/object box, people,
|
|
43
|
+
* activity chevron.
|
|
44
|
+
*
|
|
45
|
+
* `role` is optional and writes NOTHING when absent: the illustrative templates
|
|
46
|
+
* (journey, blueprint, org chart) stay neutral drawings the engine never looks
|
|
47
|
+
* at, exactly as they were before roles existed. Only the EDGY dynamic template
|
|
48
|
+
* — the one that IS the metamodel — stamps its elements.
|
|
49
|
+
*/
|
|
39
50
|
function enode(kind, x, y, w, h, opts = {}) {
|
|
40
51
|
const el = {
|
|
41
52
|
type: 'edgyNode',
|
|
@@ -52,6 +63,9 @@ function enode(kind, x, y, w, h, opts = {}) {
|
|
|
52
63
|
};
|
|
53
64
|
if (kind === 'activity')
|
|
54
65
|
el.vertices = ACTIVITY_VERTICES;
|
|
66
|
+
// `undefined` writes nothing: a neutral node keeps no `role` key.
|
|
67
|
+
if (opts.role !== undefined)
|
|
68
|
+
el.role = opts.role;
|
|
55
69
|
if (opts.text != null) {
|
|
56
70
|
el.text = surfaceText(opts.text);
|
|
57
71
|
el.color = opts.textColor ?? NODE_STROKE;
|
|
@@ -108,17 +122,53 @@ function facetsOverview() {
|
|
|
108
122
|
const out = {};
|
|
109
123
|
const tall = (key, x, name, cards) => {
|
|
110
124
|
const [hdr, sub] = C[key];
|
|
111
|
-
out[`${key}F`] = rect(x, 140, 192, 460, {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
125
|
+
out[`${key}F`] = rect(x, 140, 192, 460, {
|
|
126
|
+
fill: hdr,
|
|
127
|
+
stroke: hdr,
|
|
128
|
+
radius: 16,
|
|
129
|
+
});
|
|
130
|
+
out[`${key}N`] = label(x, 168, 192, 28, name, {
|
|
131
|
+
color: '#ffffff',
|
|
132
|
+
fontSize: 18,
|
|
133
|
+
});
|
|
134
|
+
out[`${key}1`] = rect(x + 16, 208, 160, 80, {
|
|
135
|
+
fill: sub,
|
|
136
|
+
stroke: sub,
|
|
137
|
+
radius: 6,
|
|
138
|
+
text: cards[0],
|
|
139
|
+
fontSize: 16,
|
|
140
|
+
});
|
|
141
|
+
out[`${key}2`] = rect(x + 16, 300, 160, 80, {
|
|
142
|
+
fill: sub,
|
|
143
|
+
stroke: sub,
|
|
144
|
+
radius: 6,
|
|
145
|
+
text: cards[1],
|
|
146
|
+
fontSize: 16,
|
|
147
|
+
});
|
|
148
|
+
out[`${key}3`] = enode('activity', x + 16, 392, 160, 84, {
|
|
149
|
+
fill: sub,
|
|
150
|
+
text: cards[2],
|
|
151
|
+
fontSize: 16,
|
|
152
|
+
});
|
|
116
153
|
};
|
|
117
154
|
const low = (key, x, name) => {
|
|
118
155
|
const [hdr, sub] = C[key];
|
|
119
|
-
out[`${key}F`] = rect(x, 300, 192, 424, {
|
|
120
|
-
|
|
121
|
-
|
|
156
|
+
out[`${key}F`] = rect(x, 300, 192, 424, {
|
|
157
|
+
fill: hdr,
|
|
158
|
+
stroke: hdr,
|
|
159
|
+
radius: 16,
|
|
160
|
+
});
|
|
161
|
+
out[`${key}1`] = rect(x + 16, 360, 160, 92, {
|
|
162
|
+
fill: sub,
|
|
163
|
+
stroke: sub,
|
|
164
|
+
radius: 6,
|
|
165
|
+
text: name,
|
|
166
|
+
fontSize: 16,
|
|
167
|
+
});
|
|
168
|
+
out[`${key}N`] = label(x, 686, 192, 28, name, {
|
|
169
|
+
color: '#ffffff',
|
|
170
|
+
fontSize: 18,
|
|
171
|
+
});
|
|
122
172
|
};
|
|
123
173
|
// lower facets first (drawn behind the tall ones at the overlaps)
|
|
124
174
|
low('organisation', 240, 'Organisation');
|
|
@@ -135,39 +185,114 @@ function facetsOverview() {
|
|
|
135
185
|
}
|
|
136
186
|
// ── Customer journey ──────────────────────────────────────────────────
|
|
137
187
|
function journey() {
|
|
138
|
-
const step = (i, x) => enode('activity', x, 132, 224, 116, {
|
|
139
|
-
|
|
140
|
-
|
|
188
|
+
const step = (i, x) => enode('activity', x, 132, 224, 116, {
|
|
189
|
+
fill: JOURNEY_PINK,
|
|
190
|
+
text: `Journey step ${i}`,
|
|
191
|
+
textColor: '#ffffff',
|
|
192
|
+
fontSize: 18,
|
|
193
|
+
});
|
|
194
|
+
const ch = (x, n) => enode('object', x, 392, 184, 96, {
|
|
195
|
+
fill: JOURNEY_PINK,
|
|
196
|
+
text: `Channel ${n}`,
|
|
197
|
+
fontSize: 16,
|
|
198
|
+
});
|
|
199
|
+
const tk = (x, n) => enode('object', x, 540, 168, 92, {
|
|
200
|
+
fill: JOURNEY_PINK,
|
|
201
|
+
text: `Task ${n}`,
|
|
202
|
+
fontSize: 16,
|
|
203
|
+
});
|
|
141
204
|
return {
|
|
142
205
|
cust: enode('people', 40, 120, 64, 64, { fill: '#ffffff' }),
|
|
143
206
|
custL: label(20, 192, 104, 24, 'Customer', { fontSize: 14 }),
|
|
144
|
-
band: {
|
|
145
|
-
|
|
207
|
+
band: {
|
|
208
|
+
type: 'shape',
|
|
209
|
+
shapeType: 'polygon',
|
|
210
|
+
vertices: [
|
|
211
|
+
[0, 0],
|
|
212
|
+
[0.86, 0],
|
|
213
|
+
[1, 0.5],
|
|
214
|
+
[0.86, 1],
|
|
215
|
+
[0, 1],
|
|
216
|
+
],
|
|
217
|
+
filled: true,
|
|
218
|
+
fillColor: JOURNEY_PINK,
|
|
219
|
+
strokeColor: JOURNEY_PINK,
|
|
220
|
+
strokeWidth: 1,
|
|
221
|
+
shapeStyle: ShapeStyle.General,
|
|
222
|
+
roughness: 0,
|
|
223
|
+
xywh: '[150,108,860,164]',
|
|
224
|
+
},
|
|
225
|
+
bandL: label(170, 116, 120, 24, 'Journey', {
|
|
226
|
+
color: '#ffffff',
|
|
227
|
+
fontSize: 16,
|
|
228
|
+
align: TextAlign.Left,
|
|
229
|
+
}),
|
|
146
230
|
s1: step(1, 240),
|
|
147
231
|
s2: step(2, 500),
|
|
148
232
|
s3: step(3, 760),
|
|
149
|
-
rightTask: enode('object', 1060, 132, 168, 92, {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
233
|
+
rightTask: enode('object', 1060, 132, 168, 92, {
|
|
234
|
+
text: 'Task',
|
|
235
|
+
fontSize: 16,
|
|
236
|
+
}),
|
|
237
|
+
c1: ch(258, 'X'),
|
|
238
|
+
c2: ch(518, 'Y'),
|
|
239
|
+
c3: ch(778, 'Z'),
|
|
240
|
+
t1: tk(266, 'A'),
|
|
241
|
+
t2: tk(526, 'B'),
|
|
242
|
+
t3: tk(786, 'C'),
|
|
243
|
+
trav1: label(258, 350, 184, 20, 'traverses', {
|
|
244
|
+
fontSize: 13,
|
|
245
|
+
color: '#5f6368',
|
|
246
|
+
}),
|
|
247
|
+
trav2: label(518, 350, 184, 20, 'traverses', {
|
|
248
|
+
fontSize: 13,
|
|
249
|
+
color: '#5f6368',
|
|
250
|
+
}),
|
|
251
|
+
trav3: label(778, 350, 184, 20, 'traverses', {
|
|
252
|
+
fontSize: 13,
|
|
253
|
+
color: '#5f6368',
|
|
254
|
+
}),
|
|
155
255
|
use1: label(258, 504, 184, 20, 'uses', { fontSize: 13, color: '#5f6368' }),
|
|
156
256
|
use2: label(518, 504, 184, 20, 'uses', { fontSize: 13, color: '#5f6368' }),
|
|
157
257
|
use3: label(778, 504, 184, 20, 'uses', { fontSize: 13, color: '#5f6368' }),
|
|
158
|
-
l1: attach('s1', 'c1'),
|
|
159
|
-
|
|
258
|
+
l1: attach('s1', 'c1'),
|
|
259
|
+
l2: attach('s2', 'c2'),
|
|
260
|
+
l3: attach('s3', 'c3'),
|
|
261
|
+
l4: attach('c1', 't1'),
|
|
262
|
+
l5: attach('c2', 't2'),
|
|
263
|
+
l6: attach('c3', 't3'),
|
|
160
264
|
};
|
|
161
265
|
}
|
|
162
266
|
// ── Service blueprint (six swimlanes) ─────────────────────────────────
|
|
163
267
|
function blueprint() {
|
|
164
|
-
const lanes = [
|
|
165
|
-
|
|
268
|
+
const lanes = [
|
|
269
|
+
'Physical Evidence',
|
|
270
|
+
'Customer Actions',
|
|
271
|
+
'On-stage Actions',
|
|
272
|
+
'Back-stage Actions',
|
|
273
|
+
'Support Processes',
|
|
274
|
+
'Support Systems',
|
|
275
|
+
];
|
|
276
|
+
const laneFill = [
|
|
277
|
+
'#fdeef2',
|
|
278
|
+
'#fbd5e0',
|
|
279
|
+
'#dff0fb',
|
|
280
|
+
'#d4e9f8',
|
|
281
|
+
'#d4e9f8',
|
|
282
|
+
'#d4e9f8',
|
|
283
|
+
];
|
|
166
284
|
const out = {};
|
|
167
285
|
lanes.forEach((name, i) => {
|
|
168
286
|
const y = 40 + i * 150;
|
|
169
|
-
out[`lane${i}`] = rect(36, y, 1280, 150, {
|
|
170
|
-
|
|
287
|
+
out[`lane${i}`] = rect(36, y, 1280, 150, {
|
|
288
|
+
fill: laneFill[i],
|
|
289
|
+
stroke: laneFill[i],
|
|
290
|
+
sw: 0,
|
|
291
|
+
});
|
|
292
|
+
out[`laneL${i}`] = label(52, y + 12, 260, 22, name, {
|
|
293
|
+
fontSize: 15,
|
|
294
|
+
align: TextAlign.Left,
|
|
295
|
+
});
|
|
171
296
|
});
|
|
172
297
|
const chev = (x, y, fill, t) => enode('activity', x, y, 200, 60, { fill, text: t, fontSize: 14 });
|
|
173
298
|
const box = (x, y, fill, t) => enode('object', x, y, 200, 60, { fill, text: t, fontSize: 14 });
|
|
@@ -175,11 +300,19 @@ function blueprint() {
|
|
|
175
300
|
Object.assign(out, {
|
|
176
301
|
af: rect(280, 64, 220, 70, { text: 'Admission Form' }),
|
|
177
302
|
cf: rect(1010, 64, 220, 70, { text: 'Confirmation' }),
|
|
178
|
-
sf: chev(280, 214, P, 'Send Form'),
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
303
|
+
sf: chev(280, 214, P, 'Send Form'),
|
|
304
|
+
fs: chev(600, 214, P, 'Follow status'),
|
|
305
|
+
rd: chev(1010, 214, P, 'Receive decision'),
|
|
306
|
+
fh: chev(280, 364, B, 'Form handling'),
|
|
307
|
+
cs: chev(600, 364, B, 'Customer support'),
|
|
308
|
+
ct: chev(1010, 364, B, 'Contact'),
|
|
309
|
+
csv: chev(280, 514, B, 'Customer service'),
|
|
310
|
+
cfu: box(1010, 514, B, 'Customer follow-up'),
|
|
311
|
+
pf: chev(600, 664, B, 'Process Form'),
|
|
312
|
+
dec: chev(900, 664, B, 'Decision [Accept|Reject]'),
|
|
313
|
+
crm: box(280, 814, B, 'CRM Application'),
|
|
314
|
+
cms: box(600, 814, B, 'Case Management'),
|
|
315
|
+
pay: box(900, 814, B, 'Payments System'),
|
|
183
316
|
a1: attach('af', 'sf', { arrow: true }),
|
|
184
317
|
a2: attach('sf', 'fs', { arrow: true }),
|
|
185
318
|
a3: attach('fs', 'rd', { arrow: true }),
|
|
@@ -197,8 +330,12 @@ function orgChart() {
|
|
|
197
330
|
const u = (x, y, w, t) => enode('object', x, y, w, 64, { fill: cyan, text: t, fontSize: 16 });
|
|
198
331
|
return {
|
|
199
332
|
org: u(420, 40, 180, 'Organisation'),
|
|
200
|
-
a: u(120, 200, 200, 'Business Unit A'),
|
|
201
|
-
|
|
333
|
+
a: u(120, 200, 200, 'Business Unit A'),
|
|
334
|
+
b: u(410, 200, 200, 'Business Unit B'),
|
|
335
|
+
c: u(700, 200, 200, 'Business Unit C'),
|
|
336
|
+
a1: u(60, 360, 170, 'Group A-1'),
|
|
337
|
+
a2: u(260, 360, 170, 'Group A-2'),
|
|
338
|
+
c1: u(715, 360, 170, 'Group C-1'),
|
|
202
339
|
// Attached orthogonal connectors: the router draws the org-chart elbows
|
|
203
340
|
// and the links follow when units are moved around.
|
|
204
341
|
e1: attach('org', 'a', { mode: ConnectorMode.Orthogonal }),
|
|
@@ -217,72 +354,12 @@ function orgChart() {
|
|
|
217
354
|
*/
|
|
218
355
|
export const DYN_SCALE = 4.8;
|
|
219
356
|
/**
|
|
220
|
-
* The
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
* (`labelOffset.distance` ≈ .75), short peer links keep the middle.
|
|
225
|
-
*/
|
|
226
|
-
export const EDGY_DYNAMIC_RELATIONS = [
|
|
227
|
-
// Identity
|
|
228
|
-
['content', 'purpose', 'expresses'],
|
|
229
|
-
['content', 'story', 'conveys', 0.75],
|
|
230
|
-
['story', 'purpose', 'contextualises', 0.8],
|
|
231
|
-
['organisation', 'purpose', 'pursues', 0.8],
|
|
232
|
-
['organisation', 'story', 'authors', 0.6],
|
|
233
|
-
['brand', 'purpose', 'represents', 0.8],
|
|
234
|
-
['brand', 'story', 'evokes', 0.65],
|
|
235
|
-
// Architecture
|
|
236
|
-
['organisation', 'process', 'performs', 0.8],
|
|
237
|
-
['process', 'capability', 'realises', 0.75],
|
|
238
|
-
['process', 'asset', 'requires'],
|
|
239
|
-
['capability', 'asset', 'requires', 0.75],
|
|
240
|
-
['organisation', 'capability', 'has', 0.6],
|
|
241
|
-
['product', 'capability', 'requires', 0.75],
|
|
242
|
-
['process', 'product', 'creates', 0.65],
|
|
243
|
-
// Experience
|
|
244
|
-
['task', 'journey', 'is part of', 0.6],
|
|
245
|
-
['task', 'channel', 'uses', 0.6],
|
|
246
|
-
['journey', 'channel', 'traverses', 0.6],
|
|
247
|
-
['product', 'task', 'serves', 0.9],
|
|
248
|
-
['product', 'journey', 'features in', 0.8],
|
|
249
|
-
['brand', 'task', 'supports', 0.8],
|
|
250
|
-
['brand', 'journey', 'appears in', 0.9],
|
|
251
|
-
// Intersections
|
|
252
|
-
['organisation', 'brand', 'builds', 0.85],
|
|
253
|
-
['organisation', 'product', 'makes', 0.65],
|
|
254
|
-
['product', 'brand', 'embodies', 0.8],
|
|
255
|
-
];
|
|
256
|
-
/**
|
|
257
|
-
* The 12 elements, centred coordinates in REFERENCE coords (the fixed space
|
|
258
|
-
* of consts.ts — the same space as `VENN`), laid out like the reference
|
|
259
|
-
* "elements & relations" diagram: aligned top row, Story/Capability flanks,
|
|
260
|
-
* Brand/Product astride the white centre, Task/Journey/Channel triangle.
|
|
261
|
-
* Exported (with {@link dynToModel}) for the containment test.
|
|
357
|
+
* The metamodel itself — the 12 elements and the 24 relations — now lives in
|
|
358
|
+
* `../metamodel.ts`, so the role vocabulary can DERIVE from it without this
|
|
359
|
+
* module and that one importing each other. Re-exported here under the names
|
|
360
|
+
* they have always had: nothing that reads them had to change.
|
|
262
361
|
*/
|
|
263
|
-
|
|
264
|
-
const PASTEL = {
|
|
265
|
-
identity: '#80ffb7',
|
|
266
|
-
architecture: '#a6c0ff',
|
|
267
|
-
experience: '#ff99bd',
|
|
268
|
-
organisation: '#80eaff',
|
|
269
|
-
brand: '#ffd580',
|
|
270
|
-
product: '#e599ff',
|
|
271
|
-
};
|
|
272
|
-
export const EDGY_DYNAMIC_NODES = {
|
|
273
|
-
content: { kind: 'object', cx: 237.5, cy: 100, fill: PASTEL.identity },
|
|
274
|
-
purpose: { kind: 'outcome', cx: 282.5, cy: 100, fill: PASTEL.identity },
|
|
275
|
-
organisation: { kind: 'object', cx: 340, cy: 100, w: 175, fill: PASTEL.organisation },
|
|
276
|
-
process: { kind: 'activity', cx: 397.5, cy: 100, fill: PASTEL.architecture },
|
|
277
|
-
asset: { kind: 'object', cx: 442.5, cy: 100, fill: PASTEL.architecture },
|
|
278
|
-
story: { kind: 'activity', cx: 255, cy: 152.5, fill: PASTEL.identity },
|
|
279
|
-
capability: { kind: 'outcome', cx: 425, cy: 152.5, w: 150, fill: PASTEL.architecture },
|
|
280
|
-
brand: { kind: 'object', cx: 280, cy: 195, fill: PASTEL.brand },
|
|
281
|
-
product: { kind: 'object', cx: 400, cy: 195, fill: PASTEL.product },
|
|
282
|
-
task: { kind: 'outcome', cx: 310, cy: 257.5, fill: PASTEL.experience },
|
|
283
|
-
journey: { kind: 'activity', cx: 370, cy: 257.5, fill: PASTEL.experience },
|
|
284
|
-
channel: { kind: 'object', cx: 340, cy: 297.5, fill: PASTEL.experience },
|
|
285
|
-
};
|
|
362
|
+
export { EDGY_DYNAMIC_NODES, EDGY_DYNAMIC_RELATIONS, } from '../metamodel.js';
|
|
286
363
|
/**
|
|
287
364
|
* Reference coords → template model coords: the background element sits at
|
|
288
365
|
* (0,0) and renders the cropped circles box, so a reference point maps to
|
|
@@ -298,17 +375,23 @@ function dynamic() {
|
|
|
298
375
|
showLabels: false,
|
|
299
376
|
showPictos: false,
|
|
300
377
|
cropToCircles: true,
|
|
378
|
+
// The frame a finding is attributed to. Stamped here and nowhere else in
|
|
379
|
+
// this template: the background is what makes the board an EDGY board.
|
|
380
|
+
role: EDGY_ROLE.facets,
|
|
301
381
|
xywh: `[0,0,${CROP.w * DYN_SCALE},${CROP.h * DYN_SCALE}]`,
|
|
302
382
|
},
|
|
303
383
|
};
|
|
304
|
-
for (const [key, { kind, cx, cy, w,
|
|
384
|
+
for (const [key, { kind, cx, cy, w, zone }] of Object.entries(EDGY_DYNAMIC_NODES)) {
|
|
305
385
|
const nw = w ?? NODE_SIZE[kind].w;
|
|
306
386
|
const nh = NODE_SIZE[kind].h;
|
|
307
387
|
const [mx, my] = dynToModel(cx, cy);
|
|
308
|
-
const name =
|
|
388
|
+
const name = edgyElementLabel(key);
|
|
309
389
|
out[key] = enode(kind, mx - nw / 2, my - nh / 2, nw, nh, {
|
|
310
390
|
text: name,
|
|
311
|
-
fill,
|
|
391
|
+
fill: EDGY_ZONE_FILL[zone],
|
|
392
|
+
// The OFFICIAL element, not just its kind: this template IS the
|
|
393
|
+
// metamodel, so its Purpose is a Purpose and not merely an outcome.
|
|
394
|
+
role: EDGY_ROLE[key],
|
|
312
395
|
});
|
|
313
396
|
}
|
|
314
397
|
EDGY_DYNAMIC_RELATIONS.forEach(([src, dst, verb, t], i) => {
|
|
@@ -322,21 +405,33 @@ function dynamic() {
|
|
|
322
405
|
rearEndpointStyle: PointStyle.None,
|
|
323
406
|
source: { id: src },
|
|
324
407
|
target: { id: dst },
|
|
408
|
+
// The verb, as a ROLE — one per canonical verb, derived from this very
|
|
409
|
+
// table (`../roles.ts`). Source is the subject and target the object
|
|
410
|
+
// (`docs/adr/0010` tier 1), which is exactly how the row above reads, so
|
|
411
|
+
// `edgy.non-canonical-link` finds all 24 of these sentences legal.
|
|
412
|
+
role: EDGY_VERB_ROLE[verb],
|
|
325
413
|
// Native connector label: the verb travels with the link. The x/y are
|
|
326
414
|
// re-centred on the path at the first layout, but the w/h ARE the label
|
|
327
415
|
// box — size it to the verb so the text lays out on one line. The
|
|
328
416
|
// distance slides the verb along the link like the reference diagram.
|
|
329
417
|
text: surfaceText(verb),
|
|
330
|
-
labelXYWH:
|
|
331
|
-
labelOffset: { distance: t ??
|
|
418
|
+
labelXYWH: edgyVerbLabelXYWH(verb),
|
|
419
|
+
labelOffset: { distance: t ?? EDGY_RELATION_LABEL_DISTANCE },
|
|
332
420
|
};
|
|
333
421
|
});
|
|
334
422
|
return out;
|
|
335
423
|
}
|
|
336
|
-
const single = (el) => ({
|
|
424
|
+
const single = (el) => ({
|
|
425
|
+
a: el,
|
|
426
|
+
});
|
|
337
427
|
const ATTRS = 'width="100%" height="100%" viewBox="0 0 135 80" xmlns="http://www.w3.org/2000/svg"';
|
|
338
428
|
function tpl(name, preview, elements) {
|
|
339
|
-
return {
|
|
429
|
+
return {
|
|
430
|
+
name,
|
|
431
|
+
type: 'template',
|
|
432
|
+
preview,
|
|
433
|
+
content: makeTemplateSnapshot(elements, name),
|
|
434
|
+
};
|
|
340
435
|
}
|
|
341
436
|
/** Exported for direct insertion from the EDGY senior toolbar menu. */
|
|
342
437
|
export const edgyDynamicTemplate = tpl('EDGY dynamic', `<svg ${ATTRS}><circle cx="55" cy="34" r="18" fill="#00ea4e" opacity="0.9"/><circle cx="80" cy="34" r="18" fill="#034cee" opacity="0.9"/><circle cx="67" cy="54" r="18" fill="#ff0056" opacity="0.9"/><path d="M55 30 H80 M56 31 L66 52 M79 31 L69 52" stroke="#fff" stroke-width="2"/><rect x="51" y="26" width="8" height="8" fill="#fff"/><circle cx="80" cy="30" r="4" fill="#fff"/><path d="M62 49 h6 l3 3 -3 3 h-6 z" fill="#fff"/></svg>`, dynamic());
|
|
@@ -347,9 +442,17 @@ export const edgyTemplateCategory = {
|
|
|
347
442
|
tpl('Customer journey', `<svg ${ATTRS} fill="none"><path d="M20 24 H110 L122 40 L110 56 H20 Z" fill="#f3a3c0"/><rect x="26" y="30" width="22" height="20" fill="none" stroke="#fff"/><rect x="54" y="30" width="22" height="20" fill="none" stroke="#fff"/><rect x="82" y="30" width="22" height="20" fill="none" stroke="#fff"/></svg>`, journey()),
|
|
348
443
|
tpl('Service blueprint', `<svg ${ATTRS} fill="none"><rect x="8" y="14" width="119" height="16" fill="#fbd5e0"/><rect x="8" y="32" width="119" height="34" fill="#d4e9f8"/><rect x="20" y="18" width="22" height="9" fill="#f5246e" opacity="0.5"/><rect x="20" y="40" width="22" height="9" fill="#2f6ff0" opacity="0.4"/><rect x="60" y="40" width="22" height="9" fill="#2f6ff0" opacity="0.4"/></svg>`, blueprint()),
|
|
349
444
|
tpl('Organisation chart', `<svg ${ATTRS} fill="none"><rect x="52" y="12" width="32" height="14" rx="2" fill="#4fd0ea"/><rect x="14" y="38" width="32" height="14" rx="2" fill="#4fd0ea"/><rect x="52" y="38" width="32" height="14" rx="2" fill="#4fd0ea"/><rect x="90" y="38" width="32" height="14" rx="2" fill="#4fd0ea"/><path d="M68 26 V32 M30 32 H106 M30 32 V38 M68 32 V38 M106 32 V38" stroke="#262626"/></svg>`, orgChart()),
|
|
350
|
-
tpl('Facets diagram', `<svg ${ATTRS}><circle cx="55" cy="34" r="18" fill="#00ea4e" opacity="0.9"/><circle cx="80" cy="34" r="18" fill="#034cee" opacity="0.9"/><circle cx="67" cy="54" r="18" fill="#ff0056" opacity="0.9"/></svg>`, single({
|
|
445
|
+
tpl('Facets diagram', `<svg ${ATTRS}><circle cx="55" cy="34" r="18" fill="#00ea4e" opacity="0.9"/><circle cx="80" cy="34" r="18" fill="#034cee" opacity="0.9"/><circle cx="67" cy="54" r="18" fill="#ff0056" opacity="0.9"/></svg>`, single({
|
|
446
|
+
type: 'edgy',
|
|
447
|
+
cropToCircles: true,
|
|
448
|
+
role: EDGY_ROLE.facets,
|
|
449
|
+
xywh: `[0,0,${CROP_LABELED.w * 1.5},${CROP_LABELED.h * 1.5}]`,
|
|
450
|
+
})),
|
|
351
451
|
edgyDynamicTemplate,
|
|
352
|
-
tpl('People', `<svg ${ATTRS} fill="#262626"><circle cx="67" cy="32" r="9" fill="none" stroke="#262626" stroke-width="2.4"/><path d="M50 60 a17 17 0 0 1 34 0" fill="none" stroke="#262626" stroke-width="2.4"/></svg>`, {
|
|
452
|
+
tpl('People', `<svg ${ATTRS} fill="#262626"><circle cx="67" cy="32" r="9" fill="none" stroke="#262626" stroke-width="2.4"/><path d="M50 60 a17 17 0 0 1 34 0" fill="none" stroke="#262626" stroke-width="2.4"/></svg>`, {
|
|
453
|
+
n: enode('people', 0, 0, 64, 64),
|
|
454
|
+
l: label(-28, 70, 120, 24, 'People', { fontSize: 16 }),
|
|
455
|
+
}),
|
|
353
456
|
tpl('Outcome', `<svg ${ATTRS} fill="none"><rect x="20" y="24" width="95" height="34" rx="6" stroke="#262626" stroke-width="2"/></svg>`, single(enode('outcome', 0, 0, 130, 80, { text: 'Outcome' }))),
|
|
354
457
|
tpl('Object', `<svg ${ATTRS} fill="none"><rect x="20" y="24" width="95" height="34" stroke="#262626" stroke-width="2"/></svg>`, single(enode('object', 0, 0, 130, 80, { text: 'Object' }))),
|
|
355
458
|
tpl('Activity', `<svg ${ATTRS} fill="none"><path d="M20 24 H98 L116 41 H116 L98 58 H20 Z" stroke="#262626" stroke-width="2" stroke-linejoin="round"/></svg>`, single(enode('activity', 0, 0, 140, 80, { text: 'Activity' }))),
|
package/dist/toolbar/config.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ export declare const edgyToolbarConfig: {
|
|
|
19
19
|
icon: TemplateResult;
|
|
20
20
|
active(ctx: ToolbarContext): boolean;
|
|
21
21
|
run(ctx: ToolbarContext): void;
|
|
22
|
+
}, {
|
|
23
|
+
id: string;
|
|
24
|
+
tooltip: string;
|
|
25
|
+
icon: TemplateResult<1>;
|
|
26
|
+
run(ctx: ToolbarContext): void;
|
|
22
27
|
}];
|
|
23
28
|
readonly when: (ctx: ToolbarContext) => boolean;
|
|
24
29
|
};
|
|
@@ -36,6 +41,11 @@ export declare const edgyBoardToolbarConfig: {
|
|
|
36
41
|
icon: TemplateResult;
|
|
37
42
|
active(ctx: ToolbarContext): boolean;
|
|
38
43
|
run(ctx: ToolbarContext): void;
|
|
44
|
+
}, {
|
|
45
|
+
id: string;
|
|
46
|
+
tooltip: string;
|
|
47
|
+
icon: TemplateResult<1>;
|
|
48
|
+
run(ctx: ToolbarContext): void;
|
|
39
49
|
}];
|
|
40
50
|
readonly when: (ctx: ToolbarContext) => boolean;
|
|
41
51
|
};
|
package/dist/toolbar/config.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { EdgelessCRUDIdentifier } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { createAutoLegend, dddLegendIcon } from '@formicoidea/labre-ddd-shared';
|
|
2
3
|
import { EdgyBoardElementModel, EdgyFacetsElementModel, } from '@formicoidea/labre-core/model';
|
|
3
|
-
import { ToolbarModuleExtension, } from '@formicoidea/labre-core/shared/services';
|
|
4
|
+
import { TelemetryProvider, ToolbarModuleExtension, } from '@formicoidea/labre-core/shared/services';
|
|
4
5
|
import { BlockFlavourIdentifier } from '@formicoidea/labre-core/std';
|
|
5
6
|
import { html } from 'lit';
|
|
7
|
+
import { EDGY_AUTO_LEGEND } from '../legend.js';
|
|
6
8
|
const ResizeIcon = html `<svg
|
|
7
9
|
width="24"
|
|
8
10
|
height="24"
|
|
@@ -75,11 +77,45 @@ function booleanToggle(Model, id, tooltip, icon, prop) {
|
|
|
75
77
|
},
|
|
76
78
|
};
|
|
77
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* The legend action, shared by the two EDGY backgrounds: both frame the same
|
|
82
|
+
* notation, so both answer the same question — "what is actually drawn here" —
|
|
83
|
+
* with the same table ({@link EDGY_AUTO_LEGEND}) and the same box.
|
|
84
|
+
*
|
|
85
|
+
* Registered ALWAYS-ON (`EdgyRenderViewExtension`) with the toggles it sits
|
|
86
|
+
* next to: a legend is real, editable elements written into the document, so
|
|
87
|
+
* generating one is authoring, not tooling a flag may take away
|
|
88
|
+
* (`docs/adr/0009`).
|
|
89
|
+
*/
|
|
90
|
+
function legendAction(Model, id) {
|
|
91
|
+
return {
|
|
92
|
+
id,
|
|
93
|
+
tooltip: 'Generate the legend (notation present)',
|
|
94
|
+
icon: dddLegendIcon,
|
|
95
|
+
run(ctx) {
|
|
96
|
+
const background = ctx.getSurfaceModelsByType(Model)[0];
|
|
97
|
+
if (!background)
|
|
98
|
+
return;
|
|
99
|
+
createAutoLegend(ctx.std, background, EDGY_AUTO_LEGEND);
|
|
100
|
+
ctx.std.getOptional(TelemetryProvider)?.track('FrameworkLegendCreated', {
|
|
101
|
+
// The WIRE value from `frameworks.ts` (`telemetryKey`), which for EDGY
|
|
102
|
+
// happens to be the module id itself. Same field set as Wardley's and
|
|
103
|
+
// the three DDD legend buttons, so the four are comparable.
|
|
104
|
+
framework: 'edgy',
|
|
105
|
+
element: 'legend',
|
|
106
|
+
page: 'whiteboard editor',
|
|
107
|
+
segment: 'element toolbar',
|
|
108
|
+
module: 'edgy toolbar',
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
78
113
|
export const edgyToolbarConfig = {
|
|
79
114
|
actions: [
|
|
80
115
|
booleanToggle(EdgyFacetsElementModel, 'a.toggle-resize', 'Enable / lock resizing', ResizeIcon, 'resizeEnabled'),
|
|
81
116
|
booleanToggle(EdgyFacetsElementModel, 'b.toggle-labels', 'Show / hide facet labels', LabelsIcon, 'showLabels'),
|
|
82
117
|
booleanToggle(EdgyFacetsElementModel, 'c.toggle-spotlight', 'Enable / disable hover spotlight', SpotlightIcon, 'spotlightEnabled'),
|
|
118
|
+
legendAction(EdgyFacetsElementModel, 'd.legend'),
|
|
83
119
|
],
|
|
84
120
|
when: ctx => ctx.getSurfaceModelsByType(EdgyFacetsElementModel).length > 0,
|
|
85
121
|
};
|
|
@@ -91,6 +127,7 @@ export const edgyBoardToolbarConfig = {
|
|
|
91
127
|
actions: [
|
|
92
128
|
booleanToggle(EdgyBoardElementModel, 'a.toggle-resize', 'Enable / lock resizing', ResizeIcon, 'resizeEnabled'),
|
|
93
129
|
booleanToggle(EdgyBoardElementModel, 'b.toggle-spotlight', 'Enable / disable hover spotlight', SpotlightIcon, 'spotlightEnabled'),
|
|
130
|
+
legendAction(EdgyBoardElementModel, 'c.legend'),
|
|
94
131
|
],
|
|
95
132
|
when: ctx => ctx.getSurfaceModelsByType(EdgyBoardElementModel).length > 0,
|
|
96
133
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
|
|
2
2
|
import { EmptyTool } from '@formicoidea/labre-core/gfx/pointer';
|
|
3
|
+
import { translateKey } from '@formicoidea/labre-core/shared/services';
|
|
3
4
|
import { EdgelessToolbarToolMixin } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
|
|
4
5
|
import { SignalWatcher } from '@formicoidea/labre-core/global/lit';
|
|
5
6
|
import { css, html, LitElement } from 'lit';
|
|
@@ -44,7 +45,10 @@ export class EdgelessEdgySeniorButton extends EdgelessToolbarToolMixin(SignalWat
|
|
|
44
45
|
translate: var(--active-x, 0) var(--active-y, 0);
|
|
45
46
|
rotate: var(--active-r, -2deg);
|
|
46
47
|
scale: var(--active-s, 1);
|
|
47
|
-
transition:
|
|
48
|
+
transition:
|
|
49
|
+
transform 0.3s ease,
|
|
50
|
+
translate 0.3s ease,
|
|
51
|
+
rotate 0.3s ease,
|
|
48
52
|
scale 0.3s ease;
|
|
49
53
|
}
|
|
50
54
|
.edgy-card svg {
|
|
@@ -71,7 +75,9 @@ export class EdgelessEdgySeniorButton extends EdgelessToolbarToolMixin(SignalWat
|
|
|
71
75
|
render() {
|
|
72
76
|
return html `<edgeless-toolbar-button
|
|
73
77
|
class="edgy-button"
|
|
74
|
-
.tooltip=${this.popper
|
|
78
|
+
.tooltip=${this.popper
|
|
79
|
+
? ''
|
|
80
|
+
: translateKey(this.edgeless.std, 'com.labre.framework.edgy', 'EDGY')}
|
|
75
81
|
.tooltipOffset=${4}
|
|
76
82
|
.active=${!!this.popper}
|
|
77
83
|
@click=${this._toggleMenu}
|
package/dist/toolbar/icons.d.ts
CHANGED
|
@@ -12,5 +12,13 @@ export declare const edgyPeopleIcon: import("lit-html").TemplateResult<2>;
|
|
|
12
12
|
export declare const edgyOutcomeIcon: import("lit-html").TemplateResult<2>;
|
|
13
13
|
/** Object — plain rectangle. */
|
|
14
14
|
export declare const edgyObjectIcon: import("lit-html").TemplateResult<2>;
|
|
15
|
+
/**
|
|
16
|
+
* Relation — a bare line between two ends, with the label riding on it.
|
|
17
|
+
*
|
|
18
|
+
* No arrowhead, exactly like the 24 relations of the metamodel template: EDGY
|
|
19
|
+
* draws its links as plain lines and lets the verb say which way the sentence
|
|
20
|
+
* runs. The little box in the middle IS the verb the tool writes there.
|
|
21
|
+
*/
|
|
22
|
+
export declare const edgyRelationIcon: import("lit-html").TemplateResult<2>;
|
|
15
23
|
/** Activity — right-pointing chevron. */
|
|
16
24
|
export declare const edgyActivityIcon: import("lit-html").TemplateResult<2>;
|
package/dist/toolbar/icons.js
CHANGED
|
@@ -44,6 +44,19 @@ export const edgyOutcomeIcon = svg `<svg width="24" height="24" viewBox="0 0 24
|
|
|
44
44
|
export const edgyObjectIcon = svg `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
45
45
|
<rect x="3.5" y="6.5" width="17" height="11" stroke="currentColor" stroke-width="1.6"/>
|
|
46
46
|
</svg>`;
|
|
47
|
+
/**
|
|
48
|
+
* Relation — a bare line between two ends, with the label riding on it.
|
|
49
|
+
*
|
|
50
|
+
* No arrowhead, exactly like the 24 relations of the metamodel template: EDGY
|
|
51
|
+
* draws its links as plain lines and lets the verb say which way the sentence
|
|
52
|
+
* runs. The little box in the middle IS the verb the tool writes there.
|
|
53
|
+
*/
|
|
54
|
+
export const edgyRelationIcon = svg `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
55
|
+
<path d="M3.5 12 H20.5" stroke="currentColor" stroke-width="1.6"/>
|
|
56
|
+
<circle cx="3.8" cy="12" r="1.6" fill="currentColor"/>
|
|
57
|
+
<circle cx="20.2" cy="12" r="1.6" fill="currentColor"/>
|
|
58
|
+
<rect x="8" y="8.6" width="8" height="6.8" rx="1.4" fill="var(--affine-background-primary-color, #fff)" stroke="currentColor" stroke-width="1.4"/>
|
|
59
|
+
</svg>`;
|
|
47
60
|
/** Activity — right-pointing chevron. */
|
|
48
61
|
export const edgyActivityIcon = svg `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
49
62
|
<path d="M3.5 6.5 H15 L20.5 12 L15 17.5 H3.5 Z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/>
|
|
@@ -3,6 +3,7 @@ import { html } from 'lit';
|
|
|
3
3
|
export const edgySeniorTool = SeniorToolExtension('edgy', ({ block }) => {
|
|
4
4
|
return {
|
|
5
5
|
name: 'EDGY',
|
|
6
|
+
labelKey: 'com.labre.framework.edgy',
|
|
6
7
|
content: html `<edgeless-edgy-senior-button
|
|
7
8
|
.edgeless=${block}
|
|
8
9
|
></edgeless-edgy-senior-button>`,
|
package/dist/translations.d.ts
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import { type TranslationKeyManifestEntry } from '@formicoidea/labre-core/std';
|
|
2
2
|
/**
|
|
3
|
-
* THIS framework's contribution to the translation-key manifest
|
|
3
|
+
* THIS framework's contribution to the translation-key manifest — every
|
|
4
|
+
* `com.labre.*` key EDGY can hand to `TranslationProvider.t`, derived from the
|
|
5
|
+
* very declarations the editor registers.
|
|
4
6
|
*
|
|
5
7
|
* Its command labels and descriptions are built from a TEMPLATE, so the
|
|
6
|
-
* concrete keys exist nowhere but in the declarations themselves and the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* concrete keys exist nowhere but in the declarations themselves and the core
|
|
9
|
+
* manifest could not restate them even if it wanted to. The same now holds for
|
|
10
|
+
* the roles: one key per canonical verb, derived from the metamodel, so a
|
|
11
|
+
* relation added there contributes its keys without anybody editing this file.
|
|
12
|
+
*
|
|
13
|
+
* That derivation is why the hand-drawn relation tool needed no line here: its
|
|
14
|
+
* `com.labre.commands.edgy.addRelation` and the `.description` key that carries
|
|
15
|
+
* its gesture sentence — the first DESCRIPTION any EDGY command has declared —
|
|
16
|
+
* are collected off `edgyCommands` with their English fallbacks, like every
|
|
17
|
+
* label before them.
|
|
18
|
+
*
|
|
19
|
+
* The contribution therefore ships WITH the framework: in the bundled
|
|
9
20
|
* distribution `@formicoidea/labre-framework-edgy` carries it, and a host
|
|
10
|
-
* composes it into its catalogue exactly as it already composes
|
|
11
|
-
*
|
|
12
|
-
* `packages/affine/all/src/translations.ts`.
|
|
21
|
+
* composes it into its catalogue exactly as it already composes `edgyCommands`
|
|
22
|
+
* into the command registry. See `packages/affine/all/src/translations.ts`.
|
|
13
23
|
*/
|
|
14
24
|
export declare const edgyTranslationEntries: TranslationKeyManifestEntry[];
|