@routevn/creator-model 1.8.0 → 1.9.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/package.json +1 -1
- package/src/model.js +881 -81
package/package.json
CHANGED
package/src/model.js
CHANGED
|
@@ -23,6 +23,7 @@ const COLLECTION_KEYS = [
|
|
|
23
23
|
"images",
|
|
24
24
|
"spritesheets",
|
|
25
25
|
"sounds",
|
|
26
|
+
"voices",
|
|
26
27
|
"videos",
|
|
27
28
|
"animations",
|
|
28
29
|
"particles",
|
|
@@ -245,6 +246,7 @@ const normalizeStateCollections = (state) => {
|
|
|
245
246
|
"spritesheets",
|
|
246
247
|
"particles",
|
|
247
248
|
"controls",
|
|
249
|
+
"voices",
|
|
248
250
|
].filter((key) => state[key] === undefined);
|
|
249
251
|
const normalizedTags = normalizeTagsState(state.tags);
|
|
250
252
|
const hasNormalizedTags = normalizedTags !== state.tags;
|
|
@@ -392,7 +394,7 @@ const LAYOUT_ELEMENT_BASE_TYPES = [
|
|
|
392
394
|
"container-ref-confirm-dialog-ok",
|
|
393
395
|
"container-ref-confirm-dialog-cancel",
|
|
394
396
|
];
|
|
395
|
-
export const SCHEMA_VERSION =
|
|
397
|
+
export const SCHEMA_VERSION = 9;
|
|
396
398
|
const LAYOUT_CONTAINER_ELEMENT_TYPES = [
|
|
397
399
|
"folder",
|
|
398
400
|
"container",
|
|
@@ -1443,6 +1445,105 @@ const validateSoundItems = ({ items, path, errorFactory }) => {
|
|
|
1443
1445
|
}
|
|
1444
1446
|
};
|
|
1445
1447
|
|
|
1448
|
+
const validateVoiceItems = ({ items, path, errorFactory }) => {
|
|
1449
|
+
for (const [itemId, item] of Object.entries(items)) {
|
|
1450
|
+
const itemPath = `${path}.${itemId}`;
|
|
1451
|
+
|
|
1452
|
+
if (item?.type !== "folder" && item?.type !== "voice") {
|
|
1453
|
+
return invalidFromErrorFactory(
|
|
1454
|
+
errorFactory,
|
|
1455
|
+
`${itemPath}.type must be 'folder' or 'voice'`,
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
{
|
|
1460
|
+
const result = validateAllowedKeys({
|
|
1461
|
+
value: item,
|
|
1462
|
+
allowedKeys:
|
|
1463
|
+
item.type === "folder"
|
|
1464
|
+
? ["id", "type", "name", "description"]
|
|
1465
|
+
: [
|
|
1466
|
+
"id",
|
|
1467
|
+
"type",
|
|
1468
|
+
"name",
|
|
1469
|
+
"description",
|
|
1470
|
+
"sceneId",
|
|
1471
|
+
"fileId",
|
|
1472
|
+
"waveformDataFileId",
|
|
1473
|
+
"duration",
|
|
1474
|
+
],
|
|
1475
|
+
path: itemPath,
|
|
1476
|
+
errorFactory,
|
|
1477
|
+
});
|
|
1478
|
+
if (result?.valid === false) {
|
|
1479
|
+
return result;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
if (!isNonEmptyString(item.id)) {
|
|
1484
|
+
return invalidFromErrorFactory(
|
|
1485
|
+
errorFactory,
|
|
1486
|
+
`${itemPath}.id must be a non-empty string`,
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
if (item.id !== itemId) {
|
|
1491
|
+
return invalidFromErrorFactory(
|
|
1492
|
+
errorFactory,
|
|
1493
|
+
`${itemPath}.id must match item key '${itemId}'`,
|
|
1494
|
+
);
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
if (!isNonEmptyString(item.name)) {
|
|
1498
|
+
return invalidFromErrorFactory(
|
|
1499
|
+
errorFactory,
|
|
1500
|
+
`${itemPath}.name must be a non-empty string`,
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
if (item.description !== undefined && !isString(item.description)) {
|
|
1505
|
+
return invalidFromErrorFactory(
|
|
1506
|
+
errorFactory,
|
|
1507
|
+
`${itemPath}.description must be a string when provided`,
|
|
1508
|
+
);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
if (item.type === "voice") {
|
|
1512
|
+
if (!isNonEmptyString(item.sceneId)) {
|
|
1513
|
+
return invalidFromErrorFactory(
|
|
1514
|
+
errorFactory,
|
|
1515
|
+
`${itemPath}.sceneId must be a non-empty string`,
|
|
1516
|
+
);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
if (!isNonEmptyString(item.fileId)) {
|
|
1520
|
+
return invalidFromErrorFactory(
|
|
1521
|
+
errorFactory,
|
|
1522
|
+
`${itemPath}.fileId must be a non-empty string`,
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
if (
|
|
1527
|
+
item.waveformDataFileId !== undefined &&
|
|
1528
|
+
item.waveformDataFileId !== null &&
|
|
1529
|
+
!isNonEmptyString(item.waveformDataFileId)
|
|
1530
|
+
) {
|
|
1531
|
+
return invalidFromErrorFactory(
|
|
1532
|
+
errorFactory,
|
|
1533
|
+
`${itemPath}.waveformDataFileId must be a non-empty string or null when provided`,
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
if (item.duration !== undefined && !isFiniteNumber(item.duration)) {
|
|
1538
|
+
return invalidFromErrorFactory(
|
|
1539
|
+
errorFactory,
|
|
1540
|
+
`${itemPath}.duration must be a finite number`,
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
};
|
|
1546
|
+
|
|
1446
1547
|
const validateVideoItems = ({ items, path, errorFactory }) => {
|
|
1447
1548
|
for (const [itemId, item] of Object.entries(items)) {
|
|
1448
1549
|
const itemPath = `${path}.${itemId}`;
|
|
@@ -3716,6 +3817,7 @@ const validateLayoutElementData = ({
|
|
|
3716
3817
|
"clickImageId",
|
|
3717
3818
|
"hoverSoundId",
|
|
3718
3819
|
"clickSoundId",
|
|
3820
|
+
"revealSoundId",
|
|
3719
3821
|
"textStyleId",
|
|
3720
3822
|
"hoverTextStyleId",
|
|
3721
3823
|
"clickTextStyleId",
|
|
@@ -3943,6 +4045,7 @@ const validateLayoutElementData = ({
|
|
|
3943
4045
|
"clickImageId",
|
|
3944
4046
|
"hoverSoundId",
|
|
3945
4047
|
"clickSoundId",
|
|
4048
|
+
"revealSoundId",
|
|
3946
4049
|
"textStyleId",
|
|
3947
4050
|
"hoverTextStyleId",
|
|
3948
4051
|
"clickTextStyleId",
|
|
@@ -4461,6 +4564,7 @@ const validateLayoutElementItems = ({ items, path, errorFactory }) => {
|
|
|
4461
4564
|
"clickImageId",
|
|
4462
4565
|
"hoverSoundId",
|
|
4463
4566
|
"clickSoundId",
|
|
4567
|
+
"revealSoundId",
|
|
4464
4568
|
"textStyleId",
|
|
4465
4569
|
"hoverTextStyleId",
|
|
4466
4570
|
"clickTextStyleId",
|
|
@@ -6411,6 +6515,17 @@ const validateCollection = ({ collection, path }) => {
|
|
|
6411
6515
|
return result;
|
|
6412
6516
|
}
|
|
6413
6517
|
}
|
|
6518
|
+
} else if (path === "state.voices") {
|
|
6519
|
+
{
|
|
6520
|
+
const result = validateVoiceItems({
|
|
6521
|
+
items: collection.items,
|
|
6522
|
+
path: `${path}.items`,
|
|
6523
|
+
errorFactory: createStateValidationError,
|
|
6524
|
+
});
|
|
6525
|
+
if (result?.valid === false) {
|
|
6526
|
+
return result;
|
|
6527
|
+
}
|
|
6528
|
+
}
|
|
6414
6529
|
} else if (path === "state.videos") {
|
|
6415
6530
|
{
|
|
6416
6531
|
const result = validateVideoItems({
|
|
@@ -6596,6 +6711,18 @@ const validateCollection = ({ collection, path }) => {
|
|
|
6596
6711
|
return result;
|
|
6597
6712
|
}
|
|
6598
6713
|
}
|
|
6714
|
+
} else if (path === "state.voices") {
|
|
6715
|
+
{
|
|
6716
|
+
const result = validateGenericFolderOwnership({
|
|
6717
|
+
nodes: collection.tree,
|
|
6718
|
+
items: collection.items,
|
|
6719
|
+
path: `${path}.tree`,
|
|
6720
|
+
folderLabel: "folder voice item",
|
|
6721
|
+
});
|
|
6722
|
+
if (result?.valid === false) {
|
|
6723
|
+
return result;
|
|
6724
|
+
}
|
|
6725
|
+
}
|
|
6599
6726
|
} else if (path === "state.videos") {
|
|
6600
6727
|
{
|
|
6601
6728
|
const result = validateVideoTreeFolderOwnership({
|
|
@@ -7116,6 +7243,55 @@ export const assertInvariants = ({ state }) => {
|
|
|
7116
7243
|
}
|
|
7117
7244
|
}
|
|
7118
7245
|
|
|
7246
|
+
for (const [voiceId, voice] of Object.entries(state.voices.items)) {
|
|
7247
|
+
if (voice.type !== "voice") {
|
|
7248
|
+
continue;
|
|
7249
|
+
}
|
|
7250
|
+
|
|
7251
|
+
if (!isPlainObject(sceneItems[voice.sceneId])) {
|
|
7252
|
+
return invalidInvariant(
|
|
7253
|
+
"voice.sceneId must reference an existing scene",
|
|
7254
|
+
{ voiceId, sceneId: voice.sceneId },
|
|
7255
|
+
);
|
|
7256
|
+
}
|
|
7257
|
+
|
|
7258
|
+
if (sceneItems[voice.sceneId].type === "folder") {
|
|
7259
|
+
return invalidInvariant(
|
|
7260
|
+
"voice.sceneId must reference a non-folder scene",
|
|
7261
|
+
{ voiceId, sceneId: voice.sceneId },
|
|
7262
|
+
);
|
|
7263
|
+
}
|
|
7264
|
+
|
|
7265
|
+
{
|
|
7266
|
+
const result = validateFileReference({
|
|
7267
|
+
state,
|
|
7268
|
+
fileId: voice.fileId,
|
|
7269
|
+
path: "voice.fileId",
|
|
7270
|
+
details: { voiceId, fileId: voice.fileId },
|
|
7271
|
+
errorFactory: createInvariantValidationError,
|
|
7272
|
+
});
|
|
7273
|
+
if (!result.valid) {
|
|
7274
|
+
return result;
|
|
7275
|
+
}
|
|
7276
|
+
}
|
|
7277
|
+
|
|
7278
|
+
if (
|
|
7279
|
+
voice.waveformDataFileId !== undefined &&
|
|
7280
|
+
voice.waveformDataFileId !== null
|
|
7281
|
+
) {
|
|
7282
|
+
const result = validateFileReference({
|
|
7283
|
+
state,
|
|
7284
|
+
fileId: voice.waveformDataFileId,
|
|
7285
|
+
path: "voice.waveformDataFileId",
|
|
7286
|
+
details: { voiceId, waveformDataFileId: voice.waveformDataFileId },
|
|
7287
|
+
errorFactory: createInvariantValidationError,
|
|
7288
|
+
});
|
|
7289
|
+
if (!result.valid) {
|
|
7290
|
+
return result;
|
|
7291
|
+
}
|
|
7292
|
+
}
|
|
7293
|
+
}
|
|
7294
|
+
|
|
7119
7295
|
for (const [videoId, video] of Object.entries(state.videos.items)) {
|
|
7120
7296
|
if (video.type !== "video") {
|
|
7121
7297
|
continue;
|
|
@@ -7887,7 +8063,7 @@ export const assertInvariants = ({ state }) => {
|
|
|
7887
8063
|
}
|
|
7888
8064
|
}
|
|
7889
8065
|
|
|
7890
|
-
for (const field of ["hoverSoundId", "clickSoundId"]) {
|
|
8066
|
+
for (const field of ["hoverSoundId", "clickSoundId", "revealSoundId"]) {
|
|
7891
8067
|
if (element[field] !== undefined) {
|
|
7892
8068
|
const result = assertSoundReference({
|
|
7893
8069
|
ownerIdField,
|
|
@@ -9274,7 +9450,7 @@ const validateSoundUpdateData = ({ data, errorFactory }) => {
|
|
|
9274
9450
|
}
|
|
9275
9451
|
};
|
|
9276
9452
|
|
|
9277
|
-
const
|
|
9453
|
+
const validateVoiceCreateData = ({ data, errorFactory }) => {
|
|
9278
9454
|
if (!isPlainObject(data)) {
|
|
9279
9455
|
return invalidFromErrorFactory(
|
|
9280
9456
|
errorFactory,
|
|
@@ -9282,10 +9458,10 @@ const validateVideoCreateData = ({ data, errorFactory }) => {
|
|
|
9282
9458
|
);
|
|
9283
9459
|
}
|
|
9284
9460
|
|
|
9285
|
-
if (data.type !== "folder" && data.type !== "
|
|
9461
|
+
if (data.type !== "folder" && data.type !== "voice") {
|
|
9286
9462
|
return invalidFromErrorFactory(
|
|
9287
9463
|
errorFactory,
|
|
9288
|
-
"payload.data.type must be 'folder' or '
|
|
9464
|
+
"payload.data.type must be 'folder' or 'voice'",
|
|
9289
9465
|
);
|
|
9290
9466
|
}
|
|
9291
9467
|
|
|
@@ -9299,12 +9475,10 @@ const validateVideoCreateData = ({ data, errorFactory }) => {
|
|
|
9299
9475
|
"type",
|
|
9300
9476
|
"name",
|
|
9301
9477
|
"description",
|
|
9302
|
-
"
|
|
9478
|
+
"sceneId",
|
|
9303
9479
|
"fileId",
|
|
9304
|
-
"
|
|
9480
|
+
"waveformDataFileId",
|
|
9305
9481
|
"duration",
|
|
9306
|
-
"width",
|
|
9307
|
-
"height",
|
|
9308
9482
|
],
|
|
9309
9483
|
path: "payload.data",
|
|
9310
9484
|
errorFactory,
|
|
@@ -9328,16 +9502,12 @@ const validateVideoCreateData = ({ data, errorFactory }) => {
|
|
|
9328
9502
|
);
|
|
9329
9503
|
}
|
|
9330
9504
|
|
|
9331
|
-
if (data.type === "
|
|
9332
|
-
{
|
|
9333
|
-
|
|
9334
|
-
value: data.tagIds,
|
|
9335
|
-
path: "payload.data.tagIds",
|
|
9505
|
+
if (data.type === "voice") {
|
|
9506
|
+
if (!isNonEmptyString(data.sceneId)) {
|
|
9507
|
+
return invalidFromErrorFactory(
|
|
9336
9508
|
errorFactory,
|
|
9337
|
-
|
|
9338
|
-
|
|
9339
|
-
return result;
|
|
9340
|
-
}
|
|
9509
|
+
"payload.data.sceneId must be a non-empty string",
|
|
9510
|
+
);
|
|
9341
9511
|
}
|
|
9342
9512
|
|
|
9343
9513
|
if (!isNonEmptyString(data.fileId)) {
|
|
@@ -9347,10 +9517,14 @@ const validateVideoCreateData = ({ data, errorFactory }) => {
|
|
|
9347
9517
|
);
|
|
9348
9518
|
}
|
|
9349
9519
|
|
|
9350
|
-
if (
|
|
9520
|
+
if (
|
|
9521
|
+
data.waveformDataFileId !== undefined &&
|
|
9522
|
+
data.waveformDataFileId !== null &&
|
|
9523
|
+
!isNonEmptyString(data.waveformDataFileId)
|
|
9524
|
+
) {
|
|
9351
9525
|
return invalidFromErrorFactory(
|
|
9352
9526
|
errorFactory,
|
|
9353
|
-
"payload.data.
|
|
9527
|
+
"payload.data.waveformDataFileId must be a non-empty string or null when provided",
|
|
9354
9528
|
);
|
|
9355
9529
|
}
|
|
9356
9530
|
|
|
@@ -9360,36 +9534,20 @@ const validateVideoCreateData = ({ data, errorFactory }) => {
|
|
|
9360
9534
|
"payload.data.duration must be a finite number",
|
|
9361
9535
|
);
|
|
9362
9536
|
}
|
|
9363
|
-
|
|
9364
|
-
if (data.width !== undefined && !isFiniteNumber(data.width)) {
|
|
9365
|
-
return invalidFromErrorFactory(
|
|
9366
|
-
errorFactory,
|
|
9367
|
-
"payload.data.width must be a finite number",
|
|
9368
|
-
);
|
|
9369
|
-
}
|
|
9370
|
-
|
|
9371
|
-
if (data.height !== undefined && !isFiniteNumber(data.height)) {
|
|
9372
|
-
return invalidFromErrorFactory(
|
|
9373
|
-
errorFactory,
|
|
9374
|
-
"payload.data.height must be a finite number",
|
|
9375
|
-
);
|
|
9376
|
-
}
|
|
9377
9537
|
}
|
|
9378
9538
|
};
|
|
9379
9539
|
|
|
9380
|
-
const
|
|
9540
|
+
const validateVoiceUpdateData = ({ data, errorFactory }) => {
|
|
9381
9541
|
{
|
|
9382
9542
|
const result = validateAllowedKeys({
|
|
9383
9543
|
value: data,
|
|
9384
9544
|
allowedKeys: [
|
|
9385
9545
|
"name",
|
|
9386
9546
|
"description",
|
|
9387
|
-
"
|
|
9547
|
+
"sceneId",
|
|
9388
9548
|
"fileId",
|
|
9389
|
-
"
|
|
9549
|
+
"waveformDataFileId",
|
|
9390
9550
|
"duration",
|
|
9391
|
-
"width",
|
|
9392
|
-
"height",
|
|
9393
9551
|
],
|
|
9394
9552
|
path: "payload.data",
|
|
9395
9553
|
errorFactory,
|
|
@@ -9420,15 +9578,11 @@ const validateVideoUpdateData = ({ data, errorFactory }) => {
|
|
|
9420
9578
|
);
|
|
9421
9579
|
}
|
|
9422
9580
|
|
|
9423
|
-
{
|
|
9424
|
-
|
|
9425
|
-
value: data.tagIds,
|
|
9426
|
-
path: "payload.data.tagIds",
|
|
9581
|
+
if (data.sceneId !== undefined && !isNonEmptyString(data.sceneId)) {
|
|
9582
|
+
return invalidFromErrorFactory(
|
|
9427
9583
|
errorFactory,
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
return result;
|
|
9431
|
-
}
|
|
9584
|
+
"payload.data.sceneId must be a non-empty string when provided",
|
|
9585
|
+
);
|
|
9432
9586
|
}
|
|
9433
9587
|
|
|
9434
9588
|
if (data.fileId !== undefined && !isNonEmptyString(data.fileId)) {
|
|
@@ -9439,12 +9593,13 @@ const validateVideoUpdateData = ({ data, errorFactory }) => {
|
|
|
9439
9593
|
}
|
|
9440
9594
|
|
|
9441
9595
|
if (
|
|
9442
|
-
data.
|
|
9443
|
-
|
|
9596
|
+
data.waveformDataFileId !== undefined &&
|
|
9597
|
+
data.waveformDataFileId !== null &&
|
|
9598
|
+
!isNonEmptyString(data.waveformDataFileId)
|
|
9444
9599
|
) {
|
|
9445
9600
|
return invalidFromErrorFactory(
|
|
9446
9601
|
errorFactory,
|
|
9447
|
-
"payload.data.
|
|
9602
|
+
"payload.data.waveformDataFileId must be a non-empty string or null when provided",
|
|
9448
9603
|
);
|
|
9449
9604
|
}
|
|
9450
9605
|
|
|
@@ -9454,23 +9609,9 @@ const validateVideoUpdateData = ({ data, errorFactory }) => {
|
|
|
9454
9609
|
"payload.data.duration must be a finite number",
|
|
9455
9610
|
);
|
|
9456
9611
|
}
|
|
9457
|
-
|
|
9458
|
-
if (data.width !== undefined && !isFiniteNumber(data.width)) {
|
|
9459
|
-
return invalidFromErrorFactory(
|
|
9460
|
-
errorFactory,
|
|
9461
|
-
"payload.data.width must be a finite number",
|
|
9462
|
-
);
|
|
9463
|
-
}
|
|
9464
|
-
|
|
9465
|
-
if (data.height !== undefined && !isFiniteNumber(data.height)) {
|
|
9466
|
-
return invalidFromErrorFactory(
|
|
9467
|
-
errorFactory,
|
|
9468
|
-
"payload.data.height must be a finite number",
|
|
9469
|
-
);
|
|
9470
|
-
}
|
|
9471
9612
|
};
|
|
9472
9613
|
|
|
9473
|
-
const
|
|
9614
|
+
const validateVideoCreateData = ({ data, errorFactory }) => {
|
|
9474
9615
|
if (!isPlainObject(data)) {
|
|
9475
9616
|
return invalidFromErrorFactory(
|
|
9476
9617
|
errorFactory,
|
|
@@ -9478,10 +9619,10 @@ const validateFontCreateData = ({ data, errorFactory }) => {
|
|
|
9478
9619
|
);
|
|
9479
9620
|
}
|
|
9480
9621
|
|
|
9481
|
-
if (data.type !== "folder" && data.type !== "
|
|
9622
|
+
if (data.type !== "folder" && data.type !== "video") {
|
|
9482
9623
|
return invalidFromErrorFactory(
|
|
9483
9624
|
errorFactory,
|
|
9484
|
-
"payload.data.type must be 'folder' or '
|
|
9625
|
+
"payload.data.type must be 'folder' or 'video'",
|
|
9485
9626
|
);
|
|
9486
9627
|
}
|
|
9487
9628
|
|
|
@@ -9491,7 +9632,17 @@ const validateFontCreateData = ({ data, errorFactory }) => {
|
|
|
9491
9632
|
allowedKeys:
|
|
9492
9633
|
data.type === "folder"
|
|
9493
9634
|
? ["type", "name", "description"]
|
|
9494
|
-
: [
|
|
9635
|
+
: [
|
|
9636
|
+
"type",
|
|
9637
|
+
"name",
|
|
9638
|
+
"description",
|
|
9639
|
+
"tagIds",
|
|
9640
|
+
"fileId",
|
|
9641
|
+
"thumbnailFileId",
|
|
9642
|
+
"duration",
|
|
9643
|
+
"width",
|
|
9644
|
+
"height",
|
|
9645
|
+
],
|
|
9495
9646
|
path: "payload.data",
|
|
9496
9647
|
errorFactory,
|
|
9497
9648
|
});
|
|
@@ -9514,7 +9665,7 @@ const validateFontCreateData = ({ data, errorFactory }) => {
|
|
|
9514
9665
|
);
|
|
9515
9666
|
}
|
|
9516
9667
|
|
|
9517
|
-
if (data.type === "
|
|
9668
|
+
if (data.type === "video") {
|
|
9518
9669
|
{
|
|
9519
9670
|
const result = validateOptionalUniqueIdArray({
|
|
9520
9671
|
value: data.tagIds,
|
|
@@ -9533,23 +9684,209 @@ const validateFontCreateData = ({ data, errorFactory }) => {
|
|
|
9533
9684
|
);
|
|
9534
9685
|
}
|
|
9535
9686
|
|
|
9536
|
-
if (!isNonEmptyString(data.
|
|
9687
|
+
if (!isNonEmptyString(data.thumbnailFileId)) {
|
|
9537
9688
|
return invalidFromErrorFactory(
|
|
9538
9689
|
errorFactory,
|
|
9539
|
-
"payload.data.
|
|
9690
|
+
"payload.data.thumbnailFileId must be a non-empty string",
|
|
9540
9691
|
);
|
|
9541
9692
|
}
|
|
9542
|
-
}
|
|
9543
|
-
};
|
|
9544
9693
|
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9694
|
+
if (data.duration !== undefined && !isFiniteNumber(data.duration)) {
|
|
9695
|
+
return invalidFromErrorFactory(
|
|
9696
|
+
errorFactory,
|
|
9697
|
+
"payload.data.duration must be a finite number",
|
|
9698
|
+
);
|
|
9699
|
+
}
|
|
9700
|
+
|
|
9701
|
+
if (data.width !== undefined && !isFiniteNumber(data.width)) {
|
|
9702
|
+
return invalidFromErrorFactory(
|
|
9703
|
+
errorFactory,
|
|
9704
|
+
"payload.data.width must be a finite number",
|
|
9705
|
+
);
|
|
9706
|
+
}
|
|
9707
|
+
|
|
9708
|
+
if (data.height !== undefined && !isFiniteNumber(data.height)) {
|
|
9709
|
+
return invalidFromErrorFactory(
|
|
9710
|
+
errorFactory,
|
|
9711
|
+
"payload.data.height must be a finite number",
|
|
9712
|
+
);
|
|
9713
|
+
}
|
|
9714
|
+
}
|
|
9715
|
+
};
|
|
9716
|
+
|
|
9717
|
+
const validateVideoUpdateData = ({ data, errorFactory }) => {
|
|
9718
|
+
{
|
|
9719
|
+
const result = validateAllowedKeys({
|
|
9720
|
+
value: data,
|
|
9721
|
+
allowedKeys: [
|
|
9722
|
+
"name",
|
|
9723
|
+
"description",
|
|
9724
|
+
"tagIds",
|
|
9725
|
+
"fileId",
|
|
9726
|
+
"thumbnailFileId",
|
|
9727
|
+
"duration",
|
|
9728
|
+
"width",
|
|
9729
|
+
"height",
|
|
9730
|
+
],
|
|
9731
|
+
path: "payload.data",
|
|
9732
|
+
errorFactory,
|
|
9733
|
+
});
|
|
9734
|
+
if (result?.valid === false) {
|
|
9735
|
+
return result;
|
|
9736
|
+
}
|
|
9737
|
+
}
|
|
9738
|
+
|
|
9739
|
+
if (Object.keys(data).length === 0) {
|
|
9740
|
+
return invalidFromErrorFactory(
|
|
9741
|
+
errorFactory,
|
|
9742
|
+
"payload.data must include at least one updatable field",
|
|
9743
|
+
);
|
|
9744
|
+
}
|
|
9745
|
+
|
|
9746
|
+
if (data.name !== undefined && !isNonEmptyString(data.name)) {
|
|
9747
|
+
return invalidFromErrorFactory(
|
|
9748
|
+
errorFactory,
|
|
9749
|
+
"payload.data.name must be a non-empty string when provided",
|
|
9750
|
+
);
|
|
9751
|
+
}
|
|
9752
|
+
|
|
9753
|
+
if (data.description !== undefined && !isString(data.description)) {
|
|
9754
|
+
return invalidFromErrorFactory(
|
|
9755
|
+
errorFactory,
|
|
9756
|
+
"payload.data.description must be a string when provided",
|
|
9757
|
+
);
|
|
9758
|
+
}
|
|
9759
|
+
|
|
9760
|
+
{
|
|
9761
|
+
const result = validateOptionalUniqueIdArray({
|
|
9762
|
+
value: data.tagIds,
|
|
9763
|
+
path: "payload.data.tagIds",
|
|
9764
|
+
errorFactory,
|
|
9765
|
+
});
|
|
9766
|
+
if (result?.valid === false) {
|
|
9767
|
+
return result;
|
|
9768
|
+
}
|
|
9769
|
+
}
|
|
9770
|
+
|
|
9771
|
+
if (data.fileId !== undefined && !isNonEmptyString(data.fileId)) {
|
|
9772
|
+
return invalidFromErrorFactory(
|
|
9773
|
+
errorFactory,
|
|
9774
|
+
"payload.data.fileId must be a non-empty string when provided",
|
|
9775
|
+
);
|
|
9776
|
+
}
|
|
9777
|
+
|
|
9778
|
+
if (
|
|
9779
|
+
data.thumbnailFileId !== undefined &&
|
|
9780
|
+
!isNonEmptyString(data.thumbnailFileId)
|
|
9781
|
+
) {
|
|
9782
|
+
return invalidFromErrorFactory(
|
|
9783
|
+
errorFactory,
|
|
9784
|
+
"payload.data.thumbnailFileId must be a non-empty string when provided",
|
|
9785
|
+
);
|
|
9786
|
+
}
|
|
9787
|
+
|
|
9788
|
+
if (data.duration !== undefined && !isFiniteNumber(data.duration)) {
|
|
9789
|
+
return invalidFromErrorFactory(
|
|
9790
|
+
errorFactory,
|
|
9791
|
+
"payload.data.duration must be a finite number",
|
|
9792
|
+
);
|
|
9793
|
+
}
|
|
9794
|
+
|
|
9795
|
+
if (data.width !== undefined && !isFiniteNumber(data.width)) {
|
|
9796
|
+
return invalidFromErrorFactory(
|
|
9797
|
+
errorFactory,
|
|
9798
|
+
"payload.data.width must be a finite number",
|
|
9799
|
+
);
|
|
9800
|
+
}
|
|
9801
|
+
|
|
9802
|
+
if (data.height !== undefined && !isFiniteNumber(data.height)) {
|
|
9803
|
+
return invalidFromErrorFactory(
|
|
9804
|
+
errorFactory,
|
|
9805
|
+
"payload.data.height must be a finite number",
|
|
9806
|
+
);
|
|
9807
|
+
}
|
|
9808
|
+
};
|
|
9809
|
+
|
|
9810
|
+
const validateFontCreateData = ({ data, errorFactory }) => {
|
|
9811
|
+
if (!isPlainObject(data)) {
|
|
9812
|
+
return invalidFromErrorFactory(
|
|
9813
|
+
errorFactory,
|
|
9814
|
+
"payload.data must be an object",
|
|
9815
|
+
);
|
|
9816
|
+
}
|
|
9817
|
+
|
|
9818
|
+
if (data.type !== "folder" && data.type !== "font") {
|
|
9819
|
+
return invalidFromErrorFactory(
|
|
9820
|
+
errorFactory,
|
|
9821
|
+
"payload.data.type must be 'folder' or 'font'",
|
|
9822
|
+
);
|
|
9823
|
+
}
|
|
9824
|
+
|
|
9825
|
+
{
|
|
9826
|
+
const result = validateAllowedKeys({
|
|
9827
|
+
value: data,
|
|
9828
|
+
allowedKeys:
|
|
9829
|
+
data.type === "folder"
|
|
9830
|
+
? ["type", "name", "description"]
|
|
9831
|
+
: ["type", "name", "description", "tagIds", "fileId", "fontFamily"],
|
|
9832
|
+
path: "payload.data",
|
|
9833
|
+
errorFactory,
|
|
9834
|
+
});
|
|
9835
|
+
if (result?.valid === false) {
|
|
9836
|
+
return result;
|
|
9837
|
+
}
|
|
9838
|
+
}
|
|
9839
|
+
|
|
9840
|
+
if (!isNonEmptyString(data.name)) {
|
|
9841
|
+
return invalidFromErrorFactory(
|
|
9842
|
+
errorFactory,
|
|
9843
|
+
"payload.data.name must be a non-empty string",
|
|
9844
|
+
);
|
|
9845
|
+
}
|
|
9846
|
+
|
|
9847
|
+
if (data.description !== undefined && !isString(data.description)) {
|
|
9848
|
+
return invalidFromErrorFactory(
|
|
9849
|
+
errorFactory,
|
|
9850
|
+
"payload.data.description must be a string when provided",
|
|
9851
|
+
);
|
|
9852
|
+
}
|
|
9853
|
+
|
|
9854
|
+
if (data.type === "font") {
|
|
9855
|
+
{
|
|
9856
|
+
const result = validateOptionalUniqueIdArray({
|
|
9857
|
+
value: data.tagIds,
|
|
9858
|
+
path: "payload.data.tagIds",
|
|
9859
|
+
errorFactory,
|
|
9860
|
+
});
|
|
9861
|
+
if (result?.valid === false) {
|
|
9862
|
+
return result;
|
|
9863
|
+
}
|
|
9864
|
+
}
|
|
9865
|
+
|
|
9866
|
+
if (!isNonEmptyString(data.fileId)) {
|
|
9867
|
+
return invalidFromErrorFactory(
|
|
9868
|
+
errorFactory,
|
|
9869
|
+
"payload.data.fileId must be a non-empty string",
|
|
9870
|
+
);
|
|
9871
|
+
}
|
|
9872
|
+
|
|
9873
|
+
if (!isNonEmptyString(data.fontFamily)) {
|
|
9874
|
+
return invalidFromErrorFactory(
|
|
9875
|
+
errorFactory,
|
|
9876
|
+
"payload.data.fontFamily must be a non-empty string",
|
|
9877
|
+
);
|
|
9878
|
+
}
|
|
9879
|
+
}
|
|
9880
|
+
};
|
|
9881
|
+
|
|
9882
|
+
const validateFontUpdateData = ({ data, errorFactory }) => {
|
|
9883
|
+
{
|
|
9884
|
+
const result = validateAllowedKeys({
|
|
9885
|
+
value: data,
|
|
9886
|
+
allowedKeys: ["name", "description", "tagIds", "fileId", "fontFamily"],
|
|
9887
|
+
path: "payload.data",
|
|
9888
|
+
errorFactory,
|
|
9889
|
+
});
|
|
9553
9890
|
if (result?.valid === false) {
|
|
9554
9891
|
return result;
|
|
9555
9892
|
}
|
|
@@ -11967,6 +12304,22 @@ const validateVisualElementReferenceTargets = ({
|
|
|
11967
12304
|
}
|
|
11968
12305
|
}
|
|
11969
12306
|
|
|
12307
|
+
if (data.revealSoundId !== undefined) {
|
|
12308
|
+
const sound = state.sounds.items[data.revealSoundId];
|
|
12309
|
+
if (!isPlainObject(sound) || sound.type === "folder") {
|
|
12310
|
+
return invalidFromErrorFactory(
|
|
12311
|
+
errorFactory,
|
|
12312
|
+
`${ownerLabel} element revealSoundId must reference an existing non-folder sound`,
|
|
12313
|
+
{
|
|
12314
|
+
[ownerIdField]: ownerId,
|
|
12315
|
+
elementId,
|
|
12316
|
+
field: "revealSoundId",
|
|
12317
|
+
targetId: data.revealSoundId,
|
|
12318
|
+
},
|
|
12319
|
+
);
|
|
12320
|
+
}
|
|
12321
|
+
}
|
|
12322
|
+
|
|
11970
12323
|
if (data.thumbImageId !== undefined) {
|
|
11971
12324
|
const image = state.images.items[data.thumbImageId];
|
|
11972
12325
|
if (!isPlainObject(image) || image.type === "folder") {
|
|
@@ -12854,6 +13207,28 @@ const findReferencedFileUsage = ({ state, fileId }) => {
|
|
|
12854
13207
|
}
|
|
12855
13208
|
}
|
|
12856
13209
|
|
|
13210
|
+
for (const [voiceId, voice] of Object.entries(state.voices.items)) {
|
|
13211
|
+
if (voice.type !== "voice") {
|
|
13212
|
+
continue;
|
|
13213
|
+
}
|
|
13214
|
+
|
|
13215
|
+
if (voice.fileId === fileId) {
|
|
13216
|
+
return {
|
|
13217
|
+
kind: "voice",
|
|
13218
|
+
field: "fileId",
|
|
13219
|
+
ownerId: voiceId,
|
|
13220
|
+
};
|
|
13221
|
+
}
|
|
13222
|
+
|
|
13223
|
+
if (voice.waveformDataFileId === fileId) {
|
|
13224
|
+
return {
|
|
13225
|
+
kind: "voice",
|
|
13226
|
+
field: "waveformDataFileId",
|
|
13227
|
+
ownerId: voiceId,
|
|
13228
|
+
};
|
|
13229
|
+
}
|
|
13230
|
+
}
|
|
13231
|
+
|
|
12857
13232
|
for (const [videoId, video] of Object.entries(state.videos.items)) {
|
|
12858
13233
|
if (video.type !== "video") {
|
|
12859
13234
|
continue;
|
|
@@ -15384,6 +15759,431 @@ const COMMAND_DEFINITIONS = [
|
|
|
15384
15759
|
return state;
|
|
15385
15760
|
},
|
|
15386
15761
|
},
|
|
15762
|
+
{
|
|
15763
|
+
type: "voice.create",
|
|
15764
|
+
validatePayload: ({ payload }) => {
|
|
15765
|
+
{
|
|
15766
|
+
const result = validateAllowedKeys({
|
|
15767
|
+
value: payload,
|
|
15768
|
+
allowedKeys: [
|
|
15769
|
+
"voiceId",
|
|
15770
|
+
"parentId",
|
|
15771
|
+
"data",
|
|
15772
|
+
"index",
|
|
15773
|
+
"position",
|
|
15774
|
+
"positionTargetId",
|
|
15775
|
+
],
|
|
15776
|
+
path: "payload",
|
|
15777
|
+
errorFactory: createPayloadValidationError,
|
|
15778
|
+
});
|
|
15779
|
+
if (result?.valid === false) {
|
|
15780
|
+
return result;
|
|
15781
|
+
}
|
|
15782
|
+
}
|
|
15783
|
+
|
|
15784
|
+
if (!isNonEmptyString(payload.voiceId)) {
|
|
15785
|
+
return invalidPayload("payload.voiceId must be a non-empty string");
|
|
15786
|
+
}
|
|
15787
|
+
|
|
15788
|
+
if (
|
|
15789
|
+
payload.parentId !== undefined &&
|
|
15790
|
+
payload.parentId !== null &&
|
|
15791
|
+
!isNonEmptyString(payload.parentId)
|
|
15792
|
+
) {
|
|
15793
|
+
return invalidPayload(
|
|
15794
|
+
"payload.parentId must be a non-empty string when provided",
|
|
15795
|
+
);
|
|
15796
|
+
}
|
|
15797
|
+
|
|
15798
|
+
{
|
|
15799
|
+
const result = validateVoiceCreateData({
|
|
15800
|
+
data: payload.data,
|
|
15801
|
+
errorFactory: createPayloadValidationError,
|
|
15802
|
+
});
|
|
15803
|
+
if (result?.valid === false) {
|
|
15804
|
+
return result;
|
|
15805
|
+
}
|
|
15806
|
+
}
|
|
15807
|
+
|
|
15808
|
+
{
|
|
15809
|
+
const result = validatePlacementFields({
|
|
15810
|
+
payload,
|
|
15811
|
+
errorFactory: createPayloadValidationError,
|
|
15812
|
+
});
|
|
15813
|
+
if (result?.valid === false) {
|
|
15814
|
+
return result;
|
|
15815
|
+
}
|
|
15816
|
+
}
|
|
15817
|
+
},
|
|
15818
|
+
validateAgainstState: ({ state, payload }) => {
|
|
15819
|
+
if (isPlainObject(state.voices.items[payload.voiceId])) {
|
|
15820
|
+
return invalidPrecondition("payload.voiceId must not already exist");
|
|
15821
|
+
}
|
|
15822
|
+
|
|
15823
|
+
const parentId = payload.parentId ?? null;
|
|
15824
|
+
if (parentId !== null) {
|
|
15825
|
+
const parentVoice = state.voices.items[parentId];
|
|
15826
|
+
if (!isPlainObject(parentVoice)) {
|
|
15827
|
+
return invalidPrecondition(
|
|
15828
|
+
"payload.parentId must reference an existing voice item",
|
|
15829
|
+
);
|
|
15830
|
+
}
|
|
15831
|
+
|
|
15832
|
+
if (parentVoice.type !== "folder") {
|
|
15833
|
+
return invalidPrecondition(
|
|
15834
|
+
"payload.parentId must reference a folder voice item",
|
|
15835
|
+
);
|
|
15836
|
+
}
|
|
15837
|
+
}
|
|
15838
|
+
|
|
15839
|
+
if (payload.positionTargetId !== undefined) {
|
|
15840
|
+
if (!isPlainObject(state.voices.items[payload.positionTargetId])) {
|
|
15841
|
+
return invalidPrecondition(
|
|
15842
|
+
"payload.positionTargetId must reference an existing voice item",
|
|
15843
|
+
);
|
|
15844
|
+
}
|
|
15845
|
+
|
|
15846
|
+
const targetParentId = getNodeParentId({
|
|
15847
|
+
tree: state.voices.tree,
|
|
15848
|
+
nodeId: payload.positionTargetId,
|
|
15849
|
+
});
|
|
15850
|
+
|
|
15851
|
+
if (targetParentId !== parentId) {
|
|
15852
|
+
return invalidPrecondition(
|
|
15853
|
+
"payload.positionTargetId must reference a sibling under payload.parentId",
|
|
15854
|
+
);
|
|
15855
|
+
}
|
|
15856
|
+
}
|
|
15857
|
+
|
|
15858
|
+
if (payload.data.type === "voice") {
|
|
15859
|
+
const scene = state.scenes.items[payload.data.sceneId];
|
|
15860
|
+
if (!isPlainObject(scene) || scene.type === "folder") {
|
|
15861
|
+
return invalidPrecondition(
|
|
15862
|
+
"payload.data.sceneId must reference an existing non-folder scene",
|
|
15863
|
+
);
|
|
15864
|
+
}
|
|
15865
|
+
|
|
15866
|
+
return validateReferencedFilesInData({
|
|
15867
|
+
state,
|
|
15868
|
+
data: payload.data,
|
|
15869
|
+
fields: ["fileId", "waveformDataFileId"],
|
|
15870
|
+
nullableFields: ["waveformDataFileId"],
|
|
15871
|
+
details: {
|
|
15872
|
+
voiceId: payload.voiceId,
|
|
15873
|
+
},
|
|
15874
|
+
});
|
|
15875
|
+
}
|
|
15876
|
+
},
|
|
15877
|
+
reduce: ({ state, payload }) => {
|
|
15878
|
+
const nextVoice = {
|
|
15879
|
+
id: payload.voiceId,
|
|
15880
|
+
type: payload.data.type,
|
|
15881
|
+
name: payload.data.name,
|
|
15882
|
+
};
|
|
15883
|
+
|
|
15884
|
+
if (payload.data.description !== undefined) {
|
|
15885
|
+
nextVoice.description = payload.data.description;
|
|
15886
|
+
}
|
|
15887
|
+
|
|
15888
|
+
if (payload.data.type === "voice") {
|
|
15889
|
+
nextVoice.sceneId = payload.data.sceneId;
|
|
15890
|
+
nextVoice.fileId = payload.data.fileId;
|
|
15891
|
+
if (payload.data.waveformDataFileId !== undefined) {
|
|
15892
|
+
nextVoice.waveformDataFileId = payload.data.waveformDataFileId;
|
|
15893
|
+
}
|
|
15894
|
+
if (payload.data.duration !== undefined) {
|
|
15895
|
+
nextVoice.duration = payload.data.duration;
|
|
15896
|
+
}
|
|
15897
|
+
}
|
|
15898
|
+
|
|
15899
|
+
state.voices.items[payload.voiceId] = nextVoice;
|
|
15900
|
+
|
|
15901
|
+
insertTreeNode({
|
|
15902
|
+
tree: state.voices.tree,
|
|
15903
|
+
node: {
|
|
15904
|
+
id: payload.voiceId,
|
|
15905
|
+
children: [],
|
|
15906
|
+
},
|
|
15907
|
+
parentId: payload.parentId ?? null,
|
|
15908
|
+
index: payload.index,
|
|
15909
|
+
position: payload.position,
|
|
15910
|
+
positionTargetId: payload.positionTargetId,
|
|
15911
|
+
});
|
|
15912
|
+
|
|
15913
|
+
return state;
|
|
15914
|
+
},
|
|
15915
|
+
},
|
|
15916
|
+
{
|
|
15917
|
+
type: "voice.update",
|
|
15918
|
+
validatePayload: ({ payload }) => {
|
|
15919
|
+
{
|
|
15920
|
+
const result = validateExactKeys({
|
|
15921
|
+
value: payload,
|
|
15922
|
+
expectedKeys: ["voiceId", "data"],
|
|
15923
|
+
path: "payload",
|
|
15924
|
+
errorFactory: createPayloadValidationError,
|
|
15925
|
+
});
|
|
15926
|
+
if (result?.valid === false) {
|
|
15927
|
+
return result;
|
|
15928
|
+
}
|
|
15929
|
+
}
|
|
15930
|
+
|
|
15931
|
+
if (!isNonEmptyString(payload.voiceId)) {
|
|
15932
|
+
return invalidPayload("payload.voiceId must be a non-empty string");
|
|
15933
|
+
}
|
|
15934
|
+
|
|
15935
|
+
{
|
|
15936
|
+
const result = validateVoiceUpdateData({
|
|
15937
|
+
data: payload.data,
|
|
15938
|
+
errorFactory: createPayloadValidationError,
|
|
15939
|
+
});
|
|
15940
|
+
if (result?.valid === false) {
|
|
15941
|
+
return result;
|
|
15942
|
+
}
|
|
15943
|
+
}
|
|
15944
|
+
},
|
|
15945
|
+
validateAgainstState: ({ state, payload }) => {
|
|
15946
|
+
const currentVoice = state.voices.items[payload.voiceId];
|
|
15947
|
+
if (!isPlainObject(currentVoice)) {
|
|
15948
|
+
return invalidPrecondition(
|
|
15949
|
+
"payload.voiceId must reference an existing voice item",
|
|
15950
|
+
);
|
|
15951
|
+
}
|
|
15952
|
+
|
|
15953
|
+
if (
|
|
15954
|
+
currentVoice.type === "folder" &&
|
|
15955
|
+
(payload.data.sceneId !== undefined ||
|
|
15956
|
+
payload.data.fileId !== undefined ||
|
|
15957
|
+
payload.data.waveformDataFileId !== undefined ||
|
|
15958
|
+
payload.data.duration !== undefined)
|
|
15959
|
+
) {
|
|
15960
|
+
return invalidPrecondition(
|
|
15961
|
+
"folder voice items cannot update file fields",
|
|
15962
|
+
);
|
|
15963
|
+
}
|
|
15964
|
+
|
|
15965
|
+
if (currentVoice.type === "voice") {
|
|
15966
|
+
if (payload.data.sceneId !== undefined) {
|
|
15967
|
+
const scene = state.scenes.items[payload.data.sceneId];
|
|
15968
|
+
if (!isPlainObject(scene) || scene.type === "folder") {
|
|
15969
|
+
return invalidPrecondition(
|
|
15970
|
+
"payload.data.sceneId must reference an existing non-folder scene",
|
|
15971
|
+
);
|
|
15972
|
+
}
|
|
15973
|
+
}
|
|
15974
|
+
|
|
15975
|
+
return validateReferencedFilesInData({
|
|
15976
|
+
state,
|
|
15977
|
+
data: payload.data,
|
|
15978
|
+
fields: ["fileId", "waveformDataFileId"],
|
|
15979
|
+
nullableFields: ["waveformDataFileId"],
|
|
15980
|
+
details: {
|
|
15981
|
+
voiceId: payload.voiceId,
|
|
15982
|
+
},
|
|
15983
|
+
});
|
|
15984
|
+
}
|
|
15985
|
+
},
|
|
15986
|
+
reduce: ({ state, payload }) => {
|
|
15987
|
+
state.voices.items[payload.voiceId] = {
|
|
15988
|
+
...structuredClone(state.voices.items[payload.voiceId]),
|
|
15989
|
+
...structuredClone(payload.data),
|
|
15990
|
+
};
|
|
15991
|
+
return state;
|
|
15992
|
+
},
|
|
15993
|
+
},
|
|
15994
|
+
{
|
|
15995
|
+
type: "voice.delete",
|
|
15996
|
+
validatePayload: ({ payload }) => {
|
|
15997
|
+
{
|
|
15998
|
+
const result = validateExactKeys({
|
|
15999
|
+
value: payload,
|
|
16000
|
+
expectedKeys: ["voiceIds"],
|
|
16001
|
+
path: "payload",
|
|
16002
|
+
errorFactory: createPayloadValidationError,
|
|
16003
|
+
});
|
|
16004
|
+
if (result?.valid === false) {
|
|
16005
|
+
return result;
|
|
16006
|
+
}
|
|
16007
|
+
}
|
|
16008
|
+
|
|
16009
|
+
{
|
|
16010
|
+
const result = validateRequiredUniqueIdArray({
|
|
16011
|
+
value: payload.voiceIds,
|
|
16012
|
+
path: "payload.voiceIds",
|
|
16013
|
+
errorFactory: createPayloadValidationError,
|
|
16014
|
+
});
|
|
16015
|
+
if (result?.valid === false) {
|
|
16016
|
+
return result;
|
|
16017
|
+
}
|
|
16018
|
+
}
|
|
16019
|
+
},
|
|
16020
|
+
validateAgainstState: ({ state, payload }) => {
|
|
16021
|
+
for (const voiceId of payload.voiceIds) {
|
|
16022
|
+
if (!isPlainObject(state.voices.items[voiceId])) {
|
|
16023
|
+
return invalidPrecondition(
|
|
16024
|
+
"payload.voiceIds must reference existing voice items",
|
|
16025
|
+
{ voiceId },
|
|
16026
|
+
);
|
|
16027
|
+
}
|
|
16028
|
+
}
|
|
16029
|
+
},
|
|
16030
|
+
reduce: ({ state, payload }) => {
|
|
16031
|
+
const deletedVoiceIds = new Set();
|
|
16032
|
+
|
|
16033
|
+
for (const voiceId of payload.voiceIds) {
|
|
16034
|
+
const removedNode = removeTreeNode({
|
|
16035
|
+
nodes: state.voices.tree,
|
|
16036
|
+
nodeId: voiceId,
|
|
16037
|
+
});
|
|
16038
|
+
|
|
16039
|
+
if (!removedNode) {
|
|
16040
|
+
continue;
|
|
16041
|
+
}
|
|
16042
|
+
|
|
16043
|
+
for (const id of collectTreeDescendantIds({ node: removedNode })) {
|
|
16044
|
+
deletedVoiceIds.add(id);
|
|
16045
|
+
}
|
|
16046
|
+
}
|
|
16047
|
+
|
|
16048
|
+
for (const voiceId of deletedVoiceIds) {
|
|
16049
|
+
delete state.voices.items[voiceId];
|
|
16050
|
+
}
|
|
16051
|
+
|
|
16052
|
+
return state;
|
|
16053
|
+
},
|
|
16054
|
+
},
|
|
16055
|
+
{
|
|
16056
|
+
type: "voice.move",
|
|
16057
|
+
validatePayload: ({ payload }) => {
|
|
16058
|
+
{
|
|
16059
|
+
const result = validateAllowedKeys({
|
|
16060
|
+
value: payload,
|
|
16061
|
+
allowedKeys: [
|
|
16062
|
+
"voiceId",
|
|
16063
|
+
"parentId",
|
|
16064
|
+
"index",
|
|
16065
|
+
"position",
|
|
16066
|
+
"positionTargetId",
|
|
16067
|
+
],
|
|
16068
|
+
path: "payload",
|
|
16069
|
+
errorFactory: createPayloadValidationError,
|
|
16070
|
+
});
|
|
16071
|
+
if (result?.valid === false) {
|
|
16072
|
+
return result;
|
|
16073
|
+
}
|
|
16074
|
+
}
|
|
16075
|
+
|
|
16076
|
+
if (!isNonEmptyString(payload.voiceId)) {
|
|
16077
|
+
return invalidPayload("payload.voiceId must be a non-empty string");
|
|
16078
|
+
}
|
|
16079
|
+
|
|
16080
|
+
if (
|
|
16081
|
+
payload.parentId !== undefined &&
|
|
16082
|
+
payload.parentId !== null &&
|
|
16083
|
+
!isNonEmptyString(payload.parentId)
|
|
16084
|
+
) {
|
|
16085
|
+
return invalidPayload(
|
|
16086
|
+
"payload.parentId must be a non-empty string when provided",
|
|
16087
|
+
);
|
|
16088
|
+
}
|
|
16089
|
+
|
|
16090
|
+
{
|
|
16091
|
+
const result = validatePlacementFields({
|
|
16092
|
+
payload,
|
|
16093
|
+
errorFactory: createPayloadValidationError,
|
|
16094
|
+
});
|
|
16095
|
+
if (result?.valid === false) {
|
|
16096
|
+
return result;
|
|
16097
|
+
}
|
|
16098
|
+
}
|
|
16099
|
+
},
|
|
16100
|
+
validateAgainstState: ({ state, payload }) => {
|
|
16101
|
+
const voice = state.voices.items[payload.voiceId];
|
|
16102
|
+
if (!isPlainObject(voice)) {
|
|
16103
|
+
return invalidPrecondition(
|
|
16104
|
+
"payload.voiceId must reference an existing voice item",
|
|
16105
|
+
);
|
|
16106
|
+
}
|
|
16107
|
+
|
|
16108
|
+
const voiceNode = findTreeNode({
|
|
16109
|
+
nodes: state.voices.tree,
|
|
16110
|
+
nodeId: payload.voiceId,
|
|
16111
|
+
});
|
|
16112
|
+
|
|
16113
|
+
if (payload.parentId !== undefined && payload.parentId !== null) {
|
|
16114
|
+
const parentVoice = state.voices.items[payload.parentId];
|
|
16115
|
+
if (!isPlainObject(parentVoice)) {
|
|
16116
|
+
return invalidPrecondition(
|
|
16117
|
+
"payload.parentId must reference an existing voice item",
|
|
16118
|
+
);
|
|
16119
|
+
}
|
|
16120
|
+
|
|
16121
|
+
if (parentVoice.type !== "folder") {
|
|
16122
|
+
return invalidPrecondition(
|
|
16123
|
+
"payload.parentId must reference a folder voice item",
|
|
16124
|
+
);
|
|
16125
|
+
}
|
|
16126
|
+
|
|
16127
|
+
const descendantIds = new Set(
|
|
16128
|
+
collectTreeDescendantIds({
|
|
16129
|
+
node: voiceNode,
|
|
16130
|
+
}),
|
|
16131
|
+
);
|
|
16132
|
+
|
|
16133
|
+
if (descendantIds.has(payload.parentId)) {
|
|
16134
|
+
return invalidPrecondition(
|
|
16135
|
+
"payload.parentId must not target the moved voice item or its descendants",
|
|
16136
|
+
);
|
|
16137
|
+
}
|
|
16138
|
+
}
|
|
16139
|
+
|
|
16140
|
+
if (payload.positionTargetId !== undefined) {
|
|
16141
|
+
if (payload.positionTargetId === payload.voiceId) {
|
|
16142
|
+
return invalidPrecondition(
|
|
16143
|
+
"payload.positionTargetId must not reference the moved voice item",
|
|
16144
|
+
);
|
|
16145
|
+
}
|
|
16146
|
+
|
|
16147
|
+
if (!isPlainObject(state.voices.items[payload.positionTargetId])) {
|
|
16148
|
+
return invalidPrecondition(
|
|
16149
|
+
"payload.positionTargetId must reference an existing voice item",
|
|
16150
|
+
);
|
|
16151
|
+
}
|
|
16152
|
+
|
|
16153
|
+
const targetParentId = getNodeParentId({
|
|
16154
|
+
tree: state.voices.tree,
|
|
16155
|
+
nodeId: payload.positionTargetId,
|
|
16156
|
+
});
|
|
16157
|
+
|
|
16158
|
+
if (targetParentId !== (payload.parentId ?? null)) {
|
|
16159
|
+
return invalidPrecondition(
|
|
16160
|
+
"payload.positionTargetId must reference a sibling under payload.parentId",
|
|
16161
|
+
);
|
|
16162
|
+
}
|
|
16163
|
+
}
|
|
16164
|
+
},
|
|
16165
|
+
reduce: ({ state, payload }) => {
|
|
16166
|
+
const voiceNodeResult = removeNodeOrResult({
|
|
16167
|
+
tree: state.voices.tree,
|
|
16168
|
+
nodeId: payload.voiceId,
|
|
16169
|
+
errorMessage: "voice move target missing from tree",
|
|
16170
|
+
});
|
|
16171
|
+
if (!voiceNodeResult.valid) {
|
|
16172
|
+
return voiceNodeResult;
|
|
16173
|
+
}
|
|
16174
|
+
|
|
16175
|
+
insertTreeNode({
|
|
16176
|
+
tree: state.voices.tree,
|
|
16177
|
+
node: voiceNodeResult.node,
|
|
16178
|
+
parentId: payload.parentId ?? null,
|
|
16179
|
+
index: payload.index,
|
|
16180
|
+
position: payload.position,
|
|
16181
|
+
positionTargetId: payload.positionTargetId,
|
|
16182
|
+
});
|
|
16183
|
+
|
|
16184
|
+
return state;
|
|
16185
|
+
},
|
|
16186
|
+
},
|
|
15387
16187
|
{
|
|
15388
16188
|
type: "video.create",
|
|
15389
16189
|
validatePayload: ({ payload }) => {
|