@neta-art/cohub 12.0.0 → 12.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/board/export/index.js +1 -1
- package/dist/board/export/node/index.d.ts +1 -0
- package/dist/board/export/node/index.js +5 -0
- package/dist/board/export/scene.js +2 -1
- package/dist/board/render/card-tag.d.ts +0 -1
- package/dist/board/render/card-tag.js +15 -17
- package/dist/board/render/index.d.ts +2 -2
- package/dist/board/render/index.js +6 -6
- package/dist/board/render/renderers/arrow-card-renderer.js +5 -7
- package/dist/board/render/renderers/audio-card-renderer.js +8 -7
- package/dist/board/render/renderers/base-card-renderer.d.ts +1 -1
- package/dist/board/render/renderers/base-card-renderer.js +2 -3
- package/dist/board/render/renderers/board-renderer-registry.d.ts +3 -1
- package/dist/board/render/renderers/custom-card-renderer.js +19 -17
- package/dist/board/render/renderers/file-card-renderer.js +10 -8
- package/dist/board/render/renderers/frame-card-renderer.js +5 -6
- package/dist/board/render/renderers/shape-card-renderer.js +5 -7
- package/dist/board/render/renderers/task-card-renderer.js +9 -9
- package/dist/board/render/renderers/text-card-renderer.js +5 -7
- package/dist/board/render/renderers/unknown-card-renderer.js +5 -7
- package/dist/board/render/text-resolution.d.ts +4 -7
- package/dist/board/render/text-resolution.js +20 -22
- package/dist/board/stage/canvas-size.d.ts +18 -0
- package/dist/board/stage/canvas-size.js +31 -0
- package/dist/board/stage/scene.d.ts +5 -1
- package/dist/board/stage/scene.js +35 -9
- package/dist/board/stage/stage.js +34 -13
- package/dist/board/stage/text-resolution-tracker.d.ts +8 -0
- package/dist/board/stage/text-resolution-tracker.js +33 -0
- package/dist/chunks/app-realtime.js +1 -1
- package/dist/chunks/{transport.js → http-error.js} +37 -1
- package/dist/chunks/http.js +30 -10
- package/dist/http.js +1 -1
- package/dist/index.js +2 -2
- package/dist/websocket.js +951 -1
- package/package.json +1 -1
- package/dist/chunks/websocket.js +0 -988
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { layoutBoardText } from "../../../protocol/dist/board-layout.js";
|
|
2
2
|
import { boardFontFamily } from "../../model/text-metrics.js";
|
|
3
|
-
import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
|
|
4
|
-
import { BOARD_FONT_STACKS } from "../text-measurement.js";
|
|
5
3
|
import { itemAlpha, itemColor } from "../palette.js";
|
|
4
|
+
import { BOARD_FONT_STACKS } from "../text-measurement.js";
|
|
5
|
+
import { syncTextResolution } from "../text-resolution.js";
|
|
6
6
|
import { positionShell } from "./base-card-renderer.js";
|
|
7
7
|
import { drawFarPlate } from "./far-plate.js";
|
|
8
8
|
import { Container, Graphics, Text } from "pixi.js";
|
|
@@ -32,7 +32,6 @@ function sync(container, item, context) {
|
|
|
32
32
|
const anchorX = props.align === "center" ? .5 : props.align === "right" ? 1 : 0;
|
|
33
33
|
parts.body.anchor.set(anchorX, 0);
|
|
34
34
|
parts.body.position.set(item.frame.width * anchorX, 0);
|
|
35
|
-
syncTextResolution(parts.body, parts, context.zoom * Math.max(1, previewScale));
|
|
36
35
|
parts.body.alpha = itemAlpha(context, item.style.fill);
|
|
37
36
|
parts.caret.alpha = parts.body.alpha;
|
|
38
37
|
const ink = itemColor(context, item.style.fill, "neutral");
|
|
@@ -62,6 +61,7 @@ function sync(container, item, context) {
|
|
|
62
61
|
breakWords: true
|
|
63
62
|
});
|
|
64
63
|
}
|
|
64
|
+
syncTextResolution(parts.body, context.textResolution * previewScale);
|
|
65
65
|
syncCaret(parts, item, context);
|
|
66
66
|
}
|
|
67
67
|
const textCardRenderer = {
|
|
@@ -69,11 +69,10 @@ const textCardRenderer = {
|
|
|
69
69
|
canRender: (item) => item.type === "text",
|
|
70
70
|
create: (item, context) => {
|
|
71
71
|
const root = new Container();
|
|
72
|
-
const resolution = textResolutionForZoom(context.zoom);
|
|
73
72
|
const body = new Text({
|
|
74
73
|
text: "",
|
|
75
74
|
style: { fontFamily: BOARD_FONT_STACKS.sans },
|
|
76
|
-
resolution,
|
|
75
|
+
resolution: context.textResolution,
|
|
77
76
|
roundPixels: true
|
|
78
77
|
});
|
|
79
78
|
const caret = new Graphics();
|
|
@@ -82,7 +81,6 @@ const textCardRenderer = {
|
|
|
82
81
|
root,
|
|
83
82
|
body,
|
|
84
83
|
caret,
|
|
85
|
-
resolution,
|
|
86
84
|
contentSig: ""
|
|
87
85
|
});
|
|
88
86
|
if (item.type === "text") sync(root, item, context);
|
|
@@ -93,7 +91,7 @@ const textCardRenderer = {
|
|
|
93
91
|
},
|
|
94
92
|
resolution: (container, _item, context) => {
|
|
95
93
|
const parts = partsByContainer.get(container);
|
|
96
|
-
if (parts) syncTextResolution(parts.body,
|
|
94
|
+
if (parts) syncTextResolution(parts.body, context.textResolution * parts.body.scale.x);
|
|
97
95
|
},
|
|
98
96
|
renderFar: (graphics, item, context) => {
|
|
99
97
|
if (item.type !== "text") return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
|
-
import { syncTextResolution
|
|
2
|
+
import { syncTextResolution } from "../text-resolution.js";
|
|
3
3
|
import { cardElevation, drawCardSurface, traceCardOutline } from "../surface.js";
|
|
4
4
|
import { positionShell } from "./base-card-renderer.js";
|
|
5
5
|
import { drawFarPlate } from "./far-plate.js";
|
|
@@ -12,7 +12,6 @@ function sync(container, item, context) {
|
|
|
12
12
|
positionShell(parts.root, item);
|
|
13
13
|
const { width, height } = item.frame;
|
|
14
14
|
const realType = item.type;
|
|
15
|
-
syncTextResolution(parts.label, parts, context.zoom);
|
|
16
15
|
const elevation = cardElevation(context, item.id);
|
|
17
16
|
const visualSig = [
|
|
18
17
|
width,
|
|
@@ -47,6 +46,7 @@ function sync(container, item, context) {
|
|
|
47
46
|
parts.label.text = realType;
|
|
48
47
|
parts.label.style.fill = context.palette.muted;
|
|
49
48
|
}
|
|
49
|
+
syncTextResolution(parts.label, context.textResolution);
|
|
50
50
|
parts.label.position.set(width / 2, height / 2);
|
|
51
51
|
}
|
|
52
52
|
const unknownCardRenderer = {
|
|
@@ -56,7 +56,6 @@ const unknownCardRenderer = {
|
|
|
56
56
|
const root = new Container();
|
|
57
57
|
const box = new Graphics();
|
|
58
58
|
box.context.batchMode = "batch";
|
|
59
|
-
const resolution = textResolutionForZoom(context.zoom);
|
|
60
59
|
const label = new Text({
|
|
61
60
|
text: "",
|
|
62
61
|
style: {
|
|
@@ -64,7 +63,7 @@ const unknownCardRenderer = {
|
|
|
64
63
|
fontSize: 11,
|
|
65
64
|
fontWeight: "600"
|
|
66
65
|
},
|
|
67
|
-
resolution,
|
|
66
|
+
resolution: context.textResolution,
|
|
68
67
|
roundPixels: true
|
|
69
68
|
});
|
|
70
69
|
label.anchor.set(.5);
|
|
@@ -74,8 +73,7 @@ const unknownCardRenderer = {
|
|
|
74
73
|
box,
|
|
75
74
|
label,
|
|
76
75
|
visualSig: "",
|
|
77
|
-
textSig: ""
|
|
78
|
-
resolution
|
|
76
|
+
textSig: ""
|
|
79
77
|
});
|
|
80
78
|
sync(root, item, context);
|
|
81
79
|
return root;
|
|
@@ -85,7 +83,7 @@ const unknownCardRenderer = {
|
|
|
85
83
|
},
|
|
86
84
|
resolution: (container, _item, context) => {
|
|
87
85
|
const parts = partsByContainer.get(container);
|
|
88
|
-
if (parts) syncTextResolution(parts.label,
|
|
86
|
+
if (parts) syncTextResolution(parts.label, context.textResolution);
|
|
89
87
|
},
|
|
90
88
|
renderFar: (graphics, item, context) => {
|
|
91
89
|
drawFarPlate(graphics, item.frame, {
|
|
@@ -6,15 +6,12 @@ declare function getBoardResolution(size?: {
|
|
|
6
6
|
}, devicePixelRatio?: number): number;
|
|
7
7
|
declare function textZoomBucket(zoom: number): number;
|
|
8
8
|
declare function boardCardDetailed(zoom: number): boolean;
|
|
9
|
-
/**
|
|
10
|
-
declare function
|
|
11
|
-
|
|
12
|
-
declare function syncTextResolution(text: Text, state: {
|
|
13
|
-
resolution: number;
|
|
14
|
-
}, zoom: number): void;
|
|
9
|
+
/** Exact screen density at rest; reuse or step the raster while moving. */
|
|
10
|
+
declare function boardTextResolution(target: number, current: number, settled: boolean): number;
|
|
11
|
+
declare function syncTextResolution(text: Text, resolution: number): void;
|
|
15
12
|
/** Defer expensive Pixi word-wrap rasterisation during a live resize. */
|
|
16
13
|
declare function syncTextWrapWidth(text: Text, state: {
|
|
17
14
|
wrapWidth: number;
|
|
18
15
|
}, width: number, defer: boolean): void;
|
|
19
16
|
//#endregion
|
|
20
|
-
export { boardCardDetailed, getBoardResolution, syncTextResolution, syncTextWrapWidth,
|
|
17
|
+
export { boardCardDetailed, boardTextResolution, getBoardResolution, syncTextResolution, syncTextWrapWidth, textZoomBucket };
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
//#region src/board/render/text-resolution.ts
|
|
2
2
|
const MAX_BOARD_RESOLUTION = 3;
|
|
3
|
-
const MAX_BOARD_PIXELS =
|
|
4
|
-
const
|
|
3
|
+
const MAX_BOARD_PIXELS = 1e7;
|
|
4
|
+
const MIN_TEXT_RESOLUTION = .125;
|
|
5
|
+
const MAX_TEXT_EDGE = 4096;
|
|
6
|
+
const TEXT_UPSCALE_TOLERANCE = 1.25;
|
|
5
7
|
function getBoardResolution(size, devicePixelRatio = globalThis.devicePixelRatio) {
|
|
6
8
|
const density = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? devicePixelRatio : 1;
|
|
7
9
|
const pixels = size ? size.width * size.height : 0;
|
|
8
|
-
const budget = pixels > 0 ? Math.sqrt(MAX_BOARD_PIXELS / pixels) : MAX_BOARD_RESOLUTION;
|
|
10
|
+
const budget = pixels > 0 ? Math.max(1, Math.sqrt(MAX_BOARD_PIXELS / pixels)) : MAX_BOARD_RESOLUTION;
|
|
9
11
|
return Math.min(density, MAX_BOARD_RESOLUTION, budget);
|
|
10
12
|
}
|
|
11
|
-
|
|
12
|
-
* Zoom buckets for text re-rasterisation. Pixi Text is a bitmap: at high zoom it
|
|
13
|
-
* looks soft unless we raise its resolution. We quantise zoom into a few
|
|
14
|
-
* buckets so small zoom changes don't thrash texture regeneration.
|
|
15
|
-
*/
|
|
16
|
-
const TEXT_ZOOM_BUCKETS = [
|
|
13
|
+
const ZOOM_BUCKETS = [
|
|
17
14
|
.5,
|
|
18
15
|
1,
|
|
19
16
|
1.5,
|
|
@@ -23,23 +20,24 @@ const TEXT_ZOOM_BUCKETS = [
|
|
|
23
20
|
];
|
|
24
21
|
function textZoomBucket(zoom) {
|
|
25
22
|
const clamped = Math.max(.1, Math.min(4, zoom));
|
|
26
|
-
for (const bucket of
|
|
27
|
-
return
|
|
23
|
+
for (const bucket of ZOOM_BUCKETS) if (clamped <= bucket) return bucket;
|
|
24
|
+
return ZOOM_BUCKETS[ZOOM_BUCKETS.length - 1] ?? 4;
|
|
28
25
|
}
|
|
29
26
|
function boardCardDetailed(zoom) {
|
|
30
27
|
return textZoomBucket(zoom) >= 1;
|
|
31
28
|
}
|
|
32
|
-
/**
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
/** Exact screen density at rest; reuse or step the raster while moving. */
|
|
30
|
+
function boardTextResolution(target, current, settled) {
|
|
31
|
+
if (!Number.isFinite(target) || target <= 0) return current > 0 ? current : 1;
|
|
32
|
+
if (settled || !(current > 0)) return target;
|
|
33
|
+
if (target <= current * TEXT_UPSCALE_TOLERANCE && target >= current / 2) return current;
|
|
34
|
+
return 2 ** Math.ceil(Math.log2(target));
|
|
36
35
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
text.resolution =
|
|
42
|
-
state.resolution = resolution;
|
|
36
|
+
function syncTextResolution(text, resolution) {
|
|
37
|
+
const { width, height } = text.bounds;
|
|
38
|
+
const limit = MAX_TEXT_EDGE / Math.max(1, Math.ceil(width + text.style.padding * 2), Math.ceil(height + text.style.padding * 2));
|
|
39
|
+
const next = Math.min(limit, Math.max(MIN_TEXT_RESOLUTION, resolution));
|
|
40
|
+
if (text.resolution !== next) text.resolution = next;
|
|
43
41
|
}
|
|
44
42
|
/** Defer expensive Pixi word-wrap rasterisation during a live resize. */
|
|
45
43
|
function syncTextWrapWidth(text, state, width, defer) {
|
|
@@ -48,4 +46,4 @@ function syncTextWrapWidth(text, state, width, defer) {
|
|
|
48
46
|
state.wrapWidth = width;
|
|
49
47
|
}
|
|
50
48
|
//#endregion
|
|
51
|
-
export { boardCardDetailed, getBoardResolution, syncTextResolution, syncTextWrapWidth,
|
|
49
|
+
export { boardCardDetailed, boardTextResolution, getBoardResolution, syncTextResolution, syncTextWrapWidth, textZoomBucket };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/board/stage/canvas-size.d.ts
|
|
2
|
+
type BoardCanvasBox = {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
density: number;
|
|
6
|
+
device: {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
} | null;
|
|
10
|
+
};
|
|
11
|
+
declare function measureBoardCanvasBox(entry: ResizeObserverEntry, density: number): BoardCanvasBox;
|
|
12
|
+
declare function boardCanvasSize(box: BoardCanvasBox, density: number): {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
resolution: number;
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { BoardCanvasBox, boardCanvasSize, measureBoardCanvasBox };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getBoardResolution } from "../render/text-resolution.js";
|
|
2
|
+
//#region src/board/stage/canvas-size.ts
|
|
3
|
+
function measureBoardCanvasBox(entry, density) {
|
|
4
|
+
const css = entry.contentBoxSize?.[0];
|
|
5
|
+
const device = entry.devicePixelContentBoxSize?.[0];
|
|
6
|
+
return {
|
|
7
|
+
width: css?.inlineSize ?? entry.contentRect.width,
|
|
8
|
+
height: css?.blockSize ?? entry.contentRect.height,
|
|
9
|
+
density,
|
|
10
|
+
device: device ? {
|
|
11
|
+
width: device.inlineSize,
|
|
12
|
+
height: device.blockSize
|
|
13
|
+
} : null
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function boardCanvasSize(box, density) {
|
|
17
|
+
const resolution = getBoardResolution(box, density);
|
|
18
|
+
const { device } = box;
|
|
19
|
+
if (device && box.density === density && resolution === density) return {
|
|
20
|
+
width: device.width / density,
|
|
21
|
+
height: device.height / density,
|
|
22
|
+
resolution
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
width: box.width,
|
|
26
|
+
height: box.height,
|
|
27
|
+
resolution
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { boardCanvasSize, measureBoardCanvasBox };
|
|
@@ -24,6 +24,10 @@ type SceneSyncInput = {
|
|
|
24
24
|
structureVersion: number;
|
|
25
25
|
geometryVersion: number;
|
|
26
26
|
gestureActive: boolean;
|
|
27
|
+
focus?: {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
};
|
|
27
31
|
};
|
|
28
32
|
type BoardSceneNode = {
|
|
29
33
|
item: BoardSceneItem;
|
|
@@ -43,7 +47,7 @@ declare function createBoardScene(options: {
|
|
|
43
47
|
world: Container;
|
|
44
48
|
overlay: Graphics;
|
|
45
49
|
getRenderer: BoardCardRendererResolver;
|
|
46
|
-
|
|
50
|
+
requestFrame?: () => void;
|
|
47
51
|
}): BoardScene;
|
|
48
52
|
//#endregion
|
|
49
53
|
export { BoardLiveStroke, BoardScene, BoardSceneNode, SceneSyncInput, createBoardScene };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseBoardItem } from "../../protocol/dist/board-model.js";
|
|
2
2
|
import { drawPointsBounds } from "../../protocol/dist/board-layout.js";
|
|
3
|
-
import {
|
|
3
|
+
import { textZoomBucket } from "../render/text-resolution.js";
|
|
4
4
|
import { clippingAncestor, isClippingFrame, syncClipGroup } from "../render/clip.js";
|
|
5
5
|
import { Graphics } from "pixi.js";
|
|
6
6
|
//#region src/board/stage/scene.ts
|
|
@@ -8,6 +8,7 @@ const FAR_LAYER_ENTER = 450;
|
|
|
8
8
|
const FAR_LAYER_EXIT = 350;
|
|
9
9
|
const FAR_PASS_BATCH = 240;
|
|
10
10
|
const FAR_LIVE_LIMIT = 64;
|
|
11
|
+
const RESOLUTION_BATCH = 32;
|
|
11
12
|
const Z_FAR_LAYER = -1;
|
|
12
13
|
const Z_LIFTED = 1e9;
|
|
13
14
|
const Z_LIVE_STROKE = Number.MAX_SAFE_INTEGER - 1;
|
|
@@ -40,6 +41,7 @@ function createBoardScene(options) {
|
|
|
40
41
|
let lastStructureVersion = -1;
|
|
41
42
|
let lastZoom = NaN;
|
|
42
43
|
let lastResolution = "";
|
|
44
|
+
const pendingResolution = /* @__PURE__ */ new Set();
|
|
43
45
|
let liveVersion = 0;
|
|
44
46
|
let lifted = /* @__PURE__ */ new Set();
|
|
45
47
|
const liveStrokes = /* @__PURE__ */ new Map();
|
|
@@ -69,6 +71,7 @@ function createBoardScene(options) {
|
|
|
69
71
|
function recycle(id, entry, context, destroyed) {
|
|
70
72
|
setHeldKey(context, id, null);
|
|
71
73
|
cards.delete(id);
|
|
74
|
+
pendingResolution.delete(id);
|
|
72
75
|
animatedIds.delete(id);
|
|
73
76
|
tickingIds.delete(id);
|
|
74
77
|
if (destroyed) return;
|
|
@@ -182,7 +185,7 @@ function createBoardScene(options) {
|
|
|
182
185
|
}
|
|
183
186
|
if (index < ids.length) {
|
|
184
187
|
farFrame = requestAnimationFrame(step);
|
|
185
|
-
if (target === farFront) options.
|
|
188
|
+
if (target === farFront) options.requestFrame?.();
|
|
186
189
|
return;
|
|
187
190
|
}
|
|
188
191
|
if (target === farBack) {
|
|
@@ -195,7 +198,7 @@ function createBoardScene(options) {
|
|
|
195
198
|
farFront.visible = farActive;
|
|
196
199
|
farPainted = true;
|
|
197
200
|
farRunning = false;
|
|
198
|
-
options.
|
|
201
|
+
options.requestFrame?.();
|
|
199
202
|
if (farQueued) runFarPass();
|
|
200
203
|
};
|
|
201
204
|
step();
|
|
@@ -292,9 +295,11 @@ function createBoardScene(options) {
|
|
|
292
295
|
}
|
|
293
296
|
function sync(input) {
|
|
294
297
|
const { items, scene, context, getItem, visibleIds, pinnedIds, globalSig, structureVersion, geometryVersion, gestureActive } = input;
|
|
295
|
-
const resolution = `${textZoomBucket(context.zoom)}:${
|
|
296
|
-
|
|
297
|
-
|
|
298
|
+
const resolution = `${textZoomBucket(context.zoom)}:${context.textResolution}`;
|
|
299
|
+
if (resolution !== lastResolution) {
|
|
300
|
+
lastResolution = resolution;
|
|
301
|
+
queueResolution(input.focus);
|
|
302
|
+
}
|
|
298
303
|
const structureChanged = structureVersion !== lastStructureVersion;
|
|
299
304
|
if (structureChanged) {
|
|
300
305
|
lastStructureVersion = structureVersion;
|
|
@@ -376,11 +381,10 @@ function createBoardScene(options) {
|
|
|
376
381
|
entry.renderer.update(entry.container, item, context);
|
|
377
382
|
entry.container.alpha = scene.opacity(id);
|
|
378
383
|
if (entry.renderer.tick) tickingIds.add(id);
|
|
379
|
-
|
|
380
|
-
entry.renderer.resolution(entry.container, item, context);
|
|
381
|
-
if (entry.renderer.tick) tickingIds.add(id);
|
|
384
|
+
pendingResolution.delete(id);
|
|
382
385
|
}
|
|
383
386
|
}
|
|
387
|
+
refreshResolution(context);
|
|
384
388
|
if (liveSetChanged) liveVersion += 1;
|
|
385
389
|
if (structureChanged || liveSetChanged) syncClips(scene);
|
|
386
390
|
if (context.zoom !== lastZoom) {
|
|
@@ -392,6 +396,27 @@ function createBoardScene(options) {
|
|
|
392
396
|
if (structureChanged || farModeChanged || liveSetChanged || liftChanged) applyChildOrder(scene);
|
|
393
397
|
syncLiveStrokes(input);
|
|
394
398
|
}
|
|
399
|
+
function queueResolution(focus) {
|
|
400
|
+
const ids = [...cards].filter(([, entry]) => entry.renderer.resolution);
|
|
401
|
+
if (focus) {
|
|
402
|
+
const distance = ({ item: { frame } }) => (frame.x + frame.width / 2 - focus.x) ** 2 + (frame.y + frame.height / 2 - focus.y) ** 2;
|
|
403
|
+
ids.sort(([, a], [, b]) => distance(a) - distance(b));
|
|
404
|
+
}
|
|
405
|
+
pendingResolution.clear();
|
|
406
|
+
for (const [id] of ids) pendingResolution.add(id);
|
|
407
|
+
}
|
|
408
|
+
function refreshResolution(context) {
|
|
409
|
+
let budget = RESOLUTION_BATCH;
|
|
410
|
+
for (const id of pendingResolution) {
|
|
411
|
+
if (budget-- === 0) break;
|
|
412
|
+
pendingResolution.delete(id);
|
|
413
|
+
const entry = cards.get(id);
|
|
414
|
+
if (!entry) continue;
|
|
415
|
+
entry.renderer.resolution?.(entry.container, entry.item, context);
|
|
416
|
+
if (entry.renderer.tick) tickingIds.add(id);
|
|
417
|
+
}
|
|
418
|
+
if (pendingResolution.size) options.requestFrame?.();
|
|
419
|
+
}
|
|
395
420
|
function tick(clock) {
|
|
396
421
|
let next = null;
|
|
397
422
|
for (const id of tickingIds) {
|
|
@@ -433,6 +458,7 @@ function createBoardScene(options) {
|
|
|
433
458
|
lastStructureVersion = -1;
|
|
434
459
|
lastZoom = NaN;
|
|
435
460
|
lastResolution = "";
|
|
461
|
+
pendingResolution.clear();
|
|
436
462
|
}
|
|
437
463
|
return {
|
|
438
464
|
sync,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { applyMatrix, invertMatrix } from "../../protocol/dist/board-layout.js";
|
|
2
2
|
import { featuredTaskArtifact } from "../../protocol/dist/board-task.js";
|
|
3
|
-
import { pointToWorld, stableCullRect, visibleWorldRect, worldPoint } from "../model/geometry.js";
|
|
3
|
+
import { pointToWorld, screenPoint, stableCullRect, visibleWorldRect, worldPoint } from "../model/geometry.js";
|
|
4
4
|
import { boardScriptScope, composeBoardScriptItems, prepareBoardScriptOutput, validateBoardScriptCommit } from "../model/script-scene.js";
|
|
5
5
|
import { boardScriptTarget } from "../model/script-hit.js";
|
|
6
6
|
import { createBoardAnimationSampler, resolveBoardAnimationCamera } from "../model/sampling.js";
|
|
@@ -9,16 +9,17 @@ import { buildFallbackShapeColors, pickBoardColor, resolveItemColor } from "../m
|
|
|
9
9
|
import { elementClientToLocal } from "../model/client-geometry.js";
|
|
10
10
|
import { isCreationBoardTool } from "../editor/tool.js";
|
|
11
11
|
import { resizeCursorForHandle } from "../editor/selection-transform.js";
|
|
12
|
-
import { createBoardPlayer } from "../player/index.js";
|
|
13
|
-
import { createBoardScriptObservers } from "../model/script-observers.js";
|
|
14
12
|
import { parseBoardCssColor } from "../render/css-color.js";
|
|
13
|
+
import { defaultBoardPalette, itemAlpha } from "../render/palette.js";
|
|
15
14
|
import { createBoardBackground, updateBoardBackground } from "../render/board-background.js";
|
|
16
15
|
import { getBoardResolution } from "../render/text-resolution.js";
|
|
17
|
-
import { defaultBoardPalette, itemAlpha } from "../render/palette.js";
|
|
18
16
|
import { DEFAULT_BOARD_RENDER_LABELS, getBoardCardRenderer } from "../render/renderers/board-renderer-registry.js";
|
|
19
17
|
import { createBoardScriptHost } from "../render/script-host.js";
|
|
20
18
|
import { defaultBoardRenderStyle } from "../render/style.js";
|
|
19
|
+
import { createBoardPlayer } from "../player/index.js";
|
|
20
|
+
import { createBoardScriptObservers } from "../model/script-observers.js";
|
|
21
21
|
import { boardTextureEdge } from "./assets.js";
|
|
22
|
+
import { boardCanvasSize, measureBoardCanvasBox } from "./canvas-size.js";
|
|
22
23
|
import { observeDisplayDensity } from "./display-density.js";
|
|
23
24
|
import { toBoardPointerEvent } from "./pointer.js";
|
|
24
25
|
import { createBoardPointerGestures } from "./pointer-gestures.js";
|
|
@@ -26,6 +27,7 @@ import { createBoardScene } from "./scene.js";
|
|
|
26
27
|
import { scheduleBoardMaintenance } from "./maintenance.js";
|
|
27
28
|
import { createBoardScriptPointerRouter } from "./script-pointer.js";
|
|
28
29
|
import { drawBoardOverlay } from "./overlay.js";
|
|
30
|
+
import { createTextResolutionTracker } from "./text-resolution-tracker.js";
|
|
29
31
|
import { createBoardItemViewLayer } from "./views.js";
|
|
30
32
|
import { Application, Container, Graphics, RendererType } from "pixi.js";
|
|
31
33
|
//#region src/board/stage/stage.ts
|
|
@@ -111,6 +113,7 @@ function mountBoardStage(host, options) {
|
|
|
111
113
|
let cardTickDue = 0;
|
|
112
114
|
const staleIds = /* @__PURE__ */ new Set();
|
|
113
115
|
let resizeFrame = 0;
|
|
116
|
+
let canvasBox = null;
|
|
114
117
|
let cardEpoch = 0;
|
|
115
118
|
let surface = {
|
|
116
119
|
width: 0,
|
|
@@ -146,6 +149,7 @@ function mountBoardStage(host, options) {
|
|
|
146
149
|
return cull.ids;
|
|
147
150
|
}
|
|
148
151
|
const textureScale = () => renderCamera().zoom * (app?.renderer.resolution ?? 1);
|
|
152
|
+
const textResolution = createTextResolutionTracker(TEXTURE_SETTLE_MS, scheduleTick);
|
|
149
153
|
let refined = {
|
|
150
154
|
scale: 0,
|
|
151
155
|
geometry: 0
|
|
@@ -240,6 +244,7 @@ function mountBoardStage(host, options) {
|
|
|
240
244
|
themeKey: current.key,
|
|
241
245
|
rendererType: app?.renderer.type === RendererType.CANVAS ? "canvas" : "gpu",
|
|
242
246
|
zoom: renderCamera().zoom,
|
|
247
|
+
textResolution: textResolution.value,
|
|
243
248
|
reducedMotion: options.reducedMotion?.() ?? false,
|
|
244
249
|
labels: options.labels?.() ?? DEFAULT_BOARD_RENDER_LABELS,
|
|
245
250
|
taskRun: options.taskRun ?? (() => null),
|
|
@@ -631,6 +636,7 @@ function mountBoardStage(host, options) {
|
|
|
631
636
|
world.position.set(camera.x, camera.y);
|
|
632
637
|
world.scale.set(camera.zoom);
|
|
633
638
|
const ids = visibleIds();
|
|
639
|
+
textResolution.update(textureScale());
|
|
634
640
|
const context = renderContext(current, renderScene, frame.time);
|
|
635
641
|
if (animationId && effectSampler) {
|
|
636
642
|
context.time = editor.playhead?.time ?? frame.time;
|
|
@@ -657,7 +663,8 @@ function mountBoardStage(host, options) {
|
|
|
657
663
|
staleIds,
|
|
658
664
|
structureVersion: editor.structureVersion,
|
|
659
665
|
geometryVersion: editor.geometryVersion,
|
|
660
|
-
gestureActive: editor.gestureActive && editor.interaction.type !== "panning"
|
|
666
|
+
gestureActive: editor.gestureActive && editor.interaction.type !== "panning",
|
|
667
|
+
focus: pointToWorld(screenPoint(surface.width / 2, surface.height / 2), camera)
|
|
661
668
|
});
|
|
662
669
|
staleIds.clear();
|
|
663
670
|
if (scene.liveVersion !== liveVersion) {
|
|
@@ -692,10 +699,14 @@ function mountBoardStage(host, options) {
|
|
|
692
699
|
height: host.clientHeight
|
|
693
700
|
};
|
|
694
701
|
if (size.width <= 0 || size.height <= 0) return;
|
|
695
|
-
const
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
702
|
+
const density = host.ownerDocument.defaultView?.devicePixelRatio ?? 1;
|
|
703
|
+
const canvas = boardCanvasSize(canvasBox ?? {
|
|
704
|
+
...size,
|
|
705
|
+
density,
|
|
706
|
+
device: null
|
|
707
|
+
}, density);
|
|
708
|
+
if (canvas.width !== app.screen.width || canvas.height !== app.screen.height || canvas.resolution !== app.renderer.resolution) app.renderer.resize(canvas.width, canvas.height, canvas.resolution);
|
|
709
|
+
if (size.width !== surface.width || size.height !== surface.height) {
|
|
699
710
|
surface = size;
|
|
700
711
|
editor.surfaceSize = surface;
|
|
701
712
|
options.onSurfaceChange?.(surface);
|
|
@@ -853,14 +864,17 @@ function mountBoardStage(host, options) {
|
|
|
853
864
|
}
|
|
854
865
|
scheduleTick();
|
|
855
866
|
});
|
|
856
|
-
const resizeObserver = new ResizeObserver(
|
|
867
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
868
|
+
const entry = entries[entries.length - 1];
|
|
869
|
+
if (entry) canvasBox = measureBoardCanvasBox(entry, host.ownerDocument.defaultView?.devicePixelRatio ?? 1);
|
|
870
|
+
resize();
|
|
871
|
+
});
|
|
857
872
|
return {
|
|
858
873
|
ready: (async () => {
|
|
859
874
|
const instance = new Application();
|
|
860
875
|
try {
|
|
861
876
|
await instance.init({
|
|
862
877
|
antialias: true,
|
|
863
|
-
autoDensity: true,
|
|
864
878
|
backgroundAlpha: 0,
|
|
865
879
|
width: host.clientWidth,
|
|
866
880
|
height: host.clientHeight,
|
|
@@ -885,6 +899,8 @@ function mountBoardStage(host, options) {
|
|
|
885
899
|
position: "relative",
|
|
886
900
|
zIndex: "1",
|
|
887
901
|
display: "block",
|
|
902
|
+
width: "100%",
|
|
903
|
+
height: "100%",
|
|
888
904
|
touchAction: "none"
|
|
889
905
|
});
|
|
890
906
|
host.insertBefore(instance.canvas, views.element);
|
|
@@ -903,10 +919,14 @@ function mountBoardStage(host, options) {
|
|
|
903
919
|
world: nodes,
|
|
904
920
|
overlay,
|
|
905
921
|
getRenderer: resolveRenderer,
|
|
906
|
-
|
|
922
|
+
requestFrame: scheduleTick
|
|
907
923
|
});
|
|
908
924
|
for (const [type, listener, listenerOptions] of listeners) host.addEventListener(type, listener, listenerOptions);
|
|
909
|
-
|
|
925
|
+
try {
|
|
926
|
+
resizeObserver.observe(instance.canvas, { box: "device-pixel-content-box" });
|
|
927
|
+
} catch {
|
|
928
|
+
resizeObserver.observe(instance.canvas);
|
|
929
|
+
}
|
|
910
930
|
const window = host.ownerDocument.defaultView;
|
|
911
931
|
if (window) stopDensity = observeDisplayDensity(window, resize);
|
|
912
932
|
resize();
|
|
@@ -990,6 +1010,7 @@ function mountBoardStage(host, options) {
|
|
|
990
1010
|
pointers.cancel();
|
|
991
1011
|
stopMarch();
|
|
992
1012
|
stopRefine();
|
|
1013
|
+
textResolution.dispose();
|
|
993
1014
|
scriptPointers.clear();
|
|
994
1015
|
stopTransforms?.();
|
|
995
1016
|
scriptObservers.clear();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/board/stage/text-resolution-tracker.d.ts
|
|
2
|
+
declare function createTextResolutionTracker(settleMs: number, onSettle: () => void): {
|
|
3
|
+
readonly value: number;
|
|
4
|
+
update(next: number): number;
|
|
5
|
+
dispose(): void;
|
|
6
|
+
};
|
|
7
|
+
//#endregion
|
|
8
|
+
export { createTextResolutionTracker };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { boardTextResolution } from "../render/text-resolution.js";
|
|
2
|
+
//#region src/board/stage/text-resolution-tracker.ts
|
|
3
|
+
function createTextResolutionTracker(settleMs, onSettle) {
|
|
4
|
+
let target = 0;
|
|
5
|
+
let value = 0;
|
|
6
|
+
let settled = false;
|
|
7
|
+
let timer = null;
|
|
8
|
+
return {
|
|
9
|
+
get value() {
|
|
10
|
+
return value;
|
|
11
|
+
},
|
|
12
|
+
update(next) {
|
|
13
|
+
if (next !== target) {
|
|
14
|
+
target = next;
|
|
15
|
+
settled = false;
|
|
16
|
+
if (timer !== null) clearTimeout(timer);
|
|
17
|
+
timer = setTimeout(() => {
|
|
18
|
+
timer = null;
|
|
19
|
+
settled = true;
|
|
20
|
+
onSettle();
|
|
21
|
+
}, settleMs);
|
|
22
|
+
}
|
|
23
|
+
value = boardTextResolution(target, value, settled);
|
|
24
|
+
return value;
|
|
25
|
+
},
|
|
26
|
+
dispose() {
|
|
27
|
+
if (timer !== null) clearTimeout(timer);
|
|
28
|
+
timer = null;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { createTextResolutionTracker };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { O as readRtcTransportStats, k as waitForIceGathering } from "./http.js";
|
|
2
|
-
import {
|
|
2
|
+
import { c as HttpError, p as REALTIME_ROOM_EVENT_NAME_PATTERN } from "./http-error.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region \0rolldown/runtime.js
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
@@ -509,4 +509,40 @@ var HttpTransport = class {
|
|
|
509
509
|
}
|
|
510
510
|
};
|
|
511
511
|
//#endregion
|
|
512
|
-
|
|
512
|
+
//#region src/http-error.ts
|
|
513
|
+
/** Shared HTTP error code for every plan entitlement gate (402). */
|
|
514
|
+
const FEATURE_NOT_ENTITLED_ERROR_CODE = "feature_not_entitled";
|
|
515
|
+
/** Shared HTTP error code for a blocked usage gate (negative balance limit, 402). */
|
|
516
|
+
const BILLING_ACCESS_BLOCKED_ERROR_CODE = "billing_credit_limit_exceeded";
|
|
517
|
+
function isHttpErrorCode(error, code) {
|
|
518
|
+
return error instanceof HttpError && error.code === code;
|
|
519
|
+
}
|
|
520
|
+
function isFeatureNotEntitledError(error) {
|
|
521
|
+
return isHttpErrorCode(error, FEATURE_NOT_ENTITLED_ERROR_CODE);
|
|
522
|
+
}
|
|
523
|
+
function isBillingAccessBlockedError(error) {
|
|
524
|
+
return isHttpErrorCode(error, BILLING_ACCESS_BLOCKED_ERROR_CODE);
|
|
525
|
+
}
|
|
526
|
+
function isBillingAccessBlockedCode(code) {
|
|
527
|
+
return code === BILLING_ACCESS_BLOCKED_ERROR_CODE;
|
|
528
|
+
}
|
|
529
|
+
const isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
530
|
+
function isBillingConversionIntent(value) {
|
|
531
|
+
if (!isRecord(value)) return false;
|
|
532
|
+
return (value.level === "soft" || value.level === "hard") && typeof value.title === "string" && typeof value.message === "string" && isRecord(value.primaryAction) && value.primaryAction.action === "open_billing_conversion";
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Extracts the standard `billing` payload from a response body, an
|
|
536
|
+
* `HttpError`, or a bare billing payload (e.g. a realtime error event's
|
|
537
|
+
* `billing` field). Returns `null` unless it carries a valid conversion
|
|
538
|
+
* intent.
|
|
539
|
+
*/
|
|
540
|
+
function extractBillingPayload(source) {
|
|
541
|
+
const root = source instanceof HttpError ? source.body : source;
|
|
542
|
+
if (!isRecord(root)) return null;
|
|
543
|
+
const billing = isRecord(root.billing) ? root.billing : root;
|
|
544
|
+
if (!isBillingConversionIntent(billing.conversion)) return null;
|
|
545
|
+
return billing;
|
|
546
|
+
}
|
|
547
|
+
//#endregion
|
|
548
|
+
export { isRequestSourceClientId as A, isUuidLike as B, getSessionTurnPatchStreamKey as C, COHUB_SOURCE_HEADER_NAMES as D, COHUB_SOURCE_HEADER as E, parseRequestSourceFromHeaders as F, readRequestSourceFromEnv as I, requestSourceToHeaders as L, isRequestSourceUuid as M, mergeRequestSourceIntoMeta as N, REQUEST_SOURCE_VIA_MAX_LENGTH as O, normalizeRequestSource as P, resolveRequestSourceChannel as R, getRealtimeSpaceRoom as S, normalizeRealtimeRooms as T, isUuidOrShortUuid as V, WS_REALTIME_ROOM_CAPABILITY as _, isBillingAccessBlockedError as a, WS_ROOM_SUBSCRIPTION_CAPABILITY as b, HttpError as c, matchesUnauthorizedErrorToken as d, sanitizeAccessToken as f, WS_COMPACT_STREAM_CAPABILITY as g, WS_BOARD_AWARENESS_CAPABILITY as h, isBillingAccessBlockedCode as i, isRequestSourceEmpty as j, hasRequestSourceIdentity as k, HttpTransport as l, REALTIME_ROOM_MAX_PAYLOAD_BYTES as m, FEATURE_NOT_ENTITLED_ERROR_CODE as n, isFeatureNotEntitledError as o, REALTIME_ROOM_EVENT_NAME_PATTERN as p, extractBillingPayload as r, isHttpErrorCode as s, BILLING_ACCESS_BLOCKED_ERROR_CODE as t, joinApiUrl as u, WS_REALTIME_ROOM_HISTORY_CAPABILITY as v, isRealtimeDomain as w, getRealtimeBoardRoom as x, WS_REALTIME_ROOM_RTC_CAPABILITY as y, isUuid as z };
|