@formicoidea/labre-framework-edgy 0.40.0 → 0.42.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.
@@ -1,10 +1,102 @@
1
1
  import { makeTemplateSnapshot, surfaceText, surfaceYMap, templateFromCommand, } from '@formicoidea/labre-core/gfx/template';
2
2
  import { ConnectorMode, FontFamily, PointStyle, ShapeStyle, StrokeStyle, TextAlign, } from '@formicoidea/labre-core/model';
3
3
  import { NOTATION_NEUTRALS } from '@formicoidea/labre-core/shared/consts';
4
+ import { translateKey, } from '@formicoidea/labre-core/shared/services';
4
5
  import { edgyCommands } from '../commands.js';
6
+ import { edgyElementLabel, edgyElementLabelKey, edgyVerbSeedKey, } from '../metamodel.js';
5
7
  import { NODE_FILL, NODE_STROKE, NODE_STROKE_WIDTH } from '../node/consts.js';
6
8
  import { edgyNodeProps, edgyNodeTextProps } from '../presets.js';
7
9
  import { edgyDynamicTemplate } from './dynamic.js';
10
+ /**
11
+ * The seeds that belong to these four scenes only — a case name, a lane
12
+ * title, a step counter — and nowhere else in the framework. Declared once so
13
+ * the builders below and the manifest (`../translations.ts`) read the very
14
+ * same fallback. The twelve element/zone names and the two verbs these scenes
15
+ * REUSE (`Organisation`, `Task`, `traverses`…) go through
16
+ * `edgyElementLabelKey` / `edgyVerbSeedKey` instead — the same key the
17
+ * metamodel and `templates/dynamic.ts` already write, per "un mot partagé =
18
+ * une clé".
19
+ */
20
+ const S = (slug, fallback) => ({
21
+ key: `com.labre.edgy.seed.${slug}`,
22
+ fallback,
23
+ });
24
+ /**
25
+ * The four hand-composed scenes' own tile names — chrome, not seeds: a
26
+ * `Template.nameKey`, re-resolved every time the panel opens, never written
27
+ * into a document. None of the four derives from a command.
28
+ */
29
+ export const EDGY_TEMPLATE_NAME_FACETS_OVERVIEW = [
30
+ 'com.labre.edgy.template.facets-overview',
31
+ 'Facets overview',
32
+ ];
33
+ export const EDGY_TEMPLATE_NAME_CUSTOMER_JOURNEY = [
34
+ 'com.labre.edgy.template.customer-journey',
35
+ 'Customer journey',
36
+ ];
37
+ export const EDGY_TEMPLATE_NAME_SERVICE_BLUEPRINT = [
38
+ 'com.labre.edgy.template.service-blueprint',
39
+ 'Service blueprint',
40
+ ];
41
+ export const EDGY_TEMPLATE_NAME_ORGANISATION_CHART = [
42
+ 'com.labre.edgy.template.organisation-chart',
43
+ 'Organisation chart',
44
+ ];
45
+ export const EDGY_TEMPLATE_SEED = {
46
+ facetsTitle: S('facets-title', 'Facets'),
47
+ intersectionsTitle: S('intersections-title', 'Intersections'),
48
+ journeyStep: S('journey-step', 'Journey step {{n}}'),
49
+ channel: S('channel-n', 'Channel {{n}}'),
50
+ taskN: S('task-n', 'Task {{n}}'),
51
+ customer: S('customer', 'Customer'),
52
+ laneEvidence: S('lane-physical-evidence', 'Physical Evidence'),
53
+ laneCustomerActions: S('lane-customer-actions', 'Customer Actions'),
54
+ laneOnstage: S('lane-onstage-actions', 'On-stage Actions'),
55
+ laneBackstage: S('lane-backstage-actions', 'Back-stage Actions'),
56
+ laneSupportProcesses: S('lane-support-processes', 'Support Processes'),
57
+ laneSupportSystems: S('lane-support-systems', 'Support Systems'),
58
+ admissionForm: S('admission-form', 'Admission Form'),
59
+ confirmation: S('confirmation', 'Confirmation'),
60
+ sendForm: S('send-form', 'Send Form'),
61
+ followStatus: S('follow-status', 'Follow status'),
62
+ receiveDecision: S('receive-decision', 'Receive decision'),
63
+ formHandling: S('form-handling', 'Form handling'),
64
+ customerSupport: S('customer-support', 'Customer support'),
65
+ contact: S('contact', 'Contact'),
66
+ customerService: S('customer-service', 'Customer service'),
67
+ customerFollowUp: S('customer-follow-up', 'Customer follow-up'),
68
+ processForm: S('process-form', 'Process Form'),
69
+ decisionAcceptReject: S('decision-accept-reject', 'Decision [Accept|Reject]'),
70
+ crmApplication: S('crm-application', 'CRM Application'),
71
+ caseManagement: S('case-management', 'Case Management'),
72
+ paymentsSystem: S('payments-system', 'Payments System'),
73
+ businessUnit: S('business-unit', 'Business Unit {{unit}}'),
74
+ group: S('group', 'Group {{code}}'),
75
+ };
76
+ /**
77
+ * One seed's resolved text — the host's catalogue when `std` is a real
78
+ * inserting editor, the English fallback when building the module's own
79
+ * `content` (no editor exists yet at that point).
80
+ */
81
+ function seedText(std, def, params) {
82
+ return std
83
+ ? translateKey(std, def.key, def.fallback, params)
84
+ : // No interpolation library at module load: every fallback with a
85
+ // placeholder is filled by hand here, the same way `translateKey`
86
+ // fills it internally when a host has no entry for the key.
87
+ Object.entries(params ?? {}).reduce((text, [name, value]) => text.replace(`{{${name}}}`, String(value)), def.fallback);
88
+ }
89
+ /** One of the twelve element/zone names, shared with the rest of the framework. */
90
+ function elementLabel(std, name) {
91
+ return seedText(std, {
92
+ key: edgyElementLabelKey(name),
93
+ fallback: edgyElementLabel(name),
94
+ });
95
+ }
96
+ /** One of the metamodel's verbs, shared with `templates/dynamic.ts`. */
97
+ function verbLabel(std, verb) {
98
+ return seedText(std, { key: edgyVerbSeedKey(verb), fallback: verb });
99
+ }
8
100
  /**
9
101
  * The EDGY palette — DERIVED from the toolbox, one template per command.
10
102
  *
@@ -147,8 +239,9 @@ function attach(src, dst, opts = {}) {
147
239
  };
148
240
  }
149
241
  // ── Facets overview (six facets, sub-cards, facets/intersections) ─────
150
- function facetsOverview() {
242
+ function facetsOverview(std) {
151
243
  const out = {};
244
+ const el = (name) => elementLabel(std, name);
152
245
  const tall = (key, x, name, cards) => {
153
246
  const [hdr, sub] = C[key];
154
247
  out[`${key}F`] = rect(x, 140, 192, 460, {
@@ -200,39 +293,55 @@ function facetsOverview() {
200
293
  });
201
294
  };
202
295
  // lower facets first (drawn behind the tall ones at the overlaps)
203
- low('organisation', 240, 'Organisation');
204
- low('product', 664, 'Product');
205
- low('brand', 1088, 'Brand');
206
- tall('identity', 40, 'Identity', ['Purpose', 'Content', 'Story']);
207
- tall('architecture', 464, 'Architecture', ['Capability', 'Asset', 'Process']);
208
- tall('experience', 888, 'Experience', ['Task', 'Channel', 'Journey']);
209
- out.fLabel = label(464, 80, 192, 28, 'Facets', { fontSize: 18 });
296
+ low('organisation', 240, el('organisation'));
297
+ low('product', 664, el('product'));
298
+ low('brand', 1088, el('brand'));
299
+ tall('identity', 40, el('identity'), [
300
+ el('purpose'),
301
+ el('content'),
302
+ el('story'),
303
+ ]);
304
+ tall('architecture', 464, el('architecture'), [
305
+ el('capability'),
306
+ el('asset'),
307
+ el('process'),
308
+ ]);
309
+ tall('experience', 888, el('experience'), [
310
+ el('task'),
311
+ el('channel'),
312
+ el('journey'),
313
+ ]);
314
+ out.fLabel = label(464, 80, 192, 28, seedText(std, EDGY_TEMPLATE_SEED.facetsTitle), { fontSize: 18 });
210
315
  out.fBar = line(136, 120, 984, 120);
211
- out.iLabel = label(664, 740, 192, 28, 'Intersections', { fontSize: 18 });
316
+ out.iLabel = label(664, 740, 192, 28, seedText(std, EDGY_TEMPLATE_SEED.intersectionsTitle), { fontSize: 18 });
212
317
  out.iBar = line(336, 728, 1184, 728);
213
318
  return out;
214
319
  }
215
320
  // ── Customer journey ──────────────────────────────────────────────────
216
- function journey() {
321
+ function journey(std) {
217
322
  const step = (i, x) => enode('activity', x, 132, 224, 116, {
218
323
  fill: JOURNEY_PINK,
219
- text: `Journey step ${i}`,
324
+ text: seedText(std, EDGY_TEMPLATE_SEED.journeyStep, { n: i }),
220
325
  textColor: '#ffffff',
221
326
  fontSize: 18,
222
327
  });
223
328
  const ch = (x, n) => enode('object', x, 392, 184, 96, {
224
329
  fill: JOURNEY_PINK,
225
- text: `Channel ${n}`,
330
+ text: seedText(std, EDGY_TEMPLATE_SEED.channel, { n }),
226
331
  fontSize: 16,
227
332
  });
228
333
  const tk = (x, n) => enode('object', x, 540, 168, 92, {
229
334
  fill: JOURNEY_PINK,
230
- text: `Task ${n}`,
335
+ text: seedText(std, EDGY_TEMPLATE_SEED.taskN, { n }),
231
336
  fontSize: 16,
232
337
  });
338
+ const traverses = verbLabel(std, 'traverses');
339
+ const uses = verbLabel(std, 'uses');
233
340
  return {
234
341
  cust: enode('people', 40, 120, 64, 64, { fill: NODE_FILL }),
235
- custL: label(20, 192, 104, 24, 'Customer', { fontSize: 14 }),
342
+ custL: label(20, 192, 104, 24, seedText(std, EDGY_TEMPLATE_SEED.customer), {
343
+ fontSize: 14,
344
+ }),
236
345
  // The person and its name travel together, as `createEdgyPeople` writes
237
346
  // them. A group is placed AFTER its members: the id middleware remaps
238
347
  // `children` against elements it has already seen.
@@ -255,7 +364,7 @@ function journey() {
255
364
  roughness: 0,
256
365
  xywh: '[150,108,860,164]',
257
366
  },
258
- bandL: label(170, 116, 120, 24, 'Journey', {
367
+ bandL: label(170, 116, 120, 24, elementLabel(std, 'journey'), {
259
368
  color: '#ffffff',
260
369
  fontSize: 16,
261
370
  align: TextAlign.Left,
@@ -264,7 +373,7 @@ function journey() {
264
373
  s2: step(2, 500),
265
374
  s3: step(3, 760),
266
375
  rightTask: enode('object', 1060, 132, 168, 92, {
267
- text: 'Task',
376
+ text: elementLabel(std, 'task'),
268
377
  fontSize: 16,
269
378
  }),
270
379
  c1: ch(258, 'X'),
@@ -273,27 +382,27 @@ function journey() {
273
382
  t1: tk(266, 'A'),
274
383
  t2: tk(526, 'B'),
275
384
  t3: tk(786, 'C'),
276
- trav1: label(258, 350, 184, 20, 'traverses', {
385
+ trav1: label(258, 350, 184, 20, traverses, {
277
386
  fontSize: 13,
278
387
  color: NOTATION_NEUTRALS.label,
279
388
  }),
280
- trav2: label(518, 350, 184, 20, 'traverses', {
389
+ trav2: label(518, 350, 184, 20, traverses, {
281
390
  fontSize: 13,
282
391
  color: NOTATION_NEUTRALS.label,
283
392
  }),
284
- trav3: label(778, 350, 184, 20, 'traverses', {
393
+ trav3: label(778, 350, 184, 20, traverses, {
285
394
  fontSize: 13,
286
395
  color: NOTATION_NEUTRALS.label,
287
396
  }),
288
- use1: label(258, 504, 184, 20, 'uses', {
397
+ use1: label(258, 504, 184, 20, uses, {
289
398
  fontSize: 13,
290
399
  color: NOTATION_NEUTRALS.label,
291
400
  }),
292
- use2: label(518, 504, 184, 20, 'uses', {
401
+ use2: label(518, 504, 184, 20, uses, {
293
402
  fontSize: 13,
294
403
  color: NOTATION_NEUTRALS.label,
295
404
  }),
296
- use3: label(778, 504, 184, 20, 'uses', {
405
+ use3: label(778, 504, 184, 20, uses, {
297
406
  fontSize: 13,
298
407
  color: NOTATION_NEUTRALS.label,
299
408
  }),
@@ -306,14 +415,14 @@ function journey() {
306
415
  };
307
416
  }
308
417
  // ── Service blueprint (six swimlanes) ─────────────────────────────────
309
- function blueprint() {
418
+ function blueprint(std) {
310
419
  const lanes = [
311
- 'Physical Evidence',
312
- 'Customer Actions',
313
- 'On-stage Actions',
314
- 'Back-stage Actions',
315
- 'Support Processes',
316
- 'Support Systems',
420
+ seedText(std, EDGY_TEMPLATE_SEED.laneEvidence),
421
+ seedText(std, EDGY_TEMPLATE_SEED.laneCustomerActions),
422
+ seedText(std, EDGY_TEMPLATE_SEED.laneOnstage),
423
+ seedText(std, EDGY_TEMPLATE_SEED.laneBackstage),
424
+ seedText(std, EDGY_TEMPLATE_SEED.laneSupportProcesses),
425
+ seedText(std, EDGY_TEMPLATE_SEED.laneSupportSystems),
317
426
  ];
318
427
  const laneFill = [
319
428
  '#fdeef2',
@@ -340,21 +449,25 @@ function blueprint() {
340
449
  const box = (x, y, fill, t) => enode('object', x, y, 200, 60, { fill, text: t, fontSize: 14 });
341
450
  const P = C.experience[1], B = C.architecture[1];
342
451
  Object.assign(out, {
343
- af: rect(280, 64, 220, 70, { text: 'Admission Form' }),
344
- cf: rect(1010, 64, 220, 70, { text: 'Confirmation' }),
345
- sf: chev(280, 214, P, 'Send Form'),
346
- fs: chev(600, 214, P, 'Follow status'),
347
- rd: chev(1010, 214, P, 'Receive decision'),
348
- fh: chev(280, 364, B, 'Form handling'),
349
- cs: chev(600, 364, B, 'Customer support'),
350
- ct: chev(1010, 364, B, 'Contact'),
351
- csv: chev(280, 514, B, 'Customer service'),
352
- cfu: box(1010, 514, B, 'Customer follow-up'),
353
- pf: chev(600, 664, B, 'Process Form'),
354
- dec: chev(900, 664, B, 'Decision [Accept|Reject]'),
355
- crm: box(280, 814, B, 'CRM Application'),
356
- cms: box(600, 814, B, 'Case Management'),
357
- pay: box(900, 814, B, 'Payments System'),
452
+ af: rect(280, 64, 220, 70, {
453
+ text: seedText(std, EDGY_TEMPLATE_SEED.admissionForm),
454
+ }),
455
+ cf: rect(1010, 64, 220, 70, {
456
+ text: seedText(std, EDGY_TEMPLATE_SEED.confirmation),
457
+ }),
458
+ sf: chev(280, 214, P, seedText(std, EDGY_TEMPLATE_SEED.sendForm)),
459
+ fs: chev(600, 214, P, seedText(std, EDGY_TEMPLATE_SEED.followStatus)),
460
+ rd: chev(1010, 214, P, seedText(std, EDGY_TEMPLATE_SEED.receiveDecision)),
461
+ fh: chev(280, 364, B, seedText(std, EDGY_TEMPLATE_SEED.formHandling)),
462
+ cs: chev(600, 364, B, seedText(std, EDGY_TEMPLATE_SEED.customerSupport)),
463
+ ct: chev(1010, 364, B, seedText(std, EDGY_TEMPLATE_SEED.contact)),
464
+ csv: chev(280, 514, B, seedText(std, EDGY_TEMPLATE_SEED.customerService)),
465
+ cfu: box(1010, 514, B, seedText(std, EDGY_TEMPLATE_SEED.customerFollowUp)),
466
+ pf: chev(600, 664, B, seedText(std, EDGY_TEMPLATE_SEED.processForm)),
467
+ dec: chev(900, 664, B, seedText(std, EDGY_TEMPLATE_SEED.decisionAcceptReject)),
468
+ crm: box(280, 814, B, seedText(std, EDGY_TEMPLATE_SEED.crmApplication)),
469
+ cms: box(600, 814, B, seedText(std, EDGY_TEMPLATE_SEED.caseManagement)),
470
+ pay: box(900, 814, B, seedText(std, EDGY_TEMPLATE_SEED.paymentsSystem)),
358
471
  a1: attach('af', 'sf', { arrow: true }),
359
472
  a2: attach('sf', 'fs', { arrow: true }),
360
473
  a3: attach('fs', 'rd', { arrow: true }),
@@ -367,17 +480,19 @@ function blueprint() {
367
480
  return out;
368
481
  }
369
482
  // ── Organisation chart ────────────────────────────────────────────────
370
- function orgChart() {
483
+ function orgChart(std) {
371
484
  const cyan = C.organisation[0];
372
485
  const u = (x, y, w, t) => enode('object', x, y, w, 64, { fill: cyan, text: t, fontSize: 16 });
486
+ const businessUnit = (unit) => seedText(std, EDGY_TEMPLATE_SEED.businessUnit, { unit });
487
+ const group = (code) => seedText(std, EDGY_TEMPLATE_SEED.group, { code });
373
488
  return {
374
- org: u(420, 40, 180, 'Organisation'),
375
- a: u(120, 200, 200, 'Business Unit A'),
376
- b: u(410, 200, 200, 'Business Unit B'),
377
- c: u(700, 200, 200, 'Business Unit C'),
378
- a1: u(60, 360, 170, 'Group A-1'),
379
- a2: u(260, 360, 170, 'Group A-2'),
380
- c1: u(715, 360, 170, 'Group C-1'),
489
+ org: u(420, 40, 180, elementLabel(std, 'organisation')),
490
+ a: u(120, 200, 200, businessUnit('A')),
491
+ b: u(410, 200, 200, businessUnit('B')),
492
+ c: u(700, 200, 200, businessUnit('C')),
493
+ a1: u(60, 360, 170, group('A-1')),
494
+ a2: u(260, 360, 170, group('A-2')),
495
+ c1: u(715, 360, 170, group('C-1')),
381
496
  // Attached orthogonal connectors: the router draws the org-chart elbows
382
497
  // and the links follow when units are moved around.
383
498
  e1: attach('org', 'a', { mode: ConnectorMode.Orthogonal }),
@@ -397,26 +512,46 @@ function orgChart() {
397
512
  export { EDGY_DYNAMIC_NODES, EDGY_DYNAMIC_RELATIONS, } from '../metamodel.js';
398
513
  export { DYN_SCALE, dynToModel, edgyDynamicTemplate } from './dynamic.js';
399
514
  const ATTRS = 'width="100%" height="100%" viewBox="0 0 135 80" xmlns="http://www.w3.org/2000/svg"';
400
- /** A hand-composed template — what is left once the artefacts are derived. */
401
- function tpl(name, preview, elements) {
515
+ /**
516
+ * A hand-composed template — what is left once the artefacts are derived.
517
+ *
518
+ * `build` takes the OPTIONAL inserting editor: called with none, at module
519
+ * load, for the English `content`; called again with the real `std` as
520
+ * {@link Template.localize}, so the two builds read the very same layout and
521
+ * differ only in the words a seed resolves to (`docs/adr/0023`).
522
+ */
523
+ function tpl(name, preview, build, nameKey) {
402
524
  return {
403
525
  name,
404
526
  type: 'template',
405
527
  preview,
406
- content: makeTemplateSnapshot(elements, name),
528
+ nameKey,
529
+ content: makeTemplateSnapshot(build(), name),
530
+ localize: std => makeTemplateSnapshot(build(std), name),
407
531
  };
408
532
  }
409
533
  export const edgyTemplateCategory = {
410
534
  name: 'EDGY',
535
+ // Reuses the senior button's own key — see `TemplateCategory.nameKey`.
536
+ nameKey: 'com.labre.framework.edgy',
411
537
  templates: [
412
- tpl('Facets overview', `<svg ${ATTRS} fill="none"><rect x="6" y="20" width="18" height="46" rx="3" fill="#1ec873"/><rect x="46" y="20" width="18" height="46" rx="3" fill="#2f6ff0"/><rect x="86" y="20" width="18" height="46" rx="3" fill="#f5246e"/><rect x="26" y="30" width="18" height="40" rx="3" fill="#4fd0ea"/><rect x="66" y="30" width="18" height="40" rx="3" fill="#cf8cff"/><rect x="106" y="30" width="18" height="40" rx="3" fill="#eeba51"/></svg>`, facetsOverview()),
413
- 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()),
414
- 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()),
415
- 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="${NODE_STROKE}"/></svg>`, orgChart()),
538
+ tpl('Facets overview', `<svg ${ATTRS} fill="none"><rect x="6" y="20" width="18" height="46" rx="3" fill="#1ec873"/><rect x="46" y="20" width="18" height="46" rx="3" fill="#2f6ff0"/><rect x="86" y="20" width="18" height="46" rx="3" fill="#f5246e"/><rect x="26" y="30" width="18" height="40" rx="3" fill="#4fd0ea"/><rect x="66" y="30" width="18" height="40" rx="3" fill="#cf8cff"/><rect x="106" y="30" width="18" height="40" rx="3" fill="#eeba51"/></svg>`, facetsOverview, EDGY_TEMPLATE_NAME_FACETS_OVERVIEW[0]),
539
+ 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, EDGY_TEMPLATE_NAME_CUSTOMER_JOURNEY[0]),
540
+ 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, EDGY_TEMPLATE_NAME_SERVICE_BLUEPRINT[0]),
541
+ 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="${NODE_STROKE}"/></svg>`, orgChart, EDGY_TEMPLATE_NAME_ORGANISATION_CHART[0]),
542
+ // Kept: `addFacets`'s own label is "Enterprise Design facets" (the senior
543
+ // sub-menu's wording), and this tile's tooltip has always said the
544
+ // shorter "Facets diagram". `resolveTemplateName` still resolves it
545
+ // through the command's `labelKey`, with THIS literal (not the command's)
546
+ // as the fallback, so the tile keeps its own English wording with no
547
+ // catalogue.
416
548
  templateFromCommand(byId('edgy.addFacets'), `<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>`, 'Facets diagram'),
417
549
  edgyDynamicTemplate,
418
550
  // The blank board had no template at all until the palette was derived —
419
551
  // the coverage test is what said so.
552
+ //
553
+ // Kept, same reason as `addFacets` above: `addBoard`'s own label spells
554
+ // out "(hover spotlight)", which this tile's tooltip never has.
420
555
  templateFromCommand(byId('edgy.addBoard'), `<svg ${ATTRS} fill="none"><rect x="8" y="9" width="119" height="62" rx="8" fill="${NOTATION_NEUTRALS.cardFill}" stroke="${NOTATION_NEUTRALS.cardBorder}" stroke-width="2"/><path d="M52 30 H84 M53 31 L66 58 M83 31 L70 58" stroke="${NODE_STROKE}" stroke-width="1.4"/><rect x="48" y="26" width="9" height="9" fill="#00ea4e"/><circle cx="84" cy="30" r="4.5" fill="#034cee"/><path d="M61 54 h8 l4 4 -4 4 h-8 z" fill="#ff0056"/></svg>`, 'EDGY board'),
421
556
  templateFromCommand(byId('edgy.addPeople'), `<svg ${ATTRS} fill="${NODE_STROKE}"><circle cx="67" cy="32" r="9" fill="none" stroke="${NODE_STROKE}" stroke-width="2.4"/><path d="M50 60 a17 17 0 0 1 34 0" fill="none" stroke="${NODE_STROKE}" stroke-width="2.4"/></svg>`),
422
557
  templateFromCommand(byId('edgy.addOutcome'), `<svg ${ATTRS} fill="none"><rect x="20" y="24" width="95" height="34" rx="6" stroke="${NODE_STROKE}" stroke-width="2"/></svg>`),
@@ -1,5 +1,7 @@
1
- import { type ChromeWording, type ToolbarContext } from '@formicoidea/labre-core/shared/services';
1
+ import { type ChromeWording, type ToolbarContext, type ToolbarModuleConfig } from '@formicoidea/labre-core/shared/services';
2
2
  import { type TemplateResult } from 'lit';
3
+ /** Every wording above, for `translations.ts`'s manifest contribution. */
4
+ export declare const EDGY_TOOLBAR_WORDINGS: readonly ChromeWording[];
3
5
  export declare const edgyToolbarConfig: {
4
6
  readonly actions: [{
5
7
  id: string;
@@ -15,11 +17,6 @@ export declare const edgyToolbarConfig: {
15
17
  icon: TemplateResult;
16
18
  active(ctx: ToolbarContext): boolean;
17
19
  run(ctx: ToolbarContext): void;
18
- }, {
19
- id: string;
20
- tooltipWording: ChromeWording;
21
- icon: TemplateResult<1>;
22
- run(ctx: ToolbarContext): void;
23
20
  }];
24
21
  readonly when: (ctx: ToolbarContext) => boolean;
25
22
  };
@@ -39,12 +36,11 @@ export declare const edgyBoardToolbarConfig: {
39
36
  icon: TemplateResult;
40
37
  active(ctx: ToolbarContext): boolean;
41
38
  run(ctx: ToolbarContext): void;
42
- }, {
43
- id: string;
44
- tooltipWording: ChromeWording;
45
- icon: TemplateResult<1>;
46
- run(ctx: ToolbarContext): void;
47
39
  }];
48
40
  readonly when: (ctx: ToolbarContext) => boolean;
49
41
  };
50
42
  export declare const edgyBoardToolbarExtension: import("@formicoidea/labre-core/store").ExtensionType;
43
+ export declare const edgyToolingToolbarConfig: ToolbarModuleConfig;
44
+ export declare const edgyBoardToolingToolbarConfig: ToolbarModuleConfig;
45
+ export declare const edgyToolingToolbarExtension: import("@formicoidea/labre-core/store").ExtensionType;
46
+ export declare const edgyBoardToolingToolbarExtension: import("@formicoidea/labre-core/store").ExtensionType;
@@ -1,10 +1,22 @@
1
- import { EdgelessCRUDIdentifier } from '@formicoidea/labre-core/blocks/surface';
2
- import { createAutoLegend, dddLegendIcon } from '@formicoidea/labre-ddd-shared';
1
+ import { EdgelessCRUDIdentifier, legendToolbarAction, validationToolbarConfig, } from '@formicoidea/labre-core/blocks/surface';
3
2
  import { EdgyBoardElementModel, EdgyFacetsElementModel, } from '@formicoidea/labre-core/model';
4
- import { BOARD_LEGEND_NOTATION, BOARD_RESIZE_TOGGLE, TelemetryProvider, ToolbarModuleExtension, } from '@formicoidea/labre-core/shared/services';
3
+ import { BOARD_RESIZE_TOGGLE, ToolbarModuleExtension, } from '@formicoidea/labre-core/shared/services';
5
4
  import { BlockFlavourIdentifier } from '@formicoidea/labre-core/std';
6
5
  import { html } from 'lit';
7
- import { EDGY_AUTO_LEGEND } from '../legend.js';
6
+ /** The two toolbar tooltips this file used to hard-code as English literals. */
7
+ const FACET_LABELS_TOGGLE = [
8
+ 'com.labre.edgy.toolbar.facet-labels',
9
+ 'Show / hide facet labels',
10
+ ];
11
+ const HOVER_SPOTLIGHT_TOGGLE = [
12
+ 'com.labre.edgy.toolbar.hover-spotlight',
13
+ 'Enable / disable hover spotlight',
14
+ ];
15
+ /** Every wording above, for `translations.ts`'s manifest contribution. */
16
+ export const EDGY_TOOLBAR_WORDINGS = [
17
+ FACET_LABELS_TOGGLE,
18
+ HOVER_SPOTLIGHT_TOGGLE,
19
+ ];
8
20
  const ResizeIcon = html `<svg
9
21
  width="24"
10
22
  height="24"
@@ -82,46 +94,12 @@ function booleanToggle(Model, id, tooltip, icon, prop) {
82
94
  },
83
95
  };
84
96
  }
85
- /**
86
- * The legend action, shared by the two EDGY backgrounds: both frame the same
87
- * notation, so both answer the same question — "what is actually drawn here" —
88
- * with the same table ({@link EDGY_AUTO_LEGEND}) and the same box.
89
- *
90
- * Registered ALWAYS-ON (`EdgyRenderViewExtension`) with the toggles it sits
91
- * next to: a legend is real, editable elements written into the document, so
92
- * generating one is authoring, not tooling a flag may take away
93
- * (`docs/adr/0009`).
94
- */
95
- function legendAction(Model, id) {
96
- return {
97
- id,
98
- tooltipWording: BOARD_LEGEND_NOTATION,
99
- icon: dddLegendIcon,
100
- run(ctx) {
101
- const background = ctx.getSurfaceModelsByType(Model)[0];
102
- if (!background)
103
- return;
104
- createAutoLegend(ctx.std, background, EDGY_AUTO_LEGEND);
105
- ctx.std.getOptional(TelemetryProvider)?.track('FrameworkLegendCreated', {
106
- // The WIRE value from `frameworks.ts` (`telemetryKey`), which for EDGY
107
- // happens to be the module id itself. Same field set as Wardley's and
108
- // the three DDD legend buttons, so the four are comparable.
109
- framework: 'edgy',
110
- element: 'legend',
111
- page: 'whiteboard editor',
112
- segment: 'element toolbar',
113
- module: 'edgy toolbar',
114
- });
115
- },
116
- };
117
- }
118
97
  export const edgyToolbarConfig = {
119
98
  actions: [
120
99
  booleanToggle(EdgyFacetsElementModel, 'a.toggle-resize', BOARD_RESIZE_TOGGLE, ResizeIcon, 'resizeEnabled'),
121
- booleanToggle(EdgyFacetsElementModel, 'b.toggle-labels', 'Show / hide facet labels', LabelsIcon, 'showLabels'),
100
+ booleanToggle(EdgyFacetsElementModel, 'b.toggle-labels', FACET_LABELS_TOGGLE, LabelsIcon, 'showLabels'),
122
101
  // No spotlight toggle here: the hover spotlight is BOARD logic and lives on
123
102
  // the EDGY board toolbar below. The Venn only carries APPEARANCE (#195).
124
- legendAction(EdgyFacetsElementModel, 'c.legend'),
125
103
  ],
126
104
  when: ctx => ctx.getSurfaceModelsByType(EdgyFacetsElementModel).length > 0,
127
105
  };
@@ -132,8 +110,7 @@ export const edgyToolbarExtension = ToolbarModuleExtension({
132
110
  export const edgyBoardToolbarConfig = {
133
111
  actions: [
134
112
  booleanToggle(EdgyBoardElementModel, 'a.toggle-resize', BOARD_RESIZE_TOGGLE, ResizeIcon, 'resizeEnabled'),
135
- booleanToggle(EdgyBoardElementModel, 'b.toggle-spotlight', 'Enable / disable hover spotlight', SpotlightIcon, 'spotlightEnabled'),
136
- legendAction(EdgyBoardElementModel, 'c.legend'),
113
+ booleanToggle(EdgyBoardElementModel, 'b.toggle-spotlight', HOVER_SPOTLIGHT_TOGGLE, SpotlightIcon, 'spotlightEnabled'),
137
114
  ],
138
115
  when: ctx => ctx.getSurfaceModelsByType(EdgyBoardElementModel).length > 0,
139
116
  };
@@ -141,3 +118,55 @@ export const edgyBoardToolbarExtension = ToolbarModuleExtension({
141
118
  id: BlockFlavourIdentifier('affine:surface:edgyBoard'),
142
119
  config: edgyBoardToolbarConfig,
143
120
  });
121
+ /**
122
+ * A background's flag-gated row, WHOLE: the legend button and the Validation
123
+ * dropdown, in ONE module — registered by `EdgyViewExtension` on the `custom:`
124
+ * flavour slot of each of the two frames.
125
+ *
126
+ * ## Why one module and not two
127
+ *
128
+ * `renderToolbar` merges exactly four slots per element and
129
+ * `ToolbarModuleExtension` binds by DI variant, so a second module claiming
130
+ * `custom:affine:surface:edgy` throws `DuplicateServiceDefinitionError` before
131
+ * the editor finishes setting up. The always-on module above holds the
132
+ * APPEARANCE toggles a stored board must keep; these two entries are gated by
133
+ * the same flag and appear together or not at all.
134
+ *
135
+ * ## Why the legend moved out of the always-on half
136
+ *
137
+ * It used to sit with the toggles, argued as authoring. `docs/adr/0026` (which
138
+ * amends `docs/adr/0009`) reverses that reading: GENERATING a legend is
139
+ * tooling — it is the framework telling you what its notation means — while the
140
+ * legend it wrote is content, made of plain shapes, text and this pack's own
141
+ * always-on renderers, and keeps being painted with the flag off.
142
+ *
143
+ * `c.legend` and not the factory's default `b.`: the sort is lexicographic and
144
+ * both frames already spend `a.` and `b.` on their toggles.
145
+ */
146
+ function toolingConfig(Model) {
147
+ return {
148
+ actions: [
149
+ legendToolbarAction({
150
+ id: 'c.legend',
151
+ Model,
152
+ owner: 'edgy',
153
+ framework: 'edgy',
154
+ }),
155
+ // The generic dropdown, not an EDGY variant of it: the config names no
156
+ // framework — it reads the registered rules and profiles — so this is the
157
+ // very same object wardley, bpmn, the context map and C4 register.
158
+ ...validationToolbarConfig.actions,
159
+ ],
160
+ when: (ctx) => ctx.getSurfaceModelsByType(Model).length > 0,
161
+ };
162
+ }
163
+ export const edgyToolingToolbarConfig = toolingConfig(EdgyFacetsElementModel);
164
+ export const edgyBoardToolingToolbarConfig = toolingConfig(EdgyBoardElementModel);
165
+ export const edgyToolingToolbarExtension = ToolbarModuleExtension({
166
+ id: BlockFlavourIdentifier('custom:affine:surface:edgy'),
167
+ config: edgyToolingToolbarConfig,
168
+ });
169
+ export const edgyBoardToolingToolbarExtension = ToolbarModuleExtension({
170
+ id: BlockFlavourIdentifier('custom:affine:surface:edgyBoard'),
171
+ config: edgyBoardToolingToolbarConfig,
172
+ });
@@ -10,6 +10,8 @@ export declare class EdgelessEdgySeniorButton extends EdgelessEdgySeniorButton_b
10
10
  enableActiveBackground: boolean;
11
11
  type: typeof EmptyTool;
12
12
  private _toggleMenu;
13
+ /** Lit while one of THIS framework's artefacts is armed. See the Wardley one. */
14
+ private get _armed();
13
15
  render(): import("lit-html").TemplateResult<1>;
14
16
  }
15
17
  export {};
@@ -1,7 +1,7 @@
1
1
  import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
2
2
  import { EmptyTool } from '@formicoidea/labre-core/gfx/pointer';
3
3
  import { translateKey } from '@formicoidea/labre-core/shared/services';
4
- import { EdgelessToolbarToolMixin } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
4
+ import { armedArtefact, EdgelessToolbarToolMixin, } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
5
5
  import { SignalWatcher } from '@formicoidea/labre-core/global/lit';
6
6
  import { css, html, LitElement } from 'lit';
7
7
  import { edgyToolbarIcon } from './icons.js';
@@ -72,6 +72,10 @@ export class EdgelessEdgySeniorButton extends EdgelessToolbarToolMixin(SignalWat
72
72
  const menu = this.createPopper('edgeless-edgy-menu', this);
73
73
  menu.element.edgeless = this.edgeless;
74
74
  }
75
+ /** Lit while one of THIS framework's artefacts is armed. See the Wardley one. */
76
+ get _armed() {
77
+ return armedArtefact(this.gfx)?.owner === 'edgy';
78
+ }
75
79
  render() {
76
80
  return html `<edgeless-toolbar-button
77
81
  class="edgy-button"
@@ -79,7 +83,7 @@ export class EdgelessEdgySeniorButton extends EdgelessToolbarToolMixin(SignalWat
79
83
  ? ''
80
84
  : translateKey(this.edgeless.std, 'com.labre.framework.edgy', 'EDGY')}
81
85
  .tooltipOffset=${4}
82
- .active=${!!this.popper}
86
+ .active=${!!this.popper || this._armed}
83
87
  @click=${this._toggleMenu}
84
88
  >
85
89
  <div class="edgy-root">
@@ -1 +1,40 @@
1
+ import type { FrameworkPalette, NamedPalette } from '@formicoidea/labre-core/components/color-picker';
2
+ import { type ChromeWording } from '@formicoidea/labre-core/shared/services';
3
+ /**
4
+ * The twelve EDGY swatches' own names, resolved by `resolvePaletteLabel`
5
+ * (`@labre/affine-components/color-picker`) through each swatch's own
6
+ * {@link NamedPalette.labelWording} — see Wardley's own
7
+ * `WARDLEY_PALETTE_WORDINGS` (`../../wardley/src/toolbar/node-config.ts`)
8
+ * for the same mechanism applied to a sibling framework; the two never
9
+ * import each other.
10
+ *
11
+ * Exported so `../translations.ts` can list them in the manifest under
12
+ * source `chrome`.
13
+ */
14
+ export declare const EDGY_PALETTE_WORDING_IDENTITY: ChromeWording;
15
+ export declare const EDGY_PALETTE_WORDING_ARCHITECTURE: ChromeWording;
16
+ export declare const EDGY_PALETTE_WORDING_EXPERIENCE: ChromeWording;
17
+ export declare const EDGY_PALETTE_WORDING_ORGANISATION: ChromeWording;
18
+ export declare const EDGY_PALETTE_WORDING_BRAND: ChromeWording;
19
+ export declare const EDGY_PALETTE_WORDING_PRODUCT: ChromeWording;
20
+ export declare const EDGY_PALETTE_WORDING_IDENTITY_LIGHT: ChromeWording;
21
+ export declare const EDGY_PALETTE_WORDING_ARCHITECTURE_LIGHT: ChromeWording;
22
+ export declare const EDGY_PALETTE_WORDING_EXPERIENCE_LIGHT: ChromeWording;
23
+ export declare const EDGY_PALETTE_WORDING_ORGANISATION_LIGHT: ChromeWording;
24
+ export declare const EDGY_PALETTE_WORDING_BRAND_LIGHT: ChromeWording;
25
+ export declare const EDGY_PALETTE_WORDING_PRODUCT_LIGHT: ChromeWording;
26
+ export declare const EDGY_PALETTE_WORDINGS: readonly ChromeWording[];
27
+ /**
28
+ * From the default editor palette we keep ONLY the neutrals (greys, white,
29
+ * black, transparent) — the historical colours are dropped in favour of the
30
+ * EDGY swatches above.
31
+ */
32
+ export declare const EDGY_PALETTE_LIST: NamedPalette[];
33
+ /**
34
+ * EDGY's page of the colour pickers' carousel (`docs/adr/0027`) — the same
35
+ * list its node picker is seeded with, offered on every other picker too.
36
+ * Registered from the FLAG-GATED view extension: offering hues is tooling
37
+ * (`docs/adr/0009`).
38
+ */
39
+ export declare const EDGY_FRAMEWORK_PALETTE: FrameworkPalette;
1
40
  export declare const edgyNodeToolbarExtension: import("@formicoidea/labre-core/store").ExtensionType;