@neta-art/cohub 3.0.0 → 3.2.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 +33 -2
- package/dist/{board.d.ts → board/animation.d.ts} +4 -2
- package/dist/{chunks/board.js → board/animation.js} +5 -44
- package/dist/board/codec.d.ts +25 -0
- package/dist/board/codec.js +252 -0
- package/dist/board/core/bindings.d.ts +45 -0
- package/dist/board/core/bindings.js +162 -0
- package/dist/board/core/draw-geometry.d.ts +31 -0
- package/dist/board/core/draw-geometry.js +183 -0
- package/dist/board/core/export-assets.d.ts +14 -0
- package/dist/board/core/export-assets.js +22 -0
- package/dist/board/core/export-plan.d.ts +84 -0
- package/dist/board/core/export-plan.js +128 -0
- package/dist/board/core/file-preview.d.ts +176 -0
- package/dist/board/core/file-preview.js +271 -0
- package/dist/board/core/palette.d.ts +42 -0
- package/dist/board/core/palette.js +138 -0
- package/dist/board/core/shape-definition.d.ts +35 -0
- package/dist/board/core/shape-definition.js +64 -0
- package/dist/board/core/shape-types.d.ts +144 -0
- package/dist/board/core/shape-types.js +27 -0
- package/dist/board/core/text-metrics.d.ts +18 -0
- package/dist/board/core/text-metrics.js +43 -0
- package/dist/board/export/index.d.ts +56 -0
- package/dist/board/export/index.js +86 -0
- package/dist/board/export/scene.d.ts +35 -0
- package/dist/board/export/scene.js +69 -0
- package/dist/board/geometry.d.ts +131 -0
- package/dist/board/geometry.js +468 -0
- package/dist/board/headless/index.d.ts +69 -0
- package/dist/board/headless/index.js +150 -0
- package/dist/board/image-key.d.ts +19 -0
- package/dist/board/image-key.js +34 -0
- package/dist/board/index.d.ts +16 -0
- package/dist/board/index.js +16 -0
- package/dist/board/render/index.d.ts +6 -0
- package/dist/board/render/index.js +6 -0
- package/dist/board/render/palette.d.ts +5 -0
- package/dist/board/render/palette.js +30 -0
- package/dist/board/render/renderers/arrow-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/arrow-card-renderer.js +157 -0
- package/dist/board/render/renderers/base-card-renderer.d.ts +46 -0
- package/dist/board/render/renderers/base-card-renderer.js +153 -0
- package/dist/board/render/renderers/board-renderer-registry.d.ts +85 -0
- package/dist/board/render/renderers/board-renderer-registry.js +43 -0
- package/dist/board/render/renderers/draw-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/draw-card-renderer.js +82 -0
- package/dist/board/render/renderers/far-plate.d.ts +39 -0
- package/dist/board/render/renderers/far-plate.js +92 -0
- package/dist/board/render/renderers/file-card-renderer.d.ts +33 -0
- package/dist/board/render/renderers/file-card-renderer.js +371 -0
- package/dist/board/render/renderers/frame-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/frame-card-renderer.js +99 -0
- package/dist/board/render/renderers/geo-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/geo-card-renderer.js +128 -0
- package/dist/board/render/renderers/image-card-renderer.d.ts +8 -0
- package/dist/board/render/renderers/image-card-renderer.js +138 -0
- package/dist/board/render/renderers/note-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/note-card-renderer.js +112 -0
- package/dist/board/render/renderers/text-card-renderer.d.ts +7 -0
- package/dist/board/render/renderers/text-card-renderer.js +94 -0
- package/dist/board/render/renderers/unknown-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/unknown-card-renderer.js +102 -0
- package/dist/board/render/renderers/video-card-renderer.d.ts +7 -0
- package/dist/board/render/renderers/video-card-renderer.js +146 -0
- package/dist/board/render/text-measurement.d.ts +30 -0
- package/dist/board/render/text-measurement.js +57 -0
- package/dist/board/render/text-resolution.d.ts +16 -0
- package/dist/board/render/text-resolution.js +43 -0
- package/dist/board/render/themes/board-theme-registry.d.ts +20 -0
- package/dist/board/render/themes/board-theme-registry.js +13 -0
- package/dist/board/render/themes/clean-theme.d.ts +5 -0
- package/dist/board/render/themes/clean-theme.js +102 -0
- package/dist/chunks/http.d.ts +10 -40
- package/dist/chunks/http.js +26 -4
- package/dist/chunks/transport.js +4 -2
- package/dist/chunks/websocket.d.ts +832 -21
- package/dist/chunks/websocket.js +19 -2
- package/dist/http.d.ts +2 -3
- package/dist/index.d.ts +100 -5
- package/dist/index.js +391 -1
- package/dist/protocol/dist/board-constants.d.ts +23 -0
- package/dist/protocol/dist/board-constants.js +60 -0
- package/dist/protocol/dist/board-document.d.ts +1124 -0
- package/dist/protocol/dist/board-document.js +331 -0
- package/dist/protocol/dist/board.d.ts +186 -0
- package/dist/protocol/dist/board.js +207 -0
- package/dist/protocol/dist/index.d.ts +5 -0
- package/dist/protocol/dist/index.js +5 -0
- package/dist/protocol/dist/provenance.js +12 -0
- package/dist/protocol/dist/realtime/board-awareness.d.ts +1 -0
- package/dist/protocol/dist/realtime/board-awareness.js +117 -0
- package/dist/protocol/dist/realtime/types.d.ts +2 -0
- package/package.json +39 -4
- package/dist/board.js +0 -2
- package/dist/chunks/board.d.ts +0 -2589
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/board/core/shape-types.ts
|
|
2
|
+
function resizeModeForCapabilities(capabilities) {
|
|
3
|
+
if (!capabilities.canResize) return "none";
|
|
4
|
+
return capabilities.aspectLocked ? "uniform" : "free";
|
|
5
|
+
}
|
|
6
|
+
const FULL_CAPABILITIES = {
|
|
7
|
+
canMove: true,
|
|
8
|
+
canResize: true,
|
|
9
|
+
aspectLocked: false,
|
|
10
|
+
canRotate: true,
|
|
11
|
+
canEdit: false,
|
|
12
|
+
canBind: true,
|
|
13
|
+
canSnap: true,
|
|
14
|
+
canLock: true
|
|
15
|
+
};
|
|
16
|
+
const GEO_KINDS = [
|
|
17
|
+
"rectangle",
|
|
18
|
+
"rounded",
|
|
19
|
+
"ellipse",
|
|
20
|
+
"diamond",
|
|
21
|
+
"triangle"
|
|
22
|
+
];
|
|
23
|
+
function isGeoKind(value) {
|
|
24
|
+
return typeof value === "string" && GEO_KINDS.includes(value);
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { FULL_CAPABILITIES, GEO_KINDS, isGeoKind, resizeModeForCapabilities };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/board/core/text-metrics.d.ts
|
|
2
|
+
declare const TEXT_FONT_FAMILY = "Geist";
|
|
3
|
+
declare const TEXT_FONT_SIZE = 18;
|
|
4
|
+
declare const TEXT_LINE_HEIGHT = 24;
|
|
5
|
+
declare const TEXT_MIN_FONT_SIZE = 2;
|
|
6
|
+
declare const TEXT_MAX_FONT_SIZE = 512;
|
|
7
|
+
type BoardTextMeasurer = (text: string, fontSize: number) => number | null;
|
|
8
|
+
/** Install the renderer's text measurer, or clear it for environment-free fallback metrics. */
|
|
9
|
+
declare function setBoardTextMeasurer(measurer?: BoardTextMeasurer): void;
|
|
10
|
+
declare function clampBoardTextFontSize(fontSize: number): number;
|
|
11
|
+
declare function boardTextLineHeight(fontSize: number): number;
|
|
12
|
+
/** Measure unwrapped plain text into scalable world-space bounds. */
|
|
13
|
+
declare function measureBoardText(text: string, fontSize?: number): {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { TEXT_FONT_FAMILY, TEXT_FONT_SIZE, TEXT_LINE_HEIGHT, TEXT_MAX_FONT_SIZE, TEXT_MIN_FONT_SIZE, boardTextLineHeight, clampBoardTextFontSize, measureBoardText, setBoardTextMeasurer };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { BOARD_TEXT_FONT_FAMILY } from "../../protocol/dist/board-constants.js";
|
|
2
|
+
//#region src/board/core/text-metrics.ts
|
|
3
|
+
const TEXT_FONT_FAMILY = BOARD_TEXT_FONT_FAMILY;
|
|
4
|
+
const TEXT_FONT_SIZE = 18;
|
|
5
|
+
const TEXT_LINE_HEIGHT = 24;
|
|
6
|
+
const TEXT_MIN_FONT_SIZE = 2;
|
|
7
|
+
const TEXT_MAX_FONT_SIZE = 512;
|
|
8
|
+
const TEXT_LINE_HEIGHT_RATIO = TEXT_LINE_HEIGHT / TEXT_FONT_SIZE;
|
|
9
|
+
const TEXT_MIN_WIDTH_RATIO = 16 / TEXT_FONT_SIZE;
|
|
10
|
+
const TEXT_HORIZONTAL_PADDING_RATIO = 2 / TEXT_FONT_SIZE;
|
|
11
|
+
const FALLBACK_CHARACTER_RATIO = .52;
|
|
12
|
+
let textMeasurer;
|
|
13
|
+
/** Install the renderer's text measurer, or clear it for environment-free fallback metrics. */
|
|
14
|
+
function setBoardTextMeasurer(measurer) {
|
|
15
|
+
textMeasurer = measurer;
|
|
16
|
+
}
|
|
17
|
+
function clampBoardTextFontSize(fontSize) {
|
|
18
|
+
return Math.min(TEXT_MAX_FONT_SIZE, Math.max(TEXT_MIN_FONT_SIZE, fontSize));
|
|
19
|
+
}
|
|
20
|
+
function boardTextLineHeight(fontSize) {
|
|
21
|
+
return clampBoardTextFontSize(fontSize) * TEXT_LINE_HEIGHT_RATIO;
|
|
22
|
+
}
|
|
23
|
+
/** Measure unwrapped plain text into scalable world-space bounds. */
|
|
24
|
+
function measureBoardText(text, fontSize = TEXT_FONT_SIZE) {
|
|
25
|
+
const size = clampBoardTextFontSize(fontSize);
|
|
26
|
+
const lines = (text || " ").split("\n");
|
|
27
|
+
const minWidth = size * TEXT_MIN_WIDTH_RATIO;
|
|
28
|
+
const horizontalPadding = size * TEXT_HORIZONTAL_PADDING_RATIO;
|
|
29
|
+
const lineHeight = boardTextLineHeight(size);
|
|
30
|
+
const characterWidth = size * FALLBACK_CHARACTER_RATIO;
|
|
31
|
+
let width = minWidth;
|
|
32
|
+
for (const line of lines) {
|
|
33
|
+
const value = line || " ";
|
|
34
|
+
const measured = textMeasurer?.(value, size);
|
|
35
|
+
width = Math.max(width, (measured ?? Math.max(1, value.length) * characterWidth) + horizontalPadding);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
width,
|
|
39
|
+
height: Math.max(lineHeight, lines.length * lineHeight)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { TEXT_FONT_FAMILY, TEXT_FONT_SIZE, TEXT_LINE_HEIGHT, TEXT_MAX_FONT_SIZE, TEXT_MIN_FONT_SIZE, boardTextLineHeight, clampBoardTextFontSize, measureBoardText, setBoardTextMeasurer };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { BoardDocument, BoardItem } from "../../protocol/dist/board-document.js";
|
|
2
|
+
import { BoardExportPlan, BoardExportPlanInput, BoardExportRegion } from "../core/export-plan.js";
|
|
3
|
+
import { BoardShapeColors } from "../core/palette.js";
|
|
4
|
+
import { BoardRenderPalette } from "../render/renderers/board-renderer-registry.js";
|
|
5
|
+
import "../render/index.js";
|
|
6
|
+
import { BoardExportScene, BoardExportSceneInput, createBoardExportScene } from "./scene.js";
|
|
7
|
+
import { ICanvas, Renderer, Texture } from "pixi.js";
|
|
8
|
+
//#region src/board/export/index.d.ts
|
|
9
|
+
/** Paper behind the content: the theme's page color, or none at all. */
|
|
10
|
+
type BoardExportBackground = "paper" | "transparent" | number;
|
|
11
|
+
type BoardExportOptions = {
|
|
12
|
+
region?: BoardExportRegion;
|
|
13
|
+
/** Output pixels per world unit. Defaults to 2. */
|
|
14
|
+
scale?: number;
|
|
15
|
+
/** World-space padding. Frame and rect regions default to 0, others to 32. */
|
|
16
|
+
padding?: number;
|
|
17
|
+
colorScheme?: "dark" | "light";
|
|
18
|
+
/** Defaults to "paper" — a transparent PNG surprises people who paste it. */
|
|
19
|
+
background?: BoardExportBackground;
|
|
20
|
+
palette?: Partial<BoardRenderPalette>;
|
|
21
|
+
colors?: BoardShapeColors;
|
|
22
|
+
/** Resolved textures by preview key; unresolved keys become placeholders. */
|
|
23
|
+
textures?: Map<string, Texture>;
|
|
24
|
+
/** Preview-key strategy supplied by the host; defaults to still images only. */
|
|
25
|
+
assetKey?: (item: BoardItem) => string | null;
|
|
26
|
+
maxEdge?: number;
|
|
27
|
+
maxPixels?: number;
|
|
28
|
+
};
|
|
29
|
+
type BoardExportWarning = {
|
|
30
|
+
kind: "scale-clamped";
|
|
31
|
+
requested: number;
|
|
32
|
+
applied: number;
|
|
33
|
+
} | {
|
|
34
|
+
kind: "images-missing";
|
|
35
|
+
keys: string[];
|
|
36
|
+
} | {
|
|
37
|
+
kind: "many-items";
|
|
38
|
+
count: number;
|
|
39
|
+
};
|
|
40
|
+
type BoardExportResult = {
|
|
41
|
+
canvas: ICanvas;
|
|
42
|
+
plan: BoardExportPlan;
|
|
43
|
+
warnings: BoardExportWarning[];
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Capture a board region as a canvas.
|
|
47
|
+
*
|
|
48
|
+
* Returns null when the region is empty — an empty selection is a no-op, not a
|
|
49
|
+
* failure. The scene is always destroyed, so a throwing renderer cannot leak
|
|
50
|
+
* GPU resources.
|
|
51
|
+
*/
|
|
52
|
+
declare function renderBoardExport(renderer: Renderer, input: BoardDocument, options?: BoardExportOptions): BoardExportResult | null;
|
|
53
|
+
/** Human-readable form of a warning, shared by the CLI and the web UI. */
|
|
54
|
+
declare function describeBoardExportWarning(warning: BoardExportWarning): string;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { BoardExportBackground, BoardExportOptions, type BoardExportPlan, type BoardExportPlanInput, type BoardExportRegion, BoardExportResult, type BoardExportScene, type BoardExportSceneInput, BoardExportWarning, createBoardExportScene, describeBoardExportWarning, renderBoardExport };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { normalizeBoardDocument, planBoardExport } from "../core/export-plan.js";
|
|
2
|
+
import { defaultBoardPalette } from "../render/palette.js";
|
|
3
|
+
import { createBoardExportScene } from "./scene.js";
|
|
4
|
+
import { Rectangle } from "pixi.js";
|
|
5
|
+
//#region src/board/export/index.ts
|
|
6
|
+
function resolveBackground(background, palette) {
|
|
7
|
+
if (background === "transparent") return null;
|
|
8
|
+
if (typeof background === "number") return background;
|
|
9
|
+
return palette.bg;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Capture a board region as a canvas.
|
|
13
|
+
*
|
|
14
|
+
* Returns null when the region is empty — an empty selection is a no-op, not a
|
|
15
|
+
* failure. The scene is always destroyed, so a throwing renderer cannot leak
|
|
16
|
+
* GPU resources.
|
|
17
|
+
*/
|
|
18
|
+
function renderBoardExport(renderer, input, options = {}) {
|
|
19
|
+
const document = normalizeBoardDocument(input);
|
|
20
|
+
const plan = planBoardExport({
|
|
21
|
+
document,
|
|
22
|
+
region: options.region ?? { kind: "all" },
|
|
23
|
+
scale: options.scale,
|
|
24
|
+
padding: options.padding,
|
|
25
|
+
maxEdge: options.maxEdge,
|
|
26
|
+
maxPixels: options.maxPixels
|
|
27
|
+
});
|
|
28
|
+
if (!plan) return null;
|
|
29
|
+
const colorScheme = options.colorScheme ?? "dark";
|
|
30
|
+
const palette = {
|
|
31
|
+
...defaultBoardPalette(colorScheme),
|
|
32
|
+
...options.palette
|
|
33
|
+
};
|
|
34
|
+
const scene = createBoardExportScene({
|
|
35
|
+
document,
|
|
36
|
+
items: plan.items,
|
|
37
|
+
world: plan.world,
|
|
38
|
+
scale: plan.scale,
|
|
39
|
+
colorScheme,
|
|
40
|
+
palette,
|
|
41
|
+
colors: options.colors,
|
|
42
|
+
textures: options.textures,
|
|
43
|
+
assetKey: options.assetKey,
|
|
44
|
+
background: resolveBackground(options.background, palette)
|
|
45
|
+
});
|
|
46
|
+
try {
|
|
47
|
+
const canvas = renderer.extract.canvas({
|
|
48
|
+
target: scene.root,
|
|
49
|
+
frame: new Rectangle(0, 0, plan.width, plan.height),
|
|
50
|
+
resolution: 1,
|
|
51
|
+
antialias: true,
|
|
52
|
+
clearColor: options.background === "transparent" ? void 0 : palette.bg
|
|
53
|
+
});
|
|
54
|
+
const warnings = [];
|
|
55
|
+
if (plan.clamped) warnings.push({
|
|
56
|
+
kind: "scale-clamped",
|
|
57
|
+
requested: plan.requestedScale,
|
|
58
|
+
applied: plan.scale
|
|
59
|
+
});
|
|
60
|
+
if (scene.missingImageKeys.length > 0) warnings.push({
|
|
61
|
+
kind: "images-missing",
|
|
62
|
+
keys: scene.missingImageKeys
|
|
63
|
+
});
|
|
64
|
+
if (plan.items.length > 2e3) warnings.push({
|
|
65
|
+
kind: "many-items",
|
|
66
|
+
count: plan.items.length
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
canvas,
|
|
70
|
+
plan,
|
|
71
|
+
warnings
|
|
72
|
+
};
|
|
73
|
+
} finally {
|
|
74
|
+
scene.destroy();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Human-readable form of a warning, shared by the CLI and the web UI. */
|
|
78
|
+
function describeBoardExportWarning(warning) {
|
|
79
|
+
switch (warning.kind) {
|
|
80
|
+
case "scale-clamped": return `Scale reduced from ${warning.requested}x to ${warning.applied.toFixed(2)}x to stay within the size limit.`;
|
|
81
|
+
case "images-missing": return warning.keys.length === 1 ? "1 image could not be loaded and was drawn as a placeholder." : `${warning.keys.length} images could not be loaded and were drawn as placeholders.`;
|
|
82
|
+
case "many-items": return `${warning.count} items exported; this may take a moment.`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { createBoardExportScene, describeBoardExportWarning, renderBoardExport };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { BoardDocument, BoardItem } from "../../protocol/dist/board-document.js";
|
|
2
|
+
import { Rect } from "../geometry.js";
|
|
3
|
+
import { BoardShapeColors } from "../core/palette.js";
|
|
4
|
+
import { BoardRenderPalette } from "../render/renderers/board-renderer-registry.js";
|
|
5
|
+
import "../render/index.js";
|
|
6
|
+
import { Container, Texture } from "pixi.js";
|
|
7
|
+
//#region src/board/export/scene.d.ts
|
|
8
|
+
type BoardExportSceneInput = {
|
|
9
|
+
document: BoardDocument;
|
|
10
|
+
/** Items to draw, in document (z) order. */
|
|
11
|
+
items: BoardItem[];
|
|
12
|
+
/** World rect being captured; content is translated so it starts at 0,0. */
|
|
13
|
+
world: Rect;
|
|
14
|
+
/** Output pixels per world unit. Drives text rasterisation resolution. */
|
|
15
|
+
scale: number;
|
|
16
|
+
colorScheme: "dark" | "light";
|
|
17
|
+
palette?: Partial<BoardRenderPalette>;
|
|
18
|
+
colors?: BoardShapeColors;
|
|
19
|
+
/** Resolved textures by preview key. Missing keys render as placeholders. */
|
|
20
|
+
textures?: Map<string, Texture>;
|
|
21
|
+
/** Preview-key strategy supplied by the host; defaults to still images only. */
|
|
22
|
+
assetKey?: (item: BoardItem) => string | null;
|
|
23
|
+
/** Opaque paper behind the content, or null for transparency. */
|
|
24
|
+
background?: number | null;
|
|
25
|
+
};
|
|
26
|
+
type BoardExportScene = {
|
|
27
|
+
/** Root to hand to the renderer; already positioned and scaled. */
|
|
28
|
+
root: Container;
|
|
29
|
+
/** Image keys the scene wanted but could not resolve. */
|
|
30
|
+
missingImageKeys: string[];
|
|
31
|
+
destroy: () => void;
|
|
32
|
+
};
|
|
33
|
+
declare function createBoardExportScene(input: BoardExportSceneInput): BoardExportScene;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { BoardExportScene, BoardExportSceneInput, createBoardExportScene };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { imageAssetKey } from "../image-key.js";
|
|
2
|
+
import { buildFallbackShapeColors } from "../core/palette.js";
|
|
3
|
+
import { defaultBoardPalette } from "../render/palette.js";
|
|
4
|
+
import { getBoardCardRenderer } from "../render/renderers/board-renderer-registry.js";
|
|
5
|
+
import { Container, Graphics } from "pixi.js";
|
|
6
|
+
//#region src/board/export/scene.ts
|
|
7
|
+
/**
|
|
8
|
+
* Render context for an export.
|
|
9
|
+
*
|
|
10
|
+
* Interaction state is empty by construction: nothing is selected, hovered or
|
|
11
|
+
* resizing, so no editor chrome (outlines, handles) can leak into the image.
|
|
12
|
+
*/
|
|
13
|
+
function buildContext(input) {
|
|
14
|
+
const missing = /* @__PURE__ */ new Set();
|
|
15
|
+
const byId = new Map(input.document.items.map((item) => [item.id, item]));
|
|
16
|
+
const textures = input.textures ?? /* @__PURE__ */ new Map();
|
|
17
|
+
return {
|
|
18
|
+
context: {
|
|
19
|
+
document: input.document,
|
|
20
|
+
getItem: (id) => byId.get(id) ?? null,
|
|
21
|
+
selectedIds: /* @__PURE__ */ new Set(),
|
|
22
|
+
hoveredId: null,
|
|
23
|
+
resizingIds: /* @__PURE__ */ new Set(),
|
|
24
|
+
palette: {
|
|
25
|
+
...defaultBoardPalette(input.colorScheme),
|
|
26
|
+
...input.palette
|
|
27
|
+
},
|
|
28
|
+
colors: input.colors ?? buildFallbackShapeColors(input.colorScheme),
|
|
29
|
+
colorScheme: input.colorScheme,
|
|
30
|
+
zoom: input.scale,
|
|
31
|
+
assetKey: input.assetKey ?? imageAssetKey,
|
|
32
|
+
getTexture: (key) => textures.get(key) ?? null,
|
|
33
|
+
hasError: (key) => {
|
|
34
|
+
if (!textures.has(key)) missing.add(key);
|
|
35
|
+
return false;
|
|
36
|
+
},
|
|
37
|
+
fileState: () => "ok",
|
|
38
|
+
acquireTexture: () => {},
|
|
39
|
+
releaseTexture: () => {}
|
|
40
|
+
},
|
|
41
|
+
missing
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function createBoardExportScene(input) {
|
|
45
|
+
const { context, missing } = buildContext(input);
|
|
46
|
+
const root = new Container({ label: "board-export-root" });
|
|
47
|
+
if (input.background != null) root.addChild(new Graphics().rect(0, 0, input.world.width * input.scale, input.world.height * input.scale).fill({
|
|
48
|
+
color: input.background,
|
|
49
|
+
alpha: 1
|
|
50
|
+
}));
|
|
51
|
+
const world = new Container({
|
|
52
|
+
isRenderGroup: true,
|
|
53
|
+
label: "board-export-world"
|
|
54
|
+
});
|
|
55
|
+
world.scale.set(input.scale);
|
|
56
|
+
world.position.set(-input.world.x * input.scale, -input.world.y * input.scale);
|
|
57
|
+
for (const item of input.items) {
|
|
58
|
+
const renderer = getBoardCardRenderer(item, context);
|
|
59
|
+
world.addChild(renderer.create(item, context));
|
|
60
|
+
}
|
|
61
|
+
root.addChild(world);
|
|
62
|
+
return {
|
|
63
|
+
root,
|
|
64
|
+
missingImageKeys: [...missing],
|
|
65
|
+
destroy: () => root.destroy({ children: true })
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { createBoardExportScene };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { BoardFrame, BoardViewport } from "../protocol/dist/board-document.js";
|
|
2
|
+
//#region src/board/geometry.d.ts
|
|
3
|
+
type Point = {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
};
|
|
7
|
+
type Rect = {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
};
|
|
13
|
+
type Size = {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
};
|
|
17
|
+
declare const coordBrand: unique symbol;
|
|
18
|
+
/** Surface-relative screen coordinate, in CSS pixels. */
|
|
19
|
+
type ScreenPoint = {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
readonly [coordBrand]: "screen";
|
|
23
|
+
};
|
|
24
|
+
/** Board world coordinate, in board units. */
|
|
25
|
+
type WorldPoint = {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
readonly [coordBrand]: "world";
|
|
29
|
+
};
|
|
30
|
+
declare function screenPoint(x: number, y: number): ScreenPoint;
|
|
31
|
+
declare function worldPoint(x: number, y: number): WorldPoint;
|
|
32
|
+
declare const MIN_BOARD_ZOOM = 0.05;
|
|
33
|
+
declare const MAX_BOARD_ZOOM = 8;
|
|
34
|
+
/** Smallest an item can be resized to, in world units. */
|
|
35
|
+
declare const MIN_ITEM_SIZE = 24;
|
|
36
|
+
/** Extra world-space padding applied when fitting content into view. */
|
|
37
|
+
declare const FIT_PADDING = 64;
|
|
38
|
+
/**
|
|
39
|
+
* Fraction of the viewport's larger dimension used as an off-screen margin for
|
|
40
|
+
* both card culling and texture preloading. Keeping them equal means a card's
|
|
41
|
+
* texture is already requested before the card scrolls into view.
|
|
42
|
+
*/
|
|
43
|
+
declare const VIEWPORT_MARGIN_RATIO = 0.5;
|
|
44
|
+
declare function clampZoom(zoom: number): number;
|
|
45
|
+
/** Keep transient camera state finite without discarding its last valid axes. */
|
|
46
|
+
declare function normalizeViewport(viewport: BoardViewport, fallback?: BoardViewport): BoardViewport;
|
|
47
|
+
declare function clamp(value: number, min: number, max: number): number;
|
|
48
|
+
declare function degToRad(degrees: number): number;
|
|
49
|
+
declare function radToDeg(radians: number): number;
|
|
50
|
+
declare function frameRect(frame: BoardFrame): Rect;
|
|
51
|
+
declare function rectCenter(rect: Rect): WorldPoint;
|
|
52
|
+
declare function rectsIntersect(a: Rect, b: Rect): boolean;
|
|
53
|
+
declare function rectContainsPoint(rect: Rect, point: Point, tolerance?: number): boolean;
|
|
54
|
+
declare function expandRect(rect: Rect, amount: number): Rect;
|
|
55
|
+
declare function unionRects(rects: Rect[]): Rect | null;
|
|
56
|
+
declare function rotatePointAround<P extends Point>(point: P, center: Point, angleRad: number): P;
|
|
57
|
+
/** Axis-aligned bounding box of a (possibly rotated) frame. */
|
|
58
|
+
declare function itemBounds(frame: BoardFrame): Rect;
|
|
59
|
+
declare function selectionBounds(frames: BoardFrame[]): Rect | null;
|
|
60
|
+
/** Exact point-in-frame test, accounting for rotation. */
|
|
61
|
+
declare function frameContainsPoint(frame: BoardFrame, point: WorldPoint): boolean;
|
|
62
|
+
/** World position of a resize handle on a single (possibly rotated) frame. */
|
|
63
|
+
declare function frameHandlePosition(frame: BoardFrame, handle: ResizeHandle): WorldPoint;
|
|
64
|
+
/** Corners in clockwise order, following the frame's rotation. */
|
|
65
|
+
declare function frameCorners(frame: BoardFrame): readonly WorldPoint[];
|
|
66
|
+
/** Screen-space radius (px) within which a pointer grabs a handle. */
|
|
67
|
+
declare const HANDLE_HIT_RADIUS = 8;
|
|
68
|
+
/** Screen-space offset (px) from the selection's lower-facing edge. */
|
|
69
|
+
declare const ROTATION_HANDLE_OFFSET = 28;
|
|
70
|
+
declare function frameRayIntersection(frame: Rect & {
|
|
71
|
+
rotation?: number;
|
|
72
|
+
}, pointer: WorldPoint): WorldPoint;
|
|
73
|
+
declare function rotationHandleAnchor(frame: Rect & {
|
|
74
|
+
rotation?: number;
|
|
75
|
+
}): WorldPoint;
|
|
76
|
+
declare function rotationHandlePosition(frame: Rect & {
|
|
77
|
+
rotation?: number;
|
|
78
|
+
}, zoom: number): WorldPoint;
|
|
79
|
+
type ResizeHandle = "nw" | "n" | "ne" | "e" | "se" | "s" | "sw" | "w";
|
|
80
|
+
declare const CORNER_RESIZE_HANDLES: readonly ["nw", "ne", "se", "sw"];
|
|
81
|
+
declare const EDGE_RESIZE_HANDLES: readonly ["n", "e", "s", "w"];
|
|
82
|
+
declare const RESIZE_HANDLES: ResizeHandle[];
|
|
83
|
+
/**
|
|
84
|
+
* Hit-test the continuous edge zones of a rotated frame. Corners are inset so
|
|
85
|
+
* their resize handles retain priority even on small nodes.
|
|
86
|
+
*/
|
|
87
|
+
declare function frameEdgeHandleAt(frame: BoardFrame, point: WorldPoint, zoom: number, screenRadius?: number): Extract<ResizeHandle, "n" | "e" | "s" | "w"> | null;
|
|
88
|
+
/** Direction of a handle from the rect center: -1, 0, or 1 per axis. */
|
|
89
|
+
declare const HANDLE_DIRECTION: Record<ResizeHandle, Point>;
|
|
90
|
+
/** Position of a handle on an unrotated rect. */
|
|
91
|
+
declare function handlePosition(rect: Rect, handle: ResizeHandle): WorldPoint;
|
|
92
|
+
/** Resize a frame to exact dimensions while keeping the opposite handle fixed. */
|
|
93
|
+
declare function resizeFrameToSize(frame: BoardFrame, handle: ResizeHandle, width: number, height: number): BoardFrame;
|
|
94
|
+
/**
|
|
95
|
+
* Resize a frame by dragging a handle to a world-space pointer position.
|
|
96
|
+
* The edge/corner opposite the handle stays anchored. Works correctly for
|
|
97
|
+
* rotated frames by performing the resize in the frame's local space.
|
|
98
|
+
*/
|
|
99
|
+
declare function resizeFrame(frame: BoardFrame, handle: ResizeHandle, pointer: WorldPoint, minSize?: number,
|
|
100
|
+
/** Keep the original aspect ratio (Shift). */
|
|
101
|
+
keepAspect?: boolean): BoardFrame;
|
|
102
|
+
/**
|
|
103
|
+
* Proportionally scale a group of frames by dragging a corner handle of their
|
|
104
|
+
* combined bounds. Individual rotations are preserved.
|
|
105
|
+
*/
|
|
106
|
+
declare function scaleFrames(frames: BoardFrame[], bounds: Rect, handle: ResizeHandle, pointer: WorldPoint, minSize?: number, scaleRange?: {
|
|
107
|
+
min?: number;
|
|
108
|
+
max?: number;
|
|
109
|
+
}): BoardFrame[];
|
|
110
|
+
/** Angle (degrees) from a center to a world-space pointer. */
|
|
111
|
+
declare function angleFromCenter(center: WorldPoint, pointer: WorldPoint): number;
|
|
112
|
+
/**
|
|
113
|
+
* Rotate a set of frames around a pivot by a delta (degrees). Each frame's
|
|
114
|
+
* center orbits the pivot and its own rotation increases by the delta.
|
|
115
|
+
*/
|
|
116
|
+
declare function rotateFrames(frames: BoardFrame[], pivot: WorldPoint, deltaDeg: number): BoardFrame[];
|
|
117
|
+
/** Normalize a rotation into the [-180, 180] range for tidy persistence. */
|
|
118
|
+
declare function normalizeRotation(degrees: number): number;
|
|
119
|
+
/** Convert a surface-relative screen point into world space. */
|
|
120
|
+
declare function pointToWorld(point: ScreenPoint, viewport: BoardViewport): WorldPoint;
|
|
121
|
+
declare function worldToScreen(point: WorldPoint, viewport: BoardViewport): ScreenPoint;
|
|
122
|
+
declare function screenToWorld(clientX: number, clientY: number, rect: DOMRect, viewport: BoardViewport): WorldPoint;
|
|
123
|
+
/** Zoom while keeping a surface-relative screen point fixed under the cursor. */
|
|
124
|
+
declare function zoomAround(viewport: BoardViewport, anchor: ScreenPoint, nextZoom: number): BoardViewport;
|
|
125
|
+
declare function panBy(viewport: BoardViewport, dx: number, dy: number): BoardViewport;
|
|
126
|
+
/** Compute a viewport that frames the given world rect within the surface. */
|
|
127
|
+
declare function fitToContent(content: Rect, surface: Size, padding?: number): BoardViewport;
|
|
128
|
+
/** World-space rectangle currently visible in the board stage. */
|
|
129
|
+
declare function visibleWorldRect(viewport: BoardViewport, surfaceWidth: number, surfaceHeight: number): Rect;
|
|
130
|
+
//#endregion
|
|
131
|
+
export { CORNER_RESIZE_HANDLES, EDGE_RESIZE_HANDLES, FIT_PADDING, HANDLE_DIRECTION, HANDLE_HIT_RADIUS, MAX_BOARD_ZOOM, MIN_BOARD_ZOOM, MIN_ITEM_SIZE, Point, RESIZE_HANDLES, ROTATION_HANDLE_OFFSET, Rect, ResizeHandle, ScreenPoint, Size, VIEWPORT_MARGIN_RATIO, WorldPoint, angleFromCenter, clamp, clampZoom, degToRad, expandRect, fitToContent, frameContainsPoint, frameCorners, frameEdgeHandleAt, frameHandlePosition, frameRayIntersection, frameRect, handlePosition, itemBounds, normalizeRotation, normalizeViewport, panBy, pointToWorld, radToDeg, rectCenter, rectContainsPoint, rectsIntersect, resizeFrame, resizeFrameToSize, rotateFrames, rotatePointAround, rotationHandleAnchor, rotationHandlePosition, scaleFrames, screenPoint, screenToWorld, selectionBounds, unionRects, visibleWorldRect, worldPoint, worldToScreen, zoomAround };
|