@formicoidea/labre-core 0.34.2 → 0.36.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.
- package/dist/_pkgs/affine-block-surface/extensions/editor-anchored-panel.d.ts +18 -2
- package/dist/_pkgs/affine-block-surface/extensions/editor-anchored-panel.js +141 -8
- package/dist/_pkgs/affine-block-surface/extensions/map-quality-widget.js +16 -22
- package/dist/_pkgs/affine-block-surface/extensions/reading-widget.d.ts +18 -0
- package/dist/_pkgs/affine-block-surface/extensions/reading-widget.js +35 -12
- package/dist/_pkgs/affine-block-surface/extensions/reading.d.ts +8 -0
- package/dist/_pkgs/affine-block-surface/extensions/reading.js +17 -6
- package/dist/_pkgs/affine-block-surface/extensions/tags-toolbar.js +17 -2
- package/dist/_pkgs/affine-block-surface/extensions/validation-toolbar.js +9 -2
- package/dist/_pkgs/affine-block-surface/framework-background/def.d.ts +14 -11
- package/dist/_pkgs/affine-block-surface/framework-background/renderer.js +14 -7
- package/dist/_pkgs/affine-components/highlight-dropdown-menu/highlight-duotone-icon.js +8 -8
- package/dist/_pkgs/affine-components/highlight-dropdown-menu/text-duotone-icon.js +11 -11
- package/dist/_pkgs/affine-components/icons/text.js +426 -426
- package/dist/_pkgs/affine-components/toolbar/menu-button.js +40 -0
- package/dist/_pkgs/affine-gfx-connector/direction/direction-reveal.d.ts +2 -2
- package/dist/_pkgs/affine-gfx-connector/direction/direction-reveal.js +1 -1
- package/dist/_pkgs/affine-gfx-connector/direction/direction-widget.d.ts +1 -1
- package/dist/_pkgs/affine-gfx-connector/direction/direction-widget.js +10 -1
- package/dist/_pkgs/affine-gfx-shape/element-view.d.ts +42 -1
- package/dist/_pkgs/affine-gfx-shape/element-view.js +54 -0
- package/dist/_pkgs/affine-gfx-shape/toolbar/index.d.ts +1 -0
- package/dist/_pkgs/affine-gfx-shape/toolbar/index.js +1 -0
- package/dist/_pkgs/affine-gfx-shape/toolbar/palette-color-action.d.ts +25 -0
- package/dist/_pkgs/affine-gfx-shape/toolbar/palette-color-action.js +123 -0
- package/dist/_pkgs/affine-model/elements/c4/board.d.ts +49 -0
- package/dist/_pkgs/affine-model/elements/c4/board.js +68 -0
- package/dist/_pkgs/affine-model/elements/wardley/node.d.ts +21 -6
- package/dist/_pkgs/affine-model/elements/wardley/node.js +20 -5
- package/dist/_pkgs/affine-shared/services/telemetry-service/block-lifecycle-watcher.d.ts +16 -1
- package/dist/_pkgs/affine-shared/services/telemetry-service/block-lifecycle-watcher.js +27 -2
- package/dist/_pkgs/affine-widget-edgeless-toolbar/menu/slide-menu.js +1 -1
- package/dist/_pkgs/affine-widget-keyboard-toolbar/icons.js +98 -98
- package/dist/_pkgs/data-view/core/group-by/renderer/boolean-group.js +11 -11
- package/dist/_pkgs/data-view/core/utils/menu-title.js +17 -17
- package/dist/_pkgs/data-view/view-presets/table/pc-virtual/group/top/group-title.js +18 -18
- package/dist/_pkgs/std/extension/command-registry.d.ts +15 -8
- package/dist/_pkgs/std/gfx/index.d.ts +1 -1
- package/dist/_pkgs/std/gfx/index.js +1 -1
- package/dist/_pkgs/std/gfx/interactivity/gfx-view-event-handler.d.ts +57 -0
- package/dist/_pkgs/std/gfx/interactivity/gfx-view-event-handler.js +80 -25
- package/dist/_pkgs/std/gfx/model/surface/pivot.d.ts +55 -1
- package/dist/_pkgs/std/gfx/model/surface/pivot.js +87 -0
- package/dist/_pkgs/std/gfx/model/surface/role.d.ts +12 -1
- package/dist/extensions/command-telemetry.js +4 -0
- package/package.json +1 -1
|
@@ -247,6 +247,46 @@ export class EditorMenuAction extends LitElement {
|
|
|
247
247
|
pointer-events: none;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
/*
|
|
251
|
+
* ...unless the row is a TOGGLE rather than one of N. The rule above reads
|
|
252
|
+
* a selected row as inert, which is right for a single choice — re-picking
|
|
253
|
+
* the option already in force is a no-op, so swallowing the click costs
|
|
254
|
+
* nothing. A toggle is the opposite: re-clicking the value in force is the
|
|
255
|
+
* ONLY way to un-pick it (the qualification dropdown's tag values), and an
|
|
256
|
+
* inert row leaves the user with a tag that cannot be removed. Such a row
|
|
257
|
+
* opts the pointer back in through data-toggle.
|
|
258
|
+
*/
|
|
259
|
+
:host([data-selected][data-toggle]) {
|
|
260
|
+
pointer-events: auto;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/*
|
|
264
|
+
* An OPTION row: one of N choices, the one in force carrying
|
|
265
|
+
* data-selected. Opt-in through data-option, because a plain command row is
|
|
266
|
+
* not a choice and keeps its left-packed icon and its grey highlight.
|
|
267
|
+
*
|
|
268
|
+
* The shape invents nothing. It is what edgeless-tool-icon-button draws
|
|
269
|
+
* with justify='space-between' and its default active-mode-color -- the
|
|
270
|
+
* Regular/Semibold menu -- and what size-dropdown-menu already spells out
|
|
271
|
+
* for its own check list: label left, tick right and ONLY when selected,
|
|
272
|
+
* the selected row in the primary colour. The colour IS the affordance, so
|
|
273
|
+
* the background highlight goes, and no gutter is held open on the left for
|
|
274
|
+
* a tick that is not there.
|
|
275
|
+
*/
|
|
276
|
+
:host([data-option]) {
|
|
277
|
+
justify-content: space-between;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
:host([data-option][data-selected]) {
|
|
281
|
+
color: var(--affine-primary-color);
|
|
282
|
+
background-color: unset;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* The tick takes the row's colour rather than the neutral icon one. */
|
|
286
|
+
:host([data-option][data-selected]) ::slotted(svg) {
|
|
287
|
+
color: inherit;
|
|
288
|
+
}
|
|
289
|
+
|
|
250
290
|
:host(:hover.delete),
|
|
251
291
|
:host(:hover.delete) ::slotted(svg) {
|
|
252
292
|
background-color: var(--affine-background-error-color);
|
|
@@ -6,7 +6,7 @@ import { type TypedEdge } from './typed-edge.js';
|
|
|
6
6
|
* **M2 of `docs/adr/0010` — "show it".**
|
|
7
7
|
*
|
|
8
8
|
* A typed edge reveals its orientation on hover and on selection: ONE mark, the
|
|
9
|
-
* role's verb laid ALONG the link — `
|
|
9
|
+
* role's verb laid ALONG the link — `needs` — in a box whose far end is a
|
|
10
10
|
* point aimed at the target. At rest the board keeps the canonical arrowless
|
|
11
11
|
* look, because on a Wardley map a permanent head already means something else
|
|
12
12
|
* (evolution movement), and two meanings on one glyph make both unreadable.
|
|
@@ -66,7 +66,7 @@ export interface EdgeLabelAnchor {
|
|
|
66
66
|
* this flag: the point of the box must face the TARGET, so on a link running
|
|
67
67
|
* right-to-left — where the turn put the box's right end at the SOURCE — the
|
|
68
68
|
* point moves to the box's left end. The words themselves never reverse; a
|
|
69
|
-
* mirrored `
|
|
69
|
+
* mirrored `needs` would be a smudge, not a rotation.
|
|
70
70
|
*/
|
|
71
71
|
flipped: boolean;
|
|
72
72
|
}
|
|
@@ -5,7 +5,7 @@ import { asTypedEdge, edgeIsBound, roleVocabularies, } from './typed-edge.js';
|
|
|
5
5
|
* **M2 of `docs/adr/0010` — "show it".**
|
|
6
6
|
*
|
|
7
7
|
* A typed edge reveals its orientation on hover and on selection: ONE mark, the
|
|
8
|
-
* role's verb laid ALONG the link — `
|
|
8
|
+
* role's verb laid ALONG the link — `needs` — in a box whose far end is a
|
|
9
9
|
* point aimed at the target. At rest the board keeps the canonical arrowless
|
|
10
10
|
* look, because on a Wardley map a permanent head already means something else
|
|
11
11
|
* (evolution movement), and two meanings on one glyph make both unreadable.
|
|
@@ -12,7 +12,7 @@ export declare const EDGE_DIRECTION_WIDGET = "affine-edge-direction-widget";
|
|
|
12
12
|
* is an unreadable one):
|
|
13
13
|
*
|
|
14
14
|
* - **M2** — while a typed edge is hovered or selected, the role's VERB laid
|
|
15
|
-
* along the link: `
|
|
15
|
+
* along the link: `needs`, in a box that ends in a point aimed at the
|
|
16
16
|
* provider. Since the PO acceptance of 02/08/2026 this is the only mark the
|
|
17
17
|
* reveal draws; the canvas chevron it used to sit on top of is folded into
|
|
18
18
|
* that point (see `direction-reveal.ts`). Since the second pass of the same
|
|
@@ -86,7 +86,7 @@ const EDGE_DIRECTION_Z_INDEX = 0;
|
|
|
86
86
|
* is an unreadable one):
|
|
87
87
|
*
|
|
88
88
|
* - **M2** — while a typed edge is hovered or selected, the role's VERB laid
|
|
89
|
-
* along the link: `
|
|
89
|
+
* along the link: `needs`, in a box that ends in a point aimed at the
|
|
90
90
|
* provider. Since the PO acceptance of 02/08/2026 this is the only mark the
|
|
91
91
|
* reveal draws; the canvas chevron it used to sit on top of is folded into
|
|
92
92
|
* that point (see `direction-reveal.ts`). Since the second pass of the same
|
|
@@ -139,6 +139,11 @@ let EdgeDirectionWidget = (() => {
|
|
|
139
139
|
* point has no border to speak of, and a hairline diagonal at 40 % zoom is
|
|
140
140
|
* a smudge. Solid house blue with white text reads in both themes and
|
|
141
141
|
* cannot be mistaken for the map's own ink.
|
|
142
|
+
*
|
|
143
|
+
* The background here is the DEFAULT, which every role that declares no
|
|
144
|
+
* chipColor keeps: render() overrides it inline only for a role that asks
|
|
145
|
+
* for one, so a colour decision taken about one relation never repaints the
|
|
146
|
+
* others.
|
|
142
147
|
*/
|
|
143
148
|
.edge-direction-label {
|
|
144
149
|
--edge-direction-point: 9px;
|
|
@@ -325,6 +330,9 @@ let EdgeDirectionWidget = (() => {
|
|
|
325
330
|
const verb = this._verbFor(edge);
|
|
326
331
|
if (!verb)
|
|
327
332
|
return nothing;
|
|
333
|
+
// Declared by the ROLE, defaulted by the stylesheet: only a relation that
|
|
334
|
+
// asked for its own colour gets an inline background at all.
|
|
335
|
+
const chipColor = edge.direction?.chipColor;
|
|
328
336
|
const { viewport } = this.gfx;
|
|
329
337
|
const [x, y] = viewport.toViewCoord(anchor.at[0], anchor.at[1]);
|
|
330
338
|
// Two decimals: below what a reader can see on a 12 px box, and it keeps
|
|
@@ -341,6 +349,7 @@ let EdgeDirectionWidget = (() => {
|
|
|
341
349
|
style=${styleMap({
|
|
342
350
|
left: `${x}px`,
|
|
343
351
|
top: `${y}px`,
|
|
352
|
+
...(chipColor ? { background: chipColor } : {}),
|
|
344
353
|
// Read right to left, as CSS composes it: scale by the zoom and turn
|
|
345
354
|
// onto the line, both about the box's centre, and only THEN slide that
|
|
346
355
|
// centre onto the anchor. The lengths in the class are model units, so
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import { ShapeElementModel } from '../affine-model/index.js';
|
|
2
2
|
import { GfxElementModelView } from '../std/gfx/index.js';
|
|
3
3
|
import { PolygonVertexEditingOverlay } from './overlay/polygon-vertex-editing-overlay.js';
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* The view every shape gets, and the base a framework NODE view extends when
|
|
6
|
+
* its model is a {@link ShapeElementModel} subclass.
|
|
7
|
+
*
|
|
8
|
+
* Generic over the model, with the plain shape as the default, so
|
|
9
|
+
* `WardleyNodeView extends ShapeElementView<WardleyNodeElementModel>` reads its
|
|
10
|
+
* own `kind` off `this.model` without a cast. Every existing call site names no
|
|
11
|
+
* type argument and is unchanged.
|
|
12
|
+
*
|
|
13
|
+
* A framework node that extends this inherits the polygon VERTEX EDITOR, which
|
|
14
|
+
* is the whole reason to: {@link enterVertexEditingMode} is what the toolbar's
|
|
15
|
+
* "Edit vertices" action calls, and the action checks `view instanceof
|
|
16
|
+
* ShapeElementView` — a node view outside this hierarchy makes that button
|
|
17
|
+
* silently do nothing. What such a subclass usually does NOT want is the other
|
|
18
|
+
* thing wired here, the double-click-to-edit; {@link editsTextOnDblClick} is
|
|
19
|
+
* the one line it has to say so.
|
|
20
|
+
*/
|
|
21
|
+
export declare class ShapeElementView<T extends ShapeElementModel = ShapeElementModel> extends GfxElementModelView<T> {
|
|
5
22
|
static type: string;
|
|
6
23
|
/** Overlay for polygon vertex editing (only active for polygon shapes). */
|
|
7
24
|
private _vertexEditingOverlay;
|
|
@@ -16,6 +33,17 @@ export declare class ShapeElementView extends GfxElementModelView<ShapeElementMo
|
|
|
16
33
|
* visually correct on every drag frame.
|
|
17
34
|
*/
|
|
18
35
|
private _syncConnectorPaths;
|
|
36
|
+
/**
|
|
37
|
+
* Whether a double-click on THIS element opens its own inner-text editor.
|
|
38
|
+
*
|
|
39
|
+
* True for every plain shape, which is what this editor has always done: a
|
|
40
|
+
* shape is a box you write in. It is a HOOK rather than a constant because a
|
|
41
|
+
* framework node is often not one — its name may be a text element grouped
|
|
42
|
+
* beside it, or the element may be a glyph's own wiring that must carry no
|
|
43
|
+
* words at all — and a subclass that overrode `onCreated` to dodge this one
|
|
44
|
+
* wiring would lose the vertex editor set up beside it.
|
|
45
|
+
*/
|
|
46
|
+
protected editsTextOnDblClick(): boolean;
|
|
19
47
|
private _initDblClickToEdit;
|
|
20
48
|
/**
|
|
21
49
|
* Whether a vertex is currently being dragged.
|
|
@@ -61,6 +89,19 @@ export declare class ShapeElementView extends GfxElementModelView<ShapeElementMo
|
|
|
61
89
|
* The overlay is shown when a polygon is selected and removed when
|
|
62
90
|
* deselected or when a non-polygon shape is selected.
|
|
63
91
|
*/
|
|
92
|
+
/**
|
|
93
|
+
* Whether THIS element's corners may be moved by hand.
|
|
94
|
+
*
|
|
95
|
+
* True for every plain polygon. The companion of {@link editsTextOnDblClick},
|
|
96
|
+
* and a hook for the same reason: a framework polygon's outline is often the
|
|
97
|
+
* NOTATION rather than a drawing — a Wardley accelerator points right and a
|
|
98
|
+
* decelerator points left, and that direction is the whole of what separates
|
|
99
|
+
* them — so hovering one must not offer a handle that can only turn a
|
|
100
|
+
* statement into a blob. Declining here skips the selection overlay, the
|
|
101
|
+
* pointer wiring and {@link enterVertexEditingMode} together, which is the
|
|
102
|
+
* point: a half-wired overlay is worse than none.
|
|
103
|
+
*/
|
|
104
|
+
protected editsVerticesOnSelection(): boolean;
|
|
64
105
|
private _initPolygonVertexEditing;
|
|
65
106
|
private _ensureVertexEditingOverlay;
|
|
66
107
|
private _removeVertexEditingOverlay;
|
|
@@ -6,6 +6,23 @@ import { GfxElementModelView, GfxViewInteractionExtension, } from '../std/gfx/in
|
|
|
6
6
|
import { normalizeShapeBound } from './element-renderer/index.js';
|
|
7
7
|
import { PolygonVertexEditingOverlay } from './overlay/polygon-vertex-editing-overlay.js';
|
|
8
8
|
import { mountShapeTextEditor } from './text/edgeless-shape-text-editor.js';
|
|
9
|
+
/**
|
|
10
|
+
* The view every shape gets, and the base a framework NODE view extends when
|
|
11
|
+
* its model is a {@link ShapeElementModel} subclass.
|
|
12
|
+
*
|
|
13
|
+
* Generic over the model, with the plain shape as the default, so
|
|
14
|
+
* `WardleyNodeView extends ShapeElementView<WardleyNodeElementModel>` reads its
|
|
15
|
+
* own `kind` off `this.model` without a cast. Every existing call site names no
|
|
16
|
+
* type argument and is unchanged.
|
|
17
|
+
*
|
|
18
|
+
* A framework node that extends this inherits the polygon VERTEX EDITOR, which
|
|
19
|
+
* is the whole reason to: {@link enterVertexEditingMode} is what the toolbar's
|
|
20
|
+
* "Edit vertices" action calls, and the action checks `view instanceof
|
|
21
|
+
* ShapeElementView` — a node view outside this hierarchy makes that button
|
|
22
|
+
* silently do nothing. What such a subclass usually does NOT want is the other
|
|
23
|
+
* thing wired here, the double-click-to-edit; {@link editsTextOnDblClick} is
|
|
24
|
+
* the one line it has to say so.
|
|
25
|
+
*/
|
|
9
26
|
export class ShapeElementView extends GfxElementModelView {
|
|
10
27
|
constructor() {
|
|
11
28
|
super(...arguments);
|
|
@@ -78,8 +95,23 @@ export class ShapeElementView extends GfxElementModelView {
|
|
|
78
95
|
ConnectorPathGenerator.updatePath(connector, null, elementGetter);
|
|
79
96
|
}
|
|
80
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Whether a double-click on THIS element opens its own inner-text editor.
|
|
100
|
+
*
|
|
101
|
+
* True for every plain shape, which is what this editor has always done: a
|
|
102
|
+
* shape is a box you write in. It is a HOOK rather than a constant because a
|
|
103
|
+
* framework node is often not one — its name may be a text element grouped
|
|
104
|
+
* beside it, or the element may be a glyph's own wiring that must carry no
|
|
105
|
+
* words at all — and a subclass that overrode `onCreated` to dodge this one
|
|
106
|
+
* wiring would lose the vertex editor set up beside it.
|
|
107
|
+
*/
|
|
108
|
+
editsTextOnDblClick() {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
81
111
|
_initDblClickToEdit() {
|
|
82
112
|
this.on('dblclick', () => {
|
|
113
|
+
if (!this.editsTextOnDblClick())
|
|
114
|
+
return;
|
|
83
115
|
const edgeless = this.std.view.getBlock(this.std.store.root.id);
|
|
84
116
|
if (edgeless &&
|
|
85
117
|
!this.model.isLocked() &&
|
|
@@ -333,9 +365,26 @@ export class ShapeElementView extends GfxElementModelView {
|
|
|
333
365
|
* The overlay is shown when a polygon is selected and removed when
|
|
334
366
|
* deselected or when a non-polygon shape is selected.
|
|
335
367
|
*/
|
|
368
|
+
/**
|
|
369
|
+
* Whether THIS element's corners may be moved by hand.
|
|
370
|
+
*
|
|
371
|
+
* True for every plain polygon. The companion of {@link editsTextOnDblClick},
|
|
372
|
+
* and a hook for the same reason: a framework polygon's outline is often the
|
|
373
|
+
* NOTATION rather than a drawing — a Wardley accelerator points right and a
|
|
374
|
+
* decelerator points left, and that direction is the whole of what separates
|
|
375
|
+
* them — so hovering one must not offer a handle that can only turn a
|
|
376
|
+
* statement into a blob. Declining here skips the selection overlay, the
|
|
377
|
+
* pointer wiring and {@link enterVertexEditingMode} together, which is the
|
|
378
|
+
* point: a half-wired overlay is worse than none.
|
|
379
|
+
*/
|
|
380
|
+
editsVerticesOnSelection() {
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
336
383
|
_initPolygonVertexEditing() {
|
|
337
384
|
if (this.model.shapeType !== ShapeType.Polygon)
|
|
338
385
|
return;
|
|
386
|
+
if (!this.editsVerticesOnSelection())
|
|
387
|
+
return;
|
|
339
388
|
// Subscribe to selection changes to manage overlay lifecycle
|
|
340
389
|
this.disposable.add(this.gfx.selection.slots.updated.subscribe(() => {
|
|
341
390
|
const selectedIds = this.gfx.selection.selectedIds;
|
|
@@ -481,6 +530,11 @@ export class ShapeElementView extends GfxElementModelView {
|
|
|
481
530
|
enterVertexEditingMode() {
|
|
482
531
|
if (this.model.shapeType !== ShapeType.Polygon)
|
|
483
532
|
return;
|
|
533
|
+
// The same hook the selection wiring reads, asked again here: this is the
|
|
534
|
+
// other door into the mode, and an element that declined the wiring would
|
|
535
|
+
// otherwise get an overlay with no pointer handlers behind it.
|
|
536
|
+
if (!this.editsVerticesOnSelection())
|
|
537
|
+
return;
|
|
484
538
|
this._ensureVertexEditingOverlay();
|
|
485
539
|
this._enterVertexEditingMode();
|
|
486
540
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Color, type Palette, ShapeElementModel } from '../../affine-model/index.js';
|
|
2
|
+
import type { ToolbarAction } from '../../affine-shared/services/index.js';
|
|
3
|
+
export declare function neutralPalettes(): Palette[];
|
|
4
|
+
/**
|
|
5
|
+
* A fill / stroke colour picker identical to the shape one, seeded with the
|
|
6
|
+
* caller's swatches (`.palettes`) instead of the default editor palette.
|
|
7
|
+
*
|
|
8
|
+
* Frameworks whose nodes are {@link ShapeElementModel} subclasses (EDGY,
|
|
9
|
+
* Wardley, …) all want the same picker with their own colours in front of it,
|
|
10
|
+
* so the ~100 lines that wire `edgeless-shape-color-picker` to the models live
|
|
11
|
+
* here once rather than being copied per framework.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The last word on what a picked swatch actually WRITES as a model's fill.
|
|
15
|
+
*
|
|
16
|
+
* The identity by default, which is what a colour picker means everywhere. It
|
|
17
|
+
* exists because a swatch is a hue and some elements are washes: a Wardley
|
|
18
|
+
* ZONE is drawn over the map it groups, so its fill carries an alpha, and a
|
|
19
|
+
* picked swatch that replaced `#c6dbfc99` with an opaque `#5b9cf6` would hide
|
|
20
|
+
* the very map the zone is there to annotate. A framework that has such
|
|
21
|
+
* elements says so here — per model, because the same picker serves its
|
|
22
|
+
* washes and its opaque artefacts.
|
|
23
|
+
*/
|
|
24
|
+
export type FillColorFor = (model: ShapeElementModel, value: Color) => Color;
|
|
25
|
+
export declare function paletteColorAction(id: string, palettes: Palette[], fillColorFor?: FillColorFor): ToolbarAction;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { EdgelessCRUDIdentifier } from '../../affine-block-surface/index.js';
|
|
2
|
+
import { packColor, } from '../../affine-components/color-picker/index.js';
|
|
3
|
+
import { DefaultTheme, isTransparent, LineWidth, resolveColor, ShapeElementModel, StrokeStyle, } from '../../affine-model/index.js';
|
|
4
|
+
import { getMostCommonValue } from '../../affine-shared/utils/index.js';
|
|
5
|
+
import { html } from 'lit';
|
|
6
|
+
/**
|
|
7
|
+
* The neutrals of the default editor palette: greys, white, black, transparent.
|
|
8
|
+
*
|
|
9
|
+
* A framework that seeds the picker with its OWN swatches drops the historical
|
|
10
|
+
* editor colours — they say nothing in that notation — but keeps the neutrals,
|
|
11
|
+
* which every drawing needs and no notation owns.
|
|
12
|
+
*/
|
|
13
|
+
const NEUTRAL_KEY = /grey|gray|white|black|transparent/i;
|
|
14
|
+
export function neutralPalettes() {
|
|
15
|
+
return DefaultTheme.Palettes.filter(p => NEUTRAL_KEY.test(p.key));
|
|
16
|
+
}
|
|
17
|
+
// Mirror of the shape color action's text-color rule.
|
|
18
|
+
function getTextColor(fillColor) {
|
|
19
|
+
if (fillColor === DefaultTheme.black)
|
|
20
|
+
return DefaultTheme.white;
|
|
21
|
+
if (fillColor === DefaultTheme.white)
|
|
22
|
+
return DefaultTheme.black;
|
|
23
|
+
return DefaultTheme.shapeTextColor;
|
|
24
|
+
}
|
|
25
|
+
export function paletteColorAction(id, palettes, fillColorFor = (_model, value) => value) {
|
|
26
|
+
return {
|
|
27
|
+
id,
|
|
28
|
+
when(ctx) {
|
|
29
|
+
return ctx.getSurfaceModelsByType(ShapeElementModel).length > 0;
|
|
30
|
+
},
|
|
31
|
+
content(ctx) {
|
|
32
|
+
const models = ctx.getSurfaceModelsByType(ShapeElementModel);
|
|
33
|
+
if (!models.length)
|
|
34
|
+
return null;
|
|
35
|
+
const enableCustomColor = ctx.features.getFlag('enable_color_picker');
|
|
36
|
+
const theme = ctx.theme.edgeless$.value;
|
|
37
|
+
const firstModel = models[0];
|
|
38
|
+
const originalFillColor = firstModel.fillColor;
|
|
39
|
+
const originalStrokeColor = firstModel.strokeColor;
|
|
40
|
+
const mapped = models.map(({ filled, fillColor, strokeColor, strokeWidth, strokeStyle }) => ({
|
|
41
|
+
fillColor: filled
|
|
42
|
+
? resolveColor(fillColor, theme)
|
|
43
|
+
: DefaultTheme.transparent,
|
|
44
|
+
strokeColor: resolveColor(strokeColor, theme),
|
|
45
|
+
strokeWidth,
|
|
46
|
+
strokeStyle,
|
|
47
|
+
}));
|
|
48
|
+
const fillColor = getMostCommonValue(mapped, 'fillColor') ??
|
|
49
|
+
resolveColor(DefaultTheme.shapeFillColor, theme);
|
|
50
|
+
const strokeColor = getMostCommonValue(mapped, 'strokeColor') ??
|
|
51
|
+
resolveColor(DefaultTheme.shapeStrokeColor, theme);
|
|
52
|
+
const strokeWidth = getMostCommonValue(mapped, 'strokeWidth') ?? LineWidth.Four;
|
|
53
|
+
const strokeStyle = getMostCommonValue(mapped, 'strokeStyle') ?? StrokeStyle.Solid;
|
|
54
|
+
const pickColorWrapper = (field, pickCallback) => (e) => {
|
|
55
|
+
e.stopPropagation();
|
|
56
|
+
switch (e.detail.type) {
|
|
57
|
+
case 'pick':
|
|
58
|
+
pickCallback(e.detail.detail);
|
|
59
|
+
break;
|
|
60
|
+
case 'start':
|
|
61
|
+
ctx.store.captureSync();
|
|
62
|
+
models.forEach(model => model.stash(field));
|
|
63
|
+
break;
|
|
64
|
+
case 'end':
|
|
65
|
+
ctx.store.transact(() => {
|
|
66
|
+
models.forEach(model => model.pop(field));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const onPickFillColor = pickColorWrapper('fillColor', palette => {
|
|
71
|
+
const value = palette.value;
|
|
72
|
+
const filled = isTransparent(value);
|
|
73
|
+
const crud = ctx.std.get(EdgelessCRUDIdentifier);
|
|
74
|
+
models.forEach(model => {
|
|
75
|
+
// Per model, so a selection mixing a wash and an opaque artefact
|
|
76
|
+
// gets the right fill on each — and `packColor` runs on what the
|
|
77
|
+
// hook returned, since that is the value the document stores.
|
|
78
|
+
const props = packColor('fillColor', fillColorFor(model, value));
|
|
79
|
+
if (filled && !model.filled) {
|
|
80
|
+
const color = getTextColor(value);
|
|
81
|
+
Object.assign(props, { filled, color });
|
|
82
|
+
}
|
|
83
|
+
crud.updateElement(model.id, props);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
const onPickStrokeColor = pickColorWrapper('strokeColor', palette => {
|
|
87
|
+
const props = packColor('strokeColor', palette.value);
|
|
88
|
+
const crud = ctx.std.get(EdgelessCRUDIdentifier);
|
|
89
|
+
models.forEach(model => crud.updateElement(model.id, props));
|
|
90
|
+
});
|
|
91
|
+
const onPickStrokeStyle = (e) => {
|
|
92
|
+
e.stopPropagation();
|
|
93
|
+
const { type, value } = e.detail;
|
|
94
|
+
const crud = ctx.std.get(EdgelessCRUDIdentifier);
|
|
95
|
+
const props = type === 'size'
|
|
96
|
+
? { strokeWidth: value }
|
|
97
|
+
: { strokeStyle: value };
|
|
98
|
+
for (const model of models) {
|
|
99
|
+
crud.updateElement(model.id, props);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
return html `
|
|
103
|
+
<edgeless-shape-color-picker
|
|
104
|
+
@pickFillColor=${onPickFillColor}
|
|
105
|
+
@pickStrokeColor=${onPickStrokeColor}
|
|
106
|
+
@pickStrokeStyle=${onPickStrokeStyle}
|
|
107
|
+
.palettes=${palettes}
|
|
108
|
+
.payload=${{
|
|
109
|
+
fillColor,
|
|
110
|
+
strokeColor,
|
|
111
|
+
strokeWidth,
|
|
112
|
+
strokeStyle,
|
|
113
|
+
originalFillColor,
|
|
114
|
+
originalStrokeColor,
|
|
115
|
+
theme,
|
|
116
|
+
enableCustomColor,
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
</edgeless-shape-color-picker>
|
|
120
|
+
`;
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import type { SerializedXYWH } from '../../../global/gfx/index.js';
|
|
2
|
+
import type { PointTestOptions } from '../../../std/gfx/index.js';
|
|
2
3
|
import type { FrameworkBackgroundProps } from '../framework-background/index.js';
|
|
3
4
|
import { FrameworkBackgroundElementModel } from '../framework-background/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Height of the board's title band — the header strip across the top of the
|
|
7
|
+
* sheet the diagram title is written in, in model units.
|
|
8
|
+
*
|
|
9
|
+
* Declared HERE, in the model, and re-exported by `@labre/affine-gfx-c4` rather
|
|
10
|
+
* than the other way round, for the reason `POOL_BAND_WIDTH` is: the
|
|
11
|
+
* board's own hit test needs it (a board is clickable by its title band as well
|
|
12
|
+
* as its border), and `affine-model` cannot reach into `affine-block-surface`
|
|
13
|
+
* where the declaration is resolved. One number, owned by the layer that both
|
|
14
|
+
* the declaration and the hit test can read.
|
|
15
|
+
*
|
|
16
|
+
* It is the board's TOP MARGIN, and it always was — the deep inset the plot
|
|
17
|
+
* gives up so the title has somewhere to be written. Naming it says out loud
|
|
18
|
+
* what was already true, so the strip that is painted, the strip that is picked
|
|
19
|
+
* and the strip that opens the rename are one number rather than three.
|
|
20
|
+
*
|
|
21
|
+
* 56 is what the title has been written in since the pack shipped: a 20-unit
|
|
22
|
+
* title, sitting on a baseline a third of the band up from its foot, with the
|
|
23
|
+
* rest as breathing room above and below. Changing it moves the title, so it is
|
|
24
|
+
* a visual decision and not a hit-test one.
|
|
25
|
+
*/
|
|
26
|
+
export declare const C4_BOARD_TITLE_BAND_HEIGHT = 56;
|
|
4
27
|
/**
|
|
5
28
|
* Which of C4's levels this board draws — the DECLARED fact, as opposed to the
|
|
6
29
|
* title, which is free text and says whatever its author wants it to.
|
|
@@ -85,4 +108,30 @@ export declare class C4BoardElementModel extends FrameworkBackgroundElementModel
|
|
|
85
108
|
* on a board that declares none — no fact, no rule (PRD principle 8).
|
|
86
109
|
*/
|
|
87
110
|
accessor level: C4BoardLevel | undefined;
|
|
111
|
+
/**
|
|
112
|
+
* A board is picked by its BORDER — and by its TITLE BAND, the one carve-out
|
|
113
|
+
* this frame keeps (issues #194 / #197).
|
|
114
|
+
*
|
|
115
|
+
* The same carve-out a BPMN pool makes, for the same reason and by the same
|
|
116
|
+
* arithmetic: the band is the only part of the sheet that is the SHEET rather
|
|
117
|
+
* than the diagram drawn on it. The title is written there, nothing is ever
|
|
118
|
+
* dropped there by convention, and the plot below is where the work goes — so
|
|
119
|
+
* a click there belongs to whatever node is under the pointer.
|
|
120
|
+
*
|
|
121
|
+
* Without this the title became unreachable the day backgrounds stopped being
|
|
122
|
+
* picked by their area: the only thing left answering was the view's
|
|
123
|
+
* double-click zone, which is the tight box of the DRAWN glyphs, and a single
|
|
124
|
+
* click on the title selected nothing at all.
|
|
125
|
+
*
|
|
126
|
+
* ## Where the geometry comes from
|
|
127
|
+
*
|
|
128
|
+
* The band is the top margin, full width — one number
|
|
129
|
+
* ({@link C4_BOARD_TITLE_BAND_HEIGHT}), read from here by the framework
|
|
130
|
+
* declaration, so what is painted is what is selectable. There is no
|
|
131
|
+
* zoom-grown target here as there is on a pool: the pool grows its 28-unit
|
|
132
|
+
* strip to stay reachable when zoomed out, which would put the rename zone
|
|
133
|
+
* outside the selectable one. A 56-unit header does not need it, and the
|
|
134
|
+
* promise "what is painted is what is picked" is worth more than the pixels.
|
|
135
|
+
*/
|
|
136
|
+
includesPoint(x: number, y: number, options?: PointTestOptions): boolean;
|
|
88
137
|
}
|
|
@@ -32,8 +32,31 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
|
|
|
32
32
|
}
|
|
33
33
|
return useValue ? value : void 0;
|
|
34
34
|
};
|
|
35
|
+
import { rotatePoint } from '../../../global/gfx/index.js';
|
|
35
36
|
import { field } from '../../../std/gfx/index.js';
|
|
36
37
|
import { FrameworkBackgroundElementModel } from '../framework-background/index.js';
|
|
38
|
+
/**
|
|
39
|
+
* Height of the board's title band — the header strip across the top of the
|
|
40
|
+
* sheet the diagram title is written in, in model units.
|
|
41
|
+
*
|
|
42
|
+
* Declared HERE, in the model, and re-exported by `@labre/affine-gfx-c4` rather
|
|
43
|
+
* than the other way round, for the reason `POOL_BAND_WIDTH` is: the
|
|
44
|
+
* board's own hit test needs it (a board is clickable by its title band as well
|
|
45
|
+
* as its border), and `affine-model` cannot reach into `affine-block-surface`
|
|
46
|
+
* where the declaration is resolved. One number, owned by the layer that both
|
|
47
|
+
* the declaration and the hit test can read.
|
|
48
|
+
*
|
|
49
|
+
* It is the board's TOP MARGIN, and it always was — the deep inset the plot
|
|
50
|
+
* gives up so the title has somewhere to be written. Naming it says out loud
|
|
51
|
+
* what was already true, so the strip that is painted, the strip that is picked
|
|
52
|
+
* and the strip that opens the rename are one number rather than three.
|
|
53
|
+
*
|
|
54
|
+
* 56 is what the title has been written in since the pack shipped: a 20-unit
|
|
55
|
+
* title, sitting on a baseline a third of the band up from its foot, with the
|
|
56
|
+
* rest as breathing room above and below. Changing it moves the title, so it is
|
|
57
|
+
* a visual decision and not a hit-test one.
|
|
58
|
+
*/
|
|
59
|
+
export const C4_BOARD_TITLE_BAND_HEIGHT = 56;
|
|
37
60
|
/**
|
|
38
61
|
* The C4 board: the sheet one diagram of the model is drawn on — a context
|
|
39
62
|
* diagram, a container diagram, a component diagram. A framework background like
|
|
@@ -119,6 +142,51 @@ let C4BoardElementModel = (() => {
|
|
|
119
142
|
*/
|
|
120
143
|
get level() { return this.#level_accessor_storage; }
|
|
121
144
|
set level(value) { this.#level_accessor_storage = value; }
|
|
145
|
+
/**
|
|
146
|
+
* A board is picked by its BORDER — and by its TITLE BAND, the one carve-out
|
|
147
|
+
* this frame keeps (issues #194 / #197).
|
|
148
|
+
*
|
|
149
|
+
* The same carve-out a BPMN pool makes, for the same reason and by the same
|
|
150
|
+
* arithmetic: the band is the only part of the sheet that is the SHEET rather
|
|
151
|
+
* than the diagram drawn on it. The title is written there, nothing is ever
|
|
152
|
+
* dropped there by convention, and the plot below is where the work goes — so
|
|
153
|
+
* a click there belongs to whatever node is under the pointer.
|
|
154
|
+
*
|
|
155
|
+
* Without this the title became unreachable the day backgrounds stopped being
|
|
156
|
+
* picked by their area: the only thing left answering was the view's
|
|
157
|
+
* double-click zone, which is the tight box of the DRAWN glyphs, and a single
|
|
158
|
+
* click on the title selected nothing at all.
|
|
159
|
+
*
|
|
160
|
+
* ## Where the geometry comes from
|
|
161
|
+
*
|
|
162
|
+
* The band is the top margin, full width — one number
|
|
163
|
+
* ({@link C4_BOARD_TITLE_BAND_HEIGHT}), read from here by the framework
|
|
164
|
+
* declaration, so what is painted is what is selectable. There is no
|
|
165
|
+
* zoom-grown target here as there is on a pool: the pool grows its 28-unit
|
|
166
|
+
* strip to stay reachable when zoomed out, which would put the rename zone
|
|
167
|
+
* outside the selectable one. A 56-unit header does not need it, and the
|
|
168
|
+
* promise "what is painted is what is picked" is worth more than the pixels.
|
|
169
|
+
*/
|
|
170
|
+
includesPoint(x, y, options) {
|
|
171
|
+
if (super.includesPoint(x, y, options))
|
|
172
|
+
return true;
|
|
173
|
+
const [ex, ey, w, h] = this.deserializedXYWH;
|
|
174
|
+
// Element-local coordinates, undoing the element rotation about its centre
|
|
175
|
+
// — the band is axis-aligned inside the board, not on the canvas.
|
|
176
|
+
let lx = x - ex;
|
|
177
|
+
let ly = y - ey;
|
|
178
|
+
const rotate = this.rotate ?? 0;
|
|
179
|
+
if (rotate) {
|
|
180
|
+
const [ux, uy] = rotatePoint([x, y], [ex + w / 2, ey + h / 2], -rotate);
|
|
181
|
+
lx = ux - ex;
|
|
182
|
+
ly = uy - ey;
|
|
183
|
+
}
|
|
184
|
+
if (lx < 0 || lx > w || ly < 0)
|
|
185
|
+
return false;
|
|
186
|
+
// Clamped to a board shorter than its own header — the degenerate case the
|
|
187
|
+
// renderer clamps too.
|
|
188
|
+
return ly <= Math.min(C4_BOARD_TITLE_BAND_HEIGHT, h);
|
|
189
|
+
}
|
|
122
190
|
constructor() {
|
|
123
191
|
super(...arguments);
|
|
124
192
|
__runInitializers(this, _level_extraInitializers);
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import { ShapeElementModel } from '../shape/index.js';
|
|
2
|
-
export type WardleyNodeKind = 'component' | 'anchor' | 'pipeline' | 'handle' | 'market' | 'ecosystem' | 'method';
|
|
2
|
+
export type WardleyNodeKind = 'component' | 'anchor' | 'pipeline' | 'handle' | 'market' | 'ecosystem' | 'method' | 'porter' | 'accelerator' | 'decelerator' | 'area';
|
|
3
3
|
/**
|
|
4
4
|
* A Wardley map node. Extends {@link ShapeElementModel} (a native ellipse) so it
|
|
5
5
|
* inherits ALL shape behaviour — editable stroke width / colors, native resize,
|
|
6
6
|
* center connector anchor, the shape context toolbar — for free. `kind`
|
|
7
7
|
* discriminates the plain `component`, the `anchor` (which the renderer
|
|
8
8
|
* decorates with an inscribed person glyph), the two pieces of a pipeline (the
|
|
9
|
-
* `pipeline` body + its square `handle`), the `market` outer circle,
|
|
10
|
-
* `ecosystem` grey backing disk
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* `pipeline` body + its square `handle`), the `market` outer circle, the
|
|
10
|
+
* `ecosystem` grey backing disk, the `porter` circle — a Porter's-forces
|
|
11
|
+
* glyph, which is the one kind that is NOT a link in the value chain: it marks
|
|
12
|
+
* an external competition force acting on the map — and the `accelerator` /
|
|
13
|
+
* `decelerator` arrows, which are not links in the chain either: they annotate
|
|
14
|
+
* the CLIMATE, saying that something is speeding evolution up or slowing it
|
|
15
|
+
* down. Those two are the first kinds drawn as a native POLYGON rather than an
|
|
16
|
+
* ellipse or a rect — and the `area` is the third: a translucent ZONE of the
|
|
17
|
+
* map, drawn as a rect or as a polygon over the components it groups, and the
|
|
18
|
+
* only kind whose two shapes are the same kind (the `shapeType` says which).
|
|
19
|
+
* Composite nodes (pipeline / market / ecosystem / porter / accelerator /
|
|
20
|
+
* decelerator) are built by grouping several of these + native connectors + a
|
|
21
|
+
* text label. The text label is a SEPARATE native text element grouped with the
|
|
22
|
+
* node, not stored here — the `porter` and the `area` being the exceptions that
|
|
23
|
+
* prove it, since the porter's one letter and the area's name are the shape's
|
|
24
|
+
* own inner text.
|
|
25
|
+
*
|
|
26
|
+
* `kind` is ADDITIVE by construction: a value is only ever appended, so a
|
|
27
|
+
* document written before a value was appended carries none of it and opens
|
|
28
|
+
* unchanged. `area` is the most recent, and needs no migration.
|
|
14
29
|
*/
|
|
15
30
|
export declare class WardleyNodeElementModel extends ShapeElementModel {
|
|
16
31
|
get type(): string;
|