@neta-art/cohub 8.23.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/board/animation.d.ts +57 -181
- package/dist/board/animation.js +374 -220
- package/dist/board/core/arrow-geometry.d.ts +33 -18
- package/dist/board/core/arrow-geometry.js +283 -52
- package/dist/board/core/draw-geometry.d.ts +22 -36
- package/dist/board/core/draw-geometry.js +148 -85
- package/dist/board/core/export-assets.d.ts +3 -6
- package/dist/board/core/export-assets.js +0 -2
- package/dist/board/core/export-plan.d.ts +12 -63
- package/dist/board/core/export-plan.js +55 -94
- package/dist/board/core/palette.d.ts +4 -21
- package/dist/board/core/palette.js +8 -13
- package/dist/board/core/scene.d.ts +33 -0
- package/dist/board/core/scene.js +302 -0
- package/dist/board/core/shape-definition.d.ts +12 -24
- package/dist/board/core/shape-definition.js +0 -19
- package/dist/board/core/shape-types.d.ts +6 -120
- package/dist/board/core/shape-types.js +5 -5
- package/dist/board/core/text-metrics.d.ts +8 -11
- package/dist/board/core/text-metrics.js +5 -34
- package/dist/board/core/tool-styles.d.ts +6 -16
- package/dist/board/core/tool-styles.js +17 -25
- package/dist/board/export/index.d.ts +10 -17
- package/dist/board/export/index.js +22 -21
- package/dist/board/export/scene.d.ts +10 -16
- package/dist/board/export/scene.js +17 -30
- package/dist/board/geometry.d.ts +27 -56
- package/dist/board/geometry.js +7 -67
- package/dist/board/headless/index.d.ts +7 -27
- package/dist/board/headless/index.js +20 -19
- package/dist/board/headless/sketch.d.ts +19 -0
- package/dist/board/headless/sketch.js +146 -0
- package/dist/board/image-key.d.ts +2 -12
- package/dist/board/image-key.js +3 -13
- package/dist/board/index.d.ts +18 -20
- package/dist/board/index.js +17 -17
- package/dist/board/media-playback.d.ts +3 -6
- package/dist/board/media-playback.js +6 -6
- package/dist/board/presets.d.ts +150 -0
- package/dist/board/presets.js +226 -0
- package/dist/board/render/board-background.d.ts +5 -3
- package/dist/board/render/board-background.js +8 -7
- package/dist/board/render/clip.d.ts +14 -0
- package/dist/board/render/clip.js +47 -0
- package/dist/board/render/index.d.ts +8 -5
- package/dist/board/render/index.js +7 -4
- package/dist/board/render/media-interaction.d.ts +4 -5
- package/dist/board/render/media-interaction.js +1 -2
- package/dist/board/render/palette.d.ts +6 -2
- package/dist/board/render/palette.js +7 -1
- package/dist/board/render/renderers/arrow-card-renderer.js +69 -71
- package/dist/board/render/renderers/audio-card-renderer.js +3 -3
- package/dist/board/render/renderers/base-card-renderer.d.ts +6 -17
- package/dist/board/render/renderers/base-card-renderer.js +1 -24
- package/dist/board/render/renderers/board-renderer-registry.d.ts +20 -50
- package/dist/board/render/renderers/board-renderer-registry.js +6 -7
- package/dist/board/render/renderers/draw-card-renderer.js +89 -56
- package/dist/board/render/renderers/effect-card-renderer.d.ts +6 -0
- package/dist/board/render/renderers/effect-card-renderer.js +197 -0
- package/dist/board/render/renderers/far-plate.d.ts +4 -21
- package/dist/board/render/renderers/far-plate.js +15 -34
- package/dist/board/render/renderers/file-card-renderer.d.ts +4 -25
- package/dist/board/render/renderers/file-card-renderer.js +8 -58
- package/dist/board/render/renderers/frame-card-renderer.js +28 -28
- package/dist/board/render/renderers/image-card-renderer.d.ts +4 -3
- package/dist/board/render/renderers/image-card-renderer.js +0 -9
- package/dist/board/render/renderers/shape-card-renderer.d.ts +9 -0
- package/dist/board/render/renderers/shape-card-renderer.js +240 -0
- package/dist/board/render/renderers/sketch-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/sketch-card-renderer.js +98 -0
- package/dist/board/render/renderers/task-card-renderer.d.ts +0 -1
- package/dist/board/render/renderers/task-card-renderer.js +14 -16
- package/dist/board/render/renderers/text-card-renderer.d.ts +1 -3
- package/dist/board/render/renderers/text-card-renderer.js +56 -46
- package/dist/board/render/renderers/unknown-card-renderer.js +4 -10
- package/dist/board/render/renderers/video-card-renderer.d.ts +4 -2
- package/dist/board/render/renderers/video-card-renderer.js +2 -1
- package/dist/board/render/sketch-host.d.ts +14 -0
- package/dist/board/render/sketch-host.js +216 -0
- package/dist/board/render/stroke.d.ts +12 -0
- package/dist/board/render/stroke.js +90 -0
- package/dist/board/render/text-measurement.d.ts +6 -26
- package/dist/board/render/text-measurement.js +11 -30
- package/dist/board/replay.d.ts +6 -31
- package/dist/board/replay.js +45 -203
- package/dist/board/runtime/board-player.d.ts +24 -0
- package/dist/board/runtime/board-player.js +161 -0
- package/dist/board/task.d.ts +2 -10
- package/dist/board/task.js +1 -10
- package/dist/chunks/environment.d.ts +2296 -3864
- package/dist/chunks/host-bridge.d.ts +253 -0
- package/dist/chunks/http.d.ts +238 -58
- package/dist/chunks/http.js +1061 -1034
- package/dist/chunks/transport.js +113 -59
- package/dist/chunks/websocket.d.ts +1 -1
- package/dist/chunks/websocket.js +1 -1
- package/dist/host-bridge.d.ts +54 -0
- package/dist/host-bridge.js +322 -0
- package/dist/http.d.ts +3 -3
- package/dist/index.d.ts +76 -193
- package/dist/index.js +2037 -1191
- package/dist/protocol/dist/board-constants.d.ts +1 -29
- package/dist/protocol/dist/board-constants.js +2 -115
- package/dist/protocol/dist/board-layout.d.ts +59 -0
- package/dist/protocol/dist/board-layout.js +482 -0
- package/dist/protocol/dist/board-model.d.ts +2052 -0
- package/dist/protocol/dist/board-model.js +592 -0
- package/dist/protocol/dist/board-patch.d.ts +86 -0
- package/dist/protocol/dist/board-patch.js +734 -0
- package/dist/protocol/dist/board-schema.d.ts +33 -0
- package/dist/protocol/dist/board-schema.js +59 -0
- package/dist/protocol/dist/board-upgrade.d.ts +1 -0
- package/dist/protocol/dist/board-url.d.ts +1 -3
- package/dist/protocol/dist/board-url.js +2 -2
- package/dist/protocol/dist/board.d.ts +37 -232
- package/dist/protocol/dist/board.js +62 -160
- package/dist/protocol/dist/display/actions.d.ts +1 -0
- package/dist/protocol/dist/display/index.d.ts +2 -0
- package/dist/protocol/dist/index.d.ts +11 -12
- package/dist/protocol/dist/model/metrics.d.ts +2 -0
- package/dist/protocol/dist/model/session.d.ts +2 -1
- package/dist/protocol/dist/realtime/types.d.ts +2 -2
- package/dist/protocol/dist/runtime/index.d.ts +1 -0
- package/dist/space-picker.d.ts +1 -0
- package/dist/space-picker.js +1 -0
- package/dist/types.d.ts +2 -5
- package/package.json +9 -1
- package/dist/board/core/connections.d.ts +0 -108
- package/dist/board/core/connections.js +0 -455
- package/dist/board/mutation.d.ts +0 -6
- package/dist/board/mutation.js +0 -14
- package/dist/board/render/connection-layer.d.ts +0 -53
- package/dist/board/render/connection-layer.js +0 -375
- package/dist/board/render/renderers/geo-card-renderer.d.ts +0 -5
- package/dist/board/render/renderers/geo-card-renderer.js +0 -127
- package/dist/board/semantic-document.d.ts +0 -37
- package/dist/board/semantic-document.js +0 -288
- package/dist/board/semantic-mutation.d.ts +0 -10
- package/dist/board/semantic-mutation.js +0 -209
- package/dist/protocol/dist/board-animation.js +0 -34
- package/dist/protocol/dist/board-authoring.d.ts +0 -1326
- package/dist/protocol/dist/board-authoring.js +0 -302
- package/dist/protocol/dist/board-capability-registry.d.ts +0 -2
- package/dist/protocol/dist/board-capability-registry.js +0 -123
- package/dist/protocol/dist/board-codec.d.ts +0 -3
- package/dist/protocol/dist/board-codec.js +0 -383
- package/dist/protocol/dist/board-composition.d.ts +0 -383
- package/dist/protocol/dist/board-composition.js +0 -310
- package/dist/protocol/dist/board-connection.d.ts +0 -317
- package/dist/protocol/dist/board-connection.js +0 -219
- package/dist/protocol/dist/board-document.d.ts +0 -1566
- package/dist/protocol/dist/board-document.js +0 -456
- package/dist/protocol/dist/board-effect.d.ts +0 -96
- package/dist/protocol/dist/board-effect.js +0 -54
- package/dist/protocol/dist/board-geometry.d.ts +0 -15
- package/dist/protocol/dist/board-geometry.js +0 -137
- package/dist/protocol/dist/board-node.d.ts +0 -8
- package/dist/protocol/dist/board-node.js +0 -252
- /package/dist/protocol/dist/{board-animation.d.ts → host-bridge.d.ts} +0 -0
package/dist/board/geometry.js
CHANGED
|
@@ -44,17 +44,9 @@ const DEFAULT_BOARD_VIEWPORT = {
|
|
|
44
44
|
y: 0,
|
|
45
45
|
zoom: 1
|
|
46
46
|
};
|
|
47
|
-
/** Smallest an item can be resized to, in world units. */
|
|
48
47
|
const MIN_ITEM_SIZE = 24;
|
|
49
|
-
/** Extra world-space padding applied when fitting content into view. */
|
|
50
48
|
const FIT_PADDING = 64;
|
|
51
|
-
/**
|
|
52
|
-
* Fraction of the viewport's larger dimension used as an off-screen margin for
|
|
53
|
-
* both card culling and texture preloading. Keeping them equal means a card's
|
|
54
|
-
* texture is already requested before the card scrolls into view.
|
|
55
|
-
*/
|
|
56
49
|
const VIEWPORT_MARGIN_RATIO = .5;
|
|
57
|
-
/** Viewport plus margin, snapped to a grid so small pans return the same rect. */
|
|
58
50
|
function stableCullRect(view) {
|
|
59
51
|
const margin = Math.max(view.width, view.height, 1) * VIEWPORT_MARGIN_RATIO;
|
|
60
52
|
const step = 2 ** Math.floor(Math.log2(margin / 2));
|
|
@@ -73,7 +65,6 @@ function clampZoom(zoom) {
|
|
|
73
65
|
const value = Number.isFinite(zoom) ? zoom : DEFAULT_BOARD_VIEWPORT.zoom;
|
|
74
66
|
return Math.min(8, Math.max(MIN_BOARD_ZOOM, value));
|
|
75
67
|
}
|
|
76
|
-
/** Keep transient camera state finite without discarding its last valid axes. */
|
|
77
68
|
function normalizeViewport(viewport, fallback = DEFAULT_BOARD_VIEWPORT) {
|
|
78
69
|
const fallbackX = Number.isFinite(fallback.x) ? fallback.x : DEFAULT_BOARD_VIEWPORT.x;
|
|
79
70
|
const fallbackY = Number.isFinite(fallback.y) ? fallback.y : DEFAULT_BOARD_VIEWPORT.y;
|
|
@@ -166,7 +157,6 @@ function rotatePointAround(point, center, angleRad) {
|
|
|
166
157
|
y: center.y + dx * sin + dy * cos
|
|
167
158
|
};
|
|
168
159
|
}
|
|
169
|
-
/** Axis-aligned bounding box of a (possibly rotated) frame. */
|
|
170
160
|
function itemBounds(frame) {
|
|
171
161
|
const rotation = frame.rotation || 0;
|
|
172
162
|
if (rotation === 0) return frameRect(frame);
|
|
@@ -188,7 +178,6 @@ function itemBounds(frame) {
|
|
|
188
178
|
function selectionBounds(frames) {
|
|
189
179
|
return unionRects(frames.map(itemBounds));
|
|
190
180
|
}
|
|
191
|
-
/** Exact point-in-frame test, accounting for rotation. */
|
|
192
181
|
function frameContainsPoint(frame, point) {
|
|
193
182
|
const rect = frameRect(frame);
|
|
194
183
|
const rotation = frame.rotation || 0;
|
|
@@ -196,7 +185,6 @@ function frameContainsPoint(frame, point) {
|
|
|
196
185
|
if (rotation !== 0) local = rotatePointAround(point, rectCenter(rect), -degToRad(rotation));
|
|
197
186
|
return rectContainsPoint(rect, local);
|
|
198
187
|
}
|
|
199
|
-
/** World position of a resize handle on a single (possibly rotated) frame. */
|
|
200
188
|
function frameHandlePosition(frame, handle) {
|
|
201
189
|
const rect = frameRect(frame);
|
|
202
190
|
const position = handlePosition(rect, handle);
|
|
@@ -204,7 +192,6 @@ function frameHandlePosition(frame, handle) {
|
|
|
204
192
|
if (rotation === 0) return position;
|
|
205
193
|
return rotatePointAround(position, rectCenter(rect), degToRad(rotation));
|
|
206
194
|
}
|
|
207
|
-
/** Corners in clockwise order, following the frame's rotation. */
|
|
208
195
|
function frameCorners(frame) {
|
|
209
196
|
return [
|
|
210
197
|
"nw",
|
|
@@ -213,9 +200,7 @@ function frameCorners(frame) {
|
|
|
213
200
|
"sw"
|
|
214
201
|
].map((handle) => frameHandlePosition(frame, handle));
|
|
215
202
|
}
|
|
216
|
-
/** Screen-space radius (px) within which a pointer grabs a handle. */
|
|
217
203
|
const HANDLE_HIT_RADIUS = 8;
|
|
218
|
-
/** Screen-space offset (px) from the selection's lower-facing edge. */
|
|
219
204
|
const ROTATION_HANDLE_OFFSET = 28;
|
|
220
205
|
function frameRayIntersection(frame, pointer) {
|
|
221
206
|
const center = rectCenter(frame);
|
|
@@ -257,7 +242,6 @@ const EDGE_RESIZE_HANDLES = [
|
|
|
257
242
|
"s",
|
|
258
243
|
"w"
|
|
259
244
|
];
|
|
260
|
-
/** Screen-space depth of the rotation zone outside each resize corner. */
|
|
261
245
|
const CORNER_ROTATION_ZONE_WIDTH = 16;
|
|
262
246
|
const RESIZE_HANDLES = [
|
|
263
247
|
"nw",
|
|
@@ -269,10 +253,6 @@ const RESIZE_HANDLES = [
|
|
|
269
253
|
"sw",
|
|
270
254
|
"w"
|
|
271
255
|
];
|
|
272
|
-
/**
|
|
273
|
-
* Hit-test the outward quadrant around each corner, outside its resize target.
|
|
274
|
-
* This gives fine pointers Figma-style corner rotation without adding chrome.
|
|
275
|
-
*/
|
|
276
256
|
function frameCornerRotationHandleAt(frame, point, zoom, screenRadius = 8) {
|
|
277
257
|
const rect = frameRect(frame);
|
|
278
258
|
const center = rectCenter(rect);
|
|
@@ -291,10 +271,6 @@ function frameCornerRotationHandleAt(frame, point, zoom, screenRadius = 8) {
|
|
|
291
271
|
}
|
|
292
272
|
return null;
|
|
293
273
|
}
|
|
294
|
-
/**
|
|
295
|
-
* Hit-test the continuous edge zones of a rotated frame. Corners are inset so
|
|
296
|
-
* their resize handles retain priority even on small nodes.
|
|
297
|
-
*/
|
|
298
274
|
function frameEdgeHandleAt(frame, point, zoom, screenRadius = 8) {
|
|
299
275
|
const rect = frameRect(frame);
|
|
300
276
|
const center = rectCenter(rect);
|
|
@@ -327,7 +303,6 @@ function frameEdgeHandleAt(frame, point, zoom, screenRadius = 8) {
|
|
|
327
303
|
}
|
|
328
304
|
return closest;
|
|
329
305
|
}
|
|
330
|
-
/** Direction of a handle from the rect center: -1, 0, or 1 per axis. */
|
|
331
306
|
const HANDLE_DIRECTION = {
|
|
332
307
|
nw: {
|
|
333
308
|
x: -1,
|
|
@@ -362,12 +337,10 @@ const HANDLE_DIRECTION = {
|
|
|
362
337
|
y: 0
|
|
363
338
|
}
|
|
364
339
|
};
|
|
365
|
-
/** Position of a handle on an unrotated rect. */
|
|
366
340
|
function handlePosition(rect, handle) {
|
|
367
341
|
const direction = HANDLE_DIRECTION[handle];
|
|
368
342
|
return worldPoint(rect.x + rect.width / 2 + direction.x * rect.width / 2, rect.y + rect.height / 2 + direction.y * rect.height / 2);
|
|
369
343
|
}
|
|
370
|
-
/** Resize a frame to exact dimensions while keeping the opposite handle fixed. */
|
|
371
344
|
function resizeFrameToSize(frame, handle, width, height) {
|
|
372
345
|
const direction = HANDLE_DIRECTION[handle];
|
|
373
346
|
const rect = frameRect(frame);
|
|
@@ -397,11 +370,6 @@ function resizeFrameToSize(frame, handle, width, height) {
|
|
|
397
370
|
rotation: frame.rotation || 0
|
|
398
371
|
};
|
|
399
372
|
}
|
|
400
|
-
/**
|
|
401
|
-
* Resize a frame by dragging a handle to a world-space pointer position.
|
|
402
|
-
* The edge/corner opposite the handle stays anchored. Works correctly for
|
|
403
|
-
* rotated frames by performing the resize in the frame's local space.
|
|
404
|
-
*/
|
|
405
373
|
function resizeFrame(frame, handle, pointer, minSize = 24, keepAspect = false) {
|
|
406
374
|
const direction = HANDLE_DIRECTION[handle];
|
|
407
375
|
const rect = frameRect(frame);
|
|
@@ -434,10 +402,6 @@ function resizeFrame(frame, handle, pointer, minSize = 24, keepAspect = false) {
|
|
|
434
402
|
}
|
|
435
403
|
return resizeFrameToSize(frame, handle, width, height);
|
|
436
404
|
}
|
|
437
|
-
/**
|
|
438
|
-
* Proportionally scale a group of frames by dragging a corner handle of their
|
|
439
|
-
* combined bounds. Individual rotations are preserved.
|
|
440
|
-
*/
|
|
441
405
|
function scaleFrames(frames, bounds, handle, pointer, minSize = 24, scaleRange) {
|
|
442
406
|
const direction = HANDLE_DIRECTION[handle];
|
|
443
407
|
if (direction.x === 0 || direction.y === 0) return frames;
|
|
@@ -470,14 +434,9 @@ function scaleFrames(frames, bounds, handle, pointer, minSize = 24, scaleRange)
|
|
|
470
434
|
};
|
|
471
435
|
});
|
|
472
436
|
}
|
|
473
|
-
/** Angle (degrees) from a center to a world-space pointer. */
|
|
474
437
|
function angleFromCenter(center, pointer) {
|
|
475
438
|
return radToDeg(Math.atan2(pointer.y - center.y, pointer.x - center.x));
|
|
476
439
|
}
|
|
477
|
-
/**
|
|
478
|
-
* Rotate a set of frames around a pivot by a delta (degrees). Each frame's
|
|
479
|
-
* center orbits the pivot and its own rotation increases by the delta.
|
|
480
|
-
*/
|
|
481
440
|
function rotateFrames(frames, pivot, deltaDeg) {
|
|
482
441
|
const rad = degToRad(deltaDeg);
|
|
483
442
|
return frames.map((frame) => {
|
|
@@ -491,14 +450,12 @@ function rotateFrames(frames, pivot, deltaDeg) {
|
|
|
491
450
|
};
|
|
492
451
|
});
|
|
493
452
|
}
|
|
494
|
-
/** Normalize a rotation into the [-180, 180] range for tidy persistence. */
|
|
495
453
|
function normalizeRotation(degrees) {
|
|
496
454
|
let value = degrees % 360;
|
|
497
455
|
if (value > 180) value -= 360;
|
|
498
456
|
if (value < -180) value += 360;
|
|
499
457
|
return Math.abs(value) < .01 ? 0 : value;
|
|
500
458
|
}
|
|
501
|
-
/** Convert a surface-relative screen point into world space. */
|
|
502
459
|
function pointToWorld(point, viewport) {
|
|
503
460
|
return worldPoint((point.x - viewport.x) / viewport.zoom, (point.y - viewport.y) / viewport.zoom);
|
|
504
461
|
}
|
|
@@ -508,7 +465,6 @@ function worldToScreen(point, viewport) {
|
|
|
508
465
|
function screenToWorld(clientX, clientY, rect, viewport) {
|
|
509
466
|
return pointToWorld(screenPoint(clientX - rect.left, clientY - rect.top), viewport);
|
|
510
467
|
}
|
|
511
|
-
/** Zoom while keeping a surface-relative screen point fixed under the cursor. */
|
|
512
468
|
function zoomAround(viewport, anchor, nextZoom) {
|
|
513
469
|
const current = normalizeViewport(viewport);
|
|
514
470
|
const zoom = Number.isFinite(nextZoom) ? clampZoom(nextZoom) : current.zoom;
|
|
@@ -534,10 +490,7 @@ function cameraForState(state, surface) {
|
|
|
534
490
|
zoom
|
|
535
491
|
});
|
|
536
492
|
}
|
|
537
|
-
function cameraForRect(content, surface, options = {
|
|
538
|
-
fit: "contain",
|
|
539
|
-
padding: 32
|
|
540
|
-
}) {
|
|
493
|
+
function cameraForRect(content, surface, options = {}) {
|
|
541
494
|
const padding = Math.max(0, Number.isFinite(options.padding) ? options.padding : 32);
|
|
542
495
|
const availableW = Math.max(1, surface.width - padding * 2);
|
|
543
496
|
const availableH = Math.max(1, surface.height - padding * 2);
|
|
@@ -553,26 +506,14 @@ function cameraForRect(content, surface, options = {
|
|
|
553
506
|
}, surface);
|
|
554
507
|
}
|
|
555
508
|
function rectForCameraFocus(focus, getFrame) {
|
|
556
|
-
if (focus
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
return frame ? itemBounds(frame) : null;
|
|
560
|
-
}
|
|
561
|
-
if (focus.type === "frame") {
|
|
562
|
-
const frame = getFrame(focus.frameId);
|
|
563
|
-
return frame ? itemBounds(frame) : null;
|
|
564
|
-
}
|
|
565
|
-
const frames = focus.itemIds.flatMap((id) => {
|
|
566
|
-
const frame = getFrame(id);
|
|
567
|
-
return frame ? [frame] : [];
|
|
568
|
-
});
|
|
569
|
-
return frames.length === focus.itemIds.length ? selectionBounds(frames) : null;
|
|
509
|
+
if (typeof focus !== "string") return focus;
|
|
510
|
+
const frame = getFrame(focus);
|
|
511
|
+
return frame ? itemBounds(frame) : null;
|
|
570
512
|
}
|
|
571
|
-
function cameraForFocus(
|
|
572
|
-
const rect = rectForCameraFocus(
|
|
573
|
-
return rect ? cameraForRect(rect, surface,
|
|
513
|
+
function cameraForFocus(focus, getFrame, surface, options = {}) {
|
|
514
|
+
const rect = rectForCameraFocus(focus, getFrame);
|
|
515
|
+
return rect ? cameraForRect(rect, surface, options) : null;
|
|
574
516
|
}
|
|
575
|
-
/** Compute a viewport that frames the given world rect within the surface. */
|
|
576
517
|
function fitToContent(content, surface, padding = 64) {
|
|
577
518
|
const availableW = Math.max(1, surface.width - padding * 2);
|
|
578
519
|
const availableH = Math.max(1, surface.height - padding * 2);
|
|
@@ -583,7 +524,6 @@ function fitToContent(content, surface, padding = 64) {
|
|
|
583
524
|
zoom
|
|
584
525
|
};
|
|
585
526
|
}
|
|
586
|
-
/** World-space rectangle currently visible in the board stage. */
|
|
587
527
|
function visibleWorldRect(viewport, surfaceWidth, surfaceHeight) {
|
|
588
528
|
const width = Math.max(0, surfaceWidth);
|
|
589
529
|
const height = Math.max(0, surfaceHeight);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { BoardDocument } from "../../protocol/dist/board-
|
|
1
|
+
import { BoardDocument } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import "../../protocol/dist/index.js";
|
|
3
|
+
import { BoardSketchHost } from "../render/renderers/board-renderer-registry.js";
|
|
2
4
|
import { BoardExportOptions, BoardExportResult } from "../export/index.js";
|
|
5
|
+
import { createBoardHeadlessSketchHost } from "./sketch.js";
|
|
3
6
|
import { Renderer } from "pixi.js";
|
|
4
7
|
//#region src/board/headless/index.d.ts
|
|
5
8
|
type HeadlessCanvasModule = {
|
|
@@ -8,51 +11,29 @@ type HeadlessCanvasModule = {
|
|
|
8
11
|
GlobalFonts: {
|
|
9
12
|
registerFromPath: (path: string, name?: string) => unknown;
|
|
10
13
|
has: (name: string) => boolean;
|
|
14
|
+
setAlias?: (fontName: string, alias: string) => boolean;
|
|
11
15
|
};
|
|
12
16
|
};
|
|
13
17
|
type BoardHeadlessFont = {
|
|
14
|
-
/** Absolute path to a font file (woff2, ttf and otf all work). */
|
|
15
18
|
path: string;
|
|
16
|
-
/** Family name renderers should ask for. Defaults to the board text family. */
|
|
17
19
|
family?: string;
|
|
18
20
|
};
|
|
19
21
|
type BoardHeadlessRendererOptions = {
|
|
20
|
-
/**
|
|
21
|
-
* Fonts to register before the first measurement. Board text asks for
|
|
22
|
-
* "Geist"; without a matching family the platform substitutes one, which
|
|
23
|
-
* changes glyph shapes and text metrics.
|
|
24
|
-
*/
|
|
25
22
|
fonts?: BoardHeadlessFont[];
|
|
26
|
-
/** Override the canvas module (tests, or a pre-imported instance). */
|
|
27
23
|
canvasModule?: HeadlessCanvasModule;
|
|
28
24
|
};
|
|
29
|
-
/**
|
|
30
|
-
* A decoded image, opaque to callers.
|
|
31
|
-
*
|
|
32
|
-
* Node consumers (the CLI) should not need PixiJS in their own type surface just
|
|
33
|
-
* to hand textures back to the exporter, so the concrete `Texture` stays inside
|
|
34
|
-
* this module.
|
|
35
|
-
*/
|
|
36
25
|
type BoardHeadlessTexture = {
|
|
37
26
|
readonly __boardTexture: unique symbol;
|
|
38
27
|
};
|
|
39
28
|
type BoardHeadlessRenderer = {
|
|
40
29
|
renderer: Renderer;
|
|
41
30
|
canvasModule: HeadlessCanvasModule;
|
|
42
|
-
/** Decode image bytes into a texture the exporter accepts. */
|
|
43
31
|
decodeImage: (bytes: Uint8Array, mimeType?: string) => Promise<BoardHeadlessTexture>;
|
|
44
32
|
destroy: () => void;
|
|
45
33
|
};
|
|
46
|
-
/**
|
|
47
|
-
* Create a Canvas2D renderer backed by Skia.
|
|
48
|
-
*
|
|
49
|
-
* `skipExtensionImports` is essential: it stops Pixi from auto-loading its
|
|
50
|
-
* browser environment bundle, which would pull in DOM-only pipes.
|
|
51
|
-
*/
|
|
52
34
|
declare function createBoardHeadlessRenderer(options?: BoardHeadlessRendererOptions): Promise<BoardHeadlessRenderer>;
|
|
53
35
|
type BoardHeadlessExportFormat = "png" | "jpeg" | "webp";
|
|
54
36
|
type BoardHeadlessExportOptions = Omit<BoardExportOptions, "textures" | "backgroundImage"> & {
|
|
55
|
-
/** Textures from `decodeImage`, keyed by image key. */
|
|
56
37
|
textures?: Map<string, BoardHeadlessTexture>;
|
|
57
38
|
backgroundImage?: {
|
|
58
39
|
texture: BoardHeadlessTexture;
|
|
@@ -60,8 +41,8 @@ type BoardHeadlessExportOptions = Omit<BoardExportOptions, "textures" | "backgro
|
|
|
60
41
|
position: "center" | "top" | "bottom" | "left" | "right";
|
|
61
42
|
opacity: number;
|
|
62
43
|
};
|
|
44
|
+
sketches?: BoardSketchHost;
|
|
63
45
|
format?: BoardHeadlessExportFormat;
|
|
64
|
-
/** JPEG/WebP quality, 0–1. Ignored for PNG. */
|
|
65
46
|
quality?: number;
|
|
66
47
|
};
|
|
67
48
|
type BoardHeadlessExportResult = Omit<BoardExportResult, "canvas"> & {
|
|
@@ -69,7 +50,6 @@ type BoardHeadlessExportResult = Omit<BoardExportResult, "canvas"> & {
|
|
|
69
50
|
format: BoardHeadlessExportFormat;
|
|
70
51
|
};
|
|
71
52
|
declare function boardHeadlessMimeType(format: BoardHeadlessExportFormat): string;
|
|
72
|
-
/** Render a document to encoded image bytes. Returns null for an empty region. */
|
|
73
53
|
declare function exportBoardImageBytes(headless: BoardHeadlessRenderer, document: BoardDocument, options?: BoardHeadlessExportOptions): BoardHeadlessExportResult | null;
|
|
74
54
|
//#endregion
|
|
75
|
-
export { BoardHeadlessExportFormat, BoardHeadlessExportOptions, BoardHeadlessExportResult, BoardHeadlessFont, BoardHeadlessRenderer, BoardHeadlessRendererOptions, BoardHeadlessTexture, HeadlessCanvasModule, boardHeadlessMimeType, createBoardHeadlessRenderer, exportBoardImageBytes };
|
|
55
|
+
export { BoardHeadlessExportFormat, BoardHeadlessExportOptions, BoardHeadlessExportResult, BoardHeadlessFont, BoardHeadlessRenderer, BoardHeadlessRendererOptions, BoardHeadlessTexture, HeadlessCanvasModule, boardHeadlessMimeType, createBoardHeadlessRenderer, createBoardHeadlessSketchHost, exportBoardImageBytes };
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import "../../protocol/dist/board-constants.js";
|
|
2
2
|
import { installBoardTextMeasurement } from "../render/text-measurement.js";
|
|
3
3
|
import { renderBoardExport } from "../export/index.js";
|
|
4
|
+
import { createBoardHeadlessSketchHost } from "./sketch.js";
|
|
5
|
+
import { CanvasGraphicsContextSystem, CanvasGraphicsPipe, CanvasRenderer, CanvasRendererTextSystem, CanvasTextPipe, CanvasTextSystem, DOMAdapter, GraphicsContextSystem, GraphicsPipe, ImageSource, Texture, extensions } from "pixi.js";
|
|
4
6
|
//#region src/board/headless/index.ts
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
const SYSTEM_UI_FAMILIES = [
|
|
8
|
+
"Noto Sans",
|
|
9
|
+
"DejaVu Sans",
|
|
10
|
+
"Liberation Sans",
|
|
11
|
+
"FreeSans",
|
|
12
|
+
"Helvetica Neue",
|
|
13
|
+
"Arial",
|
|
14
|
+
"Segoe UI"
|
|
15
|
+
];
|
|
16
|
+
function aliasSystemUi(fonts) {
|
|
17
|
+
if (!fonts.setAlias || fonts.has("system-ui")) return;
|
|
18
|
+
const family = SYSTEM_UI_FAMILIES.find((name) => fonts.has(name));
|
|
19
|
+
if (family) fonts.setAlias(family, "system-ui");
|
|
20
|
+
}
|
|
13
21
|
const WEBGL_PROBE_STUB = {
|
|
14
22
|
getShaderPrecisionFormat: () => ({
|
|
15
23
|
precision: 1,
|
|
@@ -29,7 +37,6 @@ async function loadCanvasModule() {
|
|
|
29
37
|
throw new Error("Board image export needs the optional '@napi-rs/canvas' package. Install it with: npm i @napi-rs/canvas", { cause });
|
|
30
38
|
}
|
|
31
39
|
}
|
|
32
|
-
/** Node has no rAF; Pixi's scheduler only needs a callback pump. */
|
|
33
40
|
function installAnimationFrameShim() {
|
|
34
41
|
const globals = globalThis;
|
|
35
42
|
if (globals.requestAnimationFrame) return () => {};
|
|
@@ -54,17 +61,11 @@ function installAnimationFrameShim() {
|
|
|
54
61
|
globals.cancelAnimationFrame = void 0;
|
|
55
62
|
};
|
|
56
63
|
}
|
|
57
|
-
/**
|
|
58
|
-
* Create a Canvas2D renderer backed by Skia.
|
|
59
|
-
*
|
|
60
|
-
* `skipExtensionImports` is essential: it stops Pixi from auto-loading its
|
|
61
|
-
* browser environment bundle, which would pull in DOM-only pipes.
|
|
62
|
-
*/
|
|
63
64
|
async function createBoardHeadlessRenderer(options = {}) {
|
|
64
65
|
const canvasModule = options.canvasModule ?? await loadCanvasModule();
|
|
65
|
-
const { CanvasGraphicsContextSystem, CanvasGraphicsPipe, CanvasRenderer, CanvasRendererTextSystem, CanvasTextPipe, CanvasTextSystem, DOMAdapter, extensions, GraphicsContextSystem, GraphicsPipe, ImageSource, Texture } = await import("pixi.js");
|
|
66
66
|
extensions.add(CanvasGraphicsPipe, GraphicsPipe, CanvasGraphicsContextSystem, GraphicsContextSystem, CanvasRendererTextSystem, CanvasTextSystem, CanvasTextPipe);
|
|
67
67
|
for (const font of options.fonts ?? []) canvasModule.GlobalFonts.registerFromPath(font.path, font.family ?? "Geist");
|
|
68
|
+
aliasSystemUi(canvasModule.GlobalFonts);
|
|
68
69
|
function createCanvas(width = 1, height = 1) {
|
|
69
70
|
const canvas = canvasModule.createCanvas(Math.max(1, Math.ceil(width)), Math.max(1, Math.ceil(height)));
|
|
70
71
|
const nativeGetContext = canvas.getContext.bind(canvas);
|
|
@@ -129,11 +130,11 @@ const MIME = {
|
|
|
129
130
|
function boardHeadlessMimeType(format) {
|
|
130
131
|
return MIME[format];
|
|
131
132
|
}
|
|
132
|
-
/** Render a document to encoded image bytes. Returns null for an empty region. */
|
|
133
133
|
function exportBoardImageBytes(headless, document, options = {}) {
|
|
134
|
-
const { format = "png", quality = .92, textures, backgroundImage, ...rest } = options;
|
|
134
|
+
const { format = "png", quality = .92, textures, sketches, backgroundImage, ...rest } = options;
|
|
135
135
|
const result = renderBoardExport(headless.renderer, document, {
|
|
136
136
|
...rest,
|
|
137
|
+
...sketches ? { sketches } : {},
|
|
137
138
|
...textures ? { textures } : {},
|
|
138
139
|
...backgroundImage ? { backgroundImage: {
|
|
139
140
|
...backgroundImage,
|
|
@@ -151,4 +152,4 @@ function exportBoardImageBytes(headless, document, options = {}) {
|
|
|
151
152
|
};
|
|
152
153
|
}
|
|
153
154
|
//#endregion
|
|
154
|
-
export { boardHeadlessMimeType, createBoardHeadlessRenderer, exportBoardImageBytes };
|
|
155
|
+
export { boardHeadlessMimeType, createBoardHeadlessRenderer, createBoardHeadlessSketchHost, exportBoardImageBytes };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BoardSketchItem } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import "../../protocol/dist/index.js";
|
|
3
|
+
import { SceneItem } from "../core/scene.js";
|
|
4
|
+
import { BoardSketchHost } from "../render/renderers/board-renderer-registry.js";
|
|
5
|
+
import { BoardHeadlessRenderer } from "./index.js";
|
|
6
|
+
//#region src/board/headless/sketch.d.ts
|
|
7
|
+
type SketchFrame = SceneItem<BoardSketchItem>;
|
|
8
|
+
type PreparedSketchHost = BoardSketchHost & {
|
|
9
|
+
prepare: (items: readonly SketchFrame[], times: readonly number[], zoom: number) => Promise<void>;
|
|
10
|
+
destroy: () => void;
|
|
11
|
+
};
|
|
12
|
+
type BoardHeadlessSketchOptions = {
|
|
13
|
+
readModule: (src: string) => Promise<string>;
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
readTimeoutMs?: number;
|
|
16
|
+
};
|
|
17
|
+
declare function createBoardHeadlessSketchHost(headless: BoardHeadlessRenderer, options: BoardHeadlessSketchOptions): PreparedSketchHost;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { BoardHeadlessSketchOptions, createBoardHeadlessSketchHost };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { ImageSource, Texture } from "pixi.js";
|
|
2
|
+
import { Script, createContext } from "node:vm";
|
|
3
|
+
//#region src/board/headless/sketch.ts
|
|
4
|
+
const MAX_EDGE = 2048;
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = 2e3;
|
|
6
|
+
const DEFAULT_READ_TIMEOUT_MS = 15e3;
|
|
7
|
+
const MAX_PREPARE_CONCURRENCY = 4;
|
|
8
|
+
function transformModule(code, src) {
|
|
9
|
+
return `(function(module, exports) {\n${code.replace(/export\s+default\s+/g, "module.exports.default = ").replace(/export\s+(async\s+)?function\s+draw\b/g, (_match, asyncKeyword = "") => `module.exports.draw = ${asyncKeyword}function draw`).replace(/export\s+(const|let|var)\s+draw\s*=/g, "module.exports.draw =")}\n})(module, module.exports);\n//# sourceURL=${JSON.stringify(src)}`;
|
|
10
|
+
}
|
|
11
|
+
function runModule(code, src, timeoutMs) {
|
|
12
|
+
const module = { exports: {} };
|
|
13
|
+
const context = createContext({
|
|
14
|
+
module,
|
|
15
|
+
exports: module.exports,
|
|
16
|
+
console: {
|
|
17
|
+
log() {},
|
|
18
|
+
warn() {},
|
|
19
|
+
error() {}
|
|
20
|
+
},
|
|
21
|
+
fetch: void 0,
|
|
22
|
+
XMLHttpRequest: void 0,
|
|
23
|
+
WebSocket: void 0,
|
|
24
|
+
EventSource: void 0,
|
|
25
|
+
importScripts: void 0,
|
|
26
|
+
indexedDB: void 0,
|
|
27
|
+
caches: void 0,
|
|
28
|
+
process: void 0,
|
|
29
|
+
require: void 0
|
|
30
|
+
});
|
|
31
|
+
new Script(transformModule(code, src), { filename: src }).runInContext(context, { timeout: timeoutMs });
|
|
32
|
+
const value = module.exports.default && typeof module.exports.default === "object" ? {
|
|
33
|
+
...module.exports.default,
|
|
34
|
+
...module.exports
|
|
35
|
+
} : module.exports;
|
|
36
|
+
if (typeof value.draw !== "function") throw new Error(`${src} does not export draw(ctx, frame)`);
|
|
37
|
+
if (value.draw.constructor?.name === "AsyncFunction") throw new Error(`${src} must export a synchronous draw(ctx, frame) for headless export.`);
|
|
38
|
+
return {
|
|
39
|
+
module: value,
|
|
40
|
+
context
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function withTimeout(promise, timeoutMs, src) {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`${src} exceeded the ${timeoutMs}ms sketch timeout.`)), timeoutMs);
|
|
46
|
+
promise.then(resolve, reject).finally(() => clearTimeout(timer));
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function frameKey(item, time, scale) {
|
|
50
|
+
return `${item.id}|${time}|${item.frame.width}|${item.frame.height}|${scale}|${item.props.seed ?? item.id}|${JSON.stringify(item.props.params)}`;
|
|
51
|
+
}
|
|
52
|
+
function frameScale(resolution, width, height) {
|
|
53
|
+
return Math.min(4, Math.max(.25, resolution), MAX_EDGE / Math.max(1, width), MAX_EDGE / Math.max(1, height));
|
|
54
|
+
}
|
|
55
|
+
function textureFromCanvas(canvas) {
|
|
56
|
+
return new Texture({ source: new ImageSource({
|
|
57
|
+
resource: canvas,
|
|
58
|
+
width: canvas.width,
|
|
59
|
+
height: canvas.height
|
|
60
|
+
}) });
|
|
61
|
+
}
|
|
62
|
+
function createBoardHeadlessSketchHost(headless, options) {
|
|
63
|
+
const textures = /* @__PURE__ */ new Map();
|
|
64
|
+
const errors = /* @__PURE__ */ new Map();
|
|
65
|
+
const lastErrors = /* @__PURE__ */ new Map();
|
|
66
|
+
const modules = /* @__PURE__ */ new Map();
|
|
67
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
68
|
+
const readTimeoutMs = options.readTimeoutMs ?? DEFAULT_READ_TIMEOUT_MS;
|
|
69
|
+
const loadModule = (src) => {
|
|
70
|
+
let pending = modules.get(src);
|
|
71
|
+
if (!pending) {
|
|
72
|
+
pending = withTimeout(options.readModule(src), readTimeoutMs, src).then((code) => runModule(code, src, timeoutMs));
|
|
73
|
+
modules.set(src, pending);
|
|
74
|
+
pending.catch(() => modules.delete(src));
|
|
75
|
+
}
|
|
76
|
+
return pending;
|
|
77
|
+
};
|
|
78
|
+
const render = async (item, time, scale) => {
|
|
79
|
+
const key = frameKey(item, time, scale);
|
|
80
|
+
if (textures.has(key) || errors.has(key)) return;
|
|
81
|
+
try {
|
|
82
|
+
const runtime = await loadModule(item.props.src);
|
|
83
|
+
const width = Math.max(1, Math.round(item.frame.width * scale));
|
|
84
|
+
const height = Math.max(1, Math.round(item.frame.height * scale));
|
|
85
|
+
const canvas = headless.canvasModule.createCanvas(width, height);
|
|
86
|
+
const context = canvas.getContext("2d");
|
|
87
|
+
context.scale(scale, scale);
|
|
88
|
+
const frame = {
|
|
89
|
+
...item.props,
|
|
90
|
+
t: time,
|
|
91
|
+
width: item.frame.width,
|
|
92
|
+
height: item.frame.height,
|
|
93
|
+
seed: item.props.seed ?? item.id
|
|
94
|
+
};
|
|
95
|
+
runtime.context.__boardDraw = runtime.module.draw;
|
|
96
|
+
runtime.context.__boardDrawContext = context;
|
|
97
|
+
runtime.context.__boardFrame = frame;
|
|
98
|
+
try {
|
|
99
|
+
const drawResult = new Script("__boardDraw(__boardDrawContext, __boardFrame)", { filename: item.props.src }).runInContext(runtime.context, { timeout: timeoutMs });
|
|
100
|
+
if (drawResult && typeof drawResult.then === "function") throw new Error(`${item.props.src} must export a synchronous draw(ctx, frame) for headless export.`);
|
|
101
|
+
} finally {
|
|
102
|
+
delete runtime.context.__boardDraw;
|
|
103
|
+
delete runtime.context.__boardDrawContext;
|
|
104
|
+
delete runtime.context.__boardFrame;
|
|
105
|
+
}
|
|
106
|
+
textures.set(key, textureFromCanvas(canvas));
|
|
107
|
+
lastErrors.delete(item.id);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
errors.set(key, error instanceof Error ? error.message : String(error));
|
|
110
|
+
lastErrors.set(item.id, error instanceof Error ? error.message : String(error));
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
frame(item, time, resolution) {
|
|
115
|
+
const scale = frameScale(resolution, item.frame.width, item.frame.height);
|
|
116
|
+
return textures.get(frameKey(item, time, scale)) ?? null;
|
|
117
|
+
},
|
|
118
|
+
error: (id) => lastErrors.get(id) ?? null,
|
|
119
|
+
release: () => {},
|
|
120
|
+
async prepare(items, times, zoom) {
|
|
121
|
+
const sketches = items.filter((item) => item.type === "sketch");
|
|
122
|
+
const jobs = [];
|
|
123
|
+
for (const item of sketches) {
|
|
124
|
+
const scale = frameScale(zoom, item.frame.width, item.frame.height);
|
|
125
|
+
for (const time of times) jobs.push(() => render(item, time, scale));
|
|
126
|
+
}
|
|
127
|
+
let next = 0;
|
|
128
|
+
const worker = async () => {
|
|
129
|
+
while (next < jobs.length) {
|
|
130
|
+
const job = jobs[next++];
|
|
131
|
+
if (job) await job();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
await Promise.all(Array.from({ length: Math.min(MAX_PREPARE_CONCURRENCY, jobs.length) }, worker));
|
|
135
|
+
},
|
|
136
|
+
destroy() {
|
|
137
|
+
for (const texture of textures.values()) texture.destroy(true);
|
|
138
|
+
textures.clear();
|
|
139
|
+
errors.clear();
|
|
140
|
+
lastErrors.clear();
|
|
141
|
+
modules.clear();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
//#endregion
|
|
146
|
+
export { createBoardHeadlessSketchHost };
|
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BoardSceneItem } from "./core/scene.js";
|
|
2
2
|
//#region src/board/image-key.d.ts
|
|
3
|
-
|
|
4
|
-
* Stable cache key for an item's image resource, or null when it has none.
|
|
5
|
-
*
|
|
6
|
-
* Space files and remote URLs are namespaced so they can never collide, and file
|
|
7
|
-
* cards contribute their cover image — which means covers ride the exact same
|
|
8
|
-
* reference counting, LRU cooling pool and viewport preloading as image nodes,
|
|
9
|
-
* with no second loader to keep in step. The exporter resolves the same keys, so
|
|
10
|
-
* an exported card shows the same picture the editor does.
|
|
11
|
-
*/
|
|
12
|
-
declare function imageAssetKey(item: BoardItem): string | null;
|
|
13
|
-
/** The source a key resolves from, recovered from its namespace prefix. */
|
|
3
|
+
declare function imageAssetKey(item: BoardSceneItem): string | null;
|
|
14
4
|
declare function boardImageKeySource(key: string): {
|
|
15
5
|
kind: "file" | "url";
|
|
16
6
|
value: string;
|
package/dist/board/image-key.js
CHANGED
|
@@ -1,29 +1,19 @@
|
|
|
1
1
|
import { featuredTaskArtifact, taskArtifactPreviewUrl } from "./task.js";
|
|
2
2
|
//#region src/board/image-key.ts
|
|
3
|
-
/**
|
|
4
|
-
* Stable cache key for an item's image resource, or null when it has none.
|
|
5
|
-
*
|
|
6
|
-
* Space files and remote URLs are namespaced so they can never collide, and file
|
|
7
|
-
* cards contribute their cover image — which means covers ride the exact same
|
|
8
|
-
* reference counting, LRU cooling pool and viewport preloading as image nodes,
|
|
9
|
-
* with no second loader to keep in step. The exporter resolves the same keys, so
|
|
10
|
-
* an exported card shows the same picture the editor does.
|
|
11
|
-
*/
|
|
12
3
|
function imageAssetKey(item) {
|
|
13
|
-
if (item.type === "image") return `file:${item.
|
|
4
|
+
if (item.type === "image") return `file:${item.props.src}`;
|
|
14
5
|
if (item.type === "file") {
|
|
15
|
-
const snapshot = item.snapshot;
|
|
6
|
+
const snapshot = item.props.snapshot;
|
|
16
7
|
if (snapshot?.coverPath) return `file:${snapshot.coverPath}`;
|
|
17
8
|
if (snapshot?.coverUrl) return `url:${snapshot.coverUrl}`;
|
|
18
9
|
return null;
|
|
19
10
|
}
|
|
20
11
|
if (item.type === "task") {
|
|
21
|
-
const url = taskArtifactPreviewUrl(featuredTaskArtifact(item.snapshot.artifacts));
|
|
12
|
+
const url = taskArtifactPreviewUrl(featuredTaskArtifact(item.props.snapshot.artifacts));
|
|
22
13
|
return url ? `url:${url}` : null;
|
|
23
14
|
}
|
|
24
15
|
return null;
|
|
25
16
|
}
|
|
26
|
-
/** The source a key resolves from, recovered from its namespace prefix. */
|
|
27
17
|
function boardImageKeySource(key) {
|
|
28
18
|
if (key.startsWith("file:")) return {
|
|
29
19
|
kind: "file",
|