@formicoidea/labre-framework-edgy 0.23.0 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/consts.d.ts +47 -0
- package/{src/consts.ts → dist/consts.js} +46 -50
- package/dist/descriptor.d.ts +7 -0
- package/{src/descriptor.ts → dist/descriptor.js} +1 -1
- package/dist/effects.d.ts +10 -0
- package/dist/effects.js +7 -0
- package/dist/element-renderer.d.ts +17 -0
- package/dist/element-renderer.js +177 -0
- package/dist/element-view.d.ts +19 -0
- package/dist/element-view.js +123 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/label-layout.d.ts +46 -0
- package/dist/label-layout.js +69 -0
- package/dist/node/consts.d.ts +34 -0
- package/{src/node/consts.ts → dist/node/consts.js} +19 -26
- package/dist/node/node-renderer.d.ts +14 -0
- package/dist/node/node-renderer.js +40 -0
- package/dist/node/node-view.d.ts +16 -0
- package/{src/node/node-view.ts → dist/node/node-view.js} +15 -21
- package/dist/templates/index.d.ts +3 -0
- package/dist/templates/index.js +210 -0
- package/dist/toolbar/config.d.ts +20 -0
- package/dist/toolbar/config.js +71 -0
- package/dist/toolbar/edgy-menu.d.ts +31 -0
- package/dist/toolbar/edgy-menu.js +195 -0
- package/dist/toolbar/edgy-senior-button.d.ts +16 -0
- package/{src/toolbar/edgy-senior-button.ts → dist/toolbar/edgy-senior-button.js} +33 -38
- package/dist/toolbar/icons.d.ts +13 -0
- package/{src/toolbar/icons.ts → dist/toolbar/icons.js} +20 -25
- package/dist/toolbar/node-config.d.ts +2 -0
- package/dist/toolbar/node-config.js +162 -0
- package/dist/toolbar/senior-tool.d.ts +2 -0
- package/{src/toolbar/senior-tool.ts → dist/toolbar/senior-tool.js} +5 -5
- package/dist/view.d.ts +7 -0
- package/dist/view.js +36 -0
- package/package.json +15 -6
- package/src/effects.ts +0 -14
- package/src/element-renderer.ts +0 -208
- package/src/element-view.ts +0 -145
- package/src/index.ts +0 -1
- package/src/label-layout.ts +0 -105
- package/src/node/node-renderer.ts +0 -64
- package/src/templates/index.ts +0 -254
- package/src/toolbar/config.ts +0 -96
- package/src/toolbar/edgy-menu.ts +0 -242
- package/src/toolbar/node-config.ts +0 -202
- package/src/view.ts +0 -39
package/src/effects.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { EdgelessEdgyMenu } from './toolbar/edgy-menu';
|
|
2
|
-
import { EdgelessEdgySeniorButton } from './toolbar/edgy-senior-button';
|
|
3
|
-
|
|
4
|
-
export function effects() {
|
|
5
|
-
customElements.define('edgeless-edgy-menu', EdgelessEdgyMenu);
|
|
6
|
-
customElements.define('edgeless-edgy-senior-button', EdgelessEdgySeniorButton);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
declare global {
|
|
10
|
-
interface HTMLElementTagNameMap {
|
|
11
|
-
'edgeless-edgy-menu': EdgelessEdgyMenu;
|
|
12
|
-
'edgeless-edgy-senior-button': EdgelessEdgySeniorButton;
|
|
13
|
-
}
|
|
14
|
-
}
|
package/src/element-renderer.ts
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ElementRenderer,
|
|
3
|
-
ElementRendererExtension,
|
|
4
|
-
} from '@formicoidea/labre-core/blocks/surface';
|
|
5
|
-
import type { EdgyFacetsElementModel } from '@formicoidea/labre-core/model';
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
COLORS,
|
|
9
|
-
FONT_FAMILY,
|
|
10
|
-
LABEL_FONT_SIZE,
|
|
11
|
-
PICTO_STROKE,
|
|
12
|
-
REF_H,
|
|
13
|
-
REF_W,
|
|
14
|
-
refScale,
|
|
15
|
-
VENN,
|
|
16
|
-
} from './consts';
|
|
17
|
-
import { CIRCLE_A, CIRCLE_B, CIRCLE_C, facetLabelAnchors } from './label-layout';
|
|
18
|
-
|
|
19
|
-
type Pt = { x: number; y: number };
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Canvas renderer for the EDGY Enterprise Design Facets diagram — reproduces the
|
|
23
|
-
* validated mockup: three overlapping circles (Identity / Architecture /
|
|
24
|
-
* Experience), the three pairwise intersection regions (Organisation / Brand /
|
|
25
|
-
* Product) painted by clip-intersection, the white centre, the six white
|
|
26
|
-
* pictograms and the three facet labels placed outside the circles.
|
|
27
|
-
*
|
|
28
|
-
* The whole diagram is drawn in the fixed reference space and scaled uniformly
|
|
29
|
-
* to the element bounds so the circles never distort.
|
|
30
|
-
*/
|
|
31
|
-
export const edgy: ElementRenderer<EdgyFacetsElementModel> = (
|
|
32
|
-
model,
|
|
33
|
-
ctx,
|
|
34
|
-
matrix
|
|
35
|
-
) => {
|
|
36
|
-
const [, , w, h] = model.deserializedXYWH;
|
|
37
|
-
const cx = w / 2;
|
|
38
|
-
const cy = h / 2;
|
|
39
|
-
ctx.setTransform(
|
|
40
|
-
matrix.translateSelf(cx, cy).rotateSelf(model.rotate).translateSelf(-cx, -cy)
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
// Uniform fit of the reference design, centered (letterboxed).
|
|
44
|
-
const { s, ox, oy } = refScale(w, h);
|
|
45
|
-
ctx.translate(ox, oy);
|
|
46
|
-
ctx.scale(s, s);
|
|
47
|
-
|
|
48
|
-
const A = CIRCLE_A;
|
|
49
|
-
const B = CIRCLE_B;
|
|
50
|
-
const C = CIRCLE_C;
|
|
51
|
-
const R = VENN.R;
|
|
52
|
-
|
|
53
|
-
const disc = (c: Pt, color: string) => {
|
|
54
|
-
ctx.beginPath();
|
|
55
|
-
ctx.arc(c.x, c.y, R, 0, Math.PI * 2);
|
|
56
|
-
ctx.fillStyle = color;
|
|
57
|
-
ctx.fill();
|
|
58
|
-
};
|
|
59
|
-
// Paint the region common to every circle in `cs` (clip-intersection).
|
|
60
|
-
const inter = (cs: Pt[], color: string) => {
|
|
61
|
-
ctx.save();
|
|
62
|
-
for (const c of cs) {
|
|
63
|
-
ctx.beginPath();
|
|
64
|
-
ctx.arc(c.x, c.y, R, 0, Math.PI * 2);
|
|
65
|
-
ctx.clip();
|
|
66
|
-
}
|
|
67
|
-
ctx.fillStyle = color;
|
|
68
|
-
ctx.fillRect(0, 0, REF_W, REF_H);
|
|
69
|
-
ctx.restore();
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// ── Facets + intersections ──────────────────────────────────────────
|
|
73
|
-
disc(A, COLORS.identity);
|
|
74
|
-
disc(B, COLORS.architecture);
|
|
75
|
-
disc(C, COLORS.experience);
|
|
76
|
-
inter([A, B], COLORS.organisation);
|
|
77
|
-
inter([A, C], COLORS.brand);
|
|
78
|
-
inter([B, C], COLORS.product);
|
|
79
|
-
inter([A, B, C], COLORS.center);
|
|
80
|
-
|
|
81
|
-
// White separating outlines.
|
|
82
|
-
ctx.strokeStyle = COLORS.separator;
|
|
83
|
-
ctx.lineWidth = 2.5;
|
|
84
|
-
for (const c of [A, B, C]) {
|
|
85
|
-
ctx.beginPath();
|
|
86
|
-
ctx.arc(c.x, c.y, R, 0, Math.PI * 2);
|
|
87
|
-
ctx.stroke();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ── White pictos ────────────────────────────────────────────────────
|
|
91
|
-
const pictoSetup = () => {
|
|
92
|
-
ctx.strokeStyle = COLORS.picto;
|
|
93
|
-
ctx.fillStyle = COLORS.picto;
|
|
94
|
-
ctx.lineWidth = PICTO_STROKE;
|
|
95
|
-
ctx.lineJoin = 'round';
|
|
96
|
-
ctx.lineCap = 'round';
|
|
97
|
-
};
|
|
98
|
-
const lens = (x: number, y: number) => {
|
|
99
|
-
pictoSetup();
|
|
100
|
-
ctx.beginPath();
|
|
101
|
-
ctx.arc(x, y, 10, 0, Math.PI * 2);
|
|
102
|
-
ctx.stroke();
|
|
103
|
-
ctx.beginPath();
|
|
104
|
-
ctx.moveTo(x + 7, y + 7);
|
|
105
|
-
ctx.lineTo(x + 15, y + 15);
|
|
106
|
-
ctx.stroke();
|
|
107
|
-
};
|
|
108
|
-
const house = (x: number, y: number) => {
|
|
109
|
-
pictoSetup();
|
|
110
|
-
ctx.beginPath();
|
|
111
|
-
ctx.moveTo(x - 11, y + 9);
|
|
112
|
-
ctx.lineTo(x - 11, y - 2);
|
|
113
|
-
ctx.lineTo(x, y - 11);
|
|
114
|
-
ctx.lineTo(x + 11, y - 2);
|
|
115
|
-
ctx.lineTo(x + 11, y + 9);
|
|
116
|
-
ctx.closePath();
|
|
117
|
-
ctx.stroke();
|
|
118
|
-
};
|
|
119
|
-
const heart = (x: number, y: number) => {
|
|
120
|
-
pictoSetup();
|
|
121
|
-
ctx.beginPath();
|
|
122
|
-
ctx.moveTo(x, y + 11);
|
|
123
|
-
ctx.bezierCurveTo(x - 14, y - 1, x - 9, y - 12, x, y - 4);
|
|
124
|
-
ctx.bezierCurveTo(x + 9, y - 12, x + 14, y - 1, x, y + 11);
|
|
125
|
-
ctx.closePath();
|
|
126
|
-
ctx.stroke();
|
|
127
|
-
};
|
|
128
|
-
const network = (x: number, y: number) => {
|
|
129
|
-
pictoSetup();
|
|
130
|
-
ctx.beginPath();
|
|
131
|
-
ctx.moveTo(x, y - 9);
|
|
132
|
-
ctx.lineTo(x - 9, y + 7);
|
|
133
|
-
ctx.lineTo(x + 9, y + 7);
|
|
134
|
-
ctx.closePath();
|
|
135
|
-
ctx.stroke();
|
|
136
|
-
for (const [px, py] of [
|
|
137
|
-
[x, y - 9],
|
|
138
|
-
[x - 9, y + 7],
|
|
139
|
-
[x + 9, y + 7],
|
|
140
|
-
]) {
|
|
141
|
-
ctx.beginPath();
|
|
142
|
-
ctx.arc(px, py, 2.6, 0, Math.PI * 2);
|
|
143
|
-
ctx.fill();
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
const sun = (x: number, y: number) => {
|
|
147
|
-
pictoSetup();
|
|
148
|
-
ctx.beginPath();
|
|
149
|
-
ctx.arc(x, y, 6, 0, Math.PI * 2);
|
|
150
|
-
ctx.stroke();
|
|
151
|
-
for (let i = 0; i < 8; i++) {
|
|
152
|
-
const a = (i * Math.PI) / 4;
|
|
153
|
-
ctx.beginPath();
|
|
154
|
-
ctx.moveTo(x + Math.cos(a) * 9, y + Math.sin(a) * 9);
|
|
155
|
-
ctx.lineTo(x + Math.cos(a) * 12, y + Math.sin(a) * 12);
|
|
156
|
-
ctx.stroke();
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
const cube = (x: number, y: number) => {
|
|
160
|
-
pictoSetup();
|
|
161
|
-
const p = [
|
|
162
|
-
[x, y - 11],
|
|
163
|
-
[x + 10, y - 5],
|
|
164
|
-
[x + 10, y + 6],
|
|
165
|
-
[x, y + 12],
|
|
166
|
-
[x - 10, y + 6],
|
|
167
|
-
[x - 10, y - 5],
|
|
168
|
-
];
|
|
169
|
-
ctx.beginPath();
|
|
170
|
-
p.forEach(([qx, qy], i) => (i ? ctx.lineTo(qx, qy) : ctx.moveTo(qx, qy)));
|
|
171
|
-
ctx.closePath();
|
|
172
|
-
ctx.stroke();
|
|
173
|
-
ctx.beginPath();
|
|
174
|
-
ctx.moveTo(x - 10, y - 5);
|
|
175
|
-
ctx.lineTo(x, y + 1);
|
|
176
|
-
ctx.lineTo(x + 10, y - 5);
|
|
177
|
-
ctx.moveTo(x, y + 1);
|
|
178
|
-
ctx.lineTo(x, y + 12);
|
|
179
|
-
ctx.stroke();
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
// Single-facet zone pictos.
|
|
183
|
-
lens(A.x - 30, A.y - 22);
|
|
184
|
-
house(B.x + 30, B.y - 22);
|
|
185
|
-
heart(C.x, C.y + 36);
|
|
186
|
-
// Intersection pictos at the validated sweet spots.
|
|
187
|
-
network(VENN.cx, 114);
|
|
188
|
-
sun(VENN.cx - 58, 206);
|
|
189
|
-
cube(VENN.cx + 58, 206);
|
|
190
|
-
|
|
191
|
-
// ── Labels (outside the circles) ────────────────────────────────────
|
|
192
|
-
if (model.showLabels) {
|
|
193
|
-
ctx.font = `500 ${LABEL_FONT_SIZE}px ${FONT_FAMILY}`;
|
|
194
|
-
ctx.textBaseline = 'middle';
|
|
195
|
-
const colorByField: Record<string, string> = {
|
|
196
|
-
identityLabel: COLORS.identity,
|
|
197
|
-
architectureLabel: COLORS.architecture,
|
|
198
|
-
experienceLabel: COLORS.experience,
|
|
199
|
-
};
|
|
200
|
-
for (const { field, text, x, y, align } of facetLabelAnchors(model)) {
|
|
201
|
-
ctx.fillStyle = colorByField[field];
|
|
202
|
-
ctx.textAlign = align;
|
|
203
|
-
ctx.fillText(text, x, y);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
export const EdgyFacetsRendererExtension = ElementRendererExtension('edgy', edgy);
|
package/src/element-view.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { EdgelessCRUDIdentifier } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
-
import type { EdgyFacetsElementModel } from '@formicoidea/labre-core/model';
|
|
3
|
-
import { rotatePoint } from '@formicoidea/labre-core/global/gfx';
|
|
4
|
-
import type { PointerEventState } from '@formicoidea/labre-core/std';
|
|
5
|
-
import {
|
|
6
|
-
GfxElementModelView,
|
|
7
|
-
GfxViewInteractionExtension,
|
|
8
|
-
} from '@formicoidea/labre-core/std/gfx';
|
|
9
|
-
|
|
10
|
-
import { refScale } from './consts';
|
|
11
|
-
import {
|
|
12
|
-
type EdgyLabelField,
|
|
13
|
-
getEdgyLabelHits,
|
|
14
|
-
hitTestEdgyLabel,
|
|
15
|
-
} from './label-layout';
|
|
16
|
-
|
|
17
|
-
export class EdgyView extends GfxElementModelView<EdgyFacetsElementModel> {
|
|
18
|
-
static override type: string = 'edgy';
|
|
19
|
-
|
|
20
|
-
/** The in-place `<input>` used to edit a label, or null when idle. */
|
|
21
|
-
private _labelEditor: HTMLInputElement | null = null;
|
|
22
|
-
|
|
23
|
-
override onCreated(): void {
|
|
24
|
-
super.onCreated();
|
|
25
|
-
this.on('dblclick', e => this._onDblClick(e));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
override onDestroyed(): void {
|
|
29
|
-
this._closeLabelEditor();
|
|
30
|
-
super.onDestroyed();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Double-click on a label → edit its text in place. */
|
|
34
|
-
private _onDblClick(e: PointerEventState): void {
|
|
35
|
-
if (this.model.isLocked()) return;
|
|
36
|
-
|
|
37
|
-
const [mx, my] = this.gfx.viewport.toModelCoord(e.x, e.y);
|
|
38
|
-
const [bx, by, w, h] = this.model.deserializedXYWH;
|
|
39
|
-
|
|
40
|
-
// Convert the model-space point into element-local coordinates, undoing the
|
|
41
|
-
// element rotation around its center.
|
|
42
|
-
let lx = mx - bx;
|
|
43
|
-
let ly = my - by;
|
|
44
|
-
const rot = this.model.rotate ?? 0;
|
|
45
|
-
if (rot) {
|
|
46
|
-
const center: [number, number] = [bx + w / 2, by + h / 2];
|
|
47
|
-
const [ux, uy] = rotatePoint([mx, my], center, -rot);
|
|
48
|
-
lx = ux - bx;
|
|
49
|
-
ly = uy - by;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Map element-local coords into the fixed reference space the labels live in.
|
|
53
|
-
const { s, ox, oy } = refScale(w, h);
|
|
54
|
-
const rx = (lx - ox) / s;
|
|
55
|
-
const ry = (ly - oy) / s;
|
|
56
|
-
|
|
57
|
-
const hit = hitTestEdgyLabel(getEdgyLabelHits(this.model), rx, ry);
|
|
58
|
-
if (!hit) return;
|
|
59
|
-
|
|
60
|
-
this._openLabelEditor(hit.field, e);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
private _openLabelEditor(field: EdgyLabelField, e: PointerEventState): void {
|
|
64
|
-
this._closeLabelEditor();
|
|
65
|
-
|
|
66
|
-
const input = document.createElement('input');
|
|
67
|
-
input.value = String(this.model[field] ?? '');
|
|
68
|
-
Object.assign(input.style, {
|
|
69
|
-
position: 'fixed',
|
|
70
|
-
left: `${e.raw.clientX}px`,
|
|
71
|
-
top: `${e.raw.clientY}px`,
|
|
72
|
-
transform: 'translate(-50%, -50%)',
|
|
73
|
-
zIndex: '10000',
|
|
74
|
-
minWidth: '140px',
|
|
75
|
-
padding: '3px 8px',
|
|
76
|
-
font: '14px Inter, sans-serif',
|
|
77
|
-
color: 'var(--affine-text-primary-color, #1f2328)',
|
|
78
|
-
background: 'var(--affine-background-overlay-panel-color, #ffffff)',
|
|
79
|
-
border: '1px solid var(--affine-primary-color, #1e96eb)',
|
|
80
|
-
borderRadius: '6px',
|
|
81
|
-
boxShadow: 'var(--affine-shadow-2, 0 2px 8px rgba(0,0,0,0.18))',
|
|
82
|
-
outline: 'none',
|
|
83
|
-
});
|
|
84
|
-
document.body.append(input);
|
|
85
|
-
this._labelEditor = input;
|
|
86
|
-
|
|
87
|
-
// Mark the element as "editing" so the global edgeless key handlers
|
|
88
|
-
// (delete, escape, etc.) don't act on it while the user types.
|
|
89
|
-
this.gfx.selection.set({ elements: [this.model.id], editing: true });
|
|
90
|
-
|
|
91
|
-
input.focus();
|
|
92
|
-
input.select();
|
|
93
|
-
|
|
94
|
-
const commit = () => {
|
|
95
|
-
if (this._labelEditor !== input) return;
|
|
96
|
-
const value = input.value;
|
|
97
|
-
this._closeLabelEditor();
|
|
98
|
-
this.gfx.std.store.captureSync();
|
|
99
|
-
this.gfx.std
|
|
100
|
-
.get(EdgelessCRUDIdentifier)
|
|
101
|
-
.updateElement(this.model.id, { [field]: value });
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
input.addEventListener('keydown', ev => {
|
|
105
|
-
ev.stopPropagation();
|
|
106
|
-
if (ev.key === 'Enter') {
|
|
107
|
-
ev.preventDefault();
|
|
108
|
-
commit();
|
|
109
|
-
} else if (ev.key === 'Escape') {
|
|
110
|
-
ev.preventDefault();
|
|
111
|
-
this._closeLabelEditor();
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
input.addEventListener('blur', commit);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
private _closeLabelEditor(): void {
|
|
118
|
-
if (!this._labelEditor) return;
|
|
119
|
-
const input = this._labelEditor;
|
|
120
|
-
this._labelEditor = null;
|
|
121
|
-
input.remove();
|
|
122
|
-
if (this.isConnected) {
|
|
123
|
-
this.gfx.selection.set({ elements: [this.model.id], editing: false });
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Resize gating: the resize handles are hidden unless `model.resizeEnabled` is
|
|
130
|
-
* true (toggled from the toolbar). Moving/selecting stays available throughout.
|
|
131
|
-
*/
|
|
132
|
-
export const EdgyInteraction = GfxViewInteractionExtension<EdgyView>(
|
|
133
|
-
EdgyView.type,
|
|
134
|
-
{
|
|
135
|
-
handleResize({ model }) {
|
|
136
|
-
return {
|
|
137
|
-
beforeResize({ set }) {
|
|
138
|
-
if (!model.resizeEnabled) {
|
|
139
|
-
set({ allowedHandlers: [] });
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
},
|
|
144
|
-
}
|
|
145
|
-
);
|
package/src/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/src/label-layout.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import type { EdgyFacetsElementModel } from '@formicoidea/labre-core/model';
|
|
2
|
-
|
|
3
|
-
import { LABEL_FONT_SIZE, VENN } from './consts';
|
|
4
|
-
|
|
5
|
-
/** The three circle centres in reference coords. */
|
|
6
|
-
export const CIRCLE_A = { x: VENN.cx - 0.866 * VENN.r0, y: VENN.cy - 0.5 * VENN.r0 }; // Identity
|
|
7
|
-
export const CIRCLE_B = { x: VENN.cx + 0.866 * VENN.r0, y: VENN.cy - 0.5 * VENN.r0 }; // Architecture
|
|
8
|
-
export const CIRCLE_C = { x: VENN.cx, y: VENN.cy + VENN.r0 }; // Experience
|
|
9
|
-
|
|
10
|
-
/** The editable label fields of the facets diagram. */
|
|
11
|
-
export type EdgyLabelField =
|
|
12
|
-
| 'identityLabel'
|
|
13
|
-
| 'architectureLabel'
|
|
14
|
-
| 'experienceLabel';
|
|
15
|
-
|
|
16
|
-
export interface EdgyLabelAnchor {
|
|
17
|
-
field: EdgyLabelField;
|
|
18
|
-
text: string;
|
|
19
|
-
x: number;
|
|
20
|
-
y: number;
|
|
21
|
-
align: 'start' | 'end' | 'center';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* The three facet name anchors in reference coords, positioned fully outside
|
|
26
|
-
* their circle (validated mockup). Shared by the renderer (to draw them) and
|
|
27
|
-
* the label-layout hit testing (to edit them).
|
|
28
|
-
*/
|
|
29
|
-
export function facetLabelAnchors(
|
|
30
|
-
model: EdgyFacetsElementModel
|
|
31
|
-
): EdgyLabelAnchor[] {
|
|
32
|
-
return [
|
|
33
|
-
{
|
|
34
|
-
field: 'identityLabel',
|
|
35
|
-
text: model.identityLabel,
|
|
36
|
-
x: CIRCLE_A.x - VENN.R - 10,
|
|
37
|
-
y: CIRCLE_A.y - 28,
|
|
38
|
-
align: 'end',
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
field: 'architectureLabel',
|
|
42
|
-
text: model.architectureLabel,
|
|
43
|
-
x: CIRCLE_B.x + VENN.R + 10,
|
|
44
|
-
y: CIRCLE_B.y - 28,
|
|
45
|
-
align: 'start',
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
field: 'experienceLabel',
|
|
49
|
-
text: model.experienceLabel,
|
|
50
|
-
x: CIRCLE_C.x,
|
|
51
|
-
y: CIRCLE_C.y + VENN.R + 22,
|
|
52
|
-
align: 'center',
|
|
53
|
-
},
|
|
54
|
-
];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** A label's hit box in reference coords (axis-aligned, padded). */
|
|
58
|
-
export interface EdgyLabelHit {
|
|
59
|
-
field: EdgyLabelField;
|
|
60
|
-
minX: number;
|
|
61
|
-
minY: number;
|
|
62
|
-
maxX: number;
|
|
63
|
-
maxY: number;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const approxTextWidth = (text: string, fontSize: number) =>
|
|
67
|
-
Math.max(fontSize, text.length * fontSize * 0.6);
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Clickable boxes of every facet label, in reference coords. Derived from the
|
|
71
|
-
* SAME anchors the renderer uses so they track the drawn text. Boxes are padded
|
|
72
|
-
* so double-clicking is forgiving. Hidden when `showLabels` is false.
|
|
73
|
-
*/
|
|
74
|
-
export function getEdgyLabelHits(model: EdgyFacetsElementModel): EdgyLabelHit[] {
|
|
75
|
-
if (!model.showLabels) return [];
|
|
76
|
-
|
|
77
|
-
const pad = 6;
|
|
78
|
-
const fs = LABEL_FONT_SIZE;
|
|
79
|
-
return facetLabelAnchors(model).map(({ field, text, x, y, align }) => {
|
|
80
|
-
const tw = approxTextWidth(text, fs);
|
|
81
|
-
const minX = align === 'end' ? x - tw : align === 'center' ? x - tw / 2 : x;
|
|
82
|
-
const maxX = align === 'end' ? x : align === 'center' ? x + tw / 2 : x + tw;
|
|
83
|
-
return {
|
|
84
|
-
field,
|
|
85
|
-
minX: minX - pad,
|
|
86
|
-
maxX: maxX + pad,
|
|
87
|
-
minY: y - fs / 2 - pad,
|
|
88
|
-
maxY: y + fs / 2 + pad,
|
|
89
|
-
};
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** First label whose (padded) box contains the reference-space point, or null. */
|
|
94
|
-
export function hitTestEdgyLabel(
|
|
95
|
-
hits: EdgyLabelHit[],
|
|
96
|
-
rx: number,
|
|
97
|
-
ry: number
|
|
98
|
-
): EdgyLabelHit | null {
|
|
99
|
-
for (const hit of hits) {
|
|
100
|
-
if (rx >= hit.minX && rx <= hit.maxX && ry >= hit.minY && ry <= hit.maxY) {
|
|
101
|
-
return hit;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ElementRenderer,
|
|
3
|
-
ElementRendererExtension,
|
|
4
|
-
} from '@formicoidea/labre-core/blocks/surface';
|
|
5
|
-
import { shape as shapeRenderer } from '@formicoidea/labre-core/gfx/shape';
|
|
6
|
-
import { DefaultTheme, type EdgyNodeElementModel } from '@formicoidea/labre-core/model';
|
|
7
|
-
|
|
8
|
-
import { PERSON_GLYPH_PATHS, PERSON_GLYPH_VIEWBOX } from './consts';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Renderer for an EDGY base-element node. The shape body (rounded rect / rect /
|
|
12
|
-
* chevron polygon / ellipse) is drawn by REUSING the native shape renderer — so
|
|
13
|
-
* stroke width, colors, inner text and theme behave exactly like a native shape.
|
|
14
|
-
* Only `people` is decorated: an inscribed person glyph (the official
|
|
15
|
-
* `Icon-People` paths) drawn on top in the node's (editable) stroke color.
|
|
16
|
-
*/
|
|
17
|
-
export const edgyNode: ElementRenderer<EdgyNodeElementModel> = (
|
|
18
|
-
model,
|
|
19
|
-
ctx,
|
|
20
|
-
matrix,
|
|
21
|
-
renderer,
|
|
22
|
-
rc,
|
|
23
|
-
bound
|
|
24
|
-
) => {
|
|
25
|
-
const [, , w, h] = model.deserializedXYWH;
|
|
26
|
-
const cx = w / 2;
|
|
27
|
-
const cy = h / 2;
|
|
28
|
-
|
|
29
|
-
// Capture the element-local transform BEFORE the shape renderer mutates the
|
|
30
|
-
// matrix, so the glyph can be drawn in the same space afterwards.
|
|
31
|
-
const glyphMatrix = DOMMatrix.fromMatrix(matrix)
|
|
32
|
-
.translateSelf(cx, cy)
|
|
33
|
-
.rotateSelf(model.rotate)
|
|
34
|
-
.translateSelf(-cx, -cy);
|
|
35
|
-
|
|
36
|
-
// Native shape (fill / stroke / inner text / theme handled natively).
|
|
37
|
-
shapeRenderer(model, ctx, matrix, renderer, rc, bound);
|
|
38
|
-
|
|
39
|
-
if (model.kind !== 'people') return;
|
|
40
|
-
|
|
41
|
-
const color = renderer.getColorValue(
|
|
42
|
-
model.strokeColor,
|
|
43
|
-
DefaultTheme.shapeStrokeColor,
|
|
44
|
-
true
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
// ── Person glyph (Icon-People), scaled to the node and centred ──────
|
|
48
|
-
const target = Math.min(w, h) * 0.8;
|
|
49
|
-
const s = target / PERSON_GLYPH_VIEWBOX;
|
|
50
|
-
|
|
51
|
-
ctx.setTransform(glyphMatrix);
|
|
52
|
-
ctx.translate(cx, cy);
|
|
53
|
-
ctx.scale(s, s);
|
|
54
|
-
ctx.translate(-PERSON_GLYPH_VIEWBOX / 2, -PERSON_GLYPH_VIEWBOX / 2);
|
|
55
|
-
ctx.fillStyle = color;
|
|
56
|
-
for (const d of PERSON_GLYPH_PATHS) {
|
|
57
|
-
ctx.fill(new Path2D(d));
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export const EdgyNodeRendererExtension = ElementRendererExtension(
|
|
62
|
-
'edgyNode',
|
|
63
|
-
edgyNode
|
|
64
|
-
);
|