@neta-art/cohub-cli 9.1.2 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/board-export.d.ts +14 -0
- package/dist/board-export.js +136 -80
- package/dist/board-kernel/lexer.js +84 -0
- package/dist/board-kernel.d.ts +4 -2
- package/dist/board-kernel.js +2913 -803
- package/dist/board-time.js +6 -0
- package/dist/commands/boards/authoring.d.ts +2 -0
- package/dist/commands/boards/authoring.js +90 -0
- package/dist/commands/boards/examples.js +12 -7
- package/dist/commands/boards.js +46 -11
- package/dist/commands/rooms.d.ts +3 -0
- package/dist/commands/rooms.js +232 -0
- package/dist/index.js +3 -0
- package/dist/runtime/model-catalog.d.ts +1 -1
- package/dist/runtime/model-catalog.js +2 -4
- package/dist/safe-remote-image.d.ts +4 -0
- package/dist/safe-remote-image.js +30 -15
- package/package.json +3 -3
package/dist/board-kernel.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { $ as groupD8, A as bgr2rgb, B as Bounds, C as Sprite, D as TexturePool, E as RenderGroup, G as TextureStyle, H as Texture, I as BigPool, J as deprecation, K as definedProps, L as GlobalResourceRegistry, M as multiplyHexColors, N as getGlobalBounds, O as InstructionSet, P as FilterEffect, Q as Rectangle, R as warn, S as DOMAdapter, T as Container, U as TextureMatrix, V as updateQuadBounds, W as TextureSource, X as v8_3_4, Y as v8_0_0, Z as uid$1, _ as Cache, a as Geometry, at as extensions, b as ImageSource, c as Filter, d as RendererType, et as Matrix, f as BindGroup, g as getTestContext, h as GlProgram, i as canUseNewCanvasBlendModes, it as ExtensionType, j as multiplyColors, k as getLocalBounds, l as State, m as GpuProgram, n as CanvasPool, nt as eventemitter3_default, o as Buffer$1, p as UniformGroup, q as nextPow2, r as canvasUtils, rt as ObservablePoint, s as BufferUsage, t as getPo2TextureFromSource, tt as Point, u as Shader, v as Ticker, w as ViewContainer, x as CanvasSource, z as Color } from "./board-kernel/getPo2TextureFromSource.js";
|
|
2
|
-
import {
|
|
2
|
+
import { Worker } from "node:worker_threads";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
3
4
|
//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/util.js
|
|
4
5
|
function getEnumValues(entries) {
|
|
5
6
|
const numericValues = Object.values(entries).filter((v) => typeof v === "number");
|
|
@@ -44,6 +45,24 @@ function floatSafeRemainder(val, step) {
|
|
|
44
45
|
if (Math.abs(ratio - roundedRatio) < tolerance) return 0;
|
|
45
46
|
return ratio - roundedRatio;
|
|
46
47
|
}
|
|
48
|
+
const EVALUATING = /* @__PURE__*/ Symbol("evaluating");
|
|
49
|
+
function defineLazy(object, key, getter) {
|
|
50
|
+
let value = void 0;
|
|
51
|
+
Object.defineProperty(object, key, {
|
|
52
|
+
get() {
|
|
53
|
+
if (value === EVALUATING) return;
|
|
54
|
+
if (value === void 0) {
|
|
55
|
+
value = EVALUATING;
|
|
56
|
+
value = getter();
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
},
|
|
60
|
+
set(v) {
|
|
61
|
+
Object.defineProperty(object, key, { value: v });
|
|
62
|
+
},
|
|
63
|
+
configurable: true
|
|
64
|
+
});
|
|
65
|
+
}
|
|
47
66
|
function assignProp(target, prop, value) {
|
|
48
67
|
Object.defineProperty(target, prop, {
|
|
49
68
|
value,
|
|
@@ -1017,6 +1036,7 @@ const anyString = /^[\s\S]{0,}$/;
|
|
|
1017
1036
|
const integer = /^-?\d+$/;
|
|
1018
1037
|
const number$2 = /^-?\d+(?:\.\d+)?$/;
|
|
1019
1038
|
const boolean$1 = /^(?:true|false)$/i;
|
|
1039
|
+
const _null$2 = /^null$/i;
|
|
1020
1040
|
const lowercase = /^[^A-Z]*$/;
|
|
1021
1041
|
const uppercase = /^[^a-z]*$/;
|
|
1022
1042
|
//#endregion
|
|
@@ -1838,6 +1858,22 @@ const $ZodBoolean = /*@__PURE__*/ $constructor("$ZodBoolean", (inst, def) => {
|
|
|
1838
1858
|
return payload;
|
|
1839
1859
|
};
|
|
1840
1860
|
});
|
|
1861
|
+
const $ZodNull = /*@__PURE__*/ $constructor("$ZodNull", (inst, def) => {
|
|
1862
|
+
$ZodType.init(inst, def);
|
|
1863
|
+
inst._zod.pattern = _null$2;
|
|
1864
|
+
inst._zod.values = /* @__PURE__ */ new Set([null]);
|
|
1865
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
1866
|
+
const input = payload.value;
|
|
1867
|
+
if (input === null) return payload;
|
|
1868
|
+
payload.issues.push({
|
|
1869
|
+
expected: "null",
|
|
1870
|
+
code: "invalid_type",
|
|
1871
|
+
input,
|
|
1872
|
+
inst
|
|
1873
|
+
});
|
|
1874
|
+
return payload;
|
|
1875
|
+
};
|
|
1876
|
+
});
|
|
1841
1877
|
const $ZodUnknown = /*@__PURE__*/ $constructor("$ZodUnknown", (inst, def) => {
|
|
1842
1878
|
$ZodType.init(inst, def);
|
|
1843
1879
|
inst._zod.parse = (payload) => payload;
|
|
@@ -2764,6 +2800,21 @@ function handleReadonlyResult(payload) {
|
|
|
2764
2800
|
if (!payload.memo) payload.value = Object.freeze(payload.value);
|
|
2765
2801
|
return payload;
|
|
2766
2802
|
}
|
|
2803
|
+
const $ZodLazy = /*@__PURE__*/ $constructor("$ZodLazy", (inst, def) => {
|
|
2804
|
+
$ZodType.init(inst, def);
|
|
2805
|
+
defineLazy(inst._zod, "innerType", () => {
|
|
2806
|
+
const d = def;
|
|
2807
|
+
if (!d._cachedInner) d._cachedInner = def.getter();
|
|
2808
|
+
return d._cachedInner;
|
|
2809
|
+
});
|
|
2810
|
+
defineLazyInternal(inst, "pattern", (zod) => zod.innerType?._zod?.pattern);
|
|
2811
|
+
defineLazyInternal(inst, "propValues", (zod) => zod.innerType?._zod?.propValues);
|
|
2812
|
+
defineLazyInternal(inst, "optin", (zod) => zod.innerType?._zod?.optin ?? void 0);
|
|
2813
|
+
defineLazyInternal(inst, "optout", (zod) => zod.innerType?._zod?.optout ?? void 0);
|
|
2814
|
+
inst._zod.parse = (payload, ctx) => {
|
|
2815
|
+
return inst._zod.innerType._zod.run(payload, ctx);
|
|
2816
|
+
};
|
|
2817
|
+
});
|
|
2767
2818
|
const $ZodCustom = /*@__PURE__*/ $constructor("$ZodCustom", (inst, def) => {
|
|
2768
2819
|
$ZodCheck.init(inst, def);
|
|
2769
2820
|
$ZodType.init(inst, def);
|
|
@@ -3505,6 +3556,13 @@ function _boolean(Class, params) {
|
|
|
3505
3556
|
});
|
|
3506
3557
|
}
|
|
3507
3558
|
// @__NO_SIDE_EFFECTS__
|
|
3559
|
+
function _null$1(Class, params) {
|
|
3560
|
+
return new Class({
|
|
3561
|
+
type: "null",
|
|
3562
|
+
...normalizeParams(params)
|
|
3563
|
+
});
|
|
3564
|
+
}
|
|
3565
|
+
// @__NO_SIDE_EFFECTS__
|
|
3508
3566
|
function _unknown(Class) {
|
|
3509
3567
|
return new Class({ type: "unknown" });
|
|
3510
3568
|
}
|
|
@@ -4286,6 +4344,13 @@ const numberProcessor = (schema, ctx, _json, params) => {
|
|
|
4286
4344
|
const booleanProcessor = (_schema, _ctx, json, _params) => {
|
|
4287
4345
|
json.type = "boolean";
|
|
4288
4346
|
};
|
|
4347
|
+
const nullProcessor = (_schema, ctx, json, _params) => {
|
|
4348
|
+
if (ctx.target === "openapi-3.0") {
|
|
4349
|
+
json.type = "string";
|
|
4350
|
+
json.nullable = true;
|
|
4351
|
+
json.enum = [null];
|
|
4352
|
+
} else json.type = "null";
|
|
4353
|
+
};
|
|
4289
4354
|
const neverProcessor = (_schema, _ctx, json, _params) => {
|
|
4290
4355
|
json.not = {};
|
|
4291
4356
|
};
|
|
@@ -4599,6 +4664,12 @@ const optionalProcessor = (schema, ctx, _json, params) => {
|
|
|
4599
4664
|
const seen = ctx.seen.get(schema);
|
|
4600
4665
|
seen.ref = def.innerType;
|
|
4601
4666
|
};
|
|
4667
|
+
const lazyProcessor = (schema, ctx, _json, params) => {
|
|
4668
|
+
const innerType = schema._zod.innerType;
|
|
4669
|
+
processSchema(innerType, ctx, params);
|
|
4670
|
+
const seen = ctx.seen.get(schema);
|
|
4671
|
+
seen.ref = innerType;
|
|
4672
|
+
};
|
|
4602
4673
|
//#endregion
|
|
4603
4674
|
//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/errors.js
|
|
4604
4675
|
const _installedErrorProtos = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);
|
|
@@ -5167,6 +5238,14 @@ const ZodBoolean = /*@__PURE__*/ $constructor("ZodBoolean", (inst, def) => {
|
|
|
5167
5238
|
function boolean(params) {
|
|
5168
5239
|
return /* @__PURE__ */ _boolean(ZodBoolean, params);
|
|
5169
5240
|
}
|
|
5241
|
+
const ZodNull = /*@__PURE__*/ $constructor("ZodNull", (inst, def) => {
|
|
5242
|
+
$ZodNull.init(inst, def);
|
|
5243
|
+
ZodType.init(inst, def);
|
|
5244
|
+
inst._zod.processJSONSchema = (ctx, json, params) => nullProcessor(inst, ctx, json, params);
|
|
5245
|
+
});
|
|
5246
|
+
function _null(params) {
|
|
5247
|
+
return /* @__PURE__ */ _null$1(ZodNull, params);
|
|
5248
|
+
}
|
|
5170
5249
|
const ZodUnknown = /*@__PURE__*/ $constructor("ZodUnknown", (inst, def) => {
|
|
5171
5250
|
$ZodUnknown.init(inst, def);
|
|
5172
5251
|
ZodType.init(inst, def);
|
|
@@ -5534,6 +5613,18 @@ function readonly(innerType) {
|
|
|
5534
5613
|
innerType
|
|
5535
5614
|
});
|
|
5536
5615
|
}
|
|
5616
|
+
const ZodLazy = /*@__PURE__*/ $constructor("ZodLazy", (inst, def) => {
|
|
5617
|
+
$ZodLazy.init(inst, def);
|
|
5618
|
+
ZodType.init(inst, def);
|
|
5619
|
+
inst._zod.processJSONSchema = (ctx, json, params) => lazyProcessor(inst, ctx, json, params);
|
|
5620
|
+
inst.unwrap = () => inst._zod.def.getter();
|
|
5621
|
+
});
|
|
5622
|
+
function lazy(getter) {
|
|
5623
|
+
return new ZodLazy({
|
|
5624
|
+
type: "lazy",
|
|
5625
|
+
getter
|
|
5626
|
+
});
|
|
5627
|
+
}
|
|
5537
5628
|
const ZodCustom = /*@__PURE__*/ $constructor("ZodCustom", (inst, def) => {
|
|
5538
5629
|
$ZodCustom.init(inst, def);
|
|
5539
5630
|
ZodType.init(inst, def);
|
|
@@ -5545,23 +5636,123 @@ function refine(fn, _params = {}) {
|
|
|
5545
5636
|
function superRefine(fn, params) {
|
|
5546
5637
|
return /* @__PURE__ */ _superRefine(fn, params);
|
|
5547
5638
|
}
|
|
5639
|
+
function json(params) {
|
|
5640
|
+
const jsonSchema = lazy(() => {
|
|
5641
|
+
return union([
|
|
5642
|
+
string(params),
|
|
5643
|
+
number$1(),
|
|
5644
|
+
boolean(),
|
|
5645
|
+
_null(),
|
|
5646
|
+
array(jsonSchema),
|
|
5647
|
+
record(string(), jsonSchema)
|
|
5648
|
+
]);
|
|
5649
|
+
});
|
|
5650
|
+
return jsonSchema;
|
|
5651
|
+
}
|
|
5548
5652
|
const BOARD_ARROW_STROKE_SIZE = 2.5;
|
|
5549
|
-
const BOARD_FONT_STACK = "\"Geist\", system-ui, -apple-system, \"Noto Sans CJK SC\", \"Noto Sans SC\", \"PingFang SC\", \"Microsoft YaHei\", sans-serif";
|
|
5550
|
-
const BOARD_MONO_FONT_STACK = "\"Geist Mono\", \"Fira Code\", ui-monospace, \"Noto Sans Mono CJK SC\", monospace";
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
const
|
|
5554
|
-
|
|
5555
|
-
const
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5653
|
+
const BOARD_FONT_STACK = "\"Geist\", system-ui, -apple-system, \"Noto Sans CJK SC\", \"Noto Sans SC\", \"PingFang SC\", \"Microsoft YaHei\", \"Noto Sans Math\", \"Noto Sans Symbols 2\", \"DejaVu Sans\", \"FreeSans\", \"FreeSerif\", sans-serif";
|
|
5654
|
+
const BOARD_MONO_FONT_STACK = "\"Geist Mono\", \"Fira Code\", ui-monospace, \"Noto Sans Mono CJK SC\", \"Noto Sans Math\", \"DejaVu Sans\", \"FreeSans\", \"FreeSerif\", monospace";
|
|
5655
|
+
//#endregion
|
|
5656
|
+
//#region ../protocol/src/board-url.ts
|
|
5657
|
+
const BOARD_REMOTE_URL_MAX_LENGTH$1 = 4096;
|
|
5658
|
+
function parseIpv4$1(host) {
|
|
5659
|
+
const parts = host.split(".").map(Number);
|
|
5660
|
+
return parts.length === 4 && parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255) ? parts : null;
|
|
5661
|
+
}
|
|
5662
|
+
function isBlockedIpv4$1(host) {
|
|
5663
|
+
const parts = parseIpv4$1(host);
|
|
5664
|
+
if (!parts) return false;
|
|
5665
|
+
const [first, second, third] = parts;
|
|
5666
|
+
if (first === 0 || first === 10 || first === 127) return true;
|
|
5667
|
+
if (first === 100 && second >= 64 && second <= 127) return true;
|
|
5668
|
+
if (first === 169 && second === 254) return true;
|
|
5669
|
+
if (first === 172 && second >= 16 && second <= 31) return true;
|
|
5670
|
+
if (first === 192 && second === 168) return true;
|
|
5671
|
+
if (first === 192 && second === 0 && (third === 0 || third === 2)) return true;
|
|
5672
|
+
if (first === 192 && second === 88 && third === 99) return true;
|
|
5673
|
+
if (first === 198 && (second === 18 || second === 19)) return true;
|
|
5674
|
+
if (first === 198 && second === 51 && third === 100) return true;
|
|
5675
|
+
if (first === 203 && second === 0 && third === 113) return true;
|
|
5676
|
+
return first >= 224;
|
|
5677
|
+
}
|
|
5678
|
+
function expandIpv6$1(host) {
|
|
5679
|
+
const [head, tail, extra] = host.toLowerCase().split("::");
|
|
5680
|
+
if (extra !== void 0) return null;
|
|
5681
|
+
const headParts = head ? head.split(":").filter(Boolean) : [];
|
|
5682
|
+
const tailParts = tail ? tail.split(":").filter(Boolean) : [];
|
|
5683
|
+
const missing = 8 - headParts.length - tailParts.length;
|
|
5684
|
+
if (missing < 0 || tail === void 0 && missing !== 0) return null;
|
|
5685
|
+
const parts = [
|
|
5686
|
+
...headParts,
|
|
5687
|
+
...Array.from({ length: missing }, () => "0"),
|
|
5688
|
+
...tailParts
|
|
5689
|
+
];
|
|
5690
|
+
if (parts.length !== 8 || parts.some((part) => !/^[0-9a-f]{1,4}$/.test(part))) return null;
|
|
5691
|
+
return parts.map((part) => part.padStart(4, "0"));
|
|
5692
|
+
}
|
|
5693
|
+
function isBlockedIpv6$1(host) {
|
|
5694
|
+
const parts = expandIpv6$1(host);
|
|
5695
|
+
if (!parts) return true;
|
|
5696
|
+
if (parts.every((part) => part === "0000")) return true;
|
|
5697
|
+
if (parts.slice(0, 7).every((part) => part === "0000") && parts[7] === "0001") return true;
|
|
5698
|
+
if (parts.slice(0, 5).every((part) => part === "0000") && parts[5] === "ffff") {
|
|
5699
|
+
const high = Number.parseInt(parts[6] ?? "0", 16);
|
|
5700
|
+
const low = Number.parseInt(parts[7] ?? "0", 16);
|
|
5701
|
+
return isBlockedIpv4$1(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`);
|
|
5702
|
+
}
|
|
5703
|
+
if (parts.slice(0, 6).every((part) => part === "0000")) return true;
|
|
5704
|
+
const first = Number.parseInt(parts[0] ?? "0", 16);
|
|
5705
|
+
if ((first & 65024) === 64512) return true;
|
|
5706
|
+
if ((first & 65472) === 65152 || (first & 65472) === 65216) return true;
|
|
5707
|
+
if ((first & 65280) === 65280) return true;
|
|
5708
|
+
return parts[0] === "2001" && parts[1] === "0db8";
|
|
5709
|
+
}
|
|
5710
|
+
function isPublicBoardRemoteAddress$1(value) {
|
|
5711
|
+
const address = value.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
5712
|
+
if (parseIpv4$1(address)) return !isBlockedIpv4$1(address);
|
|
5713
|
+
return address.includes(":") && !isBlockedIpv6$1(address);
|
|
5714
|
+
}
|
|
5715
|
+
function isBlockedHost$1(hostname) {
|
|
5716
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
5717
|
+
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal")) return true;
|
|
5718
|
+
if (parseIpv4$1(host) || host.includes(":")) return !isPublicBoardRemoteAddress$1(host);
|
|
5719
|
+
return false;
|
|
5720
|
+
}
|
|
5721
|
+
/**
|
|
5722
|
+
* Normalize a browser-loadable public HTTP(S) URL. This blocks explicit local
|
|
5723
|
+
* addresses; any future server-side fetcher must additionally validate DNS
|
|
5724
|
+
* resolution to defend against rebinding.
|
|
5725
|
+
*/
|
|
5726
|
+
function normalizeBoardRemoteUrl$1(value) {
|
|
5727
|
+
if (typeof value !== "string") return void 0;
|
|
5728
|
+
const input = value.trim();
|
|
5729
|
+
if (!input || input.length > 4096) return void 0;
|
|
5730
|
+
try {
|
|
5731
|
+
const url = new URL(input);
|
|
5732
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return void 0;
|
|
5733
|
+
if (url.username || url.password || isBlockedHost$1(url.hostname)) return;
|
|
5734
|
+
const normalized = url.toString();
|
|
5735
|
+
return normalized.length <= 4096 ? normalized : void 0;
|
|
5736
|
+
} catch {
|
|
5737
|
+
return;
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
string().max(BOARD_REMOTE_URL_MAX_LENGTH$1).refine((value) => normalizeBoardRemoteUrl$1(value) !== void 0, { message: "URL must be a public HTTP(S) URL without credentials" });
|
|
5741
|
+
const BOARD_CAMERA_TARGET$1 = "camera";
|
|
5742
|
+
const BoardKeySchema$1 = string().min(1).max(160).regex(/^[^\s/]+$/, "ids must not contain whitespace or '/'");
|
|
5743
|
+
const BoardIdSchema$1 = BoardKeySchema$1.refine((value) => value !== BOARD_CAMERA_TARGET$1, "\"camera\" is reserved");
|
|
5744
|
+
const finite$1 = number$1().finite();
|
|
5745
|
+
const nonNegative$1 = finite$1.nonnegative();
|
|
5746
|
+
const ratio$1 = finite$1.min(0).max(1);
|
|
5747
|
+
const BoardVec2Schema$1 = object({
|
|
5748
|
+
x: finite$1,
|
|
5749
|
+
y: finite$1
|
|
5559
5750
|
}).strict();
|
|
5560
|
-
const BoardSizeSchema = object({
|
|
5561
|
-
width: finite.positive(),
|
|
5562
|
-
height: finite.positive()
|
|
5751
|
+
const BoardSizeSchema$1 = object({
|
|
5752
|
+
width: finite$1.positive(),
|
|
5753
|
+
height: finite$1.positive()
|
|
5563
5754
|
}).strict();
|
|
5564
|
-
const BOARD_COLOR_TOKENS = [
|
|
5755
|
+
const BOARD_COLOR_TOKENS$1 = [
|
|
5565
5756
|
"brand",
|
|
5566
5757
|
"neutral",
|
|
5567
5758
|
"black",
|
|
@@ -5572,66 +5763,85 @@ const BOARD_COLOR_TOKENS = [
|
|
|
5572
5763
|
"violet",
|
|
5573
5764
|
"rose"
|
|
5574
5765
|
];
|
|
5575
|
-
const CSS_NAMED_COLORS = new Set("transparent aliceblue antiquewhite aqua aquamarine azure beige bisque blanchedalmond blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray grey greenyellow honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato turquoise violet wheat whitesmoke yellow yellowgreen".split(" "));
|
|
5576
|
-
function isBoardColorString(value) {
|
|
5766
|
+
const CSS_NAMED_COLORS$1 = new Set("transparent aliceblue antiquewhite aqua aquamarine azure beige bisque blanchedalmond blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray grey greenyellow honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato turquoise violet wheat whitesmoke yellow yellowgreen".split(" "));
|
|
5767
|
+
function isBoardColorString$1(value) {
|
|
5577
5768
|
const color = value.trim().toLowerCase();
|
|
5578
|
-
return BOARD_COLOR_TOKENS.includes(color) || CSS_NAMED_COLORS.has(color) || /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(color) || /^(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color)\([^()]*\)$/.test(color);
|
|
5769
|
+
return BOARD_COLOR_TOKENS$1.includes(color) || CSS_NAMED_COLORS$1.has(color) || /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(color) || /^(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color)\([^()]*\)$/.test(color);
|
|
5579
5770
|
}
|
|
5580
|
-
const colorString = string().max(120).refine(isBoardColorString, "expected a palette token or a CSS color");
|
|
5581
|
-
const BoardColorSchema = union([colorString, object({
|
|
5582
|
-
light: colorString,
|
|
5583
|
-
dark: colorString
|
|
5771
|
+
const colorString$1 = string().max(120).refine(isBoardColorString$1, "expected a palette token or a CSS color");
|
|
5772
|
+
const BoardColorSchema$1 = union([colorString$1, object({
|
|
5773
|
+
light: colorString$1,
|
|
5774
|
+
dark: colorString$1
|
|
5584
5775
|
}).strict()]);
|
|
5585
|
-
const BoardStyleSchema = object({
|
|
5586
|
-
fill: BoardColorSchema.optional(),
|
|
5587
|
-
fillOpacity: ratio.optional(),
|
|
5588
|
-
stroke: BoardColorSchema.optional(),
|
|
5589
|
-
strokeWidth: finite.min(0).max(64).optional(),
|
|
5776
|
+
const BoardStyleSchema$1 = object({
|
|
5777
|
+
fill: BoardColorSchema$1.optional(),
|
|
5778
|
+
fillOpacity: ratio$1.optional(),
|
|
5779
|
+
stroke: BoardColorSchema$1.optional(),
|
|
5780
|
+
strokeWidth: finite$1.min(0).max(64).optional(),
|
|
5590
5781
|
dash: _enum([
|
|
5591
5782
|
"solid",
|
|
5592
5783
|
"dashed",
|
|
5593
5784
|
"dotted"
|
|
5594
5785
|
]).optional(),
|
|
5595
|
-
radius: nonNegative.optional(),
|
|
5596
|
-
trim: ratio.optional()
|
|
5786
|
+
radius: nonNegative$1.optional(),
|
|
5787
|
+
trim: ratio$1.optional()
|
|
5597
5788
|
}).strict();
|
|
5598
|
-
function isSafeBoardSourcePath(value) {
|
|
5599
|
-
return value.length > 0 && value.length <= 4096 && !value.startsWith("/") && !value.includes("\\") && value.split("/").every((part) => part.length > 0 && part !== "." && part !== "..");
|
|
5789
|
+
function isSafeBoardSourcePath$1(value) {
|
|
5790
|
+
return value.length > 0 && value.length <= 4096 && !value.startsWith("/") && !/^[a-z][a-z\d+.-]*:/i.test(value.trimStart()) && !value.includes("\\") && value.split("/").every((part) => part.length > 0 && part !== "." && part !== "..");
|
|
5600
5791
|
}
|
|
5601
|
-
const srcSchema = string().refine(isSafeBoardSourcePath, "src must be a relative Space file path");
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5792
|
+
const srcSchema$1 = string().refine(isSafeBoardSourcePath$1, "src must be a relative Space file path");
|
|
5793
|
+
function parseBoardMediaSource$1(src) {
|
|
5794
|
+
if (isSafeBoardSourcePath$1(src)) return {
|
|
5795
|
+
kind: "file",
|
|
5796
|
+
value: src
|
|
5797
|
+
};
|
|
5798
|
+
return normalizeBoardRemoteUrl$1(src) ? {
|
|
5799
|
+
kind: "url",
|
|
5800
|
+
value: src
|
|
5801
|
+
} : null;
|
|
5802
|
+
}
|
|
5803
|
+
const BoardMediaSourceSchema$1 = string().max(4096).refine((src) => parseBoardMediaSource$1(src) !== null, "src must be a public HTTP(S) URL or relative Space file path").describe("Prefer a stable public HTTP(S) URL; relative Space file paths remain supported.");
|
|
5804
|
+
const BoardScriptSchema$1 = object({
|
|
5805
|
+
src: srcSchema$1.refine((path) => /\.m?js$/.test(path), "script src must be a .js or .mjs module"),
|
|
5806
|
+
params: record(string(), json()).default({}),
|
|
5807
|
+
targets: record(string().min(1).max(80), BoardIdSchema$1).default({}),
|
|
5808
|
+
seed: string().min(1).max(160).optional(),
|
|
5809
|
+
enabled: boolean().default(true)
|
|
5810
|
+
}).strict();
|
|
5811
|
+
const itemEnvelope$1 = {
|
|
5812
|
+
script: BoardScriptSchema$1.optional(),
|
|
5813
|
+
parent: BoardIdSchema$1.optional(),
|
|
5814
|
+
z: finite$1.optional(),
|
|
5815
|
+
position: BoardVec2Schema$1.default({
|
|
5606
5816
|
x: 0,
|
|
5607
5817
|
y: 0
|
|
5608
5818
|
}),
|
|
5609
|
-
rotation: finite.default(0),
|
|
5610
|
-
scale: union([finite, BoardVec2Schema]).default(1),
|
|
5611
|
-
origin: BoardVec2Schema.default({
|
|
5819
|
+
rotation: finite$1.default(0),
|
|
5820
|
+
scale: union([finite$1, BoardVec2Schema$1]).default(1),
|
|
5821
|
+
origin: BoardVec2Schema$1.default({
|
|
5612
5822
|
x: .5,
|
|
5613
5823
|
y: .5
|
|
5614
5824
|
}),
|
|
5615
|
-
opacity: ratio.default(1),
|
|
5616
|
-
style: BoardStyleSchema.default({}),
|
|
5825
|
+
opacity: ratio$1.default(1),
|
|
5826
|
+
style: BoardStyleSchema$1.default({}),
|
|
5617
5827
|
locked: boolean().optional(),
|
|
5618
5828
|
metadata: record(string(), unknown()).optional()
|
|
5619
5829
|
};
|
|
5620
|
-
const boxed = (width, height) => ({
|
|
5621
|
-
...itemEnvelope,
|
|
5622
|
-
size: BoardSizeSchema.default({
|
|
5830
|
+
const boxed$1 = (width, height) => ({
|
|
5831
|
+
...itemEnvelope$1,
|
|
5832
|
+
size: BoardSizeSchema$1.default({
|
|
5623
5833
|
width,
|
|
5624
5834
|
height
|
|
5625
5835
|
})
|
|
5626
5836
|
});
|
|
5627
|
-
const unsized = { ...itemEnvelope };
|
|
5628
|
-
const fontSize = finite.min(2).max(512);
|
|
5629
|
-
const BOARD_TEXT_ALIGNS = [
|
|
5837
|
+
const unsized$1 = { ...itemEnvelope$1 };
|
|
5838
|
+
const fontSize$1 = finite$1.min(2).max(512);
|
|
5839
|
+
const BOARD_TEXT_ALIGNS$1 = [
|
|
5630
5840
|
"left",
|
|
5631
5841
|
"center",
|
|
5632
5842
|
"right"
|
|
5633
5843
|
];
|
|
5634
|
-
const BOARD_SHAPE_GEOMETRIES = [
|
|
5844
|
+
const BOARD_SHAPE_GEOMETRIES$1 = [
|
|
5635
5845
|
"rectangle",
|
|
5636
5846
|
"rounded",
|
|
5637
5847
|
"ellipse",
|
|
@@ -5639,55 +5849,56 @@ const BOARD_SHAPE_GEOMETRIES = [
|
|
|
5639
5849
|
"triangle",
|
|
5640
5850
|
"path"
|
|
5641
5851
|
];
|
|
5642
|
-
const BoardFrameItemSchema = object({
|
|
5643
|
-
...boxed(480, 320),
|
|
5852
|
+
const BoardFrameItemSchema$1 = object({
|
|
5853
|
+
...boxed$1(480, 320),
|
|
5644
5854
|
type: literal("frame"),
|
|
5645
5855
|
props: object({
|
|
5646
5856
|
label: string().max(280).default(""),
|
|
5857
|
+
decoration: _enum(["frame", "none"]).optional(),
|
|
5647
5858
|
clip: boolean().default(true)
|
|
5648
5859
|
}).strict().prefault({})
|
|
5649
5860
|
}).strict();
|
|
5650
|
-
const BoardTextItemSchema = object({
|
|
5651
|
-
...unsized,
|
|
5861
|
+
const BoardTextItemSchema$1 = object({
|
|
5862
|
+
...unsized$1,
|
|
5652
5863
|
type: literal("text"),
|
|
5653
5864
|
props: object({
|
|
5654
5865
|
text: string().default(""),
|
|
5655
|
-
fontSize: fontSize.default(24),
|
|
5656
|
-
fontWeight: finite.min(100).max(900).default(500),
|
|
5866
|
+
fontSize: fontSize$1.default(24),
|
|
5867
|
+
fontWeight: finite$1.min(100).max(900).default(500),
|
|
5657
5868
|
font: string().min(1).max(120).default("sans"),
|
|
5658
|
-
align: _enum(BOARD_TEXT_ALIGNS).default("left"),
|
|
5659
|
-
lineHeight: finite.min(.5).max(4).default(4 / 3),
|
|
5660
|
-
width: finite.positive().optional(),
|
|
5661
|
-
reveal: ratio.default(1),
|
|
5869
|
+
align: _enum(BOARD_TEXT_ALIGNS$1).default("left"),
|
|
5870
|
+
lineHeight: finite$1.min(.5).max(4).default(4 / 3),
|
|
5871
|
+
width: finite$1.positive().optional(),
|
|
5872
|
+
reveal: ratio$1.default(1),
|
|
5662
5873
|
caret: boolean().default(false)
|
|
5663
5874
|
}).strict().prefault({})
|
|
5664
5875
|
}).strict();
|
|
5665
|
-
const BoardShapeItemSchema = object({
|
|
5666
|
-
...boxed(240, 160),
|
|
5876
|
+
const BoardShapeItemSchema$1 = object({
|
|
5877
|
+
...boxed$1(240, 160),
|
|
5667
5878
|
type: literal("shape"),
|
|
5668
5879
|
props: object({
|
|
5669
|
-
geometry: _enum(BOARD_SHAPE_GEOMETRIES).default("rectangle"),
|
|
5880
|
+
geometry: _enum(BOARD_SHAPE_GEOMETRIES$1).default("rectangle"),
|
|
5670
5881
|
path: string().min(1).max(2e5).optional(),
|
|
5671
5882
|
text: string().default(""),
|
|
5672
|
-
fontSize: fontSize.default(20),
|
|
5673
|
-
align: _enum(BOARD_TEXT_ALIGNS).default("center")
|
|
5883
|
+
fontSize: fontSize$1.default(20),
|
|
5884
|
+
align: _enum(BOARD_TEXT_ALIGNS$1).default("center")
|
|
5674
5885
|
}).strict().refine((props) => props.geometry !== "path" || Boolean(props.path), {
|
|
5675
5886
|
message: "path geometry requires props.path",
|
|
5676
5887
|
path: ["path"]
|
|
5677
5888
|
}).prefault({})
|
|
5678
5889
|
}).strict();
|
|
5679
|
-
const BoardDrawPointSchema = object({
|
|
5680
|
-
x: finite,
|
|
5681
|
-
y: finite,
|
|
5682
|
-
p: ratio.default(.5)
|
|
5890
|
+
const BoardDrawPointSchema$1 = object({
|
|
5891
|
+
x: finite$1,
|
|
5892
|
+
y: finite$1,
|
|
5893
|
+
p: ratio$1.default(.5)
|
|
5683
5894
|
}).strict();
|
|
5684
|
-
const BoardDrawItemSchema = object({
|
|
5685
|
-
...unsized,
|
|
5895
|
+
const BoardDrawItemSchema$1 = object({
|
|
5896
|
+
...unsized$1,
|
|
5686
5897
|
type: literal("draw"),
|
|
5687
|
-
props: object({ points: array(BoardDrawPointSchema).min(1).max(1e5) }).strict()
|
|
5898
|
+
props: object({ points: array(BoardDrawPointSchema$1).min(1).max(1e5) }).strict()
|
|
5688
5899
|
}).strict();
|
|
5689
|
-
const BoardArrowEndSchema = union([BoardVec2Schema, object({
|
|
5690
|
-
item: BoardIdSchema,
|
|
5900
|
+
const BoardArrowEndSchema$1 = union([BoardVec2Schema$1, object({
|
|
5901
|
+
item: BoardIdSchema$1,
|
|
5691
5902
|
anchor: union([
|
|
5692
5903
|
literal("auto"),
|
|
5693
5904
|
object({
|
|
@@ -5697,110 +5908,110 @@ const BoardArrowEndSchema = union([BoardVec2Schema, object({
|
|
|
5697
5908
|
"bottom",
|
|
5698
5909
|
"left"
|
|
5699
5910
|
]),
|
|
5700
|
-
offset: ratio.default(.5)
|
|
5911
|
+
offset: ratio$1.default(.5)
|
|
5701
5912
|
}).strict(),
|
|
5702
5913
|
object({
|
|
5703
|
-
x: ratio,
|
|
5704
|
-
y: ratio
|
|
5914
|
+
x: ratio$1,
|
|
5915
|
+
y: ratio$1
|
|
5705
5916
|
}).strict()
|
|
5706
5917
|
]).default("auto"),
|
|
5707
5918
|
port: string().min(1).max(120).optional()
|
|
5708
5919
|
}).strict()]);
|
|
5709
|
-
const BOARD_ARROW_ROUTES = [
|
|
5920
|
+
const BOARD_ARROW_ROUTES$1 = [
|
|
5710
5921
|
"straight",
|
|
5711
5922
|
"curve",
|
|
5712
5923
|
"orthogonal"
|
|
5713
5924
|
];
|
|
5714
|
-
const BoardArrowItemSchema = object({
|
|
5715
|
-
...unsized,
|
|
5925
|
+
const BoardArrowItemSchema$1 = object({
|
|
5926
|
+
...unsized$1,
|
|
5716
5927
|
type: literal("arrow"),
|
|
5717
5928
|
props: object({
|
|
5718
|
-
start: BoardArrowEndSchema,
|
|
5719
|
-
end: BoardArrowEndSchema,
|
|
5720
|
-
route: _enum(BOARD_ARROW_ROUTES).default("straight"),
|
|
5721
|
-
bend: finite.min(-.85).max(.85).default(0),
|
|
5722
|
-
waypoints: array(BoardVec2Schema).max(64).default([]),
|
|
5929
|
+
start: BoardArrowEndSchema$1,
|
|
5930
|
+
end: BoardArrowEndSchema$1,
|
|
5931
|
+
route: _enum(BOARD_ARROW_ROUTES$1).default("straight"),
|
|
5932
|
+
bend: finite$1.min(-.85).max(.85).default(0),
|
|
5933
|
+
waypoints: array(BoardVec2Schema$1).max(64).default([]),
|
|
5723
5934
|
arrowStart: boolean().default(false),
|
|
5724
5935
|
arrowEnd: boolean().default(true),
|
|
5725
5936
|
label: string().max(280).default(""),
|
|
5726
|
-
fontSize: fontSize.default(14),
|
|
5937
|
+
fontSize: fontSize$1.default(14),
|
|
5727
5938
|
relation: string().max(64).regex(/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/, "relation must be a lowercase slug").optional()
|
|
5728
5939
|
}).strict()
|
|
5729
5940
|
}).strict();
|
|
5730
|
-
const fileProps = {
|
|
5731
|
-
src:
|
|
5941
|
+
const fileProps$1 = {
|
|
5942
|
+
src: BoardMediaSourceSchema$1,
|
|
5732
5943
|
snapshot: object({
|
|
5733
5944
|
title: string().optional(),
|
|
5734
5945
|
mimeType: string().optional(),
|
|
5735
|
-
size: nonNegative.optional(),
|
|
5736
|
-
mtimeMs: nonNegative.optional(),
|
|
5737
|
-
naturalWidth: finite.positive().optional(),
|
|
5738
|
-
naturalHeight: finite.positive().optional(),
|
|
5739
|
-
durationMs: nonNegative.optional(),
|
|
5946
|
+
size: nonNegative$1.optional(),
|
|
5947
|
+
mtimeMs: nonNegative$1.optional(),
|
|
5948
|
+
naturalWidth: finite$1.positive().optional(),
|
|
5949
|
+
naturalHeight: finite$1.positive().optional(),
|
|
5950
|
+
durationMs: nonNegative$1.optional(),
|
|
5740
5951
|
excerpt: string().optional(),
|
|
5741
5952
|
coverPath: string().optional(),
|
|
5742
5953
|
coverUrl: string().optional()
|
|
5743
5954
|
}).strict().optional()
|
|
5744
5955
|
};
|
|
5745
|
-
const timedFileProps = {
|
|
5746
|
-
...fileProps,
|
|
5747
|
-
time: nonNegative.optional()
|
|
5956
|
+
const timedFileProps$1 = {
|
|
5957
|
+
...fileProps$1,
|
|
5958
|
+
time: nonNegative$1.optional()
|
|
5748
5959
|
};
|
|
5749
|
-
const BoardCropSchema = object({
|
|
5750
|
-
x: ratio,
|
|
5751
|
-
y: ratio,
|
|
5752
|
-
w: ratio,
|
|
5753
|
-
h: ratio
|
|
5960
|
+
const BoardCropSchema$1 = object({
|
|
5961
|
+
x: ratio$1,
|
|
5962
|
+
y: ratio$1,
|
|
5963
|
+
w: ratio$1,
|
|
5964
|
+
h: ratio$1
|
|
5754
5965
|
}).strict();
|
|
5755
|
-
const BoardImageItemSchema = object({
|
|
5756
|
-
...boxed(640, 360),
|
|
5966
|
+
const BoardImageItemSchema$1 = object({
|
|
5967
|
+
...boxed$1(640, 360),
|
|
5757
5968
|
type: literal("image"),
|
|
5758
5969
|
props: object({
|
|
5759
|
-
...fileProps,
|
|
5760
|
-
crop: BoardCropSchema.optional()
|
|
5970
|
+
...fileProps$1,
|
|
5971
|
+
crop: BoardCropSchema$1.optional()
|
|
5761
5972
|
}).strict()
|
|
5762
5973
|
}).strict();
|
|
5763
|
-
const BoardVideoItemSchema = object({
|
|
5764
|
-
...boxed(640, 360),
|
|
5974
|
+
const BoardVideoItemSchema$1 = object({
|
|
5975
|
+
...boxed$1(640, 360),
|
|
5765
5976
|
type: literal("video"),
|
|
5766
|
-
props: object(timedFileProps).strict()
|
|
5977
|
+
props: object(timedFileProps$1).strict()
|
|
5767
5978
|
}).strict();
|
|
5768
|
-
const BoardAudioItemSchema = object({
|
|
5769
|
-
...boxed(480, 96),
|
|
5979
|
+
const BoardAudioItemSchema$1 = object({
|
|
5980
|
+
...boxed$1(480, 96),
|
|
5770
5981
|
type: literal("audio"),
|
|
5771
|
-
props: object(timedFileProps).strict()
|
|
5982
|
+
props: object(timedFileProps$1).strict()
|
|
5772
5983
|
}).strict();
|
|
5773
|
-
const BoardFileItemSchema = object({
|
|
5774
|
-
...boxed(360, 220),
|
|
5984
|
+
const BoardFileItemSchema$1 = object({
|
|
5985
|
+
...boxed$1(360, 220),
|
|
5775
5986
|
type: literal("file"),
|
|
5776
|
-
props: object(fileProps).strict()
|
|
5987
|
+
props: object(fileProps$1).strict()
|
|
5777
5988
|
}).strict();
|
|
5778
|
-
const httpsUrl = string().max(4096).url();
|
|
5779
|
-
const artifactBase = {
|
|
5989
|
+
const httpsUrl$1 = string().max(4096).url();
|
|
5990
|
+
const artifactBase$1 = {
|
|
5780
5991
|
id: string().min(1).max(240),
|
|
5781
5992
|
title: string().max(240).optional(),
|
|
5782
|
-
url: httpsUrl,
|
|
5993
|
+
url: httpsUrl$1,
|
|
5783
5994
|
mimeType: string().max(160).optional()
|
|
5784
5995
|
};
|
|
5785
|
-
const BoardTaskArtifactSchema = discriminatedUnion("type", [
|
|
5996
|
+
const BoardTaskArtifactSchema$1 = discriminatedUnion("type", [
|
|
5786
5997
|
object({
|
|
5787
|
-
...artifactBase,
|
|
5998
|
+
...artifactBase$1,
|
|
5788
5999
|
type: literal("image"),
|
|
5789
|
-
naturalWidth: finite.positive().optional(),
|
|
5790
|
-
naturalHeight: finite.positive().optional()
|
|
6000
|
+
naturalWidth: finite$1.positive().optional(),
|
|
6001
|
+
naturalHeight: finite$1.positive().optional()
|
|
5791
6002
|
}).strict(),
|
|
5792
6003
|
object({
|
|
5793
|
-
...artifactBase,
|
|
6004
|
+
...artifactBase$1,
|
|
5794
6005
|
type: literal("video"),
|
|
5795
|
-
previewUrl: httpsUrl.optional(),
|
|
6006
|
+
previewUrl: httpsUrl$1.optional(),
|
|
5796
6007
|
durationMs: number$1().int().positive().optional(),
|
|
5797
|
-
naturalWidth: finite.positive().optional(),
|
|
5798
|
-
naturalHeight: finite.positive().optional()
|
|
6008
|
+
naturalWidth: finite$1.positive().optional(),
|
|
6009
|
+
naturalHeight: finite$1.positive().optional()
|
|
5799
6010
|
}).strict(),
|
|
5800
6011
|
object({
|
|
5801
|
-
...artifactBase,
|
|
6012
|
+
...artifactBase$1,
|
|
5802
6013
|
type: literal("audio"),
|
|
5803
|
-
previewUrl: httpsUrl.optional(),
|
|
6014
|
+
previewUrl: httpsUrl$1.optional(),
|
|
5804
6015
|
durationMs: number$1().int().positive().optional()
|
|
5805
6016
|
}).strict(),
|
|
5806
6017
|
object({
|
|
@@ -5810,7 +6021,7 @@ const BoardTaskArtifactSchema = discriminatedUnion("type", [
|
|
|
5810
6021
|
textExcerpt: string().min(1).max(480)
|
|
5811
6022
|
}).strict()
|
|
5812
6023
|
]);
|
|
5813
|
-
const BoardTaskSnapshotSchema = object({
|
|
6024
|
+
const BoardTaskSnapshotSchema$1 = object({
|
|
5814
6025
|
taskType: string().min(1).max(120),
|
|
5815
6026
|
status: _enum([
|
|
5816
6027
|
"pending",
|
|
@@ -5822,81 +6033,78 @@ const BoardTaskSnapshotSchema = object({
|
|
|
5822
6033
|
model: string().max(160).optional(),
|
|
5823
6034
|
promptExcerpt: string().max(480).optional(),
|
|
5824
6035
|
artifactCount: number$1().int().nonnegative(),
|
|
5825
|
-
artifacts: array(BoardTaskArtifactSchema).max(6).default([]),
|
|
6036
|
+
artifacts: array(BoardTaskArtifactSchema$1).max(6).default([]),
|
|
5826
6037
|
updatedAt: string().optional()
|
|
5827
6038
|
}).strict();
|
|
5828
|
-
const BoardTaskItemSchema = object({
|
|
5829
|
-
...boxed(420, 240),
|
|
6039
|
+
const BoardTaskItemSchema$1 = object({
|
|
6040
|
+
...boxed$1(420, 240),
|
|
5830
6041
|
type: literal("task"),
|
|
5831
6042
|
props: object({
|
|
5832
6043
|
taskRunId: string().min(1).max(160),
|
|
5833
|
-
snapshot: BoardTaskSnapshotSchema
|
|
6044
|
+
snapshot: BoardTaskSnapshotSchema$1
|
|
5834
6045
|
}).strict()
|
|
5835
6046
|
}).strict();
|
|
5836
|
-
const BOARD_EFFECT_KINDS = [
|
|
6047
|
+
const BOARD_EFFECT_KINDS$1 = [
|
|
5837
6048
|
"particles",
|
|
5838
6049
|
"trail",
|
|
5839
6050
|
"impact",
|
|
5840
6051
|
"flash",
|
|
5841
6052
|
"glow"
|
|
5842
6053
|
];
|
|
5843
|
-
const BoardEffectItemSchema = object({
|
|
5844
|
-
...boxed(240, 240),
|
|
6054
|
+
const BoardEffectItemSchema$1 = object({
|
|
6055
|
+
...boxed$1(240, 240),
|
|
5845
6056
|
type: literal("effect"),
|
|
5846
6057
|
props: object({
|
|
5847
|
-
kind: _enum(BOARD_EFFECT_KINDS),
|
|
5848
|
-
rate: nonNegative.max(2e4).default(40),
|
|
5849
|
-
life: finite.positive().max(6e4).default(1200),
|
|
5850
|
-
particleSize: finite.positive().max(512).default(6),
|
|
5851
|
-
speed: nonNegative.max(1e4).default(120),
|
|
5852
|
-
direction: finite.default(-90),
|
|
5853
|
-
spread: finite.min(0).max(360).default(360),
|
|
5854
|
-
gravity: finite.default(0),
|
|
5855
|
-
intensity: nonNegative.max(10).default(1),
|
|
5856
|
-
follow: BoardIdSchema.optional(),
|
|
6058
|
+
kind: _enum(BOARD_EFFECT_KINDS$1),
|
|
6059
|
+
rate: nonNegative$1.max(2e4).default(40),
|
|
6060
|
+
life: finite$1.positive().max(6e4).default(1200),
|
|
6061
|
+
particleSize: finite$1.positive().max(512).default(6),
|
|
6062
|
+
speed: nonNegative$1.max(1e4).default(120),
|
|
6063
|
+
direction: finite$1.default(-90),
|
|
6064
|
+
spread: finite$1.min(0).max(360).default(360),
|
|
6065
|
+
gravity: finite$1.default(0),
|
|
6066
|
+
intensity: nonNegative$1.max(10).default(1),
|
|
6067
|
+
follow: BoardIdSchema$1.optional(),
|
|
5857
6068
|
seed: string().min(1).max(160).optional()
|
|
5858
6069
|
}).strict()
|
|
5859
6070
|
}).strict();
|
|
5860
|
-
const
|
|
5861
|
-
...boxed(480, 320),
|
|
5862
|
-
type: literal("
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
params: record(string(), unknown()).default({}),
|
|
5866
|
-
seed: string().min(1).max(160).optional()
|
|
5867
|
-
}).strict()
|
|
6071
|
+
const BoardCustomItemSchema$1 = object({
|
|
6072
|
+
...boxed$1(480, 320),
|
|
6073
|
+
type: literal("custom"),
|
|
6074
|
+
script: BoardScriptSchema$1,
|
|
6075
|
+
props: object({}).strict().default({})
|
|
5868
6076
|
}).strict();
|
|
5869
|
-
const BOARD_EXTENSION_TYPE_PATTERN = /^[a-z][a-z0-9]*(?:\.[a-z0-9-]+)+$/;
|
|
5870
|
-
const BoardExtensionItemSchema = object({
|
|
5871
|
-
...itemEnvelope,
|
|
5872
|
-
size: BoardSizeSchema.default({
|
|
6077
|
+
const BOARD_EXTENSION_TYPE_PATTERN$1 = /^[a-z][a-z0-9]*(?:\.[a-z0-9-]+)+$/;
|
|
6078
|
+
const BoardExtensionItemSchema$1 = object({
|
|
6079
|
+
...itemEnvelope$1,
|
|
6080
|
+
size: BoardSizeSchema$1.default({
|
|
5873
6081
|
width: 240,
|
|
5874
6082
|
height: 160
|
|
5875
6083
|
}),
|
|
5876
|
-
type: string().regex(BOARD_EXTENSION_TYPE_PATTERN),
|
|
6084
|
+
type: string().regex(BOARD_EXTENSION_TYPE_PATTERN$1),
|
|
5877
6085
|
props: record(string(), unknown()).default({})
|
|
5878
6086
|
}).strict();
|
|
5879
|
-
const BOARD_ITEM_SCHEMAS = {
|
|
5880
|
-
frame: BoardFrameItemSchema,
|
|
5881
|
-
text: BoardTextItemSchema,
|
|
5882
|
-
shape: BoardShapeItemSchema,
|
|
5883
|
-
draw: BoardDrawItemSchema,
|
|
5884
|
-
arrow: BoardArrowItemSchema,
|
|
5885
|
-
image: BoardImageItemSchema,
|
|
5886
|
-
video: BoardVideoItemSchema,
|
|
5887
|
-
audio: BoardAudioItemSchema,
|
|
5888
|
-
file: BoardFileItemSchema,
|
|
5889
|
-
task: BoardTaskItemSchema,
|
|
5890
|
-
effect: BoardEffectItemSchema,
|
|
5891
|
-
|
|
5892
|
-
};
|
|
5893
|
-
const BOARD_ITEM_TYPES = Object.keys(BOARD_ITEM_SCHEMAS);
|
|
6087
|
+
const BOARD_ITEM_SCHEMAS$1 = {
|
|
6088
|
+
frame: BoardFrameItemSchema$1,
|
|
6089
|
+
text: BoardTextItemSchema$1,
|
|
6090
|
+
shape: BoardShapeItemSchema$1,
|
|
6091
|
+
draw: BoardDrawItemSchema$1,
|
|
6092
|
+
arrow: BoardArrowItemSchema$1,
|
|
6093
|
+
image: BoardImageItemSchema$1,
|
|
6094
|
+
video: BoardVideoItemSchema$1,
|
|
6095
|
+
audio: BoardAudioItemSchema$1,
|
|
6096
|
+
file: BoardFileItemSchema$1,
|
|
6097
|
+
task: BoardTaskItemSchema$1,
|
|
6098
|
+
effect: BoardEffectItemSchema$1,
|
|
6099
|
+
custom: BoardCustomItemSchema$1
|
|
6100
|
+
};
|
|
6101
|
+
const BOARD_ITEM_TYPES$1 = Object.keys(BOARD_ITEM_SCHEMAS$1);
|
|
5894
6102
|
function isBuiltinItemType(type) {
|
|
5895
|
-
return Object.hasOwn(BOARD_ITEM_SCHEMAS, type);
|
|
6103
|
+
return Object.hasOwn(BOARD_ITEM_SCHEMAS$1, type);
|
|
5896
6104
|
}
|
|
5897
6105
|
function boardItemSchema(type) {
|
|
5898
|
-
if (isBuiltinItemType(type)) return BOARD_ITEM_SCHEMAS[type];
|
|
5899
|
-
return BOARD_EXTENSION_TYPE_PATTERN.test(type) ? BoardExtensionItemSchema : null;
|
|
6106
|
+
if (isBuiltinItemType(type)) return BOARD_ITEM_SCHEMAS$1[type];
|
|
6107
|
+
return BOARD_EXTENSION_TYPE_PATTERN$1.test(type) ? BoardExtensionItemSchema$1 : null;
|
|
5900
6108
|
}
|
|
5901
6109
|
function issuesToDiagnostics(error, path) {
|
|
5902
6110
|
return error.issues.map((issue) => {
|
|
@@ -5908,7 +6116,7 @@ function issuesToDiagnostics(error, path) {
|
|
|
5908
6116
|
};
|
|
5909
6117
|
});
|
|
5910
6118
|
}
|
|
5911
|
-
function parseBoardItem(value, path = "item") {
|
|
6119
|
+
function parseBoardItem$1(value, path = "item") {
|
|
5912
6120
|
const type = value && typeof value === "object" && !Array.isArray(value) ? value.type : void 0;
|
|
5913
6121
|
if (typeof type !== "string") return {
|
|
5914
6122
|
ok: false,
|
|
@@ -5926,7 +6134,7 @@ function parseBoardItem(value, path = "item") {
|
|
|
5926
6134
|
severity: "error",
|
|
5927
6135
|
code: "INVALID_ITEM",
|
|
5928
6136
|
path: `${path}.type`,
|
|
5929
|
-
message: `unknown type ${type}; expected ${BOARD_ITEM_TYPES.join(", ")} or a namespaced extension type`
|
|
6137
|
+
message: `unknown type ${type}; expected ${BOARD_ITEM_TYPES$1.join(", ")} or a namespaced extension type`
|
|
5930
6138
|
}]
|
|
5931
6139
|
};
|
|
5932
6140
|
const parsed = schema.safeParse(value);
|
|
@@ -5938,7 +6146,7 @@ function parseBoardItem(value, path = "item") {
|
|
|
5938
6146
|
diagnostics: issuesToDiagnostics(parsed.error, path)
|
|
5939
6147
|
};
|
|
5940
6148
|
}
|
|
5941
|
-
const CSS_EASE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
6149
|
+
const CSS_EASE_KEYWORDS$1 = /* @__PURE__ */ new Set([
|
|
5942
6150
|
"linear",
|
|
5943
6151
|
"ease",
|
|
5944
6152
|
"ease-in",
|
|
@@ -5947,9 +6155,9 @@ const CSS_EASE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
5947
6155
|
"step-start",
|
|
5948
6156
|
"step-end"
|
|
5949
6157
|
]);
|
|
5950
|
-
function isBoardEase(value) {
|
|
6158
|
+
function isBoardEase$1(value) {
|
|
5951
6159
|
const ease = value.trim();
|
|
5952
|
-
if (CSS_EASE_KEYWORDS.has(ease)) return true;
|
|
6160
|
+
if (CSS_EASE_KEYWORDS$1.has(ease)) return true;
|
|
5953
6161
|
const bezier = /^cubic-bezier\(\s*([^,()]+),\s*([^,()]+),\s*([^,()]+),\s*([^,()]+)\)$/.exec(ease);
|
|
5954
6162
|
if (bezier) {
|
|
5955
6163
|
const [x1, , x2] = bezier.slice(1).map(Number);
|
|
@@ -5957,16 +6165,16 @@ function isBoardEase(value) {
|
|
|
5957
6165
|
}
|
|
5958
6166
|
return /^steps\(\s*[1-9]\d*\s*(?:,\s*(?:start|end|jump-start|jump-end|jump-none|jump-both)\s*)?\)$/.test(ease);
|
|
5959
6167
|
}
|
|
5960
|
-
const BoardEaseSchema = string().max(120).refine(isBoardEase, "expected a CSS easing: linear, ease-in-out, cubic-bezier(…) or steps(…)");
|
|
5961
|
-
const BoardKeyframeSchema = object({
|
|
5962
|
-
at: nonNegative,
|
|
6168
|
+
const BoardEaseSchema$1 = string().max(120).refine(isBoardEase$1, "expected a CSS easing: linear, ease-in-out, cubic-bezier(…) or steps(…)");
|
|
6169
|
+
const BoardKeyframeSchema$1 = object({
|
|
6170
|
+
at: nonNegative$1,
|
|
5963
6171
|
value: unknown(),
|
|
5964
|
-
ease: BoardEaseSchema.optional()
|
|
6172
|
+
ease: BoardEaseSchema$1.optional()
|
|
5965
6173
|
}).strict();
|
|
5966
|
-
const BoardTrackSchema = object({
|
|
6174
|
+
const BoardTrackSchema$1 = object({
|
|
5967
6175
|
target: string().min(1).max(160),
|
|
5968
6176
|
property: string().min(1).max(200),
|
|
5969
|
-
keyframes: array(BoardKeyframeSchema).min(1).max(1e5),
|
|
6177
|
+
keyframes: array(BoardKeyframeSchema$1).min(1).max(1e5),
|
|
5970
6178
|
composite: _enum(["replace", "add"]).default("replace"),
|
|
5971
6179
|
interpolation: _enum([
|
|
5972
6180
|
"auto",
|
|
@@ -5985,65 +6193,65 @@ const BoardTrackSchema = object({
|
|
|
5985
6193
|
]
|
|
5986
6194
|
});
|
|
5987
6195
|
});
|
|
5988
|
-
const BoardMarkerSchema = object({
|
|
5989
|
-
at: nonNegative,
|
|
6196
|
+
const BoardMarkerSchema$1 = object({
|
|
6197
|
+
at: nonNegative$1,
|
|
5990
6198
|
label: string().max(280).optional(),
|
|
5991
6199
|
pause: boolean().optional()
|
|
5992
6200
|
}).strict();
|
|
5993
|
-
const BoardAnimationHeaderSchema = object({
|
|
6201
|
+
const BoardAnimationHeaderSchema$1 = object({
|
|
5994
6202
|
name: string().max(280).optional(),
|
|
5995
|
-
duration: finite.positive().max(864e5),
|
|
6203
|
+
duration: finite$1.positive().max(864e5),
|
|
5996
6204
|
play: _enum([
|
|
5997
6205
|
"manual",
|
|
5998
6206
|
"auto",
|
|
5999
6207
|
"always"
|
|
6000
6208
|
]).default("manual"),
|
|
6001
|
-
delay: nonNegative.default(0),
|
|
6209
|
+
delay: nonNegative$1.default(0),
|
|
6002
6210
|
loop: boolean().default(false),
|
|
6003
6211
|
end: _enum(["hold", "reset"]).default("hold"),
|
|
6004
|
-
markers: array(BoardMarkerSchema).max(1e4).default([])
|
|
6212
|
+
markers: array(BoardMarkerSchema$1).max(1e4).default([])
|
|
6005
6213
|
}).strict();
|
|
6006
|
-
BoardAnimationHeaderSchema.extend({ tracks: record(
|
|
6007
|
-
const BOARD_ENTER_PRESETS = [
|
|
6214
|
+
BoardAnimationHeaderSchema$1.extend({ tracks: record(BoardKeySchema$1, BoardTrackSchema$1).default({}) }).strict();
|
|
6215
|
+
const BOARD_ENTER_PRESETS$1 = [
|
|
6008
6216
|
"fade-in",
|
|
6009
6217
|
"rise",
|
|
6010
6218
|
"drop",
|
|
6011
6219
|
"pop",
|
|
6012
6220
|
"deal"
|
|
6013
6221
|
];
|
|
6014
|
-
const BoardBackgroundSchema = object({
|
|
6222
|
+
const BoardBackgroundSchema$1 = object({
|
|
6015
6223
|
kind: _enum([
|
|
6016
6224
|
"solid",
|
|
6017
6225
|
"dots",
|
|
6018
6226
|
"grid",
|
|
6019
6227
|
"image"
|
|
6020
6228
|
]).default("dots"),
|
|
6021
|
-
color: BoardColorSchema.optional(),
|
|
6229
|
+
color: BoardColorSchema$1.optional(),
|
|
6022
6230
|
imageUrl: string().url().max(4096).optional(),
|
|
6023
6231
|
fit: _enum([
|
|
6024
6232
|
"cover",
|
|
6025
6233
|
"contain",
|
|
6026
6234
|
"repeat"
|
|
6027
6235
|
]).optional(),
|
|
6028
|
-
opacity: ratio.optional()
|
|
6236
|
+
opacity: ratio$1.optional()
|
|
6029
6237
|
}).strict();
|
|
6030
|
-
const BoardSettingsSchema = object({
|
|
6238
|
+
const BoardSettingsSchema$1 = object({
|
|
6031
6239
|
title: string().min(1).max(255).optional(),
|
|
6032
|
-
background: BoardBackgroundSchema.default({ kind: "dots" }),
|
|
6240
|
+
background: BoardBackgroundSchema$1.default({ kind: "dots" }),
|
|
6033
6241
|
grid: object({
|
|
6034
6242
|
visible: boolean().default(false),
|
|
6035
|
-
size: finite.min(4).max(512).default(24)
|
|
6243
|
+
size: finite$1.min(4).max(512).default(24)
|
|
6036
6244
|
}).strict().optional(),
|
|
6037
6245
|
enter: object({
|
|
6038
|
-
preset: _enum(BOARD_ENTER_PRESETS),
|
|
6039
|
-
duration: finite.positive().max(1e4).optional()
|
|
6246
|
+
preset: _enum(BOARD_ENTER_PRESETS$1),
|
|
6247
|
+
duration: finite$1.positive().max(1e4).optional()
|
|
6040
6248
|
}).strict().optional()
|
|
6041
6249
|
}).strict();
|
|
6042
|
-
const patchObject = record(string(), unknown());
|
|
6043
|
-
const BoardPatchSchema = object({
|
|
6044
|
-
board: patchObject.nullable().optional(),
|
|
6045
|
-
items: record(BoardIdSchema, patchObject.nullable()).optional(),
|
|
6046
|
-
animations: record(BoardIdSchema, patchObject.and(object({ tracks: record(
|
|
6250
|
+
const patchObject$1 = record(string(), unknown());
|
|
6251
|
+
const BoardPatchSchema$1 = object({
|
|
6252
|
+
board: patchObject$1.nullable().optional(),
|
|
6253
|
+
items: record(BoardIdSchema$1, patchObject$1.nullable()).optional(),
|
|
6254
|
+
animations: record(BoardIdSchema$1, patchObject$1.and(object({ tracks: record(BoardKeySchema$1, patchObject$1.nullable()).optional() })).nullable()).optional()
|
|
6047
6255
|
}).strict();
|
|
6048
6256
|
//#endregion
|
|
6049
6257
|
//#region ../protocol/src/board-layout.ts
|
|
@@ -6592,7 +6800,8 @@ const NOT_ANIMATABLE = /* @__PURE__ */ new Set([
|
|
|
6592
6800
|
"parent",
|
|
6593
6801
|
"z",
|
|
6594
6802
|
"locked",
|
|
6595
|
-
"metadata"
|
|
6803
|
+
"metadata",
|
|
6804
|
+
"script"
|
|
6596
6805
|
]);
|
|
6597
6806
|
const CAMERA_PROPERTIES = {
|
|
6598
6807
|
focus: {
|
|
@@ -6611,7 +6820,7 @@ const CAMERA_PROPERTIES = {
|
|
|
6611
6820
|
},
|
|
6612
6821
|
center: {
|
|
6613
6822
|
kind: "vec2",
|
|
6614
|
-
schema: BoardVec2Schema
|
|
6823
|
+
schema: BoardVec2Schema$1
|
|
6615
6824
|
},
|
|
6616
6825
|
shake: {
|
|
6617
6826
|
kind: "number",
|
|
@@ -6625,7 +6834,7 @@ const ANIMATION_PROPERTIES = { time: {
|
|
|
6625
6834
|
function kindOf(schema, path) {
|
|
6626
6835
|
const leaf = path[path.length - 1];
|
|
6627
6836
|
if (path.length === 1 && leaf === "scale") return "scale";
|
|
6628
|
-
if (schema === BoardColorSchema || unwrap(schema) === BoardColorSchema) return "color";
|
|
6837
|
+
if (schema === BoardColorSchema$1 || unwrap(schema) === BoardColorSchema$1) return "color";
|
|
6629
6838
|
if (def(unwrap(schema)).type === "number") return "number";
|
|
6630
6839
|
const shape = unwrapObjectShape(schema);
|
|
6631
6840
|
if (shape && Object.keys(shape).length === 2 && "x" in shape && "y" in shape) return "vec2";
|
|
@@ -6701,7 +6910,7 @@ function trackReferences(track) {
|
|
|
6701
6910
|
if (track.target === "camera" && track.property === "focus") {
|
|
6702
6911
|
for (const keyframe of track.keyframes) if (typeof keyframe.value === "string") refs.add(keyframe.value);
|
|
6703
6912
|
}
|
|
6704
|
-
refs.delete(BOARD_CAMERA_TARGET);
|
|
6913
|
+
refs.delete(BOARD_CAMERA_TARGET$1);
|
|
6705
6914
|
return [...refs];
|
|
6706
6915
|
}
|
|
6707
6916
|
function pruneBoardTrack(track, deleted) {
|
|
@@ -6728,7 +6937,7 @@ function stripId(value) {
|
|
|
6728
6937
|
return rest;
|
|
6729
6938
|
}
|
|
6730
6939
|
function applyBoardPatch(state, input, options = {}) {
|
|
6731
|
-
const parsedPatch = BoardPatchSchema.safeParse(input);
|
|
6940
|
+
const parsedPatch = BoardPatchSchema$1.safeParse(input);
|
|
6732
6941
|
if (!parsedPatch.success) return {
|
|
6733
6942
|
ok: false,
|
|
6734
6943
|
diagnostics: parsedPatch.error.issues.map((issue) => error(issue.path.map(String).join("."), "INVALID_PATCH", issue.message))
|
|
@@ -6753,7 +6962,7 @@ function applyBoardPatch(state, input, options = {}) {
|
|
|
6753
6962
|
};
|
|
6754
6963
|
if (patch.board !== void 0) {
|
|
6755
6964
|
const merged = patch.board === null ? {} : mergePatch(state.settings(), patch.board);
|
|
6756
|
-
const parsed = BoardSettingsSchema.safeParse(merged);
|
|
6965
|
+
const parsed = BoardSettingsSchema$1.safeParse(merged);
|
|
6757
6966
|
if (parsed.success) nextSettings = parsed.data;
|
|
6758
6967
|
else for (const issue of parsed.error.issues) diagnostics.push(error(["board", ...issue.path.map(String)].join("."), "INVALID_BOARD", issue.message));
|
|
6759
6968
|
}
|
|
@@ -6773,7 +6982,7 @@ function applyBoardPatch(state, input, options = {}) {
|
|
|
6773
6982
|
candidate.z = current?.z ?? top + 1;
|
|
6774
6983
|
if (!current) zByParent.set(key, top + 1);
|
|
6775
6984
|
}
|
|
6776
|
-
const parsed = parseBoardItem(candidate, `items.${id}`);
|
|
6985
|
+
const parsed = parseBoardItem$1(candidate, `items.${id}`);
|
|
6777
6986
|
if (!parsed.ok) {
|
|
6778
6987
|
diagnostics.push(...parsed.diagnostics);
|
|
6779
6988
|
continue;
|
|
@@ -6799,7 +7008,7 @@ function applyBoardPatch(state, input, options = {}) {
|
|
|
6799
7008
|
...headerPatch,
|
|
6800
7009
|
...headerPatch.duration === void 0 ? { duration: lastKeyframeAt([...Object.values(tracks ?? {}), ...state.trackIds(id).map((trackId) => state.track(id, trackId))]) } : {}
|
|
6801
7010
|
};
|
|
6802
|
-
const parsed = BoardAnimationHeaderSchema.safeParse(header);
|
|
7011
|
+
const parsed = BoardAnimationHeaderSchema$1.safeParse(header);
|
|
6803
7012
|
if (!parsed.success) {
|
|
6804
7013
|
for (const issue of parsed.error.issues) diagnostics.push(error([
|
|
6805
7014
|
"animations",
|
|
@@ -6816,7 +7025,7 @@ function applyBoardPatch(state, input, options = {}) {
|
|
|
6816
7025
|
if (existing) setTrack(id, trackId, null);
|
|
6817
7026
|
continue;
|
|
6818
7027
|
}
|
|
6819
|
-
const parsed = BoardTrackSchema.safeParse(existing ? mergePatch(existing, trackPatch) : trackPatch);
|
|
7028
|
+
const parsed = BoardTrackSchema$1.safeParse(existing ? mergePatch(existing, trackPatch) : trackPatch);
|
|
6820
7029
|
if (!parsed.success) {
|
|
6821
7030
|
for (const issue of parsed.error.issues) diagnostics.push(error([
|
|
6822
7031
|
"animations",
|
|
@@ -6981,7 +7190,7 @@ function applyBoardPatch(state, input, options = {}) {
|
|
|
6981
7190
|
function validateTrack(path, animationId, value, item, animation, track, state) {
|
|
6982
7191
|
const diagnostics = [];
|
|
6983
7192
|
let targetType;
|
|
6984
|
-
if (value.target === "camera") targetType = BOARD_CAMERA_TARGET;
|
|
7193
|
+
if (value.target === "camera") targetType = BOARD_CAMERA_TARGET$1;
|
|
6985
7194
|
else if (item(value.target)) targetType = item(value.target).type;
|
|
6986
7195
|
else if (animation(value.target)) {
|
|
6987
7196
|
targetType = "animation";
|
|
@@ -7105,7 +7314,7 @@ function parseBoardDocument(input) {
|
|
|
7105
7314
|
diagnostics: [error("", "INVALID_DOCUMENT", "expected an object")]
|
|
7106
7315
|
};
|
|
7107
7316
|
const result = applyBoardPatchToDocument({
|
|
7108
|
-
board: BoardSettingsSchema.parse({}),
|
|
7317
|
+
board: BoardSettingsSchema$1.parse({}),
|
|
7109
7318
|
items: {},
|
|
7110
7319
|
animations: {}
|
|
7111
7320
|
}, {
|
|
@@ -7119,40 +7328,436 @@ function parseBoardDocument(input) {
|
|
|
7119
7328
|
} : result;
|
|
7120
7329
|
}
|
|
7121
7330
|
//#endregion
|
|
7122
|
-
//#region ../sdk/src/board/model/
|
|
7123
|
-
const
|
|
7124
|
-
function
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7331
|
+
//#region ../sdk/src/board/model/script-index.ts
|
|
7332
|
+
const scriptsByItems = /* @__PURE__ */ new WeakMap();
|
|
7333
|
+
function boardScriptItems(document) {
|
|
7334
|
+
let scripts = scriptsByItems.get(document.items);
|
|
7335
|
+
if (!scripts) {
|
|
7336
|
+
scripts = Object.entries(document.items).filter(([, item]) => item.script?.enabled);
|
|
7337
|
+
scriptsByItems.set(document.items, scripts);
|
|
7338
|
+
}
|
|
7339
|
+
return scripts;
|
|
7340
|
+
}
|
|
7341
|
+
function hasBoardScripts(document) {
|
|
7342
|
+
return boardScriptItems(document).length > 0;
|
|
7343
|
+
}
|
|
7344
|
+
//#endregion
|
|
7345
|
+
//#region ../sdk/src/board/model/geometry.ts
|
|
7346
|
+
function worldPoint(x, y) {
|
|
7347
|
+
return {
|
|
7348
|
+
x,
|
|
7349
|
+
y
|
|
7350
|
+
};
|
|
7351
|
+
}
|
|
7352
|
+
const MIN_BOARD_ZOOM = .05;
|
|
7353
|
+
const DEFAULT_BOARD_VIEWPORT = {
|
|
7354
|
+
x: 0,
|
|
7355
|
+
y: 0,
|
|
7356
|
+
zoom: 1
|
|
7357
|
+
};
|
|
7358
|
+
function clampZoom(zoom) {
|
|
7359
|
+
const value = Number.isFinite(zoom) ? zoom : DEFAULT_BOARD_VIEWPORT.zoom;
|
|
7360
|
+
return Math.min(8, Math.max(MIN_BOARD_ZOOM, value));
|
|
7361
|
+
}
|
|
7362
|
+
function normalizeViewport(viewport, fallback = DEFAULT_BOARD_VIEWPORT) {
|
|
7363
|
+
const fallbackX = Number.isFinite(fallback.x) ? fallback.x : DEFAULT_BOARD_VIEWPORT.x;
|
|
7364
|
+
const fallbackY = Number.isFinite(fallback.y) ? fallback.y : DEFAULT_BOARD_VIEWPORT.y;
|
|
7365
|
+
const fallbackZoom = Number.isFinite(fallback.zoom) ? clampZoom(fallback.zoom) : DEFAULT_BOARD_VIEWPORT.zoom;
|
|
7366
|
+
return {
|
|
7367
|
+
x: Number.isFinite(viewport.x) ? viewport.x : fallbackX,
|
|
7368
|
+
y: Number.isFinite(viewport.y) ? viewport.y : fallbackY,
|
|
7369
|
+
zoom: Number.isFinite(viewport.zoom) ? clampZoom(viewport.zoom) : fallbackZoom
|
|
7370
|
+
};
|
|
7371
|
+
}
|
|
7372
|
+
function degToRad(degrees) {
|
|
7373
|
+
return degrees * Math.PI / 180;
|
|
7374
|
+
}
|
|
7375
|
+
function frameRect(frame) {
|
|
7376
|
+
return {
|
|
7377
|
+
x: frame.x,
|
|
7378
|
+
y: frame.y,
|
|
7379
|
+
width: frame.width,
|
|
7380
|
+
height: frame.height
|
|
7381
|
+
};
|
|
7382
|
+
}
|
|
7383
|
+
function rectCenter(rect) {
|
|
7384
|
+
return worldPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);
|
|
7385
|
+
}
|
|
7386
|
+
function rectsIntersect(a, b) {
|
|
7387
|
+
return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
|
|
7388
|
+
}
|
|
7389
|
+
function unionRects(rects) {
|
|
7390
|
+
if (rects.length === 0) return null;
|
|
7391
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
7392
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
7393
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
7394
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
7395
|
+
for (const rect of rects) {
|
|
7396
|
+
minX = Math.min(minX, rect.x);
|
|
7397
|
+
minY = Math.min(minY, rect.y);
|
|
7398
|
+
maxX = Math.max(maxX, rect.x + rect.width);
|
|
7399
|
+
maxY = Math.max(maxY, rect.y + rect.height);
|
|
7400
|
+
}
|
|
7401
|
+
return {
|
|
7402
|
+
x: minX,
|
|
7403
|
+
y: minY,
|
|
7404
|
+
width: maxX - minX,
|
|
7405
|
+
height: maxY - minY
|
|
7406
|
+
};
|
|
7407
|
+
}
|
|
7408
|
+
function itemBounds(frame) {
|
|
7409
|
+
const rotation = frame.rotation || 0;
|
|
7410
|
+
if (rotation === 0) return frameRect(frame);
|
|
7411
|
+
const center = rectCenter(frameRect(frame));
|
|
7412
|
+
const halfW = frame.width / 2;
|
|
7413
|
+
const halfH = frame.height / 2;
|
|
7414
|
+
const rad = degToRad(rotation);
|
|
7415
|
+
const cos = Math.abs(Math.cos(rad));
|
|
7416
|
+
const sin = Math.abs(Math.sin(rad));
|
|
7417
|
+
const boundW = halfW * cos + halfH * sin;
|
|
7418
|
+
const boundH = halfW * sin + halfH * cos;
|
|
7419
|
+
return {
|
|
7420
|
+
x: center.x - boundW,
|
|
7421
|
+
y: center.y - boundH,
|
|
7422
|
+
width: boundW * 2,
|
|
7423
|
+
height: boundH * 2
|
|
7424
|
+
};
|
|
7425
|
+
}
|
|
7426
|
+
function cameraForState(state, surface) {
|
|
7427
|
+
const zoom = clampZoom(state.zoom);
|
|
7428
|
+
return normalizeViewport({
|
|
7429
|
+
x: surface.width / 2 - state.centerX * zoom,
|
|
7430
|
+
y: surface.height / 2 - state.centerY * zoom,
|
|
7431
|
+
zoom
|
|
7432
|
+
});
|
|
7433
|
+
}
|
|
7434
|
+
function cameraForRect(content, surface, options = {}) {
|
|
7435
|
+
const padding = Math.max(0, Number.isFinite(options.padding) ? options.padding : 32);
|
|
7436
|
+
const availableW = Math.max(1, surface.width - padding * 2);
|
|
7437
|
+
const availableH = Math.max(1, surface.height - padding * 2);
|
|
7438
|
+
const fitZoom = options.fit === "cover" ? Math.max(availableW / content.width, availableH / content.height) : Math.min(availableW / content.width, availableH / content.height);
|
|
7439
|
+
const minZoom = clampZoom(options.minZoom ?? .05);
|
|
7440
|
+
const maxZoom = clampZoom(options.maxZoom ?? 8);
|
|
7441
|
+
const zoom = Math.min(Math.max(fitZoom, Math.min(minZoom, maxZoom)), Math.max(minZoom, maxZoom));
|
|
7442
|
+
const center = rectCenter(content);
|
|
7443
|
+
return cameraForState({
|
|
7444
|
+
centerX: center.x,
|
|
7445
|
+
centerY: center.y,
|
|
7446
|
+
zoom
|
|
7447
|
+
}, surface);
|
|
7448
|
+
}
|
|
7449
|
+
function rectForCameraFocus(focus, getFrame) {
|
|
7450
|
+
if (typeof focus !== "string") return focus;
|
|
7451
|
+
const frame = getFrame(focus);
|
|
7452
|
+
return frame ? itemBounds(frame) : null;
|
|
7453
|
+
}
|
|
7454
|
+
function cameraForFocus(focus, getFrame, surface, options = {}) {
|
|
7455
|
+
const rect = rectForCameraFocus(focus, getFrame);
|
|
7456
|
+
return rect ? cameraForRect(rect, surface, options) : null;
|
|
7457
|
+
}
|
|
7458
|
+
//#endregion
|
|
7459
|
+
//#region ../sdk/src/board/model/script-scene.ts
|
|
7460
|
+
function parseBoardItem(value) {
|
|
7461
|
+
const parsed = parseBoardItem$1(value);
|
|
7462
|
+
if (!parsed.ok) throw new Error(parsed.diagnostics[0]?.message ?? "Invalid script item.");
|
|
7463
|
+
return parsed.item;
|
|
7464
|
+
}
|
|
7465
|
+
const DISPLAY_FIELDS = /* @__PURE__ */ new Set([
|
|
7466
|
+
"position",
|
|
7467
|
+
"size",
|
|
7468
|
+
"rotation",
|
|
7469
|
+
"scale",
|
|
7470
|
+
"opacity",
|
|
7471
|
+
"style",
|
|
7472
|
+
"props"
|
|
7473
|
+
]);
|
|
7474
|
+
function boardScriptScope(document, id) {
|
|
7475
|
+
const scope = /* @__PURE__ */ new Set([id, ...Object.values(document.items[id]?.script?.targets ?? {}).filter((target) => Boolean(document.items[target]))]);
|
|
7476
|
+
if (document.items[id]?.type !== "frame") return scope;
|
|
7477
|
+
for (const candidate of Object.keys(document.items)) {
|
|
7478
|
+
let parent = document.items[candidate]?.parent;
|
|
7479
|
+
for (let depth = 0; parent && depth < 64; depth++) {
|
|
7480
|
+
if (parent === id) {
|
|
7481
|
+
scope.add(candidate);
|
|
7482
|
+
break;
|
|
7483
|
+
}
|
|
7484
|
+
parent = document.items[parent]?.parent;
|
|
7485
|
+
}
|
|
7486
|
+
}
|
|
7487
|
+
return scope;
|
|
7488
|
+
}
|
|
7489
|
+
function validateBoardScriptOutput(output, scope) {
|
|
7490
|
+
if (!output || typeof output !== "object" || !output.patches || !output.items || !output.hits || !Array.isArray(output.listens)) throw new Error("Invalid script output.");
|
|
7491
|
+
if (JSON.stringify(output).length > 1e6) throw new Error("Script output exceeds the 1 MB limit.");
|
|
7492
|
+
if (output.space !== void 0 && output.space !== "world" && output.space !== "screen") throw new Error("Invalid script scene space.");
|
|
7493
|
+
if (output.parameters) {
|
|
7494
|
+
if (Object.keys(output.parameters).length > 64) throw new Error("A script may declare at most 64 parameters.");
|
|
7495
|
+
for (const spec of Object.values(output.parameters)) {
|
|
7496
|
+
if (!spec || ![
|
|
7497
|
+
"number",
|
|
7498
|
+
"boolean",
|
|
7499
|
+
"string",
|
|
7500
|
+
"color"
|
|
7501
|
+
].includes(spec.type)) throw new Error("Invalid script parameter type.");
|
|
7502
|
+
if (spec.label !== void 0 && typeof spec.label !== "string") throw new Error("Invalid script parameter label.");
|
|
7503
|
+
if ([
|
|
7504
|
+
spec.min,
|
|
7505
|
+
spec.max,
|
|
7506
|
+
spec.step
|
|
7507
|
+
].some((value) => value !== void 0 && !Number.isFinite(value))) throw new Error("Invalid script parameter range.");
|
|
7508
|
+
}
|
|
7509
|
+
}
|
|
7510
|
+
if (Object.keys(output.items).length > 1e3 || Object.keys(output.hits).length > 1e3) throw new Error("A script may create at most 1000 temporary items or hit regions.");
|
|
7511
|
+
for (const [id, patch] of Object.entries(output.patches)) {
|
|
7512
|
+
if (!scope.has(id)) throw new Error(`Item is outside the script scope: ${id}`);
|
|
7513
|
+
if (!patch || typeof patch !== "object" || Object.keys(patch).some((key) => !DISPLAY_FIELDS.has(key))) throw new Error("Scripts may only present display properties.");
|
|
7514
|
+
}
|
|
7515
|
+
for (const hit of Object.values(output.hits)) {
|
|
7516
|
+
if (hit.cursor && ![
|
|
7517
|
+
"default",
|
|
7518
|
+
"pointer",
|
|
7519
|
+
"grab",
|
|
7520
|
+
"grabbing",
|
|
7521
|
+
"crosshair",
|
|
7522
|
+
"text",
|
|
7523
|
+
"move",
|
|
7524
|
+
"ew-resize",
|
|
7525
|
+
"ns-resize",
|
|
7526
|
+
"not-allowed"
|
|
7527
|
+
].includes(hit.cursor)) throw new Error("Invalid script cursor.");
|
|
7528
|
+
if (![
|
|
7529
|
+
hit.x,
|
|
7530
|
+
hit.y,
|
|
7531
|
+
hit.width,
|
|
7532
|
+
hit.height
|
|
7533
|
+
].every(Number.isFinite) || hit.width < 0 || hit.height < 0) throw new Error("Invalid script hit region.");
|
|
7534
|
+
}
|
|
7535
|
+
}
|
|
7536
|
+
function scriptTemporaryPrefix(owner) {
|
|
7537
|
+
let hash = 2166136261;
|
|
7538
|
+
for (const char of owner) hash = Math.imul(hash ^ char.charCodeAt(0), 16777619) >>> 0;
|
|
7539
|
+
return `~script-${hash.toString(36)}-`;
|
|
7540
|
+
}
|
|
7541
|
+
const preparedOutputs = /* @__PURE__ */ new WeakMap();
|
|
7542
|
+
const preparedScopes = /* @__PURE__ */ new WeakMap();
|
|
7543
|
+
function prepareBoardScriptOutput(document, owner, output) {
|
|
7544
|
+
const previous = preparedOutputs.get(output);
|
|
7545
|
+
if (previous?.document === document && previous.owner === owner) return previous;
|
|
7546
|
+
let scopes = preparedScopes.get(document);
|
|
7547
|
+
if (!scopes) {
|
|
7548
|
+
scopes = /* @__PURE__ */ new Map();
|
|
7549
|
+
preparedScopes.set(document, scopes);
|
|
7550
|
+
}
|
|
7551
|
+
let scope = scopes.get(owner);
|
|
7552
|
+
if (!scope) {
|
|
7553
|
+
scope = boardScriptScope(document, owner);
|
|
7554
|
+
scopes.set(owner, scope);
|
|
7555
|
+
}
|
|
7556
|
+
validateBoardScriptOutput(output, scope);
|
|
7557
|
+
const presented = /* @__PURE__ */ new Map();
|
|
7558
|
+
for (const [id, patch] of Object.entries(output.patches)) {
|
|
7559
|
+
const base = document.items[id];
|
|
7560
|
+
if (base) presented.set(id, {
|
|
7561
|
+
base,
|
|
7562
|
+
item: parseBoardItem(mergePatch(base, patch))
|
|
7563
|
+
});
|
|
7564
|
+
}
|
|
7565
|
+
const prefix = scriptTemporaryPrefix(owner);
|
|
7566
|
+
const rootId = `${prefix}root`;
|
|
7567
|
+
if (document.items[rootId]) throw new Error("Temporary item ID conflicts with the document.");
|
|
7568
|
+
const items = /* @__PURE__ */ new Map();
|
|
7569
|
+
const parsed = new Map(Object.entries(output.items).map(([id, value]) => [id, parseBoardItem(value)]));
|
|
7570
|
+
for (const [local, value] of parsed) {
|
|
7571
|
+
if (!/^[a-zA-Z0-9_-]{1,80}$/.test(local) || local === "root") throw new Error("Temporary IDs must be 1–80 letters, digits, underscores or hyphens; root is reserved.");
|
|
7572
|
+
if (value.script) throw new Error("Temporary items cannot start scripts.");
|
|
7573
|
+
if (value.parent && !(value.parent in output.items)) throw new Error("Temporary parents must belong to the same instance.");
|
|
7574
|
+
let cursor = value.parent;
|
|
7575
|
+
const visited = /* @__PURE__ */ new Set([local]);
|
|
7576
|
+
while (cursor) {
|
|
7577
|
+
if (visited.has(cursor)) throw new Error("Temporary parents must not form a cycle.");
|
|
7578
|
+
visited.add(cursor);
|
|
7579
|
+
const parentItem = parsed.get(cursor);
|
|
7580
|
+
if (!parentItem) throw new Error("Temporary parent is missing.");
|
|
7581
|
+
if (parentItem.type !== "frame") throw new Error("Only frames can contain temporary items.");
|
|
7582
|
+
cursor = parentItem.parent;
|
|
7583
|
+
}
|
|
7584
|
+
const id = `${prefix}${local}`;
|
|
7585
|
+
if (document.items[id]) throw new Error("Temporary item ID conflicts with the document.");
|
|
7586
|
+
const parent = value.parent ? `${prefix}${value.parent}` : rootId;
|
|
7587
|
+
if (value.type === "arrow") {
|
|
7588
|
+
const remap = (endpoint) => {
|
|
7589
|
+
if (!("item" in endpoint)) return endpoint;
|
|
7590
|
+
if (!(endpoint.item in output.items) && !scope.has(endpoint.item)) throw new Error("Arrow target is outside the script scope.");
|
|
7591
|
+
return endpoint.item in output.items ? {
|
|
7592
|
+
...endpoint,
|
|
7593
|
+
item: `${prefix}${endpoint.item}`
|
|
7594
|
+
} : endpoint;
|
|
7595
|
+
};
|
|
7596
|
+
items.set(id, {
|
|
7597
|
+
...value,
|
|
7598
|
+
parent,
|
|
7599
|
+
props: {
|
|
7600
|
+
...value.props,
|
|
7601
|
+
start: remap(value.props.start),
|
|
7602
|
+
end: remap(value.props.end)
|
|
7603
|
+
}
|
|
7604
|
+
});
|
|
7605
|
+
} else items.set(id, {
|
|
7606
|
+
...value,
|
|
7607
|
+
parent
|
|
7608
|
+
});
|
|
7609
|
+
}
|
|
7610
|
+
const prepared = {
|
|
7611
|
+
document,
|
|
7612
|
+
owner,
|
|
7613
|
+
rootId,
|
|
7614
|
+
items,
|
|
7615
|
+
presented
|
|
7616
|
+
};
|
|
7617
|
+
preparedOutputs.set(output, prepared);
|
|
7618
|
+
return prepared;
|
|
7619
|
+
}
|
|
7620
|
+
function composeBoardScriptItems(document, scene, outputs, incoming = /* @__PURE__ */ new Map(), screen, onError) {
|
|
7621
|
+
const result = new Map(incoming);
|
|
7622
|
+
const screenAnchors = /* @__PURE__ */ new Map();
|
|
7623
|
+
if (outputs.size === 0) return result;
|
|
7624
|
+
for (const owner of Object.keys(document.items)) {
|
|
7625
|
+
const output = outputs.get(owner);
|
|
7626
|
+
if (!output) continue;
|
|
7627
|
+
try {
|
|
7628
|
+
const changes = /* @__PURE__ */ new Map();
|
|
7629
|
+
const prepared = prepareBoardScriptOutput(document, owner, output);
|
|
7630
|
+
for (const [id, patch] of Object.entries(output.patches)) {
|
|
7631
|
+
const base = result.get(id) ?? document.items[id];
|
|
7632
|
+
if (!base) continue;
|
|
7633
|
+
let cached = prepared.presented.get(id);
|
|
7634
|
+
if (cached?.base !== base) {
|
|
7635
|
+
cached = {
|
|
7636
|
+
base,
|
|
7637
|
+
item: parseBoardItem(mergePatch(base, patch))
|
|
7638
|
+
};
|
|
7639
|
+
prepared.presented.set(id, cached);
|
|
7640
|
+
}
|
|
7641
|
+
changes.set(id, cached.item);
|
|
7642
|
+
}
|
|
7643
|
+
if (!prepared.items.size || output.space === "screen" && !screen) {
|
|
7644
|
+
for (const [id, item] of changes) result.set(id, item);
|
|
7645
|
+
continue;
|
|
7646
|
+
}
|
|
7647
|
+
const rootId = prepared.rootId;
|
|
7648
|
+
const item = changes.get(owner) ?? result.get(owner) ?? document.items[owner];
|
|
7649
|
+
if (!item) continue;
|
|
7650
|
+
const box = scene.layout.box(owner);
|
|
7651
|
+
const overlay = output.space === "screen" && screen ? {
|
|
7652
|
+
parent: void 0,
|
|
7653
|
+
position: {
|
|
7654
|
+
x: 0,
|
|
7655
|
+
y: 0
|
|
7656
|
+
},
|
|
7657
|
+
scale: 1 / screen.zoom,
|
|
7658
|
+
rotation: 0,
|
|
7659
|
+
origin: {
|
|
7660
|
+
x: 0,
|
|
7661
|
+
y: 0
|
|
7662
|
+
}
|
|
7663
|
+
} : {};
|
|
7664
|
+
changes.set(rootId, parseBoardItem({
|
|
7665
|
+
type: "frame",
|
|
7666
|
+
parent: item.parent,
|
|
7667
|
+
position: item.position,
|
|
7668
|
+
rotation: item.rotation,
|
|
7669
|
+
scale: item.scale,
|
|
7670
|
+
origin: item.origin,
|
|
7671
|
+
opacity: item.opacity,
|
|
7672
|
+
size: {
|
|
7673
|
+
width: Math.max(1, box.width),
|
|
7674
|
+
height: Math.max(1, box.height)
|
|
7675
|
+
},
|
|
7676
|
+
z: (item.z ?? 0) + .001,
|
|
7677
|
+
props: {
|
|
7678
|
+
decoration: "none",
|
|
7679
|
+
clip: false
|
|
7680
|
+
},
|
|
7681
|
+
...overlay
|
|
7682
|
+
}));
|
|
7683
|
+
for (const [id, value] of prepared.items) changes.set(id, value);
|
|
7684
|
+
for (const [id, item] of changes) result.set(id, item);
|
|
7685
|
+
if (output.space === "screen") screenAnchors.set(owner, rootId);
|
|
7686
|
+
} catch (error) {
|
|
7687
|
+
if (!onError) throw error;
|
|
7688
|
+
onError(owner, error);
|
|
7689
|
+
}
|
|
7690
|
+
}
|
|
7691
|
+
if (screenAnchors.size) {
|
|
7692
|
+
const layout = createBoardLayout((id) => result.get(id) ?? document.items[id]);
|
|
7693
|
+
for (const [owner, rootId] of screenAnchors) {
|
|
7694
|
+
const root = result.get(rootId);
|
|
7695
|
+
if (root) result.set(rootId, {
|
|
7696
|
+
...root,
|
|
7697
|
+
position: applyMatrix$1(layout.matrix(owner), {
|
|
7698
|
+
x: 0,
|
|
7699
|
+
y: 0
|
|
7700
|
+
})
|
|
7701
|
+
});
|
|
7702
|
+
}
|
|
7703
|
+
}
|
|
7704
|
+
return result;
|
|
7705
|
+
}
|
|
7706
|
+
//#endregion
|
|
7707
|
+
//#region ../sdk/src/board/model/script-example.ts
|
|
7708
|
+
const BOARD_SCRIPT_DRAWING_EXAMPLE = `export const animated = true;
|
|
7709
|
+
|
|
7710
|
+
export const parameters = {
|
|
7711
|
+
amplitude: { type: "number", label: "Amplitude", default: 40, min: 0, max: 120 }
|
|
7712
|
+
};
|
|
7713
|
+
|
|
7714
|
+
export function draw(ctx, { t, width, height, params }) {
|
|
7715
|
+
ctx.strokeStyle = "#e67845";
|
|
7716
|
+
ctx.lineWidth = 3;
|
|
7717
|
+
ctx.beginPath();
|
|
7718
|
+
for (let x = 0; x <= width; x += 2) {
|
|
7719
|
+
const y = height / 2 + Math.sin(x / 40 - t / 800) * params.amplitude;
|
|
7720
|
+
if (x === 0) ctx.moveTo(x, y);
|
|
7721
|
+
else ctx.lineTo(x, y);
|
|
7722
|
+
}
|
|
7723
|
+
ctx.stroke();
|
|
7724
|
+
}
|
|
7725
|
+
`;
|
|
7726
|
+
//#endregion
|
|
7727
|
+
//#region ../sdk/src/board/model/animation.ts
|
|
7728
|
+
const linear = (x) => x;
|
|
7729
|
+
function cubicBezier(x1, y1, x2, y2) {
|
|
7730
|
+
const cx = 3 * x1;
|
|
7731
|
+
const bx = 3 * (x2 - x1) - cx;
|
|
7732
|
+
const ax = 1 - cx - bx;
|
|
7733
|
+
const cy = 3 * y1;
|
|
7734
|
+
const by = 3 * (y2 - y1) - cy;
|
|
7735
|
+
const ay = 1 - cy - by;
|
|
7736
|
+
const sampleX = (t) => ((ax * t + bx) * t + cx) * t;
|
|
7737
|
+
const sampleY = (t) => ((ay * t + by) * t + cy) * t;
|
|
7738
|
+
const slopeX = (t) => (3 * ax * t + 2 * bx) * t + cx;
|
|
7739
|
+
return (x) => {
|
|
7740
|
+
if (x <= 0) return 0;
|
|
7741
|
+
if (x >= 1) return 1;
|
|
7742
|
+
let t = x;
|
|
7743
|
+
for (let index = 0; index < 8; index += 1) {
|
|
7744
|
+
const error = sampleX(t) - x;
|
|
7745
|
+
if (Math.abs(error) < 1e-6) return sampleY(t);
|
|
7746
|
+
const slope = slopeX(t);
|
|
7747
|
+
if (Math.abs(slope) < 1e-6) break;
|
|
7748
|
+
t -= error / slope;
|
|
7749
|
+
}
|
|
7750
|
+
let low = 0;
|
|
7751
|
+
let high = 1;
|
|
7752
|
+
t = x;
|
|
7753
|
+
for (let index = 0; index < 32; index += 1) {
|
|
7754
|
+
const value = sampleX(t);
|
|
7755
|
+
if (Math.abs(value - x) < 1e-6) break;
|
|
7756
|
+
if (value < x) low = t;
|
|
7757
|
+
else high = t;
|
|
7758
|
+
t = (low + high) / 2;
|
|
7759
|
+
}
|
|
7760
|
+
return sampleY(t);
|
|
7156
7761
|
};
|
|
7157
7762
|
}
|
|
7158
7763
|
function steps(count, position) {
|
|
@@ -7254,7 +7859,7 @@ function compileAnimation(animationId, animation, document) {
|
|
|
7254
7859
|
const compiled = [];
|
|
7255
7860
|
const children = [];
|
|
7256
7861
|
for (const [id, track] of Object.entries(tracks).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
7257
|
-
const targetType = track.target === "camera" ? BOARD_CAMERA_TARGET : document.animations[track.target] ? "animation" : document.items[track.target]?.type;
|
|
7862
|
+
const targetType = track.target === "camera" ? BOARD_CAMERA_TARGET$1 : document.animations[track.target] ? "animation" : document.items[track.target]?.type;
|
|
7258
7863
|
if (!targetType) continue;
|
|
7259
7864
|
const entry = compileTrack(animationId, id, track, targetType);
|
|
7260
7865
|
if (!entry) continue;
|
|
@@ -7302,11 +7907,21 @@ function hex(color) {
|
|
|
7302
7907
|
return `#${Math.max(0, Math.min(16777215, Math.round(color))).toString(16).padStart(6, "0")}`;
|
|
7303
7908
|
}
|
|
7304
7909
|
function mixColor(a, b, t, resolve) {
|
|
7305
|
-
const
|
|
7306
|
-
const
|
|
7307
|
-
if (
|
|
7308
|
-
const
|
|
7309
|
-
|
|
7910
|
+
const left = resolve(a);
|
|
7911
|
+
const right = resolve(b);
|
|
7912
|
+
if (left === null || right === null) return t < 1 ? a : b;
|
|
7913
|
+
const from = typeof left === "number" ? {
|
|
7914
|
+
color: left,
|
|
7915
|
+
alpha: 1
|
|
7916
|
+
} : left;
|
|
7917
|
+
const to = typeof right === "number" ? {
|
|
7918
|
+
color: right,
|
|
7919
|
+
alpha: 1
|
|
7920
|
+
} : right;
|
|
7921
|
+
const alpha = lerp(from.alpha, to.alpha, t);
|
|
7922
|
+
const channel = (shift) => alpha > 0 ? Math.round(lerp((from.color >> shift & 255) * from.alpha, (to.color >> shift & 255) * to.alpha, t) / alpha) : 0;
|
|
7923
|
+
const rgb = hex(channel(16) << 16 | channel(8) << 8 | channel(0));
|
|
7924
|
+
return alpha >= 1 ? rgb : `${rgb}${Math.round(Math.max(0, alpha) * 255).toString(16).padStart(2, "0")}`;
|
|
7310
7925
|
}
|
|
7311
7926
|
function parseHexColor(color) {
|
|
7312
7927
|
if (typeof color !== "string") return null;
|
|
@@ -7315,6 +7930,23 @@ function parseHexColor(color) {
|
|
|
7315
7930
|
const digits = match[1].length === 3 ? [...match[1]].map((digit) => digit + digit).join("") : match[1];
|
|
7316
7931
|
return Number.parseInt(digits, 16);
|
|
7317
7932
|
}
|
|
7933
|
+
function parseAnimationColor(color) {
|
|
7934
|
+
if (typeof color === "string") {
|
|
7935
|
+
if (color.trim().toLowerCase() === "transparent") return {
|
|
7936
|
+
color: 0,
|
|
7937
|
+
alpha: 0
|
|
7938
|
+
};
|
|
7939
|
+
const match = /^#([0-9a-f]{4}|[0-9a-f]{8})$/i.exec(color.trim());
|
|
7940
|
+
if (match?.[1]) {
|
|
7941
|
+
const digits = match[1].length === 4 ? [...match[1]].map((digit) => digit + digit).join("") : match[1];
|
|
7942
|
+
return {
|
|
7943
|
+
color: Number.parseInt(digits.slice(0, 6), 16),
|
|
7944
|
+
alpha: Number.parseInt(digits.slice(6), 16) / 255
|
|
7945
|
+
};
|
|
7946
|
+
}
|
|
7947
|
+
}
|
|
7948
|
+
return parseHexColor(color);
|
|
7949
|
+
}
|
|
7318
7950
|
function blend(track, index, time, resolveColor) {
|
|
7319
7951
|
const { times, values } = track;
|
|
7320
7952
|
const from = values[index];
|
|
@@ -7348,7 +7980,7 @@ function blend(track, index, time, resolveColor) {
|
|
|
7348
7980
|
default: return progress >= 1 ? to : from;
|
|
7349
7981
|
}
|
|
7350
7982
|
}
|
|
7351
|
-
function sampleBoardTrack(track, time, resolveColor =
|
|
7983
|
+
function sampleBoardTrack(track, time, resolveColor = parseAnimationColor) {
|
|
7352
7984
|
const index = keyframeIndex(track.times, time);
|
|
7353
7985
|
if (index < 0) return track.values[0];
|
|
7354
7986
|
return blend(track, index, time, resolveColor);
|
|
@@ -7421,7 +8053,7 @@ function outranks(track, time, other, otherTime) {
|
|
|
7421
8053
|
return track.id > other.id;
|
|
7422
8054
|
}
|
|
7423
8055
|
function evaluateBoardAnimations(compiled, times, options = {}) {
|
|
7424
|
-
const resolveColor = options.resolveColor ??
|
|
8056
|
+
const resolveColor = options.resolveColor ?? parseAnimationColor;
|
|
7425
8057
|
const local = new Map(times instanceof Map ? times : Object.entries(times));
|
|
7426
8058
|
for (const id of compiled.order) {
|
|
7427
8059
|
const animation = compiled.animations.get(id);
|
|
@@ -7462,7 +8094,7 @@ function evaluateBoardAnimations(compiled, times, options = {}) {
|
|
|
7462
8094
|
const camera = {};
|
|
7463
8095
|
const ordered = [...groups.values()].sort((a, b) => a.path.length - b.path.length);
|
|
7464
8096
|
for (const group of ordered) {
|
|
7465
|
-
const isCamera = group.target === BOARD_CAMERA_TARGET;
|
|
8097
|
+
const isCamera = group.target === BOARD_CAMERA_TARGET$1;
|
|
7466
8098
|
const base = isCamera ? void 0 : items.get(group.target) ?? compiled.document.items[group.target];
|
|
7467
8099
|
if (!isCamera && !base) continue;
|
|
7468
8100
|
let value = isCamera ? camera[group.path[0]] : readPath(base, group.path);
|
|
@@ -7519,65 +8151,384 @@ function boardDocumentAt(document, animationId, time, options = {}) {
|
|
|
7519
8151
|
};
|
|
7520
8152
|
}
|
|
7521
8153
|
//#endregion
|
|
7522
|
-
//#region ../sdk/src/board/model/
|
|
7523
|
-
function
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
8154
|
+
//#region ../sdk/src/board/model/scene.ts
|
|
8155
|
+
function orderBoardItems(items) {
|
|
8156
|
+
const children = /* @__PURE__ */ new Map();
|
|
8157
|
+
for (const [id, item] of Object.entries(items)) {
|
|
8158
|
+
const parent = item.parent && items[item.parent] && item.parent !== id ? item.parent : "";
|
|
8159
|
+
const list = children.get(parent) ?? [];
|
|
8160
|
+
list.push(id);
|
|
8161
|
+
children.set(parent, list);
|
|
8162
|
+
}
|
|
8163
|
+
const compare = (a, b) => (items[a]?.z ?? 0) - (items[b]?.z ?? 0) || (a < b ? -1 : a > b ? 1 : 0);
|
|
8164
|
+
for (const list of children.values()) list.sort(compare);
|
|
8165
|
+
const order = [];
|
|
8166
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8167
|
+
const visit = (id) => {
|
|
8168
|
+
if (seen.has(id)) return;
|
|
8169
|
+
seen.add(id);
|
|
8170
|
+
order.push(id);
|
|
8171
|
+
for (const child of children.get(id) ?? []) visit(child);
|
|
7527
8172
|
};
|
|
8173
|
+
for (const id of children.get("") ?? []) visit(id);
|
|
8174
|
+
for (const id of Object.keys(items)) visit(id);
|
|
8175
|
+
return order;
|
|
7528
8176
|
}
|
|
7529
|
-
function
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
8177
|
+
function buildBoardScene(document, previous) {
|
|
8178
|
+
const items = document.items;
|
|
8179
|
+
const children = /* @__PURE__ */ new Map();
|
|
8180
|
+
const binders = /* @__PURE__ */ new Map();
|
|
8181
|
+
for (const [id, item] of Object.entries(items)) {
|
|
8182
|
+
const parent = item.parent && items[item.parent] ? item.parent : "";
|
|
8183
|
+
const list = children.get(parent) ?? [];
|
|
8184
|
+
list.push(id);
|
|
8185
|
+
children.set(parent, list);
|
|
8186
|
+
for (const bound of arrowBindings(item)) binders.set(bound, [...binders.get(bound) ?? [], id]);
|
|
8187
|
+
}
|
|
8188
|
+
const layout = createBoardLayout((id) => items[id], (id) => [...children.get(id) ?? [], ...binders.get(id) ?? []]);
|
|
8189
|
+
const stable = /* @__PURE__ */ new Map();
|
|
8190
|
+
const isStable = (id, depth = 0) => {
|
|
8191
|
+
const known = stable.get(id);
|
|
8192
|
+
if (known !== void 0) return known;
|
|
8193
|
+
const item = items[id];
|
|
8194
|
+
const before = previous?.get(id);
|
|
8195
|
+
let result = Boolean(item && before && stripSceneSource(before) === item) && depth < 64;
|
|
8196
|
+
if (result && item?.parent && items[item.parent]) result = isStable(item.parent, depth + 1);
|
|
8197
|
+
if (result && item) {
|
|
8198
|
+
for (const bound of arrowBindings(item)) if (items[bound] && !isStable(bound, depth + 1)) result = false;
|
|
8199
|
+
}
|
|
8200
|
+
stable.set(id, result);
|
|
8201
|
+
return result;
|
|
7538
8202
|
};
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
let minY = Number.POSITIVE_INFINITY;
|
|
7550
|
-
let maxX = Number.NEGATIVE_INFINITY;
|
|
7551
|
-
let maxY = Number.NEGATIVE_INFINITY;
|
|
7552
|
-
for (const rect of rects) {
|
|
7553
|
-
minX = Math.min(minX, rect.x);
|
|
7554
|
-
minY = Math.min(minY, rect.y);
|
|
7555
|
-
maxX = Math.max(maxX, rect.x + rect.width);
|
|
7556
|
-
maxY = Math.max(maxY, rect.y + rect.height);
|
|
8203
|
+
const ordered = orderBoardItems(items);
|
|
8204
|
+
const sceneItems = [];
|
|
8205
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8206
|
+
const indexById = /* @__PURE__ */ new Map();
|
|
8207
|
+
for (const id of ordered) {
|
|
8208
|
+
const item = items[id];
|
|
8209
|
+
const scene = (isStable(id) ? previous?.get(id) : void 0) ?? toSceneItem(id, item, layout);
|
|
8210
|
+
indexById.set(id, sceneItems.length);
|
|
8211
|
+
sceneItems.push(scene);
|
|
8212
|
+
byId.set(id, scene);
|
|
7557
8213
|
}
|
|
7558
8214
|
return {
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
8215
|
+
items: sceneItems,
|
|
8216
|
+
get: (id) => byId.get(id),
|
|
8217
|
+
indexOf: (id) => indexById.get(id) ?? -1,
|
|
8218
|
+
layout,
|
|
8219
|
+
children: (id) => children.get(id ?? "") ?? [],
|
|
8220
|
+
descendants: (id) => {
|
|
8221
|
+
const result = [];
|
|
8222
|
+
const seen = /* @__PURE__ */ new Set([id]);
|
|
8223
|
+
const stack = [...children.get(id) ?? []];
|
|
8224
|
+
while (stack.length) {
|
|
8225
|
+
const next = stack.pop();
|
|
8226
|
+
if (seen.has(next)) continue;
|
|
8227
|
+
seen.add(next);
|
|
8228
|
+
result.push(next);
|
|
8229
|
+
stack.push(...children.get(next) ?? []);
|
|
8230
|
+
}
|
|
8231
|
+
return result;
|
|
8232
|
+
},
|
|
8233
|
+
binders: (id) => binders.get(id) ?? [],
|
|
8234
|
+
opacity: (id) => cumulativeOpacity((key) => items[key], id)
|
|
8235
|
+
};
|
|
8236
|
+
}
|
|
8237
|
+
function cumulativeOpacity(lookup, id) {
|
|
8238
|
+
let value = 1;
|
|
8239
|
+
let cursor = id;
|
|
8240
|
+
for (let depth = 0; cursor && depth < 64; depth += 1) {
|
|
8241
|
+
const item = lookup(cursor);
|
|
8242
|
+
if (!item) break;
|
|
8243
|
+
value *= item.opacity;
|
|
8244
|
+
cursor = item.parent;
|
|
8245
|
+
}
|
|
8246
|
+
return value;
|
|
8247
|
+
}
|
|
8248
|
+
const sources = /* @__PURE__ */ new WeakMap();
|
|
8249
|
+
function stripSceneSource(scene) {
|
|
8250
|
+
return sources.get(scene);
|
|
8251
|
+
}
|
|
8252
|
+
function toSceneItem(id, item, layout) {
|
|
8253
|
+
const scene = {
|
|
8254
|
+
...item,
|
|
8255
|
+
id,
|
|
8256
|
+
frame: layout.frame(id)
|
|
8257
|
+
};
|
|
8258
|
+
sources.set(scene, item);
|
|
8259
|
+
return scene;
|
|
8260
|
+
}
|
|
8261
|
+
//#endregion
|
|
8262
|
+
//#region ../sdk/src/board/model/sampling.ts
|
|
8263
|
+
function createBoardAnimationSampler(document, animation, options = {}) {
|
|
8264
|
+
const selected = animation ? document.animations[animation] : void 0;
|
|
8265
|
+
if (animation && !selected) throw new Error(`Animation ${animation} does not exist.`);
|
|
8266
|
+
const compiled = compileBoardAnimations(document);
|
|
8267
|
+
const cache = /* @__PURE__ */ new Map();
|
|
8268
|
+
const geometry = /* @__PURE__ */ new Map();
|
|
8269
|
+
function sample(time) {
|
|
8270
|
+
const times = /* @__PURE__ */ new Map();
|
|
8271
|
+
for (const [id, entry] of compiled.animations) if (entry.header.play === "always") times.set(id, boardAnimationTime(entry.header, time) ?? 0);
|
|
8272
|
+
if (animation && selected) times.set(animation, Math.min(time, selected.duration));
|
|
8273
|
+
const evaluation = evaluateBoardAnimations(compiled, times, options);
|
|
8274
|
+
return {
|
|
8275
|
+
document: applyBoardEvaluation(document, evaluation),
|
|
8276
|
+
evaluation
|
|
8277
|
+
};
|
|
8278
|
+
}
|
|
8279
|
+
function stateAt(time) {
|
|
8280
|
+
if (!Number.isFinite(time) || time < 0) throw new Error("Animation time must be finite and non-negative.");
|
|
8281
|
+
let value = cache.get(time);
|
|
8282
|
+
if (!value) {
|
|
8283
|
+
value = sample(time);
|
|
8284
|
+
const oldest = cache.keys().next().value;
|
|
8285
|
+
if (cache.size >= 64 && oldest !== void 0) cache.delete(oldest);
|
|
8286
|
+
cache.set(time, value);
|
|
8287
|
+
}
|
|
8288
|
+
return value;
|
|
8289
|
+
}
|
|
8290
|
+
let current;
|
|
7576
8291
|
return {
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
8292
|
+
frameAt(id, time) {
|
|
8293
|
+
const key = `${time}:${id}`;
|
|
8294
|
+
if (geometry.has(key)) return geometry.get(key);
|
|
8295
|
+
const sampled = stateAt(time).document;
|
|
8296
|
+
const items = {};
|
|
8297
|
+
const pending = [id];
|
|
8298
|
+
while (pending.length) {
|
|
8299
|
+
const next = pending.pop();
|
|
8300
|
+
if (!next || items[next]) continue;
|
|
8301
|
+
const item = sampled.items[next];
|
|
8302
|
+
if (!item) continue;
|
|
8303
|
+
items[next] = item;
|
|
8304
|
+
if (item.parent) pending.push(item.parent);
|
|
8305
|
+
pending.push(...arrowBindings(item));
|
|
8306
|
+
}
|
|
8307
|
+
const frame = buildBoardScene({
|
|
8308
|
+
...sampled,
|
|
8309
|
+
items
|
|
8310
|
+
}).get(id)?.frame;
|
|
8311
|
+
if (geometry.size >= 2048) geometry.clear();
|
|
8312
|
+
geometry.set(key, frame);
|
|
8313
|
+
return frame;
|
|
8314
|
+
},
|
|
8315
|
+
at(time) {
|
|
8316
|
+
if (current?.time === time) return current.value;
|
|
8317
|
+
const sampled = stateAt(time);
|
|
8318
|
+
const value = {
|
|
8319
|
+
...sampled,
|
|
8320
|
+
scene: buildBoardScene(sampled.document)
|
|
8321
|
+
};
|
|
8322
|
+
current = {
|
|
8323
|
+
time,
|
|
8324
|
+
value
|
|
8325
|
+
};
|
|
8326
|
+
return value;
|
|
8327
|
+
}
|
|
8328
|
+
};
|
|
8329
|
+
}
|
|
8330
|
+
function resolveBoardAnimationCamera(override, bounds, surface, time, base = {
|
|
8331
|
+
x: 0,
|
|
8332
|
+
y: 0,
|
|
8333
|
+
zoom: 1
|
|
8334
|
+
}) {
|
|
8335
|
+
let camera = override.focus === void 0 ? base : cameraForFocus(override.focus, bounds, surface, { padding: 48 }) ?? base;
|
|
8336
|
+
if (override.zoom !== void 0) camera = {
|
|
8337
|
+
...camera,
|
|
8338
|
+
zoom: override.zoom
|
|
8339
|
+
};
|
|
8340
|
+
if (override.center) camera = cameraForState({
|
|
8341
|
+
centerX: override.center.x,
|
|
8342
|
+
centerY: override.center.y,
|
|
8343
|
+
zoom: camera.zoom
|
|
8344
|
+
}, surface);
|
|
8345
|
+
if (override.shake) camera = {
|
|
8346
|
+
...camera,
|
|
8347
|
+
x: camera.x + Math.sin(time / 1e3 * 91.3) * override.shake,
|
|
8348
|
+
y: camera.y + Math.cos(time / 1e3 * 77.9) * override.shake
|
|
8349
|
+
};
|
|
8350
|
+
return camera;
|
|
8351
|
+
}
|
|
8352
|
+
//#endregion
|
|
8353
|
+
//#region ../sdk/src/board/model/authoring.ts
|
|
8354
|
+
function validateChapterComposition(document, chapters) {
|
|
8355
|
+
const uses = /* @__PURE__ */ new Map();
|
|
8356
|
+
for (const [index, chapter] of chapters.entries()) for (const track of Object.values(document.animations[chapter.animation]?.tracks ?? {})) {
|
|
8357
|
+
let properties = uses.get(track.target);
|
|
8358
|
+
if (!properties) {
|
|
8359
|
+
properties = /* @__PURE__ */ new Map();
|
|
8360
|
+
uses.set(track.target, properties);
|
|
8361
|
+
}
|
|
8362
|
+
for (const [property, usage] of properties) if ((property === track.property || property.startsWith(`${track.property}.`) || track.property.startsWith(`${property}.`)) && (usage.additive || track.composite === "add") && [...usage.chapters].some((other) => other !== index)) throw new Error(`Chapters share additive tracks on ${track.target}.${track.property}; use distinct items or combine the tracks before sequencing.`);
|
|
8363
|
+
const usage = properties.get(track.property) ?? {
|
|
8364
|
+
chapters: /* @__PURE__ */ new Set(),
|
|
8365
|
+
additive: false
|
|
8366
|
+
};
|
|
8367
|
+
usage.chapters.add(index);
|
|
8368
|
+
usage.additive ||= track.composite === "add";
|
|
8369
|
+
properties.set(track.property, usage);
|
|
8370
|
+
}
|
|
8371
|
+
}
|
|
8372
|
+
function boardSequence(document, chapters) {
|
|
8373
|
+
if (!chapters.length) throw new Error("A sequence needs at least one chapter.");
|
|
8374
|
+
validateChapterComposition(document, chapters);
|
|
8375
|
+
const tracks = {};
|
|
8376
|
+
const markers = [];
|
|
8377
|
+
let cursor = 0;
|
|
8378
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8379
|
+
const seenFrames = /* @__PURE__ */ new Set();
|
|
8380
|
+
for (const [index, chapter] of chapters.entries()) {
|
|
8381
|
+
const animation = document.animations[chapter.animation];
|
|
8382
|
+
if (!animation) throw new Error(`Animation ${chapter.animation} does not exist.`);
|
|
8383
|
+
if (animation.play !== "manual") throw new Error(`Chapter ${chapter.animation} must use manual playback.`);
|
|
8384
|
+
if (!Number.isFinite(animation.duration) || animation.duration <= 0) throw new Error("Chapter duration must be positive and finite.");
|
|
8385
|
+
if (seen.has(chapter.animation)) throw new Error(`Animation ${chapter.animation} appears more than once; use separate animation IDs.`);
|
|
8386
|
+
seen.add(chapter.animation);
|
|
8387
|
+
if (chapter.frame && document.items[chapter.frame]?.type !== "frame") throw new Error(`Chapter ${chapter.animation} needs a valid frame.`);
|
|
8388
|
+
if (chapter.frame && seenFrames.has(chapter.frame)) throw new Error("Each chapter needs a separate frame, or omit frame visibility control.");
|
|
8389
|
+
if (chapter.frame) seenFrames.add(chapter.frame);
|
|
8390
|
+
const gap = chapter.gap ?? 0;
|
|
8391
|
+
if (!Number.isFinite(gap) || gap < 0) throw new Error("Chapter gap must be finite and non-negative.");
|
|
8392
|
+
cursor += gap;
|
|
8393
|
+
const start = cursor;
|
|
8394
|
+
const end = start + animation.duration;
|
|
8395
|
+
const rankWidth = String(Object.keys(animation.tracks).length).length;
|
|
8396
|
+
const ranks = new Map(Object.keys(animation.tracks).sort().map((id, rank) => [id, String(rank).padStart(rankWidth, "0")]));
|
|
8397
|
+
const chapterIndex = String(index).padStart(String(chapters.length).length, "0");
|
|
8398
|
+
for (const [id, track] of Object.entries(animation.tracks)) {
|
|
8399
|
+
if (document.animations[track.target]) throw new Error(`Chapter ${chapter.animation} contains nested clocks; compose its leaf animations instead.`);
|
|
8400
|
+
if (chapter.frame && track.target === "camera" && track.property === "focus") continue;
|
|
8401
|
+
tracks[`chapter-${chapterIndex}-${ranks.get(id)}`] = {
|
|
8402
|
+
...track,
|
|
8403
|
+
keyframes: track.keyframes.map((keyframe) => ({
|
|
8404
|
+
...keyframe,
|
|
8405
|
+
at: start + keyframe.at
|
|
8406
|
+
}))
|
|
8407
|
+
};
|
|
8408
|
+
}
|
|
8409
|
+
const boundary = end + (chapter.pause && index < chapters.length - 1 ? 1 : 0);
|
|
8410
|
+
if (chapter.frame) {
|
|
8411
|
+
tracks[`visibility-${index}`] = {
|
|
8412
|
+
target: chapter.frame,
|
|
8413
|
+
property: "opacity",
|
|
8414
|
+
composite: "add",
|
|
8415
|
+
interpolation: "step",
|
|
8416
|
+
keyframes: [
|
|
8417
|
+
...start > 0 ? [{
|
|
8418
|
+
at: 0,
|
|
8419
|
+
value: 0
|
|
8420
|
+
}] : [],
|
|
8421
|
+
{
|
|
8422
|
+
at: start,
|
|
8423
|
+
value: 1
|
|
8424
|
+
},
|
|
8425
|
+
...index < chapters.length - 1 ? [{
|
|
8426
|
+
at: boundary + (chapters[index + 1]?.gap ?? 0),
|
|
8427
|
+
value: 0
|
|
8428
|
+
}] : []
|
|
8429
|
+
]
|
|
8430
|
+
};
|
|
8431
|
+
tracks[`camera-${index}`] = {
|
|
8432
|
+
target: "camera",
|
|
8433
|
+
property: "focus",
|
|
8434
|
+
composite: "replace",
|
|
8435
|
+
interpolation: "step",
|
|
8436
|
+
keyframes: [{
|
|
8437
|
+
at: start,
|
|
8438
|
+
value: chapter.frame
|
|
8439
|
+
}]
|
|
8440
|
+
};
|
|
8441
|
+
}
|
|
8442
|
+
markers.push({
|
|
8443
|
+
at: start,
|
|
8444
|
+
label: chapter.label ?? animation.name ?? chapter.animation
|
|
8445
|
+
});
|
|
8446
|
+
for (const marker of animation.markers) markers.push({
|
|
8447
|
+
...marker,
|
|
8448
|
+
at: start + marker.at
|
|
8449
|
+
});
|
|
8450
|
+
if (chapter.pause) markers.push({
|
|
8451
|
+
at: end,
|
|
8452
|
+
pause: true
|
|
8453
|
+
});
|
|
8454
|
+
cursor = boundary;
|
|
8455
|
+
}
|
|
8456
|
+
const byTime = /* @__PURE__ */ new Map();
|
|
8457
|
+
for (const marker of markers) {
|
|
8458
|
+
const previous = byTime.get(marker.at);
|
|
8459
|
+
byTime.set(marker.at, {
|
|
8460
|
+
...previous,
|
|
8461
|
+
...marker,
|
|
8462
|
+
pause: Boolean(previous?.pause || marker.pause)
|
|
8463
|
+
});
|
|
8464
|
+
}
|
|
8465
|
+
return {
|
|
8466
|
+
duration: cursor,
|
|
8467
|
+
play: "manual",
|
|
8468
|
+
end: "hold",
|
|
8469
|
+
delay: 0,
|
|
8470
|
+
loop: false,
|
|
8471
|
+
tracks,
|
|
8472
|
+
markers: [...byTime.values()].sort((a, b) => a.at - b.at)
|
|
8473
|
+
};
|
|
8474
|
+
}
|
|
8475
|
+
function boardSequencePatch(document, id, chapters) {
|
|
8476
|
+
BoardIdSchema$1.parse(id);
|
|
8477
|
+
if (chapters.some((chapter) => chapter.animation === id)) throw new Error("A sequence cannot overwrite one of its source chapters.");
|
|
8478
|
+
const animation = boardSequence(document, chapters);
|
|
8479
|
+
const tracks = { ...animation.tracks };
|
|
8480
|
+
for (const key of Object.keys(document.animations[id]?.tracks ?? {})) if (!(key in tracks)) tracks[key] = null;
|
|
8481
|
+
const patch = { animations: { [id]: {
|
|
8482
|
+
...animation,
|
|
8483
|
+
tracks
|
|
8484
|
+
} } };
|
|
8485
|
+
const result = applyBoardPatchToDocument(document, patch);
|
|
8486
|
+
if (!result.ok) throw new Error(result.diagnostics.map((entry) => `${entry.path}: ${entry.message}`).join("; "));
|
|
8487
|
+
return patch;
|
|
8488
|
+
}
|
|
8489
|
+
function boardPathTrack(target, points, options) {
|
|
8490
|
+
const { duration, at = 0, samples = 64, orient = false } = options;
|
|
8491
|
+
if (points.length < 2 || points.length > 1e4 || points.some((p) => !Number.isFinite(p.x) || !Number.isFinite(p.y))) throw new Error("A path needs 2 to 10000 finite points.");
|
|
8492
|
+
if (!Number.isFinite(duration) || duration <= 0 || !Number.isFinite(at) || at < 0) throw new Error("Path duration must be positive and start time non-negative.");
|
|
8493
|
+
if (!Number.isInteger(samples) || samples < 2 || samples > 1e4) throw new Error("Path samples must be an integer from 2 to 10000.");
|
|
8494
|
+
const segments = [];
|
|
8495
|
+
let total = 0;
|
|
8496
|
+
let previous = points[0];
|
|
8497
|
+
for (const point of points.slice(1)) {
|
|
8498
|
+
if (!previous) break;
|
|
8499
|
+
const start = total;
|
|
8500
|
+
total += Math.hypot(point.x - previous.x, point.y - previous.y);
|
|
8501
|
+
segments.push({
|
|
8502
|
+
a: previous,
|
|
8503
|
+
b: point,
|
|
8504
|
+
start,
|
|
8505
|
+
end: total
|
|
8506
|
+
});
|
|
8507
|
+
previous = point;
|
|
8508
|
+
}
|
|
8509
|
+
const distances = [.../* @__PURE__ */ new Set([...segments.map((segment) => segment.end), ...Array.from({ length: samples }, (_, index) => total * index / (samples - 1))])].sort((a, b) => a - b);
|
|
8510
|
+
let segmentIndex = 0;
|
|
8511
|
+
return {
|
|
8512
|
+
target,
|
|
8513
|
+
property: "position",
|
|
8514
|
+
composite: "replace",
|
|
8515
|
+
interpolation: "auto",
|
|
8516
|
+
orient,
|
|
8517
|
+
keyframes: distances.map((distance) => {
|
|
8518
|
+
const progress = total > 0 ? distance / total : 0;
|
|
8519
|
+
while (segmentIndex < segments.length - 1 && (segments[segmentIndex]?.end ?? 0) < distance) segmentIndex++;
|
|
8520
|
+
const segment = segments[segmentIndex];
|
|
8521
|
+
if (!segment) throw new Error("Path has no segments.");
|
|
8522
|
+
const { a, b, start, end } = segment;
|
|
8523
|
+
const fraction = end > start ? (distance - start) / (end - start) : 0;
|
|
8524
|
+
return {
|
|
8525
|
+
at: at + duration * progress,
|
|
8526
|
+
value: {
|
|
8527
|
+
x: a.x + (b.x - a.x) * fraction,
|
|
8528
|
+
y: a.y + (b.y - a.y) * fraction
|
|
8529
|
+
}
|
|
8530
|
+
};
|
|
8531
|
+
})
|
|
7581
8532
|
};
|
|
7582
8533
|
}
|
|
7583
8534
|
const CURVE_SEGMENTS = 20;
|
|
@@ -7783,12 +8734,12 @@ function resolveSceneArrow(item, scene) {
|
|
|
7783
8734
|
return resolved;
|
|
7784
8735
|
}
|
|
7785
8736
|
const resolvedCache = /* @__PURE__ */ new WeakMap();
|
|
7786
|
-
function frameKey
|
|
8737
|
+
function frameKey(frame) {
|
|
7787
8738
|
return frame ? `${frame.x},${frame.y},${frame.width},${frame.height},${frame.rotation}` : "-";
|
|
7788
8739
|
}
|
|
7789
8740
|
function arrowKey(item, matrix, getFrame) {
|
|
7790
8741
|
const { start, end } = item.props;
|
|
7791
|
-
return `${matrix.join(",")}|${isArrowBinding(start) ? frameKey
|
|
8742
|
+
return `${matrix.join(",")}|${isArrowBinding(start) ? frameKey(getFrame(start.item)) : ""}|${isArrowBinding(end) ? frameKey(getFrame(end.item)) : ""}`;
|
|
7792
8743
|
}
|
|
7793
8744
|
function pathMidpoint(path) {
|
|
7794
8745
|
if (path.length === 0) return worldPoint(0, 0);
|
|
@@ -8076,6 +9027,7 @@ function resolveRegion(scene, region) {
|
|
|
8076
9027
|
return boundsOf.get(item.id) ?? null;
|
|
8077
9028
|
};
|
|
8078
9029
|
const visible = (item, rect) => {
|
|
9030
|
+
if (item.type === "effect" && item.props.kind === "trail" && item.props.follow) return true;
|
|
8079
9031
|
const box = bounds(item);
|
|
8080
9032
|
return box !== null && rectsIntersect(box, rect);
|
|
8081
9033
|
};
|
|
@@ -8152,7 +9104,31 @@ function planBoardExport(input) {
|
|
|
8152
9104
|
};
|
|
8153
9105
|
}
|
|
8154
9106
|
//#endregion
|
|
9107
|
+
//#region ../sdk/src/board/model/media.ts
|
|
9108
|
+
function getMediaResourceTitle(value) {
|
|
9109
|
+
if (!/^[a-z][a-z\d+.-]*:/i.test(value)) return value.split("/").filter(Boolean).pop() || value;
|
|
9110
|
+
try {
|
|
9111
|
+
const url = new URL(value);
|
|
9112
|
+
const title = url.pathname.split("/").filter(Boolean).pop() || url.hostname;
|
|
9113
|
+
try {
|
|
9114
|
+
return decodeURIComponent(title);
|
|
9115
|
+
} catch {
|
|
9116
|
+
return title;
|
|
9117
|
+
}
|
|
9118
|
+
} catch {
|
|
9119
|
+
return value.split("/").filter(Boolean).pop() || value;
|
|
9120
|
+
}
|
|
9121
|
+
}
|
|
9122
|
+
//#endregion
|
|
8155
9123
|
//#region ../sdk/src/board/model/file-snapshot.ts
|
|
9124
|
+
/**
|
|
9125
|
+
* File-card snapshot derivation — pure, renderer-agnostic, dependency-free.
|
|
9126
|
+
*
|
|
9127
|
+
* Whoever holds the file content calls `buildFileSnapshot`; today that is the
|
|
9128
|
+
* web client, for cards near its viewport. Nothing here ever writes back to the
|
|
9129
|
+
* workspace file. The file on disk stays the single source of truth, and the
|
|
9130
|
+
* snapshot is a cache keyed by mtime so a stale card is detectable.
|
|
9131
|
+
*/
|
|
8156
9132
|
const DOC_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
8157
9133
|
"md",
|
|
8158
9134
|
"mdx",
|
|
@@ -8242,7 +9218,7 @@ const MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
8242
9218
|
"tgz"
|
|
8243
9219
|
]);
|
|
8244
9220
|
function extensionOf(path) {
|
|
8245
|
-
const name = path
|
|
9221
|
+
const name = fileBaseName(path);
|
|
8246
9222
|
const dot = name.lastIndexOf(".");
|
|
8247
9223
|
if (dot <= 0) return "";
|
|
8248
9224
|
return name.slice(dot + 1).toLowerCase();
|
|
@@ -8270,7 +9246,7 @@ function fileCategory(path, mimeType) {
|
|
|
8270
9246
|
}
|
|
8271
9247
|
/** Basename of a path, used as a last-resort card title. */
|
|
8272
9248
|
function fileBaseName(path) {
|
|
8273
|
-
return path
|
|
9249
|
+
return getMediaResourceTitle(path);
|
|
8274
9250
|
}
|
|
8275
9251
|
/** Basename with a trailing extension stripped. Dotfiles keep their full name. */
|
|
8276
9252
|
function fileStem(path) {
|
|
@@ -8294,7 +9270,7 @@ function filePreviewKind(snapshot) {
|
|
|
8294
9270
|
}
|
|
8295
9271
|
/** Short uppercase type label for a file card (`MD`, `JSON`, `FILE`). */
|
|
8296
9272
|
function fileTypeLabel(path) {
|
|
8297
|
-
const name = path
|
|
9273
|
+
const name = fileBaseName(path);
|
|
8298
9274
|
const dot = name.lastIndexOf(".");
|
|
8299
9275
|
if (dot <= 0) return (name.replace(/^\./, "") || "file").toUpperCase();
|
|
8300
9276
|
return name.slice(dot + 1).toUpperCase();
|
|
@@ -8467,114 +9443,6 @@ function resolveItemColor(color, fallback, colors, mode, parseCss, part = "strok
|
|
|
8467
9443
|
return parseCss(value) ?? pickBoardColor(colors, fallback, mode)[part];
|
|
8468
9444
|
}
|
|
8469
9445
|
//#endregion
|
|
8470
|
-
//#region ../sdk/src/board/model/scene.ts
|
|
8471
|
-
function orderBoardItems(items) {
|
|
8472
|
-
const children = /* @__PURE__ */ new Map();
|
|
8473
|
-
for (const [id, item] of Object.entries(items)) {
|
|
8474
|
-
const parent = item.parent && items[item.parent] && item.parent !== id ? item.parent : "";
|
|
8475
|
-
const list = children.get(parent) ?? [];
|
|
8476
|
-
list.push(id);
|
|
8477
|
-
children.set(parent, list);
|
|
8478
|
-
}
|
|
8479
|
-
const compare = (a, b) => (items[a]?.z ?? 0) - (items[b]?.z ?? 0) || (a < b ? -1 : a > b ? 1 : 0);
|
|
8480
|
-
for (const list of children.values()) list.sort(compare);
|
|
8481
|
-
const order = [];
|
|
8482
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8483
|
-
const visit = (id) => {
|
|
8484
|
-
if (seen.has(id)) return;
|
|
8485
|
-
seen.add(id);
|
|
8486
|
-
order.push(id);
|
|
8487
|
-
for (const child of children.get(id) ?? []) visit(child);
|
|
8488
|
-
};
|
|
8489
|
-
for (const id of children.get("") ?? []) visit(id);
|
|
8490
|
-
for (const id of Object.keys(items)) visit(id);
|
|
8491
|
-
return order;
|
|
8492
|
-
}
|
|
8493
|
-
function buildBoardScene(document, previous) {
|
|
8494
|
-
const items = document.items;
|
|
8495
|
-
const children = /* @__PURE__ */ new Map();
|
|
8496
|
-
const binders = /* @__PURE__ */ new Map();
|
|
8497
|
-
for (const [id, item] of Object.entries(items)) {
|
|
8498
|
-
const parent = item.parent && items[item.parent] ? item.parent : "";
|
|
8499
|
-
const list = children.get(parent) ?? [];
|
|
8500
|
-
list.push(id);
|
|
8501
|
-
children.set(parent, list);
|
|
8502
|
-
for (const bound of arrowBindings(item)) binders.set(bound, [...binders.get(bound) ?? [], id]);
|
|
8503
|
-
}
|
|
8504
|
-
const layout = createBoardLayout((id) => items[id], (id) => [...children.get(id) ?? [], ...binders.get(id) ?? []]);
|
|
8505
|
-
const stable = /* @__PURE__ */ new Map();
|
|
8506
|
-
const isStable = (id, depth = 0) => {
|
|
8507
|
-
const known = stable.get(id);
|
|
8508
|
-
if (known !== void 0) return known;
|
|
8509
|
-
const item = items[id];
|
|
8510
|
-
const before = previous?.get(id);
|
|
8511
|
-
let result = Boolean(item && before && stripSceneSource(before) === item) && depth < 64;
|
|
8512
|
-
if (result && item?.parent && items[item.parent]) result = isStable(item.parent, depth + 1);
|
|
8513
|
-
if (result && item) {
|
|
8514
|
-
for (const bound of arrowBindings(item)) if (items[bound] && !isStable(bound, depth + 1)) result = false;
|
|
8515
|
-
}
|
|
8516
|
-
stable.set(id, result);
|
|
8517
|
-
return result;
|
|
8518
|
-
};
|
|
8519
|
-
const ordered = orderBoardItems(items);
|
|
8520
|
-
const sceneItems = [];
|
|
8521
|
-
const byId = /* @__PURE__ */ new Map();
|
|
8522
|
-
const indexById = /* @__PURE__ */ new Map();
|
|
8523
|
-
for (const id of ordered) {
|
|
8524
|
-
const item = items[id];
|
|
8525
|
-
const scene = (isStable(id) ? previous?.get(id) : void 0) ?? toSceneItem(id, item, layout);
|
|
8526
|
-
indexById.set(id, sceneItems.length);
|
|
8527
|
-
sceneItems.push(scene);
|
|
8528
|
-
byId.set(id, scene);
|
|
8529
|
-
}
|
|
8530
|
-
return {
|
|
8531
|
-
items: sceneItems,
|
|
8532
|
-
get: (id) => byId.get(id),
|
|
8533
|
-
indexOf: (id) => indexById.get(id) ?? -1,
|
|
8534
|
-
layout,
|
|
8535
|
-
children: (id) => children.get(id ?? "") ?? [],
|
|
8536
|
-
descendants: (id) => {
|
|
8537
|
-
const result = [];
|
|
8538
|
-
const seen = /* @__PURE__ */ new Set([id]);
|
|
8539
|
-
const stack = [...children.get(id) ?? []];
|
|
8540
|
-
while (stack.length) {
|
|
8541
|
-
const next = stack.pop();
|
|
8542
|
-
if (seen.has(next)) continue;
|
|
8543
|
-
seen.add(next);
|
|
8544
|
-
result.push(next);
|
|
8545
|
-
stack.push(...children.get(next) ?? []);
|
|
8546
|
-
}
|
|
8547
|
-
return result;
|
|
8548
|
-
},
|
|
8549
|
-
binders: (id) => binders.get(id) ?? [],
|
|
8550
|
-
opacity: (id) => cumulativeOpacity((key) => items[key], id)
|
|
8551
|
-
};
|
|
8552
|
-
}
|
|
8553
|
-
function cumulativeOpacity(lookup, id) {
|
|
8554
|
-
let value = 1;
|
|
8555
|
-
let cursor = id;
|
|
8556
|
-
for (let depth = 0; cursor && depth < 64; depth += 1) {
|
|
8557
|
-
const item = lookup(cursor);
|
|
8558
|
-
if (!item) break;
|
|
8559
|
-
value *= item.opacity;
|
|
8560
|
-
cursor = item.parent;
|
|
8561
|
-
}
|
|
8562
|
-
return value;
|
|
8563
|
-
}
|
|
8564
|
-
const sources = /* @__PURE__ */ new WeakMap();
|
|
8565
|
-
function stripSceneSource(scene) {
|
|
8566
|
-
return sources.get(scene);
|
|
8567
|
-
}
|
|
8568
|
-
function toSceneItem(id, item, layout) {
|
|
8569
|
-
const scene = {
|
|
8570
|
-
...item,
|
|
8571
|
-
id,
|
|
8572
|
-
frame: layout.frame(id)
|
|
8573
|
-
};
|
|
8574
|
-
sources.set(scene, item);
|
|
8575
|
-
return scene;
|
|
8576
|
-
}
|
|
8577
|
-
//#endregion
|
|
8578
9446
|
//#region ../sdk/src/board/model/text-metrics.ts
|
|
8579
9447
|
function boardFontFamily(font, stacks) {
|
|
8580
9448
|
if (font === "sans" || font === "serif" || font === "mono") return stacks[font];
|
|
@@ -8619,11 +9487,14 @@ function taskArtifactPreviewUrl(artifact) {
|
|
|
8619
9487
|
//#endregion
|
|
8620
9488
|
//#region ../sdk/src/board/model/image-key.ts
|
|
8621
9489
|
function imageAssetKey(item) {
|
|
8622
|
-
if (item.type === "image")
|
|
9490
|
+
if (item.type === "image") {
|
|
9491
|
+
const source = parseBoardMediaSource$1(item.props.src);
|
|
9492
|
+
return source ? `${source.kind}:${source.value}` : null;
|
|
9493
|
+
}
|
|
8623
9494
|
if (item.type === "file") {
|
|
8624
9495
|
const snapshot = item.props.snapshot;
|
|
8625
|
-
if (snapshot?.coverPath) return `file:${snapshot.coverPath}`;
|
|
8626
9496
|
if (snapshot?.coverUrl) return `url:${snapshot.coverUrl}`;
|
|
9497
|
+
if (snapshot?.coverPath) return `file:${snapshot.coverPath}`;
|
|
8627
9498
|
return null;
|
|
8628
9499
|
}
|
|
8629
9500
|
if (item.type === "task") {
|
|
@@ -26041,20 +26912,122 @@ var CanvasRenderer = class extends AbstractRenderer {
|
|
|
26041
26912
|
//#region ../../node_modules/.pnpm/pixi.js@8.20.1/node_modules/pixi.js/lib/index.mjs
|
|
26042
26913
|
extensions.add(browserExt, webworkerExt);
|
|
26043
26914
|
//#endregion
|
|
26044
|
-
//#region ../sdk/src/board/render/
|
|
26045
|
-
const
|
|
26046
|
-
|
|
26047
|
-
|
|
26048
|
-
|
|
26049
|
-
|
|
26050
|
-
|
|
26051
|
-
|
|
26052
|
-
|
|
26053
|
-
|
|
26915
|
+
//#region ../sdk/src/board/render/css-color.ts
|
|
26916
|
+
const cache = /* @__PURE__ */ new Map();
|
|
26917
|
+
let canvas = null;
|
|
26918
|
+
function parseBoardCssPaint(value) {
|
|
26919
|
+
const key = value.trim();
|
|
26920
|
+
if (!key) return null;
|
|
26921
|
+
if (key.toLowerCase() === "transparent") return {
|
|
26922
|
+
color: 0,
|
|
26923
|
+
alpha: 0
|
|
26924
|
+
};
|
|
26925
|
+
const hex = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(key)?.[1];
|
|
26926
|
+
if (hex) {
|
|
26927
|
+
const digits = hex.length <= 4 ? [...hex].map((digit) => digit + digit).join("") : hex;
|
|
26928
|
+
return {
|
|
26929
|
+
color: Number.parseInt(digits.slice(0, 6), 16),
|
|
26930
|
+
alpha: digits.length === 8 ? Number.parseInt(digits.slice(6), 16) / 255 : 1
|
|
26931
|
+
};
|
|
26932
|
+
}
|
|
26933
|
+
if (cache.has(key)) return cache.get(key) ?? null;
|
|
26934
|
+
let result = null;
|
|
26054
26935
|
try {
|
|
26055
|
-
|
|
26936
|
+
canvas ??= DOMAdapter.get().createCanvas(1, 1);
|
|
26937
|
+
const context = canvas.getContext("2d");
|
|
26938
|
+
context.fillStyle = "#010203";
|
|
26939
|
+
context.fillStyle = key;
|
|
26940
|
+
const first = context.fillStyle;
|
|
26941
|
+
context.fillStyle = "#040506";
|
|
26942
|
+
context.fillStyle = key;
|
|
26943
|
+
if (first === context.fillStyle) {
|
|
26944
|
+
context.clearRect(0, 0, 1, 1);
|
|
26945
|
+
context.fillRect(0, 0, 1, 1);
|
|
26946
|
+
const [r = 0, g = 0, b = 0, a = 0] = context.getImageData(0, 0, 1, 1).data;
|
|
26947
|
+
result = {
|
|
26948
|
+
color: r << 16 | g << 8 | b,
|
|
26949
|
+
alpha: a / 255
|
|
26950
|
+
};
|
|
26951
|
+
}
|
|
26056
26952
|
} catch {
|
|
26057
|
-
|
|
26953
|
+
result = null;
|
|
26954
|
+
}
|
|
26955
|
+
if (cache.size >= 256) cache.clear();
|
|
26956
|
+
cache.set(key, result);
|
|
26957
|
+
return result;
|
|
26958
|
+
}
|
|
26959
|
+
function parseBoardCssColor(value) {
|
|
26960
|
+
return parseBoardCssPaint(value)?.color ?? null;
|
|
26961
|
+
}
|
|
26962
|
+
//#endregion
|
|
26963
|
+
//#region ../sdk/src/board/render/palette.ts
|
|
26964
|
+
const DARK = {
|
|
26965
|
+
bg: 1315860,
|
|
26966
|
+
surface: 2105376,
|
|
26967
|
+
hover: 2763306,
|
|
26968
|
+
border: 3815994,
|
|
26969
|
+
brand: 16734751,
|
|
26970
|
+
text: 16053492,
|
|
26971
|
+
muted: 9211020,
|
|
26972
|
+
rare: 3718648,
|
|
26973
|
+
epic: 10980346,
|
|
26974
|
+
legendary: 16096779
|
|
26975
|
+
};
|
|
26976
|
+
const LIGHT = {
|
|
26977
|
+
bg: 16777215,
|
|
26978
|
+
surface: 16053493,
|
|
26979
|
+
hover: 15329772,
|
|
26980
|
+
border: 14079706,
|
|
26981
|
+
brand: 15222030,
|
|
26982
|
+
text: 1579035,
|
|
26983
|
+
muted: 7041664,
|
|
26984
|
+
rare: 2450411,
|
|
26985
|
+
epic: 8141549,
|
|
26986
|
+
legendary: 11817737
|
|
26987
|
+
};
|
|
26988
|
+
function defaultBoardPalette(mode) {
|
|
26989
|
+
return { ...mode === "light" ? LIGHT : DARK };
|
|
26990
|
+
}
|
|
26991
|
+
function itemAlpha(context, color) {
|
|
26992
|
+
const value = typeof color === "object" && color ? color[context.colorScheme] : color;
|
|
26993
|
+
return value ? parseBoardCssPaint(value)?.alpha ?? 1 : 1;
|
|
26994
|
+
}
|
|
26995
|
+
function itemColor(context, color, fallback, part = "stroke") {
|
|
26996
|
+
if (((typeof color === "object" && color ? color[context.colorScheme] : color) ?? fallback) === "neutral" && part !== "fill") return context.palette.text;
|
|
26997
|
+
return resolveItemColor(color, fallback, context.colors, context.colorScheme, parseBoardCssColor, part);
|
|
26998
|
+
}
|
|
26999
|
+
//#endregion
|
|
27000
|
+
//#region ../sdk/src/board/render/sampling.ts
|
|
27001
|
+
function createBoardRenderSampler(document, animation, options = {}) {
|
|
27002
|
+
const colorScheme = options.colorScheme ?? "dark";
|
|
27003
|
+
const context = {
|
|
27004
|
+
colorScheme,
|
|
27005
|
+
colors: options.colors ?? buildFallbackShapeColors(colorScheme),
|
|
27006
|
+
palette: {
|
|
27007
|
+
...defaultBoardPalette(colorScheme),
|
|
27008
|
+
...options.palette
|
|
27009
|
+
}
|
|
27010
|
+
};
|
|
27011
|
+
return createBoardAnimationSampler(document, animation, { resolveColor: (color) => ({
|
|
27012
|
+
color: itemColor(context, color, "brand", "fill"),
|
|
27013
|
+
alpha: itemAlpha(context, color)
|
|
27014
|
+
}) });
|
|
27015
|
+
}
|
|
27016
|
+
//#endregion
|
|
27017
|
+
//#region ../sdk/src/board/render/text-measurement.ts
|
|
27018
|
+
const BOARD_FONT_STACKS = {
|
|
27019
|
+
sans: BOARD_FONT_STACK,
|
|
27020
|
+
serif: "ui-serif, Georgia, \"Noto Serif CJK SC\", \"Songti SC\", \"Noto Sans Math\", \"DejaVu Sans\", \"Noto Sans CJK SC\", \"FreeSans\", \"FreeSerif\", sans-serif",
|
|
27021
|
+
mono: BOARD_MONO_FONT_STACK
|
|
27022
|
+
};
|
|
27023
|
+
let measureContext;
|
|
27024
|
+
let installed = false;
|
|
27025
|
+
function getMeasureContext() {
|
|
27026
|
+
if (measureContext !== void 0) return measureContext;
|
|
27027
|
+
try {
|
|
27028
|
+
measureContext = DOMAdapter.get().createCanvas(1, 1).getContext("2d") ?? null;
|
|
27029
|
+
} catch {
|
|
27030
|
+
measureContext = null;
|
|
26058
27031
|
}
|
|
26059
27032
|
return measureContext;
|
|
26060
27033
|
}
|
|
@@ -26074,38 +27047,115 @@ function ensureBoardTextMeasurement() {
|
|
|
26074
27047
|
installBoardTextMeasurement();
|
|
26075
27048
|
}
|
|
26076
27049
|
//#endregion
|
|
26077
|
-
//#region ../sdk/src/board/render/
|
|
26078
|
-
const
|
|
26079
|
-
|
|
26080
|
-
|
|
26081
|
-
|
|
26082
|
-
const key =
|
|
26083
|
-
|
|
26084
|
-
|
|
26085
|
-
if (cached !== void 0 || cache.has(key)) return cached ?? null;
|
|
26086
|
-
let result = null;
|
|
27050
|
+
//#region ../sdk/src/board/render/diagnostics.ts
|
|
27051
|
+
const glyphCache = /* @__PURE__ */ new Map();
|
|
27052
|
+
let glyphContext;
|
|
27053
|
+
function missingGlyph(character, font) {
|
|
27054
|
+
if (/\s/u.test(character)) return false;
|
|
27055
|
+
const key = `${font}:${character}`;
|
|
27056
|
+
const cached = glyphCache.get(key);
|
|
27057
|
+
if (cached !== void 0) return cached;
|
|
26087
27058
|
try {
|
|
26088
|
-
|
|
26089
|
-
|
|
26090
|
-
|
|
26091
|
-
|
|
26092
|
-
|
|
26093
|
-
if (
|
|
26094
|
-
|
|
26095
|
-
|
|
26096
|
-
|
|
26097
|
-
|
|
26098
|
-
|
|
26099
|
-
|
|
26100
|
-
|
|
27059
|
+
glyphContext ??= DOMAdapter.get().createCanvas(64, 64).getContext("2d");
|
|
27060
|
+
const ctx = glyphContext;
|
|
27061
|
+
ctx.font = `32px ${font}`;
|
|
27062
|
+
const absent = "";
|
|
27063
|
+
if (ctx.measureText(character).width !== ctx.measureText(absent).width) {
|
|
27064
|
+
if (glyphCache.size >= 2048) glyphCache.clear();
|
|
27065
|
+
glyphCache.set(key, false);
|
|
27066
|
+
return false;
|
|
27067
|
+
}
|
|
27068
|
+
const pixels = (text) => {
|
|
27069
|
+
ctx.clearRect(0, 0, 64, 64);
|
|
27070
|
+
ctx.fillStyle = "#000000";
|
|
27071
|
+
ctx.fillText(text, 0, 40);
|
|
27072
|
+
return ctx.getImageData(0, 0, 64, 64).data;
|
|
27073
|
+
};
|
|
27074
|
+
const a = pixels(character);
|
|
27075
|
+
const b = pixels(absent);
|
|
27076
|
+
const missing = a.some((n) => n !== 0) && a.every((n, i) => n === b[i]);
|
|
27077
|
+
if (glyphCache.size >= 2048) glyphCache.clear();
|
|
27078
|
+
glyphCache.set(key, missing);
|
|
27079
|
+
return missing;
|
|
27080
|
+
} catch {
|
|
27081
|
+
return false;
|
|
27082
|
+
}
|
|
27083
|
+
}
|
|
27084
|
+
function measureBoardText(props) {
|
|
27085
|
+
ensureBoardTextMeasurement();
|
|
27086
|
+
const font = boardFontFamily(props.font, BOARD_FONT_STACKS);
|
|
27087
|
+
const characters = [...new Set(props.text)].slice(0, 512).filter((char) => missingGlyph(char, font)).join("");
|
|
27088
|
+
return {
|
|
27089
|
+
...layoutBoardText(props),
|
|
27090
|
+
missingCharacters: characters
|
|
27091
|
+
};
|
|
27092
|
+
}
|
|
27093
|
+
function inspectBoardScene(scene, limit = 100) {
|
|
27094
|
+
ensureBoardTextMeasurement();
|
|
27095
|
+
const diagnostics = [];
|
|
27096
|
+
const texts = scene.items.filter((item) => item.type === "text" && scene.opacity(item.id) > .01);
|
|
27097
|
+
const push = (entry) => {
|
|
27098
|
+
if (diagnostics.length < limit) diagnostics.push(entry);
|
|
27099
|
+
};
|
|
27100
|
+
for (const item of scene.items) {
|
|
27101
|
+
if (diagnostics.length >= limit) break;
|
|
27102
|
+
if (item.type === "effect" && item.props.follow && item.props.kind !== "trail") push({
|
|
27103
|
+
code: "effect-follow-unused",
|
|
27104
|
+
item: item.id
|
|
27105
|
+
});
|
|
27106
|
+
}
|
|
27107
|
+
let glyphBudget = 256;
|
|
27108
|
+
for (const item of texts) {
|
|
27109
|
+
if (diagnostics.length >= limit || item.type !== "text") break;
|
|
27110
|
+
if (glyphBudget-- > 0) {
|
|
27111
|
+
const measured = measureBoardText(item.props);
|
|
27112
|
+
if (measured.missingCharacters) push({
|
|
27113
|
+
code: "glyph-unavailable",
|
|
27114
|
+
item: item.id,
|
|
27115
|
+
characters: measured.missingCharacters
|
|
27116
|
+
});
|
|
27117
|
+
}
|
|
27118
|
+
const bounds = itemBounds(item.frame);
|
|
27119
|
+
let parentId = item.parent;
|
|
27120
|
+
while (parentId) {
|
|
27121
|
+
const parent = scene.get(parentId);
|
|
27122
|
+
if (!parent) break;
|
|
27123
|
+
if (parent.type === "frame" && parent.props.clip) {
|
|
27124
|
+
const clip = itemBounds(parent.frame);
|
|
27125
|
+
if (bounds.x < clip.x - 1 || bounds.y < clip.y - 1 || bounds.x + bounds.width > clip.x + clip.width + 1 || bounds.y + bounds.height > clip.y + clip.height + 1) {
|
|
27126
|
+
push({
|
|
27127
|
+
code: "text-clipped",
|
|
27128
|
+
item: item.id,
|
|
27129
|
+
related: parentId
|
|
27130
|
+
});
|
|
27131
|
+
break;
|
|
27132
|
+
}
|
|
26101
27133
|
}
|
|
27134
|
+
parentId = parent.parent;
|
|
27135
|
+
}
|
|
27136
|
+
}
|
|
27137
|
+
const sorted = texts.map((item) => ({
|
|
27138
|
+
item,
|
|
27139
|
+
rect: itemBounds(item.frame)
|
|
27140
|
+
})).sort((a, b) => a.rect.x - b.rect.x);
|
|
27141
|
+
let comparisons = 0;
|
|
27142
|
+
for (let i = 0; i < sorted.length && diagnostics.length < limit; i++) {
|
|
27143
|
+
const a = sorted[i];
|
|
27144
|
+
if (!a) continue;
|
|
27145
|
+
for (let j = i + 1; j < sorted.length; j++) {
|
|
27146
|
+
const b = sorted[j];
|
|
27147
|
+
if (!b) continue;
|
|
27148
|
+
if (b.rect.x >= a.rect.x + a.rect.width) break;
|
|
27149
|
+
if (++comparisons > 1e4) return diagnostics;
|
|
27150
|
+
if (a.item.parent === b.item.parent && rectsIntersect(a.rect, b.rect)) push({
|
|
27151
|
+
code: "text-overlap",
|
|
27152
|
+
item: a.item.id,
|
|
27153
|
+
related: b.item.id
|
|
27154
|
+
});
|
|
27155
|
+
if (diagnostics.length >= limit) break;
|
|
26102
27156
|
}
|
|
26103
|
-
} catch {
|
|
26104
|
-
result = null;
|
|
26105
27157
|
}
|
|
26106
|
-
|
|
26107
|
-
cache.set(key, result);
|
|
26108
|
-
return result;
|
|
27158
|
+
return diagnostics;
|
|
26109
27159
|
}
|
|
26110
27160
|
//#endregion
|
|
26111
27161
|
//#region ../sdk/src/board/render/clip.ts
|
|
@@ -26990,39 +28040,6 @@ const taskCardRenderer = {
|
|
|
26990
28040
|
}
|
|
26991
28041
|
};
|
|
26992
28042
|
//#endregion
|
|
26993
|
-
//#region ../sdk/src/board/render/palette.ts
|
|
26994
|
-
const DARK = {
|
|
26995
|
-
bg: 1315860,
|
|
26996
|
-
surface: 2105376,
|
|
26997
|
-
hover: 2763306,
|
|
26998
|
-
border: 3815994,
|
|
26999
|
-
brand: 16734751,
|
|
27000
|
-
text: 16053492,
|
|
27001
|
-
muted: 9211020,
|
|
27002
|
-
rare: 3718648,
|
|
27003
|
-
epic: 10980346,
|
|
27004
|
-
legendary: 16096779
|
|
27005
|
-
};
|
|
27006
|
-
const LIGHT = {
|
|
27007
|
-
bg: 16777215,
|
|
27008
|
-
surface: 16053493,
|
|
27009
|
-
hover: 15329772,
|
|
27010
|
-
border: 14079706,
|
|
27011
|
-
brand: 15222030,
|
|
27012
|
-
text: 1579035,
|
|
27013
|
-
muted: 7041664,
|
|
27014
|
-
rare: 2450411,
|
|
27015
|
-
epic: 8141549,
|
|
27016
|
-
legendary: 11817737
|
|
27017
|
-
};
|
|
27018
|
-
function defaultBoardPalette(mode) {
|
|
27019
|
-
return { ...mode === "light" ? LIGHT : DARK };
|
|
27020
|
-
}
|
|
27021
|
-
function itemColor(context, color, fallback, part = "stroke") {
|
|
27022
|
-
if (((typeof color === "object" && color ? color[context.colorScheme] : color) ?? fallback) === "neutral" && part !== "fill") return context.palette.text;
|
|
27023
|
-
return resolveItemColor(color, fallback, context.colors, context.colorScheme, parseBoardCssColor, part);
|
|
27024
|
-
}
|
|
27025
|
-
//#endregion
|
|
27026
28043
|
//#region ../sdk/src/board/render/stroke.ts
|
|
27027
28044
|
const HEAD_SPREAD = Math.PI / 6;
|
|
27028
28045
|
function dashPattern(dash, width) {
|
|
@@ -27110,6 +28127,27 @@ function endAngle(path, atStart) {
|
|
|
27110
28127
|
}
|
|
27111
28128
|
return null;
|
|
27112
28129
|
}
|
|
28130
|
+
function roundRectPoints(width, height, radius) {
|
|
28131
|
+
const r = Math.min(radius, width / 2, height / 2);
|
|
28132
|
+
if (r <= 0) return [
|
|
28133
|
+
worldPoint(0, 0),
|
|
28134
|
+
worldPoint(width, 0),
|
|
28135
|
+
worldPoint(width, height),
|
|
28136
|
+
worldPoint(0, height),
|
|
28137
|
+
worldPoint(0, 0)
|
|
28138
|
+
];
|
|
28139
|
+
const corner = (cx, cy, from) => Array.from({ length: 9 }, (_, index) => {
|
|
28140
|
+
const angle = from + index / 8 * (Math.PI / 2);
|
|
28141
|
+
return worldPoint(cx + Math.cos(angle) * r, cy + Math.sin(angle) * r);
|
|
28142
|
+
});
|
|
28143
|
+
return [
|
|
28144
|
+
...corner(width - r, r, -Math.PI / 2),
|
|
28145
|
+
...corner(width - r, height - r, 0),
|
|
28146
|
+
...corner(r, height - r, Math.PI / 2),
|
|
28147
|
+
...corner(r, r, Math.PI),
|
|
28148
|
+
worldPoint(width - r, 0)
|
|
28149
|
+
];
|
|
28150
|
+
}
|
|
27113
28151
|
//#endregion
|
|
27114
28152
|
//#region ../sdk/src/board/render/renderers/arrow-card-renderer.ts
|
|
27115
28153
|
const partsByContainer$10 = /* @__PURE__ */ new WeakMap();
|
|
@@ -27122,6 +28160,7 @@ function sync$10(container, item, context) {
|
|
|
27122
28160
|
const { props, style } = item;
|
|
27123
28161
|
const selected = context.selectedIds.has(item.id);
|
|
27124
28162
|
const hovered = context.hoveredId === item.id;
|
|
28163
|
+
parts.line.alpha = itemAlpha(context, style.stroke);
|
|
27125
28164
|
const color = itemColor(context, style.stroke, "brand");
|
|
27126
28165
|
const size = style.strokeWidth ?? 2.5;
|
|
27127
28166
|
syncTextResolution(parts.label, parts, context.zoom);
|
|
@@ -27235,7 +28274,7 @@ const arrowCardRenderer = {
|
|
|
27235
28274
|
drawFarStroke(graphics, trimPolyline(resolveSceneArrow(arrow, context.scene).path, arrow.style.trim), {
|
|
27236
28275
|
color: itemColor(context, arrow.style.stroke, "brand"),
|
|
27237
28276
|
width: arrow.style.strokeWidth ?? 2.5,
|
|
27238
|
-
alpha: .85
|
|
28277
|
+
alpha: .85 * itemAlpha(context, arrow.style.stroke)
|
|
27239
28278
|
});
|
|
27240
28279
|
},
|
|
27241
28280
|
destroy: (container) => {
|
|
@@ -27255,7 +28294,7 @@ function sync$9(container, item, context) {
|
|
|
27255
28294
|
const { width, height } = item.frame;
|
|
27256
28295
|
const selected = context.selectedIds.has(item.id);
|
|
27257
28296
|
const hovered = context.hoveredId === item.id;
|
|
27258
|
-
const title = item.props.snapshot?.title ?? item.props.src
|
|
28297
|
+
const title = item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
|
|
27259
28298
|
syncTextResolution(parts.label, parts, context.zoom);
|
|
27260
28299
|
if (parts.label.text !== title) parts.label.text = title;
|
|
27261
28300
|
syncTextWrapWidth(parts.label, parts, Math.max(1, width - 24), false);
|
|
@@ -27430,6 +28469,7 @@ function sync$8(container, item, context) {
|
|
|
27430
28469
|
}
|
|
27431
28470
|
const stroke = parts.stroke;
|
|
27432
28471
|
if (!stroke) return;
|
|
28472
|
+
stroke.alpha = itemAlpha(context, item.style.stroke);
|
|
27433
28473
|
if (stroke instanceof Mesh) stroke.tint = color;
|
|
27434
28474
|
const { box } = parts;
|
|
27435
28475
|
const sx = item.frame.width / box.width;
|
|
@@ -27472,7 +28512,7 @@ const drawCardRenderer = {
|
|
|
27472
28512
|
drawFarStroke(graphics, farStrokeSamples(trimDrawPoints(draw.props.points, draw.style.trim)).map((point) => applyMatrix$1(matrix, point)), {
|
|
27473
28513
|
color: itemColor(context, draw.style.stroke, "brand"),
|
|
27474
28514
|
width: draw.style.strokeWidth ?? 4,
|
|
27475
|
-
alpha:
|
|
28515
|
+
alpha: itemAlpha(context, draw.style.stroke)
|
|
27476
28516
|
});
|
|
27477
28517
|
},
|
|
27478
28518
|
destroy: (container) => {
|
|
@@ -27588,11 +28628,27 @@ function drawTrail(graphics, parts, item, context, color) {
|
|
|
27588
28628
|
parts.trail.length = 0;
|
|
27589
28629
|
return;
|
|
27590
28630
|
}
|
|
27591
|
-
const
|
|
27592
|
-
|
|
27593
|
-
|
|
27594
|
-
|
|
28631
|
+
const angle = item.frame.rotation * Math.PI / 180;
|
|
28632
|
+
const localPoint = (frame, t) => {
|
|
28633
|
+
const dx = frame.x + frame.width / 2 - item.frame.x - item.frame.width / 2;
|
|
28634
|
+
const dy = frame.y + frame.height / 2 - item.frame.y - item.frame.height / 2;
|
|
28635
|
+
return {
|
|
28636
|
+
x: dx * Math.cos(angle) + dy * Math.sin(angle) + item.frame.width / 2,
|
|
28637
|
+
y: -dx * Math.sin(angle) + dy * Math.cos(angle) + item.frame.height / 2,
|
|
28638
|
+
t
|
|
28639
|
+
};
|
|
27595
28640
|
};
|
|
28641
|
+
const head = localPoint(target.frame, time);
|
|
28642
|
+
if (context.frameAt) {
|
|
28643
|
+
parts.trail.length = 0;
|
|
28644
|
+
const start = Math.max(0, time - item.props.life);
|
|
28645
|
+
const step = Math.max(1e3 / 30, item.props.life / 63);
|
|
28646
|
+
for (let t = Math.ceil(start / step) * step; t < time; t += step) {
|
|
28647
|
+
const past = context.frameAt(target.id, t);
|
|
28648
|
+
if (past) parts.trail.push(localPoint(past, t));
|
|
28649
|
+
}
|
|
28650
|
+
parts.trail.push(head);
|
|
28651
|
+
}
|
|
27596
28652
|
const last = parts.trail.at(-1);
|
|
27597
28653
|
if (last && (time < last.t || time - last.t > item.props.life)) parts.trail.length = 0;
|
|
27598
28654
|
if (!last || Math.hypot(head.x - last.x, head.y - last.y) > .5) parts.trail.push(head);
|
|
@@ -27619,6 +28675,7 @@ function sync$7(container, item, context) {
|
|
|
27619
28675
|
const seed = hashString(props.seed ?? item.id);
|
|
27620
28676
|
const time = Math.max(0, context.time);
|
|
27621
28677
|
const graphics = parts.graphics;
|
|
28678
|
+
graphics.alpha = itemAlpha(context, item.style.fill ?? item.style.stroke);
|
|
27622
28679
|
graphics.clear();
|
|
27623
28680
|
switch (props.kind) {
|
|
27624
28681
|
case "particles":
|
|
@@ -27685,13 +28742,15 @@ function sync$6(container, item, context) {
|
|
|
27685
28742
|
const selected = context.selectedIds.has(item.id);
|
|
27686
28743
|
const hovered = context.hoveredId === item.id;
|
|
27687
28744
|
const fill = itemColor(context, style.fill ?? style.stroke, "neutral", "fill");
|
|
27688
|
-
const fillAlpha = style.fillOpacity ?? (style.fill ? 1 : DEFAULT_WASH);
|
|
28745
|
+
const fillAlpha = (style.fillOpacity ?? (style.fill ? 1 : DEFAULT_WASH)) * itemAlpha(context, style.fill ?? style.stroke);
|
|
27689
28746
|
const baseStroke = itemColor(context, style.stroke, "neutral");
|
|
27690
|
-
const stroke = selected ? context.palette.brand : baseStroke;
|
|
27691
|
-
const alpha = selected ? 1 : hovered ? .85 : .55;
|
|
28747
|
+
const stroke = selected ? context.palette.brand : hovered && style.stroke ? context.palette.muted : baseStroke;
|
|
28748
|
+
const alpha = (selected || style.stroke ? 1 : hovered ? .85 : .55) * itemAlpha(context, style.stroke);
|
|
27692
28749
|
const radius = style.radius ?? 4;
|
|
27693
28750
|
const strokeWidth = style.strokeWidth ?? 1.5;
|
|
28751
|
+
const decorated = props.decoration !== "none";
|
|
27694
28752
|
const visualSig = [
|
|
28753
|
+
props.decoration,
|
|
27695
28754
|
frame.width,
|
|
27696
28755
|
frame.height,
|
|
27697
28756
|
selected,
|
|
@@ -27699,23 +28758,36 @@ function sync$6(container, item, context) {
|
|
|
27699
28758
|
fill,
|
|
27700
28759
|
fillAlpha,
|
|
27701
28760
|
stroke,
|
|
28761
|
+
alpha,
|
|
27702
28762
|
radius,
|
|
27703
|
-
strokeWidth
|
|
28763
|
+
strokeWidth,
|
|
28764
|
+
style.dash,
|
|
28765
|
+
style.trim
|
|
27704
28766
|
].join("|");
|
|
27705
28767
|
if (visualSig !== parts.visualSig) {
|
|
27706
28768
|
parts.visualSig = visualSig;
|
|
27707
28769
|
parts.box.clear();
|
|
27708
|
-
parts.box.roundRect(0, 0, frame.width, frame.height, radius).fill({
|
|
28770
|
+
if (decorated) parts.box.roundRect(0, 0, frame.width, frame.height, radius).fill({
|
|
27709
28771
|
color: fill,
|
|
27710
28772
|
alpha: fillAlpha
|
|
27711
28773
|
});
|
|
27712
|
-
|
|
27713
|
-
|
|
27714
|
-
|
|
27715
|
-
|
|
27716
|
-
|
|
28774
|
+
const width = selected ? Math.max(2, strokeWidth) : strokeWidth;
|
|
28775
|
+
const pattern = decorated ? dashPattern(style.dash, width) : null;
|
|
28776
|
+
const trim = decorated ? style.trim ?? 1 : 1;
|
|
28777
|
+
if ((decorated && strokeWidth > 0 || selected || hovered) && trim > 0) {
|
|
28778
|
+
if (pattern || trim < 1) tracePolyline(parts.box, trimPolyline(roundRectPoints(frame.width, frame.height, radius), trim), pattern);
|
|
28779
|
+
else parts.box.roundRect(0, 0, frame.width, frame.height, radius);
|
|
28780
|
+
parts.box.stroke({
|
|
28781
|
+
color: stroke,
|
|
28782
|
+
width,
|
|
28783
|
+
alpha,
|
|
28784
|
+
cap: pattern || trim < 1 ? "round" : "butt",
|
|
28785
|
+
join: "round"
|
|
28786
|
+
});
|
|
28787
|
+
}
|
|
27717
28788
|
}
|
|
27718
|
-
|
|
28789
|
+
parts.label.visible = decorated;
|
|
28790
|
+
const title = locked ? `🔒 ${props.label}`.trim() : props.label;
|
|
27719
28791
|
const labelColor = style.stroke ? baseStroke : context.palette.muted;
|
|
27720
28792
|
const textSig = [title, labelColor].join("|");
|
|
27721
28793
|
if (textSig !== parts.textSig) {
|
|
@@ -27753,10 +28825,10 @@ const frameCardRenderer = {
|
|
|
27753
28825
|
if (item.type === "frame") sync$6(container, item, context);
|
|
27754
28826
|
},
|
|
27755
28827
|
renderFar: (graphics, item, context) => {
|
|
27756
|
-
if (item.type !== "frame") return;
|
|
28828
|
+
if (item.type !== "frame" || item.props.decoration === "none") return;
|
|
27757
28829
|
drawFarPlate(graphics, item.frame, {
|
|
27758
28830
|
fill: itemColor(context, item.style.fill ?? item.style.stroke, "neutral", "fill"),
|
|
27759
|
-
fillAlpha: Math.max(.07, item.style.fillOpacity ?? (item.style.fill ? 1 : 0))
|
|
28831
|
+
fillAlpha: Math.max(.07, item.style.fillOpacity ?? (item.style.fill ? 1 : 0)) * itemAlpha(context, item.style.fill ?? item.style.stroke)
|
|
27760
28832
|
});
|
|
27761
28833
|
},
|
|
27762
28834
|
destroy: (container) => {
|
|
@@ -27845,27 +28917,7 @@ function outlinePoints(geometry, width, height, radius) {
|
|
|
27845
28917
|
p(0, height),
|
|
27846
28918
|
p(width / 2, 0)
|
|
27847
28919
|
];
|
|
27848
|
-
default:
|
|
27849
|
-
const r = Math.min(radius, width / 2, height / 2);
|
|
27850
|
-
if (r <= 0) return [
|
|
27851
|
-
p(0, 0),
|
|
27852
|
-
p(width, 0),
|
|
27853
|
-
p(width, height),
|
|
27854
|
-
p(0, height),
|
|
27855
|
-
p(0, 0)
|
|
27856
|
-
];
|
|
27857
|
-
const corner = (cx, cy, from) => Array.from({ length: 9 }, (_, index) => {
|
|
27858
|
-
const angle = from + index / 8 * (Math.PI / 2);
|
|
27859
|
-
return p(cx + Math.cos(angle) * r, cy + Math.sin(angle) * r);
|
|
27860
|
-
});
|
|
27861
|
-
return [
|
|
27862
|
-
...corner(width - r, r, -Math.PI / 2),
|
|
27863
|
-
...corner(width - r, height - r, 0),
|
|
27864
|
-
...corner(r, height - r, Math.PI / 2),
|
|
27865
|
-
...corner(r, r, Math.PI),
|
|
27866
|
-
p(width - r, 0)
|
|
27867
|
-
];
|
|
27868
|
-
}
|
|
28920
|
+
default: return roundRectPoints(width, height, radius);
|
|
27869
28921
|
}
|
|
27870
28922
|
}
|
|
27871
28923
|
function sync$5(container, item, context) {
|
|
@@ -27878,7 +28930,8 @@ function sync$5(container, item, context) {
|
|
|
27878
28930
|
const hovered = context.hoveredId === item.id;
|
|
27879
28931
|
const stroke = itemColor(context, style.stroke ?? style.fill, "brand");
|
|
27880
28932
|
const fill = itemColor(context, style.fill ?? style.stroke, "brand", "fill");
|
|
27881
|
-
const fillOpacity = style.fillOpacity ?? (style.fill ? 1 : 0);
|
|
28933
|
+
const fillOpacity = (style.fillOpacity ?? (style.fill ? 1 : 0)) * itemAlpha(context, style.fill ?? style.stroke);
|
|
28934
|
+
const strokeAlpha = itemAlpha(context, style.stroke ?? style.fill);
|
|
27882
28935
|
const strokeWidth = style.strokeWidth ?? 1.75;
|
|
27883
28936
|
syncTextResolution(parts.label, parts, context.zoom);
|
|
27884
28937
|
const visualSig = [
|
|
@@ -27891,6 +28944,7 @@ function sync$5(container, item, context) {
|
|
|
27891
28944
|
fill,
|
|
27892
28945
|
fillOpacity,
|
|
27893
28946
|
stroke,
|
|
28947
|
+
strokeAlpha,
|
|
27894
28948
|
strokeWidth,
|
|
27895
28949
|
style.dash,
|
|
27896
28950
|
style.radius,
|
|
@@ -27918,12 +28972,13 @@ function sync$5(container, item, context) {
|
|
|
27918
28972
|
parts.shape.stroke({
|
|
27919
28973
|
color: hovered && !selected ? context.palette.muted : stroke,
|
|
27920
28974
|
width: lineWidth,
|
|
27921
|
-
alpha: selected ? 1 : hovered ? .95 : .85,
|
|
28975
|
+
alpha: (selected ? 1 : hovered ? .95 : .85) * strokeAlpha,
|
|
27922
28976
|
cap: partial || pattern ? "round" : "butt",
|
|
27923
28977
|
join: "round"
|
|
27924
28978
|
});
|
|
27925
28979
|
}
|
|
27926
28980
|
}
|
|
28981
|
+
parts.label.alpha = itemAlpha(context, style.stroke);
|
|
27927
28982
|
const label = itemColor(context, style.stroke, "neutral", "label");
|
|
27928
28983
|
const textSig = [
|
|
27929
28984
|
props.text,
|
|
@@ -27985,9 +29040,9 @@ const shapeCardRenderer = {
|
|
|
27985
29040
|
if (item.type !== "shape") return;
|
|
27986
29041
|
drawFarPlate(graphics, item.frame, {
|
|
27987
29042
|
fill: itemColor(context, item.style.fill ?? item.style.stroke, "brand", "fill"),
|
|
27988
|
-
fillAlpha: Math.max(.18, item.style.fillOpacity ?? 0),
|
|
29043
|
+
fillAlpha: Math.max(.18, item.style.fillOpacity ?? 0) * itemAlpha(context, item.style.fill ?? item.style.stroke),
|
|
27989
29044
|
accent: itemColor(context, item.style.stroke, "brand"),
|
|
27990
|
-
accentAlpha: .9
|
|
29045
|
+
accentAlpha: .9 * itemAlpha(context, item.style.stroke)
|
|
27991
29046
|
});
|
|
27992
29047
|
},
|
|
27993
29048
|
destroy: (container) => {
|
|
@@ -27996,26 +29051,27 @@ const shapeCardRenderer = {
|
|
|
27996
29051
|
}
|
|
27997
29052
|
};
|
|
27998
29053
|
//#endregion
|
|
27999
|
-
//#region ../sdk/src/board/render/renderers/
|
|
29054
|
+
//#region ../sdk/src/board/render/renderers/custom-card-renderer.ts
|
|
28000
29055
|
const partsByContainer$4 = /* @__PURE__ */ new WeakMap();
|
|
28001
29056
|
function sync$4(container, item, context) {
|
|
28002
29057
|
const parts = partsByContainer$4.get(container);
|
|
28003
29058
|
if (!parts) return;
|
|
28004
29059
|
if (parts.id !== item.id) {
|
|
28005
|
-
context.
|
|
29060
|
+
context.scripts?.release(parts.id);
|
|
28006
29061
|
parts.id = item.id;
|
|
28007
29062
|
}
|
|
28008
29063
|
positionShell(parts.root, item);
|
|
28009
29064
|
const { width, height } = item.frame;
|
|
28010
29065
|
const resolution = Math.min(4, Math.max(.25, context.zoom * getBoardResolution()));
|
|
28011
|
-
const texture = context.
|
|
28012
|
-
const error = context.
|
|
29066
|
+
const texture = context.scripts?.frame(item, context.time, resolution) ?? null;
|
|
29067
|
+
const error = context.scripts?.error(item.id) ?? null;
|
|
28013
29068
|
parts.sprite.visible = Boolean(texture);
|
|
28014
29069
|
if (texture && parts.sprite.texture !== texture) parts.sprite.texture = texture;
|
|
28015
29070
|
parts.sprite.width = width;
|
|
28016
29071
|
parts.sprite.height = height;
|
|
28017
29072
|
const selected = context.selectedIds.has(item.id);
|
|
28018
|
-
const
|
|
29073
|
+
const ready = Boolean(texture) || context.scripts?.ready?.(item.id) === true;
|
|
29074
|
+
const message = error ? `${item.script.src}\n${error}` : ready ? "" : item.script.src;
|
|
28019
29075
|
const sig = [
|
|
28020
29076
|
width,
|
|
28021
29077
|
height,
|
|
@@ -28028,21 +29084,21 @@ function sync$4(container, item, context) {
|
|
|
28028
29084
|
if (sig === parts.sig) return;
|
|
28029
29085
|
parts.sig = sig;
|
|
28030
29086
|
parts.placeholder.clear();
|
|
28031
|
-
if (!
|
|
29087
|
+
if (!ready || selected || error) parts.placeholder.rect(0, 0, width, height).stroke({
|
|
28032
29088
|
color: selected ? context.palette.brand : context.palette.border,
|
|
28033
29089
|
width: selected ? 2 : 1,
|
|
28034
29090
|
alpha: .8
|
|
28035
29091
|
});
|
|
28036
|
-
parts.label.visible = !
|
|
29092
|
+
parts.label.visible = !ready || Boolean(error);
|
|
28037
29093
|
parts.label.text = message;
|
|
28038
29094
|
parts.label.style.fill = error ? context.colors.rose.stroke : context.palette.muted;
|
|
28039
29095
|
parts.label.style.wordWrapWidth = Math.max(40, width - 16);
|
|
28040
29096
|
parts.label.position.set(width / 2, height / 2);
|
|
28041
29097
|
}
|
|
28042
|
-
const
|
|
28043
|
-
id: "
|
|
28044
|
-
canRender: (item) => item.type === "
|
|
28045
|
-
animated:
|
|
29098
|
+
const customCardRenderer = {
|
|
29099
|
+
id: "custom-card",
|
|
29100
|
+
canRender: (item) => item.type === "custom",
|
|
29101
|
+
animated: false,
|
|
28046
29102
|
create: (item, context) => {
|
|
28047
29103
|
const root = new Container();
|
|
28048
29104
|
const sprite = new Sprite(Texture.EMPTY);
|
|
@@ -28068,11 +29124,11 @@ const sketchCardRenderer = {
|
|
|
28068
29124
|
label,
|
|
28069
29125
|
sig: ""
|
|
28070
29126
|
});
|
|
28071
|
-
if (item.type === "
|
|
29127
|
+
if (item.type === "custom") sync$4(root, item, context);
|
|
28072
29128
|
return root;
|
|
28073
29129
|
},
|
|
28074
29130
|
update: (container, item, context) => {
|
|
28075
|
-
if (item.type === "
|
|
29131
|
+
if (item.type === "custom") sync$4(container, item, context);
|
|
28076
29132
|
},
|
|
28077
29133
|
renderFar: (graphics, item, context) => {
|
|
28078
29134
|
drawFarPlate(graphics, item.frame, {
|
|
@@ -28082,7 +29138,7 @@ const sketchCardRenderer = {
|
|
|
28082
29138
|
},
|
|
28083
29139
|
destroy: (container, context) => {
|
|
28084
29140
|
const parts = partsByContainer$4.get(container);
|
|
28085
|
-
if (parts) context.
|
|
29141
|
+
if (parts) context.scripts?.release(parts.id);
|
|
28086
29142
|
parts?.root.destroy({ children: true });
|
|
28087
29143
|
partsByContainer$4.delete(container);
|
|
28088
29144
|
}
|
|
@@ -28102,6 +29158,39 @@ function layoutContain$1(sprite, width, height) {
|
|
|
28102
29158
|
sprite.x = (width - sprite.width) / 2;
|
|
28103
29159
|
sprite.y = (height - sprite.height) / 2;
|
|
28104
29160
|
}
|
|
29161
|
+
function cropRect(crop) {
|
|
29162
|
+
if (!crop) return null;
|
|
29163
|
+
const w = Math.min(crop.w, 1 - crop.x);
|
|
29164
|
+
const h = Math.min(crop.h, 1 - crop.y);
|
|
29165
|
+
return w > 0 && h > 0 ? {
|
|
29166
|
+
x: crop.x,
|
|
29167
|
+
y: crop.y,
|
|
29168
|
+
w,
|
|
29169
|
+
h
|
|
29170
|
+
} : null;
|
|
29171
|
+
}
|
|
29172
|
+
function showTexture(parts, texture, crop) {
|
|
29173
|
+
const rect = texture ? cropRect(crop) : null;
|
|
29174
|
+
const key = rect ? `${rect.x}|${rect.y}|${rect.w}|${rect.h}` : "";
|
|
29175
|
+
const previous = parts.cropped;
|
|
29176
|
+
const reusable = previous && parts.cropSource === texture && parts.cropKey === key;
|
|
29177
|
+
if (texture && rect && !reusable) {
|
|
29178
|
+
const { frame } = texture;
|
|
29179
|
+
const width = Math.max(1, frame.width * rect.w);
|
|
29180
|
+
const height = Math.max(1, frame.height * rect.h);
|
|
29181
|
+
const x = frame.x + Math.min(frame.width * rect.x, frame.width - width);
|
|
29182
|
+
const y = frame.y + Math.min(frame.height * rect.y, frame.height - height);
|
|
29183
|
+
parts.cropped = new Texture({
|
|
29184
|
+
source: texture.source,
|
|
29185
|
+
frame: new Rectangle(x, y, width, height)
|
|
29186
|
+
});
|
|
29187
|
+
} else if (!rect) parts.cropped = null;
|
|
29188
|
+
parts.cropSource = rect ? texture : null;
|
|
29189
|
+
parts.cropKey = key;
|
|
29190
|
+
const display = parts.cropped ?? texture ?? Texture.EMPTY;
|
|
29191
|
+
if (parts.sprite.texture !== display) parts.sprite.texture = display;
|
|
29192
|
+
if (previous && previous !== parts.cropped) previous.destroy(false);
|
|
29193
|
+
}
|
|
28105
29194
|
function sync$3(container, item, context) {
|
|
28106
29195
|
const parts = partsByContainer$3.get(container);
|
|
28107
29196
|
if (!parts) return;
|
|
@@ -28120,12 +29209,13 @@ function sync$3(container, item, context) {
|
|
|
28120
29209
|
selected,
|
|
28121
29210
|
hovered,
|
|
28122
29211
|
texId,
|
|
29212
|
+
JSON.stringify(item.props.crop ?? null),
|
|
28123
29213
|
failed,
|
|
28124
29214
|
context.colorScheme
|
|
28125
29215
|
].join("|");
|
|
28126
29216
|
if (sig === parts.sig) return;
|
|
28127
29217
|
parts.sig = sig;
|
|
28128
|
-
|
|
29218
|
+
showTexture(parts, texture, item.props.crop);
|
|
28129
29219
|
parts.sprite.visible = Boolean(texture);
|
|
28130
29220
|
if (texture) layoutContain$1(parts.sprite, width, height);
|
|
28131
29221
|
parts.placeholder.clear();
|
|
@@ -28185,7 +29275,10 @@ const imageCardRenderer = {
|
|
|
28185
29275
|
root,
|
|
28186
29276
|
sprite,
|
|
28187
29277
|
placeholder,
|
|
28188
|
-
sig: ""
|
|
29278
|
+
sig: "",
|
|
29279
|
+
cropped: null,
|
|
29280
|
+
cropSource: null,
|
|
29281
|
+
cropKey: ""
|
|
28189
29282
|
});
|
|
28190
29283
|
if (item.type === "image") sync$3(root, item, context);
|
|
28191
29284
|
return root;
|
|
@@ -28205,7 +29298,9 @@ const imageCardRenderer = {
|
|
|
28205
29298
|
});
|
|
28206
29299
|
},
|
|
28207
29300
|
destroy: (container) => {
|
|
28208
|
-
partsByContainer$3.get(container)
|
|
29301
|
+
const parts = partsByContainer$3.get(container);
|
|
29302
|
+
parts?.root.destroy({ children: true });
|
|
29303
|
+
parts?.cropped?.destroy(false);
|
|
28209
29304
|
partsByContainer$3.delete(container);
|
|
28210
29305
|
}
|
|
28211
29306
|
};
|
|
@@ -28237,6 +29332,8 @@ function sync$2(container, item, context) {
|
|
|
28237
29332
|
parts.body.anchor.set(anchorX, 0);
|
|
28238
29333
|
parts.body.position.set(item.frame.width * anchorX, 0);
|
|
28239
29334
|
syncTextResolution(parts.body, parts, context.zoom * Math.max(1, previewScale));
|
|
29335
|
+
parts.body.alpha = itemAlpha(context, item.style.fill);
|
|
29336
|
+
parts.caret.alpha = parts.body.alpha;
|
|
28240
29337
|
const ink = itemColor(context, item.style.fill, "neutral");
|
|
28241
29338
|
const text = revealed(props.text, props.reveal);
|
|
28242
29339
|
const contentSig = [
|
|
@@ -28297,7 +29394,7 @@ const textCardRenderer = {
|
|
|
28297
29394
|
if (item.type !== "text") return;
|
|
28298
29395
|
drawFarPlate(graphics, item.frame, {
|
|
28299
29396
|
fill: itemColor(context, item.style.fill, "neutral"),
|
|
28300
|
-
fillAlpha: .35
|
|
29397
|
+
fillAlpha: .35 * itemAlpha(context, item.style.fill)
|
|
28301
29398
|
});
|
|
28302
29399
|
},
|
|
28303
29400
|
destroy: (container) => {
|
|
@@ -28357,6 +29454,7 @@ const unknownCardRenderer = {
|
|
|
28357
29454
|
create: (item, context) => {
|
|
28358
29455
|
const root = new Container();
|
|
28359
29456
|
const box = new Graphics();
|
|
29457
|
+
box.context.batchMode = "batch";
|
|
28360
29458
|
const resolution = textResolutionForZoom(context.zoom);
|
|
28361
29459
|
const label = new Text({
|
|
28362
29460
|
text: "",
|
|
@@ -28416,7 +29514,7 @@ function sync(container, item, context) {
|
|
|
28416
29514
|
const selected = context.selectedIds.has(item.id);
|
|
28417
29515
|
const hovered = context.hoveredId === item.id;
|
|
28418
29516
|
const resizing = context.resizingIds.has(item.id);
|
|
28419
|
-
const title = item.props.snapshot?.title ?? item.props.src
|
|
29517
|
+
const title = item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
|
|
28420
29518
|
const key = context.assetKey(item);
|
|
28421
29519
|
const texture = key ? context.getTexture(key) : null;
|
|
28422
29520
|
const failed = Boolean(key && !texture && context.hasError(key));
|
|
@@ -28546,7 +29644,7 @@ const builtinCardRenderers = [
|
|
|
28546
29644
|
arrowCardRenderer,
|
|
28547
29645
|
frameCardRenderer,
|
|
28548
29646
|
effectCardRenderer,
|
|
28549
|
-
|
|
29647
|
+
customCardRenderer
|
|
28550
29648
|
];
|
|
28551
29649
|
/**
|
|
28552
29650
|
* Pick a renderer per item: `renderers` first, then the built-ins. Items no
|
|
@@ -28562,100 +29660,968 @@ function createBoardCardRendererResolver(renderers = []) {
|
|
|
28562
29660
|
/** The built-in renderers alone. */
|
|
28563
29661
|
const getBoardCardRenderer = createBoardCardRendererResolver();
|
|
28564
29662
|
//#endregion
|
|
28565
|
-
//#region ../
|
|
28566
|
-
|
|
28567
|
-
|
|
28568
|
-
const
|
|
28569
|
-
return
|
|
28570
|
-
|
|
28571
|
-
|
|
28572
|
-
|
|
28573
|
-
|
|
28574
|
-
|
|
28575
|
-
|
|
28576
|
-
|
|
28577
|
-
|
|
28578
|
-
|
|
28579
|
-
|
|
28580
|
-
|
|
28581
|
-
|
|
28582
|
-
|
|
28583
|
-
|
|
28584
|
-
|
|
28585
|
-
|
|
28586
|
-
|
|
28587
|
-
|
|
28588
|
-
|
|
28589
|
-
|
|
28590
|
-
|
|
28591
|
-
|
|
28592
|
-
|
|
28593
|
-
|
|
28594
|
-
|
|
28595
|
-
|
|
28596
|
-
missing
|
|
28597
|
-
|
|
29663
|
+
//#region ../protocol/dist/board-url.js
|
|
29664
|
+
const BOARD_REMOTE_URL_MAX_LENGTH = 4096;
|
|
29665
|
+
function parseIpv4(host) {
|
|
29666
|
+
const parts = host.split(".").map(Number);
|
|
29667
|
+
return parts.length === 4 && parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255) ? parts : null;
|
|
29668
|
+
}
|
|
29669
|
+
function isBlockedIpv4(host) {
|
|
29670
|
+
const parts = parseIpv4(host);
|
|
29671
|
+
if (!parts) return false;
|
|
29672
|
+
const [first, second, third] = parts;
|
|
29673
|
+
if (first === 0 || first === 10 || first === 127) return true;
|
|
29674
|
+
if (first === 100 && second >= 64 && second <= 127) return true;
|
|
29675
|
+
if (first === 169 && second === 254) return true;
|
|
29676
|
+
if (first === 172 && second >= 16 && second <= 31) return true;
|
|
29677
|
+
if (first === 192 && second === 168) return true;
|
|
29678
|
+
if (first === 192 && second === 0 && (third === 0 || third === 2)) return true;
|
|
29679
|
+
if (first === 192 && second === 88 && third === 99) return true;
|
|
29680
|
+
if (first === 198 && (second === 18 || second === 19)) return true;
|
|
29681
|
+
if (first === 198 && second === 51 && third === 100) return true;
|
|
29682
|
+
if (first === 203 && second === 0 && third === 113) return true;
|
|
29683
|
+
return first >= 224;
|
|
29684
|
+
}
|
|
29685
|
+
function expandIpv6(host) {
|
|
29686
|
+
const [head, tail, extra] = host.toLowerCase().split("::");
|
|
29687
|
+
if (extra !== void 0) return null;
|
|
29688
|
+
const headParts = head ? head.split(":").filter(Boolean) : [];
|
|
29689
|
+
const tailParts = tail ? tail.split(":").filter(Boolean) : [];
|
|
29690
|
+
const missing = 8 - headParts.length - tailParts.length;
|
|
29691
|
+
if (missing < 0 || tail === void 0 && missing !== 0) return null;
|
|
29692
|
+
const parts = [
|
|
29693
|
+
...headParts,
|
|
29694
|
+
...Array.from({ length: missing }, () => "0"),
|
|
29695
|
+
...tailParts
|
|
29696
|
+
];
|
|
29697
|
+
if (parts.length !== 8 || parts.some((part) => !/^[0-9a-f]{1,4}$/.test(part))) return null;
|
|
29698
|
+
return parts.map((part) => part.padStart(4, "0"));
|
|
29699
|
+
}
|
|
29700
|
+
function isBlockedIpv6(host) {
|
|
29701
|
+
const parts = expandIpv6(host);
|
|
29702
|
+
if (!parts) return true;
|
|
29703
|
+
if (parts.every((part) => part === "0000")) return true;
|
|
29704
|
+
if (parts.slice(0, 7).every((part) => part === "0000") && parts[7] === "0001") return true;
|
|
29705
|
+
if (parts.slice(0, 5).every((part) => part === "0000") && parts[5] === "ffff") {
|
|
29706
|
+
const high = Number.parseInt(parts[6] ?? "0", 16);
|
|
29707
|
+
const low = Number.parseInt(parts[7] ?? "0", 16);
|
|
29708
|
+
return isBlockedIpv4(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`);
|
|
29709
|
+
}
|
|
29710
|
+
if (parts.slice(0, 6).every((part) => part === "0000")) return true;
|
|
29711
|
+
const first = Number.parseInt(parts[0] ?? "0", 16);
|
|
29712
|
+
if ((first & 65024) === 64512) return true;
|
|
29713
|
+
if ((first & 65472) === 65152 || (first & 65472) === 65216) return true;
|
|
29714
|
+
if ((first & 65280) === 65280) return true;
|
|
29715
|
+
return parts[0] === "2001" && parts[1] === "0db8";
|
|
29716
|
+
}
|
|
29717
|
+
function isPublicBoardRemoteAddress(value) {
|
|
29718
|
+
const address = value.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
29719
|
+
if (parseIpv4(address)) return !isBlockedIpv4(address);
|
|
29720
|
+
return address.includes(":") && !isBlockedIpv6(address);
|
|
29721
|
+
}
|
|
29722
|
+
function isBlockedHost(hostname) {
|
|
29723
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
29724
|
+
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal")) return true;
|
|
29725
|
+
if (parseIpv4(host) || host.includes(":")) return !isPublicBoardRemoteAddress(host);
|
|
29726
|
+
return false;
|
|
28598
29727
|
}
|
|
28599
|
-
|
|
28600
|
-
|
|
28601
|
-
|
|
28602
|
-
|
|
28603
|
-
|
|
28604
|
-
|
|
28605
|
-
|
|
28606
|
-
|
|
28607
|
-
|
|
28608
|
-
|
|
28609
|
-
const
|
|
28610
|
-
if (
|
|
28611
|
-
|
|
28612
|
-
|
|
28613
|
-
|
|
28614
|
-
|
|
28615
|
-
|
|
28616
|
-
else {
|
|
28617
|
-
const sprite = new Sprite({
|
|
28618
|
-
texture,
|
|
28619
|
-
alpha: opacity
|
|
28620
|
-
});
|
|
28621
|
-
const scale = fit === "cover" ? Math.max(outputWidth / texture.width, outputHeight / texture.height) : Math.min(outputWidth / texture.width, outputHeight / texture.height);
|
|
28622
|
-
sprite.width = texture.width * scale;
|
|
28623
|
-
sprite.height = texture.height * scale;
|
|
28624
|
-
const x = position === "left" ? 0 : position === "right" ? outputWidth - sprite.width : (outputWidth - sprite.width) / 2;
|
|
28625
|
-
const y = position === "top" ? 0 : position === "bottom" ? outputHeight - sprite.height : (outputHeight - sprite.height) / 2;
|
|
28626
|
-
sprite.position.set(x, y);
|
|
28627
|
-
root.addChild(sprite);
|
|
28628
|
-
}
|
|
28629
|
-
}
|
|
28630
|
-
const world = new Container({
|
|
28631
|
-
isRenderGroup: true,
|
|
28632
|
-
label: "board-export-world"
|
|
28633
|
-
});
|
|
28634
|
-
world.scale.set(input.scale);
|
|
28635
|
-
world.position.set(-input.world.x * input.scale, -input.world.y * input.scale);
|
|
28636
|
-
const resolve = input.renderers ?? getBoardCardRenderer;
|
|
28637
|
-
const groups = /* @__PURE__ */ new Map();
|
|
28638
|
-
for (const item of input.items) {
|
|
28639
|
-
const clip = clippingAncestor(input.scene, item);
|
|
28640
|
-
const host = clip && groups.get(clip) || world;
|
|
28641
|
-
const container = resolve(item, context).create(item, context);
|
|
28642
|
-
container.alpha = input.scene.opacity(item.id);
|
|
28643
|
-
host.addChild(container);
|
|
28644
|
-
if (isClippingFrame(item)) {
|
|
28645
|
-
const { group } = syncClipGroup(void 0, item);
|
|
28646
|
-
host.addChild(group);
|
|
28647
|
-
groups.set(item.id, group);
|
|
28648
|
-
}
|
|
29728
|
+
/**
|
|
29729
|
+
* Normalize a browser-loadable public HTTP(S) URL. This blocks explicit local
|
|
29730
|
+
* addresses; any future server-side fetcher must additionally validate DNS
|
|
29731
|
+
* resolution to defend against rebinding.
|
|
29732
|
+
*/
|
|
29733
|
+
function normalizeBoardRemoteUrl(value) {
|
|
29734
|
+
if (typeof value !== "string") return void 0;
|
|
29735
|
+
const input = value.trim();
|
|
29736
|
+
if (!input || input.length > 4096) return void 0;
|
|
29737
|
+
try {
|
|
29738
|
+
const url = new URL(input);
|
|
29739
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return void 0;
|
|
29740
|
+
if (url.username || url.password || isBlockedHost(url.hostname)) return;
|
|
29741
|
+
const normalized = url.toString();
|
|
29742
|
+
return normalized.length <= 4096 ? normalized : void 0;
|
|
29743
|
+
} catch {
|
|
29744
|
+
return;
|
|
28649
29745
|
}
|
|
28650
|
-
root.addChild(world);
|
|
28651
|
-
return {
|
|
28652
|
-
root,
|
|
28653
|
-
missingImageKeys: [...missing],
|
|
28654
|
-
destroy: () => root.destroy({ children: true })
|
|
28655
|
-
};
|
|
28656
29746
|
}
|
|
28657
|
-
|
|
28658
|
-
|
|
29747
|
+
string().max(BOARD_REMOTE_URL_MAX_LENGTH).refine((value) => normalizeBoardRemoteUrl(value) !== void 0, { message: "URL must be a public HTTP(S) URL without credentials" });
|
|
29748
|
+
const BOARD_CAMERA_TARGET = "camera";
|
|
29749
|
+
const BoardKeySchema = string().min(1).max(160).regex(/^[^\s/]+$/, "ids must not contain whitespace or '/'");
|
|
29750
|
+
const BoardIdSchema = BoardKeySchema.refine((value) => value !== BOARD_CAMERA_TARGET, "\"camera\" is reserved");
|
|
29751
|
+
const finite = number$1().finite();
|
|
29752
|
+
const nonNegative = finite.nonnegative();
|
|
29753
|
+
const ratio = finite.min(0).max(1);
|
|
29754
|
+
const BoardVec2Schema = object({
|
|
29755
|
+
x: finite,
|
|
29756
|
+
y: finite
|
|
29757
|
+
}).strict();
|
|
29758
|
+
const BoardSizeSchema = object({
|
|
29759
|
+
width: finite.positive(),
|
|
29760
|
+
height: finite.positive()
|
|
29761
|
+
}).strict();
|
|
29762
|
+
const BOARD_COLOR_TOKENS = [
|
|
29763
|
+
"brand",
|
|
29764
|
+
"neutral",
|
|
29765
|
+
"black",
|
|
29766
|
+
"white",
|
|
29767
|
+
"blue",
|
|
29768
|
+
"green",
|
|
29769
|
+
"amber",
|
|
29770
|
+
"violet",
|
|
29771
|
+
"rose"
|
|
29772
|
+
];
|
|
29773
|
+
const CSS_NAMED_COLORS = new Set("transparent aliceblue antiquewhite aqua aquamarine azure beige bisque blanchedalmond blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray grey greenyellow honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato turquoise violet wheat whitesmoke yellow yellowgreen".split(" "));
|
|
29774
|
+
function isBoardColorString(value) {
|
|
29775
|
+
const color = value.trim().toLowerCase();
|
|
29776
|
+
return BOARD_COLOR_TOKENS.includes(color) || CSS_NAMED_COLORS.has(color) || /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(color) || /^(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color)\([^()]*\)$/.test(color);
|
|
29777
|
+
}
|
|
29778
|
+
const colorString = string().max(120).refine(isBoardColorString, "expected a palette token or a CSS color");
|
|
29779
|
+
const BoardColorSchema = union([colorString, object({
|
|
29780
|
+
light: colorString,
|
|
29781
|
+
dark: colorString
|
|
29782
|
+
}).strict()]);
|
|
29783
|
+
const BoardStyleSchema = object({
|
|
29784
|
+
fill: BoardColorSchema.optional(),
|
|
29785
|
+
fillOpacity: ratio.optional(),
|
|
29786
|
+
stroke: BoardColorSchema.optional(),
|
|
29787
|
+
strokeWidth: finite.min(0).max(64).optional(),
|
|
29788
|
+
dash: _enum([
|
|
29789
|
+
"solid",
|
|
29790
|
+
"dashed",
|
|
29791
|
+
"dotted"
|
|
29792
|
+
]).optional(),
|
|
29793
|
+
radius: nonNegative.optional(),
|
|
29794
|
+
trim: ratio.optional()
|
|
29795
|
+
}).strict();
|
|
29796
|
+
function isSafeBoardSourcePath(value) {
|
|
29797
|
+
return value.length > 0 && value.length <= 4096 && !value.startsWith("/") && !/^[a-z][a-z\d+.-]*:/i.test(value.trimStart()) && !value.includes("\\") && value.split("/").every((part) => part.length > 0 && part !== "." && part !== "..");
|
|
29798
|
+
}
|
|
29799
|
+
const srcSchema = string().refine(isSafeBoardSourcePath, "src must be a relative Space file path");
|
|
29800
|
+
function parseBoardMediaSource(src) {
|
|
29801
|
+
if (isSafeBoardSourcePath(src)) return {
|
|
29802
|
+
kind: "file",
|
|
29803
|
+
value: src
|
|
29804
|
+
};
|
|
29805
|
+
return normalizeBoardRemoteUrl(src) ? {
|
|
29806
|
+
kind: "url",
|
|
29807
|
+
value: src
|
|
29808
|
+
} : null;
|
|
29809
|
+
}
|
|
29810
|
+
const BoardMediaSourceSchema = string().max(4096).refine((src) => parseBoardMediaSource(src) !== null, "src must be a public HTTP(S) URL or relative Space file path").describe("Prefer a stable public HTTP(S) URL; relative Space file paths remain supported.");
|
|
29811
|
+
const BoardScriptSchema = object({
|
|
29812
|
+
src: srcSchema.refine((path) => /\.m?js$/.test(path), "script src must be a .js or .mjs module"),
|
|
29813
|
+
params: record(string(), json()).default({}),
|
|
29814
|
+
targets: record(string().min(1).max(80), BoardIdSchema).default({}),
|
|
29815
|
+
seed: string().min(1).max(160).optional(),
|
|
29816
|
+
enabled: boolean().default(true)
|
|
29817
|
+
}).strict();
|
|
29818
|
+
const itemEnvelope = {
|
|
29819
|
+
script: BoardScriptSchema.optional(),
|
|
29820
|
+
parent: BoardIdSchema.optional(),
|
|
29821
|
+
z: finite.optional(),
|
|
29822
|
+
position: BoardVec2Schema.default({
|
|
29823
|
+
x: 0,
|
|
29824
|
+
y: 0
|
|
29825
|
+
}),
|
|
29826
|
+
rotation: finite.default(0),
|
|
29827
|
+
scale: union([finite, BoardVec2Schema]).default(1),
|
|
29828
|
+
origin: BoardVec2Schema.default({
|
|
29829
|
+
x: .5,
|
|
29830
|
+
y: .5
|
|
29831
|
+
}),
|
|
29832
|
+
opacity: ratio.default(1),
|
|
29833
|
+
style: BoardStyleSchema.default({}),
|
|
29834
|
+
locked: boolean().optional(),
|
|
29835
|
+
metadata: record(string(), unknown()).optional()
|
|
29836
|
+
};
|
|
29837
|
+
const boxed = (width, height) => ({
|
|
29838
|
+
...itemEnvelope,
|
|
29839
|
+
size: BoardSizeSchema.default({
|
|
29840
|
+
width,
|
|
29841
|
+
height
|
|
29842
|
+
})
|
|
29843
|
+
});
|
|
29844
|
+
const unsized = { ...itemEnvelope };
|
|
29845
|
+
const fontSize = finite.min(2).max(512);
|
|
29846
|
+
const BOARD_TEXT_ALIGNS = [
|
|
29847
|
+
"left",
|
|
29848
|
+
"center",
|
|
29849
|
+
"right"
|
|
29850
|
+
];
|
|
29851
|
+
const BOARD_SHAPE_GEOMETRIES = [
|
|
29852
|
+
"rectangle",
|
|
29853
|
+
"rounded",
|
|
29854
|
+
"ellipse",
|
|
29855
|
+
"diamond",
|
|
29856
|
+
"triangle",
|
|
29857
|
+
"path"
|
|
29858
|
+
];
|
|
29859
|
+
object({
|
|
29860
|
+
...boxed(480, 320),
|
|
29861
|
+
type: literal("frame"),
|
|
29862
|
+
props: object({
|
|
29863
|
+
label: string().max(280).default(""),
|
|
29864
|
+
decoration: _enum(["frame", "none"]).optional(),
|
|
29865
|
+
clip: boolean().default(true)
|
|
29866
|
+
}).strict().prefault({})
|
|
29867
|
+
}).strict();
|
|
29868
|
+
object({
|
|
29869
|
+
...unsized,
|
|
29870
|
+
type: literal("text"),
|
|
29871
|
+
props: object({
|
|
29872
|
+
text: string().default(""),
|
|
29873
|
+
fontSize: fontSize.default(24),
|
|
29874
|
+
fontWeight: finite.min(100).max(900).default(500),
|
|
29875
|
+
font: string().min(1).max(120).default("sans"),
|
|
29876
|
+
align: _enum(BOARD_TEXT_ALIGNS).default("left"),
|
|
29877
|
+
lineHeight: finite.min(.5).max(4).default(4 / 3),
|
|
29878
|
+
width: finite.positive().optional(),
|
|
29879
|
+
reveal: ratio.default(1),
|
|
29880
|
+
caret: boolean().default(false)
|
|
29881
|
+
}).strict().prefault({})
|
|
29882
|
+
}).strict();
|
|
29883
|
+
object({
|
|
29884
|
+
...boxed(240, 160),
|
|
29885
|
+
type: literal("shape"),
|
|
29886
|
+
props: object({
|
|
29887
|
+
geometry: _enum(BOARD_SHAPE_GEOMETRIES).default("rectangle"),
|
|
29888
|
+
path: string().min(1).max(2e5).optional(),
|
|
29889
|
+
text: string().default(""),
|
|
29890
|
+
fontSize: fontSize.default(20),
|
|
29891
|
+
align: _enum(BOARD_TEXT_ALIGNS).default("center")
|
|
29892
|
+
}).strict().refine((props) => props.geometry !== "path" || Boolean(props.path), {
|
|
29893
|
+
message: "path geometry requires props.path",
|
|
29894
|
+
path: ["path"]
|
|
29895
|
+
}).prefault({})
|
|
29896
|
+
}).strict();
|
|
29897
|
+
const BoardDrawPointSchema = object({
|
|
29898
|
+
x: finite,
|
|
29899
|
+
y: finite,
|
|
29900
|
+
p: ratio.default(.5)
|
|
29901
|
+
}).strict();
|
|
29902
|
+
object({
|
|
29903
|
+
...unsized,
|
|
29904
|
+
type: literal("draw"),
|
|
29905
|
+
props: object({ points: array(BoardDrawPointSchema).min(1).max(1e5) }).strict()
|
|
29906
|
+
}).strict();
|
|
29907
|
+
const BoardArrowEndSchema = union([BoardVec2Schema, object({
|
|
29908
|
+
item: BoardIdSchema,
|
|
29909
|
+
anchor: union([
|
|
29910
|
+
literal("auto"),
|
|
29911
|
+
object({
|
|
29912
|
+
side: _enum([
|
|
29913
|
+
"top",
|
|
29914
|
+
"right",
|
|
29915
|
+
"bottom",
|
|
29916
|
+
"left"
|
|
29917
|
+
]),
|
|
29918
|
+
offset: ratio.default(.5)
|
|
29919
|
+
}).strict(),
|
|
29920
|
+
object({
|
|
29921
|
+
x: ratio,
|
|
29922
|
+
y: ratio
|
|
29923
|
+
}).strict()
|
|
29924
|
+
]).default("auto"),
|
|
29925
|
+
port: string().min(1).max(120).optional()
|
|
29926
|
+
}).strict()]);
|
|
29927
|
+
const BOARD_ARROW_ROUTES = [
|
|
29928
|
+
"straight",
|
|
29929
|
+
"curve",
|
|
29930
|
+
"orthogonal"
|
|
29931
|
+
];
|
|
29932
|
+
object({
|
|
29933
|
+
...unsized,
|
|
29934
|
+
type: literal("arrow"),
|
|
29935
|
+
props: object({
|
|
29936
|
+
start: BoardArrowEndSchema,
|
|
29937
|
+
end: BoardArrowEndSchema,
|
|
29938
|
+
route: _enum(BOARD_ARROW_ROUTES).default("straight"),
|
|
29939
|
+
bend: finite.min(-.85).max(.85).default(0),
|
|
29940
|
+
waypoints: array(BoardVec2Schema).max(64).default([]),
|
|
29941
|
+
arrowStart: boolean().default(false),
|
|
29942
|
+
arrowEnd: boolean().default(true),
|
|
29943
|
+
label: string().max(280).default(""),
|
|
29944
|
+
fontSize: fontSize.default(14),
|
|
29945
|
+
relation: string().max(64).regex(/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/, "relation must be a lowercase slug").optional()
|
|
29946
|
+
}).strict()
|
|
29947
|
+
}).strict();
|
|
29948
|
+
const fileProps = {
|
|
29949
|
+
src: BoardMediaSourceSchema,
|
|
29950
|
+
snapshot: object({
|
|
29951
|
+
title: string().optional(),
|
|
29952
|
+
mimeType: string().optional(),
|
|
29953
|
+
size: nonNegative.optional(),
|
|
29954
|
+
mtimeMs: nonNegative.optional(),
|
|
29955
|
+
naturalWidth: finite.positive().optional(),
|
|
29956
|
+
naturalHeight: finite.positive().optional(),
|
|
29957
|
+
durationMs: nonNegative.optional(),
|
|
29958
|
+
excerpt: string().optional(),
|
|
29959
|
+
coverPath: string().optional(),
|
|
29960
|
+
coverUrl: string().optional()
|
|
29961
|
+
}).strict().optional()
|
|
29962
|
+
};
|
|
29963
|
+
const timedFileProps = {
|
|
29964
|
+
...fileProps,
|
|
29965
|
+
time: nonNegative.optional()
|
|
29966
|
+
};
|
|
29967
|
+
const BoardCropSchema = object({
|
|
29968
|
+
x: ratio,
|
|
29969
|
+
y: ratio,
|
|
29970
|
+
w: ratio,
|
|
29971
|
+
h: ratio
|
|
29972
|
+
}).strict();
|
|
29973
|
+
object({
|
|
29974
|
+
...boxed(640, 360),
|
|
29975
|
+
type: literal("image"),
|
|
29976
|
+
props: object({
|
|
29977
|
+
...fileProps,
|
|
29978
|
+
crop: BoardCropSchema.optional()
|
|
29979
|
+
}).strict()
|
|
29980
|
+
}).strict();
|
|
29981
|
+
object({
|
|
29982
|
+
...boxed(640, 360),
|
|
29983
|
+
type: literal("video"),
|
|
29984
|
+
props: object(timedFileProps).strict()
|
|
29985
|
+
}).strict();
|
|
29986
|
+
object({
|
|
29987
|
+
...boxed(480, 96),
|
|
29988
|
+
type: literal("audio"),
|
|
29989
|
+
props: object(timedFileProps).strict()
|
|
29990
|
+
}).strict();
|
|
29991
|
+
object({
|
|
29992
|
+
...boxed(360, 220),
|
|
29993
|
+
type: literal("file"),
|
|
29994
|
+
props: object(fileProps).strict()
|
|
29995
|
+
}).strict();
|
|
29996
|
+
const httpsUrl = string().max(4096).url();
|
|
29997
|
+
const artifactBase = {
|
|
29998
|
+
id: string().min(1).max(240),
|
|
29999
|
+
title: string().max(240).optional(),
|
|
30000
|
+
url: httpsUrl,
|
|
30001
|
+
mimeType: string().max(160).optional()
|
|
30002
|
+
};
|
|
30003
|
+
const BoardTaskArtifactSchema = discriminatedUnion("type", [
|
|
30004
|
+
object({
|
|
30005
|
+
...artifactBase,
|
|
30006
|
+
type: literal("image"),
|
|
30007
|
+
naturalWidth: finite.positive().optional(),
|
|
30008
|
+
naturalHeight: finite.positive().optional()
|
|
30009
|
+
}).strict(),
|
|
30010
|
+
object({
|
|
30011
|
+
...artifactBase,
|
|
30012
|
+
type: literal("video"),
|
|
30013
|
+
previewUrl: httpsUrl.optional(),
|
|
30014
|
+
durationMs: number$1().int().positive().optional(),
|
|
30015
|
+
naturalWidth: finite.positive().optional(),
|
|
30016
|
+
naturalHeight: finite.positive().optional()
|
|
30017
|
+
}).strict(),
|
|
30018
|
+
object({
|
|
30019
|
+
...artifactBase,
|
|
30020
|
+
type: literal("audio"),
|
|
30021
|
+
previewUrl: httpsUrl.optional(),
|
|
30022
|
+
durationMs: number$1().int().positive().optional()
|
|
30023
|
+
}).strict(),
|
|
30024
|
+
object({
|
|
30025
|
+
id: string().min(1).max(240),
|
|
30026
|
+
type: literal("text"),
|
|
30027
|
+
title: string().max(240).optional(),
|
|
30028
|
+
textExcerpt: string().min(1).max(480)
|
|
30029
|
+
}).strict()
|
|
30030
|
+
]);
|
|
30031
|
+
const BoardTaskSnapshotSchema = object({
|
|
30032
|
+
taskType: string().min(1).max(120),
|
|
30033
|
+
status: _enum([
|
|
30034
|
+
"pending",
|
|
30035
|
+
"running",
|
|
30036
|
+
"completed",
|
|
30037
|
+
"failed"
|
|
30038
|
+
]),
|
|
30039
|
+
title: string().min(1).max(240),
|
|
30040
|
+
model: string().max(160).optional(),
|
|
30041
|
+
promptExcerpt: string().max(480).optional(),
|
|
30042
|
+
artifactCount: number$1().int().nonnegative(),
|
|
30043
|
+
artifacts: array(BoardTaskArtifactSchema).max(6).default([]),
|
|
30044
|
+
updatedAt: string().optional()
|
|
30045
|
+
}).strict();
|
|
30046
|
+
object({
|
|
30047
|
+
...boxed(420, 240),
|
|
30048
|
+
type: literal("task"),
|
|
30049
|
+
props: object({
|
|
30050
|
+
taskRunId: string().min(1).max(160),
|
|
30051
|
+
snapshot: BoardTaskSnapshotSchema
|
|
30052
|
+
}).strict()
|
|
30053
|
+
}).strict();
|
|
30054
|
+
const BOARD_EFFECT_KINDS = [
|
|
30055
|
+
"particles",
|
|
30056
|
+
"trail",
|
|
30057
|
+
"impact",
|
|
30058
|
+
"flash",
|
|
30059
|
+
"glow"
|
|
30060
|
+
];
|
|
30061
|
+
object({
|
|
30062
|
+
...boxed(240, 240),
|
|
30063
|
+
type: literal("effect"),
|
|
30064
|
+
props: object({
|
|
30065
|
+
kind: _enum(BOARD_EFFECT_KINDS),
|
|
30066
|
+
rate: nonNegative.max(2e4).default(40),
|
|
30067
|
+
life: finite.positive().max(6e4).default(1200),
|
|
30068
|
+
particleSize: finite.positive().max(512).default(6),
|
|
30069
|
+
speed: nonNegative.max(1e4).default(120),
|
|
30070
|
+
direction: finite.default(-90),
|
|
30071
|
+
spread: finite.min(0).max(360).default(360),
|
|
30072
|
+
gravity: finite.default(0),
|
|
30073
|
+
intensity: nonNegative.max(10).default(1),
|
|
30074
|
+
follow: BoardIdSchema.optional(),
|
|
30075
|
+
seed: string().min(1).max(160).optional()
|
|
30076
|
+
}).strict()
|
|
30077
|
+
}).strict();
|
|
30078
|
+
object({
|
|
30079
|
+
...boxed(480, 320),
|
|
30080
|
+
type: literal("custom"),
|
|
30081
|
+
script: BoardScriptSchema,
|
|
30082
|
+
props: object({}).strict().default({})
|
|
30083
|
+
}).strict();
|
|
30084
|
+
const BOARD_EXTENSION_TYPE_PATTERN = /^[a-z][a-z0-9]*(?:\.[a-z0-9-]+)+$/;
|
|
30085
|
+
object({
|
|
30086
|
+
...itemEnvelope,
|
|
30087
|
+
size: BoardSizeSchema.default({
|
|
30088
|
+
width: 240,
|
|
30089
|
+
height: 160
|
|
30090
|
+
}),
|
|
30091
|
+
type: string().regex(BOARD_EXTENSION_TYPE_PATTERN),
|
|
30092
|
+
props: record(string(), unknown()).default({})
|
|
30093
|
+
}).strict();
|
|
30094
|
+
const CSS_EASE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
30095
|
+
"linear",
|
|
30096
|
+
"ease",
|
|
30097
|
+
"ease-in",
|
|
30098
|
+
"ease-out",
|
|
30099
|
+
"ease-in-out",
|
|
30100
|
+
"step-start",
|
|
30101
|
+
"step-end"
|
|
30102
|
+
]);
|
|
30103
|
+
function isBoardEase(value) {
|
|
30104
|
+
const ease = value.trim();
|
|
30105
|
+
if (CSS_EASE_KEYWORDS.has(ease)) return true;
|
|
30106
|
+
const bezier = /^cubic-bezier\(\s*([^,()]+),\s*([^,()]+),\s*([^,()]+),\s*([^,()]+)\)$/.exec(ease);
|
|
30107
|
+
if (bezier) {
|
|
30108
|
+
const [x1, , x2] = bezier.slice(1).map(Number);
|
|
30109
|
+
return bezier.slice(1).every((part) => Number.isFinite(Number(part))) && x1 >= 0 && x1 <= 1 && x2 >= 0 && x2 <= 1;
|
|
30110
|
+
}
|
|
30111
|
+
return /^steps\(\s*[1-9]\d*\s*(?:,\s*(?:start|end|jump-start|jump-end|jump-none|jump-both)\s*)?\)$/.test(ease);
|
|
30112
|
+
}
|
|
30113
|
+
const BoardEaseSchema = string().max(120).refine(isBoardEase, "expected a CSS easing: linear, ease-in-out, cubic-bezier(…) or steps(…)");
|
|
30114
|
+
const BoardKeyframeSchema = object({
|
|
30115
|
+
at: nonNegative,
|
|
30116
|
+
value: unknown(),
|
|
30117
|
+
ease: BoardEaseSchema.optional()
|
|
30118
|
+
}).strict();
|
|
30119
|
+
const BoardTrackSchema = object({
|
|
30120
|
+
target: string().min(1).max(160),
|
|
30121
|
+
property: string().min(1).max(200),
|
|
30122
|
+
keyframes: array(BoardKeyframeSchema).min(1).max(1e5),
|
|
30123
|
+
composite: _enum(["replace", "add"]).default("replace"),
|
|
30124
|
+
interpolation: _enum([
|
|
30125
|
+
"auto",
|
|
30126
|
+
"step",
|
|
30127
|
+
"spline"
|
|
30128
|
+
]).default("auto"),
|
|
30129
|
+
orient: boolean().optional()
|
|
30130
|
+
}).strict().superRefine((track, context) => {
|
|
30131
|
+
for (let index = 1; index < track.keyframes.length; index += 1) if ((track.keyframes[index]?.at ?? 0) < (track.keyframes[index - 1]?.at ?? 0)) context.addIssue({
|
|
30132
|
+
code: "custom",
|
|
30133
|
+
message: "keyframes must be ordered by at",
|
|
30134
|
+
path: [
|
|
30135
|
+
"keyframes",
|
|
30136
|
+
index,
|
|
30137
|
+
"at"
|
|
30138
|
+
]
|
|
30139
|
+
});
|
|
30140
|
+
});
|
|
30141
|
+
const BoardMarkerSchema = object({
|
|
30142
|
+
at: nonNegative,
|
|
30143
|
+
label: string().max(280).optional(),
|
|
30144
|
+
pause: boolean().optional()
|
|
30145
|
+
}).strict();
|
|
30146
|
+
object({
|
|
30147
|
+
name: string().max(280).optional(),
|
|
30148
|
+
duration: finite.positive().max(864e5),
|
|
30149
|
+
play: _enum([
|
|
30150
|
+
"manual",
|
|
30151
|
+
"auto",
|
|
30152
|
+
"always"
|
|
30153
|
+
]).default("manual"),
|
|
30154
|
+
delay: nonNegative.default(0),
|
|
30155
|
+
loop: boolean().default(false),
|
|
30156
|
+
end: _enum(["hold", "reset"]).default("hold"),
|
|
30157
|
+
markers: array(BoardMarkerSchema).max(1e4).default([])
|
|
30158
|
+
}).strict().extend({ tracks: record(BoardKeySchema, BoardTrackSchema).default({}) }).strict();
|
|
30159
|
+
const BOARD_ENTER_PRESETS = [
|
|
30160
|
+
"fade-in",
|
|
30161
|
+
"rise",
|
|
30162
|
+
"drop",
|
|
30163
|
+
"pop",
|
|
30164
|
+
"deal"
|
|
30165
|
+
];
|
|
30166
|
+
const BoardBackgroundSchema = object({
|
|
30167
|
+
kind: _enum([
|
|
30168
|
+
"solid",
|
|
30169
|
+
"dots",
|
|
30170
|
+
"grid",
|
|
30171
|
+
"image"
|
|
30172
|
+
]).default("dots"),
|
|
30173
|
+
color: BoardColorSchema.optional(),
|
|
30174
|
+
imageUrl: string().url().max(4096).optional(),
|
|
30175
|
+
fit: _enum([
|
|
30176
|
+
"cover",
|
|
30177
|
+
"contain",
|
|
30178
|
+
"repeat"
|
|
30179
|
+
]).optional(),
|
|
30180
|
+
opacity: ratio.optional()
|
|
30181
|
+
}).strict();
|
|
30182
|
+
object({
|
|
30183
|
+
title: string().min(1).max(255).optional(),
|
|
30184
|
+
background: BoardBackgroundSchema.default({ kind: "dots" }),
|
|
30185
|
+
grid: object({
|
|
30186
|
+
visible: boolean().default(false),
|
|
30187
|
+
size: finite.min(4).max(512).default(24)
|
|
30188
|
+
}).strict().optional(),
|
|
30189
|
+
enter: object({
|
|
30190
|
+
preset: _enum(BOARD_ENTER_PRESETS),
|
|
30191
|
+
duration: finite.positive().max(1e4).optional()
|
|
30192
|
+
}).strict().optional()
|
|
30193
|
+
}).strict();
|
|
30194
|
+
const patchObject = record(string(), unknown());
|
|
30195
|
+
object({
|
|
30196
|
+
board: patchObject.nullable().optional(),
|
|
30197
|
+
items: record(BoardIdSchema, patchObject.nullable()).optional(),
|
|
30198
|
+
animations: record(BoardIdSchema, patchObject.and(object({ tracks: record(BoardKeySchema, patchObject.nullable()).optional() })).nullable()).optional()
|
|
30199
|
+
}).strict();
|
|
30200
|
+
function createBoardScriptModuleReader(read, timeoutMs = 15e3) {
|
|
30201
|
+
const cache = /* @__PURE__ */ new Map();
|
|
30202
|
+
const queue = [];
|
|
30203
|
+
let active = 0;
|
|
30204
|
+
let disposed = false;
|
|
30205
|
+
function pump() {
|
|
30206
|
+
while (active < 4 && queue.length) queue.shift()?.();
|
|
30207
|
+
}
|
|
30208
|
+
return {
|
|
30209
|
+
read(path) {
|
|
30210
|
+
if (disposed) return Promise.reject(/* @__PURE__ */ new Error("Module reader is disposed."));
|
|
30211
|
+
const cached = cache.get(path);
|
|
30212
|
+
if (cached) return cached;
|
|
30213
|
+
const promise = new Promise((resolve, reject) => {
|
|
30214
|
+
queue.push(() => {
|
|
30215
|
+
if (disposed) {
|
|
30216
|
+
reject(/* @__PURE__ */ new Error("Module reader is disposed."));
|
|
30217
|
+
return;
|
|
30218
|
+
}
|
|
30219
|
+
active++;
|
|
30220
|
+
let timer;
|
|
30221
|
+
const timeout = new Promise((_resolve, rejectRead) => {
|
|
30222
|
+
timer = setTimeout(() => rejectRead(/* @__PURE__ */ new Error(`Module read timed out: ${path}`)), timeoutMs);
|
|
30223
|
+
});
|
|
30224
|
+
Promise.race([Promise.resolve().then(() => read(path)), timeout]).then(resolve, reject).finally(() => {
|
|
30225
|
+
clearTimeout(timer);
|
|
30226
|
+
active--;
|
|
30227
|
+
pump();
|
|
30228
|
+
});
|
|
30229
|
+
});
|
|
30230
|
+
pump();
|
|
30231
|
+
});
|
|
30232
|
+
cache.set(path, promise);
|
|
30233
|
+
promise.catch(() => {
|
|
30234
|
+
if (cache.get(path) === promise) cache.delete(path);
|
|
30235
|
+
});
|
|
30236
|
+
return promise;
|
|
30237
|
+
},
|
|
30238
|
+
invalidate(path) {
|
|
30239
|
+
if (path) cache.delete(path);
|
|
30240
|
+
else cache.clear();
|
|
30241
|
+
},
|
|
30242
|
+
destroy() {
|
|
30243
|
+
disposed = true;
|
|
30244
|
+
cache.clear();
|
|
30245
|
+
for (const job of queue.splice(0)) job();
|
|
30246
|
+
}
|
|
30247
|
+
};
|
|
30248
|
+
}
|
|
30249
|
+
function resolveScriptImport(from, specifier) {
|
|
30250
|
+
if (!specifier.startsWith("./") && !specifier.startsWith("../")) throw new Error(`Only relative module imports are supported: ${specifier}`);
|
|
30251
|
+
const parts = from.split("/").slice(0, -1);
|
|
30252
|
+
for (const part of specifier.split("/")) {
|
|
30253
|
+
if (part === ".") continue;
|
|
30254
|
+
if (part === "..") {
|
|
30255
|
+
if (!parts.length) throw new Error("Module import leaves the Space.");
|
|
30256
|
+
parts.pop();
|
|
30257
|
+
} else parts.push(part);
|
|
30258
|
+
}
|
|
30259
|
+
const path = parts.join("/");
|
|
30260
|
+
if (!isSafeBoardSourcePath(path) || !/\.m?js$/.test(path)) throw new Error(`Invalid module path: ${path}`);
|
|
30261
|
+
return path;
|
|
30262
|
+
}
|
|
30263
|
+
async function loadBoardScriptSources(src, read, timeoutMs = 15e3) {
|
|
30264
|
+
if (!isSafeBoardSourcePath(src) || !/\.m?js$/.test(src)) throw new Error("Invalid script entry path.");
|
|
30265
|
+
const { init, parse } = await import("./board-kernel/lexer.js");
|
|
30266
|
+
await init;
|
|
30267
|
+
const sources = {};
|
|
30268
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
30269
|
+
let bytes = 0;
|
|
30270
|
+
async function visit(path) {
|
|
30271
|
+
if (visiting.has(path)) throw new Error(`Circular module dependency: ${path}`);
|
|
30272
|
+
if (sources[path]) return;
|
|
30273
|
+
if (Object.keys(sources).length + visiting.size >= 64) throw new Error("A script may load at most 64 modules.");
|
|
30274
|
+
visiting.add(path);
|
|
30275
|
+
let timer;
|
|
30276
|
+
const code = await Promise.race([Promise.resolve().then(() => read(path)), new Promise((_resolve, reject) => {
|
|
30277
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Module read timed out: ${path}`)), timeoutMs);
|
|
30278
|
+
})]).finally(() => clearTimeout(timer));
|
|
30279
|
+
bytes += new TextEncoder().encode(code).byteLength;
|
|
30280
|
+
if (bytes > 2e6) throw new Error("Script modules exceed the 2 MB limit.");
|
|
30281
|
+
const [imports] = parse(code, path);
|
|
30282
|
+
const resolved = [];
|
|
30283
|
+
for (const entry of imports) {
|
|
30284
|
+
if (entry.d === -2) throw new Error("import.meta is not available in Board scripts.");
|
|
30285
|
+
if (entry.n === void 0) throw new Error("Dynamic imports must use a literal relative path.");
|
|
30286
|
+
const dependency = resolveScriptImport(path, entry.n);
|
|
30287
|
+
await visit(dependency);
|
|
30288
|
+
resolved.push({
|
|
30289
|
+
start: entry.s,
|
|
30290
|
+
end: entry.e,
|
|
30291
|
+
path: dependency,
|
|
30292
|
+
dynamic: entry.d >= 0
|
|
30293
|
+
});
|
|
30294
|
+
}
|
|
30295
|
+
sources[path] = {
|
|
30296
|
+
code,
|
|
30297
|
+
imports: resolved
|
|
30298
|
+
};
|
|
30299
|
+
visiting.delete(path);
|
|
30300
|
+
}
|
|
30301
|
+
await visit(src);
|
|
30302
|
+
return sources;
|
|
30303
|
+
}
|
|
30304
|
+
//#endregion
|
|
30305
|
+
//#region ../sdk/src/board/render/script-runtime.ts
|
|
30306
|
+
/** Self-contained so browser workers and Node execute exactly the same kernel. */
|
|
30307
|
+
function createScriptRuntime(module, initial, host) {
|
|
30308
|
+
if ("mount" in module) throw new Error("Board scripts do not support DOM mount(). Use draw() or native scene items.");
|
|
30309
|
+
let input = initial;
|
|
30310
|
+
let disposed = false;
|
|
30311
|
+
let revision = 0;
|
|
30312
|
+
let randomState = 2166136261;
|
|
30313
|
+
for (const char of initial.item.script?.seed ?? initial.id) randomState = Math.imul(randomState ^ char.charCodeAt(0), 16777619) >>> 0;
|
|
30314
|
+
const parameterDefaults = Object.fromEntries(Object.entries(module.parameters ?? {}).filter(([, spec]) => spec.default !== void 0).map(([key, spec]) => [key, spec.default]));
|
|
30315
|
+
let params = {
|
|
30316
|
+
...parameterDefaults,
|
|
30317
|
+
...initial.item.script?.params
|
|
30318
|
+
};
|
|
30319
|
+
const state = structuredClone(initial.snapshot?.state ?? {});
|
|
30320
|
+
if (initial.snapshot) randomState = initial.snapshot.random;
|
|
30321
|
+
let time = initial.snapshot?.time ?? 0;
|
|
30322
|
+
let started = Boolean(initial.snapshot);
|
|
30323
|
+
const stepMs = 1e3 / 60;
|
|
30324
|
+
let frame = {
|
|
30325
|
+
t: time,
|
|
30326
|
+
dt: 0,
|
|
30327
|
+
width: initial.width,
|
|
30328
|
+
height: initial.height,
|
|
30329
|
+
seed: initial.item.script?.seed ?? initial.id,
|
|
30330
|
+
params,
|
|
30331
|
+
state
|
|
30332
|
+
};
|
|
30333
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
30334
|
+
const frames = /* @__PURE__ */ new Set();
|
|
30335
|
+
const cleanups = [];
|
|
30336
|
+
const effects = structuredClone(initial.snapshot?.effects);
|
|
30337
|
+
let patches = effects?.patches ?? {};
|
|
30338
|
+
let items = effects?.items ?? {};
|
|
30339
|
+
let space = effects?.space ?? "world";
|
|
30340
|
+
let hits = effects?.hits ?? {};
|
|
30341
|
+
const invalidate = () => {
|
|
30342
|
+
if (disposed) return;
|
|
30343
|
+
revision++;
|
|
30344
|
+
host.invalidate();
|
|
30345
|
+
};
|
|
30346
|
+
const present = (id, patch) => {
|
|
30347
|
+
if (patch === null) delete patches[id];
|
|
30348
|
+
else patches[id] = structuredClone(patch);
|
|
30349
|
+
invalidate();
|
|
30350
|
+
};
|
|
30351
|
+
const context = {
|
|
30352
|
+
self: {
|
|
30353
|
+
id: initial.id,
|
|
30354
|
+
get base() {
|
|
30355
|
+
return input.item;
|
|
30356
|
+
},
|
|
30357
|
+
present: (patch) => present(initial.id, patch)
|
|
30358
|
+
},
|
|
30359
|
+
get params() {
|
|
30360
|
+
return params;
|
|
30361
|
+
},
|
|
30362
|
+
get targets() {
|
|
30363
|
+
return input.item.script?.targets ?? {};
|
|
30364
|
+
},
|
|
30365
|
+
async emit(target, type, data) {
|
|
30366
|
+
await host.call("board.emit", {
|
|
30367
|
+
target,
|
|
30368
|
+
type,
|
|
30369
|
+
data
|
|
30370
|
+
});
|
|
30371
|
+
},
|
|
30372
|
+
state,
|
|
30373
|
+
get frame() {
|
|
30374
|
+
return frame;
|
|
30375
|
+
},
|
|
30376
|
+
random() {
|
|
30377
|
+
randomState = Math.imul(randomState, 1664525) + 1013904223 >>> 0;
|
|
30378
|
+
return randomState / 4294967296;
|
|
30379
|
+
},
|
|
30380
|
+
on(type, handler) {
|
|
30381
|
+
let listeners = handlers.get(type);
|
|
30382
|
+
if (!listeners) {
|
|
30383
|
+
listeners = /* @__PURE__ */ new Set();
|
|
30384
|
+
handlers.set(type, listeners);
|
|
30385
|
+
}
|
|
30386
|
+
listeners.add(handler);
|
|
30387
|
+
invalidate();
|
|
30388
|
+
return () => {
|
|
30389
|
+
listeners.delete(handler);
|
|
30390
|
+
invalidate();
|
|
30391
|
+
};
|
|
30392
|
+
},
|
|
30393
|
+
onFrame(handler) {
|
|
30394
|
+
frames.add(handler);
|
|
30395
|
+
invalidate();
|
|
30396
|
+
return () => {
|
|
30397
|
+
frames.delete(handler);
|
|
30398
|
+
invalidate();
|
|
30399
|
+
};
|
|
30400
|
+
},
|
|
30401
|
+
onDispose: (handler) => cleanups.push(handler),
|
|
30402
|
+
invalidate,
|
|
30403
|
+
get: (id) => input.items[id],
|
|
30404
|
+
present,
|
|
30405
|
+
scene(next, options) {
|
|
30406
|
+
items = structuredClone(next);
|
|
30407
|
+
space = options?.space ?? "world";
|
|
30408
|
+
invalidate();
|
|
30409
|
+
},
|
|
30410
|
+
hitRegions(next) {
|
|
30411
|
+
hits = structuredClone(next);
|
|
30412
|
+
invalidate();
|
|
30413
|
+
},
|
|
30414
|
+
async commit(patch) {
|
|
30415
|
+
await host.call("board.commit", patch);
|
|
30416
|
+
},
|
|
30417
|
+
capabilities: {
|
|
30418
|
+
has: (name) => (input.capabilities ?? []).includes(name),
|
|
30419
|
+
call: (name, value) => host.call(name, value)
|
|
30420
|
+
}
|
|
30421
|
+
};
|
|
30422
|
+
const setup = module.default?.(context);
|
|
30423
|
+
if (typeof setup === "function") cleanups.push(setup);
|
|
30424
|
+
return {
|
|
30425
|
+
get animated() {
|
|
30426
|
+
return frames.size > 0 || module.animated === true;
|
|
30427
|
+
},
|
|
30428
|
+
update(next) {
|
|
30429
|
+
input = next;
|
|
30430
|
+
params = {
|
|
30431
|
+
...parameterDefaults,
|
|
30432
|
+
...next.item.script?.params
|
|
30433
|
+
};
|
|
30434
|
+
},
|
|
30435
|
+
step(t) {
|
|
30436
|
+
if (disposed) return;
|
|
30437
|
+
if (!Number.isFinite(t) || t < time - .001) throw new Error("Reset the instance before seeking backwards.");
|
|
30438
|
+
const update = (at, dt) => {
|
|
30439
|
+
frame = {
|
|
30440
|
+
...frame,
|
|
30441
|
+
t: at,
|
|
30442
|
+
dt,
|
|
30443
|
+
width: input.width,
|
|
30444
|
+
height: input.height,
|
|
30445
|
+
params: context.params
|
|
30446
|
+
};
|
|
30447
|
+
for (const handler of frames) handler(frame);
|
|
30448
|
+
};
|
|
30449
|
+
if (!started) {
|
|
30450
|
+
started = true;
|
|
30451
|
+
update(time, 0);
|
|
30452
|
+
}
|
|
30453
|
+
if (frames.size) while (time + stepMs <= t + 1e-6) {
|
|
30454
|
+
time += stepMs;
|
|
30455
|
+
update(time, stepMs);
|
|
30456
|
+
}
|
|
30457
|
+
else time = t;
|
|
30458
|
+
frame = {
|
|
30459
|
+
...frame,
|
|
30460
|
+
t,
|
|
30461
|
+
dt: 0,
|
|
30462
|
+
width: input.width,
|
|
30463
|
+
height: input.height,
|
|
30464
|
+
params: context.params
|
|
30465
|
+
};
|
|
30466
|
+
},
|
|
30467
|
+
event(event) {
|
|
30468
|
+
if (disposed) return;
|
|
30469
|
+
const listeners = handlers.get(event.type);
|
|
30470
|
+
if (!listeners?.size) return;
|
|
30471
|
+
for (const handler of listeners) handler(event);
|
|
30472
|
+
invalidate();
|
|
30473
|
+
},
|
|
30474
|
+
draw(canvas) {
|
|
30475
|
+
const result = module.draw?.(canvas, frame);
|
|
30476
|
+
if (result && typeof result.then === "function") throw new Error("draw() must be synchronous.");
|
|
30477
|
+
},
|
|
30478
|
+
output() {
|
|
30479
|
+
return {
|
|
30480
|
+
parameters: module.parameters,
|
|
30481
|
+
space,
|
|
30482
|
+
patches,
|
|
30483
|
+
items,
|
|
30484
|
+
hits,
|
|
30485
|
+
listens: [...handlers].filter(([, callbacks]) => callbacks.size).map(([type]) => type),
|
|
30486
|
+
animated: this.animated,
|
|
30487
|
+
updating: frames.size > 0,
|
|
30488
|
+
version: revision
|
|
30489
|
+
};
|
|
30490
|
+
},
|
|
30491
|
+
snapshot(includeEffects = true, clone = true) {
|
|
30492
|
+
const snapshot = {
|
|
30493
|
+
state,
|
|
30494
|
+
time,
|
|
30495
|
+
random: randomState,
|
|
30496
|
+
effects: includeEffects ? {
|
|
30497
|
+
patches,
|
|
30498
|
+
items,
|
|
30499
|
+
hits,
|
|
30500
|
+
space
|
|
30501
|
+
} : void 0
|
|
30502
|
+
};
|
|
30503
|
+
return clone ? structuredClone(snapshot) : snapshot;
|
|
30504
|
+
},
|
|
30505
|
+
destroy() {
|
|
30506
|
+
disposed = true;
|
|
30507
|
+
for (const cleanup of cleanups.reverse()) cleanup();
|
|
30508
|
+
handlers.clear();
|
|
30509
|
+
frames.clear();
|
|
30510
|
+
patches = {};
|
|
30511
|
+
items = {};
|
|
30512
|
+
hits = {};
|
|
30513
|
+
}
|
|
30514
|
+
};
|
|
30515
|
+
}
|
|
30516
|
+
//#endregion
|
|
30517
|
+
//#region ../sdk/src/board/export/scene.ts
|
|
30518
|
+
function buildContext(input) {
|
|
30519
|
+
const missing = /* @__PURE__ */ new Set();
|
|
30520
|
+
const textures = input.textures ?? /* @__PURE__ */ new Map();
|
|
30521
|
+
return {
|
|
30522
|
+
context: {
|
|
30523
|
+
settings: input.settings,
|
|
30524
|
+
scene: input.scene,
|
|
30525
|
+
time: input.time ?? 0,
|
|
30526
|
+
frameAt: input.frameAt,
|
|
30527
|
+
selectedIds: /* @__PURE__ */ new Set(),
|
|
30528
|
+
hoveredId: null,
|
|
30529
|
+
resizingIds: /* @__PURE__ */ new Set(),
|
|
30530
|
+
palette: {
|
|
30531
|
+
...defaultBoardPalette(input.colorScheme),
|
|
30532
|
+
...input.palette
|
|
30533
|
+
},
|
|
30534
|
+
colors: input.colors ?? buildFallbackShapeColors(input.colorScheme),
|
|
30535
|
+
colorScheme: input.colorScheme,
|
|
30536
|
+
rendererType: "canvas",
|
|
30537
|
+
zoom: input.scale,
|
|
30538
|
+
scripts: input.scripts,
|
|
30539
|
+
assetKey: input.assetKey ?? imageAssetKey,
|
|
30540
|
+
getTexture: (key) => textures.get(key) ?? null,
|
|
30541
|
+
hasError: (key) => {
|
|
30542
|
+
if (!textures.has(key)) missing.add(key);
|
|
30543
|
+
return false;
|
|
30544
|
+
},
|
|
30545
|
+
fileState: () => "ok",
|
|
30546
|
+
acquireTexture: () => {},
|
|
30547
|
+
releaseTexture: () => {}
|
|
30548
|
+
},
|
|
30549
|
+
missing
|
|
30550
|
+
};
|
|
30551
|
+
}
|
|
30552
|
+
function createBoardExportScene(input) {
|
|
30553
|
+
const { context, missing } = buildContext(input);
|
|
30554
|
+
const root = new Container({ label: "board-export-root" });
|
|
30555
|
+
const outputWidth = input.world.width * input.scale;
|
|
30556
|
+
const outputHeight = input.world.height * input.scale;
|
|
30557
|
+
if (input.background != null) root.addChild(new Graphics().rect(0, 0, outputWidth, outputHeight).fill({
|
|
30558
|
+
color: input.background,
|
|
30559
|
+
alpha: 1
|
|
30560
|
+
}));
|
|
30561
|
+
if (input.backgroundImage) {
|
|
30562
|
+
const { texture, fit, position, opacity } = input.backgroundImage;
|
|
30563
|
+
if (fit === "repeat") root.addChild(new TilingSprite({
|
|
30564
|
+
texture,
|
|
30565
|
+
width: outputWidth,
|
|
30566
|
+
height: outputHeight,
|
|
30567
|
+
alpha: opacity
|
|
30568
|
+
}));
|
|
30569
|
+
else {
|
|
30570
|
+
const sprite = new Sprite({
|
|
30571
|
+
texture,
|
|
30572
|
+
alpha: opacity
|
|
30573
|
+
});
|
|
30574
|
+
const scale = fit === "cover" ? Math.max(outputWidth / texture.width, outputHeight / texture.height) : Math.min(outputWidth / texture.width, outputHeight / texture.height);
|
|
30575
|
+
sprite.width = texture.width * scale;
|
|
30576
|
+
sprite.height = texture.height * scale;
|
|
30577
|
+
const x = position === "left" ? 0 : position === "right" ? outputWidth - sprite.width : (outputWidth - sprite.width) / 2;
|
|
30578
|
+
const y = position === "top" ? 0 : position === "bottom" ? outputHeight - sprite.height : (outputHeight - sprite.height) / 2;
|
|
30579
|
+
sprite.position.set(x, y);
|
|
30580
|
+
root.addChild(sprite);
|
|
30581
|
+
}
|
|
30582
|
+
}
|
|
30583
|
+
const world = new Container({
|
|
30584
|
+
isRenderGroup: true,
|
|
30585
|
+
label: "board-export-world"
|
|
30586
|
+
});
|
|
30587
|
+
world.scale.set(input.scale);
|
|
30588
|
+
world.position.set(-input.world.x * input.scale, -input.world.y * input.scale);
|
|
30589
|
+
const resolve = input.renderers ?? getBoardCardRenderer;
|
|
30590
|
+
const groups = /* @__PURE__ */ new Map();
|
|
30591
|
+
function hostFor(item) {
|
|
30592
|
+
const clip = clippingAncestor(input.scene, item);
|
|
30593
|
+
const ancestor = clip ? input.scene.get(clip) : void 0;
|
|
30594
|
+
if (!ancestor) return world;
|
|
30595
|
+
let group = groups.get(ancestor.id);
|
|
30596
|
+
if (!group) {
|
|
30597
|
+
group = syncClipGroup(void 0, ancestor).group;
|
|
30598
|
+
hostFor(ancestor).addChild(group);
|
|
30599
|
+
groups.set(ancestor.id, group);
|
|
30600
|
+
}
|
|
30601
|
+
return group;
|
|
30602
|
+
}
|
|
30603
|
+
for (const item of input.items) {
|
|
30604
|
+
const opacity = input.scene.opacity(item.id);
|
|
30605
|
+
if (opacity <= 0) continue;
|
|
30606
|
+
const host = hostFor(item);
|
|
30607
|
+
const container = resolve(item, context).create(item, context);
|
|
30608
|
+
container.alpha = opacity;
|
|
30609
|
+
host.addChild(container);
|
|
30610
|
+
if (isClippingFrame(item) && !groups.has(item.id)) {
|
|
30611
|
+
const { group } = syncClipGroup(void 0, item);
|
|
30612
|
+
host.addChild(group);
|
|
30613
|
+
groups.set(item.id, group);
|
|
30614
|
+
}
|
|
30615
|
+
}
|
|
30616
|
+
root.addChild(world);
|
|
30617
|
+
return {
|
|
30618
|
+
root,
|
|
30619
|
+
missingImageKeys: [...missing],
|
|
30620
|
+
destroy: () => root.destroy({ children: true })
|
|
30621
|
+
};
|
|
30622
|
+
}
|
|
30623
|
+
//#endregion
|
|
30624
|
+
//#region ../sdk/src/board/export/index.ts
|
|
28659
30625
|
function resolveBackground(background, palette, document, colorScheme) {
|
|
28660
30626
|
if (background === "transparent") return null;
|
|
28661
30627
|
if (typeof background === "number") return background;
|
|
@@ -28663,20 +30629,50 @@ function resolveBackground(background, palette, document, colorScheme) {
|
|
|
28663
30629
|
const css = typeof declared === "object" ? declared[colorScheme] : declared;
|
|
28664
30630
|
return css ? parseBoardCssColor(css) ?? palette.bg : palette.bg;
|
|
28665
30631
|
}
|
|
28666
|
-
function
|
|
30632
|
+
function prepareBoardExport(input, options = {}) {
|
|
28667
30633
|
ensureBoardTextMeasurement();
|
|
28668
|
-
const base = normalizeBoardDocument(input);
|
|
28669
|
-
|
|
28670
|
-
|
|
30634
|
+
const base = options.sampler ? input : normalizeBoardDocument(input);
|
|
30635
|
+
if (options.camera && options.region && options.region.kind !== "all") throw new Error("Camera export cannot be combined with a fixed region.");
|
|
30636
|
+
if (options.camera && !options.at) throw new Error("Camera export needs an animation time.");
|
|
30637
|
+
if (options.camera && ![options.camera.width, options.camera.height].every((n) => Number.isSafeInteger(n) && n > 0)) throw new Error("Camera dimensions must be positive integers.");
|
|
30638
|
+
const sampler = options.at ? options.sampler ?? createBoardRenderSampler(base, options.at.animation ?? null, options) : void 0;
|
|
30639
|
+
const sample = options.at ? sampler?.at(options.at.time) : void 0;
|
|
30640
|
+
const sampled = sample?.document ?? base;
|
|
30641
|
+
const document = options.scripts?.document?.(sampled, options.at?.time ?? 0) ?? sampled;
|
|
30642
|
+
const scene = document === sampled ? sample?.scene ?? buildBoardScene(document) : buildBoardScene(document);
|
|
30643
|
+
const camera = options.camera ? resolveBoardAnimationCamera(sample?.evaluation.camera ?? {}, (id) => scene.get(id)?.frame, options.camera, options.at?.time ?? 0) : void 0;
|
|
28671
30644
|
const plan = planBoardExport({
|
|
28672
30645
|
scene,
|
|
28673
|
-
region: options.
|
|
28674
|
-
|
|
28675
|
-
|
|
30646
|
+
region: camera && options.camera ? {
|
|
30647
|
+
kind: "rect",
|
|
30648
|
+
rect: {
|
|
30649
|
+
x: -camera.x / camera.zoom,
|
|
30650
|
+
y: -camera.y / camera.zoom,
|
|
30651
|
+
width: options.camera.width / camera.zoom,
|
|
30652
|
+
height: options.camera.height / camera.zoom
|
|
30653
|
+
}
|
|
30654
|
+
} : options.region ?? { kind: "all" },
|
|
30655
|
+
scale: camera?.zoom ?? options.scale,
|
|
30656
|
+
padding: camera ? 0 : options.padding,
|
|
28676
30657
|
maxEdge: options.maxEdge,
|
|
28677
30658
|
maxPixels: options.maxPixels
|
|
28678
30659
|
});
|
|
28679
|
-
if (
|
|
30660
|
+
if (plan && camera && options.camera) {
|
|
30661
|
+
const ratio = plan.clamped ? plan.scale / camera.zoom : 1;
|
|
30662
|
+
plan.width = Math.max(1, Math.floor(options.camera.width * ratio));
|
|
30663
|
+
plan.height = Math.max(1, Math.floor(options.camera.height * ratio));
|
|
30664
|
+
}
|
|
30665
|
+
return plan ? {
|
|
30666
|
+
document,
|
|
30667
|
+
scene,
|
|
30668
|
+
plan,
|
|
30669
|
+
sampler
|
|
30670
|
+
} : null;
|
|
30671
|
+
}
|
|
30672
|
+
function renderBoardExport(renderer, input, options = {}) {
|
|
30673
|
+
const prepared = prepareBoardExport(input, options);
|
|
30674
|
+
if (!prepared) return null;
|
|
30675
|
+
const { document, scene, plan, sampler } = prepared;
|
|
28680
30676
|
const colorScheme = options.colorScheme ?? "dark";
|
|
28681
30677
|
const palette = {
|
|
28682
30678
|
...defaultBoardPalette(colorScheme),
|
|
@@ -28687,13 +30683,14 @@ function renderBoardExport(renderer, input, options = {}) {
|
|
|
28687
30683
|
scene,
|
|
28688
30684
|
items: plan.items,
|
|
28689
30685
|
time: options.at?.time ?? 0,
|
|
30686
|
+
frameAt: sampler?.frameAt,
|
|
28690
30687
|
world: plan.world,
|
|
28691
30688
|
scale: plan.scale,
|
|
28692
30689
|
colorScheme,
|
|
28693
30690
|
palette,
|
|
28694
30691
|
colors: options.colors,
|
|
28695
30692
|
textures: options.textures,
|
|
28696
|
-
|
|
30693
|
+
scripts: options.scripts,
|
|
28697
30694
|
assetKey: options.assetKey,
|
|
28698
30695
|
renderers: options.renderers,
|
|
28699
30696
|
background: resolveBackground(options.background, palette, document, colorScheme),
|
|
@@ -28731,145 +30728,253 @@ function renderBoardExport(renderer, input, options = {}) {
|
|
|
28731
30728
|
}
|
|
28732
30729
|
}
|
|
28733
30730
|
//#endregion
|
|
28734
|
-
//#region ../sdk/src/board/export/node/
|
|
28735
|
-
const
|
|
28736
|
-
const
|
|
28737
|
-
const
|
|
28738
|
-
const
|
|
28739
|
-
|
|
28740
|
-
|
|
28741
|
-
|
|
28742
|
-
|
|
28743
|
-
|
|
28744
|
-
|
|
28745
|
-
|
|
28746
|
-
|
|
28747
|
-
|
|
28748
|
-
|
|
28749
|
-
|
|
28750
|
-
|
|
28751
|
-
|
|
28752
|
-
|
|
28753
|
-
|
|
28754
|
-
|
|
28755
|
-
|
|
28756
|
-
|
|
28757
|
-
|
|
28758
|
-
|
|
28759
|
-
|
|
28760
|
-
|
|
28761
|
-
|
|
28762
|
-
|
|
28763
|
-
|
|
28764
|
-
|
|
28765
|
-
|
|
28766
|
-
|
|
28767
|
-
|
|
28768
|
-
|
|
28769
|
-
|
|
28770
|
-
|
|
28771
|
-
|
|
28772
|
-
|
|
28773
|
-
|
|
28774
|
-
|
|
28775
|
-
|
|
28776
|
-
|
|
28777
|
-
|
|
28778
|
-
|
|
28779
|
-
}
|
|
28780
|
-
|
|
28781
|
-
|
|
28782
|
-
}
|
|
28783
|
-
|
|
28784
|
-
|
|
28785
|
-
|
|
28786
|
-
|
|
28787
|
-
|
|
28788
|
-
|
|
28789
|
-
|
|
28790
|
-
|
|
28791
|
-
|
|
28792
|
-
|
|
28793
|
-
|
|
30731
|
+
//#region ../sdk/src/board/export/node/scripts.ts
|
|
30732
|
+
const WORKER = `
|
|
30733
|
+
const {parentPort, workerData} = require('node:worker_threads');
|
|
30734
|
+
const {createContext, Script, SourceTextModule} = require('node:vm');
|
|
30735
|
+
const {createCanvas} = require(workerData.canvasModule);
|
|
30736
|
+
const instances = new Map();
|
|
30737
|
+
const graphs = new Map();
|
|
30738
|
+
const update = new Script('__runtime.update(JSON.parse(__inputJson)); __runtime.step(__time)');
|
|
30739
|
+
const draw = new Script('__runtime.draw(__drawing)');
|
|
30740
|
+
const output = new Script('JSON.stringify(__runtime.output())');
|
|
30741
|
+
const dispose = new Script('__runtime.destroy()');
|
|
30742
|
+
function release(id) {
|
|
30743
|
+
const entry=instances.get(id);instances.delete(id);
|
|
30744
|
+
if(entry) {try {dispose.runInContext(entry.context,{timeout:workerData.timeout});} catch {}}
|
|
30745
|
+
}
|
|
30746
|
+
async function create(data) {
|
|
30747
|
+
const context = createContext({console:{log(){},warn(){},error(){}},structuredClone},{codeGeneration:{strings:false,wasm:false}});
|
|
30748
|
+
new Script("const __name=(value,name)=>Object.defineProperty(value,'name',{value:name,configurable:true})").runInContext(context);
|
|
30749
|
+
const sources = graphs.get(data.src);
|
|
30750
|
+
if(!sources) throw new Error('Module graph is unavailable.');
|
|
30751
|
+
const modules = new Map();
|
|
30752
|
+
const resolve = (from,name) => {
|
|
30753
|
+
const parts=from.split('/').slice(0,-1);
|
|
30754
|
+
for(const part of name.split('/')) { if(part==='..') parts.pop(); else if(part!=='.') parts.push(part); }
|
|
30755
|
+
return parts.join('/');
|
|
30756
|
+
};
|
|
30757
|
+
const link = (name,ref) => load(resolve(ref.identifier,name));
|
|
30758
|
+
function load(path) {
|
|
30759
|
+
if(modules.has(path)) return modules.get(path);
|
|
30760
|
+
const source=sources[path];
|
|
30761
|
+
if(!source) throw new Error('Module is unavailable: '+path);
|
|
30762
|
+
const module=new SourceTextModule(source.code,{context,identifier:path,importModuleDynamically:async name=>{
|
|
30763
|
+
const child=load(resolve(path,name));
|
|
30764
|
+
if(child.status==='unlinked') await child.link(link);
|
|
30765
|
+
if(child.status==='linked') await child.evaluate({timeout:workerData.timeout});
|
|
30766
|
+
return child;
|
|
30767
|
+
}});
|
|
30768
|
+
modules.set(path,module);return module;
|
|
30769
|
+
}
|
|
30770
|
+
const root=load(data.src);
|
|
30771
|
+
await root.link(link);await root.evaluate({timeout:workerData.timeout});
|
|
30772
|
+
context.__module=root.namespace;
|
|
30773
|
+
context.__inputJson=JSON.stringify(data.input);
|
|
30774
|
+
new Script('globalThis.__runtime=('+workerData.runtime+')(__module,JSON.parse(__inputJson),{call:async()=>{throw new Error("Capabilities are unavailable during export.")},invalidate(){}})').runInContext(context,{timeout:workerData.timeout});
|
|
30775
|
+
return {context,signature:data.signature,time:-1,draw:!!root.namespace.draw,canvas:null};
|
|
30776
|
+
}
|
|
30777
|
+
parentPort.on('message',async data=>{
|
|
30778
|
+
try {
|
|
30779
|
+
if(data.release) {for(const id of data.release) release(id);return;}
|
|
30780
|
+
if(data.sources) graphs.set(data.src,data.sources);
|
|
30781
|
+
let entry=instances.get(data.input.id);
|
|
30782
|
+
if(!entry || entry.signature!==data.signature || data.time<entry.time) {
|
|
30783
|
+
release(data.input.id);
|
|
30784
|
+
entry=await create(data);instances.set(data.input.id,entry);
|
|
30785
|
+
}
|
|
30786
|
+
const context=entry.context;
|
|
30787
|
+
context.__inputJson=JSON.stringify(data.input);context.__time=data.time;
|
|
30788
|
+
update.runInContext(context,{timeout:workerData.timeout});entry.time=data.time;
|
|
30789
|
+
let png;
|
|
30790
|
+
if(entry.draw) {
|
|
30791
|
+
const width=Math.max(1,Math.round(data.input.width*data.scale));
|
|
30792
|
+
const height=Math.max(1,Math.round(data.input.height*data.scale));
|
|
30793
|
+
const canvas=entry.canvas??=createCanvas(width,height);
|
|
30794
|
+
canvas.width=width;canvas.height=height;
|
|
30795
|
+
const drawing=canvas.getContext('2d');drawing.scale(data.scale,data.scale);
|
|
30796
|
+
context.__drawing=drawing;draw.runInContext(context,{timeout:workerData.timeout});
|
|
30797
|
+
png=canvas.toBuffer('image/png');delete context.__drawing;
|
|
30798
|
+
}
|
|
30799
|
+
parentPort.postMessage({png,output:JSON.parse(output.runInContext(context,{timeout:workerData.timeout}))});
|
|
30800
|
+
} catch(error) { instances.delete(data.input?.id);parentPort.postMessage({error:String(error.stack||error)}); }
|
|
30801
|
+
});
|
|
30802
|
+
`;
|
|
30803
|
+
function createNodeBoardScriptHost(renderer, options) {
|
|
28794
30804
|
const textures = /* @__PURE__ */ new Map();
|
|
30805
|
+
const outputs = /* @__PURE__ */ new Map();
|
|
28795
30806
|
const errors = /* @__PURE__ */ new Map();
|
|
28796
|
-
const
|
|
28797
|
-
const modules =
|
|
28798
|
-
const
|
|
28799
|
-
|
|
28800
|
-
|
|
28801
|
-
|
|
28802
|
-
|
|
28803
|
-
|
|
28804
|
-
|
|
28805
|
-
|
|
28806
|
-
|
|
28807
|
-
|
|
28808
|
-
|
|
28809
|
-
|
|
28810
|
-
|
|
28811
|
-
|
|
28812
|
-
|
|
28813
|
-
|
|
28814
|
-
|
|
28815
|
-
|
|
28816
|
-
|
|
28817
|
-
|
|
28818
|
-
|
|
28819
|
-
|
|
28820
|
-
|
|
28821
|
-
|
|
28822
|
-
|
|
28823
|
-
|
|
28824
|
-
|
|
28825
|
-
};
|
|
28826
|
-
runtime.context.__boardDraw = runtime.module.draw;
|
|
28827
|
-
runtime.context.__boardDrawContext = context;
|
|
28828
|
-
runtime.context.__boardFrame = frame;
|
|
28829
|
-
try {
|
|
28830
|
-
const drawResult = new Script("__boardDraw(__boardDrawContext, __boardFrame)", { filename: item.props.src }).runInContext(runtime.context, { timeout: timeoutMs });
|
|
28831
|
-
if (drawResult && typeof drawResult.then === "function") throw new Error(`${item.props.src} must export a synchronous draw(ctx, frame) for Node export.`);
|
|
28832
|
-
} finally {
|
|
28833
|
-
delete runtime.context.__boardDraw;
|
|
28834
|
-
delete runtime.context.__boardDrawContext;
|
|
28835
|
-
delete runtime.context.__boardFrame;
|
|
30807
|
+
const sources = /* @__PURE__ */ new Map();
|
|
30808
|
+
const modules = createBoardScriptModuleReader(options.readModule, options.readTimeoutMs);
|
|
30809
|
+
const lanes = Array.from({ length: 4 }, () => ({
|
|
30810
|
+
worker: null,
|
|
30811
|
+
tail: Promise.resolve(),
|
|
30812
|
+
graphs: /* @__PURE__ */ new Set()
|
|
30813
|
+
}));
|
|
30814
|
+
const assignments = /* @__PURE__ */ new Map();
|
|
30815
|
+
let disposed = false;
|
|
30816
|
+
let nextLane = 0;
|
|
30817
|
+
const timeout = options.timeoutMs ?? 2e3;
|
|
30818
|
+
const key = (id, time) => `${id}|${time}`;
|
|
30819
|
+
function stop(lane) {
|
|
30820
|
+
const worker = lane.worker;
|
|
30821
|
+
lane.worker = null;
|
|
30822
|
+
lane.graphs.clear();
|
|
30823
|
+
worker?.terminate();
|
|
30824
|
+
}
|
|
30825
|
+
function sample(input, graph, time, scale) {
|
|
30826
|
+
let lane = assignments.get(input.id);
|
|
30827
|
+
if (!lane) {
|
|
30828
|
+
lane = lanes[nextLane++ % lanes.length];
|
|
30829
|
+
assignments.set(input.id, lane);
|
|
30830
|
+
}
|
|
30831
|
+
const selected = lane;
|
|
30832
|
+
const task = selected.tail.then(() => new Promise((resolve, reject) => {
|
|
30833
|
+
if (disposed) {
|
|
30834
|
+
reject(/* @__PURE__ */ new Error("Script host is disposed."));
|
|
30835
|
+
return;
|
|
28836
30836
|
}
|
|
28837
|
-
|
|
28838
|
-
|
|
28839
|
-
|
|
28840
|
-
|
|
28841
|
-
|
|
28842
|
-
|
|
28843
|
-
|
|
30837
|
+
if (!Number.isFinite(time) || time < 0 || time > 6e5) {
|
|
30838
|
+
reject(/* @__PURE__ */ new Error("Script export time must be between 0 and 600000 ms."));
|
|
30839
|
+
return;
|
|
30840
|
+
}
|
|
30841
|
+
const freshWorker = !selected.worker;
|
|
30842
|
+
selected.worker ??= new Worker(WORKER, {
|
|
30843
|
+
eval: true,
|
|
30844
|
+
execArgv: ["--experimental-vm-modules", "--disable-warning=ExperimentalWarning"],
|
|
30845
|
+
env: {},
|
|
30846
|
+
resourceLimits: { maxOldGenerationSizeMb: 128 },
|
|
30847
|
+
workerData: {
|
|
30848
|
+
runtime: createScriptRuntime.toString(),
|
|
30849
|
+
timeout,
|
|
30850
|
+
canvasModule: fileURLToPath(import.meta.resolve("@napi-rs/canvas"))
|
|
30851
|
+
}
|
|
30852
|
+
});
|
|
30853
|
+
const worker = selected.worker;
|
|
30854
|
+
if (freshWorker) {
|
|
30855
|
+
const stopped = (error) => {
|
|
30856
|
+
if (selected.worker !== worker) return;
|
|
30857
|
+
if (selected.reject) selected.reject(error);
|
|
30858
|
+
else stop(selected);
|
|
30859
|
+
};
|
|
30860
|
+
worker.on("error", stopped);
|
|
30861
|
+
worker.on("exit", () => stopped(/* @__PURE__ */ new Error("Script worker stopped.")));
|
|
30862
|
+
}
|
|
30863
|
+
const cleanup = () => {
|
|
30864
|
+
clearTimeout(timer);
|
|
30865
|
+
worker.off("message", message);
|
|
30866
|
+
selected.reject = void 0;
|
|
30867
|
+
};
|
|
30868
|
+
const failure = (error) => {
|
|
30869
|
+
cleanup();
|
|
30870
|
+
stop(selected);
|
|
30871
|
+
reject(error);
|
|
30872
|
+
};
|
|
30873
|
+
const message = (value) => {
|
|
30874
|
+
cleanup();
|
|
30875
|
+
if (value.error) reject(new Error(value.error));
|
|
30876
|
+
else resolve(value);
|
|
30877
|
+
};
|
|
30878
|
+
const timer = setTimeout(() => failure(/* @__PURE__ */ new Error("Script export timed out.")), Math.max(timeout, 15e3));
|
|
30879
|
+
selected.reject = failure;
|
|
30880
|
+
worker.once("message", message);
|
|
30881
|
+
const src = input.item.script?.src ?? "";
|
|
30882
|
+
worker.postMessage({
|
|
30883
|
+
input,
|
|
30884
|
+
src,
|
|
30885
|
+
signature: JSON.stringify(input.item.script),
|
|
30886
|
+
sources: selected.graphs.has(src) ? void 0 : graph,
|
|
30887
|
+
time,
|
|
30888
|
+
scale
|
|
30889
|
+
});
|
|
30890
|
+
selected.graphs.add(src);
|
|
30891
|
+
}));
|
|
30892
|
+
selected.tail = task.catch(() => void 0);
|
|
30893
|
+
return task;
|
|
30894
|
+
}
|
|
28844
30895
|
return {
|
|
28845
|
-
|
|
28846
|
-
|
|
28847
|
-
|
|
30896
|
+
ready: (id) => [...outputs.values()].some((at) => at.has(id)),
|
|
30897
|
+
frame: (item, time) => textures.get(key(item.id, time)) ?? null,
|
|
30898
|
+
error: (id) => errors.get(id) ?? null,
|
|
30899
|
+
release() {},
|
|
30900
|
+
document(document, time) {
|
|
30901
|
+
const at = outputs.get(time);
|
|
30902
|
+
if (!at?.size) return document;
|
|
30903
|
+
const items = { ...document.items };
|
|
30904
|
+
for (const [id, item] of composeBoardScriptItems(document, buildBoardScene(document), at)) items[id] = item;
|
|
30905
|
+
return {
|
|
30906
|
+
...document,
|
|
30907
|
+
items
|
|
30908
|
+
};
|
|
30909
|
+
},
|
|
30910
|
+
clearFrames() {
|
|
30911
|
+
for (const texture of textures.values()) texture.destroy(true);
|
|
30912
|
+
textures.clear();
|
|
30913
|
+
outputs.clear();
|
|
30914
|
+
errors.clear();
|
|
28848
30915
|
},
|
|
28849
|
-
error: (id) => lastErrors.get(id) ?? null,
|
|
28850
|
-
release: () => {},
|
|
28851
30916
|
async prepare(items, times, zoom) {
|
|
28852
|
-
const
|
|
28853
|
-
const
|
|
28854
|
-
for (const
|
|
28855
|
-
|
|
28856
|
-
|
|
30917
|
+
const scripted = items.filter((item) => item.script?.enabled);
|
|
30918
|
+
const alive = new Set(scripted.map((item) => item.id));
|
|
30919
|
+
for (const [id, lane] of assignments) if (!alive.has(id)) {
|
|
30920
|
+
lane.worker?.postMessage({ release: [id] });
|
|
30921
|
+
assignments.delete(id);
|
|
28857
30922
|
}
|
|
28858
|
-
|
|
28859
|
-
const
|
|
28860
|
-
|
|
28861
|
-
|
|
28862
|
-
|
|
30923
|
+
if (!scripted.length) return;
|
|
30924
|
+
const all = Object.fromEntries(items.map(({ id, frame: _frame, ...item }) => [id, item]));
|
|
30925
|
+
await Promise.all(scripted.map(async (item) => {
|
|
30926
|
+
const base = all[item.id];
|
|
30927
|
+
const src = item.script?.src;
|
|
30928
|
+
if (!base || !src) return;
|
|
30929
|
+
try {
|
|
30930
|
+
let graph = sources.get(src);
|
|
30931
|
+
if (!graph) {
|
|
30932
|
+
graph = loadBoardScriptSources(src, modules.read, options.readTimeoutMs);
|
|
30933
|
+
sources.set(src, graph);
|
|
30934
|
+
}
|
|
30935
|
+
const resolved = await graph;
|
|
30936
|
+
const width = "size" in item ? item.size.width : item.frame.width;
|
|
30937
|
+
const height = "size" in item ? item.size.height : item.frame.height;
|
|
30938
|
+
const scale = Math.min(4, Math.max(.25, zoom), 2048 / Math.max(1, width, height));
|
|
30939
|
+
const scoped = Object.fromEntries([...boardScriptScope({ items: all }, item.id)].flatMap((id) => all[id] ? [[id, all[id]]] : []));
|
|
30940
|
+
for (const time of times) {
|
|
30941
|
+
const result = await sample({
|
|
30942
|
+
id: item.id,
|
|
30943
|
+
item: base,
|
|
30944
|
+
items: scoped,
|
|
30945
|
+
width,
|
|
30946
|
+
height
|
|
30947
|
+
}, resolved, time, scale);
|
|
30948
|
+
if (disposed) return;
|
|
30949
|
+
if (result.png) {
|
|
30950
|
+
const frameKey = key(item.id, time);
|
|
30951
|
+
textures.get(frameKey)?.destroy(true);
|
|
30952
|
+
textures.set(frameKey, await renderer.decodeImage(result.png));
|
|
30953
|
+
}
|
|
30954
|
+
let at = outputs.get(time);
|
|
30955
|
+
if (!at) {
|
|
30956
|
+
at = /* @__PURE__ */ new Map();
|
|
30957
|
+
outputs.set(time, at);
|
|
30958
|
+
}
|
|
30959
|
+
at.set(item.id, result.output);
|
|
30960
|
+
}
|
|
30961
|
+
} catch (error) {
|
|
30962
|
+
errors.set(item.id, error instanceof Error ? error.message : String(error));
|
|
28863
30963
|
}
|
|
28864
|
-
};
|
|
28865
|
-
await Promise.all(Array.from({ length: Math.min(MAX_PREPARE_CONCURRENCY, jobs.length) }, worker));
|
|
30964
|
+
}));
|
|
28866
30965
|
},
|
|
28867
30966
|
destroy() {
|
|
30967
|
+
disposed = true;
|
|
30968
|
+
for (const lane of lanes) {
|
|
30969
|
+
lane.reject?.(/* @__PURE__ */ new Error("Script host is disposed."));
|
|
30970
|
+
stop(lane);
|
|
30971
|
+
}
|
|
28868
30972
|
for (const texture of textures.values()) texture.destroy(true);
|
|
28869
30973
|
textures.clear();
|
|
28870
|
-
|
|
28871
|
-
|
|
28872
|
-
modules.
|
|
30974
|
+
outputs.clear();
|
|
30975
|
+
sources.clear();
|
|
30976
|
+
modules.destroy();
|
|
30977
|
+
assignments.clear();
|
|
28873
30978
|
}
|
|
28874
30979
|
};
|
|
28875
30980
|
}
|
|
@@ -28999,10 +31104,10 @@ const MIME = {
|
|
|
28999
31104
|
webp: "image/webp"
|
|
29000
31105
|
};
|
|
29001
31106
|
function exportBoardImageBytes(renderer, document, options = {}) {
|
|
29002
|
-
const { format = "png", quality = .92, textures,
|
|
31107
|
+
const { format = "png", quality = .92, textures, scripts, backgroundImage, ...rest } = options;
|
|
29003
31108
|
const result = renderBoardExport(renderer.renderer, document, {
|
|
29004
31109
|
...rest,
|
|
29005
|
-
...
|
|
31110
|
+
...scripts ? { scripts } : {},
|
|
29006
31111
|
...textures ? { textures } : {},
|
|
29007
31112
|
...backgroundImage ? { backgroundImage: {
|
|
29008
31113
|
...backgroundImage,
|
|
@@ -29011,13 +31116,18 @@ function exportBoardImageBytes(renderer, document, options = {}) {
|
|
|
29011
31116
|
});
|
|
29012
31117
|
if (!result) return null;
|
|
29013
31118
|
const canvas = result.canvas;
|
|
29014
|
-
|
|
29015
|
-
|
|
29016
|
-
|
|
29017
|
-
|
|
29018
|
-
|
|
29019
|
-
|
|
29020
|
-
|
|
31119
|
+
try {
|
|
31120
|
+
const bytes = format === "png" ? canvas.toBuffer(MIME.png) : canvas.toBuffer(MIME[format], Math.round(quality * 100));
|
|
31121
|
+
return {
|
|
31122
|
+
bytes: new Uint8Array(bytes),
|
|
31123
|
+
plan: result.plan,
|
|
31124
|
+
warnings: result.warnings,
|
|
31125
|
+
format
|
|
31126
|
+
};
|
|
31127
|
+
} finally {
|
|
31128
|
+
canvas.width = 1;
|
|
31129
|
+
canvas.height = 1;
|
|
31130
|
+
}
|
|
29021
31131
|
}
|
|
29022
31132
|
//#endregion
|
|
29023
|
-
export { boardDocumentAt, boardImageKeySource, buildBoardScene, createNodeBoardRenderer,
|
|
31133
|
+
export { BOARD_SCRIPT_DRAWING_EXAMPLE, boardDocumentAt, boardImageKeySource, boardPathTrack, boardSequence, boardSequencePatch, buildBoardScene, createBoardAnimationSampler, createBoardRenderSampler, createNodeBoardRenderer, createNodeBoardScriptHost, exportBoardImageBytes, hasBoardScripts, imageAssetKey, inspectBoardScene, measureBoardText, parseBoardDocument, planBoardExport, prepareBoardExport, selectBoardExportAssets };
|