@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,242 +1,242 @@
1
- import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
2
- import { createGroupCommand } from '@formicoidea/labre-core/gfx/group';
3
- import { EmptyTool } from '@formicoidea/labre-core/gfx/pointer';
4
- import { FontFamily, ShapeStyle } from '@formicoidea/labre-core/model';
5
- import { TelemetryProvider } from '@formicoidea/labre-core/shared/services';
6
- import { EdgelessToolbarToolMixin } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
7
- import { Bound } from '@formicoidea/labre-core/global/gfx';
8
- import type { GfxController } from '@formicoidea/labre-core/std/gfx';
9
- import { css, html, LitElement } from 'lit';
10
-
11
- import { REF_H, REF_W } from '../consts';
12
- import {
13
- ACTIVITY_VERTICES,
14
- INNER_FONT_SIZE,
15
- LABEL_FONT_SIZE,
16
- LABEL_GAP,
17
- NODE_FILL,
18
- NODE_LABEL,
19
- NODE_SIZE,
20
- NODE_STROKE,
21
- NODE_STROKE_WIDTH,
22
- OUTCOME_RADIUS,
23
- } from '../node/consts';
24
- import {
25
- edgyActivityIcon,
26
- edgyFacetsIcon,
27
- edgyObjectIcon,
28
- edgyOutcomeIcon,
29
- edgyPeopleIcon,
30
- } from './icons';
31
-
32
- type Surface = NonNullable<GfxController['surface']>;
33
- type BoxKind = 'outcome' | 'object' | 'activity';
34
-
35
- /** Default facets-diagram size (REF aspect, scaled up so it reads on canvas). */
36
- const FACETS_SCALE = 1.5;
37
-
38
- /** Height of the native People free-text label. */
39
- const LABEL_H = LABEL_FONT_SIZE + 8;
40
-
41
- /**
42
- * The popover above the toolbar for the EDGY toolbox. Items create the facets
43
- * diagram (the on-click Venn) and the four base-element prefab shapes — all
44
- * native shapes so they stay editable.
45
- */
46
- export class EdgelessEdgyMenu extends EdgelessToolbarToolMixin(LitElement) {
47
- static override styles = css`
48
- :host {
49
- position: absolute;
50
- display: flex;
51
- z-index: -1;
52
- }
53
- .menu-content {
54
- display: flex;
55
- align-items: center;
56
- justify-content: center;
57
- }
58
- .button-group-container {
59
- display: flex;
60
- align-items: center;
61
- gap: 14px;
62
- fill: var(--affine-icon-color);
63
- }
64
- .button-group-container svg {
65
- width: 24px;
66
- height: 24px;
67
- }
68
- `;
69
-
70
- override type = EmptyTool;
71
-
72
- /** Create the Enterprise Design Facets diagram centred on the viewport. */
73
- private _createFacets() {
74
- const { gfx } = this;
75
- if (!gfx.surface) return;
76
-
77
- const width = REF_W * FACETS_SCALE;
78
- const height = REF_H * FACETS_SCALE;
79
- const { centerX, centerY } = gfx.viewport;
80
- const id = gfx.surface.addElement({
81
- type: 'edgy',
82
- xywh: new Bound(
83
- centerX - width / 2,
84
- centerY - height / 2,
85
- width,
86
- height
87
- ).serialize(),
88
- });
89
- this._track('facets');
90
- this._finish(id);
91
- }
92
-
93
- /** Add a native free-text label (Inter), used for the People node. */
94
- private _addLabel(surface: Surface, text: string, x: number, y: number) {
95
- return surface.addElement({
96
- type: 'text',
97
- text,
98
- fontFamily: FontFamily.Inter,
99
- fontSize: LABEL_FONT_SIZE,
100
- color: NODE_STROKE,
101
- textAlign: 'center',
102
- xywh: new Bound(x, y, 120, LABEL_H).serialize(),
103
- });
104
- }
105
-
106
- private _group(ids: string[]) {
107
- const [, result] = this.edgeless.std.command.exec(createGroupCommand, {
108
- elements: ids,
109
- });
110
- return result.groupId || ids[0];
111
- }
112
-
113
- /** Shared props for an EDGY node shape. */
114
- private _baseShapeProps(kind: BoxKind | 'people') {
115
- return {
116
- type: 'edgyNode' as const,
117
- kind,
118
- filled: true,
119
- fillColor: NODE_FILL,
120
- strokeColor: NODE_STROKE,
121
- shapeStyle: ShapeStyle.General,
122
- roughness: 0,
123
- };
124
- }
125
-
126
- /** Create a box base element (outcome / object / activity) with inner text. */
127
- private _createBox(kind: BoxKind) {
128
- const surface = this.gfx.surface;
129
- if (!surface) return;
130
-
131
- const { w, h } = NODE_SIZE[kind];
132
- const { centerX: cx, centerY: cy } = this.gfx.viewport;
133
- const shapeType = kind === 'activity' ? 'polygon' : 'rect';
134
-
135
- const id = surface.addElement({
136
- ...this._baseShapeProps(kind),
137
- shapeType,
138
- strokeWidth: NODE_STROKE_WIDTH,
139
- radius: kind === 'outcome' ? OUTCOME_RADIUS : 0,
140
- vertices: kind === 'activity' ? ACTIVITY_VERTICES : null,
141
- text: NODE_LABEL[kind],
142
- color: NODE_STROKE,
143
- fontFamily: FontFamily.Inter,
144
- fontSize: INNER_FONT_SIZE,
145
- textAlign: 'center',
146
- xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
147
- });
148
- this._track(`node:${kind}`);
149
- this._finish(id);
150
- }
151
-
152
- /**
153
- * Create the People base element: an (invisible) ellipse decorated with the
154
- * person glyph by the renderer, plus a native text label below, grouped.
155
- */
156
- private _createPeople() {
157
- const surface = this.gfx.surface;
158
- if (!surface) return;
159
-
160
- const { w, h } = NODE_SIZE.people;
161
- const { centerX: cx, centerY: cy } = this.gfx.viewport;
162
-
163
- const nodeId = surface.addElement({
164
- ...this._baseShapeProps('people'),
165
- shapeType: 'ellipse',
166
- // No visible outline — People is just the glyph; the ellipse is the bound.
167
- strokeWidth: 0,
168
- xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
169
- });
170
- const labelId = this._addLabel(
171
- surface,
172
- NODE_LABEL.people,
173
- cx - 60,
174
- cy + h / 2 + LABEL_GAP
175
- );
176
-
177
- this._track('node:people');
178
- this._finish(this._group([nodeId, labelId]));
179
- }
180
-
181
- private _finish(id: string) {
182
- const { gfx } = this;
183
- gfx.doc.captureSync();
184
- gfx.tool.setTool(DefaultTool);
185
- gfx.selection.set({ elements: [id], editing: false });
186
- // Keep the palette open (native sub-menu behaviour).
187
- }
188
-
189
- private _track(element: string) {
190
- this.edgeless.std.getOptional(TelemetryProvider)?.track(
191
- 'FrameworkElementAdded',
192
- {
193
- framework: 'edgy',
194
- element,
195
- page: 'whiteboard editor',
196
- segment: 'edgy toolbox',
197
- module: 'edgy menu',
198
- }
199
- );
200
- }
201
-
202
- override render() {
203
- return html`
204
- <edgeless-slide-menu>
205
- <div class="menu-content">
206
- <div class="button-group-container">
207
- <edgeless-tool-icon-button
208
- .tooltip=${'Enterprise Design facets'}
209
- @click=${this._createFacets}
210
- >
211
- ${edgyFacetsIcon}
212
- </edgeless-tool-icon-button>
213
- <edgeless-tool-icon-button
214
- .tooltip=${'People'}
215
- @click=${this._createPeople}
216
- >
217
- ${edgyPeopleIcon}
218
- </edgeless-tool-icon-button>
219
- <edgeless-tool-icon-button
220
- .tooltip=${'Outcome'}
221
- @click=${() => this._createBox('outcome')}
222
- >
223
- ${edgyOutcomeIcon}
224
- </edgeless-tool-icon-button>
225
- <edgeless-tool-icon-button
226
- .tooltip=${'Object'}
227
- @click=${() => this._createBox('object')}
228
- >
229
- ${edgyObjectIcon}
230
- </edgeless-tool-icon-button>
231
- <edgeless-tool-icon-button
232
- .tooltip=${'Activity'}
233
- @click=${() => this._createBox('activity')}
234
- >
235
- ${edgyActivityIcon}
236
- </edgeless-tool-icon-button>
237
- </div>
238
- </div>
239
- </edgeless-slide-menu>
240
- `;
241
- }
242
- }
1
+ import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
2
+ import { createGroupCommand } from '@formicoidea/labre-core/gfx/group';
3
+ import { EmptyTool } from '@formicoidea/labre-core/gfx/pointer';
4
+ import { FontFamily, ShapeStyle } from '@formicoidea/labre-core/model';
5
+ import { TelemetryProvider } from '@formicoidea/labre-core/shared/services';
6
+ import { EdgelessToolbarToolMixin } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
7
+ import { Bound } from '@formicoidea/labre-core/global/gfx';
8
+ import type { GfxController } from '@formicoidea/labre-core/std/gfx';
9
+ import { css, html, LitElement } from 'lit';
10
+
11
+ import { REF_H, REF_W } from '../consts';
12
+ import {
13
+ ACTIVITY_VERTICES,
14
+ INNER_FONT_SIZE,
15
+ LABEL_FONT_SIZE,
16
+ LABEL_GAP,
17
+ NODE_FILL,
18
+ NODE_LABEL,
19
+ NODE_SIZE,
20
+ NODE_STROKE,
21
+ NODE_STROKE_WIDTH,
22
+ OUTCOME_RADIUS,
23
+ } from '../node/consts';
24
+ import {
25
+ edgyActivityIcon,
26
+ edgyFacetsIcon,
27
+ edgyObjectIcon,
28
+ edgyOutcomeIcon,
29
+ edgyPeopleIcon,
30
+ } from './icons';
31
+
32
+ type Surface = NonNullable<GfxController['surface']>;
33
+ type BoxKind = 'outcome' | 'object' | 'activity';
34
+
35
+ /** Default facets-diagram size (REF aspect, scaled up so it reads on canvas). */
36
+ const FACETS_SCALE = 1.5;
37
+
38
+ /** Height of the native People free-text label. */
39
+ const LABEL_H = LABEL_FONT_SIZE + 8;
40
+
41
+ /**
42
+ * The popover above the toolbar for the EDGY toolbox. Items create the facets
43
+ * diagram (the on-click Venn) and the four base-element prefab shapes — all
44
+ * native shapes so they stay editable.
45
+ */
46
+ export class EdgelessEdgyMenu extends EdgelessToolbarToolMixin(LitElement) {
47
+ static override styles = css`
48
+ :host {
49
+ position: absolute;
50
+ display: flex;
51
+ z-index: -1;
52
+ }
53
+ .menu-content {
54
+ display: flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ }
58
+ .button-group-container {
59
+ display: flex;
60
+ align-items: center;
61
+ gap: 14px;
62
+ fill: var(--affine-icon-color);
63
+ }
64
+ .button-group-container svg {
65
+ width: 24px;
66
+ height: 24px;
67
+ }
68
+ `;
69
+
70
+ override type = EmptyTool;
71
+
72
+ /** Create the Enterprise Design Facets diagram centred on the viewport. */
73
+ private _createFacets() {
74
+ const { gfx } = this;
75
+ if (!gfx.surface) return;
76
+
77
+ const width = REF_W * FACETS_SCALE;
78
+ const height = REF_H * FACETS_SCALE;
79
+ const { centerX, centerY } = gfx.viewport;
80
+ const id = gfx.surface.addElement({
81
+ type: 'edgy',
82
+ xywh: new Bound(
83
+ centerX - width / 2,
84
+ centerY - height / 2,
85
+ width,
86
+ height
87
+ ).serialize(),
88
+ });
89
+ this._track('facets');
90
+ this._finish(id);
91
+ }
92
+
93
+ /** Add a native free-text label (Inter), used for the People node. */
94
+ private _addLabel(surface: Surface, text: string, x: number, y: number) {
95
+ return surface.addElement({
96
+ type: 'text',
97
+ text,
98
+ fontFamily: FontFamily.Inter,
99
+ fontSize: LABEL_FONT_SIZE,
100
+ color: NODE_STROKE,
101
+ textAlign: 'center',
102
+ xywh: new Bound(x, y, 120, LABEL_H).serialize(),
103
+ });
104
+ }
105
+
106
+ private _group(ids: string[]) {
107
+ const [, result] = this.edgeless.std.command.exec(createGroupCommand, {
108
+ elements: ids,
109
+ });
110
+ return result.groupId || ids[0];
111
+ }
112
+
113
+ /** Shared props for an EDGY node shape. */
114
+ private _baseShapeProps(kind: BoxKind | 'people') {
115
+ return {
116
+ type: 'edgyNode' as const,
117
+ kind,
118
+ filled: true,
119
+ fillColor: NODE_FILL,
120
+ strokeColor: NODE_STROKE,
121
+ shapeStyle: ShapeStyle.General,
122
+ roughness: 0,
123
+ };
124
+ }
125
+
126
+ /** Create a box base element (outcome / object / activity) with inner text. */
127
+ private _createBox(kind: BoxKind) {
128
+ const surface = this.gfx.surface;
129
+ if (!surface) return;
130
+
131
+ const { w, h } = NODE_SIZE[kind];
132
+ const { centerX: cx, centerY: cy } = this.gfx.viewport;
133
+ const shapeType = kind === 'activity' ? 'polygon' : 'rect';
134
+
135
+ const id = surface.addElement({
136
+ ...this._baseShapeProps(kind),
137
+ shapeType,
138
+ strokeWidth: NODE_STROKE_WIDTH,
139
+ radius: kind === 'outcome' ? OUTCOME_RADIUS : 0,
140
+ vertices: kind === 'activity' ? ACTIVITY_VERTICES : null,
141
+ text: NODE_LABEL[kind],
142
+ color: NODE_STROKE,
143
+ fontFamily: FontFamily.Inter,
144
+ fontSize: INNER_FONT_SIZE,
145
+ textAlign: 'center',
146
+ xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
147
+ });
148
+ this._track(`node:${kind}`);
149
+ this._finish(id);
150
+ }
151
+
152
+ /**
153
+ * Create the People base element: an (invisible) ellipse decorated with the
154
+ * person glyph by the renderer, plus a native text label below, grouped.
155
+ */
156
+ private _createPeople() {
157
+ const surface = this.gfx.surface;
158
+ if (!surface) return;
159
+
160
+ const { w, h } = NODE_SIZE.people;
161
+ const { centerX: cx, centerY: cy } = this.gfx.viewport;
162
+
163
+ const nodeId = surface.addElement({
164
+ ...this._baseShapeProps('people'),
165
+ shapeType: 'ellipse',
166
+ // No visible outline — People is just the glyph; the ellipse is the bound.
167
+ strokeWidth: 0,
168
+ xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
169
+ });
170
+ const labelId = this._addLabel(
171
+ surface,
172
+ NODE_LABEL.people,
173
+ cx - 60,
174
+ cy + h / 2 + LABEL_GAP
175
+ );
176
+
177
+ this._track('node:people');
178
+ this._finish(this._group([nodeId, labelId]));
179
+ }
180
+
181
+ private _finish(id: string) {
182
+ const { gfx } = this;
183
+ gfx.doc.captureSync();
184
+ gfx.tool.setTool(DefaultTool);
185
+ gfx.selection.set({ elements: [id], editing: false });
186
+ // Keep the palette open (native sub-menu behaviour).
187
+ }
188
+
189
+ private _track(element: string) {
190
+ this.edgeless.std.getOptional(TelemetryProvider)?.track(
191
+ 'FrameworkElementAdded',
192
+ {
193
+ framework: 'edgy',
194
+ element,
195
+ page: 'whiteboard editor',
196
+ segment: 'edgy toolbox',
197
+ module: 'edgy menu',
198
+ }
199
+ );
200
+ }
201
+
202
+ override render() {
203
+ return html`
204
+ <edgeless-slide-menu>
205
+ <div class="menu-content">
206
+ <div class="button-group-container">
207
+ <edgeless-tool-icon-button
208
+ .tooltip=${'Enterprise Design facets'}
209
+ @click=${this._createFacets}
210
+ >
211
+ ${edgyFacetsIcon}
212
+ </edgeless-tool-icon-button>
213
+ <edgeless-tool-icon-button
214
+ .tooltip=${'People'}
215
+ @click=${this._createPeople}
216
+ >
217
+ ${edgyPeopleIcon}
218
+ </edgeless-tool-icon-button>
219
+ <edgeless-tool-icon-button
220
+ .tooltip=${'Outcome'}
221
+ @click=${() => this._createBox('outcome')}
222
+ >
223
+ ${edgyOutcomeIcon}
224
+ </edgeless-tool-icon-button>
225
+ <edgeless-tool-icon-button
226
+ .tooltip=${'Object'}
227
+ @click=${() => this._createBox('object')}
228
+ >
229
+ ${edgyObjectIcon}
230
+ </edgeless-tool-icon-button>
231
+ <edgeless-tool-icon-button
232
+ .tooltip=${'Activity'}
233
+ @click=${() => this._createBox('activity')}
234
+ >
235
+ ${edgyActivityIcon}
236
+ </edgeless-tool-icon-button>
237
+ </div>
238
+ </div>
239
+ </edgeless-slide-menu>
240
+ `;
241
+ }
242
+ }