@formicoidea/labre-framework-edgy 0.23.1 → 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.
Files changed (48) hide show
  1. package/package.json +6 -15
  2. package/{dist/consts.js → src/consts.ts} +50 -46
  3. package/{dist/descriptor.js → src/descriptor.ts} +1 -1
  4. package/src/effects.ts +14 -0
  5. package/src/element-renderer.ts +208 -0
  6. package/src/element-view.ts +145 -0
  7. package/src/index.ts +1 -0
  8. package/src/label-layout.ts +105 -0
  9. package/{dist/node/consts.js → src/node/consts.ts} +56 -49
  10. package/src/node/node-renderer.ts +64 -0
  11. package/{dist/node/node-view.js → src/node/node-view.ts} +33 -27
  12. package/src/templates/index.ts +254 -0
  13. package/src/toolbar/config.ts +96 -0
  14. package/src/toolbar/edgy-menu.ts +242 -0
  15. package/{dist/toolbar/edgy-senior-button.js → src/toolbar/edgy-senior-button.ts} +102 -97
  16. package/{dist/toolbar/icons.js → src/toolbar/icons.ts} +25 -20
  17. package/src/toolbar/node-config.ts +202 -0
  18. package/{dist/toolbar/senior-tool.js → src/toolbar/senior-tool.ts} +11 -11
  19. package/src/view.ts +39 -0
  20. package/dist/consts.d.ts +0 -47
  21. package/dist/descriptor.d.ts +0 -7
  22. package/dist/effects.d.ts +0 -10
  23. package/dist/effects.js +0 -7
  24. package/dist/element-renderer.d.ts +0 -17
  25. package/dist/element-renderer.js +0 -177
  26. package/dist/element-view.d.ts +0 -19
  27. package/dist/element-view.js +0 -123
  28. package/dist/index.d.ts +0 -2
  29. package/dist/index.js +0 -2
  30. package/dist/label-layout.d.ts +0 -46
  31. package/dist/label-layout.js +0 -69
  32. package/dist/node/consts.d.ts +0 -34
  33. package/dist/node/node-renderer.d.ts +0 -14
  34. package/dist/node/node-renderer.js +0 -40
  35. package/dist/node/node-view.d.ts +0 -16
  36. package/dist/templates/index.d.ts +0 -3
  37. package/dist/templates/index.js +0 -210
  38. package/dist/toolbar/config.d.ts +0 -20
  39. package/dist/toolbar/config.js +0 -71
  40. package/dist/toolbar/edgy-menu.d.ts +0 -31
  41. package/dist/toolbar/edgy-menu.js +0 -195
  42. package/dist/toolbar/edgy-senior-button.d.ts +0 -16
  43. package/dist/toolbar/icons.d.ts +0 -13
  44. package/dist/toolbar/node-config.d.ts +0 -2
  45. package/dist/toolbar/node-config.js +0 -162
  46. package/dist/toolbar/senior-tool.d.ts +0 -2
  47. package/dist/view.d.ts +0 -7
  48. package/dist/view.js +0 -36
@@ -0,0 +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,97 +1,102 @@
1
- import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
2
- import { EmptyTool } from '@formicoidea/labre-core/gfx/pointer';
3
- import { EdgelessToolbarToolMixin } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
4
- import { SignalWatcher } from '@formicoidea/labre-core/global/lit';
5
- import { css, html, LitElement } from 'lit';
6
- import { edgyToolbarIcon } from './icons';
7
- /**
8
- * Main toolbar button (colored facets glyph) that opens the EDGY toolbox
9
- * sub-menu above the toolbar. Mirrors the Wardley senior button.
10
- */
11
- export class EdgelessEdgySeniorButton extends EdgelessToolbarToolMixin(SignalWatcher(LitElement)) {
12
- constructor() {
13
- super(...arguments);
14
- this.enableActiveBackground = true;
15
- this.type = EmptyTool;
16
- }
17
- static { this.styles = css `
18
- :host,
19
- .edgy-button {
20
- display: block;
21
- width: 100%;
22
- height: 100%;
23
- }
24
- :host {
25
- position: relative;
26
- }
27
- .edgy-root {
28
- width: 100%;
29
- height: 64px;
30
- position: relative;
31
- overflow: hidden;
32
- cursor: pointer;
33
- display: flex;
34
- align-items: flex-end;
35
- justify-content: center;
36
- }
37
- .edgy-card {
38
- --y: -4px;
39
- --s: 1;
40
- position: absolute;
41
- bottom: 0;
42
- width: 54px;
43
- height: 54px;
44
- transform: translateY(var(--y)) scale(var(--s));
45
- translate: var(--active-x, 0) var(--active-y, 0);
46
- rotate: var(--active-r, -2deg);
47
- scale: var(--active-s, 1);
48
- transition: transform 0.3s ease, translate 0.3s ease, rotate 0.3s ease,
49
- scale 0.3s ease;
50
- }
51
- .edgy-card svg {
52
- display: block;
53
- width: 100%;
54
- height: 100%;
55
- }
56
- .edgy-root:hover .edgy-card {
57
- --y: -10px;
58
- --s: 1.07;
59
- }
60
- `; }
61
- _toggleMenu() {
62
- if (this.popper) {
63
- this.popper.dispose();
64
- this.popper = null;
65
- return;
66
- }
67
- this.setEdgelessTool(DefaultTool);
68
- const menu = this.createPopper('edgeless-edgy-menu', this);
69
- menu.element.edgeless = this.edgeless;
70
- const el = menu.element;
71
- const wrap = el.parentElement;
72
- if (wrap) {
73
- wrap.style.overflow = 'visible';
74
- wrap.style.justifyContent = 'flex-end';
75
- }
76
- Object.assign(el.style, {
77
- position: 'static',
78
- width: 'max-content',
79
- maxWidth: 'calc(100vw - 16px)',
80
- marginLeft: '0',
81
- });
82
- }
83
- render() {
84
- return html `<edgeless-toolbar-button
85
- class="edgy-button"
86
- .tooltip=${this.popper ? '' : 'EDGY'}
87
- .tooltipOffset=${4}
88
- .active=${!!this.popper}
89
- @click=${this._toggleMenu}
90
- >
91
- <div class="edgy-root">
92
- <div class="edgy-card">${edgyToolbarIcon}</div>
93
- </div>
94
- </edgeless-toolbar-button>`;
95
- }
96
- }
97
- //# sourceMappingURL=edgy-senior-button.js.map
1
+ import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
2
+ import { EmptyTool } from '@formicoidea/labre-core/gfx/pointer';
3
+ import { EdgelessToolbarToolMixin } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
4
+ import { SignalWatcher } from '@formicoidea/labre-core/global/lit';
5
+ import { css, html, LitElement } from 'lit';
6
+
7
+ import { edgyToolbarIcon } from './icons';
8
+
9
+ /**
10
+ * Main toolbar button (colored facets glyph) that opens the EDGY toolbox
11
+ * sub-menu above the toolbar. Mirrors the Wardley senior button.
12
+ */
13
+ export class EdgelessEdgySeniorButton extends EdgelessToolbarToolMixin(
14
+ SignalWatcher(LitElement)
15
+ ) {
16
+ static override styles = css`
17
+ :host,
18
+ .edgy-button {
19
+ display: block;
20
+ width: 100%;
21
+ height: 100%;
22
+ }
23
+ :host {
24
+ position: relative;
25
+ }
26
+ .edgy-root {
27
+ width: 100%;
28
+ height: 64px;
29
+ position: relative;
30
+ overflow: hidden;
31
+ cursor: pointer;
32
+ display: flex;
33
+ align-items: flex-end;
34
+ justify-content: center;
35
+ }
36
+ .edgy-card {
37
+ --y: -4px;
38
+ --s: 1;
39
+ position: absolute;
40
+ bottom: 0;
41
+ width: 54px;
42
+ height: 54px;
43
+ transform: translateY(var(--y)) scale(var(--s));
44
+ translate: var(--active-x, 0) var(--active-y, 0);
45
+ rotate: var(--active-r, -2deg);
46
+ scale: var(--active-s, 1);
47
+ transition: transform 0.3s ease, translate 0.3s ease, rotate 0.3s ease,
48
+ scale 0.3s ease;
49
+ }
50
+ .edgy-card svg {
51
+ display: block;
52
+ width: 100%;
53
+ height: 100%;
54
+ }
55
+ .edgy-root:hover .edgy-card {
56
+ --y: -10px;
57
+ --s: 1.07;
58
+ }
59
+ `;
60
+
61
+ override enableActiveBackground = true;
62
+
63
+ override type = EmptyTool;
64
+
65
+ private _toggleMenu() {
66
+ if (this.popper) {
67
+ this.popper.dispose();
68
+ this.popper = null;
69
+ return;
70
+ }
71
+ this.setEdgelessTool(DefaultTool);
72
+ const menu = this.createPopper('edgeless-edgy-menu', this);
73
+ menu.element.edgeless = this.edgeless;
74
+
75
+ const el = menu.element as HTMLElement;
76
+ const wrap = el.parentElement;
77
+ if (wrap) {
78
+ wrap.style.overflow = 'visible';
79
+ wrap.style.justifyContent = 'flex-end';
80
+ }
81
+ Object.assign(el.style, {
82
+ position: 'static',
83
+ width: 'max-content',
84
+ maxWidth: 'calc(100vw - 16px)',
85
+ marginLeft: '0',
86
+ });
87
+ }
88
+
89
+ override render() {
90
+ return html`<edgeless-toolbar-button
91
+ class="edgy-button"
92
+ .tooltip=${this.popper ? '' : 'EDGY'}
93
+ .tooltipOffset=${4}
94
+ .active=${!!this.popper}
95
+ @click=${this._toggleMenu}
96
+ >
97
+ <div class="edgy-root">
98
+ <div class="edgy-card">${edgyToolbarIcon}</div>
99
+ </div>
100
+ </edgeless-toolbar-button>`;
101
+ }
102
+ }
@@ -1,33 +1,38 @@
1
- import { svg } from 'lit';
2
- /** Colored EDGY facets glyph for the main toolbar button (3 overlapping circles). */
3
- export const edgyToolbarIcon = svg `<svg width="100%" height="100%" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
1
+ import { svg } from 'lit';
2
+
3
+ /** Colored EDGY facets glyph for the main toolbar button (3 overlapping circles). */
4
+ export const edgyToolbarIcon = svg`<svg width="100%" height="100%" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
4
5
  <g opacity="0.95">
5
6
  <circle cx="22" cy="24" r="13" fill="#00ea4e"/>
6
7
  <circle cx="34" cy="24" r="13" fill="#034cee"/>
7
8
  <circle cx="28" cy="34" r="13" fill="#ff0056"/>
8
9
  </g>
9
- </svg>`;
10
- /** Menu icon — the facets diagram (colored mini Venn). */
11
- export const edgyFacetsIcon = svg `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
10
+ </svg>`;
11
+
12
+ /** Menu icon the facets diagram (colored mini Venn). */
13
+ export const edgyFacetsIcon = svg`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
12
14
  <circle cx="9.5" cy="10" r="6" fill="#00ea4e" opacity="0.92"/>
13
15
  <circle cx="14.5" cy="10" r="6" fill="#034cee" opacity="0.92"/>
14
16
  <circle cx="12" cy="14.5" r="6" fill="#ff0056" opacity="0.92"/>
15
- </svg>`;
16
- /** People — person glyph (official Icon-People), uses currentColor. */
17
- export const edgyPeopleIcon = svg `<svg width="24" height="24" viewBox="0 0 32 32" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
17
+ </svg>`;
18
+
19
+ /** People person glyph (official Icon-People), uses currentColor. */
20
+ export const edgyPeopleIcon = svg`<svg width="24" height="24" viewBox="0 0 32 32" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
18
21
  <path d="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"/>
19
22
  <path d="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"/>
20
- </svg>`;
21
- /** Outcome — lightly rounded rectangle. */
22
- export const edgyOutcomeIcon = svg `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
23
+ </svg>`;
24
+
25
+ /** Outcome lightly rounded rectangle. */
26
+ export const edgyOutcomeIcon = svg`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
23
27
  <rect x="3.5" y="6.5" width="17" height="11" rx="2" stroke="currentColor" stroke-width="1.6"/>
24
- </svg>`;
25
- /** Object — plain rectangle. */
26
- export const edgyObjectIcon = svg `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
28
+ </svg>`;
29
+
30
+ /** Object plain rectangle. */
31
+ export const edgyObjectIcon = svg`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
27
32
  <rect x="3.5" y="6.5" width="17" height="11" stroke="currentColor" stroke-width="1.6"/>
28
- </svg>`;
29
- /** Activity — right-pointing chevron. */
30
- export const edgyActivityIcon = svg `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
33
+ </svg>`;
34
+
35
+ /** Activity right-pointing chevron. */
36
+ export const edgyActivityIcon = svg`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
31
37
  <path d="M3.5 6.5 H15 L20.5 12 L15 17.5 H3.5 Z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/>
32
- </svg>`;
33
- //# sourceMappingURL=icons.js.map
38
+ </svg>`;