@neta-art/cohub 5.3.3 → 5.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/board/codec.d.ts +2 -0
- package/dist/board/codec.js +18 -13
- package/dist/board/core/arrow-geometry.d.ts +23 -0
- package/dist/board/core/arrow-geometry.js +109 -0
- package/dist/board/core/connections.d.ts +91 -0
- package/dist/board/core/connections.js +414 -0
- package/dist/board/core/export-plan.d.ts +15 -4
- package/dist/board/core/export-plan.js +49 -16
- package/dist/board/core/shape-definition.js +1 -1
- package/dist/board/core/shape-types.d.ts +8 -2
- package/dist/board/core/shape-types.js +1 -1
- package/dist/board/core/tool-styles.d.ts +9 -3
- package/dist/board/core/tool-styles.js +10 -6
- package/dist/board/export/index.js +1 -0
- package/dist/board/export/scene.d.ts +3 -0
- package/dist/board/export/scene.js +18 -1
- package/dist/board/index.d.ts +8 -5
- package/dist/board/index.js +8 -5
- package/dist/board/render/connection-layer.d.ts +48 -0
- package/dist/board/render/connection-layer.js +223 -0
- package/dist/board/render/index.d.ts +2 -1
- package/dist/board/render/index.js +3 -2
- package/dist/board/render/renderers/arrow-card-renderer.js +35 -48
- package/dist/chunks/http.d.ts +53 -1
- package/dist/chunks/http.js +287 -1
- package/dist/chunks/websocket.d.ts +296 -57
- package/dist/http.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +17 -56
- package/dist/protocol/dist/board-connection.d.ts +310 -0
- package/dist/protocol/dist/board-connection.js +215 -0
- package/dist/protocol/dist/board-constants.d.ts +11 -1
- package/dist/protocol/dist/board-constants.js +15 -1
- package/dist/protocol/dist/board-document.d.ts +106 -60
- package/dist/protocol/dist/board-document.js +57 -17
- package/dist/protocol/dist/board.d.ts +1 -0
- package/dist/protocol/dist/board.js +3 -0
- package/dist/protocol/dist/index.d.ts +2 -1
- package/dist/protocol/dist/index.js +2 -1
- package/dist/protocol/dist/realtime/board-awareness.js +15 -15
- package/package.json +1 -1
- package/dist/board/core/bindings.d.ts +0 -45
- package/dist/board/core/bindings.js +0 -162
package/dist/board/codec.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BoardConnectionRecord } from "../protocol/dist/board-connection.js";
|
|
1
2
|
import { BoardManifest, BoardNodeInput, BoardNodeRecord, BoardRecord, InvalidBoardFileError, isBoardPath, parseBoardManifest, serializeBoardManifest } from "../protocol/dist/board.js";
|
|
2
3
|
import "../protocol/dist/index.js";
|
|
3
4
|
import { BoardAppearance, BoardDocument, BoardItem } from "../protocol/dist/board-document.js";
|
|
@@ -20,6 +21,7 @@ declare function boardNodeToItem(node: BoardNodeRecord): BoardItem;
|
|
|
20
21
|
declare function boardBootstrapToDocument(input: {
|
|
21
22
|
board: BoardRecord;
|
|
22
23
|
nodes: BoardNodeRecord[];
|
|
24
|
+
connections?: BoardConnectionRecord[];
|
|
23
25
|
}): BoardDocument;
|
|
24
26
|
//#endregion
|
|
25
27
|
export { BOARD_NODE_SOURCE, type BoardManifest, type BoardNodeRecord, type BoardRecord, DEFAULT_BOARD_APPEARANCE, ITEM_BASE_KEYS, InvalidBoardFileError, WireBackedBoardItem, boardBootstrapToDocument, boardNodeToItem, isBoardPath, isRecord, nodeInputFromRecord, parseBoardManifest, serializeBoardManifest, sourceForItem };
|
package/dist/board/codec.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BOARD_DOCUMENT_KIND, InvalidBoardFileError, isBoardPath, parseBoardManifest, serializeBoardManifest } from "../protocol/dist/board.js";
|
|
2
|
-
import { BoardAppearanceSchema,
|
|
2
|
+
import { BoardAppearanceSchema, BoardFileSnapshotSchema, UNKNOWN_BOARD_ITEM_TYPE, parseBoardDocument } from "../protocol/dist/board-document.js";
|
|
3
3
|
import { TEXT_FONT_SIZE, clampBoardTextFontSize } from "./core/text-metrics.js";
|
|
4
4
|
import "../protocol/dist/index.js";
|
|
5
5
|
//#region src/board/codec.ts
|
|
@@ -72,6 +72,17 @@ function spaceFilePathFromNode(node) {
|
|
|
72
72
|
if (typeof node.refPath === "string" && node.refPath) return node.refPath;
|
|
73
73
|
return null;
|
|
74
74
|
}
|
|
75
|
+
/** Read a stored world point, defaulting to the origin when absent. */
|
|
76
|
+
function pointFromData(value) {
|
|
77
|
+
if (isRecord(value) && typeof value.x === "number" && typeof value.y === "number") return {
|
|
78
|
+
x: value.x,
|
|
79
|
+
y: value.y
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
x: 0,
|
|
83
|
+
y: 0
|
|
84
|
+
};
|
|
85
|
+
}
|
|
75
86
|
function boardNodeToItemValue(node) {
|
|
76
87
|
const frame = frameFromNode(node);
|
|
77
88
|
const style = styleFromNode(node);
|
|
@@ -112,16 +123,8 @@ function boardNodeToItemValue(node) {
|
|
|
112
123
|
case "arrow": return {
|
|
113
124
|
id: node.nodeId,
|
|
114
125
|
type: "arrow",
|
|
115
|
-
start: data.start
|
|
116
|
-
|
|
117
|
-
x: 0,
|
|
118
|
-
y: 0
|
|
119
|
-
},
|
|
120
|
-
end: data.end ?? {
|
|
121
|
-
kind: "point",
|
|
122
|
-
x: 0,
|
|
123
|
-
y: 0
|
|
124
|
-
},
|
|
126
|
+
start: pointFromData(data.start),
|
|
127
|
+
end: pointFromData(data.end),
|
|
125
128
|
bend: typeof data.bend === "number" ? data.bend : 0,
|
|
126
129
|
color: typeof data.color === "string" ? data.color : "brand",
|
|
127
130
|
size: typeof data.size === "number" ? data.size : 2.5,
|
|
@@ -219,7 +222,8 @@ function boardNodeToItem(node) {
|
|
|
219
222
|
}
|
|
220
223
|
function boardBootstrapToDocument(input) {
|
|
221
224
|
const appearance = BoardAppearanceSchema.safeParse(input.board.metadata.appearance);
|
|
222
|
-
const
|
|
225
|
+
const connections = (input.connections ?? []).map(({ boardId: _boardId, revision: _revision, createdAt: _createdAt, updatedAt: _updatedAt, ...connection }) => connection);
|
|
226
|
+
const document = parseBoardDocument({
|
|
223
227
|
kind: BOARD_DOCUMENT_KIND,
|
|
224
228
|
version: 1,
|
|
225
229
|
appearance: appearance.success ? appearance.data : DEFAULT_BOARD_APPEARANCE,
|
|
@@ -228,7 +232,8 @@ function boardBootstrapToDocument(input) {
|
|
|
228
232
|
y: 0,
|
|
229
233
|
zoom: 1
|
|
230
234
|
},
|
|
231
|
-
items: input.nodes.map(boardNodeToItem)
|
|
235
|
+
items: input.nodes.map(boardNodeToItem),
|
|
236
|
+
connections
|
|
232
237
|
});
|
|
233
238
|
const sources = new Map(input.nodes.map((node) => [node.nodeId, nodeInputFromRecord(node)]));
|
|
234
239
|
return {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BoardArrowItem, BoardFrame } from "../../protocol/dist/board-document.js";
|
|
2
|
+
import { Rect, WorldPoint } from "../geometry.js";
|
|
3
|
+
//#region src/board/core/arrow-geometry.d.ts
|
|
4
|
+
type ResolvedArrow = {
|
|
5
|
+
start: WorldPoint;
|
|
6
|
+
end: WorldPoint;
|
|
7
|
+
/** Quadratic control point (already bent); the midpoint when bend is 0. */
|
|
8
|
+
control: WorldPoint;
|
|
9
|
+
};
|
|
10
|
+
/** Resolve an arrow's endpoints and its bent control point. */
|
|
11
|
+
declare function resolveArrow(item: BoardArrowItem): ResolvedArrow;
|
|
12
|
+
/** Sample an arrow's quadratic curve into world points. */
|
|
13
|
+
declare function sampleArrow(resolved: ResolvedArrow, segments: number): WorldPoint[];
|
|
14
|
+
/** World bounds of an arrow, padded for its stroke. */
|
|
15
|
+
declare function arrowBounds(item: BoardArrowItem): Rect;
|
|
16
|
+
/** The frame an arrow should carry, derived from its endpoints. */
|
|
17
|
+
declare function arrowFrame(item: BoardArrowItem): BoardFrame;
|
|
18
|
+
/** Distance from a world point to an arrow's curve (hit testing). */
|
|
19
|
+
declare function distanceToArrow(item: BoardArrowItem, point: WorldPoint): number;
|
|
20
|
+
/** Translate an arrow by a delta, keeping its frame consistent. */
|
|
21
|
+
declare function translateArrow(item: BoardArrowItem, dx: number, dy: number): BoardArrowItem;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { ResolvedArrow, arrowBounds, arrowFrame, distanceToArrow, resolveArrow, sampleArrow, translateArrow };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { worldPoint } from "../geometry.js";
|
|
2
|
+
//#region src/board/core/arrow-geometry.ts
|
|
3
|
+
/**
|
|
4
|
+
* Free-arrow geometry.
|
|
5
|
+
*
|
|
6
|
+
* An arrow is a standalone annotation stroke between two world points — it does
|
|
7
|
+
* not relate nodes. Node relations are `BoardConnection`s, which resolve their
|
|
8
|
+
* geometry from the nodes they join (see ./connections). Keeping the two apart is
|
|
9
|
+
* what makes each one simple: an arrow owns absolute coordinates and needs no
|
|
10
|
+
* lookup, while a connection owns no coordinates at all.
|
|
11
|
+
*/
|
|
12
|
+
/** Resolve an arrow's endpoints and its bent control point. */
|
|
13
|
+
function resolveArrow(item) {
|
|
14
|
+
const start = worldPoint(item.start.x, item.start.y);
|
|
15
|
+
const end = worldPoint(item.end.x, item.end.y);
|
|
16
|
+
const mid = worldPoint((start.x + end.x) / 2, (start.y + end.y) / 2);
|
|
17
|
+
if (!item.bend) return {
|
|
18
|
+
start,
|
|
19
|
+
end,
|
|
20
|
+
control: mid
|
|
21
|
+
};
|
|
22
|
+
const dx = end.x - start.x;
|
|
23
|
+
const dy = end.y - start.y;
|
|
24
|
+
const length = Math.hypot(dx, dy) || 1;
|
|
25
|
+
const offset = item.bend * length;
|
|
26
|
+
return {
|
|
27
|
+
start,
|
|
28
|
+
end,
|
|
29
|
+
control: worldPoint(mid.x + -dy / length * offset, mid.y + dx / length * offset)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Sample an arrow's quadratic curve into world points. */
|
|
33
|
+
function sampleArrow(resolved, segments) {
|
|
34
|
+
const { start, control, end } = resolved;
|
|
35
|
+
const out = [];
|
|
36
|
+
for (let index = 0; index <= segments; index += 1) {
|
|
37
|
+
const t = index / segments;
|
|
38
|
+
const mt = 1 - t;
|
|
39
|
+
out.push(worldPoint(mt * mt * start.x + 2 * mt * t * control.x + t * t * end.x, mt * mt * start.y + 2 * mt * t * control.y + t * t * end.y));
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
/** World bounds of an arrow, padded for its stroke. */
|
|
44
|
+
function arrowBounds(item) {
|
|
45
|
+
const samples = sampleArrow(resolveArrow(item), 12);
|
|
46
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
47
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
48
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
49
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
50
|
+
for (const point of samples) {
|
|
51
|
+
minX = Math.min(minX, point.x);
|
|
52
|
+
minY = Math.min(minY, point.y);
|
|
53
|
+
maxX = Math.max(maxX, point.x);
|
|
54
|
+
maxY = Math.max(maxY, point.y);
|
|
55
|
+
}
|
|
56
|
+
const pad = Math.max(8, item.size * 2);
|
|
57
|
+
return {
|
|
58
|
+
x: minX - pad,
|
|
59
|
+
y: minY - pad,
|
|
60
|
+
width: Math.max(1, maxX - minX + pad * 2),
|
|
61
|
+
height: Math.max(1, maxY - minY + pad * 2)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** The frame an arrow should carry, derived from its endpoints. */
|
|
65
|
+
function arrowFrame(item) {
|
|
66
|
+
return {
|
|
67
|
+
...arrowBounds(item),
|
|
68
|
+
rotation: 0
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Distance from a world point to an arrow's curve (hit testing). */
|
|
72
|
+
function distanceToArrow(item, point) {
|
|
73
|
+
const samples = sampleArrow(resolveArrow(item), 16);
|
|
74
|
+
let min = Number.POSITIVE_INFINITY;
|
|
75
|
+
for (let index = 0; index < samples.length - 1; index += 1) {
|
|
76
|
+
const from = samples[index];
|
|
77
|
+
const to = samples[index + 1];
|
|
78
|
+
if (!from || !to) continue;
|
|
79
|
+
const distance = segmentDistance(point, from, to);
|
|
80
|
+
if (distance < min) min = distance;
|
|
81
|
+
}
|
|
82
|
+
return min;
|
|
83
|
+
}
|
|
84
|
+
function segmentDistance(p, a, b) {
|
|
85
|
+
const dx = b.x - a.x;
|
|
86
|
+
const dy = b.y - a.y;
|
|
87
|
+
const lengthSq = dx * dx + dy * dy;
|
|
88
|
+
if (lengthSq === 0) return Math.hypot(p.x - a.x, p.y - a.y);
|
|
89
|
+
const t = Math.min(1, Math.max(0, ((p.x - a.x) * dx + (p.y - a.y) * dy) / lengthSq));
|
|
90
|
+
return Math.hypot(p.x - (a.x + t * dx), p.y - (a.y + t * dy));
|
|
91
|
+
}
|
|
92
|
+
/** Translate an arrow by a delta, keeping its frame consistent. */
|
|
93
|
+
function translateArrow(item, dx, dy) {
|
|
94
|
+
const move = (point) => ({
|
|
95
|
+
x: point.x + dx,
|
|
96
|
+
y: point.y + dy
|
|
97
|
+
});
|
|
98
|
+
const next = {
|
|
99
|
+
...item,
|
|
100
|
+
start: move(item.start),
|
|
101
|
+
end: move(item.end)
|
|
102
|
+
};
|
|
103
|
+
return {
|
|
104
|
+
...next,
|
|
105
|
+
frame: arrowFrame(next)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
export { arrowBounds, arrowFrame, distanceToArrow, resolveArrow, sampleArrow, translateArrow };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { BoardConnection, BoardConnectionAnchor, BoardConnectionSide } from "../../protocol/dist/board-connection.js";
|
|
2
|
+
import { BoardFrame } from "../../protocol/dist/board-document.js";
|
|
3
|
+
import { Rect, WorldPoint } from "../geometry.js";
|
|
4
|
+
//#region src/board/core/connections.d.ts
|
|
5
|
+
type FrameLookup = (id: string) => BoardFrame | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Gap between a node's edge and the line's endpoint, in world units.
|
|
8
|
+
*
|
|
9
|
+
* A connection that touches the border reads as part of the node; a small gap
|
|
10
|
+
* makes the relation legible as its own object and keeps an arrowhead from being
|
|
11
|
+
* swallowed by the card it points at.
|
|
12
|
+
*/
|
|
13
|
+
declare const CONNECTION_ENDPOINT_GAP = 4;
|
|
14
|
+
type ResolvedConnectionEndpoint = {
|
|
15
|
+
point: WorldPoint;
|
|
16
|
+
/** Outward normal at the attachment, used to aim arrowheads and elbows. */
|
|
17
|
+
normal: WorldPoint;
|
|
18
|
+
side: BoardConnectionSide;
|
|
19
|
+
};
|
|
20
|
+
type ResolvedConnection = {
|
|
21
|
+
source: ResolvedConnectionEndpoint;
|
|
22
|
+
target: ResolvedConnectionEndpoint;
|
|
23
|
+
/** Sampled world-space path, always at least two points. */
|
|
24
|
+
path: WorldPoint[];
|
|
25
|
+
/** Midpoint of the path, for labels and the drag handle. */
|
|
26
|
+
mid: WorldPoint;
|
|
27
|
+
};
|
|
28
|
+
/** Denormalize a (nx, ny) anchor to a world point on a possibly rotated frame. */
|
|
29
|
+
declare function anchorToWorld(frame: BoardFrame, nx: number, ny: number): WorldPoint;
|
|
30
|
+
/** Normalize a world point into a (nx, ny) anchor on a frame, clamped to it. */
|
|
31
|
+
declare function worldToAnchor(frame: BoardFrame, point: WorldPoint): {
|
|
32
|
+
nx: number;
|
|
33
|
+
ny: number;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Choose the side an `auto` anchor should use.
|
|
37
|
+
*
|
|
38
|
+
* The dominant axis of the center-to-center vector wins, weighted by the frame's
|
|
39
|
+
* own proportions so a wide card prefers its long edges. Comparing raw dx/dy
|
|
40
|
+
* instead would make a wide node attach to its short side for most angles, which
|
|
41
|
+
* is the classic "line leaves from the corner" artifact.
|
|
42
|
+
*/
|
|
43
|
+
declare function autoConnectionSide(from: BoardFrame, to: BoardFrame): BoardConnectionSide;
|
|
44
|
+
/**
|
|
45
|
+
* World point of an anchor on a frame, ignoring the gap and the facing node.
|
|
46
|
+
*
|
|
47
|
+
* For a live drag the other end is the pointer, not a node, so `auto` has no
|
|
48
|
+
* frame to face: it resolves to the node's centre, which is the only honest
|
|
49
|
+
* answer while the relation has nowhere to point yet.
|
|
50
|
+
*/
|
|
51
|
+
declare function anchorPointOnFrame(anchor: BoardConnectionAnchor, frame: BoardFrame): WorldPoint;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a connection into world-space geometry, or null if either node is gone.
|
|
54
|
+
*
|
|
55
|
+
* A null result means "not drawable right now", never "delete this": the caller
|
|
56
|
+
* may be looking at a viewport-culled read where an endpoint simply was not
|
|
57
|
+
* fetched.
|
|
58
|
+
*/
|
|
59
|
+
declare function resolveConnection(connection: BoardConnection, getFrame: FrameLookup, options?: {
|
|
60
|
+
gap?: number;
|
|
61
|
+
}): ResolvedConnection | null;
|
|
62
|
+
/** Midpoint by arc length, so a label sits visually centered on the line. */
|
|
63
|
+
declare function pathMidpoint(path: WorldPoint[]): WorldPoint;
|
|
64
|
+
/** World bounds of a resolved connection, padded for its stroke. */
|
|
65
|
+
declare function connectionBounds(resolved: ResolvedConnection, strokeSize: number): Rect;
|
|
66
|
+
/** Distance from a world point to a resolved connection's path. */
|
|
67
|
+
declare function distanceToConnection(resolved: ResolvedConnection, point: WorldPoint): number;
|
|
68
|
+
/** Hit test a connection with a zoom-independent screen-space tolerance. */
|
|
69
|
+
declare function connectionHitTest(resolved: ResolvedConnection, point: WorldPoint, strokeSize: number): boolean;
|
|
70
|
+
/** Whether the connection draws an arrowhead at its source / target. */
|
|
71
|
+
declare function connectionArrowheads(connection: BoardConnection): {
|
|
72
|
+
atSource: boolean;
|
|
73
|
+
atTarget: boolean;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Index of connections by the nodes they touch.
|
|
77
|
+
*
|
|
78
|
+
* Built once per document revision and consulted per gesture frame: moving a node
|
|
79
|
+
* must cost its own degree, not a scan of every connection on the board. Without
|
|
80
|
+
* this, dragging one node in a densely connected board is O(connections) per
|
|
81
|
+
* frame, which is exactly the cost that shows up as drag lag.
|
|
82
|
+
*/
|
|
83
|
+
type ConnectionIndex = {
|
|
84
|
+
/** Connection ids touching a node (either endpoint). */
|
|
85
|
+
byNode: (nodeId: string) => readonly string[];
|
|
86
|
+
get: (connectionId: string) => BoardConnection | undefined;
|
|
87
|
+
readonly all: readonly BoardConnection[];
|
|
88
|
+
};
|
|
89
|
+
declare function createConnectionIndex(connections: readonly BoardConnection[]): ConnectionIndex;
|
|
90
|
+
//#endregion
|
|
91
|
+
export { CONNECTION_ENDPOINT_GAP, ConnectionIndex, FrameLookup, ResolvedConnection, ResolvedConnectionEndpoint, anchorPointOnFrame, anchorToWorld, autoConnectionSide, connectionArrowheads, connectionBounds, connectionHitTest, createConnectionIndex, distanceToConnection, pathMidpoint, resolveConnection, worldToAnchor };
|