@neta-art/cohub 12.1.1 → 12.3.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 +85 -8
- package/dist/board/editor/clipboard.js +1 -1
- package/dist/board/editor/creation.js +1 -1
- package/dist/board/editor/document.d.ts +3 -1
- package/dist/board/editor/document.js +8 -6
- package/dist/board/editor/editor.d.ts +8 -3
- package/dist/board/editor/editor.js +8 -2
- package/dist/board/editor/index.d.ts +3 -3
- package/dist/board/editor/index.js +2 -2
- package/dist/board/editor/interaction.d.ts +23 -2
- package/dist/board/editor/interaction.js +93 -4
- package/dist/board/editor/query.d.ts +1 -1
- package/dist/board/editor/query.js +3 -7
- package/dist/board/editor/selection.js +1 -1
- package/dist/board/editor/snapshots.js +5 -57
- package/dist/board/editor/tool.d.ts +4 -1
- package/dist/board/editor/tool.js +6 -1
- package/dist/board/export/index.d.ts +2 -2
- package/dist/board/export/index.js +1 -1
- package/dist/board/export/node/fonts.js +4 -0
- package/dist/board/export/node/index.d.ts +2 -0
- package/dist/board/export/node/index.js +8 -1
- package/dist/board/export/node/scripts.js +1 -1
- package/dist/board/export/scene.d.ts +2 -2
- package/dist/board/export/scene.js +2 -2
- package/dist/board/model/asset-key.d.ts +24 -0
- package/dist/board/model/asset-key.js +104 -0
- package/dist/board/model/export-assets.d.ts +7 -3
- package/dist/board/model/export-assets.js +34 -13
- package/dist/board/model/index.d.ts +9 -7
- package/dist/board/model/index.js +22 -20
- package/dist/board/model/items/create.js +3 -3
- package/dist/board/model/items/definition.d.ts +9 -1
- package/dist/board/model/items/index.d.ts +2 -2
- package/dist/board/model/items/registry.d.ts +2 -1
- package/dist/board/model/items/registry.js +1 -0
- package/dist/board/model/media-sizes.d.ts +28 -0
- package/dist/board/model/media-sizes.js +64 -0
- package/dist/board/model/palette.d.ts +1 -0
- package/dist/board/model/palette.js +20 -130
- package/dist/board/model/sampling.js +1 -1
- package/dist/board/model/text-metrics.d.ts +28 -3
- package/dist/board/model/text-metrics.js +36 -2
- package/dist/board/model/themes.d.ts +495 -0
- package/dist/board/model/themes.js +500 -0
- package/dist/board/render/card-tag.js +2 -2
- package/dist/board/render/index.d.ts +11 -7
- package/dist/board/render/index.js +9 -5
- package/dist/board/render/labels.d.ts +15 -0
- package/dist/board/render/labels.js +14 -0
- package/dist/board/render/lod.d.ts +30 -0
- package/dist/board/render/lod.js +33 -0
- package/dist/board/render/media-interaction.d.ts +5 -2
- package/dist/board/render/media-interaction.js +15 -3
- package/dist/board/render/palette.js +2 -25
- package/dist/board/render/renderers/arrow-card-renderer.js +7 -5
- package/dist/board/render/renderers/audio-card-renderer.js +3 -2
- package/dist/board/render/renderers/board-renderer-registry.d.ts +22 -1
- package/dist/board/render/renderers/board-renderer-registry.js +9 -4
- package/dist/board/render/renderers/custom-card-renderer.js +5 -5
- package/dist/board/render/renderers/draw-card-renderer.js +1 -1
- package/dist/board/render/renderers/far-plate.d.ts +14 -1
- package/dist/board/render/renderers/far-plate.js +31 -8
- package/dist/board/render/renderers/file-card-renderer.js +49 -31
- package/dist/board/render/renderers/frame-card-renderer.js +9 -3
- package/dist/board/render/renderers/image-card-renderer.js +34 -8
- package/dist/board/render/renderers/model-card-renderer.js +6 -6
- package/dist/board/render/renderers/shape-card-renderer.js +25 -24
- package/dist/board/render/renderers/task-card-renderer.js +9 -10
- package/dist/board/render/renderers/text-card-renderer.js +86 -41
- package/dist/board/render/renderers/video-card-renderer.js +5 -4
- package/dist/board/render/script-export.js +1 -1
- package/dist/board/render/script-host.js +3 -3
- package/dist/board/render/style.d.ts +8 -2
- package/dist/board/render/style.js +8 -2
- package/dist/board/render/surface.d.ts +5 -0
- package/dist/board/render/surface.js +13 -3
- package/dist/board/render/text-edit.d.ts +32 -0
- package/dist/board/render/text-edit.js +134 -0
- package/dist/board/render/text-fit.d.ts +1 -3
- package/dist/board/render/text-fit.js +6 -49
- package/dist/board/render/text-measurement.d.ts +25 -1
- package/dist/board/render/text-measurement.js +157 -19
- package/dist/board/render/text-resolution.d.ts +1 -6
- package/dist/board/render/text-resolution.js +1 -10
- package/dist/board/render/theme.d.ts +15 -0
- package/dist/board/render/theme.js +15 -0
- package/dist/board/replica/index.d.ts +4 -2
- package/dist/board/replica/index.js +28 -17
- package/dist/board/replica/indexeddb.d.ts +16 -0
- package/dist/board/replica/indexeddb.js +155 -0
- package/dist/board/stage/assets.d.ts +28 -5
- package/dist/board/stage/assets.js +293 -102
- package/dist/board/stage/bench.d.ts +35 -0
- package/dist/board/stage/bench.js +153 -0
- package/dist/board/stage/device.d.ts +41 -0
- package/dist/board/stage/device.js +62 -0
- package/dist/board/stage/frame-budget.d.ts +19 -0
- package/dist/board/stage/frame-budget.js +69 -0
- package/dist/board/stage/idle.d.ts +4 -0
- package/dist/board/stage/idle.js +26 -0
- package/dist/board/stage/index.d.ts +11 -7
- package/dist/board/stage/index.js +7 -4
- package/dist/board/stage/lru.d.ts +13 -9
- package/dist/board/stage/lru.js +38 -20
- package/dist/board/stage/presses.d.ts +21 -0
- package/dist/board/stage/presses.js +66 -0
- package/dist/board/stage/scene.d.ts +15 -1
- package/dist/board/stage/scene.js +248 -141
- package/dist/board/stage/stage.d.ts +28 -2
- package/dist/board/stage/stage.js +404 -112
- package/dist/board/stage/texture-pool.d.ts +10 -1
- package/dist/board/stage/texture-pool.js +72 -2
- package/dist/chunks/environment.d.ts +6 -0
- package/dist/chunks/http.d.ts +9 -1
- package/dist/chunks/http.js +791 -4
- package/dist/index.d.ts +52 -1
- package/dist/index.js +202 -862
- package/dist/media.d.ts +3 -27
- package/dist/media.js +3 -82
- package/dist/protocol/dist/board-constants.js +3 -3
- package/dist/protocol/dist/board-layout.d.ts +17 -3
- package/dist/protocol/dist/board-layout.js +68 -11
- package/dist/protocol/dist/board-model.d.ts +15 -1
- package/dist/protocol/dist/board-model.js +12 -2
- package/dist/protocol/dist/board-patch.js +1 -1
- package/dist/protocol/dist/board-schema.d.ts +2 -1
- package/dist/protocol/dist/board-schema.js +4 -1
- package/dist/protocol/dist/core/media.d.ts +35 -1
- package/dist/protocol/dist/core/media.js +127 -1
- package/dist/protocol/dist/index.d.ts +3 -3
- package/package.json +4 -2
- package/dist/board/model/image-key.d.ts +0 -9
- package/dist/board/model/image-key.js +0 -33
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { BOARD_FONT_STACK } from "../../protocol/dist/board-constants.js";
|
|
2
|
+
import { layoutBoardText } from "../../protocol/dist/board-layout.js";
|
|
3
|
+
import { degToRad, rotatePointAround, worldPoint, worldToScreen } from "../model/geometry.js";
|
|
4
|
+
import { resolveSceneArrow } from "../model/arrow-geometry.js";
|
|
5
|
+
import { BOARD_FRAME_LABEL_LINE_HEIGHT, frameLabelOffset, layoutShapeLabel } from "../model/text-metrics.js";
|
|
6
|
+
import { boardTextStyle, canvasFont, canvasFontFamily, layoutTextLines } from "./text-measurement.js";
|
|
7
|
+
import { ARROW_LABEL, arrowLabelAnchor } from "./labels.js";
|
|
8
|
+
//#region src/board/render/text-edit.ts
|
|
9
|
+
function place({ center, ...box }, viewport) {
|
|
10
|
+
const screen = worldToScreen(center, viewport);
|
|
11
|
+
return {
|
|
12
|
+
left: screen.x - box.width / 2 - box.padding.x,
|
|
13
|
+
top: screen.y - box.height / 2 - box.padding.y,
|
|
14
|
+
...box
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function label(draft, font, lineHeight) {
|
|
18
|
+
const { lines, width } = layoutTextLines(draft, canvasFont(font));
|
|
19
|
+
return {
|
|
20
|
+
width: Math.max(width, font.fontSize * 4),
|
|
21
|
+
height: lines.length * lineHeight
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function boardTextEditLayout(item, { viewport, scene, style, draft }) {
|
|
25
|
+
const { frame } = item;
|
|
26
|
+
const rotation = frame.rotation || 0;
|
|
27
|
+
const center = worldPoint(frame.x + frame.width / 2, frame.y + frame.height / 2);
|
|
28
|
+
const { zoom } = viewport;
|
|
29
|
+
if (item.type === "text") {
|
|
30
|
+
const { props } = item;
|
|
31
|
+
const text = layoutBoardText({
|
|
32
|
+
...props,
|
|
33
|
+
text: draft
|
|
34
|
+
});
|
|
35
|
+
const scale = zoom * frame.width / text.width;
|
|
36
|
+
return place({
|
|
37
|
+
center,
|
|
38
|
+
width: text.width,
|
|
39
|
+
height: text.height,
|
|
40
|
+
padding: {
|
|
41
|
+
x: 1 / scale,
|
|
42
|
+
y: 0
|
|
43
|
+
},
|
|
44
|
+
scale,
|
|
45
|
+
rotation,
|
|
46
|
+
font: boardTextStyle(props),
|
|
47
|
+
lineHeight: text.lineHeight,
|
|
48
|
+
textAlign: props.align ?? "left",
|
|
49
|
+
whiteSpace: props.width ? "pre-wrap" : "pre",
|
|
50
|
+
live: true
|
|
51
|
+
}, viewport);
|
|
52
|
+
}
|
|
53
|
+
if (item.type === "shape") {
|
|
54
|
+
const { props } = item;
|
|
55
|
+
const text = layoutShapeLabel({
|
|
56
|
+
text: draft,
|
|
57
|
+
fontSize: props.fontSize
|
|
58
|
+
}, frame.width);
|
|
59
|
+
return place({
|
|
60
|
+
center,
|
|
61
|
+
width: Math.max(0, frame.width - 16),
|
|
62
|
+
height: Math.min(frame.height, text.height),
|
|
63
|
+
padding: {
|
|
64
|
+
x: 8,
|
|
65
|
+
y: Math.max(0, (frame.height - text.height) / 2)
|
|
66
|
+
},
|
|
67
|
+
scale: zoom,
|
|
68
|
+
rotation,
|
|
69
|
+
font: boardTextStyle({
|
|
70
|
+
font: "sans",
|
|
71
|
+
fontSize: props.fontSize,
|
|
72
|
+
fontWeight: 500
|
|
73
|
+
}),
|
|
74
|
+
lineHeight: text.lineHeight,
|
|
75
|
+
textAlign: props.align ?? "center",
|
|
76
|
+
whiteSpace: "pre-wrap",
|
|
77
|
+
live: false
|
|
78
|
+
}, viewport);
|
|
79
|
+
}
|
|
80
|
+
if (item.type === "frame") {
|
|
81
|
+
const fontSize = style?.frameLabelSize ?? 12;
|
|
82
|
+
const lineHeight = fontSize * BOARD_FRAME_LABEL_LINE_HEIGHT;
|
|
83
|
+
const font = {
|
|
84
|
+
fontFamily: canvasFontFamily(style?.font ?? "\"Geist Variable\", \"Geist\", \"Noto Sans CJK SC\", \"Noto Sans SC\", \"PingFang SC\", \"Microsoft YaHei\", system-ui, -apple-system, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Noto Color Emoji\", \"Noto Sans Math\", \"Noto Sans Symbols 2\", \"DejaVu Sans\", \"FreeSans\", \"FreeSerif\", sans-serif"),
|
|
85
|
+
fontSize,
|
|
86
|
+
fontWeight: "600"
|
|
87
|
+
};
|
|
88
|
+
const size = label(draft, font, lineHeight);
|
|
89
|
+
const { x, y } = frameLabelOffset(fontSize);
|
|
90
|
+
const local = worldPoint(frame.x + x + size.width / 2, frame.y + y + size.height / 2);
|
|
91
|
+
return place({
|
|
92
|
+
center: rotation ? rotatePointAround(local, center, degToRad(rotation)) : local,
|
|
93
|
+
...size,
|
|
94
|
+
padding: {
|
|
95
|
+
x: 4,
|
|
96
|
+
y: 2
|
|
97
|
+
},
|
|
98
|
+
scale: zoom,
|
|
99
|
+
rotation,
|
|
100
|
+
font,
|
|
101
|
+
lineHeight,
|
|
102
|
+
textAlign: "left",
|
|
103
|
+
whiteSpace: "pre",
|
|
104
|
+
live: false
|
|
105
|
+
}, viewport);
|
|
106
|
+
}
|
|
107
|
+
if (item.type === "arrow") {
|
|
108
|
+
const { fontSize } = item.props;
|
|
109
|
+
const font = {
|
|
110
|
+
fontFamily: canvasFontFamily(BOARD_FONT_STACK),
|
|
111
|
+
fontSize,
|
|
112
|
+
fontWeight: ARROW_LABEL.fontWeight
|
|
113
|
+
};
|
|
114
|
+
const lineHeight = fontSize * ARROW_LABEL.lineHeight;
|
|
115
|
+
return place({
|
|
116
|
+
center: arrowLabelAnchor(resolveSceneArrow(item, scene), item.style),
|
|
117
|
+
...label(draft, font, lineHeight),
|
|
118
|
+
padding: {
|
|
119
|
+
x: fontSize * ARROW_LABEL.padX,
|
|
120
|
+
y: fontSize * ARROW_LABEL.padY
|
|
121
|
+
},
|
|
122
|
+
scale: zoom,
|
|
123
|
+
rotation: 0,
|
|
124
|
+
font,
|
|
125
|
+
lineHeight,
|
|
126
|
+
textAlign: "center",
|
|
127
|
+
whiteSpace: "pre",
|
|
128
|
+
live: false
|
|
129
|
+
}, viewport);
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
export { boardTextEditLayout };
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { Text } from "pixi.js";
|
|
2
2
|
//#region src/board/render/text-fit.d.ts
|
|
3
|
-
declare function ellipsizeWrappedLines(lines: string[], maxLines: number): string;
|
|
4
|
-
declare function fitLineWithEllipsis(line: string, wrapWidth: number, measureWidth: (value: string) => number): string;
|
|
5
3
|
declare function fitTextToLines(text: Text, value: string, maxLines: number, wrapWidth: number): void;
|
|
6
4
|
declare function fitTextToWidth(text: Text, value: string, width: number): void;
|
|
7
5
|
//#endregion
|
|
8
|
-
export {
|
|
6
|
+
export { fitTextToLines, fitTextToWidth };
|
|
@@ -1,53 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fitLineWithEllipsis, layoutTextLines } from "./text-measurement.js";
|
|
2
|
+
import { CanvasTextMetrics, fontStringFromTextStyle } from "pixi.js";
|
|
2
3
|
//#region src/board/render/text-fit.ts
|
|
3
|
-
function ellipsizeWrappedLines(lines, maxLines) {
|
|
4
|
-
if (maxLines <= 0 || lines.length === 0) return "";
|
|
5
|
-
if (lines.length <= maxLines) return lines.join("\n");
|
|
6
|
-
const kept = lines.slice(0, maxLines);
|
|
7
|
-
const lastIndex = kept.length - 1;
|
|
8
|
-
const last = (kept[lastIndex] ?? "").replace(/\s+$/u, "");
|
|
9
|
-
kept[lastIndex] = last ? `${last}…` : "…";
|
|
10
|
-
return kept.join("\n");
|
|
11
|
-
}
|
|
12
|
-
function fitLineWithEllipsis(line, wrapWidth, measureWidth) {
|
|
13
|
-
const trimmed = line.replace(/\s+$/u, "");
|
|
14
|
-
if (!trimmed) return "…";
|
|
15
|
-
const full = `${trimmed}…`;
|
|
16
|
-
if (measureWidth(full) <= wrapWidth) return full;
|
|
17
|
-
let lo = 0;
|
|
18
|
-
let hi = trimmed.length;
|
|
19
|
-
let best = "…";
|
|
20
|
-
while (lo <= hi) {
|
|
21
|
-
const mid = lo + hi >> 1;
|
|
22
|
-
const candidate = mid > 0 ? `${trimmed.slice(0, mid)}…` : "…";
|
|
23
|
-
if (measureWidth(candidate) <= wrapWidth) {
|
|
24
|
-
best = candidate;
|
|
25
|
-
lo = mid + 1;
|
|
26
|
-
} else hi = mid - 1;
|
|
27
|
-
}
|
|
28
|
-
return best;
|
|
29
|
-
}
|
|
30
4
|
function fitTextToLines(text, value, maxLines, wrapWidth) {
|
|
31
|
-
|
|
32
|
-
text.style.
|
|
33
|
-
|
|
34
|
-
if (text.text !== "") text.text = "";
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const charCap = Math.max(maxLines * Math.ceil(width), maxLines * 4);
|
|
38
|
-
const sample = value.length > charCap ? value.slice(0, charCap) : value;
|
|
39
|
-
const metrics = CanvasTextMetrics.measureText(sample, text.style);
|
|
40
|
-
if (!(metrics.lines.length > maxLines || sample !== value)) {
|
|
41
|
-
if (text.text !== value) text.text = value;
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
const keptCount = Math.min(maxLines, metrics.lines.length);
|
|
45
|
-
const kept = metrics.lines.slice(0, keptCount);
|
|
46
|
-
const lastIndex = kept.length - 1;
|
|
47
|
-
const probe = text.style.clone();
|
|
48
|
-
probe.wordWrap = false;
|
|
49
|
-
kept[lastIndex] = fitLineWithEllipsis(kept[lastIndex] ?? "", width, (candidate) => CanvasTextMetrics.measureText(candidate, probe).width);
|
|
50
|
-
const fitted = kept.join("\n");
|
|
5
|
+
text.style.wordWrap = false;
|
|
6
|
+
const { lines } = layoutTextLines(value, fontStringFromTextStyle(text.style), Math.max(1, wrapWidth), { maxLines });
|
|
7
|
+
const fitted = lines.join("\n");
|
|
51
8
|
if (text.text !== fitted) text.text = fitted;
|
|
52
9
|
}
|
|
53
10
|
function fitTextToWidth(text, value, width) {
|
|
@@ -57,4 +14,4 @@ function fitTextToWidth(text, value, width) {
|
|
|
57
14
|
if (text.text !== fitted) text.text = fitted;
|
|
58
15
|
}
|
|
59
16
|
//#endregion
|
|
60
|
-
export {
|
|
17
|
+
export { fitTextToLines, fitTextToWidth };
|
|
@@ -1,10 +1,34 @@
|
|
|
1
|
+
import { BoardTextFont, BoardTextLines } from "../../protocol/dist/board-layout.js";
|
|
2
|
+
import "../../protocol/dist/index.js";
|
|
3
|
+
import { TextStyle } from "pixi.js";
|
|
1
4
|
//#region src/board/render/text-measurement.d.ts
|
|
2
5
|
declare const BOARD_FONT_STACKS: {
|
|
3
6
|
sans: string;
|
|
4
7
|
serif: string;
|
|
5
8
|
mono: string;
|
|
6
9
|
};
|
|
10
|
+
type BoardTextStyle = {
|
|
11
|
+
fontFamily: string;
|
|
12
|
+
fontSize: number;
|
|
13
|
+
fontWeight: string;
|
|
14
|
+
};
|
|
15
|
+
declare function canvasFontFamily(families: string): string;
|
|
16
|
+
declare function boardTextStyle({ font, fontSize, fontWeight }: BoardTextFont): BoardTextStyle;
|
|
17
|
+
declare function canvasFont({ fontFamily, fontSize, fontWeight }: BoardTextStyle): string;
|
|
18
|
+
declare function measureTextWidth(text: string, font: string): number;
|
|
19
|
+
type BoardTextWhiteSpace = "normal" | "pre-wrap";
|
|
20
|
+
type BoardTextLineOptions = {
|
|
21
|
+
whiteSpace?: BoardTextWhiteSpace;
|
|
22
|
+
maxLines?: number;
|
|
23
|
+
};
|
|
24
|
+
type BoardTextLineLayout = BoardTextLines & {
|
|
25
|
+
truncated: boolean;
|
|
26
|
+
};
|
|
27
|
+
declare function layoutTextLines(text: string, font: string, maxWidth?: number, { whiteSpace, maxLines }?: BoardTextLineOptions): BoardTextLineLayout;
|
|
28
|
+
declare function fitLineWithEllipsis(line: string, width: number, measureWidth: (value: string) => number): string;
|
|
29
|
+
declare function cssBaselineShift(style: TextStyle): number;
|
|
7
30
|
declare function installBoardTextMeasurement(): void;
|
|
8
31
|
declare function ensureBoardTextMeasurement(): void;
|
|
32
|
+
declare function onBoardTextFontsChange(listener: () => void): () => void;
|
|
9
33
|
//#endregion
|
|
10
|
-
export { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement };
|
|
34
|
+
export { BOARD_FONT_STACKS, BoardTextLineLayout, BoardTextLineOptions, BoardTextStyle, BoardTextWhiteSpace, boardTextStyle, canvasFont, canvasFontFamily, cssBaselineShift, ensureBoardTextMeasurement, fitLineWithEllipsis, installBoardTextMeasurement, layoutTextLines, measureTextWidth, onBoardTextFontsChange };
|
|
@@ -1,38 +1,176 @@
|
|
|
1
1
|
import { BOARD_FONT_STACK, BOARD_MONO_FONT_STACK } from "../../protocol/dist/board-constants.js";
|
|
2
|
-
import {
|
|
2
|
+
import { clampBoardFontSize, setBoardTextLayouter } from "../../protocol/dist/board-layout.js";
|
|
3
3
|
import { boardFontFamily } from "../model/text-metrics.js";
|
|
4
|
-
import { DOMAdapter } from "pixi.js";
|
|
4
|
+
import { CanvasTextMetrics, DOMAdapter, fontStringFromTextStyle } from "pixi.js";
|
|
5
|
+
import { clearCache, layoutWithLines, prepareWithSegments } from "@chenglou/pretext";
|
|
5
6
|
//#region src/board/render/text-measurement.ts
|
|
6
7
|
const BOARD_FONT_STACKS = {
|
|
7
8
|
sans: BOARD_FONT_STACK,
|
|
8
|
-
serif: "ui-serif, Georgia, \"Noto Serif CJK SC\", \"Songti SC\", \"Noto Sans Math\", \"DejaVu Sans\", \"Noto Sans CJK SC\", \"FreeSans\", \"FreeSerif\", sans-serif",
|
|
9
|
+
serif: "ui-serif, Georgia, \"Noto Serif CJK SC\", \"Songti SC\", \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Noto Color Emoji\", \"Noto Sans Math\", \"DejaVu Sans\", \"Noto Sans CJK SC\", \"FreeSans\", \"FreeSerif\", sans-serif",
|
|
9
10
|
mono: BOARD_MONO_FONT_STACK
|
|
10
11
|
};
|
|
12
|
+
const GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
13
|
+
"serif",
|
|
14
|
+
"sans-serif",
|
|
15
|
+
"monospace",
|
|
16
|
+
"cursive",
|
|
17
|
+
"fantasy",
|
|
18
|
+
"system-ui"
|
|
19
|
+
]);
|
|
20
|
+
const QUOTED = /^(["']).+\1$/;
|
|
21
|
+
const familyLists = /* @__PURE__ */ new Map();
|
|
22
|
+
function canvasFontFamily(families) {
|
|
23
|
+
let list = familyLists.get(families);
|
|
24
|
+
if (list === void 0) {
|
|
25
|
+
list = families.split(",").map((name) => name.trim()).filter(Boolean).map((name) => QUOTED.test(name) || GENERIC_FAMILIES.has(name) ? name : `"${name}"`).join(", ");
|
|
26
|
+
familyLists.set(families, list);
|
|
27
|
+
}
|
|
28
|
+
return list;
|
|
29
|
+
}
|
|
30
|
+
function boardTextStyle({ font, fontSize, fontWeight }) {
|
|
31
|
+
return {
|
|
32
|
+
fontFamily: canvasFontFamily(boardFontFamily(font, BOARD_FONT_STACKS)),
|
|
33
|
+
fontSize: clampBoardFontSize(fontSize),
|
|
34
|
+
fontWeight: String(Math.round(fontWeight / 100) * 100)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function canvasFont({ fontFamily, fontSize, fontWeight }) {
|
|
38
|
+
return `${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
39
|
+
}
|
|
40
|
+
const PREPARED_LIMIT = 256;
|
|
41
|
+
const prepared = /* @__PURE__ */ new Map();
|
|
42
|
+
const measuredFonts = /* @__PURE__ */ new Set();
|
|
43
|
+
const measuredFamilies = /* @__PURE__ */ new Set();
|
|
44
|
+
const TRAILING_SPACE = /[ \t]+$/u;
|
|
45
|
+
const unquote = (name) => name.trim().replace(/^["']|["']$/gu, "");
|
|
46
|
+
function prepareText(text, font, whiteSpace) {
|
|
47
|
+
const key = `${whiteSpace}\n${font}\n${text}`;
|
|
48
|
+
let value = prepared.get(key);
|
|
49
|
+
if (value) {
|
|
50
|
+
prepared.delete(key);
|
|
51
|
+
prepared.set(key, value);
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
if (!measuredFonts.has(font)) {
|
|
55
|
+
measuredFonts.add(font);
|
|
56
|
+
for (const name of /px\s+(.+)$/u.exec(font)?.[1]?.split(",") ?? []) measuredFamilies.add(unquote(name));
|
|
57
|
+
}
|
|
58
|
+
value = prepareWithSegments(text, font, { whiteSpace });
|
|
59
|
+
if (prepared.size >= PREPARED_LIMIT) prepared.delete(prepared.keys().next().value);
|
|
60
|
+
prepared.set(key, value);
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
11
63
|
let measureContext;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (measureContext
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
64
|
+
function measureTextWidth(text, font) {
|
|
65
|
+
measureContext ??= DOMAdapter.get().createCanvas(1, 1).getContext("2d");
|
|
66
|
+
if (!measureContext) return 0;
|
|
67
|
+
measureContext.font = font;
|
|
68
|
+
return measureContext.measureText(text).width;
|
|
69
|
+
}
|
|
70
|
+
function layoutTextLines(text, font, maxWidth = Number.POSITIVE_INFINITY, { whiteSpace = "pre-wrap", maxLines = Number.POSITIVE_INFINITY } = {}) {
|
|
71
|
+
const lines = [];
|
|
72
|
+
const widths = [];
|
|
73
|
+
if (maxLines <= 0) return {
|
|
74
|
+
lines,
|
|
75
|
+
width: 0,
|
|
76
|
+
truncated: text.length > 0
|
|
77
|
+
};
|
|
78
|
+
let source = whiteSpace === "normal" ? text.replace(/\s+/gu, " ").trim() : text.replace(/\r\n?/gu, "\n").replaceAll(" ", " ");
|
|
79
|
+
const cap = Number.isFinite(maxLines) && Number.isFinite(maxWidth) ? maxLines * (Math.ceil(maxWidth) + 1) : Number.POSITIVE_INFINITY;
|
|
80
|
+
let truncated = source.length > cap;
|
|
81
|
+
if (truncated) source = source.slice(0, cap);
|
|
82
|
+
const paragraphs = whiteSpace === "normal" ? source ? [source] : [] : source.split("\n");
|
|
83
|
+
for (const paragraph of paragraphs) {
|
|
84
|
+
if (lines.length > maxLines) break;
|
|
85
|
+
if (!paragraph) {
|
|
86
|
+
lines.push("");
|
|
87
|
+
widths.push(0);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const wrapped = layoutWithLines(prepareText(paragraph, font, whiteSpace), maxWidth, 1).lines;
|
|
91
|
+
for (const [index, line] of wrapped.entries()) {
|
|
92
|
+
lines.push(index === wrapped.length - 1 ? line.text : line.text.replace(TRAILING_SPACE, ""));
|
|
93
|
+
widths.push(line.width);
|
|
94
|
+
}
|
|
19
95
|
}
|
|
20
|
-
|
|
96
|
+
if (lines.length > maxLines) {
|
|
97
|
+
truncated = true;
|
|
98
|
+
lines.length = maxLines;
|
|
99
|
+
widths.length = maxLines;
|
|
100
|
+
}
|
|
101
|
+
if (truncated && lines.length > 0) {
|
|
102
|
+
const last = lines.length - 1;
|
|
103
|
+
lines[last] = fitLineWithEllipsis(lines[last], maxWidth, (value) => measureTextWidth(value, font));
|
|
104
|
+
widths[last] = measureTextWidth(lines[last], font);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
lines,
|
|
108
|
+
width: widths.reduce((max, width) => Math.max(max, width), 0),
|
|
109
|
+
truncated
|
|
110
|
+
};
|
|
21
111
|
}
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
112
|
+
const graphemes = typeof Intl.Segmenter === "function" ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
|
|
113
|
+
function fitLineWithEllipsis(line, width, measureWidth) {
|
|
114
|
+
const trimmed = line.replace(/\s+$/u, "");
|
|
115
|
+
if (!trimmed) return "…";
|
|
116
|
+
if (measureWidth(`${trimmed}…`) <= width) return `${trimmed}…`;
|
|
117
|
+
const parts = graphemes ? Array.from(graphemes.segment(trimmed), ({ segment }) => segment) : Array.from(trimmed);
|
|
118
|
+
let lo = 0;
|
|
119
|
+
let hi = parts.length - 1;
|
|
120
|
+
while (lo < hi) {
|
|
121
|
+
const mid = lo + hi + 1 >> 1;
|
|
122
|
+
if (measureWidth(`${parts.slice(0, mid).join("")}…`) <= width) lo = mid;
|
|
123
|
+
else hi = mid - 1;
|
|
124
|
+
}
|
|
125
|
+
return `${parts.slice(0, lo).join("")}…`;
|
|
126
|
+
}
|
|
127
|
+
const baselineShifts = /* @__PURE__ */ new Map();
|
|
128
|
+
function cssBaselineShift(style) {
|
|
129
|
+
const font = fontStringFromTextStyle(style);
|
|
130
|
+
let shift = baselineShifts.get(font);
|
|
131
|
+
if (shift === void 0) {
|
|
132
|
+
const ink = CanvasTextMetrics.measureFont(font);
|
|
133
|
+
const context = DOMAdapter.get().createCanvas(1, 1).getContext("2d");
|
|
134
|
+
shift = 0;
|
|
135
|
+
if (context) {
|
|
136
|
+
context.font = font;
|
|
137
|
+
const { fontBoundingBoxAscent: ascent, fontBoundingBoxDescent: descent } = context.measureText("M");
|
|
138
|
+
if (Number.isFinite(ascent) && Number.isFinite(descent)) shift = (ascent - descent - (ink.ascent - ink.descent)) / 2;
|
|
139
|
+
}
|
|
140
|
+
baselineShifts.set(font, shift);
|
|
141
|
+
}
|
|
142
|
+
return shift;
|
|
143
|
+
}
|
|
144
|
+
const layoutBoardLines = (text, font, maxWidth) => layoutTextLines(text, canvasFont(boardTextStyle(font)), maxWidth);
|
|
145
|
+
const canMeasure = () => typeof OffscreenCanvas !== "undefined" || typeof document !== "undefined";
|
|
146
|
+
let installed = false;
|
|
147
|
+
let fontsChanged = false;
|
|
148
|
+
const fontListeners = /* @__PURE__ */ new Set();
|
|
149
|
+
function notifyFontsChanged() {
|
|
150
|
+
fontsChanged = false;
|
|
151
|
+
installBoardTextMeasurement();
|
|
152
|
+
for (const listener of fontListeners) listener();
|
|
27
153
|
}
|
|
28
154
|
function installBoardTextMeasurement() {
|
|
29
|
-
measureContext = void 0;
|
|
30
155
|
installed = true;
|
|
31
|
-
|
|
156
|
+
prepared.clear();
|
|
157
|
+
baselineShifts.clear();
|
|
158
|
+
clearCache();
|
|
159
|
+
CanvasTextMetrics.clearMetrics();
|
|
160
|
+
setBoardTextLayouter(canMeasure() ? layoutBoardLines : void 0);
|
|
32
161
|
}
|
|
33
162
|
function ensureBoardTextMeasurement() {
|
|
34
163
|
if (installed) return;
|
|
35
164
|
installBoardTextMeasurement();
|
|
165
|
+
globalThis.document?.fonts?.addEventListener("loadingdone", (event) => {
|
|
166
|
+
if (fontsChanged || !event.fontfaces.some((face) => measuredFamilies.has(unquote(face.family)))) return;
|
|
167
|
+
fontsChanged = true;
|
|
168
|
+
requestAnimationFrame(notifyFontsChanged);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function onBoardTextFontsChange(listener) {
|
|
172
|
+
fontListeners.add(listener);
|
|
173
|
+
return () => fontListeners.delete(listener);
|
|
36
174
|
}
|
|
37
175
|
//#endregion
|
|
38
|
-
export { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement };
|
|
176
|
+
export { BOARD_FONT_STACKS, boardTextStyle, canvasFont, canvasFontFamily, cssBaselineShift, ensureBoardTextMeasurement, fitLineWithEllipsis, installBoardTextMeasurement, layoutTextLines, measureTextWidth, onBoardTextFontsChange };
|
|
@@ -5,13 +5,8 @@ declare function getBoardResolution(size?: {
|
|
|
5
5
|
height: number;
|
|
6
6
|
}, devicePixelRatio?: number): number;
|
|
7
7
|
declare function textZoomBucket(zoom: number): number;
|
|
8
|
-
declare function boardCardDetailed(zoom: number): boolean;
|
|
9
8
|
/** Exact screen density at rest; reuse or step the raster while moving. */
|
|
10
9
|
declare function boardTextResolution(target: number, current: number, settled: boolean): number;
|
|
11
10
|
declare function syncTextResolution(text: Text, resolution: number): void;
|
|
12
|
-
/** Defer expensive Pixi word-wrap rasterisation during a live resize. */
|
|
13
|
-
declare function syncTextWrapWidth(text: Text, state: {
|
|
14
|
-
wrapWidth: number;
|
|
15
|
-
}, width: number, defer: boolean): void;
|
|
16
11
|
//#endregion
|
|
17
|
-
export {
|
|
12
|
+
export { boardTextResolution, getBoardResolution, syncTextResolution, textZoomBucket };
|
|
@@ -23,9 +23,6 @@ function textZoomBucket(zoom) {
|
|
|
23
23
|
for (const bucket of ZOOM_BUCKETS) if (clamped <= bucket) return bucket;
|
|
24
24
|
return ZOOM_BUCKETS[ZOOM_BUCKETS.length - 1] ?? 4;
|
|
25
25
|
}
|
|
26
|
-
function boardCardDetailed(zoom) {
|
|
27
|
-
return textZoomBucket(zoom) >= 1;
|
|
28
|
-
}
|
|
29
26
|
/** Exact screen density at rest; reuse or step the raster while moving. */
|
|
30
27
|
function boardTextResolution(target, current, settled) {
|
|
31
28
|
if (!Number.isFinite(target) || target <= 0) return current > 0 ? current : 1;
|
|
@@ -39,11 +36,5 @@ function syncTextResolution(text, resolution) {
|
|
|
39
36
|
const next = Math.min(limit, Math.max(MIN_TEXT_RESOLUTION, resolution));
|
|
40
37
|
if (text.resolution !== next) text.resolution = next;
|
|
41
38
|
}
|
|
42
|
-
/** Defer expensive Pixi word-wrap rasterisation during a live resize. */
|
|
43
|
-
function syncTextWrapWidth(text, state, width, defer) {
|
|
44
|
-
if (defer || width === state.wrapWidth) return;
|
|
45
|
-
text.style.wordWrapWidth = width;
|
|
46
|
-
state.wrapWidth = width;
|
|
47
|
-
}
|
|
48
39
|
//#endregion
|
|
49
|
-
export {
|
|
40
|
+
export { boardTextResolution, getBoardResolution, syncTextResolution, textZoomBucket };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BoardShapeColors } from "../model/palette.js";
|
|
2
|
+
import { BoardThemeId } from "../model/themes.js";
|
|
3
|
+
import { BoardRenderStyle } from "./style.js";
|
|
4
|
+
import { BoardRenderPalette } from "./renderers/board-renderer-registry.js";
|
|
5
|
+
//#region src/board/render/theme.d.ts
|
|
6
|
+
type BoardRenderTheme = {
|
|
7
|
+
colorScheme: "dark" | "light";
|
|
8
|
+
palette: BoardRenderPalette;
|
|
9
|
+
colors: BoardShapeColors;
|
|
10
|
+
style: BoardRenderStyle;
|
|
11
|
+
};
|
|
12
|
+
/** A built-in Cohub theme as the app draws it; spread it into export or sampler options. */
|
|
13
|
+
declare function boardRenderTheme(id: BoardThemeId): BoardRenderTheme;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { BoardRenderTheme, boardRenderTheme };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BOARD_THEME_DATA } from "../model/themes.js";
|
|
2
|
+
import { resolveBoardRenderStyle } from "./style.js";
|
|
3
|
+
//#region src/board/render/theme.ts
|
|
4
|
+
/** A built-in Cohub theme as the app draws it; spread it into export or sampler options. */
|
|
5
|
+
function boardRenderTheme(id) {
|
|
6
|
+
const { colorScheme, palette, colors, style } = BOARD_THEME_DATA[id];
|
|
7
|
+
return {
|
|
8
|
+
colorScheme,
|
|
9
|
+
palette: { ...palette },
|
|
10
|
+
colors: Object.fromEntries(Object.entries(colors).map(([key, value]) => [key, { ...value }])),
|
|
11
|
+
style: resolveBoardRenderStyle(colorScheme, style)
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { boardRenderTheme };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BoardDelta, BoardDocument, BoardPatch } from "../../protocol/dist/board-model.js";
|
|
2
2
|
import { BoardApplyResult, BoardPlaybackSnapshot, BoardReadResult } from "../../protocol/dist/board.js";
|
|
3
3
|
import "../../protocol/dist/index.js";
|
|
4
|
+
import { BoardReplicaDatabaseOptions, BoardReplicaScope, clearBoardReplicaStorage, createIndexedDbBoardReplicaStorage } from "./indexeddb.js";
|
|
4
5
|
//#region src/board/replica/index.d.ts
|
|
5
6
|
type BoardPendingPatch = {
|
|
6
7
|
mutationId: string;
|
|
@@ -27,6 +28,7 @@ type BoardRemote = {
|
|
|
27
28
|
playback: BoardPlaybackSnapshot | null;
|
|
28
29
|
};
|
|
29
30
|
}) => void;
|
|
31
|
+
resync?: () => void;
|
|
30
32
|
}): () => void;
|
|
31
33
|
};
|
|
32
34
|
type BoardReplicaStorage = {
|
|
@@ -62,9 +64,9 @@ declare function createBoardReplica(options: BoardReplicaOptions): {
|
|
|
62
64
|
receive: (payload: BoardChangedPayload) => void;
|
|
63
65
|
receivePlayback: (next: BoardPlaybackSnapshot | null) => void;
|
|
64
66
|
refresh: () => Promise<void>;
|
|
65
|
-
retry()
|
|
67
|
+
retry: () => Promise<void>;
|
|
66
68
|
dispose(): void;
|
|
67
69
|
};
|
|
68
70
|
type BoardReplica = ReturnType<typeof createBoardReplica>;
|
|
69
71
|
//#endregion
|
|
70
|
-
export { BoardChangedPayload, BoardPendingPatch, BoardRemote, BoardReplica, BoardReplicaOptions, BoardReplicaState, BoardReplicaStorage, createBoardReplica };
|
|
72
|
+
export { BoardChangedPayload, BoardPendingPatch, BoardRemote, BoardReplica, type BoardReplicaDatabaseOptions, BoardReplicaOptions, type BoardReplicaScope, BoardReplicaState, BoardReplicaStorage, clearBoardReplicaStorage, createBoardReplica, createIndexedDbBoardReplicaStorage };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { applyBoardDelta, applyBoardPatchToDocument, parseBoardDocument } from "../../protocol/dist/board-patch.js";
|
|
2
2
|
import { createBoardEntityId } from "../model/items/id.js";
|
|
3
3
|
import { shareBoardDocument } from "../model/document-share.js";
|
|
4
|
+
import { clearBoardReplicaStorage, createIndexedDbBoardReplicaStorage } from "./indexeddb.js";
|
|
4
5
|
//#region src/board/replica/index.ts
|
|
5
6
|
function memoryStorage() {
|
|
6
7
|
return {
|
|
@@ -47,6 +48,7 @@ function createBoardReplica(options) {
|
|
|
47
48
|
let retryTimer = null;
|
|
48
49
|
let retryDelay = RETRY_BASE_MS;
|
|
49
50
|
let disposed = false;
|
|
51
|
+
let restored = false;
|
|
50
52
|
let unpersisted = null;
|
|
51
53
|
let persistTimer = null;
|
|
52
54
|
function subscribeRemote() {
|
|
@@ -54,7 +56,8 @@ function createBoardReplica(options) {
|
|
|
54
56
|
try {
|
|
55
57
|
return remote.subscribe({
|
|
56
58
|
changed: (event) => receive(event.payload),
|
|
57
|
-
playback: (event) => receivePlayback(event.payload.playback)
|
|
59
|
+
playback: (event) => receivePlayback(event.payload.playback),
|
|
60
|
+
resync: () => void retry()
|
|
58
61
|
});
|
|
59
62
|
} catch {
|
|
60
63
|
return null;
|
|
@@ -157,7 +160,7 @@ function createBoardReplica(options) {
|
|
|
157
160
|
retryDelay = Math.min(RETRY_MAX_MS, retryDelay * 2);
|
|
158
161
|
}
|
|
159
162
|
async function flush() {
|
|
160
|
-
if (sending || disposed || !loaded) return;
|
|
163
|
+
if (sending || disposed || !loaded || !restored) return;
|
|
161
164
|
sending = true;
|
|
162
165
|
try {
|
|
163
166
|
while (pending.length && !disposed) {
|
|
@@ -217,6 +220,19 @@ function createBoardReplica(options) {
|
|
|
217
220
|
}
|
|
218
221
|
emit();
|
|
219
222
|
}
|
|
223
|
+
function retry() {
|
|
224
|
+
if (disposed) return Promise.resolve();
|
|
225
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
226
|
+
retryTimer = null;
|
|
227
|
+
retryDelay = RETRY_BASE_MS;
|
|
228
|
+
readError = null;
|
|
229
|
+
writeError = null;
|
|
230
|
+
emit();
|
|
231
|
+
return refresh().then(flush);
|
|
232
|
+
}
|
|
233
|
+
function onOnline() {
|
|
234
|
+
if (pending.length || readError || writeError) retry();
|
|
235
|
+
}
|
|
220
236
|
function receivePlayback(next) {
|
|
221
237
|
playbackRevision += 1;
|
|
222
238
|
playback = next;
|
|
@@ -232,13 +248,15 @@ function createBoardReplica(options) {
|
|
|
232
248
|
},
|
|
233
249
|
async start() {
|
|
234
250
|
unsubscribeRemote ??= subscribeRemote();
|
|
251
|
+
globalThis.addEventListener?.("online", onOnline);
|
|
235
252
|
const [cached, stored] = await Promise.all([storage.readDocument().catch(() => null), storage.listPending().catch(() => [])]);
|
|
236
253
|
if (disposed) return;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
254
|
+
const known = new Set(stored.map((entry) => entry.mutationId));
|
|
255
|
+
pending = [...stored, ...pending.filter((entry) => !known.has(entry.mutationId))];
|
|
256
|
+
restored = true;
|
|
257
|
+
const parsed = cached && !server ? parseBoardDocument(cached.document) : null;
|
|
258
|
+
if (cached && parsed?.ok) adoptServer(parsed.document, cached.version, false);
|
|
259
|
+
else recompute();
|
|
242
260
|
emit();
|
|
243
261
|
await refresh();
|
|
244
262
|
await flush();
|
|
@@ -256,17 +274,10 @@ function createBoardReplica(options) {
|
|
|
256
274
|
receive,
|
|
257
275
|
receivePlayback,
|
|
258
276
|
refresh,
|
|
259
|
-
retry
|
|
260
|
-
if (retryTimer) clearTimeout(retryTimer);
|
|
261
|
-
retryTimer = null;
|
|
262
|
-
retryDelay = RETRY_BASE_MS;
|
|
263
|
-
readError = null;
|
|
264
|
-
writeError = null;
|
|
265
|
-
emit();
|
|
266
|
-
return refresh().then(flush);
|
|
267
|
-
},
|
|
277
|
+
retry,
|
|
268
278
|
dispose() {
|
|
269
279
|
disposed = true;
|
|
280
|
+
globalThis.removeEventListener?.("online", onOnline);
|
|
270
281
|
unsubscribeRemote?.();
|
|
271
282
|
unsubscribeRemote = null;
|
|
272
283
|
listeners.clear();
|
|
@@ -276,4 +287,4 @@ function createBoardReplica(options) {
|
|
|
276
287
|
};
|
|
277
288
|
}
|
|
278
289
|
//#endregion
|
|
279
|
-
export { createBoardReplica };
|
|
290
|
+
export { clearBoardReplicaStorage, createBoardReplica, createIndexedDbBoardReplicaStorage };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BoardReplicaStorage } from "./index.js";
|
|
2
|
+
//#region src/board/replica/indexeddb.d.ts
|
|
3
|
+
type BoardReplicaScope = {
|
|
4
|
+
userKey: string;
|
|
5
|
+
spaceId: string;
|
|
6
|
+
boardId: string;
|
|
7
|
+
};
|
|
8
|
+
type BoardReplicaDatabaseOptions = {
|
|
9
|
+
database?: string;
|
|
10
|
+
};
|
|
11
|
+
declare function createIndexedDbBoardReplicaStorage(scope: BoardReplicaScope, options?: BoardReplicaDatabaseOptions): BoardReplicaStorage;
|
|
12
|
+
declare function clearBoardReplicaStorage(options?: BoardReplicaDatabaseOptions & {
|
|
13
|
+
userKey?: string;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { BoardReplicaDatabaseOptions, BoardReplicaScope, clearBoardReplicaStorage, createIndexedDbBoardReplicaStorage };
|