@neta-art/cohub 9.0.0 → 10.0.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/README.md +100 -103
- package/dist/board/editor/align.d.ts +11 -0
- package/dist/board/editor/align.js +72 -0
- package/dist/board/editor/animation.d.ts +14 -0
- package/dist/board/editor/animation.js +172 -0
- package/dist/board/editor/camera.d.ts +32 -0
- package/dist/board/editor/camera.js +137 -0
- package/dist/board/editor/clipboard.d.ts +41 -0
- package/dist/board/editor/clipboard.js +122 -0
- package/dist/board/editor/connection-ports.d.ts +19 -0
- package/dist/board/editor/connection-ports.js +85 -0
- package/dist/board/editor/context.d.ts +111 -0
- package/dist/board/editor/context.js +30 -0
- package/dist/board/editor/copy.d.ts +17 -0
- package/dist/board/editor/copy.js +116 -0
- package/dist/board/editor/creation.d.ts +47 -0
- package/dist/board/editor/creation.js +224 -0
- package/dist/board/editor/deletion.d.ts +9 -0
- package/dist/board/editor/deletion.js +90 -0
- package/dist/board/editor/document-edits.d.ts +22 -0
- package/dist/board/editor/document-edits.js +193 -0
- package/dist/board/editor/document.d.ts +35 -0
- package/dist/board/editor/document.js +255 -0
- package/dist/board/editor/editor.d.ts +210 -0
- package/dist/board/editor/editor.js +429 -0
- package/dist/board/editor/index.d.ts +18 -0
- package/dist/board/editor/index.js +12 -0
- package/dist/board/editor/interaction.d.ts +115 -0
- package/dist/board/editor/interaction.js +584 -0
- package/dist/board/editor/query.d.ts +27 -0
- package/dist/board/editor/query.js +201 -0
- package/dist/board/editor/remote.d.ts +11 -0
- package/dist/board/editor/remote.js +55 -0
- package/dist/board/editor/selection-transform.d.ts +24 -0
- package/dist/board/editor/selection-transform.js +81 -0
- package/dist/board/editor/selection.d.ts +33 -0
- package/dist/board/editor/selection.js +196 -0
- package/dist/board/editor/snapping.d.ts +33 -0
- package/dist/board/editor/snapping.js +75 -0
- package/dist/board/editor/snapshots.d.ts +27 -0
- package/dist/board/editor/snapshots.js +132 -0
- package/dist/board/editor/spatial.d.ts +41 -0
- package/dist/board/editor/spatial.js +191 -0
- package/dist/board/editor/state.d.ts +12 -0
- package/dist/board/editor/state.js +49 -0
- package/dist/board/editor/tool.d.ts +10 -0
- package/dist/board/editor/tool.js +19 -0
- package/dist/board/editor/wheel.d.ts +5 -0
- package/dist/board/editor/wheel.js +17 -0
- package/dist/board/export/index.d.ts +5 -4
- package/dist/board/export/index.js +4 -3
- package/dist/board/export/node/index.d.ts +55 -0
- package/dist/board/{headless → export/node}/index.js +12 -12
- package/dist/board/export/node/sketch.d.ts +19 -0
- package/dist/board/{headless → export/node}/sketch.js +6 -6
- package/dist/board/export/scene.d.ts +6 -4
- package/dist/board/export/scene.js +4 -3
- package/dist/board/{animation.d.ts → model/animation.d.ts} +4 -4
- package/dist/board/{animation.js → model/animation.js} +3 -3
- package/dist/board/{core → model}/arrow-geometry.d.ts +2 -2
- package/dist/board/{core → model}/arrow-geometry.js +2 -2
- package/dist/board/{core → model}/draw-geometry.d.ts +2 -2
- package/dist/board/{core → model}/draw-geometry.js +1 -1
- package/dist/board/model/draw-input.d.ts +14 -0
- package/dist/board/model/draw-input.js +28 -0
- package/dist/board/{core → model}/export-assets.d.ts +1 -1
- package/dist/board/{core → model}/export-assets.js +1 -1
- package/dist/board/{core → model}/export-plan.d.ts +2 -2
- package/dist/board/{core → model}/export-plan.js +2 -2
- package/dist/board/{core → model}/file-preview.d.ts +1 -1
- package/dist/board/{core → model}/file-preview.js +1 -1
- package/dist/board/{core → model}/file-snapshot.d.ts +1 -1
- package/dist/board/{core → model}/file-snapshot.js +1 -1
- package/dist/board/{geometry.d.ts → model/geometry.d.ts} +3 -3
- package/dist/board/{geometry.js → model/geometry.js} +1 -1
- package/dist/board/{image-key.d.ts → model/image-key.d.ts} +2 -2
- package/dist/board/{image-key.js → model/image-key.js} +1 -1
- package/dist/board/model/index.d.ts +35 -0
- package/dist/board/{index.js → model/index.js} +26 -22
- package/dist/board/model/items/builtin.d.ts +5 -0
- package/dist/board/model/items/builtin.js +119 -0
- package/dist/board/model/items/create.d.ts +59 -0
- package/dist/board/model/items/create.js +271 -0
- package/dist/board/model/items/definition.d.ts +35 -0
- package/dist/board/model/items/definition.js +18 -0
- package/dist/board/model/items/id.d.ts +5 -0
- package/dist/board/model/items/id.js +11 -0
- package/dist/board/model/items/index.d.ts +6 -0
- package/dist/board/model/items/index.js +6 -0
- package/dist/board/model/items/registry.d.ts +35 -0
- package/dist/board/model/items/registry.js +84 -0
- package/dist/board/{media-playback.d.ts → model/media-playback.d.ts} +2 -2
- package/dist/board/{media-playback.js → model/media-playback.js} +1 -1
- package/dist/board/{media.d.ts → model/media.d.ts} +1 -1
- package/dist/board/{media.js → model/media.js} +1 -1
- package/dist/board/{core → model}/palette.d.ts +1 -1
- package/dist/board/{core → model}/palette.js +1 -1
- package/dist/board/{presets.d.ts → model/presets.d.ts} +3 -3
- package/dist/board/{presets.js → model/presets.js} +1 -1
- package/dist/board/{replay.d.ts → model/replay.d.ts} +5 -5
- package/dist/board/{replay.js → model/replay.js} +2 -2
- package/dist/board/{core → model}/scene.d.ts +1 -1
- package/dist/board/{core → model}/scene.js +1 -1
- package/dist/board/model/shape-types.d.ts +8 -0
- package/dist/board/model/shape-types.js +8 -0
- package/dist/board/{task.d.ts → model/task.d.ts} +4 -4
- package/dist/board/{task.js → model/task.js} +3 -3
- package/dist/board/{core → model}/text-metrics.d.ts +1 -1
- package/dist/board/{core → model}/text-metrics.js +1 -1
- package/dist/board/{core → model}/tool-styles.d.ts +1 -1
- package/dist/board/{core → model}/tool-styles.js +1 -1
- package/dist/board/{runtime/board-player.d.ts → player/index.d.ts} +2 -2
- package/dist/board/{runtime/board-player.js → player/index.js} +5 -4
- package/dist/board/render/board-background.d.ts +1 -1
- package/dist/board/render/clip.d.ts +1 -1
- package/dist/board/render/index.d.ts +2 -2
- package/dist/board/render/index.js +2 -2
- package/dist/board/render/media-interaction.d.ts +2 -2
- package/dist/board/render/media-interaction.js +1 -1
- package/dist/board/render/palette.d.ts +1 -1
- package/dist/board/render/palette.js +1 -1
- package/dist/board/render/renderers/arrow-card-renderer.js +1 -1
- package/dist/board/render/renderers/board-renderer-registry.d.ts +11 -6
- package/dist/board/render/renderers/board-renderer-registry.js +15 -15
- package/dist/board/render/renderers/draw-card-renderer.js +1 -1
- package/dist/board/render/renderers/far-plate.js +1 -1
- package/dist/board/render/renderers/file-card-renderer.d.ts +1 -1
- package/dist/board/render/renderers/file-card-renderer.js +2 -2
- package/dist/board/render/renderers/image-card-renderer.d.ts +1 -1
- package/dist/board/render/renderers/shape-card-renderer.js +1 -1
- package/dist/board/render/renderers/task-card-renderer.js +1 -1
- package/dist/board/render/renderers/text-card-renderer.js +1 -1
- package/dist/board/render/renderers/video-card-renderer.d.ts +1 -1
- package/dist/board/render/stroke.d.ts +1 -1
- package/dist/board/render/stroke.js +1 -1
- package/dist/board/render/text-measurement.js +1 -1
- package/dist/board/replica/index.d.ts +70 -0
- package/dist/board/replica/index.js +276 -0
- package/dist/board/stage/assets.d.ts +41 -0
- package/dist/board/stage/assets.js +350 -0
- package/dist/board/stage/index.d.ts +8 -0
- package/dist/board/stage/index.js +8 -0
- package/dist/board/stage/lru.d.ts +32 -0
- package/dist/board/stage/lru.js +29 -0
- package/dist/board/stage/pointer.d.ts +8 -0
- package/dist/board/stage/pointer.js +33 -0
- package/dist/board/stage/scene.d.ts +85 -0
- package/dist/board/stage/scene.js +512 -0
- package/dist/board/stage/stage.d.ts +66 -0
- package/dist/board/stage/stage.js +568 -0
- package/dist/board/stage/texture-pool.d.ts +24 -0
- package/dist/board/stage/texture-pool.js +154 -0
- package/dist/board/stage/views.d.ts +38 -0
- package/dist/board/stage/views.js +93 -0
- package/dist/chunks/environment.d.ts +25 -1
- package/dist/chunks/http.d.ts +99 -66
- package/dist/chunks/http.js +214 -207
- package/dist/chunks/websocket.d.ts +1 -1
- package/dist/http.d.ts +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +20 -4
- package/dist/media.d.ts +1 -10
- package/dist/media.js +16 -27
- package/dist/protocol/dist/board-patch.js +1 -1
- package/dist/protocol/dist/board.d.ts +28 -2
- package/dist/protocol/dist/board.js +3 -1
- package/dist/protocol/dist/core/media.d.ts +5 -0
- package/dist/protocol/dist/core/media.js +39 -0
- package/dist/protocol/dist/index.d.ts +5 -5
- package/dist/types.d.ts +1 -1
- package/package.json +26 -14
- package/dist/board/core/shape-definition.d.ts +0 -23
- package/dist/board/core/shape-definition.js +0 -45
- package/dist/board/core/shape-types.d.ts +0 -38
- package/dist/board/core/shape-types.js +0 -22
- package/dist/board/headless/index.d.ts +0 -55
- package/dist/board/headless/sketch.d.ts +0 -19
- package/dist/board/index.d.ts +0 -30
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
//#region src/board/editor/snapping.ts
|
|
2
|
+
/** The three snap positions (near/center/far) of a bounds rect on one axis. */
|
|
3
|
+
function axisPositions(rect, axis) {
|
|
4
|
+
const origin = axis === "x" ? rect.x : rect.y;
|
|
5
|
+
const size = axis === "x" ? rect.width : rect.height;
|
|
6
|
+
return [
|
|
7
|
+
origin,
|
|
8
|
+
origin + size / 2,
|
|
9
|
+
origin + size
|
|
10
|
+
];
|
|
11
|
+
}
|
|
12
|
+
function axisExtent(rect, axis) {
|
|
13
|
+
if (axis === "x") return [rect.y, rect.y + rect.height];
|
|
14
|
+
return [rect.x, rect.x + rect.width];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Compute the snap for a moving selection bounds against candidate target
|
|
18
|
+
* bounds. Each axis is solved independently: the nearest matching edge/center
|
|
19
|
+
* within `threshold` wins. Grid lines are included as targets when enabled.
|
|
20
|
+
*/
|
|
21
|
+
function computeSnap(moving, targets, options) {
|
|
22
|
+
const { threshold } = options;
|
|
23
|
+
const guides = [];
|
|
24
|
+
let dx = 0;
|
|
25
|
+
let dy = 0;
|
|
26
|
+
for (const axis of ["x", "y"]) {
|
|
27
|
+
const movingPositions = axisPositions(moving, axis);
|
|
28
|
+
const candidates = [];
|
|
29
|
+
for (const target of targets) {
|
|
30
|
+
const [from, to] = axisExtent(target, axis);
|
|
31
|
+
for (const at of axisPositions(target, axis)) candidates.push({
|
|
32
|
+
at,
|
|
33
|
+
from,
|
|
34
|
+
to
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (options.gridSize && options.gridSize > 0) {
|
|
38
|
+
const [from, to] = axisExtent(moving, axis);
|
|
39
|
+
for (const pos of movingPositions) {
|
|
40
|
+
const grid = Math.round(pos / options.gridSize) * options.gridSize;
|
|
41
|
+
candidates.push({
|
|
42
|
+
at: grid,
|
|
43
|
+
from,
|
|
44
|
+
to
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
let best = null;
|
|
49
|
+
for (const movingPos of movingPositions) for (const candidate of candidates) {
|
|
50
|
+
const delta = candidate.at - movingPos;
|
|
51
|
+
if (Math.abs(delta) > threshold) continue;
|
|
52
|
+
if (!best || Math.abs(delta) < Math.abs(best.delta)) best = {
|
|
53
|
+
delta,
|
|
54
|
+
...candidate
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (!best) continue;
|
|
58
|
+
if (axis === "x") dx = best.delta;
|
|
59
|
+
else dy = best.delta;
|
|
60
|
+
const [movingFrom, movingTo] = axisExtent(moving, axis);
|
|
61
|
+
guides.push({
|
|
62
|
+
axis,
|
|
63
|
+
at: best.at,
|
|
64
|
+
from: Math.min(best.from, movingFrom),
|
|
65
|
+
to: Math.max(best.to, movingTo)
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
dx,
|
|
70
|
+
dy,
|
|
71
|
+
guides
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
export { computeSnap };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BoardTaskSnapshot } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import { BoardFileSnapshotFacts } from "../model/file-snapshot.js";
|
|
3
|
+
import "../model/index.js";
|
|
4
|
+
import { EditorContext } from "./context.js";
|
|
5
|
+
//#region src/board/editor/snapshots.d.ts
|
|
6
|
+
type BoardNaturalSize = {
|
|
7
|
+
id: string;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
};
|
|
11
|
+
declare function createSnapshotsModule(ctx: EditorContext): {
|
|
12
|
+
applyMediaFileChange: (path: string, change: {
|
|
13
|
+
size?: number;
|
|
14
|
+
mtimeMs?: number;
|
|
15
|
+
removed?: boolean;
|
|
16
|
+
}) => void;
|
|
17
|
+
adoptMediaNaturalSizes: (sizes: BoardNaturalSize[]) => void;
|
|
18
|
+
applyFileSnapshots: (snapshots: Array<{
|
|
19
|
+
id: string;
|
|
20
|
+
snapshot: BoardFileSnapshotFacts;
|
|
21
|
+
replace?: boolean;
|
|
22
|
+
}>) => void;
|
|
23
|
+
applyTaskSnapshots: (snapshots: ReadonlyMap<string, BoardTaskSnapshot>) => void;
|
|
24
|
+
};
|
|
25
|
+
type SnapshotsModule = ReturnType<typeof createSnapshotsModule>;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { BoardNaturalSize, SnapshotsModule, createSnapshotsModule };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { mergeFileSnapshot } from "../model/file-preview.js";
|
|
2
|
+
import { featuredTaskArtifact } from "../model/task.js";
|
|
3
|
+
import { sameJson } from "./document-edits.js";
|
|
4
|
+
//#region src/board/editor/snapshots.ts
|
|
5
|
+
function fitHeight(item, natural) {
|
|
6
|
+
const height = item.size.width * natural.height / natural.width;
|
|
7
|
+
if (!Number.isFinite(height) || height <= 0) return null;
|
|
8
|
+
return {
|
|
9
|
+
...item,
|
|
10
|
+
position: {
|
|
11
|
+
x: item.position.x,
|
|
12
|
+
y: item.position.y + (item.size.height - height) / 2
|
|
13
|
+
},
|
|
14
|
+
size: {
|
|
15
|
+
...item.size,
|
|
16
|
+
height
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function createSnapshotsModule(ctx) {
|
|
21
|
+
const { state } = ctx;
|
|
22
|
+
const commit = (changes) => ctx.document.commitItems(changes, false);
|
|
23
|
+
function applyMediaFileChange(path, change) {
|
|
24
|
+
if (change.removed) return;
|
|
25
|
+
const changes = /* @__PURE__ */ new Map();
|
|
26
|
+
for (const id of ctx.query.mediaIdsForPath(path)) {
|
|
27
|
+
const item = state.base.items[id];
|
|
28
|
+
if (!item || item.type !== "image" && item.type !== "video" && item.type !== "audio") continue;
|
|
29
|
+
const snapshot = { ...item.props.snapshot };
|
|
30
|
+
if (change.size !== void 0) snapshot.size = change.size;
|
|
31
|
+
if (change.mtimeMs !== void 0) snapshot.mtimeMs = change.mtimeMs;
|
|
32
|
+
delete snapshot.naturalWidth;
|
|
33
|
+
delete snapshot.naturalHeight;
|
|
34
|
+
if (item.type === "audio") delete snapshot.durationMs;
|
|
35
|
+
if (!sameJson(snapshot, item.props.snapshot ?? {})) changes.set(id, {
|
|
36
|
+
...item,
|
|
37
|
+
props: {
|
|
38
|
+
...item.props,
|
|
39
|
+
snapshot
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
commit(changes);
|
|
44
|
+
}
|
|
45
|
+
function adoptMediaNaturalSizes(sizes) {
|
|
46
|
+
const changes = /* @__PURE__ */ new Map();
|
|
47
|
+
for (const natural of sizes) {
|
|
48
|
+
const item = state.base.items[natural.id];
|
|
49
|
+
if (!item || natural.width <= 0 || natural.height <= 0) continue;
|
|
50
|
+
const dimensions = {
|
|
51
|
+
naturalWidth: natural.width,
|
|
52
|
+
naturalHeight: natural.height
|
|
53
|
+
};
|
|
54
|
+
if (item.type === "task") {
|
|
55
|
+
const artifact = featuredTaskArtifact(item.props.snapshot.artifacts);
|
|
56
|
+
if (artifact?.type !== "image" && artifact?.type !== "video" || artifact.naturalWidth && artifact.naturalHeight) continue;
|
|
57
|
+
const fitted = fitHeight(item, natural);
|
|
58
|
+
if (!fitted) continue;
|
|
59
|
+
const artifacts = item.props.snapshot.artifacts.map((entry) => entry.id === artifact.id ? {
|
|
60
|
+
...entry,
|
|
61
|
+
...dimensions
|
|
62
|
+
} : entry);
|
|
63
|
+
changes.set(natural.id, {
|
|
64
|
+
...fitted,
|
|
65
|
+
props: {
|
|
66
|
+
...item.props,
|
|
67
|
+
snapshot: {
|
|
68
|
+
...item.props.snapshot,
|
|
69
|
+
artifacts
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (item.type !== "image" && item.type !== "video") continue;
|
|
76
|
+
if (item.props.snapshot?.naturalWidth && item.props.snapshot.naturalHeight) continue;
|
|
77
|
+
const fitted = fitHeight(item, natural);
|
|
78
|
+
if (!fitted) continue;
|
|
79
|
+
changes.set(natural.id, {
|
|
80
|
+
...fitted,
|
|
81
|
+
props: {
|
|
82
|
+
...item.props,
|
|
83
|
+
snapshot: {
|
|
84
|
+
...item.props.snapshot,
|
|
85
|
+
...dimensions
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
commit(changes);
|
|
91
|
+
}
|
|
92
|
+
function applyFileSnapshots(snapshots) {
|
|
93
|
+
const changes = /* @__PURE__ */ new Map();
|
|
94
|
+
for (const entry of snapshots) {
|
|
95
|
+
const item = state.base.items[entry.id];
|
|
96
|
+
if (item?.type !== "file") continue;
|
|
97
|
+
const merged = mergeFileSnapshot(item.props.snapshot, entry.snapshot, entry.replace === true);
|
|
98
|
+
if (!sameJson(merged, item.props.snapshot ?? {})) changes.set(entry.id, {
|
|
99
|
+
...item,
|
|
100
|
+
props: {
|
|
101
|
+
...item.props,
|
|
102
|
+
snapshot: merged
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
commit(changes);
|
|
107
|
+
}
|
|
108
|
+
function applyTaskSnapshots(snapshots) {
|
|
109
|
+
if (snapshots.size === 0) return;
|
|
110
|
+
const changes = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const [id, item] of Object.entries(state.base.items)) {
|
|
112
|
+
if (item.type !== "task") continue;
|
|
113
|
+
const snapshot = snapshots.get(item.props.taskRunId);
|
|
114
|
+
if (snapshot && !sameJson(snapshot, item.props.snapshot)) changes.set(id, {
|
|
115
|
+
...item,
|
|
116
|
+
props: {
|
|
117
|
+
...item.props,
|
|
118
|
+
snapshot
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
commit(changes);
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
applyMediaFileChange,
|
|
126
|
+
adoptMediaNaturalSizes,
|
|
127
|
+
applyFileSnapshots,
|
|
128
|
+
applyTaskSnapshots
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
export { createSnapshotsModule };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Point, Rect } from "../model/geometry.js";
|
|
2
|
+
import "../model/index.js";
|
|
3
|
+
//#region src/board/editor/spatial.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* An indexed board item: its stable id, z-order (array index, higher = on
|
|
6
|
+
* top), and an axis-aligned bounding box. The AABB already accounts for
|
|
7
|
+
* rotation (see itemBounds), so the tree operates in world space and never
|
|
8
|
+
* reasons about rotation directly — callers refine rotated hits themselves.
|
|
9
|
+
*/
|
|
10
|
+
type SpatialEntry = {
|
|
11
|
+
id: string;
|
|
12
|
+
order: number;
|
|
13
|
+
rect: Rect;
|
|
14
|
+
};
|
|
15
|
+
type SpatialIndex = {
|
|
16
|
+
/** Rebuild the tree from scratch. */
|
|
17
|
+
rebuild: (entries: SpatialEntry[]) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Apply a set of dirty entries: remove-then-insert each (or remove if
|
|
20
|
+
* absent from the map). Falls back to a full rebuild when the dirty set is
|
|
21
|
+
* large or when new entries fall outside the current root bounds.
|
|
22
|
+
*/
|
|
23
|
+
upsert: (entries: Map<string, SpatialEntry | null>) => void;
|
|
24
|
+
/** Ids whose AABB intersects the range, in no particular order. */
|
|
25
|
+
idsInRect: (range: Rect) => string[];
|
|
26
|
+
/**
|
|
27
|
+
* Ids whose AABB contains the point, ordered topmost-first (highest
|
|
28
|
+
* z-order first) so callers can refine with an exact rotated hit test and
|
|
29
|
+
* take the first match.
|
|
30
|
+
*/
|
|
31
|
+
idsAtPoint: (point: Point) => string[];
|
|
32
|
+
readonly size: number;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* A point quadtree over item bounding boxes. Supports both wholesale rebuild
|
|
36
|
+
* (document load / large batch changes) and dirty-entry upsert (gesture frames)
|
|
37
|
+
* so a drag never pays a full O(n) rebuild every frame.
|
|
38
|
+
*/
|
|
39
|
+
declare function createSpatialIndex(): SpatialIndex;
|
|
40
|
+
//#endregion
|
|
41
|
+
export { SpatialEntry, SpatialIndex, createSpatialIndex };
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { rectContainsPoint, rectsIntersect } from "../model/geometry.js";
|
|
2
|
+
//#region src/board/editor/spatial.ts
|
|
3
|
+
/** Max entries in a leaf before it subdivides. */
|
|
4
|
+
const NODE_CAPACITY = 8;
|
|
5
|
+
/** Deepest the tree may subdivide (bounds get tiny; stop splitting). */
|
|
6
|
+
const MAX_DEPTH = 10;
|
|
7
|
+
/** Subdivision stops once a cell is smaller than this (degenerate bounds). */
|
|
8
|
+
const MIN_CELL_SIZE = 1;
|
|
9
|
+
/**
|
|
10
|
+
* When dirty entries exceed this fraction of the tree (or this absolute count),
|
|
11
|
+
* a full rebuild is cheaper and more compact than many surgical removes.
|
|
12
|
+
*/
|
|
13
|
+
const REBUILD_DIRTY_RATIO = .25;
|
|
14
|
+
const REBUILD_DIRTY_MIN = 32;
|
|
15
|
+
function rectContainsRect(outer, inner) {
|
|
16
|
+
return inner.x >= outer.x && inner.y >= outer.y && inner.x + inner.width <= outer.x + outer.width && inner.y + inner.height <= outer.y + outer.height;
|
|
17
|
+
}
|
|
18
|
+
function quadrantBounds(bounds) {
|
|
19
|
+
const halfW = bounds.width / 2;
|
|
20
|
+
const halfH = bounds.height / 2;
|
|
21
|
+
const { x, y } = bounds;
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
x,
|
|
25
|
+
y,
|
|
26
|
+
width: halfW,
|
|
27
|
+
height: halfH
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
x: x + halfW,
|
|
31
|
+
y,
|
|
32
|
+
width: halfW,
|
|
33
|
+
height: halfH
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
x,
|
|
37
|
+
y: y + halfH,
|
|
38
|
+
width: halfW,
|
|
39
|
+
height: halfH
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
x: x + halfW,
|
|
43
|
+
y: y + halfH,
|
|
44
|
+
width: halfW,
|
|
45
|
+
height: halfH
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
function createNode(bounds, depth) {
|
|
50
|
+
return {
|
|
51
|
+
bounds,
|
|
52
|
+
depth,
|
|
53
|
+
entries: [],
|
|
54
|
+
children: null
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function canSubdivide(node) {
|
|
58
|
+
return node.depth < MAX_DEPTH && node.bounds.width > MIN_CELL_SIZE && node.bounds.height > MIN_CELL_SIZE;
|
|
59
|
+
}
|
|
60
|
+
function insertEntry(node, entry) {
|
|
61
|
+
if (node.children) {
|
|
62
|
+
for (const child of node.children) if (rectContainsRect(child.bounds, entry.rect)) {
|
|
63
|
+
insertEntry(child, entry);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
node.entries.push(entry);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
node.entries.push(entry);
|
|
70
|
+
if (node.entries.length <= NODE_CAPACITY || !canSubdivide(node)) return;
|
|
71
|
+
node.children = quadrantBounds(node.bounds).map((bounds) => createNode(bounds, node.depth + 1));
|
|
72
|
+
const kept = [];
|
|
73
|
+
for (const existing of node.entries) {
|
|
74
|
+
let placed = false;
|
|
75
|
+
for (const child of node.children) if (rectContainsRect(child.bounds, existing.rect)) {
|
|
76
|
+
insertEntry(child, existing);
|
|
77
|
+
placed = true;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
if (!placed) kept.push(existing);
|
|
81
|
+
}
|
|
82
|
+
node.entries = kept;
|
|
83
|
+
}
|
|
84
|
+
/** Remove an entry by id. Returns true if found. */
|
|
85
|
+
function removeEntry(node, id) {
|
|
86
|
+
const index = node.entries.findIndex((entry) => entry.id === id);
|
|
87
|
+
if (index >= 0) {
|
|
88
|
+
node.entries.splice(index, 1);
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
if (!node.children) return false;
|
|
92
|
+
for (const child of node.children) if (removeEntry(child, id)) return true;
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
function queryRect(node, range, out) {
|
|
96
|
+
if (!rectsIntersect(node.bounds, range)) return;
|
|
97
|
+
for (const entry of node.entries) if (rectsIntersect(entry.rect, range)) out.push(entry);
|
|
98
|
+
if (!node.children) return;
|
|
99
|
+
for (const child of node.children) queryRect(child, range, out);
|
|
100
|
+
}
|
|
101
|
+
function queryPoint(node, point, out) {
|
|
102
|
+
if (!rectContainsPoint(node.bounds, point)) return;
|
|
103
|
+
for (const entry of node.entries) if (rectContainsPoint(entry.rect, point)) out.push(entry);
|
|
104
|
+
if (!node.children) return;
|
|
105
|
+
for (const child of node.children) queryPoint(child, point, out);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A point quadtree over item bounding boxes. Supports both wholesale rebuild
|
|
109
|
+
* (document load / large batch changes) and dirty-entry upsert (gesture frames)
|
|
110
|
+
* so a drag never pays a full O(n) rebuild every frame.
|
|
111
|
+
*/
|
|
112
|
+
function createSpatialIndex() {
|
|
113
|
+
let root = null;
|
|
114
|
+
/** Authoritative entry map — source of truth for rebuilds. */
|
|
115
|
+
const byId = /* @__PURE__ */ new Map();
|
|
116
|
+
function rebuildFromMap() {
|
|
117
|
+
const entries = [...byId.values()];
|
|
118
|
+
if (entries.length === 0) {
|
|
119
|
+
root = null;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
123
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
124
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
125
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
126
|
+
for (const entry of entries) {
|
|
127
|
+
minX = Math.min(minX, entry.rect.x);
|
|
128
|
+
minY = Math.min(minY, entry.rect.y);
|
|
129
|
+
maxX = Math.max(maxX, entry.rect.x + entry.rect.width);
|
|
130
|
+
maxY = Math.max(maxY, entry.rect.y + entry.rect.height);
|
|
131
|
+
}
|
|
132
|
+
const width = Math.max(maxX - minX, MIN_CELL_SIZE);
|
|
133
|
+
const height = Math.max(maxY - minY, MIN_CELL_SIZE);
|
|
134
|
+
const pad = Math.max(width, height) * .05;
|
|
135
|
+
root = createNode({
|
|
136
|
+
x: minX - pad,
|
|
137
|
+
y: minY - pad,
|
|
138
|
+
width: width + pad * 2,
|
|
139
|
+
height: height + pad * 2
|
|
140
|
+
}, 0);
|
|
141
|
+
for (const entry of entries) insertEntry(root, entry);
|
|
142
|
+
}
|
|
143
|
+
function rebuild(entries) {
|
|
144
|
+
byId.clear();
|
|
145
|
+
for (const entry of entries) byId.set(entry.id, entry);
|
|
146
|
+
rebuildFromMap();
|
|
147
|
+
}
|
|
148
|
+
function upsert(entries) {
|
|
149
|
+
if (entries.size === 0) return;
|
|
150
|
+
const dirtyCount = entries.size;
|
|
151
|
+
const total = Math.max(byId.size, 1);
|
|
152
|
+
const shouldRebuild = dirtyCount >= REBUILD_DIRTY_MIN && dirtyCount / total >= REBUILD_DIRTY_RATIO;
|
|
153
|
+
let expandsRoot = false;
|
|
154
|
+
for (const [id, entry] of entries) if (entry) {
|
|
155
|
+
byId.set(id, entry);
|
|
156
|
+
if (root && !rectContainsRect(root.bounds, entry.rect)) expandsRoot = true;
|
|
157
|
+
} else byId.delete(id);
|
|
158
|
+
if (!root || shouldRebuild || expandsRoot) {
|
|
159
|
+
rebuildFromMap();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
for (const [id, entry] of entries) {
|
|
163
|
+
removeEntry(root, id);
|
|
164
|
+
if (entry) insertEntry(root, entry);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function idsInRect(range) {
|
|
168
|
+
if (!root) return [];
|
|
169
|
+
const hits = [];
|
|
170
|
+
queryRect(root, range, hits);
|
|
171
|
+
return hits.map((entry) => entry.id);
|
|
172
|
+
}
|
|
173
|
+
function idsAtPoint(point) {
|
|
174
|
+
if (!root) return [];
|
|
175
|
+
const hits = [];
|
|
176
|
+
queryPoint(root, point, hits);
|
|
177
|
+
hits.sort((a, b) => b.order - a.order);
|
|
178
|
+
return hits.map((entry) => entry.id);
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
rebuild,
|
|
182
|
+
upsert,
|
|
183
|
+
idsInRect,
|
|
184
|
+
idsAtPoint,
|
|
185
|
+
get size() {
|
|
186
|
+
return byId.size;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
export { createSpatialIndex };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/board/editor/state.d.ts
|
|
2
|
+
type BoardEditorChannel = "document" | "scene" | "selection" | "camera" | "tool" | "interaction" | "hover" | "editing" | "playback" | "history" | "status";
|
|
3
|
+
type BoardEditorListener = () => void;
|
|
4
|
+
type BoardEditorChannels = {
|
|
5
|
+
emit(channel: BoardEditorChannel): void;
|
|
6
|
+
subscribe(channels: BoardEditorChannel | readonly BoardEditorChannel[], listener: BoardEditorListener): () => void;
|
|
7
|
+
};
|
|
8
|
+
declare function createChannels(): BoardEditorChannels;
|
|
9
|
+
declare function observable<T extends Record<string, unknown>>(initial: T, channelOf: { readonly [K in keyof T]: BoardEditorChannel; }, channels: BoardEditorChannels): T;
|
|
10
|
+
declare function memo<const D extends readonly unknown[], R>(inputs: () => D, compute: (...inputs: D) => R): () => R;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { BoardEditorChannel, BoardEditorChannels, BoardEditorListener, createChannels, memo, observable };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/board/editor/state.ts
|
|
2
|
+
function createChannels() {
|
|
3
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
4
|
+
return {
|
|
5
|
+
emit(channel) {
|
|
6
|
+
const subscribed = listeners.get(channel);
|
|
7
|
+
if (subscribed) for (const listener of [...subscribed]) listener();
|
|
8
|
+
},
|
|
9
|
+
subscribe(channels, listener) {
|
|
10
|
+
const list = typeof channels === "string" ? [channels] : channels;
|
|
11
|
+
for (const channel of list) {
|
|
12
|
+
const set = listeners.get(channel) ?? /* @__PURE__ */ new Set();
|
|
13
|
+
set.add(listener);
|
|
14
|
+
listeners.set(channel, set);
|
|
15
|
+
}
|
|
16
|
+
return () => {
|
|
17
|
+
for (const channel of list) listeners.get(channel)?.delete(listener);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function observable(initial, channelOf, channels) {
|
|
23
|
+
const values = { ...initial };
|
|
24
|
+
const state = {};
|
|
25
|
+
for (const key of Object.keys(initial)) Object.defineProperty(state, key, {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get: () => values[key],
|
|
28
|
+
set: (value) => {
|
|
29
|
+
if (Object.is(values[key], value)) return;
|
|
30
|
+
values[key] = value;
|
|
31
|
+
channels.emit(channelOf[key]);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return state;
|
|
35
|
+
}
|
|
36
|
+
function memo(inputs, compute) {
|
|
37
|
+
let previous = null;
|
|
38
|
+
let value;
|
|
39
|
+
return () => {
|
|
40
|
+
const next = inputs();
|
|
41
|
+
if (previous === null || next.length !== previous.length || next.some((input, index) => !Object.is(input, previous?.[index]))) {
|
|
42
|
+
previous = next;
|
|
43
|
+
value = compute(...next);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { createChannels, memo, observable };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/board/editor/tool.d.ts
|
|
2
|
+
type BoardToolId = "select" | "hand" | "text" | "shape" | "draw" | "arrow" | "frame";
|
|
3
|
+
declare const BOARD_HAND_TAP_SLOP = 8;
|
|
4
|
+
declare function defaultBoardTool(isMobile: boolean): BoardToolId;
|
|
5
|
+
declare function canTapSelectWithHand(pointerType: string): boolean;
|
|
6
|
+
declare function isWithinHandTapSlop(dx: number, dy: number): boolean;
|
|
7
|
+
declare function isContinuousBoardTool(tool: BoardToolId): boolean;
|
|
8
|
+
declare function isCreationBoardTool(tool: BoardToolId): boolean;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { BOARD_HAND_TAP_SLOP, BoardToolId, canTapSelectWithHand, defaultBoardTool, isContinuousBoardTool, isCreationBoardTool, isWithinHandTapSlop };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/board/editor/tool.ts
|
|
2
|
+
const BOARD_HAND_TAP_SLOP = 8;
|
|
3
|
+
function defaultBoardTool(isMobile) {
|
|
4
|
+
return isMobile ? "hand" : "select";
|
|
5
|
+
}
|
|
6
|
+
function canTapSelectWithHand(pointerType) {
|
|
7
|
+
return pointerType === "touch" || pointerType === "pen";
|
|
8
|
+
}
|
|
9
|
+
function isWithinHandTapSlop(dx, dy) {
|
|
10
|
+
return Number.isFinite(dx) && Number.isFinite(dy) && Math.hypot(dx, dy) <= 8;
|
|
11
|
+
}
|
|
12
|
+
function isContinuousBoardTool(tool) {
|
|
13
|
+
return tool === "draw";
|
|
14
|
+
}
|
|
15
|
+
function isCreationBoardTool(tool) {
|
|
16
|
+
return tool === "text" || tool === "shape" || tool === "draw" || tool === "arrow" || tool === "frame";
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { BOARD_HAND_TAP_SLOP, canTapSelectWithHand, defaultBoardTool, isContinuousBoardTool, isCreationBoardTool, isWithinHandTapSlop };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/board/editor/wheel.ts
|
|
2
|
+
const WHEEL_ZOOM_INTENSITY = .012;
|
|
3
|
+
const MAX_WHEEL_ZOOM_FACTOR = 1.35;
|
|
4
|
+
const LINE_DELTA_PX = 16;
|
|
5
|
+
const PAGE_DELTA_PX = 80;
|
|
6
|
+
function normalizeWheelDelta(delta, deltaMode = 0) {
|
|
7
|
+
if (!Number.isFinite(delta)) return 0;
|
|
8
|
+
if (deltaMode === 1) return delta * LINE_DELTA_PX;
|
|
9
|
+
if (deltaMode === 2) return delta * PAGE_DELTA_PX;
|
|
10
|
+
return delta;
|
|
11
|
+
}
|
|
12
|
+
function wheelZoomFactor(deltaY, deltaMode = 0) {
|
|
13
|
+
const factor = Math.exp(-normalizeWheelDelta(deltaY, deltaMode) * WHEEL_ZOOM_INTENSITY);
|
|
14
|
+
return Math.min(MAX_WHEEL_ZOOM_FACTOR, Math.max(1 / MAX_WHEEL_ZOOM_FACTOR, factor));
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { normalizeWheelDelta, wheelZoomFactor };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { BoardDocument } from "../../protocol/dist/board-model.js";
|
|
2
2
|
import "../../protocol/dist/index.js";
|
|
3
|
-
import { BoardSceneItem } from "../
|
|
4
|
-
import { BoardExportPlan, BoardExportPlanInput, BoardExportRegion } from "../
|
|
5
|
-
import { BoardShapeColors } from "../
|
|
6
|
-
import { BoardRenderPalette, BoardSketchHost } from "../render/renderers/board-renderer-registry.js";
|
|
3
|
+
import { BoardSceneItem } from "../model/scene.js";
|
|
4
|
+
import { BoardExportPlan, BoardExportPlanInput, BoardExportRegion } from "../model/export-plan.js";
|
|
5
|
+
import { BoardShapeColors } from "../model/palette.js";
|
|
6
|
+
import { BoardCardRendererResolver, BoardRenderPalette, BoardSketchHost } from "../render/renderers/board-renderer-registry.js";
|
|
7
7
|
import "../render/index.js";
|
|
8
8
|
import { BoardExportScene, BoardExportSceneInput, createBoardExportScene } from "./scene.js";
|
|
9
9
|
import { ICanvas, Renderer, Texture } from "pixi.js";
|
|
@@ -24,6 +24,7 @@ type BoardExportOptions = {
|
|
|
24
24
|
textures?: Map<string, Texture>;
|
|
25
25
|
sketches?: BoardSketchHost;
|
|
26
26
|
assetKey?: (item: BoardSceneItem) => string | null;
|
|
27
|
+
renderers?: BoardCardRendererResolver;
|
|
27
28
|
backgroundImage?: {
|
|
28
29
|
texture: Texture;
|
|
29
30
|
fit: "cover" | "contain" | "repeat";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { boardDocumentAt } from "../animation.js";
|
|
2
|
-
import { normalizeBoardDocument, planBoardExport } from "../
|
|
3
|
-
import { buildBoardScene } from "../
|
|
1
|
+
import { boardDocumentAt } from "../model/animation.js";
|
|
2
|
+
import { normalizeBoardDocument, planBoardExport } from "../model/export-plan.js";
|
|
3
|
+
import { buildBoardScene } from "../model/scene.js";
|
|
4
4
|
import { parseBoardCssColor } from "../render/css-color.js";
|
|
5
5
|
import { defaultBoardPalette } from "../render/palette.js";
|
|
6
6
|
import { ensureBoardTextMeasurement } from "../render/text-measurement.js";
|
|
@@ -46,6 +46,7 @@ function renderBoardExport(renderer, input, options = {}) {
|
|
|
46
46
|
textures: options.textures,
|
|
47
47
|
sketches: options.sketches,
|
|
48
48
|
assetKey: options.assetKey,
|
|
49
|
+
renderers: options.renderers,
|
|
49
50
|
background: resolveBackground(options.background, palette, document, colorScheme),
|
|
50
51
|
backgroundImage: options.backgroundImage
|
|
51
52
|
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { BoardDocument } from "../../../protocol/dist/board-model.js";
|
|
2
|
+
import "../../../protocol/dist/index.js";
|
|
3
|
+
import { BoardSketchHost } from "../../render/renderers/board-renderer-registry.js";
|
|
4
|
+
import { BoardExportOptions, BoardExportResult } from "../index.js";
|
|
5
|
+
import { createNodeBoardSketchHost } from "./sketch.js";
|
|
6
|
+
import { Renderer } from "pixi.js";
|
|
7
|
+
//#region src/board/export/node/index.d.ts
|
|
8
|
+
type NodeCanvasModule = {
|
|
9
|
+
createCanvas: (width: number, height: number) => unknown;
|
|
10
|
+
Image: new () => unknown;
|
|
11
|
+
GlobalFonts: {
|
|
12
|
+
registerFromPath: (path: string, name?: string) => unknown;
|
|
13
|
+
has: (name: string) => boolean;
|
|
14
|
+
setAlias?: (fontName: string, alias: string) => boolean;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
type BoardNodeFont = {
|
|
18
|
+
path: string;
|
|
19
|
+
family?: string;
|
|
20
|
+
};
|
|
21
|
+
type BoardNodeRendererOptions = {
|
|
22
|
+
fonts?: BoardNodeFont[];
|
|
23
|
+
canvasModule?: NodeCanvasModule;
|
|
24
|
+
};
|
|
25
|
+
type BoardNodeTexture = {
|
|
26
|
+
readonly __boardTexture: unique symbol;
|
|
27
|
+
};
|
|
28
|
+
type BoardNodeRenderer = {
|
|
29
|
+
renderer: Renderer;
|
|
30
|
+
canvasModule: NodeCanvasModule;
|
|
31
|
+
decodeImage: (bytes: Uint8Array, mimeType?: string) => Promise<BoardNodeTexture>;
|
|
32
|
+
destroy: () => void;
|
|
33
|
+
};
|
|
34
|
+
declare function createNodeBoardRenderer(options?: BoardNodeRendererOptions): Promise<BoardNodeRenderer>;
|
|
35
|
+
type BoardNodeExportFormat = "png" | "jpeg" | "webp";
|
|
36
|
+
type BoardNodeExportOptions = Omit<BoardExportOptions, "textures" | "backgroundImage"> & {
|
|
37
|
+
textures?: Map<string, BoardNodeTexture>;
|
|
38
|
+
backgroundImage?: {
|
|
39
|
+
texture: BoardNodeTexture;
|
|
40
|
+
fit: "cover" | "contain" | "repeat";
|
|
41
|
+
position: "center" | "top" | "bottom" | "left" | "right";
|
|
42
|
+
opacity: number;
|
|
43
|
+
};
|
|
44
|
+
sketches?: BoardSketchHost;
|
|
45
|
+
format?: BoardNodeExportFormat;
|
|
46
|
+
quality?: number;
|
|
47
|
+
};
|
|
48
|
+
type BoardNodeExportResult = Omit<BoardExportResult, "canvas"> & {
|
|
49
|
+
bytes: Uint8Array;
|
|
50
|
+
format: BoardNodeExportFormat;
|
|
51
|
+
};
|
|
52
|
+
declare function boardImageMimeType(format: BoardNodeExportFormat): string;
|
|
53
|
+
declare function exportBoardImageBytes(renderer: BoardNodeRenderer, document: BoardDocument, options?: BoardNodeExportOptions): BoardNodeExportResult | null;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { BoardNodeExportFormat, BoardNodeExportOptions, BoardNodeExportResult, BoardNodeFont, BoardNodeRenderer, BoardNodeRendererOptions, BoardNodeTexture, NodeCanvasModule, boardImageMimeType, createNodeBoardRenderer, createNodeBoardSketchHost, exportBoardImageBytes };
|