@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,40 +1,31 @@
1
- import { EdgelessCRUDIdentifier } from '@formicoidea/labre-core/blocks/surface';
2
- import { rotatePoint } from '@formicoidea/labre-core/global/gfx';
3
- import { GfxElementModelView, GfxViewInteractionExtension, } from '@formicoidea/labre-core/std/gfx';
1
+ import { FrameworkBackgroundView } from '@formicoidea/labre-core/blocks/surface';
2
+ import { GfxViewInteractionExtension } from '@formicoidea/labre-core/std/gfx';
4
3
  import { cropLabeledScale, refScale } from './consts.js';
5
- import { getEdgyLabelHits, hitTestEdgyLabel, } from './label-layout.js';
6
- export class EdgyView extends GfxElementModelView {
7
- constructor() {
8
- super(...arguments);
9
- /** The in-place `<input>` used to edit a label, or null when idle. */
10
- this._labelEditor = null;
11
- }
4
+ import { getEdgyLabelHits, hitTestEdgyLabel } from './label-layout.js';
5
+ /**
6
+ * The EDGY facets diagram's view: the shared background gesture, aimed by this
7
+ * framework's own label layout.
8
+ *
9
+ * The three facet names are NOT declared in a `FrameworkBackgroundDef` — the
10
+ * diagram is a Venn figure fitted into a reference space, and its labels hang
11
+ * off the circles rather than off a plot — so the hit test stays here, mapped
12
+ * with the SAME fit the renderer used. Everything above it (where a
13
+ * double-click is allowed to land, the in-place `<input>`, the commit that
14
+ * refuses to write an untouched value) comes from
15
+ * {@link FrameworkBackgroundView}, which is where this diagram's own copy of it
16
+ * went — see issue #355.
17
+ */
18
+ export class EdgyView extends FrameworkBackgroundView {
12
19
  static { this.type = 'edgy'; }
13
- onCreated() {
14
- super.onCreated();
15
- this.on('dblclick', e => this._onDblClick(e));
16
- }
17
- onDestroyed() {
18
- this._closeLabelEditor();
19
- super.onDestroyed();
20
- }
21
- /** The editable facet label under a MODEL-space point, or null. */
22
- _labelAt(mx, my) {
20
+ /**
21
+ * A diagram is SELECTED by its border (`EdgyFacetsElementModel`), and the
22
+ * three facet labels widen that for the pointer — the base class' doing; this
23
+ * method only says where they are.
24
+ */
25
+ labelAt(lx, ly, w, h) {
23
26
  // No labels shown → nothing to edit (and the cropped mapping differs).
24
27
  if (!this.model.showLabels)
25
28
  return null;
26
- const [bx, by, w, h] = this.model.deserializedXYWH;
27
- // Convert the model-space point into element-local coordinates, undoing the
28
- // element rotation around its center.
29
- let lx = mx - bx;
30
- let ly = my - by;
31
- const rot = this.model.rotate ?? 0;
32
- if (rot) {
33
- const center = [bx + w / 2, by + h / 2];
34
- const [ux, uy] = rotatePoint([mx, my], center, -rot);
35
- lx = ux - bx;
36
- ly = uy - by;
37
- }
38
29
  // Map element-local coords into the fixed reference space the labels live
39
30
  // in — with the SAME fit the renderer used (cropped or letterboxed).
40
31
  const { s, ox, oy } = this.model.cropToCircles
@@ -42,91 +33,11 @@ export class EdgyView extends GfxElementModelView {
42
33
  : refScale(w, h);
43
34
  const rx = (lx - ox) / s;
44
35
  const ry = (ly - oy) / s;
45
- return hitTestEdgyLabel(getEdgyLabelHits(this.model), rx, ry);
46
- }
47
- /**
48
- * The diagram is SELECTED by its border (`EdgyFacetsElementModel`), but its
49
- * three facet labels must still receive the double-click that renames them.
50
- *
51
- * Same seam Wardley uses, for the same reason: the pointer router asks the
52
- * VIEW, so a framework declares its own gesture zones next to the code that
53
- * draws them, and the model layer keeps a hit test that only says what a
54
- * click SELECTS.
55
- */
56
- includesPoint(x, y, options, host) {
57
- if (super.includesPoint(x, y, options, host))
58
- return true;
59
- return this._labelAt(x, y) !== null;
60
- }
61
- /** Double-click on a label → edit its text in place. */
62
- _onDblClick(e) {
63
- if (this.model.isLocked())
64
- return;
65
- const [mx, my] = this.gfx.viewport.toModelCoord(e.x, e.y);
66
- const hit = this._labelAt(mx, my);
36
+ const hit = hitTestEdgyLabel(getEdgyLabelHits(this.model), rx, ry);
67
37
  if (!hit)
68
- return;
69
- this._openLabelEditor(hit.field, e);
70
- }
71
- _openLabelEditor(field, e) {
72
- this._closeLabelEditor();
73
- const input = document.createElement('input');
74
- input.value = String(this.model[field] ?? '');
75
- Object.assign(input.style, {
76
- position: 'fixed',
77
- left: `${e.raw.clientX}px`,
78
- top: `${e.raw.clientY}px`,
79
- transform: 'translate(-50%, -50%)',
80
- zIndex: '10000',
81
- minWidth: '140px',
82
- padding: '3px 8px',
83
- font: '14px Inter, sans-serif',
84
- color: 'var(--affine-text-primary-color, #1f2328)',
85
- background: 'var(--affine-background-overlay-panel-color, #ffffff)',
86
- border: '1px solid var(--affine-primary-color, #1e96eb)',
87
- borderRadius: '6px',
88
- boxShadow: 'var(--affine-shadow-2, 0 2px 8px rgba(0,0,0,0.18))',
89
- outline: 'none',
90
- });
91
- document.body.append(input);
92
- this._labelEditor = input;
93
- // Mark the element as "editing" so the global edgeless key handlers
94
- // (delete, escape, etc.) don't act on it while the user types.
95
- this.gfx.selection.set({ elements: [this.model.id], editing: true });
96
- input.focus();
97
- input.select();
98
- const commit = () => {
99
- if (this._labelEditor !== input)
100
- return;
101
- const value = input.value;
102
- this._closeLabelEditor();
103
- this.gfx.std.store.captureSync();
104
- this.gfx.std
105
- .get(EdgelessCRUDIdentifier)
106
- .updateElement(this.model.id, { [field]: value });
107
- };
108
- input.addEventListener('keydown', ev => {
109
- ev.stopPropagation();
110
- if (ev.key === 'Enter') {
111
- ev.preventDefault();
112
- commit();
113
- }
114
- else if (ev.key === 'Escape') {
115
- ev.preventDefault();
116
- this._closeLabelEditor();
117
- }
118
- });
119
- input.addEventListener('blur', commit);
120
- }
121
- _closeLabelEditor() {
122
- if (!this._labelEditor)
123
- return;
124
- const input = this._labelEditor;
125
- this._labelEditor = null;
126
- input.remove();
127
- if (this.isConnected) {
128
- this.gfx.selection.set({ elements: [this.model.id], editing: false });
129
- }
38
+ return null;
39
+ // The facet names carry a hard default, so the drawn words ARE the prop.
40
+ return { prop: hit.field, text: String(this.model[hit.field] ?? '') };
130
41
  }
131
42
  }
132
43
  /**
@@ -87,6 +87,26 @@ export type EdgyElementName = 'content' | 'purpose' | 'organisation' | 'process'
87
87
  * prints as a row label or a section title. One expression, one wording.
88
88
  */
89
89
  export declare const edgyElementLabel: (name: EdgyElementName | EdgyZone) => string;
90
+ /**
91
+ * The i18n key {@link edgyElementLabel} is the English default of — one key
92
+ * per lowercase name, shared by every surface that writes the word: the "EDGY
93
+ * dynamic" template (`templates/dynamic.ts`), the Facets overview,
94
+ * Organisation chart and Customer journey scenes (`templates/index.ts`), and
95
+ * the facets background's own creation site (`actions.ts`, `EdgyFacetsProps`).
96
+ * `organisation`, `brand` and `product` are BOTH an element name and an
97
+ * intersection zone id — one word, one key, exactly the "un mot partagé = une
98
+ * clé" rule.
99
+ */
100
+ export declare const edgyElementLabelKey: (name: EdgyElementName | EdgyZone) => string;
101
+ /**
102
+ * The i18n key one of the metamodel's 24 canonical VERBS is the English
103
+ * default of — drawn raw as a connector label wherever the "EDGY dynamic"
104
+ * template or a hand-composed scene uses it (`surfaceText(verb)`), sharing one
105
+ * key per distinct verb string across every surface that writes it.
106
+ */
107
+ export declare const edgyVerbSeedKey: (verb: string) => string;
108
+ /** Every distinct verb {@link EDGY_DYNAMIC_RELATIONS} carries, in table order. */
109
+ export declare const EDGY_DYNAMIC_VERBS: readonly string[];
90
110
  export declare const EDGY_DYNAMIC_NODES: Record<EdgyElementName, {
91
111
  kind: 'outcome' | 'object' | 'activity';
92
112
  cx: number;
package/dist/metamodel.js CHANGED
@@ -100,6 +100,28 @@ export const EDGY_DYNAMIC_RELATIONS = [
100
100
  * prints as a row label or a section title. One expression, one wording.
101
101
  */
102
102
  export const edgyElementLabel = (name) => name.charAt(0).toUpperCase() + name.slice(1);
103
+ /**
104
+ * The i18n key {@link edgyElementLabel} is the English default of — one key
105
+ * per lowercase name, shared by every surface that writes the word: the "EDGY
106
+ * dynamic" template (`templates/dynamic.ts`), the Facets overview,
107
+ * Organisation chart and Customer journey scenes (`templates/index.ts`), and
108
+ * the facets background's own creation site (`actions.ts`, `EdgyFacetsProps`).
109
+ * `organisation`, `brand` and `product` are BOTH an element name and an
110
+ * intersection zone id — one word, one key, exactly the "un mot partagé = une
111
+ * clé" rule.
112
+ */
113
+ export const edgyElementLabelKey = (name) => `com.labre.edgy.seed.${name}`;
114
+ /**
115
+ * The i18n key one of the metamodel's 24 canonical VERBS is the English
116
+ * default of — drawn raw as a connector label wherever the "EDGY dynamic"
117
+ * template or a hand-composed scene uses it (`surfaceText(verb)`), sharing one
118
+ * key per distinct verb string across every surface that writes it.
119
+ */
120
+ export const edgyVerbSeedKey = (verb) => `com.labre.edgy.seed.verb-${verb.replace(/\s+/g, '-')}`;
121
+ /** Every distinct verb {@link EDGY_DYNAMIC_RELATIONS} carries, in table order. */
122
+ export const EDGY_DYNAMIC_VERBS = [
123
+ ...new Set(EDGY_DYNAMIC_RELATIONS.map(([, , verb]) => verb)),
124
+ ];
103
125
  export const EDGY_DYNAMIC_NODES = {
104
126
  content: { kind: 'object', cx: 237.5, cy: 100, zone: 'identity' },
105
127
  purpose: { kind: 'outcome', cx: 282.5, cy: 100, zone: 'identity' },
@@ -1,4 +1,4 @@
1
- import { ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
1
+ import { ElementRendererExtension, taggedPath2D, } from '@formicoidea/labre-core/blocks/surface';
2
2
  import { shape as shapeRenderer } from '@formicoidea/labre-core/gfx/shape';
3
3
  import { DefaultTheme } from '@formicoidea/labre-core/model';
4
4
  import { PERSON_GLYPH_PATHS, PERSON_GLYPH_VIEWBOX } from './consts.js';
@@ -33,7 +33,7 @@ export const edgyNode = (model, ctx, matrix, renderer, rc, bound) => {
33
33
  ctx.translate(-PERSON_GLYPH_VIEWBOX / 2, -PERSON_GLYPH_VIEWBOX / 2);
34
34
  ctx.fillStyle = color;
35
35
  for (const d of PERSON_GLYPH_PATHS) {
36
- ctx.fill(new Path2D(d));
36
+ ctx.fill(taggedPath2D(d));
37
37
  }
38
38
  };
39
39
  export const EdgyNodeRendererExtension = ElementRendererExtension('edgyNode', edgyNode);
@@ -1,7 +1,8 @@
1
1
  import { makeTemplateSnapshot, surfaceText, } from '@formicoidea/labre-core/gfx/template';
2
2
  import { ConnectorMode, PointStyle, StrokeStyle } from '@formicoidea/labre-core/model';
3
+ import { translateKey } from '@formicoidea/labre-core/shared/services';
3
4
  import { CROP } from '../consts.js';
4
- import { EDGY_DYNAMIC_NODES, EDGY_DYNAMIC_RELATIONS, EDGY_ZONE_FILL, edgyElementLabel, } from '../metamodel.js';
5
+ import { EDGY_DYNAMIC_NODES, EDGY_DYNAMIC_RELATIONS, EDGY_ZONE_FILL, edgyElementLabel, edgyElementLabelKey, edgyVerbSeedKey, } from '../metamodel.js';
5
6
  import { NODE_SIZE, NODE_STROKE } from '../node/consts.js';
6
7
  import { edgyNodeProps, edgyNodeTextProps } from '../presets.js';
7
8
  import { EDGY_RELATION_LABEL_DISTANCE, edgyVerbLabelXYWH } from '../relation.js';
@@ -31,7 +32,15 @@ export const DYN_SCALE = 4.8;
31
32
  export function dynToModel(refX, refY) {
32
33
  return [(refX - CROP.x) * DYN_SCALE, (refY - CROP.y) * DYN_SCALE];
33
34
  }
34
- function dynamic() {
35
+ /**
36
+ * One label's resolved text — the host's catalogue when `std` is a real
37
+ * inserting editor, the English fallback when building the module's own
38
+ * `content` (no editor exists yet at that point).
39
+ */
40
+ function seedText(std, key, fallback) {
41
+ return std ? translateKey(std, key, fallback) : fallback;
42
+ }
43
+ function dynamic(std) {
35
44
  const out = {
36
45
  bg: {
37
46
  type: 'edgy',
@@ -48,7 +57,7 @@ function dynamic() {
48
57
  const nw = w ?? NODE_SIZE[kind].w;
49
58
  const nh = NODE_SIZE[kind].h;
50
59
  const [mx, my] = dynToModel(cx, cy);
51
- const name = edgyElementLabel(key);
60
+ const name = seedText(std, edgyElementLabelKey(key), edgyElementLabel(key));
52
61
  out[key] = {
53
62
  // What an EDGY node IS — the same description the toolbox draws from.
54
63
  ...edgyNodeProps(kind, {
@@ -65,6 +74,7 @@ function dynamic() {
65
74
  };
66
75
  }
67
76
  EDGY_DYNAMIC_RELATIONS.forEach(([src, dst, verb, t], i) => {
77
+ const verbText = seedText(std, edgyVerbSeedKey(verb), verb);
68
78
  out[`rel${i}`] = {
69
79
  type: 'connector',
70
80
  mode: ConnectorMode.Straight,
@@ -84,7 +94,12 @@ function dynamic() {
84
94
  // re-centred on the path at the first layout, but the w/h ARE the label
85
95
  // box — size it to the verb so the text lays out on one line. The
86
96
  // distance slides the verb along the link like the reference diagram.
87
- text: surfaceText(verb),
97
+ //
98
+ // The BOX (`edgyVerbLabelXYWH`) is still measured off the ENGLISH verb:
99
+ // it is layout, sized once from the table this template already reads,
100
+ // and a translated word longer than its English original wraps inside
101
+ // that box like any other native text rather than resizing it.
102
+ text: surfaceText(verbText),
88
103
  labelXYWH: edgyVerbLabelXYWH(verb),
89
104
  labelOffset: { distance: t ?? EDGY_RELATION_LABEL_DISTANCE },
90
105
  };
@@ -103,4 +118,5 @@ export const edgyDynamicTemplate = {
103
118
  // and fails the day the command is renamed.
104
119
  commandId: 'edgy.insertDynamic',
105
120
  content: makeTemplateSnapshot(dynamic(), NAME),
121
+ localize: std => makeTemplateSnapshot(dynamic(std), NAME),
106
122
  };
@@ -1,4 +1,132 @@
1
1
  import { type TemplateCategory } from '@formicoidea/labre-core/gfx/template';
2
+ import { type ChromeWording } from '@formicoidea/labre-core/shared/services';
3
+ /**
4
+ * The four hand-composed scenes' own tile names — chrome, not seeds: a
5
+ * `Template.nameKey`, re-resolved every time the panel opens, never written
6
+ * into a document. None of the four derives from a command.
7
+ */
8
+ export declare const EDGY_TEMPLATE_NAME_FACETS_OVERVIEW: ChromeWording;
9
+ export declare const EDGY_TEMPLATE_NAME_CUSTOMER_JOURNEY: ChromeWording;
10
+ export declare const EDGY_TEMPLATE_NAME_SERVICE_BLUEPRINT: ChromeWording;
11
+ export declare const EDGY_TEMPLATE_NAME_ORGANISATION_CHART: ChromeWording;
12
+ export declare const EDGY_TEMPLATE_SEED: {
13
+ readonly facetsTitle: {
14
+ key: string;
15
+ fallback: string;
16
+ };
17
+ readonly intersectionsTitle: {
18
+ key: string;
19
+ fallback: string;
20
+ };
21
+ readonly journeyStep: {
22
+ key: string;
23
+ fallback: string;
24
+ };
25
+ readonly channel: {
26
+ key: string;
27
+ fallback: string;
28
+ };
29
+ readonly taskN: {
30
+ key: string;
31
+ fallback: string;
32
+ };
33
+ readonly customer: {
34
+ key: string;
35
+ fallback: string;
36
+ };
37
+ readonly laneEvidence: {
38
+ key: string;
39
+ fallback: string;
40
+ };
41
+ readonly laneCustomerActions: {
42
+ key: string;
43
+ fallback: string;
44
+ };
45
+ readonly laneOnstage: {
46
+ key: string;
47
+ fallback: string;
48
+ };
49
+ readonly laneBackstage: {
50
+ key: string;
51
+ fallback: string;
52
+ };
53
+ readonly laneSupportProcesses: {
54
+ key: string;
55
+ fallback: string;
56
+ };
57
+ readonly laneSupportSystems: {
58
+ key: string;
59
+ fallback: string;
60
+ };
61
+ readonly admissionForm: {
62
+ key: string;
63
+ fallback: string;
64
+ };
65
+ readonly confirmation: {
66
+ key: string;
67
+ fallback: string;
68
+ };
69
+ readonly sendForm: {
70
+ key: string;
71
+ fallback: string;
72
+ };
73
+ readonly followStatus: {
74
+ key: string;
75
+ fallback: string;
76
+ };
77
+ readonly receiveDecision: {
78
+ key: string;
79
+ fallback: string;
80
+ };
81
+ readonly formHandling: {
82
+ key: string;
83
+ fallback: string;
84
+ };
85
+ readonly customerSupport: {
86
+ key: string;
87
+ fallback: string;
88
+ };
89
+ readonly contact: {
90
+ key: string;
91
+ fallback: string;
92
+ };
93
+ readonly customerService: {
94
+ key: string;
95
+ fallback: string;
96
+ };
97
+ readonly customerFollowUp: {
98
+ key: string;
99
+ fallback: string;
100
+ };
101
+ readonly processForm: {
102
+ key: string;
103
+ fallback: string;
104
+ };
105
+ readonly decisionAcceptReject: {
106
+ key: string;
107
+ fallback: string;
108
+ };
109
+ readonly crmApplication: {
110
+ key: string;
111
+ fallback: string;
112
+ };
113
+ readonly caseManagement: {
114
+ key: string;
115
+ fallback: string;
116
+ };
117
+ readonly paymentsSystem: {
118
+ key: string;
119
+ fallback: string;
120
+ };
121
+ readonly businessUnit: {
122
+ key: string;
123
+ fallback: string;
124
+ };
125
+ readonly group: {
126
+ key: string;
127
+ fallback: string;
128
+ };
129
+ };
2
130
  /**
3
131
  * The metamodel itself — the 12 elements and the 24 relations — lives in
4
132
  * `../metamodel.ts`, and the template that draws it in `./dynamic.ts`.