@formicoidea/labre-framework-edgy 0.23.0 → 0.23.2

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,145 +1,145 @@
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
- );
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
+ );
@@ -1,105 +1,105 @@
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
+ 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,56 +1,56 @@
1
- import type { EdgyNodeKind } from '@formicoidea/labre-core/model';
2
-
3
- /**
4
- * Visual constants for the four EDGY base-element nodes. Each node is a NATIVE
5
- * shape (ShapeElementModel-derived) so its stroke / fill / inner text are
6
- * editable via the shape toolbar; these are just the pre-formatted defaults at
7
- * creation.
8
- */
9
- export const NODE_FILL = '#ffffff';
10
- export const NODE_STROKE = '#262626';
11
- export const NODE_STROKE_WIDTH = 2;
12
-
13
- /** Outcome corner radius (absolute px — a lightly rounded rectangle). */
14
- export const OUTCOME_RADIUS = 6;
15
-
16
- /** Right-pointing chevron, as normalized polygon vertices (Activity). */
17
- export const ACTIVITY_VERTICES: number[][] = [
18
- [0, 0],
19
- [0.8, 0],
20
- [1, 0.5],
21
- [0.8, 1],
22
- [0, 1],
23
- ];
24
-
25
- /** Default node sizes (model units) per kind. */
26
- export const NODE_SIZE: Record<EdgyNodeKind, { w: number; h: number }> = {
27
- people: { w: 64, h: 64 },
28
- outcome: { w: 130, h: 80 },
29
- object: { w: 130, h: 80 },
30
- activity: { w: 140, h: 80 },
31
- };
32
-
33
- /** Inner-text + label font (matches the diagram labels). */
34
- export const INNER_FONT_SIZE = 20;
35
- export const LABEL_FONT_SIZE = 18;
36
- export const LABEL_GAP = 8;
37
-
38
- /** Default inner text / label per kind. */
39
- export const NODE_LABEL: Record<EdgyNodeKind, string> = {
40
- people: 'People',
41
- outcome: 'Outcome',
42
- object: 'Object',
43
- activity: 'Activity',
44
- };
45
-
46
- /**
47
- * Person glyph for `kind: 'people'`, taken from the official `Icon-People.svg`
48
- * (viewBox 0 0 32 32). Two filled outline rings (head + shoulders) — filling
49
- * them yields the line-art person. Drawn in the node stroke color, scaled to
50
- * the node and centred.
51
- */
52
- export const PERSON_GLYPH_VIEWBOX = 32;
53
- export const PERSON_GLYPH_PATHS = [
54
- 'm16,19c-3.308,0-6-2.692-6-6v-4c0-3.308,2.692-6,6-6s6,2.692,6,6v4c0,3.308-2.692,6-6,6Zm0-14c-2.206,0-4,1.794-4,4v4c0,2.206,1.794,4,4,4s4-1.794,4-4v-4c0-2.206-1.794-4-4-4Z',
55
- 'm29,30H3v-3.5c0-3.308,2.692-6,6-6h14c3.308,0,6,2.692,6,6v3.5Zm-24-2h22v-1.5c0-2.206-1.794-4-4-4h-14c-2.206,0-4,1.794-4,4v1.5Z',
56
- ];
1
+ import type { EdgyNodeKind } from '@formicoidea/labre-core/model';
2
+
3
+ /**
4
+ * Visual constants for the four EDGY base-element nodes. Each node is a NATIVE
5
+ * shape (ShapeElementModel-derived) so its stroke / fill / inner text are
6
+ * editable via the shape toolbar; these are just the pre-formatted defaults at
7
+ * creation.
8
+ */
9
+ export const NODE_FILL = '#ffffff';
10
+ export const NODE_STROKE = '#262626';
11
+ export const NODE_STROKE_WIDTH = 2;
12
+
13
+ /** Outcome corner radius (absolute px — a lightly rounded rectangle). */
14
+ export const OUTCOME_RADIUS = 6;
15
+
16
+ /** Right-pointing chevron, as normalized polygon vertices (Activity). */
17
+ export const ACTIVITY_VERTICES: number[][] = [
18
+ [0, 0],
19
+ [0.8, 0],
20
+ [1, 0.5],
21
+ [0.8, 1],
22
+ [0, 1],
23
+ ];
24
+
25
+ /** Default node sizes (model units) per kind. */
26
+ export const NODE_SIZE: Record<EdgyNodeKind, { w: number; h: number }> = {
27
+ people: { w: 64, h: 64 },
28
+ outcome: { w: 130, h: 80 },
29
+ object: { w: 130, h: 80 },
30
+ activity: { w: 140, h: 80 },
31
+ };
32
+
33
+ /** Inner-text + label font (matches the diagram labels). */
34
+ export const INNER_FONT_SIZE = 20;
35
+ export const LABEL_FONT_SIZE = 18;
36
+ export const LABEL_GAP = 8;
37
+
38
+ /** Default inner text / label per kind. */
39
+ export const NODE_LABEL: Record<EdgyNodeKind, string> = {
40
+ people: 'People',
41
+ outcome: 'Outcome',
42
+ object: 'Object',
43
+ activity: 'Activity',
44
+ };
45
+
46
+ /**
47
+ * Person glyph for `kind: 'people'`, taken from the official `Icon-People.svg`
48
+ * (viewBox 0 0 32 32). Two filled outline rings (head + shoulders) — filling
49
+ * them yields the line-art person. Drawn in the node stroke color, scaled to
50
+ * the node and centred.
51
+ */
52
+ export const PERSON_GLYPH_VIEWBOX = 32;
53
+ export const PERSON_GLYPH_PATHS = [
54
+ 'm16,19c-3.308,0-6-2.692-6-6v-4c0-3.308,2.692-6,6-6s6,2.692,6,6v4c0,3.308-2.692,6-6,6Zm0-14c-2.206,0-4,1.794-4,4v4c0,2.206,1.794,4,4,4s4-1.794,4-4v-4c0-2.206-1.794-4-4-4Z',
55
+ 'm29,30H3v-3.5c0-3.308,2.692-6,6-6h14c3.308,0,6,2.692,6,6v3.5Zm-24-2h22v-1.5c0-2.206-1.794-4-4-4h-14c-2.206,0-4,1.794-4,4v1.5Z',
56
+ ];