@routevn/creator-model 1.12.1 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -2
- package/package.json +1 -1
- package/src/model.js +989 -32
package/README.md
CHANGED
|
@@ -131,8 +131,12 @@ font IDs. Both forms are persisted as supplied, and every ID must reference an
|
|
|
131
131
|
existing non-folder font:
|
|
132
132
|
|
|
133
133
|
```js
|
|
134
|
-
{
|
|
135
|
-
|
|
134
|
+
{
|
|
135
|
+
fontId: "font-primary";
|
|
136
|
+
}
|
|
137
|
+
{
|
|
138
|
+
fontId: ["font-primary", "font-fallback"];
|
|
139
|
+
}
|
|
136
140
|
```
|
|
137
141
|
|
|
138
142
|
## Computed Variables
|
|
@@ -173,6 +177,40 @@ Computed definitions may use a simple `expr` or literal `value`, or ordered
|
|
|
173
177
|
operator grammar, result types, concrete `variables.*`/`runtime.*` paths,
|
|
174
178
|
declared variable references, and an acyclic computed dependency graph.
|
|
175
179
|
|
|
180
|
+
Computed definitions may also store examples. Example inputs mirror the
|
|
181
|
+
evaluation context, while calculated results remain derived and are not
|
|
182
|
+
persisted:
|
|
183
|
+
|
|
184
|
+
```js
|
|
185
|
+
{
|
|
186
|
+
computed: {
|
|
187
|
+
expr: {
|
|
188
|
+
mul: [
|
|
189
|
+
{
|
|
190
|
+
div: [
|
|
191
|
+
{ var: "variables.hp" },
|
|
192
|
+
{ var: "variables.maxHp" },
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
100,
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
examples: [
|
|
199
|
+
{
|
|
200
|
+
id: "example-low-health",
|
|
201
|
+
name: "Low health",
|
|
202
|
+
input: {
|
|
203
|
+
variables: {
|
|
204
|
+
hp: 40,
|
|
205
|
+
maxHp: 80,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
},
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
176
214
|
## File Structure
|
|
177
215
|
|
|
178
216
|
```text
|
|
@@ -191,6 +229,8 @@ tests/
|
|
|
191
229
|
images.spec.yaml
|
|
192
230
|
sounds-and-videos.spec.yaml
|
|
193
231
|
animations.spec.yaml
|
|
232
|
+
audio-effects.test.js
|
|
233
|
+
audio-effects-drift.test.js
|
|
194
234
|
fonts-and-colors.spec.yaml
|
|
195
235
|
transforms-variables-textstyles.spec.yaml
|
|
196
236
|
characters-and-layouts.spec.yaml
|
|
@@ -298,6 +338,40 @@ Transition animation `mask` accepts one mask object or a non-empty ordered
|
|
|
298
338
|
array. Each mask may define a non-negative safe-integer `delay` in
|
|
299
339
|
milliseconds.
|
|
300
340
|
|
|
341
|
+
Audio effects are foldered resources stored in `audioEffects.items` and
|
|
342
|
+
`audioEffects.tree`. Non-folder items use this wrapper:
|
|
343
|
+
|
|
344
|
+
```js
|
|
345
|
+
{
|
|
346
|
+
id: "crossfade",
|
|
347
|
+
type: "audioEffect",
|
|
348
|
+
name: "Crossfade",
|
|
349
|
+
description: "Fade between two BGM sources",
|
|
350
|
+
tagIds: ["smooth"],
|
|
351
|
+
preview: {
|
|
352
|
+
outgoing: { soundId: "calm-theme" },
|
|
353
|
+
incoming: { soundId: "battle-theme" },
|
|
354
|
+
},
|
|
355
|
+
audioEffect: {
|
|
356
|
+
type: "transition",
|
|
357
|
+
prev: { fade: { duration: 600, easing: "easeInOutSine" } },
|
|
358
|
+
next: { fade: { duration: 900, easing: "easeInOutSine" } },
|
|
359
|
+
},
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
The optional editor preview uses `outgoing` and `incoming` sound slots for a
|
|
364
|
+
transition effect, or a single `target` sound slot for an update effect. Each
|
|
365
|
+
slot is an object containing a `soundId`.
|
|
366
|
+
|
|
367
|
+
`audioEffect.type` is either `transition` or `update`. Transition effects have
|
|
368
|
+
at least one `prev.fade` or `next.fade`. Update effects tween one or more of
|
|
369
|
+
`volume`, `pan`, and `playbackRate`; each property has non-empty keyframes and
|
|
370
|
+
must finish with an absolute numeric keyframe. That final number becomes the
|
|
371
|
+
persistent BGM property value after the effect finishes. Absolute volume values
|
|
372
|
+
are bounded to `0..100`, pan to `-1..1`, and playback rate to `>= 0`; relative
|
|
373
|
+
keyframes represent unbounded numeric deltas and cannot be final.
|
|
374
|
+
|
|
301
375
|
## Current Scope
|
|
302
376
|
|
|
303
377
|
Currently implemented command types:
|
|
@@ -336,6 +410,10 @@ Currently implemented command types:
|
|
|
336
410
|
- `animation.update`
|
|
337
411
|
- `animation.delete`
|
|
338
412
|
- `animation.move`
|
|
413
|
+
- `audioEffect.create`
|
|
414
|
+
- `audioEffect.update`
|
|
415
|
+
- `audioEffect.delete`
|
|
416
|
+
- `audioEffect.move`
|
|
339
417
|
- `font.create`
|
|
340
418
|
- `font.update`
|
|
341
419
|
- `font.delete`
|
package/package.json
CHANGED
package/src/model.js
CHANGED
|
@@ -26,6 +26,7 @@ const COLLECTION_KEYS = [
|
|
|
26
26
|
"voices",
|
|
27
27
|
"videos",
|
|
28
28
|
"animations",
|
|
29
|
+
"audioEffects",
|
|
29
30
|
"particles",
|
|
30
31
|
"characters",
|
|
31
32
|
"fonts",
|
|
@@ -66,6 +67,7 @@ const TAG_SCOPE_BASE_KEYS = [
|
|
|
66
67
|
"layouts",
|
|
67
68
|
"controls",
|
|
68
69
|
"animations",
|
|
70
|
+
"audioEffects",
|
|
69
71
|
"particles",
|
|
70
72
|
"spritesheets",
|
|
71
73
|
];
|
|
@@ -83,6 +85,7 @@ const createEmptyTagsState = () => ({
|
|
|
83
85
|
layouts: createEmptyCollectionState(),
|
|
84
86
|
controls: createEmptyCollectionState(),
|
|
85
87
|
animations: createEmptyCollectionState(),
|
|
88
|
+
audioEffects: createEmptyCollectionState(),
|
|
86
89
|
particles: createEmptyCollectionState(),
|
|
87
90
|
spritesheets: createEmptyCollectionState(),
|
|
88
91
|
});
|
|
@@ -248,6 +251,7 @@ const normalizeStateCollections = (state) => {
|
|
|
248
251
|
"particles",
|
|
249
252
|
"controls",
|
|
250
253
|
"voices",
|
|
254
|
+
"audioEffects",
|
|
251
255
|
].filter((key) => state[key] === undefined);
|
|
252
256
|
const normalizedTags = normalizeTagsState(state.tags);
|
|
253
257
|
const hasNormalizedTags = normalizedTags !== state.tags;
|
|
@@ -340,6 +344,7 @@ const ANIMATION_EASING_KEYS = [
|
|
|
340
344
|
"easeOutElastic",
|
|
341
345
|
"easeInOutElastic",
|
|
342
346
|
];
|
|
347
|
+
const AUDIO_EFFECT_TWEEN_PROPERTY_KEYS = ["volume", "pan", "playbackRate"];
|
|
343
348
|
const VARIABLE_SCOPE_KEYS = ["context", "device", "account"];
|
|
344
349
|
const VARIABLE_TYPE_KEYS = ["string", "number", "boolean", "object"];
|
|
345
350
|
const LAYOUT_TYPE_KEYS = [
|
|
@@ -403,7 +408,7 @@ const SAVE_LOAD_DATE_FORMATS = new Set([
|
|
|
403
408
|
"DD MMM YYYY",
|
|
404
409
|
"YYYY年MM月DD日",
|
|
405
410
|
]);
|
|
406
|
-
export const SCHEMA_VERSION =
|
|
411
|
+
export const SCHEMA_VERSION = 13;
|
|
407
412
|
const LAYOUT_CONTAINER_ELEMENT_TYPES = [
|
|
408
413
|
"folder",
|
|
409
414
|
"container",
|
|
@@ -2434,6 +2439,585 @@ const validateAnimationItems = ({ items, path, errorFactory }) => {
|
|
|
2434
2439
|
}
|
|
2435
2440
|
};
|
|
2436
2441
|
|
|
2442
|
+
const validateAudioEffectEasing = ({ value, path, errorFactory }) => {
|
|
2443
|
+
if (value !== undefined && !ANIMATION_EASING_KEYS.includes(value)) {
|
|
2444
|
+
return invalidFromErrorFactory(
|
|
2445
|
+
errorFactory,
|
|
2446
|
+
`${path} must be a supported Route Graphics easing`,
|
|
2447
|
+
);
|
|
2448
|
+
}
|
|
2449
|
+
};
|
|
2450
|
+
|
|
2451
|
+
const validateAudioEffectAbsoluteValue = ({
|
|
2452
|
+
value,
|
|
2453
|
+
property,
|
|
2454
|
+
path,
|
|
2455
|
+
errorFactory,
|
|
2456
|
+
}) => {
|
|
2457
|
+
if (property === "volume" && (value < 0 || value > 100)) {
|
|
2458
|
+
return invalidFromErrorFactory(
|
|
2459
|
+
errorFactory,
|
|
2460
|
+
`${path} must be between 0 and 100 for an absolute volume keyframe`,
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
if (property === "pan" && (value < -1 || value > 1)) {
|
|
2465
|
+
return invalidFromErrorFactory(
|
|
2466
|
+
errorFactory,
|
|
2467
|
+
`${path} must be between -1 and 1 for an absolute pan keyframe`,
|
|
2468
|
+
);
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
if (property === "playbackRate" && value < 0) {
|
|
2472
|
+
return invalidFromErrorFactory(
|
|
2473
|
+
errorFactory,
|
|
2474
|
+
`${path} must be greater than or equal to 0 for an absolute playbackRate keyframe`,
|
|
2475
|
+
);
|
|
2476
|
+
}
|
|
2477
|
+
};
|
|
2478
|
+
|
|
2479
|
+
const validateAudioEffectKeyframes = ({
|
|
2480
|
+
keyframes,
|
|
2481
|
+
property,
|
|
2482
|
+
path,
|
|
2483
|
+
errorFactory,
|
|
2484
|
+
}) => {
|
|
2485
|
+
if (!Array.isArray(keyframes) || keyframes.length === 0) {
|
|
2486
|
+
return invalidFromErrorFactory(
|
|
2487
|
+
errorFactory,
|
|
2488
|
+
`${path} must be a non-empty array`,
|
|
2489
|
+
);
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
for (const [index, keyframe] of keyframes.entries()) {
|
|
2493
|
+
const keyframePath = `${path}[${index}]`;
|
|
2494
|
+
|
|
2495
|
+
{
|
|
2496
|
+
const result = validateAllowedKeys({
|
|
2497
|
+
value: keyframe,
|
|
2498
|
+
allowedKeys: [
|
|
2499
|
+
"startValue",
|
|
2500
|
+
"value",
|
|
2501
|
+
"duration",
|
|
2502
|
+
"delay",
|
|
2503
|
+
"easing",
|
|
2504
|
+
"relative",
|
|
2505
|
+
],
|
|
2506
|
+
path: keyframePath,
|
|
2507
|
+
errorFactory,
|
|
2508
|
+
});
|
|
2509
|
+
if (result?.valid === false) {
|
|
2510
|
+
return result;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
if (!Object.hasOwn(keyframe, "value")) {
|
|
2515
|
+
return invalidFromErrorFactory(
|
|
2516
|
+
errorFactory,
|
|
2517
|
+
`${keyframePath}.value is required`,
|
|
2518
|
+
);
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
if (!Object.hasOwn(keyframe, "duration")) {
|
|
2522
|
+
return invalidFromErrorFactory(
|
|
2523
|
+
errorFactory,
|
|
2524
|
+
`${keyframePath}.duration is required`,
|
|
2525
|
+
);
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
if (!isFiniteNumber(keyframe.value)) {
|
|
2529
|
+
return invalidFromErrorFactory(
|
|
2530
|
+
errorFactory,
|
|
2531
|
+
`${keyframePath}.value must be a finite number`,
|
|
2532
|
+
);
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
if (!isFiniteNumber(keyframe.duration) || keyframe.duration < 0) {
|
|
2536
|
+
return invalidFromErrorFactory(
|
|
2537
|
+
errorFactory,
|
|
2538
|
+
`${keyframePath}.duration must be a finite number >= 0`,
|
|
2539
|
+
);
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
if (
|
|
2543
|
+
keyframe.delay !== undefined &&
|
|
2544
|
+
(!isFiniteNumber(keyframe.delay) || keyframe.delay < 0)
|
|
2545
|
+
) {
|
|
2546
|
+
return invalidFromErrorFactory(
|
|
2547
|
+
errorFactory,
|
|
2548
|
+
`${keyframePath}.delay must be a finite number >= 0 when provided`,
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
if (
|
|
2553
|
+
keyframe.startValue !== undefined &&
|
|
2554
|
+
!isFiniteNumber(keyframe.startValue)
|
|
2555
|
+
) {
|
|
2556
|
+
return invalidFromErrorFactory(
|
|
2557
|
+
errorFactory,
|
|
2558
|
+
`${keyframePath}.startValue must be a finite number when provided`,
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
if (
|
|
2563
|
+
keyframe.relative !== undefined &&
|
|
2564
|
+
typeof keyframe.relative !== "boolean"
|
|
2565
|
+
) {
|
|
2566
|
+
return invalidFromErrorFactory(
|
|
2567
|
+
errorFactory,
|
|
2568
|
+
`${keyframePath}.relative must be a boolean when provided`,
|
|
2569
|
+
);
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
{
|
|
2573
|
+
const result = validateAudioEffectEasing({
|
|
2574
|
+
value: keyframe.easing,
|
|
2575
|
+
path: `${keyframePath}.easing`,
|
|
2576
|
+
errorFactory,
|
|
2577
|
+
});
|
|
2578
|
+
if (result?.valid === false) {
|
|
2579
|
+
return result;
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
if (keyframe.relative !== true) {
|
|
2584
|
+
const result = validateAudioEffectAbsoluteValue({
|
|
2585
|
+
value: keyframe.value,
|
|
2586
|
+
property,
|
|
2587
|
+
path: `${keyframePath}.value`,
|
|
2588
|
+
errorFactory,
|
|
2589
|
+
});
|
|
2590
|
+
if (result?.valid === false) {
|
|
2591
|
+
return result;
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
if (keyframe.startValue !== undefined) {
|
|
2595
|
+
const result = validateAudioEffectAbsoluteValue({
|
|
2596
|
+
value: keyframe.startValue,
|
|
2597
|
+
property,
|
|
2598
|
+
path: `${keyframePath}.startValue`,
|
|
2599
|
+
errorFactory,
|
|
2600
|
+
});
|
|
2601
|
+
if (result?.valid === false) {
|
|
2602
|
+
return result;
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
const finalKeyframe = keyframes.at(-1);
|
|
2609
|
+
if (finalKeyframe.relative === true) {
|
|
2610
|
+
return invalidFromErrorFactory(
|
|
2611
|
+
errorFactory,
|
|
2612
|
+
`${path} final keyframe must use an absolute numeric value`,
|
|
2613
|
+
);
|
|
2614
|
+
}
|
|
2615
|
+
};
|
|
2616
|
+
|
|
2617
|
+
const validateAudioEffectTween = ({ tween, path, errorFactory }) => {
|
|
2618
|
+
if (!isPlainObject(tween)) {
|
|
2619
|
+
return invalidFromErrorFactory(errorFactory, `${path} must be an object`);
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
if (Object.keys(tween).length === 0) {
|
|
2623
|
+
return invalidFromErrorFactory(
|
|
2624
|
+
errorFactory,
|
|
2625
|
+
`${path} must contain at least one tween property`,
|
|
2626
|
+
);
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
for (const [property, config] of Object.entries(tween)) {
|
|
2630
|
+
if (!AUDIO_EFFECT_TWEEN_PROPERTY_KEYS.includes(property)) {
|
|
2631
|
+
return invalidFromErrorFactory(
|
|
2632
|
+
errorFactory,
|
|
2633
|
+
`${path}.${property} is not a supported audio effect tween property`,
|
|
2634
|
+
);
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
{
|
|
2638
|
+
const result = validateExactKeys({
|
|
2639
|
+
value: config,
|
|
2640
|
+
expectedKeys: ["keyframes"],
|
|
2641
|
+
path: `${path}.${property}`,
|
|
2642
|
+
errorFactory,
|
|
2643
|
+
});
|
|
2644
|
+
if (result?.valid === false) {
|
|
2645
|
+
return result;
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
{
|
|
2650
|
+
const result = validateAudioEffectKeyframes({
|
|
2651
|
+
keyframes: config.keyframes,
|
|
2652
|
+
property,
|
|
2653
|
+
path: `${path}.${property}.keyframes`,
|
|
2654
|
+
errorFactory,
|
|
2655
|
+
});
|
|
2656
|
+
if (result?.valid === false) {
|
|
2657
|
+
return result;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
};
|
|
2662
|
+
|
|
2663
|
+
const validateAudioEffectFade = ({ fade, path, side, errorFactory }) => {
|
|
2664
|
+
if (!isPlainObject(fade)) {
|
|
2665
|
+
return invalidFromErrorFactory(errorFactory, `${path} must be an object`);
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
if (Object.hasOwn(fade, "keyframes")) {
|
|
2669
|
+
{
|
|
2670
|
+
const result = validateExactKeys({
|
|
2671
|
+
value: fade,
|
|
2672
|
+
expectedKeys: ["keyframes"],
|
|
2673
|
+
path,
|
|
2674
|
+
errorFactory,
|
|
2675
|
+
});
|
|
2676
|
+
if (result?.valid === false) {
|
|
2677
|
+
return result;
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
{
|
|
2682
|
+
const result = validateAudioEffectKeyframes({
|
|
2683
|
+
keyframes: fade.keyframes,
|
|
2684
|
+
property: "volume",
|
|
2685
|
+
path: `${path}.keyframes`,
|
|
2686
|
+
errorFactory,
|
|
2687
|
+
});
|
|
2688
|
+
if (result?.valid === false) {
|
|
2689
|
+
return result;
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
for (const [index, keyframe] of fade.keyframes.entries()) {
|
|
2694
|
+
if (keyframe.relative === true) {
|
|
2695
|
+
return invalidFromErrorFactory(
|
|
2696
|
+
errorFactory,
|
|
2697
|
+
`${path}.keyframes[${index}].relative is not supported for transition fades`,
|
|
2698
|
+
);
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
return;
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
{
|
|
2706
|
+
const result = validateAllowedKeys({
|
|
2707
|
+
value: fade,
|
|
2708
|
+
allowedKeys: ["delay", "duration", "easing"],
|
|
2709
|
+
path,
|
|
2710
|
+
errorFactory,
|
|
2711
|
+
});
|
|
2712
|
+
if (result?.valid === false) {
|
|
2713
|
+
return result;
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
if (!Object.hasOwn(fade, "duration")) {
|
|
2718
|
+
return invalidFromErrorFactory(
|
|
2719
|
+
errorFactory,
|
|
2720
|
+
`${path}.duration is required`,
|
|
2721
|
+
);
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
if (!isFiniteNumber(fade.duration) || fade.duration < 0) {
|
|
2725
|
+
return invalidFromErrorFactory(
|
|
2726
|
+
errorFactory,
|
|
2727
|
+
`${path}.duration must be a finite number >= 0`,
|
|
2728
|
+
);
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
if (
|
|
2732
|
+
fade.delay !== undefined &&
|
|
2733
|
+
(!isFiniteNumber(fade.delay) || fade.delay < 0)
|
|
2734
|
+
) {
|
|
2735
|
+
return invalidFromErrorFactory(
|
|
2736
|
+
errorFactory,
|
|
2737
|
+
`${path}.delay must be a finite number >= 0 when provided`,
|
|
2738
|
+
);
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
return validateAudioEffectEasing({
|
|
2742
|
+
value: fade.easing,
|
|
2743
|
+
path: `${path}.easing`,
|
|
2744
|
+
errorFactory,
|
|
2745
|
+
});
|
|
2746
|
+
};
|
|
2747
|
+
|
|
2748
|
+
const validateAudioEffectTransitionSide = ({
|
|
2749
|
+
side,
|
|
2750
|
+
sideName,
|
|
2751
|
+
path,
|
|
2752
|
+
errorFactory,
|
|
2753
|
+
}) => {
|
|
2754
|
+
{
|
|
2755
|
+
const result = validateExactKeys({
|
|
2756
|
+
value: side,
|
|
2757
|
+
expectedKeys: ["fade"],
|
|
2758
|
+
path,
|
|
2759
|
+
errorFactory,
|
|
2760
|
+
});
|
|
2761
|
+
if (result?.valid === false) {
|
|
2762
|
+
return result;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
return validateAudioEffectFade({
|
|
2767
|
+
fade: side.fade,
|
|
2768
|
+
path: `${path}.fade`,
|
|
2769
|
+
side: sideName,
|
|
2770
|
+
errorFactory,
|
|
2771
|
+
});
|
|
2772
|
+
};
|
|
2773
|
+
|
|
2774
|
+
const validateAudioEffectDefinition = ({ audioEffect, path, errorFactory }) => {
|
|
2775
|
+
{
|
|
2776
|
+
const result = validateAllowedKeys({
|
|
2777
|
+
value: audioEffect,
|
|
2778
|
+
allowedKeys: ["type", "tween", "prev", "next"],
|
|
2779
|
+
path,
|
|
2780
|
+
errorFactory,
|
|
2781
|
+
});
|
|
2782
|
+
if (result?.valid === false) {
|
|
2783
|
+
return result;
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
if (audioEffect.type !== "update" && audioEffect.type !== "transition") {
|
|
2788
|
+
return invalidFromErrorFactory(
|
|
2789
|
+
errorFactory,
|
|
2790
|
+
`${path}.type must be 'update' or 'transition'`,
|
|
2791
|
+
);
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
if (audioEffect.type === "update") {
|
|
2795
|
+
if (audioEffect.prev !== undefined || audioEffect.next !== undefined) {
|
|
2796
|
+
return invalidFromErrorFactory(
|
|
2797
|
+
errorFactory,
|
|
2798
|
+
`${path}.update audio effects cannot define prev or next`,
|
|
2799
|
+
);
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
if (audioEffect.tween === undefined) {
|
|
2803
|
+
return invalidFromErrorFactory(
|
|
2804
|
+
errorFactory,
|
|
2805
|
+
`${path}.tween is required when ${path}.type is 'update'`,
|
|
2806
|
+
);
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
return validateAudioEffectTween({
|
|
2810
|
+
tween: audioEffect.tween,
|
|
2811
|
+
path: `${path}.tween`,
|
|
2812
|
+
errorFactory,
|
|
2813
|
+
});
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
if (audioEffect.tween !== undefined) {
|
|
2817
|
+
return invalidFromErrorFactory(
|
|
2818
|
+
errorFactory,
|
|
2819
|
+
`${path}.transition audio effects cannot define tween`,
|
|
2820
|
+
);
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
if (audioEffect.prev === undefined && audioEffect.next === undefined) {
|
|
2824
|
+
return invalidFromErrorFactory(
|
|
2825
|
+
errorFactory,
|
|
2826
|
+
`${path}.prev or ${path}.next is required when ${path}.type is 'transition'`,
|
|
2827
|
+
);
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
for (const side of ["prev", "next"]) {
|
|
2831
|
+
if (audioEffect[side] === undefined) {
|
|
2832
|
+
continue;
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
const result = validateAudioEffectTransitionSide({
|
|
2836
|
+
side: audioEffect[side],
|
|
2837
|
+
sideName: side,
|
|
2838
|
+
path: `${path}.${side}`,
|
|
2839
|
+
errorFactory,
|
|
2840
|
+
});
|
|
2841
|
+
if (result?.valid === false) {
|
|
2842
|
+
return result;
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
};
|
|
2846
|
+
|
|
2847
|
+
const validateAudioEffectPreviewSlot = ({ value, path, errorFactory }) => {
|
|
2848
|
+
if (value === undefined) {
|
|
2849
|
+
return VALID_RESULT;
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
if (!isPlainObject(value)) {
|
|
2853
|
+
return invalidFromErrorFactory(
|
|
2854
|
+
errorFactory,
|
|
2855
|
+
`${path} must be an object when provided`,
|
|
2856
|
+
);
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
{
|
|
2860
|
+
const result = validateAllowedKeys({
|
|
2861
|
+
value,
|
|
2862
|
+
allowedKeys: ["soundId"],
|
|
2863
|
+
path,
|
|
2864
|
+
errorFactory,
|
|
2865
|
+
});
|
|
2866
|
+
if (result?.valid === false) {
|
|
2867
|
+
return result;
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
if (value.soundId !== undefined && !isNonEmptyString(value.soundId)) {
|
|
2872
|
+
return invalidFromErrorFactory(
|
|
2873
|
+
errorFactory,
|
|
2874
|
+
`${path}.soundId must be a non-empty string when provided`,
|
|
2875
|
+
);
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2878
|
+
return VALID_RESULT;
|
|
2879
|
+
};
|
|
2880
|
+
|
|
2881
|
+
const validateAudioEffectPreviewObject = ({ value, path, errorFactory }) => {
|
|
2882
|
+
if (value === undefined) {
|
|
2883
|
+
return VALID_RESULT;
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
if (!isPlainObject(value)) {
|
|
2887
|
+
return invalidFromErrorFactory(
|
|
2888
|
+
errorFactory,
|
|
2889
|
+
`${path} must be an object when provided`,
|
|
2890
|
+
);
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
{
|
|
2894
|
+
const result = validateAllowedKeys({
|
|
2895
|
+
value,
|
|
2896
|
+
allowedKeys: ["target", "outgoing", "incoming"],
|
|
2897
|
+
path,
|
|
2898
|
+
errorFactory,
|
|
2899
|
+
});
|
|
2900
|
+
if (result?.valid === false) {
|
|
2901
|
+
return result;
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
for (const key of ["target", "outgoing", "incoming"]) {
|
|
2906
|
+
const result = validateAudioEffectPreviewSlot({
|
|
2907
|
+
value: value[key],
|
|
2908
|
+
path: `${path}.${key}`,
|
|
2909
|
+
errorFactory,
|
|
2910
|
+
});
|
|
2911
|
+
if (result?.valid === false) {
|
|
2912
|
+
return result;
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
return VALID_RESULT;
|
|
2917
|
+
};
|
|
2918
|
+
|
|
2919
|
+
const validateAudioEffectItems = ({ items, path, errorFactory }) => {
|
|
2920
|
+
for (const [itemId, item] of Object.entries(items)) {
|
|
2921
|
+
const itemPath = `${path}.${itemId}`;
|
|
2922
|
+
|
|
2923
|
+
if (item?.type !== "folder" && item?.type !== "audioEffect") {
|
|
2924
|
+
return invalidFromErrorFactory(
|
|
2925
|
+
errorFactory,
|
|
2926
|
+
`${itemPath}.type must be 'folder' or 'audioEffect'`,
|
|
2927
|
+
);
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
{
|
|
2931
|
+
const result = validateAllowedKeys({
|
|
2932
|
+
value: item,
|
|
2933
|
+
allowedKeys:
|
|
2934
|
+
item.type === "folder"
|
|
2935
|
+
? ["id", "type", "name", "description"]
|
|
2936
|
+
: [
|
|
2937
|
+
"id",
|
|
2938
|
+
"type",
|
|
2939
|
+
"name",
|
|
2940
|
+
"description",
|
|
2941
|
+
"tagIds",
|
|
2942
|
+
"preview",
|
|
2943
|
+
"audioEffect",
|
|
2944
|
+
],
|
|
2945
|
+
path: itemPath,
|
|
2946
|
+
errorFactory,
|
|
2947
|
+
});
|
|
2948
|
+
if (result?.valid === false) {
|
|
2949
|
+
return result;
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
if (!isNonEmptyString(item.id)) {
|
|
2954
|
+
return invalidFromErrorFactory(
|
|
2955
|
+
errorFactory,
|
|
2956
|
+
`${itemPath}.id must be a non-empty string`,
|
|
2957
|
+
);
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
if (item.id !== itemId) {
|
|
2961
|
+
return invalidFromErrorFactory(
|
|
2962
|
+
errorFactory,
|
|
2963
|
+
`${itemPath}.id must match item key '${itemId}'`,
|
|
2964
|
+
);
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
if (!isNonEmptyString(item.name)) {
|
|
2968
|
+
return invalidFromErrorFactory(
|
|
2969
|
+
errorFactory,
|
|
2970
|
+
`${itemPath}.name must be a non-empty string`,
|
|
2971
|
+
);
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
if (item.description !== undefined && !isString(item.description)) {
|
|
2975
|
+
return invalidFromErrorFactory(
|
|
2976
|
+
errorFactory,
|
|
2977
|
+
`${itemPath}.description must be a string when provided`,
|
|
2978
|
+
);
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
if (item.type !== "audioEffect") {
|
|
2982
|
+
continue;
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2985
|
+
{
|
|
2986
|
+
const result = validateAudioEffectPreviewObject({
|
|
2987
|
+
value: item.preview,
|
|
2988
|
+
path: `${itemPath}.preview`,
|
|
2989
|
+
errorFactory,
|
|
2990
|
+
});
|
|
2991
|
+
if (result?.valid === false) {
|
|
2992
|
+
return result;
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
{
|
|
2997
|
+
const result = validateOptionalUniqueIdArray({
|
|
2998
|
+
value: item.tagIds,
|
|
2999
|
+
path: `${itemPath}.tagIds`,
|
|
3000
|
+
errorFactory,
|
|
3001
|
+
allowEmpty: false,
|
|
3002
|
+
});
|
|
3003
|
+
if (result?.valid === false) {
|
|
3004
|
+
return result;
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
{
|
|
3009
|
+
const result = validateAudioEffectDefinition({
|
|
3010
|
+
audioEffect: item.audioEffect,
|
|
3011
|
+
path: `${itemPath}.audioEffect`,
|
|
3012
|
+
errorFactory,
|
|
3013
|
+
});
|
|
3014
|
+
if (result?.valid === false) {
|
|
3015
|
+
return result;
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
};
|
|
3020
|
+
|
|
2437
3021
|
const validateFontWeightFields = ({ value, path, errorFactory }) => {
|
|
2438
3022
|
const presentKeys = FONT_WEIGHT_KEYS.filter((key) =>
|
|
2439
3023
|
Object.hasOwn(value, key),
|
|
@@ -3759,33 +4343,126 @@ const validateComputedResultConfig = ({
|
|
|
3759
4343
|
if (dataResult?.valid === false) {
|
|
3760
4344
|
return dataResult;
|
|
3761
4345
|
}
|
|
3762
|
-
return validateVariableTypedValue({
|
|
3763
|
-
value: resultConfig.value,
|
|
3764
|
-
variableType,
|
|
3765
|
-
path: `${path}.value`,
|
|
3766
|
-
errorFactory,
|
|
3767
|
-
});
|
|
4346
|
+
return validateVariableTypedValue({
|
|
4347
|
+
value: resultConfig.value,
|
|
4348
|
+
variableType,
|
|
4349
|
+
path: `${path}.value`,
|
|
4350
|
+
errorFactory,
|
|
4351
|
+
});
|
|
4352
|
+
}
|
|
4353
|
+
|
|
4354
|
+
const expressionResult = validateComputedExpression({
|
|
4355
|
+
expression: resultConfig.expr,
|
|
4356
|
+
path: `${path}.expr`,
|
|
4357
|
+
errorFactory,
|
|
4358
|
+
variables,
|
|
4359
|
+
dependencies,
|
|
4360
|
+
});
|
|
4361
|
+
if (expressionResult?.valid === false) {
|
|
4362
|
+
return expressionResult;
|
|
4363
|
+
}
|
|
4364
|
+
if (
|
|
4365
|
+
expressionResult.valueType !== undefined &&
|
|
4366
|
+
expressionResult.valueType !== variableType
|
|
4367
|
+
) {
|
|
4368
|
+
return invalidFromErrorFactory(
|
|
4369
|
+
errorFactory,
|
|
4370
|
+
`${path}.expr must resolve to ${variableType}`,
|
|
4371
|
+
);
|
|
4372
|
+
}
|
|
4373
|
+
return VALID_RESULT;
|
|
4374
|
+
};
|
|
4375
|
+
|
|
4376
|
+
const validateComputedExamples = ({ examples, path, errorFactory }) => {
|
|
4377
|
+
if (!Array.isArray(examples)) {
|
|
4378
|
+
return invalidFromErrorFactory(errorFactory, `${path} must be an array`);
|
|
4379
|
+
}
|
|
4380
|
+
|
|
4381
|
+
const exampleIds = new Set();
|
|
4382
|
+
for (const [index, example] of examples.entries()) {
|
|
4383
|
+
const examplePath = `${path}[${index}]`;
|
|
4384
|
+
{
|
|
4385
|
+
const result = validateAllowedKeys({
|
|
4386
|
+
value: example,
|
|
4387
|
+
allowedKeys: ["id", "name", "input"],
|
|
4388
|
+
path: examplePath,
|
|
4389
|
+
errorFactory,
|
|
4390
|
+
});
|
|
4391
|
+
if (result?.valid === false) {
|
|
4392
|
+
return result;
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
|
|
4396
|
+
if (!isNonEmptyString(example.id)) {
|
|
4397
|
+
return invalidFromErrorFactory(
|
|
4398
|
+
errorFactory,
|
|
4399
|
+
`${examplePath}.id must be a non-empty string`,
|
|
4400
|
+
);
|
|
4401
|
+
}
|
|
4402
|
+
if (exampleIds.has(example.id)) {
|
|
4403
|
+
return invalidFromErrorFactory(
|
|
4404
|
+
errorFactory,
|
|
4405
|
+
`${examplePath}.id must be unique within examples`,
|
|
4406
|
+
);
|
|
4407
|
+
}
|
|
4408
|
+
exampleIds.add(example.id);
|
|
4409
|
+
|
|
4410
|
+
if (Object.hasOwn(example, "name") && !isNonEmptyString(example.name)) {
|
|
4411
|
+
return invalidFromErrorFactory(
|
|
4412
|
+
errorFactory,
|
|
4413
|
+
`${examplePath}.name must be a non-empty string`,
|
|
4414
|
+
);
|
|
4415
|
+
}
|
|
4416
|
+
|
|
4417
|
+
if (!Object.hasOwn(example, "input")) {
|
|
4418
|
+
return invalidFromErrorFactory(
|
|
4419
|
+
errorFactory,
|
|
4420
|
+
`${examplePath}.input is required`,
|
|
4421
|
+
);
|
|
4422
|
+
}
|
|
4423
|
+
if (
|
|
4424
|
+
!isPlainObject(example.input) ||
|
|
4425
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(example.input))
|
|
4426
|
+
) {
|
|
4427
|
+
return invalidFromErrorFactory(
|
|
4428
|
+
errorFactory,
|
|
4429
|
+
`${examplePath}.input must be an object`,
|
|
4430
|
+
);
|
|
4431
|
+
}
|
|
4432
|
+
{
|
|
4433
|
+
const result = validateAllowedKeys({
|
|
4434
|
+
value: example.input,
|
|
4435
|
+
allowedKeys: ["variables", "runtime"],
|
|
4436
|
+
path: `${examplePath}.input`,
|
|
4437
|
+
errorFactory,
|
|
4438
|
+
});
|
|
4439
|
+
if (result?.valid === false) {
|
|
4440
|
+
return result;
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4444
|
+
for (const namespace of ["variables", "runtime"]) {
|
|
4445
|
+
if (!Object.hasOwn(example.input, namespace)) {
|
|
4446
|
+
continue;
|
|
4447
|
+
}
|
|
4448
|
+
const namespacePath = `${examplePath}.input.${namespace}`;
|
|
4449
|
+
if (!isPlainObject(example.input[namespace])) {
|
|
4450
|
+
return invalidFromErrorFactory(
|
|
4451
|
+
errorFactory,
|
|
4452
|
+
`${namespacePath} must be an object`,
|
|
4453
|
+
);
|
|
4454
|
+
}
|
|
4455
|
+
const result = validateComputedDataValue({
|
|
4456
|
+
value: example.input[namespace],
|
|
4457
|
+
path: namespacePath,
|
|
4458
|
+
errorFactory,
|
|
4459
|
+
});
|
|
4460
|
+
if (result?.valid === false) {
|
|
4461
|
+
return result;
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
3768
4464
|
}
|
|
3769
4465
|
|
|
3770
|
-
const expressionResult = validateComputedExpression({
|
|
3771
|
-
expression: resultConfig.expr,
|
|
3772
|
-
path: `${path}.expr`,
|
|
3773
|
-
errorFactory,
|
|
3774
|
-
variables,
|
|
3775
|
-
dependencies,
|
|
3776
|
-
});
|
|
3777
|
-
if (expressionResult?.valid === false) {
|
|
3778
|
-
return expressionResult;
|
|
3779
|
-
}
|
|
3780
|
-
if (
|
|
3781
|
-
expressionResult.valueType !== undefined &&
|
|
3782
|
-
expressionResult.valueType !== variableType
|
|
3783
|
-
) {
|
|
3784
|
-
return invalidFromErrorFactory(
|
|
3785
|
-
errorFactory,
|
|
3786
|
-
`${path}.expr must resolve to ${variableType}`,
|
|
3787
|
-
);
|
|
3788
|
-
}
|
|
3789
4466
|
return VALID_RESULT;
|
|
3790
4467
|
};
|
|
3791
4468
|
|
|
@@ -3801,11 +4478,22 @@ const validateVariableComputedConfig = ({
|
|
|
3801
4478
|
return invalidFromErrorFactory(errorFactory, `${path} must be an object`);
|
|
3802
4479
|
}
|
|
3803
4480
|
|
|
4481
|
+
if (Object.hasOwn(computed, "examples")) {
|
|
4482
|
+
const result = validateComputedExamples({
|
|
4483
|
+
examples: computed.examples,
|
|
4484
|
+
path: `${path}.examples`,
|
|
4485
|
+
errorFactory,
|
|
4486
|
+
});
|
|
4487
|
+
if (result?.valid === false) {
|
|
4488
|
+
return result;
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
|
|
3804
4492
|
if (Object.hasOwn(computed, "branches")) {
|
|
3805
4493
|
{
|
|
3806
4494
|
const result = validateAllowedKeys({
|
|
3807
4495
|
value: computed,
|
|
3808
|
-
allowedKeys: ["branches", "default"],
|
|
4496
|
+
allowedKeys: ["branches", "default", "examples"],
|
|
3809
4497
|
path,
|
|
3810
4498
|
errorFactory,
|
|
3811
4499
|
});
|
|
@@ -3881,6 +4569,7 @@ const validateVariableComputedConfig = ({
|
|
|
3881
4569
|
errorFactory,
|
|
3882
4570
|
variables,
|
|
3883
4571
|
dependencies,
|
|
4572
|
+
allowedKeys: ["expr", "value", "examples"],
|
|
3884
4573
|
});
|
|
3885
4574
|
};
|
|
3886
4575
|
|
|
@@ -6750,6 +7439,15 @@ const stripDeletedTagIdsFromScopeItems = ({
|
|
|
6750
7439
|
return;
|
|
6751
7440
|
}
|
|
6752
7441
|
|
|
7442
|
+
if (scopeKey === "audioEffects") {
|
|
7443
|
+
for (const item of Object.values(state.audioEffects.items)) {
|
|
7444
|
+
if (item?.type === "audioEffect") {
|
|
7445
|
+
stripDeletedTagIdsFromItem({ item, deletedTagIds });
|
|
7446
|
+
}
|
|
7447
|
+
}
|
|
7448
|
+
return;
|
|
7449
|
+
}
|
|
7450
|
+
|
|
6753
7451
|
if (scopeKey === "particles") {
|
|
6754
7452
|
for (const item of Object.values(state.particles.items)) {
|
|
6755
7453
|
if (item?.type === "particle") {
|
|
@@ -7850,6 +8548,17 @@ const validateCollection = ({ collection, path }) => {
|
|
|
7850
8548
|
return result;
|
|
7851
8549
|
}
|
|
7852
8550
|
}
|
|
8551
|
+
} else if (path === "state.audioEffects") {
|
|
8552
|
+
{
|
|
8553
|
+
const result = validateAudioEffectItems({
|
|
8554
|
+
items: collection.items,
|
|
8555
|
+
path: `${path}.items`,
|
|
8556
|
+
errorFactory: createStateValidationError,
|
|
8557
|
+
});
|
|
8558
|
+
if (result?.valid === false) {
|
|
8559
|
+
return result;
|
|
8560
|
+
}
|
|
8561
|
+
}
|
|
7853
8562
|
} else if (path === "state.fonts") {
|
|
7854
8563
|
{
|
|
7855
8564
|
const result = validateFontItems({
|
|
@@ -8071,6 +8780,7 @@ const validateCollection = ({ collection, path }) => {
|
|
|
8071
8780
|
}
|
|
8072
8781
|
} else if (
|
|
8073
8782
|
path === "state.files" ||
|
|
8783
|
+
path === "state.audioEffects" ||
|
|
8074
8784
|
path === "state.particles" ||
|
|
8075
8785
|
path === "state.transforms" ||
|
|
8076
8786
|
path === "state.variables" ||
|
|
@@ -8085,11 +8795,13 @@ const validateCollection = ({ collection, path }) => {
|
|
|
8085
8795
|
items: collection.items,
|
|
8086
8796
|
path: `${path}.tree`,
|
|
8087
8797
|
folderLabel:
|
|
8088
|
-
path === "state.
|
|
8089
|
-
? "folder
|
|
8090
|
-
: path === "state.
|
|
8091
|
-
? "folder
|
|
8092
|
-
:
|
|
8798
|
+
path === "state.audioEffects"
|
|
8799
|
+
? "folder audio effect item"
|
|
8800
|
+
: path === "state.layouts"
|
|
8801
|
+
? "folder layout item"
|
|
8802
|
+
: path === "state.controls"
|
|
8803
|
+
? "folder control item"
|
|
8804
|
+
: "folder item",
|
|
8093
8805
|
});
|
|
8094
8806
|
if (result?.valid === false) {
|
|
8095
8807
|
return result;
|
|
@@ -8755,6 +9467,28 @@ export const assertInvariants = ({ state }) => {
|
|
|
8755
9467
|
}
|
|
8756
9468
|
}
|
|
8757
9469
|
|
|
9470
|
+
for (const [audioEffectId, audioEffect] of Object.entries(
|
|
9471
|
+
state.audioEffects.items,
|
|
9472
|
+
)) {
|
|
9473
|
+
if (audioEffect.type !== "audioEffect") {
|
|
9474
|
+
continue;
|
|
9475
|
+
}
|
|
9476
|
+
|
|
9477
|
+
const result = validateTagIdsAgainstScope({
|
|
9478
|
+
state,
|
|
9479
|
+
tagIds: audioEffect.tagIds,
|
|
9480
|
+
scopeKey: "audioEffects",
|
|
9481
|
+
path: "audioEffect.tagIds",
|
|
9482
|
+
details: {
|
|
9483
|
+
audioEffectId,
|
|
9484
|
+
},
|
|
9485
|
+
errorFactory: createInvariantValidationError,
|
|
9486
|
+
});
|
|
9487
|
+
if (!result.valid) {
|
|
9488
|
+
return result;
|
|
9489
|
+
}
|
|
9490
|
+
}
|
|
9491
|
+
|
|
8758
9492
|
for (const [characterId, character] of Object.entries(
|
|
8759
9493
|
state.characters.items,
|
|
8760
9494
|
)) {
|
|
@@ -11734,6 +12468,148 @@ const validateAnimationUpdateData = ({ data, errorFactory }) => {
|
|
|
11734
12468
|
}
|
|
11735
12469
|
};
|
|
11736
12470
|
|
|
12471
|
+
const validateAudioEffectCreateData = ({ data, errorFactory }) => {
|
|
12472
|
+
if (!isPlainObject(data)) {
|
|
12473
|
+
return invalidFromErrorFactory(
|
|
12474
|
+
errorFactory,
|
|
12475
|
+
"payload.data must be an object",
|
|
12476
|
+
);
|
|
12477
|
+
}
|
|
12478
|
+
|
|
12479
|
+
if (data.type !== "folder" && data.type !== "audioEffect") {
|
|
12480
|
+
return invalidFromErrorFactory(
|
|
12481
|
+
errorFactory,
|
|
12482
|
+
"payload.data.type must be 'folder' or 'audioEffect'",
|
|
12483
|
+
);
|
|
12484
|
+
}
|
|
12485
|
+
|
|
12486
|
+
{
|
|
12487
|
+
const result = validateAllowedKeys({
|
|
12488
|
+
value: data,
|
|
12489
|
+
allowedKeys:
|
|
12490
|
+
data.type === "folder"
|
|
12491
|
+
? ["type", "name", "description"]
|
|
12492
|
+
: ["type", "name", "description", "tagIds", "preview", "audioEffect"],
|
|
12493
|
+
path: "payload.data",
|
|
12494
|
+
errorFactory,
|
|
12495
|
+
});
|
|
12496
|
+
if (result?.valid === false) {
|
|
12497
|
+
return result;
|
|
12498
|
+
}
|
|
12499
|
+
}
|
|
12500
|
+
|
|
12501
|
+
if (!isNonEmptyString(data.name)) {
|
|
12502
|
+
return invalidFromErrorFactory(
|
|
12503
|
+
errorFactory,
|
|
12504
|
+
"payload.data.name must be a non-empty string",
|
|
12505
|
+
);
|
|
12506
|
+
}
|
|
12507
|
+
|
|
12508
|
+
if (data.description !== undefined && !isString(data.description)) {
|
|
12509
|
+
return invalidFromErrorFactory(
|
|
12510
|
+
errorFactory,
|
|
12511
|
+
"payload.data.description must be a string when provided",
|
|
12512
|
+
);
|
|
12513
|
+
}
|
|
12514
|
+
|
|
12515
|
+
if (data.type !== "audioEffect") {
|
|
12516
|
+
return;
|
|
12517
|
+
}
|
|
12518
|
+
|
|
12519
|
+
{
|
|
12520
|
+
const result = validateAudioEffectPreviewObject({
|
|
12521
|
+
value: data.preview,
|
|
12522
|
+
path: "payload.data.preview",
|
|
12523
|
+
errorFactory,
|
|
12524
|
+
});
|
|
12525
|
+
if (result?.valid === false) {
|
|
12526
|
+
return result;
|
|
12527
|
+
}
|
|
12528
|
+
}
|
|
12529
|
+
|
|
12530
|
+
{
|
|
12531
|
+
const result = validateOptionalUniqueIdArray({
|
|
12532
|
+
value: data.tagIds,
|
|
12533
|
+
path: "payload.data.tagIds",
|
|
12534
|
+
errorFactory,
|
|
12535
|
+
});
|
|
12536
|
+
if (result?.valid === false) {
|
|
12537
|
+
return result;
|
|
12538
|
+
}
|
|
12539
|
+
}
|
|
12540
|
+
|
|
12541
|
+
return validateAudioEffectDefinition({
|
|
12542
|
+
audioEffect: data.audioEffect,
|
|
12543
|
+
path: "payload.data.audioEffect",
|
|
12544
|
+
errorFactory,
|
|
12545
|
+
});
|
|
12546
|
+
};
|
|
12547
|
+
|
|
12548
|
+
const validateAudioEffectUpdateData = ({ data, errorFactory }) => {
|
|
12549
|
+
{
|
|
12550
|
+
const result = validateAllowedKeys({
|
|
12551
|
+
value: data,
|
|
12552
|
+
allowedKeys: ["name", "description", "tagIds", "preview", "audioEffect"],
|
|
12553
|
+
path: "payload.data",
|
|
12554
|
+
errorFactory,
|
|
12555
|
+
});
|
|
12556
|
+
if (result?.valid === false) {
|
|
12557
|
+
return result;
|
|
12558
|
+
}
|
|
12559
|
+
}
|
|
12560
|
+
|
|
12561
|
+
if (Object.keys(data).length === 0) {
|
|
12562
|
+
return invalidFromErrorFactory(
|
|
12563
|
+
errorFactory,
|
|
12564
|
+
"payload.data must include at least one updatable field",
|
|
12565
|
+
);
|
|
12566
|
+
}
|
|
12567
|
+
|
|
12568
|
+
if (Object.hasOwn(data, "name") && !isNonEmptyString(data.name)) {
|
|
12569
|
+
return invalidFromErrorFactory(
|
|
12570
|
+
errorFactory,
|
|
12571
|
+
"payload.data.name must be a non-empty string when provided",
|
|
12572
|
+
);
|
|
12573
|
+
}
|
|
12574
|
+
|
|
12575
|
+
if (data.description !== undefined && !isString(data.description)) {
|
|
12576
|
+
return invalidFromErrorFactory(
|
|
12577
|
+
errorFactory,
|
|
12578
|
+
"payload.data.description must be a string when provided",
|
|
12579
|
+
);
|
|
12580
|
+
}
|
|
12581
|
+
|
|
12582
|
+
{
|
|
12583
|
+
const result = validateAudioEffectPreviewObject({
|
|
12584
|
+
value: data.preview,
|
|
12585
|
+
path: "payload.data.preview",
|
|
12586
|
+
errorFactory,
|
|
12587
|
+
});
|
|
12588
|
+
if (result?.valid === false) {
|
|
12589
|
+
return result;
|
|
12590
|
+
}
|
|
12591
|
+
}
|
|
12592
|
+
|
|
12593
|
+
{
|
|
12594
|
+
const result = validateOptionalUniqueIdArray({
|
|
12595
|
+
value: data.tagIds,
|
|
12596
|
+
path: "payload.data.tagIds",
|
|
12597
|
+
errorFactory,
|
|
12598
|
+
});
|
|
12599
|
+
if (result?.valid === false) {
|
|
12600
|
+
return result;
|
|
12601
|
+
}
|
|
12602
|
+
}
|
|
12603
|
+
|
|
12604
|
+
if (Object.hasOwn(data, "audioEffect")) {
|
|
12605
|
+
return validateAudioEffectDefinition({
|
|
12606
|
+
audioEffect: data.audioEffect,
|
|
12607
|
+
path: "payload.data.audioEffect",
|
|
12608
|
+
errorFactory,
|
|
12609
|
+
});
|
|
12610
|
+
}
|
|
12611
|
+
};
|
|
12612
|
+
|
|
11737
12613
|
const validateTransformCreateData = ({ data, errorFactory }) => {
|
|
11738
12614
|
if (!isPlainObject(data)) {
|
|
11739
12615
|
return invalidFromErrorFactory(
|
|
@@ -18545,6 +19421,87 @@ const COMMAND_DEFINITIONS = [
|
|
|
18545
19421
|
return state;
|
|
18546
19422
|
},
|
|
18547
19423
|
},
|
|
19424
|
+
...createFolderedCollectionCommandDefinitions({
|
|
19425
|
+
familyName: "audioEffect",
|
|
19426
|
+
collectionKey: "audioEffects",
|
|
19427
|
+
idField: "audioEffectId",
|
|
19428
|
+
itemLabel: "audio effect item",
|
|
19429
|
+
createDataValidator: validateAudioEffectCreateData,
|
|
19430
|
+
updateDataValidator: validateAudioEffectUpdateData,
|
|
19431
|
+
createItem: ({ payload }) => {
|
|
19432
|
+
const item = {
|
|
19433
|
+
id: payload.audioEffectId,
|
|
19434
|
+
type: payload.data.type,
|
|
19435
|
+
name: payload.data.name,
|
|
19436
|
+
};
|
|
19437
|
+
|
|
19438
|
+
if (payload.data.description !== undefined) {
|
|
19439
|
+
item.description = payload.data.description;
|
|
19440
|
+
}
|
|
19441
|
+
|
|
19442
|
+
if (payload.data.type === "audioEffect") {
|
|
19443
|
+
assignOptionalTagIds({
|
|
19444
|
+
target: item,
|
|
19445
|
+
tagIds: payload.data.tagIds,
|
|
19446
|
+
});
|
|
19447
|
+
if (payload.data.preview !== undefined) {
|
|
19448
|
+
item.preview = structuredClone(payload.data.preview);
|
|
19449
|
+
}
|
|
19450
|
+
item.audioEffect = structuredClone(payload.data.audioEffect);
|
|
19451
|
+
}
|
|
19452
|
+
|
|
19453
|
+
return item;
|
|
19454
|
+
},
|
|
19455
|
+
updateItem: ({ currentItem, payload }) =>
|
|
19456
|
+
applyTagIdsUpdate({
|
|
19457
|
+
currentItem,
|
|
19458
|
+
data: payload.data,
|
|
19459
|
+
}),
|
|
19460
|
+
validateCreateState: ({ state, payload }) => {
|
|
19461
|
+
if (payload.data.type !== "audioEffect") {
|
|
19462
|
+
return;
|
|
19463
|
+
}
|
|
19464
|
+
|
|
19465
|
+
return validateTagIdsAgainstScope({
|
|
19466
|
+
state,
|
|
19467
|
+
tagIds: payload.data.tagIds,
|
|
19468
|
+
scopeKey: "audioEffects",
|
|
19469
|
+
path: "payload.data.tagIds",
|
|
19470
|
+
details: {
|
|
19471
|
+
audioEffectId: payload.audioEffectId,
|
|
19472
|
+
},
|
|
19473
|
+
errorFactory: createPreconditionValidationError,
|
|
19474
|
+
});
|
|
19475
|
+
},
|
|
19476
|
+
validateUpdateState: ({ state, payload, currentItem }) => {
|
|
19477
|
+
if (
|
|
19478
|
+
currentItem.type === "folder" &&
|
|
19479
|
+
Object.keys(payload.data).some(
|
|
19480
|
+
(key) => key !== "name" && key !== "description",
|
|
19481
|
+
)
|
|
19482
|
+
) {
|
|
19483
|
+
return invalidFromErrorFactory(
|
|
19484
|
+
createPreconditionValidationError,
|
|
19485
|
+
"folder audio effect items cannot update audio effect fields",
|
|
19486
|
+
);
|
|
19487
|
+
}
|
|
19488
|
+
|
|
19489
|
+
if (currentItem.type !== "audioEffect") {
|
|
19490
|
+
return;
|
|
19491
|
+
}
|
|
19492
|
+
|
|
19493
|
+
return validateTagIdsAgainstScope({
|
|
19494
|
+
state,
|
|
19495
|
+
tagIds: payload.data.tagIds,
|
|
19496
|
+
scopeKey: "audioEffects",
|
|
19497
|
+
path: "payload.data.tagIds",
|
|
19498
|
+
details: {
|
|
19499
|
+
audioEffectId: payload.audioEffectId,
|
|
19500
|
+
},
|
|
19501
|
+
errorFactory: createPreconditionValidationError,
|
|
19502
|
+
});
|
|
19503
|
+
},
|
|
19504
|
+
}),
|
|
18548
19505
|
{
|
|
18549
19506
|
type: "font.create",
|
|
18550
19507
|
validatePayload: ({ payload }) => {
|