@routevn/creator-model 1.2.10 → 1.3.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 +1597 -176
package/src/model.js
CHANGED
|
@@ -35,7 +35,7 @@ const COLLECTION_KEYS = [
|
|
|
35
35
|
"layouts",
|
|
36
36
|
"controls",
|
|
37
37
|
];
|
|
38
|
-
const ROOT_KEYS = ["project", "story", ...COLLECTION_KEYS];
|
|
38
|
+
const ROOT_KEYS = ["project", "story", "tags", ...COLLECTION_KEYS];
|
|
39
39
|
const LINE_UPDATE_ACTIONS_PRESERVE_PATHS = ["dialogue.content"];
|
|
40
40
|
const LINE_UPDATE_ACTIONS_PRESERVE_PATHS_SET = new Set(
|
|
41
41
|
LINE_UPDATE_ACTIONS_PRESERVE_PATHS,
|
|
@@ -44,6 +44,55 @@ const createEmptyCollectionState = () => ({
|
|
|
44
44
|
items: {},
|
|
45
45
|
tree: [],
|
|
46
46
|
});
|
|
47
|
+
const TAG_SCOPE_BASE_KEYS = [
|
|
48
|
+
"images",
|
|
49
|
+
"sounds",
|
|
50
|
+
"videos",
|
|
51
|
+
"characters",
|
|
52
|
+
"transforms",
|
|
53
|
+
];
|
|
54
|
+
const CHARACTER_SPRITE_TAG_SCOPE_PREFIX = "characterSprites:";
|
|
55
|
+
const createEmptyTagsState = () => ({
|
|
56
|
+
images: createEmptyCollectionState(),
|
|
57
|
+
sounds: createEmptyCollectionState(),
|
|
58
|
+
videos: createEmptyCollectionState(),
|
|
59
|
+
characters: createEmptyCollectionState(),
|
|
60
|
+
transforms: createEmptyCollectionState(),
|
|
61
|
+
});
|
|
62
|
+
const isCharacterSpriteTagScopeKey = (value) =>
|
|
63
|
+
isNonEmptyString(value) &&
|
|
64
|
+
value.startsWith(CHARACTER_SPRITE_TAG_SCOPE_PREFIX) &&
|
|
65
|
+
value.length > CHARACTER_SPRITE_TAG_SCOPE_PREFIX.length;
|
|
66
|
+
const getCharacterSpriteTagScopeCharacterId = (scopeKey) =>
|
|
67
|
+
isCharacterSpriteTagScopeKey(scopeKey)
|
|
68
|
+
? scopeKey.slice(CHARACTER_SPRITE_TAG_SCOPE_PREFIX.length)
|
|
69
|
+
: undefined;
|
|
70
|
+
const isBaseTagScopeKey = (scopeKey) => TAG_SCOPE_BASE_KEYS.includes(scopeKey);
|
|
71
|
+
const normalizeTagsState = (tags) => {
|
|
72
|
+
if (tags === undefined) {
|
|
73
|
+
return createEmptyTagsState();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!isPlainObject(tags)) {
|
|
77
|
+
return tags;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const missingBaseScopeKeys = TAG_SCOPE_BASE_KEYS.filter(
|
|
81
|
+
(scopeKey) => tags[scopeKey] === undefined,
|
|
82
|
+
);
|
|
83
|
+
if (missingBaseScopeKeys.length === 0) {
|
|
84
|
+
return tags;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const nextTags = {
|
|
88
|
+
...tags,
|
|
89
|
+
};
|
|
90
|
+
for (const scopeKey of missingBaseScopeKeys) {
|
|
91
|
+
nextTags[scopeKey] = createEmptyCollectionState();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return nextTags;
|
|
95
|
+
};
|
|
47
96
|
|
|
48
97
|
const normalizeStateCollections = (state) => {
|
|
49
98
|
if (!isPlainObject(state)) {
|
|
@@ -55,8 +104,10 @@ const normalizeStateCollections = (state) => {
|
|
|
55
104
|
"particles",
|
|
56
105
|
"controls",
|
|
57
106
|
].filter((key) => state[key] === undefined);
|
|
107
|
+
const normalizedTags = normalizeTagsState(state.tags);
|
|
108
|
+
const hasNormalizedTags = normalizedTags !== state.tags;
|
|
58
109
|
|
|
59
|
-
if (missingCollectionKeys.length === 0) {
|
|
110
|
+
if (missingCollectionKeys.length === 0 && !hasNormalizedTags) {
|
|
60
111
|
return state;
|
|
61
112
|
}
|
|
62
113
|
|
|
@@ -68,6 +119,10 @@ const normalizeStateCollections = (state) => {
|
|
|
68
119
|
nextState[key] = createEmptyCollectionState();
|
|
69
120
|
});
|
|
70
121
|
|
|
122
|
+
if (hasNormalizedTags) {
|
|
123
|
+
nextState.tags = normalizedTags;
|
|
124
|
+
}
|
|
125
|
+
|
|
71
126
|
return nextState;
|
|
72
127
|
};
|
|
73
128
|
const isString = (value) => typeof value === "string";
|
|
@@ -164,7 +219,7 @@ const LAYOUT_ELEMENT_BASE_TYPES = [
|
|
|
164
219
|
"container-ref-confirm-dialog-ok",
|
|
165
220
|
"container-ref-confirm-dialog-cancel",
|
|
166
221
|
];
|
|
167
|
-
export const SCHEMA_VERSION =
|
|
222
|
+
export const SCHEMA_VERSION = 3;
|
|
168
223
|
const LAYOUT_CONTAINER_ELEMENT_TYPES = [
|
|
169
224
|
"folder",
|
|
170
225
|
"container",
|
|
@@ -268,6 +323,7 @@ const isVariableReferenceTarget = (state, variableId) => {
|
|
|
268
323
|
};
|
|
269
324
|
|
|
270
325
|
const LAYOUT_ITEM_TARGET_SET = new Set(["item.savedAt"]);
|
|
326
|
+
const LAYOUT_DIALOGUE_TARGET_SET = new Set(["dialogue.characterId"]);
|
|
271
327
|
const RUNTIME_TARGET_DOT_PATTERN = /^runtime\.([A-Za-z_$][A-Za-z0-9_$]*)$/;
|
|
272
328
|
const VARIABLE_TARGET_DOT_PATTERN = /^variables\.([A-Za-z_$][A-Za-z0-9_$]*)$/;
|
|
273
329
|
const VARIABLE_TARGET_BRACKET_PATTERN = /^variables\[(.+)\]$/;
|
|
@@ -284,6 +340,13 @@ const parseLayoutConditionTarget = (target) => {
|
|
|
284
340
|
};
|
|
285
341
|
}
|
|
286
342
|
|
|
343
|
+
if (LAYOUT_DIALOGUE_TARGET_SET.has(target)) {
|
|
344
|
+
return {
|
|
345
|
+
kind: "dialogue",
|
|
346
|
+
target,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
287
350
|
const runtimeMatch = target.match(RUNTIME_TARGET_DOT_PATTERN);
|
|
288
351
|
if (runtimeMatch && isRuntimeFieldId(runtimeMatch[1])) {
|
|
289
352
|
return {
|
|
@@ -340,7 +403,7 @@ const isLayoutConditionTarget = (state, target) => {
|
|
|
340
403
|
return false;
|
|
341
404
|
}
|
|
342
405
|
|
|
343
|
-
if (parsedTarget.kind
|
|
406
|
+
if (parsedTarget.kind !== "variable") {
|
|
344
407
|
return true;
|
|
345
408
|
}
|
|
346
409
|
|
|
@@ -781,6 +844,7 @@ const validateImageItems = ({ items, path, errorFactory }) => {
|
|
|
781
844
|
"type",
|
|
782
845
|
"name",
|
|
783
846
|
"description",
|
|
847
|
+
"tagIds",
|
|
784
848
|
"thumbnailFileId",
|
|
785
849
|
"fileId",
|
|
786
850
|
"width",
|
|
@@ -823,6 +887,18 @@ const validateImageItems = ({ items, path, errorFactory }) => {
|
|
|
823
887
|
}
|
|
824
888
|
|
|
825
889
|
if (item.type === "image") {
|
|
890
|
+
{
|
|
891
|
+
const result = validateOptionalUniqueIdArray({
|
|
892
|
+
value: item.tagIds,
|
|
893
|
+
path: `${itemPath}.tagIds`,
|
|
894
|
+
errorFactory,
|
|
895
|
+
allowEmpty: false,
|
|
896
|
+
});
|
|
897
|
+
if (result?.valid === false) {
|
|
898
|
+
return result;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
826
902
|
if (
|
|
827
903
|
item.thumbnailFileId !== undefined &&
|
|
828
904
|
!isNonEmptyString(item.thumbnailFileId)
|
|
@@ -1070,6 +1146,7 @@ const validateSoundItems = ({ items, path, errorFactory }) => {
|
|
|
1070
1146
|
"type",
|
|
1071
1147
|
"name",
|
|
1072
1148
|
"description",
|
|
1149
|
+
"tagIds",
|
|
1073
1150
|
"fileId",
|
|
1074
1151
|
"waveformDataFileId",
|
|
1075
1152
|
"duration",
|
|
@@ -1111,6 +1188,18 @@ const validateSoundItems = ({ items, path, errorFactory }) => {
|
|
|
1111
1188
|
}
|
|
1112
1189
|
|
|
1113
1190
|
if (item.type === "sound") {
|
|
1191
|
+
{
|
|
1192
|
+
const result = validateOptionalUniqueIdArray({
|
|
1193
|
+
value: item.tagIds,
|
|
1194
|
+
path: `${itemPath}.tagIds`,
|
|
1195
|
+
errorFactory,
|
|
1196
|
+
allowEmpty: false,
|
|
1197
|
+
});
|
|
1198
|
+
if (result?.valid === false) {
|
|
1199
|
+
return result;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1114
1203
|
if (!isNonEmptyString(item.fileId)) {
|
|
1115
1204
|
return invalidFromErrorFactory(
|
|
1116
1205
|
errorFactory,
|
|
@@ -1161,6 +1250,7 @@ const validateVideoItems = ({ items, path, errorFactory }) => {
|
|
|
1161
1250
|
"type",
|
|
1162
1251
|
"name",
|
|
1163
1252
|
"description",
|
|
1253
|
+
"tagIds",
|
|
1164
1254
|
"fileId",
|
|
1165
1255
|
"thumbnailFileId",
|
|
1166
1256
|
"duration",
|
|
@@ -1204,6 +1294,18 @@ const validateVideoItems = ({ items, path, errorFactory }) => {
|
|
|
1204
1294
|
}
|
|
1205
1295
|
|
|
1206
1296
|
if (item.type === "video") {
|
|
1297
|
+
{
|
|
1298
|
+
const result = validateOptionalUniqueIdArray({
|
|
1299
|
+
value: item.tagIds,
|
|
1300
|
+
path: `${itemPath}.tagIds`,
|
|
1301
|
+
errorFactory,
|
|
1302
|
+
allowEmpty: false,
|
|
1303
|
+
});
|
|
1304
|
+
if (result?.valid === false) {
|
|
1305
|
+
return result;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1207
1309
|
if (!isNonEmptyString(item.fileId)) {
|
|
1208
1310
|
return invalidFromErrorFactory(
|
|
1209
1311
|
errorFactory,
|
|
@@ -2085,6 +2187,7 @@ const validateTransformItems = ({ items, path, errorFactory }) => {
|
|
|
2085
2187
|
"type",
|
|
2086
2188
|
"name",
|
|
2087
2189
|
"description",
|
|
2190
|
+
"tagIds",
|
|
2088
2191
|
"x",
|
|
2089
2192
|
"y",
|
|
2090
2193
|
"scaleX",
|
|
@@ -2130,6 +2233,18 @@ const validateTransformItems = ({ items, path, errorFactory }) => {
|
|
|
2130
2233
|
}
|
|
2131
2234
|
|
|
2132
2235
|
if (item.type === "transform") {
|
|
2236
|
+
{
|
|
2237
|
+
const result = validateOptionalUniqueIdArray({
|
|
2238
|
+
value: item.tagIds,
|
|
2239
|
+
path: `${itemPath}.tagIds`,
|
|
2240
|
+
errorFactory,
|
|
2241
|
+
allowEmpty: false,
|
|
2242
|
+
});
|
|
2243
|
+
if (result?.valid === false) {
|
|
2244
|
+
return result;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2133
2248
|
for (const key of [
|
|
2134
2249
|
"x",
|
|
2135
2250
|
"y",
|
|
@@ -2594,7 +2709,12 @@ const validateTextStyleItems = ({ items, path, errorFactory }) => {
|
|
|
2594
2709
|
}
|
|
2595
2710
|
};
|
|
2596
2711
|
|
|
2597
|
-
const validateCharacterSpriteItems = ({
|
|
2712
|
+
const validateCharacterSpriteItems = ({
|
|
2713
|
+
items,
|
|
2714
|
+
path,
|
|
2715
|
+
errorFactory,
|
|
2716
|
+
allowTagIds = true,
|
|
2717
|
+
}) => {
|
|
2598
2718
|
for (const [itemId, item] of Object.entries(items)) {
|
|
2599
2719
|
const itemPath = `${path}.${itemId}`;
|
|
2600
2720
|
|
|
@@ -2616,6 +2736,7 @@ const validateCharacterSpriteItems = ({ items, path, errorFactory }) => {
|
|
|
2616
2736
|
"type",
|
|
2617
2737
|
"name",
|
|
2618
2738
|
"description",
|
|
2739
|
+
...(allowTagIds ? ["tagIds"] : []),
|
|
2619
2740
|
"thumbnailFileId",
|
|
2620
2741
|
"fileId",
|
|
2621
2742
|
"width",
|
|
@@ -2658,6 +2779,20 @@ const validateCharacterSpriteItems = ({ items, path, errorFactory }) => {
|
|
|
2658
2779
|
}
|
|
2659
2780
|
|
|
2660
2781
|
if (item.type === "image") {
|
|
2782
|
+
if (allowTagIds) {
|
|
2783
|
+
{
|
|
2784
|
+
const result = validateOptionalUniqueIdArray({
|
|
2785
|
+
value: item.tagIds,
|
|
2786
|
+
path: `${itemPath}.tagIds`,
|
|
2787
|
+
errorFactory,
|
|
2788
|
+
allowEmpty: false,
|
|
2789
|
+
});
|
|
2790
|
+
if (result?.valid === false) {
|
|
2791
|
+
return result;
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2661
2796
|
if (
|
|
2662
2797
|
item.thumbnailFileId !== undefined &&
|
|
2663
2798
|
!isNonEmptyString(item.thumbnailFileId)
|
|
@@ -3453,6 +3588,7 @@ const validateCharacterItems = ({ items, path, errorFactory }) => {
|
|
|
3453
3588
|
"type",
|
|
3454
3589
|
"name",
|
|
3455
3590
|
"description",
|
|
3591
|
+
"tagIds",
|
|
3456
3592
|
"shortcut",
|
|
3457
3593
|
"fileId",
|
|
3458
3594
|
"sprites",
|
|
@@ -3494,6 +3630,18 @@ const validateCharacterItems = ({ items, path, errorFactory }) => {
|
|
|
3494
3630
|
}
|
|
3495
3631
|
|
|
3496
3632
|
if (item.type === "character") {
|
|
3633
|
+
{
|
|
3634
|
+
const result = validateOptionalUniqueIdArray({
|
|
3635
|
+
value: item.tagIds,
|
|
3636
|
+
path: `${itemPath}.tagIds`,
|
|
3637
|
+
errorFactory,
|
|
3638
|
+
allowEmpty: false,
|
|
3639
|
+
});
|
|
3640
|
+
if (result?.valid === false) {
|
|
3641
|
+
return result;
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3497
3645
|
if (item.shortcut !== undefined && !isString(item.shortcut)) {
|
|
3498
3646
|
return invalidFromErrorFactory(
|
|
3499
3647
|
errorFactory,
|
|
@@ -3525,80 +3673,23 @@ const validateCharacterItems = ({ items, path, errorFactory }) => {
|
|
|
3525
3673
|
}
|
|
3526
3674
|
};
|
|
3527
3675
|
|
|
3528
|
-
const
|
|
3529
|
-
|
|
3530
|
-
return VALID_RESULT;
|
|
3531
|
-
}
|
|
3532
|
-
|
|
3533
|
-
if (!isPlainObject(value)) {
|
|
3534
|
-
return invalidFromErrorFactory(
|
|
3535
|
-
errorFactory,
|
|
3536
|
-
`${path} must be an object when provided`,
|
|
3537
|
-
);
|
|
3538
|
-
}
|
|
3539
|
-
|
|
3540
|
-
for (const [key, interaction] of Object.entries(value)) {
|
|
3541
|
-
if (!isNonEmptyString(key)) {
|
|
3542
|
-
return invalidFromErrorFactory(
|
|
3543
|
-
errorFactory,
|
|
3544
|
-
`${path} keys must be non-empty strings`,
|
|
3545
|
-
);
|
|
3546
|
-
}
|
|
3547
|
-
|
|
3548
|
-
if (!isPlainObject(interaction)) {
|
|
3549
|
-
return invalidFromErrorFactory(
|
|
3550
|
-
errorFactory,
|
|
3551
|
-
`${path}.${key} must be an object`,
|
|
3552
|
-
);
|
|
3553
|
-
}
|
|
3554
|
-
}
|
|
3555
|
-
|
|
3556
|
-
return VALID_RESULT;
|
|
3557
|
-
};
|
|
3558
|
-
|
|
3559
|
-
const validatePreviewObject = ({ value, path, errorFactory }) => {
|
|
3560
|
-
if (value === undefined) {
|
|
3561
|
-
return VALID_RESULT;
|
|
3562
|
-
}
|
|
3563
|
-
|
|
3564
|
-
if (!isPlainObject(value)) {
|
|
3565
|
-
return invalidFromErrorFactory(
|
|
3566
|
-
errorFactory,
|
|
3567
|
-
`${path} must be an object when provided`,
|
|
3568
|
-
);
|
|
3569
|
-
}
|
|
3570
|
-
|
|
3571
|
-
return VALID_RESULT;
|
|
3572
|
-
};
|
|
3676
|
+
const validateTagItems = ({ items, path, errorFactory }) => {
|
|
3677
|
+
const seenNames = new Set();
|
|
3573
3678
|
|
|
3574
|
-
const validateLayoutItems = ({ items, path, errorFactory }) => {
|
|
3575
3679
|
for (const [itemId, item] of Object.entries(items)) {
|
|
3576
3680
|
const itemPath = `${path}.${itemId}`;
|
|
3577
3681
|
|
|
3578
|
-
if (item?.type !== "
|
|
3682
|
+
if (item?.type !== "tag") {
|
|
3579
3683
|
return invalidFromErrorFactory(
|
|
3580
3684
|
errorFactory,
|
|
3581
|
-
`${itemPath}.type must be '
|
|
3685
|
+
`${itemPath}.type must be 'tag'`,
|
|
3582
3686
|
);
|
|
3583
3687
|
}
|
|
3584
3688
|
|
|
3585
3689
|
{
|
|
3586
3690
|
const result = validateAllowedKeys({
|
|
3587
3691
|
value: item,
|
|
3588
|
-
allowedKeys:
|
|
3589
|
-
item.type === "folder"
|
|
3590
|
-
? ["id", "type", "name", "description"]
|
|
3591
|
-
: [
|
|
3592
|
-
"id",
|
|
3593
|
-
"type",
|
|
3594
|
-
"name",
|
|
3595
|
-
"description",
|
|
3596
|
-
"layoutType",
|
|
3597
|
-
"isFragment",
|
|
3598
|
-
"thumbnailFileId",
|
|
3599
|
-
"preview",
|
|
3600
|
-
"elements",
|
|
3601
|
-
],
|
|
3692
|
+
allowedKeys: ["id", "type", "name", "color"],
|
|
3602
3693
|
path: itemPath,
|
|
3603
3694
|
errorFactory,
|
|
3604
3695
|
});
|
|
@@ -3628,82 +3719,602 @@ const validateLayoutItems = ({ items, path, errorFactory }) => {
|
|
|
3628
3719
|
);
|
|
3629
3720
|
}
|
|
3630
3721
|
|
|
3631
|
-
if (item.
|
|
3722
|
+
if (item.color !== undefined && !isHexColor(item.color)) {
|
|
3632
3723
|
return invalidFromErrorFactory(
|
|
3633
3724
|
errorFactory,
|
|
3634
|
-
`${itemPath}.
|
|
3725
|
+
`${itemPath}.color must be a hex color when provided`,
|
|
3635
3726
|
);
|
|
3636
3727
|
}
|
|
3637
3728
|
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
!isNonEmptyString(item.thumbnailFileId)
|
|
3641
|
-
) {
|
|
3729
|
+
const normalizedName = item.name.trim().toLowerCase();
|
|
3730
|
+
if (seenNames.has(normalizedName)) {
|
|
3642
3731
|
return invalidFromErrorFactory(
|
|
3643
3732
|
errorFactory,
|
|
3644
|
-
`${itemPath}.
|
|
3733
|
+
`${itemPath}.name must be unique within its tag scope`,
|
|
3645
3734
|
);
|
|
3646
3735
|
}
|
|
3647
3736
|
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
}
|
|
3737
|
+
seenNames.add(normalizedName);
|
|
3738
|
+
}
|
|
3739
|
+
};
|
|
3740
|
+
|
|
3741
|
+
const validateFlatTagTree = ({ nodes, path, errorFactory }) => {
|
|
3742
|
+
const visitNodes = (entries, entryPath) => {
|
|
3743
|
+
if (!Array.isArray(entries)) {
|
|
3744
|
+
return VALID_RESULT;
|
|
3657
3745
|
}
|
|
3658
3746
|
|
|
3659
|
-
|
|
3660
|
-
if (
|
|
3747
|
+
for (const [index, node] of entries.entries()) {
|
|
3748
|
+
if (Object.hasOwn(node, "children")) {
|
|
3661
3749
|
return invalidFromErrorFactory(
|
|
3662
3750
|
errorFactory,
|
|
3663
|
-
`${
|
|
3751
|
+
`${entryPath}[${index}].children is not allowed`,
|
|
3664
3752
|
);
|
|
3665
3753
|
}
|
|
3754
|
+
}
|
|
3666
3755
|
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
typeof item.isFragment !== "boolean"
|
|
3670
|
-
) {
|
|
3671
|
-
return invalidFromErrorFactory(
|
|
3672
|
-
errorFactory,
|
|
3673
|
-
`${itemPath}.isFragment must be a boolean when provided`,
|
|
3674
|
-
);
|
|
3675
|
-
}
|
|
3756
|
+
return VALID_RESULT;
|
|
3757
|
+
};
|
|
3676
3758
|
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3759
|
+
return visitNodes(nodes, path);
|
|
3760
|
+
};
|
|
3761
|
+
|
|
3762
|
+
const validateTagCreateData = ({
|
|
3763
|
+
data,
|
|
3764
|
+
path = "payload.data",
|
|
3765
|
+
errorFactory = createPayloadValidationError,
|
|
3766
|
+
}) => {
|
|
3767
|
+
{
|
|
3768
|
+
const result = validateAllowedKeys({
|
|
3769
|
+
value: data,
|
|
3770
|
+
allowedKeys: ["type", "name", "color"],
|
|
3771
|
+
path,
|
|
3772
|
+
errorFactory,
|
|
3773
|
+
});
|
|
3774
|
+
if (result?.valid === false) {
|
|
3775
|
+
return result;
|
|
3689
3776
|
}
|
|
3690
3777
|
}
|
|
3778
|
+
|
|
3779
|
+
if (data?.type !== "tag") {
|
|
3780
|
+
return invalidFromErrorFactory(
|
|
3781
|
+
errorFactory,
|
|
3782
|
+
`${path}.type must be 'tag'`,
|
|
3783
|
+
);
|
|
3784
|
+
}
|
|
3785
|
+
|
|
3786
|
+
if (!isNonEmptyString(data.name)) {
|
|
3787
|
+
return invalidFromErrorFactory(
|
|
3788
|
+
errorFactory,
|
|
3789
|
+
`${path}.name must be a non-empty string`,
|
|
3790
|
+
);
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
if (data.color !== undefined && !isHexColor(data.color)) {
|
|
3794
|
+
return invalidFromErrorFactory(
|
|
3795
|
+
errorFactory,
|
|
3796
|
+
`${path}.color must be a hex color when provided`,
|
|
3797
|
+
);
|
|
3798
|
+
}
|
|
3691
3799
|
};
|
|
3692
3800
|
|
|
3693
|
-
const
|
|
3694
|
-
|
|
3695
|
-
|
|
3801
|
+
const validateTagUpdateData = ({
|
|
3802
|
+
data,
|
|
3803
|
+
path = "payload.data",
|
|
3804
|
+
errorFactory = createPayloadValidationError,
|
|
3805
|
+
}) => {
|
|
3806
|
+
{
|
|
3807
|
+
const result = validateAllowedKeys({
|
|
3808
|
+
value: data,
|
|
3809
|
+
allowedKeys: ["name", "color"],
|
|
3810
|
+
path,
|
|
3811
|
+
errorFactory,
|
|
3812
|
+
});
|
|
3813
|
+
if (result?.valid === false) {
|
|
3814
|
+
return result;
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3696
3817
|
|
|
3697
|
-
|
|
3818
|
+
if (Object.keys(data).length === 0) {
|
|
3819
|
+
return invalidFromErrorFactory(
|
|
3820
|
+
errorFactory,
|
|
3821
|
+
`${path} must include at least one field`,
|
|
3822
|
+
);
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
if (data.name !== undefined && !isNonEmptyString(data.name)) {
|
|
3826
|
+
return invalidFromErrorFactory(
|
|
3827
|
+
errorFactory,
|
|
3828
|
+
`${path}.name must be a non-empty string when provided`,
|
|
3829
|
+
);
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3832
|
+
if (
|
|
3833
|
+
data.color !== undefined &&
|
|
3834
|
+
data.color !== null &&
|
|
3835
|
+
!isHexColor(data.color)
|
|
3836
|
+
) {
|
|
3837
|
+
return invalidFromErrorFactory(
|
|
3838
|
+
errorFactory,
|
|
3839
|
+
`${path}.color must be a hex color or null when provided`,
|
|
3840
|
+
);
|
|
3841
|
+
}
|
|
3842
|
+
};
|
|
3843
|
+
|
|
3844
|
+
const validateTagsRoot = ({ state, tags, path, errorFactory }) => {
|
|
3845
|
+
if (!isPlainObject(tags)) {
|
|
3846
|
+
return invalidFromErrorFactory(errorFactory, `${path} must be an object`);
|
|
3847
|
+
}
|
|
3848
|
+
|
|
3849
|
+
for (const scopeKey of Object.keys(tags)) {
|
|
3850
|
+
if (!isBaseTagScopeKey(scopeKey) && !isCharacterSpriteTagScopeKey(scopeKey)) {
|
|
3698
3851
|
return invalidFromErrorFactory(
|
|
3699
3852
|
errorFactory,
|
|
3700
|
-
`${
|
|
3853
|
+
`${path}.${scopeKey} is not allowed`,
|
|
3701
3854
|
);
|
|
3702
3855
|
}
|
|
3856
|
+
}
|
|
3703
3857
|
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3858
|
+
for (const scopeKey of TAG_SCOPE_BASE_KEYS) {
|
|
3859
|
+
if (!Object.hasOwn(tags, scopeKey)) {
|
|
3860
|
+
return invalidFromErrorFactory(
|
|
3861
|
+
errorFactory,
|
|
3862
|
+
`${path}.${scopeKey} is required`,
|
|
3863
|
+
);
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
|
|
3867
|
+
for (const [scopeKey, collection] of Object.entries(tags)) {
|
|
3868
|
+
{
|
|
3869
|
+
const result = validateNestedCollection({
|
|
3870
|
+
collection,
|
|
3871
|
+
path: `${path}.${scopeKey}`,
|
|
3872
|
+
itemValidator: validateTagItems,
|
|
3873
|
+
treeValidator: validateFlatTagTree,
|
|
3874
|
+
errorFactory,
|
|
3875
|
+
});
|
|
3876
|
+
if (result?.valid === false) {
|
|
3877
|
+
return result;
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
if (!isCharacterSpriteTagScopeKey(scopeKey)) {
|
|
3882
|
+
continue;
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
const characterId = getCharacterSpriteTagScopeCharacterId(scopeKey);
|
|
3886
|
+
const character = state.characters?.items?.[characterId];
|
|
3887
|
+
if (!isPlainObject(character) || character.type !== "character") {
|
|
3888
|
+
return invalidFromErrorFactory(
|
|
3889
|
+
errorFactory,
|
|
3890
|
+
`${path}.${scopeKey} must reference an existing character`,
|
|
3891
|
+
);
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
};
|
|
3895
|
+
|
|
3896
|
+
const getTagScopeCollection = ({ state, scopeKey }) => state.tags?.[scopeKey];
|
|
3897
|
+
|
|
3898
|
+
const validateTagScopeKey = ({ scopeKey, path, errorFactory }) => {
|
|
3899
|
+
if (!isNonEmptyString(scopeKey)) {
|
|
3900
|
+
return invalidFromErrorFactory(
|
|
3901
|
+
errorFactory,
|
|
3902
|
+
`${path} must be a non-empty string`,
|
|
3903
|
+
);
|
|
3904
|
+
}
|
|
3905
|
+
|
|
3906
|
+
if (isBaseTagScopeKey(scopeKey) || isCharacterSpriteTagScopeKey(scopeKey)) {
|
|
3907
|
+
return VALID_RESULT;
|
|
3908
|
+
}
|
|
3909
|
+
|
|
3910
|
+
return invalidFromErrorFactory(
|
|
3911
|
+
errorFactory,
|
|
3912
|
+
`${path} must be a supported tag scope key`,
|
|
3913
|
+
);
|
|
3914
|
+
};
|
|
3915
|
+
|
|
3916
|
+
const validateTagScopeAgainstState = ({
|
|
3917
|
+
state,
|
|
3918
|
+
scopeKey,
|
|
3919
|
+
path,
|
|
3920
|
+
details = {},
|
|
3921
|
+
errorFactory = createPreconditionValidationError,
|
|
3922
|
+
}) => {
|
|
3923
|
+
const scopeKeyResult = validateTagScopeKey({
|
|
3924
|
+
scopeKey,
|
|
3925
|
+
path,
|
|
3926
|
+
errorFactory,
|
|
3927
|
+
});
|
|
3928
|
+
if (!scopeKeyResult.valid) {
|
|
3929
|
+
return scopeKeyResult;
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3932
|
+
if (!isCharacterSpriteTagScopeKey(scopeKey)) {
|
|
3933
|
+
return VALID_RESULT;
|
|
3934
|
+
}
|
|
3935
|
+
|
|
3936
|
+
const characterId = getCharacterSpriteTagScopeCharacterId(scopeKey);
|
|
3937
|
+
const character = state.characters?.items?.[characterId];
|
|
3938
|
+
if (!isPlainObject(character) || character.type !== "character") {
|
|
3939
|
+
return invalidFromErrorFactory(
|
|
3940
|
+
errorFactory,
|
|
3941
|
+
`${path} must reference an existing character sprite tag scope`,
|
|
3942
|
+
{
|
|
3943
|
+
...details,
|
|
3944
|
+
characterId,
|
|
3945
|
+
scopeKey,
|
|
3946
|
+
},
|
|
3947
|
+
);
|
|
3948
|
+
}
|
|
3949
|
+
|
|
3950
|
+
return VALID_RESULT;
|
|
3951
|
+
};
|
|
3952
|
+
|
|
3953
|
+
const validateTagIdsAgainstScope = ({
|
|
3954
|
+
state,
|
|
3955
|
+
tagIds,
|
|
3956
|
+
scopeKey,
|
|
3957
|
+
path,
|
|
3958
|
+
details = {},
|
|
3959
|
+
errorFactory = createPreconditionValidationError,
|
|
3960
|
+
}) => {
|
|
3961
|
+
if (tagIds === undefined) {
|
|
3962
|
+
return VALID_RESULT;
|
|
3963
|
+
}
|
|
3964
|
+
|
|
3965
|
+
const scopeResult = validateTagScopeAgainstState({
|
|
3966
|
+
state,
|
|
3967
|
+
scopeKey,
|
|
3968
|
+
path,
|
|
3969
|
+
details,
|
|
3970
|
+
errorFactory,
|
|
3971
|
+
});
|
|
3972
|
+
if (!scopeResult.valid) {
|
|
3973
|
+
return scopeResult;
|
|
3974
|
+
}
|
|
3975
|
+
|
|
3976
|
+
const collection = getTagScopeCollection({ state, scopeKey });
|
|
3977
|
+
for (const [index, tagId] of tagIds.entries()) {
|
|
3978
|
+
const tag = collection?.items?.[tagId];
|
|
3979
|
+
if (!isPlainObject(tag) || tag.type !== "tag") {
|
|
3980
|
+
return invalidFromErrorFactory(
|
|
3981
|
+
errorFactory,
|
|
3982
|
+
`${path}[${index}] must reference an existing tag in scope '${scopeKey}'`,
|
|
3983
|
+
{
|
|
3984
|
+
...details,
|
|
3985
|
+
scopeKey,
|
|
3986
|
+
tagId,
|
|
3987
|
+
},
|
|
3988
|
+
);
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
return VALID_RESULT;
|
|
3993
|
+
};
|
|
3994
|
+
|
|
3995
|
+
const validateUniqueTagNameInScope = ({
|
|
3996
|
+
collection,
|
|
3997
|
+
name,
|
|
3998
|
+
path,
|
|
3999
|
+
excludeTagId,
|
|
4000
|
+
errorFactory = createPreconditionValidationError,
|
|
4001
|
+
}) => {
|
|
4002
|
+
const normalizedName = name.trim().toLowerCase();
|
|
4003
|
+
|
|
4004
|
+
for (const [tagId, tag] of Object.entries(collection?.items || {})) {
|
|
4005
|
+
if (tagId === excludeTagId || tag?.type !== "tag") {
|
|
4006
|
+
continue;
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
if (tag.name?.trim?.().toLowerCase?.() === normalizedName) {
|
|
4010
|
+
return invalidFromErrorFactory(
|
|
4011
|
+
errorFactory,
|
|
4012
|
+
`${path} must be unique within its tag scope`,
|
|
4013
|
+
);
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
return VALID_RESULT;
|
|
4018
|
+
};
|
|
4019
|
+
|
|
4020
|
+
const ensureTagScopeCollection = ({ state, scopeKey }) => {
|
|
4021
|
+
state.tags ??= createEmptyTagsState();
|
|
4022
|
+
state.tags[scopeKey] ??= createEmptyCollectionState();
|
|
4023
|
+
return state.tags[scopeKey];
|
|
4024
|
+
};
|
|
4025
|
+
|
|
4026
|
+
const assignOptionalTagIds = ({ target, tagIds }) => {
|
|
4027
|
+
if (Array.isArray(tagIds) && tagIds.length > 0) {
|
|
4028
|
+
target.tagIds = structuredClone(tagIds);
|
|
4029
|
+
}
|
|
4030
|
+
};
|
|
4031
|
+
|
|
4032
|
+
const applyTagIdsUpdate = ({ currentItem, data }) => {
|
|
4033
|
+
const nextData = structuredClone(data);
|
|
4034
|
+
if (nextData.tagIds === undefined) {
|
|
4035
|
+
delete nextData.tagIds;
|
|
4036
|
+
}
|
|
4037
|
+
|
|
4038
|
+
const nextItem = {
|
|
4039
|
+
...structuredClone(currentItem),
|
|
4040
|
+
...nextData,
|
|
4041
|
+
};
|
|
4042
|
+
|
|
4043
|
+
if (data.tagIds !== undefined) {
|
|
4044
|
+
if (Array.isArray(data.tagIds) && data.tagIds.length > 0) {
|
|
4045
|
+
nextItem.tagIds = structuredClone(data.tagIds);
|
|
4046
|
+
} else {
|
|
4047
|
+
delete nextItem.tagIds;
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
4050
|
+
|
|
4051
|
+
return nextItem;
|
|
4052
|
+
};
|
|
4053
|
+
|
|
4054
|
+
const stripDeletedTagIdsFromItem = ({ item, deletedTagIds }) => {
|
|
4055
|
+
if (!Array.isArray(item?.tagIds) || item.tagIds.length === 0) {
|
|
4056
|
+
return;
|
|
4057
|
+
}
|
|
4058
|
+
|
|
4059
|
+
const remainingTagIds = item.tagIds.filter((tagId) => !deletedTagIds.has(tagId));
|
|
4060
|
+
if (remainingTagIds.length === item.tagIds.length) {
|
|
4061
|
+
return;
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
if (remainingTagIds.length === 0) {
|
|
4065
|
+
delete item.tagIds;
|
|
4066
|
+
return;
|
|
4067
|
+
}
|
|
4068
|
+
|
|
4069
|
+
item.tagIds = remainingTagIds;
|
|
4070
|
+
};
|
|
4071
|
+
|
|
4072
|
+
const stripDeletedTagIdsFromScopeItems = ({ state, scopeKey, deletedTagIds }) => {
|
|
4073
|
+
if (deletedTagIds.size === 0) {
|
|
4074
|
+
return;
|
|
4075
|
+
}
|
|
4076
|
+
|
|
4077
|
+
if (scopeKey === "images") {
|
|
4078
|
+
for (const item of Object.values(state.images.items)) {
|
|
4079
|
+
if (item?.type === "image") {
|
|
4080
|
+
stripDeletedTagIdsFromItem({ item, deletedTagIds });
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
return;
|
|
4084
|
+
}
|
|
4085
|
+
|
|
4086
|
+
if (scopeKey === "sounds") {
|
|
4087
|
+
for (const item of Object.values(state.sounds.items)) {
|
|
4088
|
+
if (item?.type === "sound") {
|
|
4089
|
+
stripDeletedTagIdsFromItem({ item, deletedTagIds });
|
|
4090
|
+
}
|
|
4091
|
+
}
|
|
4092
|
+
return;
|
|
4093
|
+
}
|
|
4094
|
+
|
|
4095
|
+
if (scopeKey === "videos") {
|
|
4096
|
+
for (const item of Object.values(state.videos.items)) {
|
|
4097
|
+
if (item?.type === "video") {
|
|
4098
|
+
stripDeletedTagIdsFromItem({ item, deletedTagIds });
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
4103
|
+
|
|
4104
|
+
if (scopeKey === "characters") {
|
|
4105
|
+
for (const item of Object.values(state.characters.items)) {
|
|
4106
|
+
if (item?.type === "character") {
|
|
4107
|
+
stripDeletedTagIdsFromItem({ item, deletedTagIds });
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
return;
|
|
4111
|
+
}
|
|
4112
|
+
|
|
4113
|
+
if (scopeKey === "transforms") {
|
|
4114
|
+
for (const item of Object.values(state.transforms.items)) {
|
|
4115
|
+
if (item?.type === "transform") {
|
|
4116
|
+
stripDeletedTagIdsFromItem({ item, deletedTagIds });
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
return;
|
|
4120
|
+
}
|
|
4121
|
+
|
|
4122
|
+
if (!isCharacterSpriteTagScopeKey(scopeKey)) {
|
|
4123
|
+
return;
|
|
4124
|
+
}
|
|
4125
|
+
|
|
4126
|
+
const characterId = getCharacterSpriteTagScopeCharacterId(scopeKey);
|
|
4127
|
+
const collection = getCharacterSpriteCollection({
|
|
4128
|
+
state,
|
|
4129
|
+
characterId,
|
|
4130
|
+
});
|
|
4131
|
+
|
|
4132
|
+
for (const item of Object.values(collection?.items || {})) {
|
|
4133
|
+
if (item?.type === "image") {
|
|
4134
|
+
stripDeletedTagIdsFromItem({ item, deletedTagIds });
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
};
|
|
4138
|
+
|
|
4139
|
+
const validateKeyboardMap = ({ value, path, errorFactory }) => {
|
|
4140
|
+
if (value === undefined) {
|
|
4141
|
+
return VALID_RESULT;
|
|
4142
|
+
}
|
|
4143
|
+
|
|
4144
|
+
if (!isPlainObject(value)) {
|
|
4145
|
+
return invalidFromErrorFactory(
|
|
4146
|
+
errorFactory,
|
|
4147
|
+
`${path} must be an object when provided`,
|
|
4148
|
+
);
|
|
4149
|
+
}
|
|
4150
|
+
|
|
4151
|
+
for (const [key, interaction] of Object.entries(value)) {
|
|
4152
|
+
if (!isNonEmptyString(key)) {
|
|
4153
|
+
return invalidFromErrorFactory(
|
|
4154
|
+
errorFactory,
|
|
4155
|
+
`${path} keys must be non-empty strings`,
|
|
4156
|
+
);
|
|
4157
|
+
}
|
|
4158
|
+
|
|
4159
|
+
if (!isPlainObject(interaction)) {
|
|
4160
|
+
return invalidFromErrorFactory(
|
|
4161
|
+
errorFactory,
|
|
4162
|
+
`${path}.${key} must be an object`,
|
|
4163
|
+
);
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
|
|
4167
|
+
return VALID_RESULT;
|
|
4168
|
+
};
|
|
4169
|
+
|
|
4170
|
+
const validatePreviewObject = ({ value, path, errorFactory }) => {
|
|
4171
|
+
if (value === undefined) {
|
|
4172
|
+
return VALID_RESULT;
|
|
4173
|
+
}
|
|
4174
|
+
|
|
4175
|
+
if (!isPlainObject(value)) {
|
|
4176
|
+
return invalidFromErrorFactory(
|
|
4177
|
+
errorFactory,
|
|
4178
|
+
`${path} must be an object when provided`,
|
|
4179
|
+
);
|
|
4180
|
+
}
|
|
4181
|
+
|
|
4182
|
+
return VALID_RESULT;
|
|
4183
|
+
};
|
|
4184
|
+
|
|
4185
|
+
const validateLayoutItems = ({ items, path, errorFactory }) => {
|
|
4186
|
+
for (const [itemId, item] of Object.entries(items)) {
|
|
4187
|
+
const itemPath = `${path}.${itemId}`;
|
|
4188
|
+
|
|
4189
|
+
if (item?.type !== "folder" && item?.type !== "layout") {
|
|
4190
|
+
return invalidFromErrorFactory(
|
|
4191
|
+
errorFactory,
|
|
4192
|
+
`${itemPath}.type must be 'folder' or 'layout'`,
|
|
4193
|
+
);
|
|
4194
|
+
}
|
|
4195
|
+
|
|
4196
|
+
{
|
|
4197
|
+
const result = validateAllowedKeys({
|
|
4198
|
+
value: item,
|
|
4199
|
+
allowedKeys:
|
|
4200
|
+
item.type === "folder"
|
|
4201
|
+
? ["id", "type", "name", "description"]
|
|
4202
|
+
: [
|
|
4203
|
+
"id",
|
|
4204
|
+
"type",
|
|
4205
|
+
"name",
|
|
4206
|
+
"description",
|
|
4207
|
+
"layoutType",
|
|
4208
|
+
"isFragment",
|
|
4209
|
+
"thumbnailFileId",
|
|
4210
|
+
"preview",
|
|
4211
|
+
"elements",
|
|
4212
|
+
],
|
|
4213
|
+
path: itemPath,
|
|
4214
|
+
errorFactory,
|
|
4215
|
+
});
|
|
4216
|
+
if (result?.valid === false) {
|
|
4217
|
+
return result;
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4221
|
+
if (!isNonEmptyString(item.id)) {
|
|
4222
|
+
return invalidFromErrorFactory(
|
|
4223
|
+
errorFactory,
|
|
4224
|
+
`${itemPath}.id must be a non-empty string`,
|
|
4225
|
+
);
|
|
4226
|
+
}
|
|
4227
|
+
|
|
4228
|
+
if (item.id !== itemId) {
|
|
4229
|
+
return invalidFromErrorFactory(
|
|
4230
|
+
errorFactory,
|
|
4231
|
+
`${itemPath}.id must match item key '${itemId}'`,
|
|
4232
|
+
);
|
|
4233
|
+
}
|
|
4234
|
+
|
|
4235
|
+
if (!isNonEmptyString(item.name)) {
|
|
4236
|
+
return invalidFromErrorFactory(
|
|
4237
|
+
errorFactory,
|
|
4238
|
+
`${itemPath}.name must be a non-empty string`,
|
|
4239
|
+
);
|
|
4240
|
+
}
|
|
4241
|
+
|
|
4242
|
+
if (item.description !== undefined && !isString(item.description)) {
|
|
4243
|
+
return invalidFromErrorFactory(
|
|
4244
|
+
errorFactory,
|
|
4245
|
+
`${itemPath}.description must be a string when provided`,
|
|
4246
|
+
);
|
|
4247
|
+
}
|
|
4248
|
+
|
|
4249
|
+
if (
|
|
4250
|
+
item.thumbnailFileId !== undefined &&
|
|
4251
|
+
!isNonEmptyString(item.thumbnailFileId)
|
|
4252
|
+
) {
|
|
4253
|
+
return invalidFromErrorFactory(
|
|
4254
|
+
errorFactory,
|
|
4255
|
+
`${itemPath}.thumbnailFileId must be a non-empty string when provided`,
|
|
4256
|
+
);
|
|
4257
|
+
}
|
|
4258
|
+
|
|
4259
|
+
{
|
|
4260
|
+
const result = validatePreviewObject({
|
|
4261
|
+
value: item.preview,
|
|
4262
|
+
path: `${itemPath}.preview`,
|
|
4263
|
+
errorFactory,
|
|
4264
|
+
});
|
|
4265
|
+
if (result?.valid === false) {
|
|
4266
|
+
return result;
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
|
|
4270
|
+
if (item.type === "layout") {
|
|
4271
|
+
if (!LAYOUT_TYPE_KEYS.includes(item.layoutType)) {
|
|
4272
|
+
return invalidFromErrorFactory(
|
|
4273
|
+
errorFactory,
|
|
4274
|
+
`${itemPath}.layoutType must be 'general', 'save-load', 'confirmDialog', 'dialogue-adv', 'dialogue-nvl', 'choice', or 'history'`,
|
|
4275
|
+
);
|
|
4276
|
+
}
|
|
4277
|
+
|
|
4278
|
+
if (
|
|
4279
|
+
item.isFragment !== undefined &&
|
|
4280
|
+
typeof item.isFragment !== "boolean"
|
|
4281
|
+
) {
|
|
4282
|
+
return invalidFromErrorFactory(
|
|
4283
|
+
errorFactory,
|
|
4284
|
+
`${itemPath}.isFragment must be a boolean when provided`,
|
|
4285
|
+
);
|
|
4286
|
+
}
|
|
4287
|
+
|
|
4288
|
+
{
|
|
4289
|
+
const result = validateNestedCollection({
|
|
4290
|
+
collection: item.elements,
|
|
4291
|
+
path: `${itemPath}.elements`,
|
|
4292
|
+
itemValidator: validateLayoutElementItems,
|
|
4293
|
+
treeValidator: validateLayoutElementTreeOwnership,
|
|
4294
|
+
treeNodeLabel: "layout element",
|
|
4295
|
+
});
|
|
4296
|
+
if (result?.valid === false) {
|
|
4297
|
+
return result;
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
}
|
|
4302
|
+
};
|
|
4303
|
+
|
|
4304
|
+
const validateControlItems = ({ items, path, errorFactory }) => {
|
|
4305
|
+
for (const [itemId, item] of Object.entries(items)) {
|
|
4306
|
+
const itemPath = `${path}.${itemId}`;
|
|
4307
|
+
|
|
4308
|
+
if (item?.type !== "folder" && item?.type !== "control") {
|
|
4309
|
+
return invalidFromErrorFactory(
|
|
4310
|
+
errorFactory,
|
|
4311
|
+
`${itemPath}.type must be 'folder' or 'control'`,
|
|
4312
|
+
);
|
|
4313
|
+
}
|
|
4314
|
+
|
|
4315
|
+
{
|
|
4316
|
+
const result = validateAllowedKeys({
|
|
4317
|
+
value: item,
|
|
3707
4318
|
allowedKeys:
|
|
3708
4319
|
item.type === "folder"
|
|
3709
4320
|
? ["id", "type", "name", "description"]
|
|
@@ -4854,6 +5465,22 @@ export const assertInvariants = ({ state }) => {
|
|
|
4854
5465
|
return result;
|
|
4855
5466
|
}
|
|
4856
5467
|
}
|
|
5468
|
+
|
|
5469
|
+
{
|
|
5470
|
+
const result = validateTagIdsAgainstScope({
|
|
5471
|
+
state,
|
|
5472
|
+
tagIds: image.tagIds,
|
|
5473
|
+
scopeKey: "images",
|
|
5474
|
+
path: "image.tagIds",
|
|
5475
|
+
details: {
|
|
5476
|
+
imageId,
|
|
5477
|
+
},
|
|
5478
|
+
errorFactory: createInvariantValidationError,
|
|
5479
|
+
});
|
|
5480
|
+
if (!result.valid) {
|
|
5481
|
+
return result;
|
|
5482
|
+
}
|
|
5483
|
+
}
|
|
4857
5484
|
}
|
|
4858
5485
|
|
|
4859
5486
|
for (const [spritesheetId, spritesheet] of Object.entries(
|
|
@@ -4926,6 +5553,22 @@ export const assertInvariants = ({ state }) => {
|
|
|
4926
5553
|
return result;
|
|
4927
5554
|
}
|
|
4928
5555
|
}
|
|
5556
|
+
|
|
5557
|
+
{
|
|
5558
|
+
const result = validateTagIdsAgainstScope({
|
|
5559
|
+
state,
|
|
5560
|
+
tagIds: sound.tagIds,
|
|
5561
|
+
scopeKey: "sounds",
|
|
5562
|
+
path: "sound.tagIds",
|
|
5563
|
+
details: {
|
|
5564
|
+
soundId,
|
|
5565
|
+
},
|
|
5566
|
+
errorFactory: createInvariantValidationError,
|
|
5567
|
+
});
|
|
5568
|
+
if (!result.valid) {
|
|
5569
|
+
return result;
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
4929
5572
|
}
|
|
4930
5573
|
|
|
4931
5574
|
for (const [videoId, video] of Object.entries(state.videos.items)) {
|
|
@@ -4958,6 +5601,22 @@ export const assertInvariants = ({ state }) => {
|
|
|
4958
5601
|
return result;
|
|
4959
5602
|
}
|
|
4960
5603
|
}
|
|
5604
|
+
|
|
5605
|
+
{
|
|
5606
|
+
const result = validateTagIdsAgainstScope({
|
|
5607
|
+
state,
|
|
5608
|
+
tagIds: video.tagIds,
|
|
5609
|
+
scopeKey: "videos",
|
|
5610
|
+
path: "video.tagIds",
|
|
5611
|
+
details: {
|
|
5612
|
+
videoId,
|
|
5613
|
+
},
|
|
5614
|
+
errorFactory: createInvariantValidationError,
|
|
5615
|
+
});
|
|
5616
|
+
if (!result.valid) {
|
|
5617
|
+
return result;
|
|
5618
|
+
}
|
|
5619
|
+
}
|
|
4961
5620
|
}
|
|
4962
5621
|
|
|
4963
5622
|
for (const [fontId, font] of Object.entries(state.fonts.items)) {
|
|
@@ -5016,6 +5675,22 @@ export const assertInvariants = ({ state }) => {
|
|
|
5016
5675
|
}
|
|
5017
5676
|
}
|
|
5018
5677
|
|
|
5678
|
+
{
|
|
5679
|
+
const result = validateTagIdsAgainstScope({
|
|
5680
|
+
state,
|
|
5681
|
+
tagIds: character.tagIds,
|
|
5682
|
+
scopeKey: "characters",
|
|
5683
|
+
path: "character.tagIds",
|
|
5684
|
+
details: {
|
|
5685
|
+
characterId,
|
|
5686
|
+
},
|
|
5687
|
+
errorFactory: createInvariantValidationError,
|
|
5688
|
+
});
|
|
5689
|
+
if (!result.valid) {
|
|
5690
|
+
return result;
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5693
|
+
|
|
5019
5694
|
for (const [spriteId, sprite] of Object.entries(
|
|
5020
5695
|
character.sprites?.items || {},
|
|
5021
5696
|
)) {
|
|
@@ -5034,23 +5709,64 @@ export const assertInvariants = ({ state }) => {
|
|
|
5034
5709
|
return result;
|
|
5035
5710
|
}
|
|
5036
5711
|
|
|
5712
|
+
{
|
|
5713
|
+
const tagResult = validateTagIdsAgainstScope({
|
|
5714
|
+
state,
|
|
5715
|
+
tagIds: sprite.tagIds,
|
|
5716
|
+
scopeKey: `${CHARACTER_SPRITE_TAG_SCOPE_PREFIX}${characterId}`,
|
|
5717
|
+
path: "character.sprite.tagIds",
|
|
5718
|
+
details: {
|
|
5719
|
+
characterId,
|
|
5720
|
+
spriteId,
|
|
5721
|
+
},
|
|
5722
|
+
errorFactory: createInvariantValidationError,
|
|
5723
|
+
});
|
|
5724
|
+
if (!tagResult.valid) {
|
|
5725
|
+
return tagResult;
|
|
5726
|
+
}
|
|
5727
|
+
}
|
|
5728
|
+
|
|
5037
5729
|
if (sprite.thumbnailFileId === undefined) {
|
|
5038
5730
|
continue;
|
|
5039
5731
|
}
|
|
5040
5732
|
|
|
5041
|
-
const thumbnailResult = validateFileReference({
|
|
5733
|
+
const thumbnailResult = validateFileReference({
|
|
5734
|
+
state,
|
|
5735
|
+
fileId: sprite.thumbnailFileId,
|
|
5736
|
+
path: "character.sprite.thumbnailFileId",
|
|
5737
|
+
details: {
|
|
5738
|
+
characterId,
|
|
5739
|
+
spriteId,
|
|
5740
|
+
thumbnailFileId: sprite.thumbnailFileId,
|
|
5741
|
+
},
|
|
5742
|
+
errorFactory: createInvariantValidationError,
|
|
5743
|
+
});
|
|
5744
|
+
if (!thumbnailResult.valid) {
|
|
5745
|
+
return thumbnailResult;
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5748
|
+
}
|
|
5749
|
+
|
|
5750
|
+
for (const [transformId, transform] of Object.entries(
|
|
5751
|
+
state.transforms.items,
|
|
5752
|
+
)) {
|
|
5753
|
+
if (transform.type !== "transform") {
|
|
5754
|
+
continue;
|
|
5755
|
+
}
|
|
5756
|
+
|
|
5757
|
+
{
|
|
5758
|
+
const result = validateTagIdsAgainstScope({
|
|
5042
5759
|
state,
|
|
5043
|
-
|
|
5044
|
-
|
|
5760
|
+
tagIds: transform.tagIds,
|
|
5761
|
+
scopeKey: "transforms",
|
|
5762
|
+
path: "transform.tagIds",
|
|
5045
5763
|
details: {
|
|
5046
|
-
|
|
5047
|
-
spriteId,
|
|
5048
|
-
thumbnailFileId: sprite.thumbnailFileId,
|
|
5764
|
+
transformId,
|
|
5049
5765
|
},
|
|
5050
5766
|
errorFactory: createInvariantValidationError,
|
|
5051
5767
|
});
|
|
5052
|
-
if (!
|
|
5053
|
-
return
|
|
5768
|
+
if (!result.valid) {
|
|
5769
|
+
return result;
|
|
5054
5770
|
}
|
|
5055
5771
|
}
|
|
5056
5772
|
}
|
|
@@ -5366,7 +6082,7 @@ export const assertInvariants = ({ state }) => {
|
|
|
5366
6082
|
!isLayoutConditionTarget(state, rule.when.target)
|
|
5367
6083
|
) {
|
|
5368
6084
|
return invalidInvariant(
|
|
5369
|
-
`${ownerLabel} element conditionalOverrides when target must reference an existing variable or supported
|
|
6085
|
+
`${ownerLabel} element conditionalOverrides when target must reference an existing variable or supported layout condition`,
|
|
5370
6086
|
{
|
|
5371
6087
|
[ownerIdField]: ownerId,
|
|
5372
6088
|
elementId,
|
|
@@ -5511,6 +6227,18 @@ const runValidateState = ({ state }) => {
|
|
|
5511
6227
|
);
|
|
5512
6228
|
}
|
|
5513
6229
|
|
|
6230
|
+
{
|
|
6231
|
+
const result = validateTagsRoot({
|
|
6232
|
+
state: normalizedState,
|
|
6233
|
+
tags: normalizedState.tags,
|
|
6234
|
+
path: "state.tags",
|
|
6235
|
+
errorFactory: createStateValidationError,
|
|
6236
|
+
});
|
|
6237
|
+
if (result?.valid === false) {
|
|
6238
|
+
return result;
|
|
6239
|
+
}
|
|
6240
|
+
}
|
|
6241
|
+
|
|
5514
6242
|
for (const collectionKey of COLLECTION_KEYS) {
|
|
5515
6243
|
{
|
|
5516
6244
|
const result = validateCollection({
|
|
@@ -5726,6 +6454,46 @@ const validateRequiredUniqueIdArray = ({ value, path, errorFactory }) => {
|
|
|
5726
6454
|
}
|
|
5727
6455
|
};
|
|
5728
6456
|
|
|
6457
|
+
const validateOptionalUniqueIdArray = ({
|
|
6458
|
+
value,
|
|
6459
|
+
path,
|
|
6460
|
+
errorFactory,
|
|
6461
|
+
allowEmpty = true,
|
|
6462
|
+
}) => {
|
|
6463
|
+
if (value === undefined) {
|
|
6464
|
+
return VALID_RESULT;
|
|
6465
|
+
}
|
|
6466
|
+
|
|
6467
|
+
if (!Array.isArray(value) || (!allowEmpty && value.length === 0)) {
|
|
6468
|
+
return invalidFromErrorFactory(
|
|
6469
|
+
errorFactory,
|
|
6470
|
+
allowEmpty
|
|
6471
|
+
? `${path} must be an array when provided`
|
|
6472
|
+
: `${path} must be a non-empty array when provided`,
|
|
6473
|
+
);
|
|
6474
|
+
}
|
|
6475
|
+
|
|
6476
|
+
const seen = new Set();
|
|
6477
|
+
|
|
6478
|
+
for (const [index, entry] of value.entries()) {
|
|
6479
|
+
if (!isNonEmptyString(entry)) {
|
|
6480
|
+
return invalidFromErrorFactory(
|
|
6481
|
+
errorFactory,
|
|
6482
|
+
`${path}[${index}] must be a non-empty string`,
|
|
6483
|
+
);
|
|
6484
|
+
}
|
|
6485
|
+
|
|
6486
|
+
if (seen.has(entry)) {
|
|
6487
|
+
return invalidFromErrorFactory(
|
|
6488
|
+
errorFactory,
|
|
6489
|
+
`${path}[${index}] must be unique`,
|
|
6490
|
+
);
|
|
6491
|
+
}
|
|
6492
|
+
|
|
6493
|
+
seen.add(entry);
|
|
6494
|
+
}
|
|
6495
|
+
};
|
|
6496
|
+
|
|
5729
6497
|
const validateSectionCreateData = ({ data, errorFactory }) => {
|
|
5730
6498
|
{
|
|
5731
6499
|
const result = validateExactKeys({
|
|
@@ -5948,6 +6716,7 @@ const validateImageCreateData = ({ data, errorFactory }) => {
|
|
|
5948
6716
|
"type",
|
|
5949
6717
|
"name",
|
|
5950
6718
|
"description",
|
|
6719
|
+
"tagIds",
|
|
5951
6720
|
"thumbnailFileId",
|
|
5952
6721
|
"fileId",
|
|
5953
6722
|
"width",
|
|
@@ -5976,6 +6745,17 @@ const validateImageCreateData = ({ data, errorFactory }) => {
|
|
|
5976
6745
|
}
|
|
5977
6746
|
|
|
5978
6747
|
if (data.type === "image") {
|
|
6748
|
+
{
|
|
6749
|
+
const result = validateOptionalUniqueIdArray({
|
|
6750
|
+
value: data.tagIds,
|
|
6751
|
+
path: "payload.data.tagIds",
|
|
6752
|
+
errorFactory,
|
|
6753
|
+
});
|
|
6754
|
+
if (result?.valid === false) {
|
|
6755
|
+
return result;
|
|
6756
|
+
}
|
|
6757
|
+
}
|
|
6758
|
+
|
|
5979
6759
|
if (
|
|
5980
6760
|
data.thumbnailFileId !== undefined &&
|
|
5981
6761
|
!isNonEmptyString(data.thumbnailFileId)
|
|
@@ -6016,6 +6796,7 @@ const validateImageUpdateData = ({ data, errorFactory }) => {
|
|
|
6016
6796
|
allowedKeys: [
|
|
6017
6797
|
"name",
|
|
6018
6798
|
"description",
|
|
6799
|
+
"tagIds",
|
|
6019
6800
|
"thumbnailFileId",
|
|
6020
6801
|
"fileId",
|
|
6021
6802
|
"width",
|
|
@@ -6050,6 +6831,17 @@ const validateImageUpdateData = ({ data, errorFactory }) => {
|
|
|
6050
6831
|
);
|
|
6051
6832
|
}
|
|
6052
6833
|
|
|
6834
|
+
{
|
|
6835
|
+
const result = validateOptionalUniqueIdArray({
|
|
6836
|
+
value: data.tagIds,
|
|
6837
|
+
path: "payload.data.tagIds",
|
|
6838
|
+
errorFactory,
|
|
6839
|
+
});
|
|
6840
|
+
if (result?.valid === false) {
|
|
6841
|
+
return result;
|
|
6842
|
+
}
|
|
6843
|
+
}
|
|
6844
|
+
|
|
6053
6845
|
if (
|
|
6054
6846
|
data.thumbnailFileId !== undefined &&
|
|
6055
6847
|
!isNonEmptyString(data.thumbnailFileId)
|
|
@@ -6314,6 +7106,7 @@ const validateSoundCreateData = ({ data, errorFactory }) => {
|
|
|
6314
7106
|
"type",
|
|
6315
7107
|
"name",
|
|
6316
7108
|
"description",
|
|
7109
|
+
"tagIds",
|
|
6317
7110
|
"fileId",
|
|
6318
7111
|
"waveformDataFileId",
|
|
6319
7112
|
"duration",
|
|
@@ -6341,6 +7134,17 @@ const validateSoundCreateData = ({ data, errorFactory }) => {
|
|
|
6341
7134
|
}
|
|
6342
7135
|
|
|
6343
7136
|
if (data.type === "sound") {
|
|
7137
|
+
{
|
|
7138
|
+
const result = validateOptionalUniqueIdArray({
|
|
7139
|
+
value: data.tagIds,
|
|
7140
|
+
path: "payload.data.tagIds",
|
|
7141
|
+
errorFactory,
|
|
7142
|
+
});
|
|
7143
|
+
if (result?.valid === false) {
|
|
7144
|
+
return result;
|
|
7145
|
+
}
|
|
7146
|
+
}
|
|
7147
|
+
|
|
6344
7148
|
if (!isNonEmptyString(data.fileId)) {
|
|
6345
7149
|
return invalidFromErrorFactory(
|
|
6346
7150
|
errorFactory,
|
|
@@ -6375,6 +7179,7 @@ const validateSoundUpdateData = ({ data, errorFactory }) => {
|
|
|
6375
7179
|
allowedKeys: [
|
|
6376
7180
|
"name",
|
|
6377
7181
|
"description",
|
|
7182
|
+
"tagIds",
|
|
6378
7183
|
"fileId",
|
|
6379
7184
|
"waveformDataFileId",
|
|
6380
7185
|
"duration",
|
|
@@ -6408,6 +7213,17 @@ const validateSoundUpdateData = ({ data, errorFactory }) => {
|
|
|
6408
7213
|
);
|
|
6409
7214
|
}
|
|
6410
7215
|
|
|
7216
|
+
{
|
|
7217
|
+
const result = validateOptionalUniqueIdArray({
|
|
7218
|
+
value: data.tagIds,
|
|
7219
|
+
path: "payload.data.tagIds",
|
|
7220
|
+
errorFactory,
|
|
7221
|
+
});
|
|
7222
|
+
if (result?.valid === false) {
|
|
7223
|
+
return result;
|
|
7224
|
+
}
|
|
7225
|
+
}
|
|
7226
|
+
|
|
6411
7227
|
if (data.fileId !== undefined && !isNonEmptyString(data.fileId)) {
|
|
6412
7228
|
return invalidFromErrorFactory(
|
|
6413
7229
|
errorFactory,
|
|
@@ -6459,6 +7275,7 @@ const validateVideoCreateData = ({ data, errorFactory }) => {
|
|
|
6459
7275
|
"type",
|
|
6460
7276
|
"name",
|
|
6461
7277
|
"description",
|
|
7278
|
+
"tagIds",
|
|
6462
7279
|
"fileId",
|
|
6463
7280
|
"thumbnailFileId",
|
|
6464
7281
|
"duration",
|
|
@@ -6488,6 +7305,17 @@ const validateVideoCreateData = ({ data, errorFactory }) => {
|
|
|
6488
7305
|
}
|
|
6489
7306
|
|
|
6490
7307
|
if (data.type === "video") {
|
|
7308
|
+
{
|
|
7309
|
+
const result = validateOptionalUniqueIdArray({
|
|
7310
|
+
value: data.tagIds,
|
|
7311
|
+
path: "payload.data.tagIds",
|
|
7312
|
+
errorFactory,
|
|
7313
|
+
});
|
|
7314
|
+
if (result?.valid === false) {
|
|
7315
|
+
return result;
|
|
7316
|
+
}
|
|
7317
|
+
}
|
|
7318
|
+
|
|
6491
7319
|
if (!isNonEmptyString(data.fileId)) {
|
|
6492
7320
|
return invalidFromErrorFactory(
|
|
6493
7321
|
errorFactory,
|
|
@@ -6532,6 +7360,7 @@ const validateVideoUpdateData = ({ data, errorFactory }) => {
|
|
|
6532
7360
|
allowedKeys: [
|
|
6533
7361
|
"name",
|
|
6534
7362
|
"description",
|
|
7363
|
+
"tagIds",
|
|
6535
7364
|
"fileId",
|
|
6536
7365
|
"thumbnailFileId",
|
|
6537
7366
|
"duration",
|
|
@@ -6567,6 +7396,17 @@ const validateVideoUpdateData = ({ data, errorFactory }) => {
|
|
|
6567
7396
|
);
|
|
6568
7397
|
}
|
|
6569
7398
|
|
|
7399
|
+
{
|
|
7400
|
+
const result = validateOptionalUniqueIdArray({
|
|
7401
|
+
value: data.tagIds,
|
|
7402
|
+
path: "payload.data.tagIds",
|
|
7403
|
+
errorFactory,
|
|
7404
|
+
});
|
|
7405
|
+
if (result?.valid === false) {
|
|
7406
|
+
return result;
|
|
7407
|
+
}
|
|
7408
|
+
}
|
|
7409
|
+
|
|
6570
7410
|
if (data.fileId !== undefined && !isNonEmptyString(data.fileId)) {
|
|
6571
7411
|
return invalidFromErrorFactory(
|
|
6572
7412
|
errorFactory,
|
|
@@ -7054,6 +7894,7 @@ const validateTransformCreateData = ({ data, errorFactory }) => {
|
|
|
7054
7894
|
"type",
|
|
7055
7895
|
"name",
|
|
7056
7896
|
"description",
|
|
7897
|
+
"tagIds",
|
|
7057
7898
|
"x",
|
|
7058
7899
|
"y",
|
|
7059
7900
|
"scaleX",
|
|
@@ -7085,6 +7926,17 @@ const validateTransformCreateData = ({ data, errorFactory }) => {
|
|
|
7085
7926
|
}
|
|
7086
7927
|
|
|
7087
7928
|
if (data.type === "transform") {
|
|
7929
|
+
{
|
|
7930
|
+
const result = validateOptionalUniqueIdArray({
|
|
7931
|
+
value: data.tagIds,
|
|
7932
|
+
path: "payload.data.tagIds",
|
|
7933
|
+
errorFactory,
|
|
7934
|
+
});
|
|
7935
|
+
if (result?.valid === false) {
|
|
7936
|
+
return result;
|
|
7937
|
+
}
|
|
7938
|
+
}
|
|
7939
|
+
|
|
7088
7940
|
for (const key of [
|
|
7089
7941
|
"x",
|
|
7090
7942
|
"y",
|
|
@@ -7290,6 +8142,7 @@ const validateTransformUpdateData = ({ data, errorFactory }) => {
|
|
|
7290
8142
|
allowedKeys: [
|
|
7291
8143
|
"name",
|
|
7292
8144
|
"description",
|
|
8145
|
+
"tagIds",
|
|
7293
8146
|
"x",
|
|
7294
8147
|
"y",
|
|
7295
8148
|
"scaleX",
|
|
@@ -7327,6 +8180,17 @@ const validateTransformUpdateData = ({ data, errorFactory }) => {
|
|
|
7327
8180
|
);
|
|
7328
8181
|
}
|
|
7329
8182
|
|
|
8183
|
+
{
|
|
8184
|
+
const result = validateOptionalUniqueIdArray({
|
|
8185
|
+
value: data.tagIds,
|
|
8186
|
+
path: "payload.data.tagIds",
|
|
8187
|
+
errorFactory,
|
|
8188
|
+
});
|
|
8189
|
+
if (result?.valid === false) {
|
|
8190
|
+
return result;
|
|
8191
|
+
}
|
|
8192
|
+
}
|
|
8193
|
+
|
|
7330
8194
|
for (const key of [
|
|
7331
8195
|
"x",
|
|
7332
8196
|
"y",
|
|
@@ -7680,6 +8544,7 @@ const validateCharacterSpriteCreateData = ({ data, errorFactory }) => {
|
|
|
7680
8544
|
"type",
|
|
7681
8545
|
"name",
|
|
7682
8546
|
"description",
|
|
8547
|
+
"tagIds",
|
|
7683
8548
|
"fileId",
|
|
7684
8549
|
"thumbnailFileId",
|
|
7685
8550
|
"width",
|
|
@@ -7708,6 +8573,17 @@ const validateCharacterSpriteCreateData = ({ data, errorFactory }) => {
|
|
|
7708
8573
|
}
|
|
7709
8574
|
|
|
7710
8575
|
if (data.type === "image") {
|
|
8576
|
+
{
|
|
8577
|
+
const result = validateOptionalUniqueIdArray({
|
|
8578
|
+
value: data.tagIds,
|
|
8579
|
+
path: "payload.data.tagIds",
|
|
8580
|
+
errorFactory,
|
|
8581
|
+
});
|
|
8582
|
+
if (result?.valid === false) {
|
|
8583
|
+
return result;
|
|
8584
|
+
}
|
|
8585
|
+
}
|
|
8586
|
+
|
|
7711
8587
|
if (!isNonEmptyString(data.fileId)) {
|
|
7712
8588
|
return invalidFromErrorFactory(
|
|
7713
8589
|
errorFactory,
|
|
@@ -7748,6 +8624,7 @@ const validateCharacterSpriteUpdateData = ({ data, errorFactory }) => {
|
|
|
7748
8624
|
allowedKeys: [
|
|
7749
8625
|
"name",
|
|
7750
8626
|
"description",
|
|
8627
|
+
"tagIds",
|
|
7751
8628
|
"fileId",
|
|
7752
8629
|
"thumbnailFileId",
|
|
7753
8630
|
"width",
|
|
@@ -7775,6 +8652,17 @@ const validateCharacterSpriteUpdateData = ({ data, errorFactory }) => {
|
|
|
7775
8652
|
);
|
|
7776
8653
|
}
|
|
7777
8654
|
|
|
8655
|
+
{
|
|
8656
|
+
const result = validateOptionalUniqueIdArray({
|
|
8657
|
+
value: data.tagIds,
|
|
8658
|
+
path: "payload.data.tagIds",
|
|
8659
|
+
errorFactory,
|
|
8660
|
+
});
|
|
8661
|
+
if (result?.valid === false) {
|
|
8662
|
+
return result;
|
|
8663
|
+
}
|
|
8664
|
+
}
|
|
8665
|
+
|
|
7778
8666
|
if (data.fileId !== undefined && !isNonEmptyString(data.fileId)) {
|
|
7779
8667
|
return invalidFromErrorFactory(
|
|
7780
8668
|
errorFactory,
|
|
@@ -7839,6 +8727,7 @@ const validateCharacterCreateData = ({ data, errorFactory }) => {
|
|
|
7839
8727
|
"type",
|
|
7840
8728
|
"name",
|
|
7841
8729
|
"description",
|
|
8730
|
+
"tagIds",
|
|
7842
8731
|
"shortcut",
|
|
7843
8732
|
"fileId",
|
|
7844
8733
|
"sprites",
|
|
@@ -7866,6 +8755,17 @@ const validateCharacterCreateData = ({ data, errorFactory }) => {
|
|
|
7866
8755
|
}
|
|
7867
8756
|
|
|
7868
8757
|
if (data.type === "character") {
|
|
8758
|
+
{
|
|
8759
|
+
const result = validateOptionalUniqueIdArray({
|
|
8760
|
+
value: data.tagIds,
|
|
8761
|
+
path: "payload.data.tagIds",
|
|
8762
|
+
errorFactory,
|
|
8763
|
+
});
|
|
8764
|
+
if (result?.valid === false) {
|
|
8765
|
+
return result;
|
|
8766
|
+
}
|
|
8767
|
+
}
|
|
8768
|
+
|
|
7869
8769
|
if (data.shortcut !== undefined && !isString(data.shortcut)) {
|
|
7870
8770
|
return invalidFromErrorFactory(
|
|
7871
8771
|
errorFactory,
|
|
@@ -7885,7 +8785,13 @@ const validateCharacterCreateData = ({ data, errorFactory }) => {
|
|
|
7885
8785
|
const result = validateNestedCollection({
|
|
7886
8786
|
collection: data.sprites,
|
|
7887
8787
|
path: "payload.data.sprites",
|
|
7888
|
-
itemValidator:
|
|
8788
|
+
itemValidator: ({ items, path, errorFactory }) =>
|
|
8789
|
+
validateCharacterSpriteItems({
|
|
8790
|
+
items,
|
|
8791
|
+
path,
|
|
8792
|
+
errorFactory,
|
|
8793
|
+
allowTagIds: false,
|
|
8794
|
+
}),
|
|
7889
8795
|
treeValidator: validateGenericFolderOwnership,
|
|
7890
8796
|
folderLabel: "folder sprite item",
|
|
7891
8797
|
errorFactory,
|
|
@@ -7905,6 +8811,7 @@ const validateCharacterUpdateData = ({ data, errorFactory }) => {
|
|
|
7905
8811
|
allowedKeys: [
|
|
7906
8812
|
"name",
|
|
7907
8813
|
"description",
|
|
8814
|
+
"tagIds",
|
|
7908
8815
|
"shortcut",
|
|
7909
8816
|
"fileId",
|
|
7910
8817
|
],
|
|
@@ -7944,6 +8851,17 @@ const validateCharacterUpdateData = ({ data, errorFactory }) => {
|
|
|
7944
8851
|
);
|
|
7945
8852
|
}
|
|
7946
8853
|
|
|
8854
|
+
{
|
|
8855
|
+
const result = validateOptionalUniqueIdArray({
|
|
8856
|
+
value: data.tagIds,
|
|
8857
|
+
path: "payload.data.tagIds",
|
|
8858
|
+
errorFactory,
|
|
8859
|
+
});
|
|
8860
|
+
if (result?.valid === false) {
|
|
8861
|
+
return result;
|
|
8862
|
+
}
|
|
8863
|
+
}
|
|
8864
|
+
|
|
7947
8865
|
if (data.fileId !== undefined && !isNonEmptyString(data.fileId)) {
|
|
7948
8866
|
return invalidFromErrorFactory(
|
|
7949
8867
|
errorFactory,
|
|
@@ -8616,7 +9534,7 @@ const validateVisualElementReferenceTargets = ({
|
|
|
8616
9534
|
if (!isLayoutConditionTarget(state, rule?.when?.target)) {
|
|
8617
9535
|
return invalidFromErrorFactory(
|
|
8618
9536
|
errorFactory,
|
|
8619
|
-
`${ownerLabel} element conditionalOverrides.${index}.when.target must reference an existing variable or supported
|
|
9537
|
+
`${ownerLabel} element conditionalOverrides.${index}.when.target must reference an existing variable or supported layout condition`,
|
|
8620
9538
|
{
|
|
8621
9539
|
[ownerIdField]: ownerId,
|
|
8622
9540
|
elementId,
|
|
@@ -8790,6 +9708,7 @@ const createFolderedCollectionCommandDefinitions = ({
|
|
|
8790
9708
|
validateCreateState = () => {},
|
|
8791
9709
|
validateUpdateState = () => {},
|
|
8792
9710
|
validateDeleteState = () => {},
|
|
9711
|
+
afterDelete = () => {},
|
|
8793
9712
|
includeUpdate = true,
|
|
8794
9713
|
}) => {
|
|
8795
9714
|
const existingMessage = `payload.${idField} must reference an existing ${itemLabel}`;
|
|
@@ -9037,6 +9956,7 @@ const createFolderedCollectionCommandDefinitions = ({
|
|
|
9037
9956
|
},
|
|
9038
9957
|
reduce: ({ state, payload }) => {
|
|
9039
9958
|
const deletedIds = new Set();
|
|
9959
|
+
const deletedItemsById = new Map();
|
|
9040
9960
|
|
|
9041
9961
|
for (const itemId of payload[deleteArrayField]) {
|
|
9042
9962
|
const removedNode = removeTreeNode({
|
|
@@ -9052,6 +9972,10 @@ const createFolderedCollectionCommandDefinitions = ({
|
|
|
9052
9972
|
node: removedNode,
|
|
9053
9973
|
})) {
|
|
9054
9974
|
deletedIds.add(descendantId);
|
|
9975
|
+
deletedItemsById.set(
|
|
9976
|
+
descendantId,
|
|
9977
|
+
state[collectionKey].items[descendantId],
|
|
9978
|
+
);
|
|
9055
9979
|
}
|
|
9056
9980
|
}
|
|
9057
9981
|
|
|
@@ -9059,6 +9983,13 @@ const createFolderedCollectionCommandDefinitions = ({
|
|
|
9059
9983
|
delete state[collectionKey].items[itemId];
|
|
9060
9984
|
}
|
|
9061
9985
|
|
|
9986
|
+
afterDelete({
|
|
9987
|
+
state,
|
|
9988
|
+
payload,
|
|
9989
|
+
deletedIds,
|
|
9990
|
+
deletedItemsById,
|
|
9991
|
+
});
|
|
9992
|
+
|
|
9062
9993
|
return state;
|
|
9063
9994
|
},
|
|
9064
9995
|
},
|
|
@@ -9389,7 +10320,8 @@ const COMMAND_DEFINITIONS = [
|
|
|
9389
10320
|
}
|
|
9390
10321
|
},
|
|
9391
10322
|
validateAgainstState: () => {},
|
|
9392
|
-
reduce: ({ payload }) =>
|
|
10323
|
+
reduce: ({ payload }) =>
|
|
10324
|
+
structuredClone(normalizeStateCollections(payload.state)),
|
|
9393
10325
|
},
|
|
9394
10326
|
...createFolderedCollectionCommandDefinitions({
|
|
9395
10327
|
familyName: "file",
|
|
@@ -10881,6 +11813,16 @@ const COMMAND_DEFINITIONS = [
|
|
|
10881
11813
|
if (!result.valid) {
|
|
10882
11814
|
return result;
|
|
10883
11815
|
}
|
|
11816
|
+
|
|
11817
|
+
return validateTagIdsAgainstScope({
|
|
11818
|
+
state,
|
|
11819
|
+
tagIds: payload.data.tagIds,
|
|
11820
|
+
scopeKey: "images",
|
|
11821
|
+
path: "payload.data.tagIds",
|
|
11822
|
+
details: {
|
|
11823
|
+
imageId: payload.imageId,
|
|
11824
|
+
},
|
|
11825
|
+
});
|
|
10884
11826
|
}
|
|
10885
11827
|
},
|
|
10886
11828
|
reduce: ({ state, payload }) => {
|
|
@@ -10905,6 +11847,11 @@ const COMMAND_DEFINITIONS = [
|
|
|
10905
11847
|
if (payload.data.height !== undefined) {
|
|
10906
11848
|
nextImage.height = payload.data.height;
|
|
10907
11849
|
}
|
|
11850
|
+
|
|
11851
|
+
assignOptionalTagIds({
|
|
11852
|
+
target: nextImage,
|
|
11853
|
+
tagIds: payload.data.tagIds,
|
|
11854
|
+
});
|
|
10908
11855
|
}
|
|
10909
11856
|
|
|
10910
11857
|
state.images.items[payload.imageId] = nextImage;
|
|
@@ -10966,7 +11913,8 @@ const COMMAND_DEFINITIONS = [
|
|
|
10966
11913
|
(payload.data.fileId !== undefined ||
|
|
10967
11914
|
payload.data.thumbnailFileId !== undefined ||
|
|
10968
11915
|
payload.data.width !== undefined ||
|
|
10969
|
-
payload.data.height !== undefined
|
|
11916
|
+
payload.data.height !== undefined ||
|
|
11917
|
+
payload.data.tagIds !== undefined)
|
|
10970
11918
|
) {
|
|
10971
11919
|
return invalidPrecondition(
|
|
10972
11920
|
"folder image items cannot update file fields",
|
|
@@ -10985,14 +11933,24 @@ const COMMAND_DEFINITIONS = [
|
|
|
10985
11933
|
if (!result.valid) {
|
|
10986
11934
|
return result;
|
|
10987
11935
|
}
|
|
11936
|
+
|
|
11937
|
+
return validateTagIdsAgainstScope({
|
|
11938
|
+
state,
|
|
11939
|
+
tagIds: payload.data.tagIds,
|
|
11940
|
+
scopeKey: "images",
|
|
11941
|
+
path: "payload.data.tagIds",
|
|
11942
|
+
details: {
|
|
11943
|
+
imageId: payload.imageId,
|
|
11944
|
+
},
|
|
11945
|
+
});
|
|
10988
11946
|
}
|
|
10989
11947
|
},
|
|
10990
11948
|
reduce: ({ state, payload }) => {
|
|
10991
11949
|
const currentImage = state.images.items[payload.imageId];
|
|
10992
|
-
state.images.items[payload.imageId] = {
|
|
10993
|
-
|
|
10994
|
-
|
|
10995
|
-
};
|
|
11950
|
+
state.images.items[payload.imageId] = applyTagIdsUpdate({
|
|
11951
|
+
currentItem: currentImage,
|
|
11952
|
+
data: payload.data,
|
|
11953
|
+
});
|
|
10996
11954
|
return state;
|
|
10997
11955
|
},
|
|
10998
11956
|
},
|
|
@@ -11298,6 +12256,16 @@ const COMMAND_DEFINITIONS = [
|
|
|
11298
12256
|
if (!result.valid) {
|
|
11299
12257
|
return result;
|
|
11300
12258
|
}
|
|
12259
|
+
|
|
12260
|
+
return validateTagIdsAgainstScope({
|
|
12261
|
+
state,
|
|
12262
|
+
tagIds: payload.data.tagIds,
|
|
12263
|
+
scopeKey: "sounds",
|
|
12264
|
+
path: "payload.data.tagIds",
|
|
12265
|
+
details: {
|
|
12266
|
+
soundId: payload.soundId,
|
|
12267
|
+
},
|
|
12268
|
+
});
|
|
11301
12269
|
}
|
|
11302
12270
|
},
|
|
11303
12271
|
reduce: ({ state, payload }) => {
|
|
@@ -11319,6 +12287,11 @@ const COMMAND_DEFINITIONS = [
|
|
|
11319
12287
|
if (payload.data.duration !== undefined) {
|
|
11320
12288
|
nextSound.duration = payload.data.duration;
|
|
11321
12289
|
}
|
|
12290
|
+
|
|
12291
|
+
assignOptionalTagIds({
|
|
12292
|
+
target: nextSound,
|
|
12293
|
+
tagIds: payload.data.tagIds,
|
|
12294
|
+
});
|
|
11322
12295
|
}
|
|
11323
12296
|
|
|
11324
12297
|
state.sounds.items[payload.soundId] = nextSound;
|
|
@@ -11379,7 +12352,8 @@ const COMMAND_DEFINITIONS = [
|
|
|
11379
12352
|
currentSound.type === "folder" &&
|
|
11380
12353
|
(payload.data.fileId !== undefined ||
|
|
11381
12354
|
payload.data.waveformDataFileId !== undefined ||
|
|
11382
|
-
payload.data.duration !== undefined
|
|
12355
|
+
payload.data.duration !== undefined ||
|
|
12356
|
+
payload.data.tagIds !== undefined)
|
|
11383
12357
|
) {
|
|
11384
12358
|
return invalidPrecondition(
|
|
11385
12359
|
"folder sound items cannot update file fields",
|
|
@@ -11399,14 +12373,24 @@ const COMMAND_DEFINITIONS = [
|
|
|
11399
12373
|
if (!result.valid) {
|
|
11400
12374
|
return result;
|
|
11401
12375
|
}
|
|
12376
|
+
|
|
12377
|
+
return validateTagIdsAgainstScope({
|
|
12378
|
+
state,
|
|
12379
|
+
tagIds: payload.data.tagIds,
|
|
12380
|
+
scopeKey: "sounds",
|
|
12381
|
+
path: "payload.data.tagIds",
|
|
12382
|
+
details: {
|
|
12383
|
+
soundId: payload.soundId,
|
|
12384
|
+
},
|
|
12385
|
+
});
|
|
11402
12386
|
}
|
|
11403
12387
|
},
|
|
11404
12388
|
reduce: ({ state, payload }) => {
|
|
11405
12389
|
const currentSound = state.sounds.items[payload.soundId];
|
|
11406
|
-
state.sounds.items[payload.soundId] = {
|
|
11407
|
-
|
|
11408
|
-
|
|
11409
|
-
};
|
|
12390
|
+
state.sounds.items[payload.soundId] = applyTagIdsUpdate({
|
|
12391
|
+
currentItem: currentSound,
|
|
12392
|
+
data: payload.data,
|
|
12393
|
+
});
|
|
11410
12394
|
return state;
|
|
11411
12395
|
},
|
|
11412
12396
|
},
|
|
@@ -11711,6 +12695,16 @@ const COMMAND_DEFINITIONS = [
|
|
|
11711
12695
|
if (!result.valid) {
|
|
11712
12696
|
return result;
|
|
11713
12697
|
}
|
|
12698
|
+
|
|
12699
|
+
return validateTagIdsAgainstScope({
|
|
12700
|
+
state,
|
|
12701
|
+
tagIds: payload.data.tagIds,
|
|
12702
|
+
scopeKey: "videos",
|
|
12703
|
+
path: "payload.data.tagIds",
|
|
12704
|
+
details: {
|
|
12705
|
+
videoId: payload.videoId,
|
|
12706
|
+
},
|
|
12707
|
+
});
|
|
11714
12708
|
}
|
|
11715
12709
|
},
|
|
11716
12710
|
reduce: ({ state, payload }) => {
|
|
@@ -11736,6 +12730,11 @@ const COMMAND_DEFINITIONS = [
|
|
|
11736
12730
|
if (payload.data.height !== undefined) {
|
|
11737
12731
|
nextVideo.height = payload.data.height;
|
|
11738
12732
|
}
|
|
12733
|
+
|
|
12734
|
+
assignOptionalTagIds({
|
|
12735
|
+
target: nextVideo,
|
|
12736
|
+
tagIds: payload.data.tagIds,
|
|
12737
|
+
});
|
|
11739
12738
|
}
|
|
11740
12739
|
|
|
11741
12740
|
state.videos.items[payload.videoId] = nextVideo;
|
|
@@ -11798,7 +12797,8 @@ const COMMAND_DEFINITIONS = [
|
|
|
11798
12797
|
payload.data.thumbnailFileId !== undefined ||
|
|
11799
12798
|
payload.data.duration !== undefined ||
|
|
11800
12799
|
payload.data.width !== undefined ||
|
|
11801
|
-
payload.data.height !== undefined
|
|
12800
|
+
payload.data.height !== undefined ||
|
|
12801
|
+
payload.data.tagIds !== undefined)
|
|
11802
12802
|
) {
|
|
11803
12803
|
return invalidPrecondition(
|
|
11804
12804
|
"folder video items cannot update file fields",
|
|
@@ -11817,14 +12817,24 @@ const COMMAND_DEFINITIONS = [
|
|
|
11817
12817
|
if (!result.valid) {
|
|
11818
12818
|
return result;
|
|
11819
12819
|
}
|
|
12820
|
+
|
|
12821
|
+
return validateTagIdsAgainstScope({
|
|
12822
|
+
state,
|
|
12823
|
+
tagIds: payload.data.tagIds,
|
|
12824
|
+
scopeKey: "videos",
|
|
12825
|
+
path: "payload.data.tagIds",
|
|
12826
|
+
details: {
|
|
12827
|
+
videoId: payload.videoId,
|
|
12828
|
+
},
|
|
12829
|
+
});
|
|
11820
12830
|
}
|
|
11821
12831
|
},
|
|
11822
12832
|
reduce: ({ state, payload }) => {
|
|
11823
12833
|
const currentVideo = state.videos.items[payload.videoId];
|
|
11824
|
-
state.videos.items[payload.videoId] = {
|
|
11825
|
-
|
|
11826
|
-
|
|
11827
|
-
};
|
|
12834
|
+
state.videos.items[payload.videoId] = applyTagIdsUpdate({
|
|
12835
|
+
currentItem: currentVideo,
|
|
12836
|
+
data: payload.data,
|
|
12837
|
+
});
|
|
11828
12838
|
return state;
|
|
11829
12839
|
},
|
|
11830
12840
|
},
|
|
@@ -13248,7 +14258,7 @@ const COMMAND_DEFINITIONS = [
|
|
|
13248
14258
|
|
|
13249
14259
|
return nextItem;
|
|
13250
14260
|
},
|
|
13251
|
-
validateUpdateState: ({ payload, currentItem }) => {
|
|
14261
|
+
validateUpdateState: ({ state, payload, currentItem }) => {
|
|
13252
14262
|
if (
|
|
13253
14263
|
currentItem.type === "folder" &&
|
|
13254
14264
|
Object.keys(payload.data).some(
|
|
@@ -13268,28 +14278,57 @@ const COMMAND_DEFINITIONS = [
|
|
|
13268
14278
|
itemLabel: "transform item",
|
|
13269
14279
|
createDataValidator: validateTransformCreateData,
|
|
13270
14280
|
updateDataValidator: validateTransformUpdateData,
|
|
13271
|
-
createItem: ({ payload }) =>
|
|
13272
|
-
|
|
13273
|
-
|
|
13274
|
-
|
|
13275
|
-
|
|
13276
|
-
|
|
13277
|
-
|
|
13278
|
-
|
|
13279
|
-
|
|
13280
|
-
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
13285
|
-
|
|
13286
|
-
|
|
13287
|
-
|
|
13288
|
-
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
|
|
14281
|
+
createItem: ({ payload }) => {
|
|
14282
|
+
const item = {
|
|
14283
|
+
id: payload.transformId,
|
|
14284
|
+
type: payload.data.type,
|
|
14285
|
+
name: payload.data.name,
|
|
14286
|
+
...(payload.data.description !== undefined
|
|
14287
|
+
? {
|
|
14288
|
+
description: payload.data.description,
|
|
14289
|
+
}
|
|
14290
|
+
: {}),
|
|
14291
|
+
};
|
|
14292
|
+
|
|
14293
|
+
if (payload.data.type !== "transform") {
|
|
14294
|
+
return item;
|
|
14295
|
+
}
|
|
14296
|
+
|
|
14297
|
+
item.x = payload.data.x;
|
|
14298
|
+
item.y = payload.data.y;
|
|
14299
|
+
item.scaleX = payload.data.scaleX;
|
|
14300
|
+
item.scaleY = payload.data.scaleY;
|
|
14301
|
+
item.anchorX = payload.data.anchorX;
|
|
14302
|
+
item.anchorY = payload.data.anchorY;
|
|
14303
|
+
item.rotation = payload.data.rotation;
|
|
14304
|
+
assignOptionalTagIds({
|
|
14305
|
+
target: item,
|
|
14306
|
+
tagIds: payload.data.tagIds,
|
|
14307
|
+
});
|
|
14308
|
+
|
|
14309
|
+
return item;
|
|
14310
|
+
},
|
|
14311
|
+
updateItem: ({ currentItem, payload }) =>
|
|
14312
|
+
applyTagIdsUpdate({
|
|
14313
|
+
currentItem,
|
|
14314
|
+
data: payload.data,
|
|
14315
|
+
}),
|
|
14316
|
+
validateCreateState: ({ state, payload }) => {
|
|
14317
|
+
if (payload.data.type !== "transform") {
|
|
14318
|
+
return;
|
|
14319
|
+
}
|
|
14320
|
+
|
|
14321
|
+
return validateTagIdsAgainstScope({
|
|
14322
|
+
state,
|
|
14323
|
+
tagIds: payload.data.tagIds,
|
|
14324
|
+
scopeKey: "transforms",
|
|
14325
|
+
path: "payload.data.tagIds",
|
|
14326
|
+
details: {
|
|
14327
|
+
transformId: payload.transformId,
|
|
14328
|
+
},
|
|
14329
|
+
});
|
|
14330
|
+
},
|
|
14331
|
+
validateUpdateState: ({ state, payload, currentItem }) => {
|
|
13293
14332
|
if (
|
|
13294
14333
|
currentItem.type === "folder" &&
|
|
13295
14334
|
Object.keys(payload.data).some(
|
|
@@ -13300,6 +14339,18 @@ const COMMAND_DEFINITIONS = [
|
|
|
13300
14339
|
"folder transform items cannot update transform fields",
|
|
13301
14340
|
);
|
|
13302
14341
|
}
|
|
14342
|
+
|
|
14343
|
+
if (currentItem.type === "transform") {
|
|
14344
|
+
return validateTagIdsAgainstScope({
|
|
14345
|
+
state,
|
|
14346
|
+
tagIds: payload.data.tagIds,
|
|
14347
|
+
scopeKey: "transforms",
|
|
14348
|
+
path: "payload.data.tagIds",
|
|
14349
|
+
details: {
|
|
14350
|
+
transformId: payload.transformId,
|
|
14351
|
+
},
|
|
14352
|
+
});
|
|
14353
|
+
}
|
|
13303
14354
|
},
|
|
13304
14355
|
}),
|
|
13305
14356
|
...createFolderedCollectionCommandDefinitions({
|
|
@@ -13326,7 +14377,7 @@ const COMMAND_DEFINITIONS = [
|
|
|
13326
14377
|
value: payload.data.value,
|
|
13327
14378
|
}),
|
|
13328
14379
|
}),
|
|
13329
|
-
validateUpdateState: ({ payload, currentItem }) => {
|
|
14380
|
+
validateUpdateState: ({ state, payload, currentItem }) => {
|
|
13330
14381
|
if (
|
|
13331
14382
|
currentItem.type === "folder" &&
|
|
13332
14383
|
Object.keys(payload.data).some(
|
|
@@ -13457,6 +14508,11 @@ const COMMAND_DEFINITIONS = [
|
|
|
13457
14508
|
item.fileId = payload.data.fileId;
|
|
13458
14509
|
}
|
|
13459
14510
|
|
|
14511
|
+
assignOptionalTagIds({
|
|
14512
|
+
target: item,
|
|
14513
|
+
tagIds: payload.data.tagIds,
|
|
14514
|
+
});
|
|
14515
|
+
|
|
13460
14516
|
item.sprites =
|
|
13461
14517
|
payload.data.sprites === undefined
|
|
13462
14518
|
? { items: {}, tree: [] }
|
|
@@ -13464,6 +14520,11 @@ const COMMAND_DEFINITIONS = [
|
|
|
13464
14520
|
|
|
13465
14521
|
return item;
|
|
13466
14522
|
},
|
|
14523
|
+
updateItem: ({ currentItem, payload }) =>
|
|
14524
|
+
applyTagIdsUpdate({
|
|
14525
|
+
currentItem,
|
|
14526
|
+
data: payload.data,
|
|
14527
|
+
}),
|
|
13467
14528
|
validateCreateState: ({ state, payload }) => {
|
|
13468
14529
|
if (payload.data.type !== "character") {
|
|
13469
14530
|
return;
|
|
@@ -13483,6 +14544,21 @@ const COMMAND_DEFINITIONS = [
|
|
|
13483
14544
|
}
|
|
13484
14545
|
}
|
|
13485
14546
|
|
|
14547
|
+
{
|
|
14548
|
+
const result = validateTagIdsAgainstScope({
|
|
14549
|
+
state,
|
|
14550
|
+
tagIds: payload.data.tagIds,
|
|
14551
|
+
scopeKey: "characters",
|
|
14552
|
+
path: "payload.data.tagIds",
|
|
14553
|
+
details: {
|
|
14554
|
+
characterId: payload.characterId,
|
|
14555
|
+
},
|
|
14556
|
+
});
|
|
14557
|
+
if (!result.valid) {
|
|
14558
|
+
return result;
|
|
14559
|
+
}
|
|
14560
|
+
}
|
|
14561
|
+
|
|
13486
14562
|
for (const [spriteId, sprite] of Object.entries(
|
|
13487
14563
|
payload.data.sprites?.items || {},
|
|
13488
14564
|
)) {
|
|
@@ -13490,6 +14566,16 @@ const COMMAND_DEFINITIONS = [
|
|
|
13490
14566
|
continue;
|
|
13491
14567
|
}
|
|
13492
14568
|
|
|
14569
|
+
if (sprite.tagIds !== undefined) {
|
|
14570
|
+
return invalidPrecondition(
|
|
14571
|
+
"payload.data.sprites.items.*.tagIds are not supported during character.create",
|
|
14572
|
+
{
|
|
14573
|
+
characterId: payload.characterId,
|
|
14574
|
+
spriteId,
|
|
14575
|
+
},
|
|
14576
|
+
);
|
|
14577
|
+
}
|
|
14578
|
+
|
|
13493
14579
|
const result = validateFileReference({
|
|
13494
14580
|
state,
|
|
13495
14581
|
fileId: sprite.fileId,
|
|
@@ -13547,6 +14633,27 @@ const COMMAND_DEFINITIONS = [
|
|
|
13547
14633
|
if (!result.valid) {
|
|
13548
14634
|
return result;
|
|
13549
14635
|
}
|
|
14636
|
+
|
|
14637
|
+
return validateTagIdsAgainstScope({
|
|
14638
|
+
state,
|
|
14639
|
+
tagIds: payload.data.tagIds,
|
|
14640
|
+
scopeKey: "characters",
|
|
14641
|
+
path: "payload.data.tagIds",
|
|
14642
|
+
details: {
|
|
14643
|
+
characterId: payload.characterId,
|
|
14644
|
+
},
|
|
14645
|
+
});
|
|
14646
|
+
}
|
|
14647
|
+
},
|
|
14648
|
+
afterDelete: ({ state, deletedItemsById }) => {
|
|
14649
|
+
for (const [characterId, item] of deletedItemsById.entries()) {
|
|
14650
|
+
if (item?.type !== "character") {
|
|
14651
|
+
continue;
|
|
14652
|
+
}
|
|
14653
|
+
|
|
14654
|
+
delete state.tags[
|
|
14655
|
+
`${CHARACTER_SPRITE_TAG_SCOPE_PREFIX}${characterId}`
|
|
14656
|
+
];
|
|
13550
14657
|
}
|
|
13551
14658
|
},
|
|
13552
14659
|
}),
|
|
@@ -13817,6 +14924,17 @@ const COMMAND_DEFINITIONS = [
|
|
|
13817
14924
|
if (!result.valid) {
|
|
13818
14925
|
return result;
|
|
13819
14926
|
}
|
|
14927
|
+
|
|
14928
|
+
return validateTagIdsAgainstScope({
|
|
14929
|
+
state,
|
|
14930
|
+
tagIds: payload.data.tagIds,
|
|
14931
|
+
scopeKey: `${CHARACTER_SPRITE_TAG_SCOPE_PREFIX}${payload.characterId}`,
|
|
14932
|
+
path: "payload.data.tagIds",
|
|
14933
|
+
details: {
|
|
14934
|
+
characterId: payload.characterId,
|
|
14935
|
+
spriteId: payload.spriteId,
|
|
14936
|
+
},
|
|
14937
|
+
});
|
|
13820
14938
|
}
|
|
13821
14939
|
},
|
|
13822
14940
|
reduce: ({ state, payload }) => {
|
|
@@ -13825,10 +14943,15 @@ const COMMAND_DEFINITIONS = [
|
|
|
13825
14943
|
characterId: payload.characterId,
|
|
13826
14944
|
});
|
|
13827
14945
|
|
|
13828
|
-
|
|
14946
|
+
const nextSprite = {
|
|
13829
14947
|
id: payload.spriteId,
|
|
13830
14948
|
...structuredClone(payload.data),
|
|
13831
14949
|
};
|
|
14950
|
+
if (payload.data.tagIds !== undefined && payload.data.tagIds.length === 0) {
|
|
14951
|
+
delete nextSprite.tagIds;
|
|
14952
|
+
}
|
|
14953
|
+
|
|
14954
|
+
collection.items[payload.spriteId] = nextSprite;
|
|
13832
14955
|
|
|
13833
14956
|
insertTreeNode({
|
|
13834
14957
|
tree: collection.tree,
|
|
@@ -13922,6 +15045,17 @@ const COMMAND_DEFINITIONS = [
|
|
|
13922
15045
|
if (!result.valid) {
|
|
13923
15046
|
return result;
|
|
13924
15047
|
}
|
|
15048
|
+
|
|
15049
|
+
return validateTagIdsAgainstScope({
|
|
15050
|
+
state,
|
|
15051
|
+
tagIds: payload.data.tagIds,
|
|
15052
|
+
scopeKey: `${CHARACTER_SPRITE_TAG_SCOPE_PREFIX}${payload.characterId}`,
|
|
15053
|
+
path: "payload.data.tagIds",
|
|
15054
|
+
details: {
|
|
15055
|
+
characterId: payload.characterId,
|
|
15056
|
+
spriteId: payload.spriteId,
|
|
15057
|
+
},
|
|
15058
|
+
});
|
|
13925
15059
|
}
|
|
13926
15060
|
},
|
|
13927
15061
|
reduce: ({ state, payload }) => {
|
|
@@ -13931,10 +15065,10 @@ const COMMAND_DEFINITIONS = [
|
|
|
13931
15065
|
});
|
|
13932
15066
|
const currentItem = collection.items[payload.spriteId];
|
|
13933
15067
|
|
|
13934
|
-
collection.items[payload.spriteId] = {
|
|
13935
|
-
|
|
13936
|
-
|
|
13937
|
-
};
|
|
15068
|
+
collection.items[payload.spriteId] = applyTagIdsUpdate({
|
|
15069
|
+
currentItem,
|
|
15070
|
+
data: payload.data,
|
|
15071
|
+
});
|
|
13938
15072
|
|
|
13939
15073
|
return state;
|
|
13940
15074
|
},
|
|
@@ -14166,6 +15300,297 @@ const COMMAND_DEFINITIONS = [
|
|
|
14166
15300
|
return state;
|
|
14167
15301
|
},
|
|
14168
15302
|
},
|
|
15303
|
+
{
|
|
15304
|
+
type: "tag.create",
|
|
15305
|
+
validatePayload: ({ payload }) => {
|
|
15306
|
+
{
|
|
15307
|
+
const result = validateExactKeys({
|
|
15308
|
+
value: payload,
|
|
15309
|
+
expectedKeys: ["scopeKey", "tagId", "data"],
|
|
15310
|
+
path: "payload",
|
|
15311
|
+
errorFactory: createPayloadValidationError,
|
|
15312
|
+
});
|
|
15313
|
+
if (result?.valid === false) {
|
|
15314
|
+
return result;
|
|
15315
|
+
}
|
|
15316
|
+
}
|
|
15317
|
+
|
|
15318
|
+
{
|
|
15319
|
+
const result = validateTagScopeKey({
|
|
15320
|
+
scopeKey: payload.scopeKey,
|
|
15321
|
+
path: "payload.scopeKey",
|
|
15322
|
+
errorFactory: createPayloadValidationError,
|
|
15323
|
+
});
|
|
15324
|
+
if (result?.valid === false) {
|
|
15325
|
+
return result;
|
|
15326
|
+
}
|
|
15327
|
+
}
|
|
15328
|
+
|
|
15329
|
+
if (!isNonEmptyString(payload.tagId)) {
|
|
15330
|
+
return invalidPayload("payload.tagId must be a non-empty string");
|
|
15331
|
+
}
|
|
15332
|
+
|
|
15333
|
+
{
|
|
15334
|
+
const result = validateTagCreateData({
|
|
15335
|
+
data: payload.data,
|
|
15336
|
+
errorFactory: createPayloadValidationError,
|
|
15337
|
+
});
|
|
15338
|
+
if (result?.valid === false) {
|
|
15339
|
+
return result;
|
|
15340
|
+
}
|
|
15341
|
+
}
|
|
15342
|
+
},
|
|
15343
|
+
validateAgainstState: ({ state, payload }) => {
|
|
15344
|
+
{
|
|
15345
|
+
const result = validateTagScopeAgainstState({
|
|
15346
|
+
state,
|
|
15347
|
+
scopeKey: payload.scopeKey,
|
|
15348
|
+
path: "payload.scopeKey",
|
|
15349
|
+
});
|
|
15350
|
+
if (!result.valid) {
|
|
15351
|
+
return result;
|
|
15352
|
+
}
|
|
15353
|
+
}
|
|
15354
|
+
|
|
15355
|
+
const collection = getTagScopeCollection({
|
|
15356
|
+
state,
|
|
15357
|
+
scopeKey: payload.scopeKey,
|
|
15358
|
+
});
|
|
15359
|
+
if (isPlainObject(collection?.items?.[payload.tagId])) {
|
|
15360
|
+
return invalidPrecondition("payload.tagId must not already exist");
|
|
15361
|
+
}
|
|
15362
|
+
|
|
15363
|
+
return validateUniqueTagNameInScope({
|
|
15364
|
+
collection,
|
|
15365
|
+
name: payload.data.name,
|
|
15366
|
+
path: "payload.data.name",
|
|
15367
|
+
});
|
|
15368
|
+
},
|
|
15369
|
+
reduce: ({ state, payload }) => {
|
|
15370
|
+
const collection = ensureTagScopeCollection({
|
|
15371
|
+
state,
|
|
15372
|
+
scopeKey: payload.scopeKey,
|
|
15373
|
+
});
|
|
15374
|
+
const nextTag = {
|
|
15375
|
+
id: payload.tagId,
|
|
15376
|
+
type: "tag",
|
|
15377
|
+
name: payload.data.name,
|
|
15378
|
+
};
|
|
15379
|
+
|
|
15380
|
+
if (payload.data.color !== undefined) {
|
|
15381
|
+
nextTag.color = payload.data.color;
|
|
15382
|
+
}
|
|
15383
|
+
|
|
15384
|
+
collection.items[payload.tagId] = nextTag;
|
|
15385
|
+
collection.tree.push({
|
|
15386
|
+
id: payload.tagId,
|
|
15387
|
+
});
|
|
15388
|
+
|
|
15389
|
+
return state;
|
|
15390
|
+
},
|
|
15391
|
+
},
|
|
15392
|
+
{
|
|
15393
|
+
type: "tag.update",
|
|
15394
|
+
validatePayload: ({ payload }) => {
|
|
15395
|
+
{
|
|
15396
|
+
const result = validateExactKeys({
|
|
15397
|
+
value: payload,
|
|
15398
|
+
expectedKeys: ["scopeKey", "tagId", "data"],
|
|
15399
|
+
path: "payload",
|
|
15400
|
+
errorFactory: createPayloadValidationError,
|
|
15401
|
+
});
|
|
15402
|
+
if (result?.valid === false) {
|
|
15403
|
+
return result;
|
|
15404
|
+
}
|
|
15405
|
+
}
|
|
15406
|
+
|
|
15407
|
+
{
|
|
15408
|
+
const result = validateTagScopeKey({
|
|
15409
|
+
scopeKey: payload.scopeKey,
|
|
15410
|
+
path: "payload.scopeKey",
|
|
15411
|
+
errorFactory: createPayloadValidationError,
|
|
15412
|
+
});
|
|
15413
|
+
if (result?.valid === false) {
|
|
15414
|
+
return result;
|
|
15415
|
+
}
|
|
15416
|
+
}
|
|
15417
|
+
|
|
15418
|
+
if (!isNonEmptyString(payload.tagId)) {
|
|
15419
|
+
return invalidPayload("payload.tagId must be a non-empty string");
|
|
15420
|
+
}
|
|
15421
|
+
|
|
15422
|
+
{
|
|
15423
|
+
const result = validateTagUpdateData({
|
|
15424
|
+
data: payload.data,
|
|
15425
|
+
errorFactory: createPayloadValidationError,
|
|
15426
|
+
});
|
|
15427
|
+
if (result?.valid === false) {
|
|
15428
|
+
return result;
|
|
15429
|
+
}
|
|
15430
|
+
}
|
|
15431
|
+
},
|
|
15432
|
+
validateAgainstState: ({ state, payload }) => {
|
|
15433
|
+
{
|
|
15434
|
+
const result = validateTagScopeAgainstState({
|
|
15435
|
+
state,
|
|
15436
|
+
scopeKey: payload.scopeKey,
|
|
15437
|
+
path: "payload.scopeKey",
|
|
15438
|
+
});
|
|
15439
|
+
if (!result.valid) {
|
|
15440
|
+
return result;
|
|
15441
|
+
}
|
|
15442
|
+
}
|
|
15443
|
+
|
|
15444
|
+
const collection = getTagScopeCollection({
|
|
15445
|
+
state,
|
|
15446
|
+
scopeKey: payload.scopeKey,
|
|
15447
|
+
});
|
|
15448
|
+
const currentTag = collection?.items?.[payload.tagId];
|
|
15449
|
+
if (!isPlainObject(currentTag) || currentTag.type !== "tag") {
|
|
15450
|
+
return invalidPrecondition(
|
|
15451
|
+
"payload.tagId must reference an existing tag in payload.scopeKey",
|
|
15452
|
+
);
|
|
15453
|
+
}
|
|
15454
|
+
|
|
15455
|
+
if (payload.data.name === undefined) {
|
|
15456
|
+
return VALID_RESULT;
|
|
15457
|
+
}
|
|
15458
|
+
|
|
15459
|
+
return validateUniqueTagNameInScope({
|
|
15460
|
+
collection,
|
|
15461
|
+
name: payload.data.name,
|
|
15462
|
+
path: "payload.data.name",
|
|
15463
|
+
excludeTagId: payload.tagId,
|
|
15464
|
+
});
|
|
15465
|
+
},
|
|
15466
|
+
reduce: ({ state, payload }) => {
|
|
15467
|
+
const collection = ensureTagScopeCollection({
|
|
15468
|
+
state,
|
|
15469
|
+
scopeKey: payload.scopeKey,
|
|
15470
|
+
});
|
|
15471
|
+
const currentTag = collection.items[payload.tagId];
|
|
15472
|
+
const nextTag = {
|
|
15473
|
+
...structuredClone(currentTag),
|
|
15474
|
+
};
|
|
15475
|
+
|
|
15476
|
+
if (payload.data.name !== undefined) {
|
|
15477
|
+
nextTag.name = payload.data.name;
|
|
15478
|
+
}
|
|
15479
|
+
|
|
15480
|
+
if (payload.data.color === null) {
|
|
15481
|
+
delete nextTag.color;
|
|
15482
|
+
} else if (payload.data.color !== undefined) {
|
|
15483
|
+
nextTag.color = payload.data.color;
|
|
15484
|
+
}
|
|
15485
|
+
|
|
15486
|
+
collection.items[payload.tagId] = nextTag;
|
|
15487
|
+
return state;
|
|
15488
|
+
},
|
|
15489
|
+
},
|
|
15490
|
+
{
|
|
15491
|
+
type: "tag.delete",
|
|
15492
|
+
validatePayload: ({ payload }) => {
|
|
15493
|
+
{
|
|
15494
|
+
const result = validateExactKeys({
|
|
15495
|
+
value: payload,
|
|
15496
|
+
expectedKeys: ["scopeKey", "tagIds"],
|
|
15497
|
+
path: "payload",
|
|
15498
|
+
errorFactory: createPayloadValidationError,
|
|
15499
|
+
});
|
|
15500
|
+
if (result?.valid === false) {
|
|
15501
|
+
return result;
|
|
15502
|
+
}
|
|
15503
|
+
}
|
|
15504
|
+
|
|
15505
|
+
{
|
|
15506
|
+
const result = validateTagScopeKey({
|
|
15507
|
+
scopeKey: payload.scopeKey,
|
|
15508
|
+
path: "payload.scopeKey",
|
|
15509
|
+
errorFactory: createPayloadValidationError,
|
|
15510
|
+
});
|
|
15511
|
+
if (result?.valid === false) {
|
|
15512
|
+
return result;
|
|
15513
|
+
}
|
|
15514
|
+
}
|
|
15515
|
+
|
|
15516
|
+
{
|
|
15517
|
+
const result = validateRequiredUniqueIdArray({
|
|
15518
|
+
value: payload.tagIds,
|
|
15519
|
+
path: "payload.tagIds",
|
|
15520
|
+
errorFactory: createPayloadValidationError,
|
|
15521
|
+
});
|
|
15522
|
+
if (result?.valid === false) {
|
|
15523
|
+
return result;
|
|
15524
|
+
}
|
|
15525
|
+
}
|
|
15526
|
+
},
|
|
15527
|
+
validateAgainstState: ({ state, payload }) => {
|
|
15528
|
+
{
|
|
15529
|
+
const result = validateTagScopeAgainstState({
|
|
15530
|
+
state,
|
|
15531
|
+
scopeKey: payload.scopeKey,
|
|
15532
|
+
path: "payload.scopeKey",
|
|
15533
|
+
});
|
|
15534
|
+
if (!result.valid) {
|
|
15535
|
+
return result;
|
|
15536
|
+
}
|
|
15537
|
+
}
|
|
15538
|
+
|
|
15539
|
+
const collection = getTagScopeCollection({
|
|
15540
|
+
state,
|
|
15541
|
+
scopeKey: payload.scopeKey,
|
|
15542
|
+
});
|
|
15543
|
+
for (const tagId of payload.tagIds) {
|
|
15544
|
+
const tag = collection?.items?.[tagId];
|
|
15545
|
+
if (!isPlainObject(tag) || tag.type !== "tag") {
|
|
15546
|
+
return invalidPrecondition(
|
|
15547
|
+
"payload.tagIds must reference existing tags in payload.scopeKey",
|
|
15548
|
+
{
|
|
15549
|
+
scopeKey: payload.scopeKey,
|
|
15550
|
+
tagId,
|
|
15551
|
+
},
|
|
15552
|
+
);
|
|
15553
|
+
}
|
|
15554
|
+
}
|
|
15555
|
+
|
|
15556
|
+
return VALID_RESULT;
|
|
15557
|
+
},
|
|
15558
|
+
reduce: ({ state, payload }) => {
|
|
15559
|
+
const collection = ensureTagScopeCollection({
|
|
15560
|
+
state,
|
|
15561
|
+
scopeKey: payload.scopeKey,
|
|
15562
|
+
});
|
|
15563
|
+
const deletedTagIds = new Set();
|
|
15564
|
+
|
|
15565
|
+
for (const tagId of payload.tagIds) {
|
|
15566
|
+
const removedNode = removeTreeNode({
|
|
15567
|
+
nodes: collection.tree,
|
|
15568
|
+
nodeId: tagId,
|
|
15569
|
+
});
|
|
15570
|
+
if (!removedNode) {
|
|
15571
|
+
continue;
|
|
15572
|
+
}
|
|
15573
|
+
|
|
15574
|
+
for (const deletedTagId of collectTreeDescendantIds({
|
|
15575
|
+
node: removedNode,
|
|
15576
|
+
})) {
|
|
15577
|
+
deletedTagIds.add(deletedTagId);
|
|
15578
|
+
}
|
|
15579
|
+
}
|
|
15580
|
+
|
|
15581
|
+
for (const tagId of deletedTagIds) {
|
|
15582
|
+
delete collection.items[tagId];
|
|
15583
|
+
}
|
|
15584
|
+
|
|
15585
|
+
stripDeletedTagIdsFromScopeItems({
|
|
15586
|
+
state,
|
|
15587
|
+
scopeKey: payload.scopeKey,
|
|
15588
|
+
deletedTagIds,
|
|
15589
|
+
});
|
|
15590
|
+
|
|
15591
|
+
return state;
|
|
15592
|
+
},
|
|
15593
|
+
},
|
|
14169
15594
|
{
|
|
14170
15595
|
type: "layout.element.create",
|
|
14171
15596
|
validatePayload: ({ payload }) => {
|
|
@@ -15338,11 +16763,7 @@ export const validateAgainstState = ({ state, command }) => {
|
|
|
15338
16763
|
export const processCommand = ({ state, command }) => {
|
|
15339
16764
|
return captureValidation(() => {
|
|
15340
16765
|
const normalizedState = normalizeStateCollections(state);
|
|
15341
|
-
const shouldMaterializeNormalizedState =
|
|
15342
|
-
typeof command?.type === "string" &&
|
|
15343
|
-
(command.type.startsWith("control.") ||
|
|
15344
|
-
command.type.startsWith("spritesheet.") ||
|
|
15345
|
-
command.type.startsWith("particle."));
|
|
16766
|
+
const shouldMaterializeNormalizedState = normalizedState !== state;
|
|
15346
16767
|
|
|
15347
16768
|
const stateResult = validateState({ state: normalizedState });
|
|
15348
16769
|
if (!stateResult.valid) {
|