@routevn/creator-model 1.0.0 → 1.0.2
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 +928 -64
package/package.json
CHANGED
package/src/model.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
|
|
19
19
|
const COLLECTION_KEYS = [
|
|
20
20
|
"scenes",
|
|
21
|
+
"files",
|
|
21
22
|
"images",
|
|
22
23
|
"sounds",
|
|
23
24
|
"videos",
|
|
@@ -32,6 +33,33 @@ const COLLECTION_KEYS = [
|
|
|
32
33
|
];
|
|
33
34
|
const ROOT_KEYS = ["project", "story", ...COLLECTION_KEYS];
|
|
34
35
|
const isString = (value) => typeof value === "string";
|
|
36
|
+
const FILE_ITEM_TYPES = [
|
|
37
|
+
"image",
|
|
38
|
+
"image-thumbnail",
|
|
39
|
+
"audio",
|
|
40
|
+
"audio-waveform",
|
|
41
|
+
"video",
|
|
42
|
+
"video-thumbnail",
|
|
43
|
+
"font",
|
|
44
|
+
];
|
|
45
|
+
const IMAGE_FILE_REFERENCE_TYPES = {
|
|
46
|
+
fileId: ["image"],
|
|
47
|
+
thumbnailFileId: ["image-thumbnail"],
|
|
48
|
+
};
|
|
49
|
+
const SOUND_FILE_REFERENCE_TYPES = {
|
|
50
|
+
fileId: ["audio"],
|
|
51
|
+
waveformDataFileId: ["audio-waveform"],
|
|
52
|
+
};
|
|
53
|
+
const VIDEO_FILE_REFERENCE_TYPES = {
|
|
54
|
+
fileId: ["video"],
|
|
55
|
+
thumbnailFileId: ["video-thumbnail"],
|
|
56
|
+
};
|
|
57
|
+
const FONT_FILE_REFERENCE_TYPES = {
|
|
58
|
+
fileId: ["font"],
|
|
59
|
+
};
|
|
60
|
+
const CHARACTER_FILE_REFERENCE_TYPES = {
|
|
61
|
+
fileId: ["image"],
|
|
62
|
+
};
|
|
35
63
|
const isHexColor = (value) =>
|
|
36
64
|
typeof value === "string" && /^#[0-9a-fA-F]{6}$/.test(value);
|
|
37
65
|
const LIVE_TWEEN_PROPERTY_KEYS = [
|
|
@@ -104,7 +132,7 @@ const LAYOUT_ELEMENT_BASE_TYPES = [
|
|
|
104
132
|
"container-ref-choice-item",
|
|
105
133
|
"container-ref-dialogue-line",
|
|
106
134
|
];
|
|
107
|
-
export const SCHEMA_VERSION =
|
|
135
|
+
export const SCHEMA_VERSION = 2;
|
|
108
136
|
const LAYOUT_CONTAINER_ELEMENT_TYPES = [
|
|
109
137
|
"folder",
|
|
110
138
|
"container",
|
|
@@ -482,6 +510,79 @@ const validateLineItems = ({ items, path, errorFactory }) => {
|
|
|
482
510
|
}
|
|
483
511
|
};
|
|
484
512
|
|
|
513
|
+
const validateFileItems = ({ items, path, errorFactory }) => {
|
|
514
|
+
for (const [itemId, item] of Object.entries(items)) {
|
|
515
|
+
const itemPath = `${path}.${itemId}`;
|
|
516
|
+
|
|
517
|
+
if (item?.type !== "folder" && !FILE_ITEM_TYPES.includes(item?.type)) {
|
|
518
|
+
return invalidFromErrorFactory(
|
|
519
|
+
errorFactory,
|
|
520
|
+
`${itemPath}.type must be 'folder' or a supported file type`,
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
{
|
|
525
|
+
const result = validateAllowedKeys({
|
|
526
|
+
value: item,
|
|
527
|
+
allowedKeys:
|
|
528
|
+
item.type === "folder"
|
|
529
|
+
? ["id", "type", "name"]
|
|
530
|
+
: ["id", "type", "mimeType", "size", "sha256"],
|
|
531
|
+
path: itemPath,
|
|
532
|
+
errorFactory,
|
|
533
|
+
});
|
|
534
|
+
if (result?.valid === false) {
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (!isNonEmptyString(item.id)) {
|
|
540
|
+
return invalidFromErrorFactory(
|
|
541
|
+
errorFactory,
|
|
542
|
+
`${itemPath}.id must be a non-empty string`,
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (item.id !== itemId) {
|
|
547
|
+
return invalidFromErrorFactory(
|
|
548
|
+
errorFactory,
|
|
549
|
+
`${itemPath}.id must match item key '${itemId}'`,
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (item.type === "folder") {
|
|
554
|
+
if (!isNonEmptyString(item.name)) {
|
|
555
|
+
return invalidFromErrorFactory(
|
|
556
|
+
errorFactory,
|
|
557
|
+
`${itemPath}.name must be a non-empty string`,
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (!isNonEmptyString(item.mimeType)) {
|
|
564
|
+
return invalidFromErrorFactory(
|
|
565
|
+
errorFactory,
|
|
566
|
+
`${itemPath}.mimeType must be a non-empty string`,
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (!isFiniteNumber(item.size)) {
|
|
571
|
+
return invalidFromErrorFactory(
|
|
572
|
+
errorFactory,
|
|
573
|
+
`${itemPath}.size must be a finite number`,
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (!isNonEmptyString(item.sha256)) {
|
|
578
|
+
return invalidFromErrorFactory(
|
|
579
|
+
errorFactory,
|
|
580
|
+
`${itemPath}.sha256 must be a non-empty string`,
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
|
|
485
586
|
const validateImageItems = ({ items, path, errorFactory }) => {
|
|
486
587
|
for (const [itemId, item] of Object.entries(items)) {
|
|
487
588
|
const itemPath = `${path}.${itemId}`;
|
|
@@ -501,6 +602,7 @@ const validateImageItems = ({ items, path, errorFactory }) => {
|
|
|
501
602
|
"type",
|
|
502
603
|
"name",
|
|
503
604
|
"description",
|
|
605
|
+
"thumbnailFileId",
|
|
504
606
|
"fileId",
|
|
505
607
|
"fileType",
|
|
506
608
|
"fileSize",
|
|
@@ -534,6 +636,16 @@ const validateImageItems = ({ items, path, errorFactory }) => {
|
|
|
534
636
|
}
|
|
535
637
|
|
|
536
638
|
if (item.type === "image") {
|
|
639
|
+
if (
|
|
640
|
+
item.thumbnailFileId !== undefined &&
|
|
641
|
+
!isNonEmptyString(item.thumbnailFileId)
|
|
642
|
+
) {
|
|
643
|
+
return invalidFromErrorFactory(
|
|
644
|
+
errorFactory,
|
|
645
|
+
`${itemPath}.thumbnailFileId must be a non-empty string when provided`,
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
|
|
537
649
|
if (!isNonEmptyString(item.fileId)) {
|
|
538
650
|
return invalidFromErrorFactory(errorFactory, `${itemPath}.fileId must be a non-empty string`);
|
|
539
651
|
}
|
|
@@ -1737,6 +1849,7 @@ const validateLayoutElementData = ({
|
|
|
1737
1849
|
"variableId",
|
|
1738
1850
|
"$when",
|
|
1739
1851
|
"click",
|
|
1852
|
+
"rightClick",
|
|
1740
1853
|
"change",
|
|
1741
1854
|
];
|
|
1742
1855
|
|
|
@@ -1870,6 +1983,10 @@ const validateLayoutElementData = ({
|
|
|
1870
1983
|
return invalidFromErrorFactory(errorFactory, `${path}.click must be an object when provided`);
|
|
1871
1984
|
}
|
|
1872
1985
|
|
|
1986
|
+
if (data.rightClick !== undefined && !isPlainObject(data.rightClick)) {
|
|
1987
|
+
return invalidFromErrorFactory(errorFactory, `${path}.rightClick must be an object when provided`);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1873
1990
|
if (data.change !== undefined && !isPlainObject(data.change)) {
|
|
1874
1991
|
return invalidFromErrorFactory(errorFactory, `${path}.change must be an object when provided`);
|
|
1875
1992
|
}
|
|
@@ -1921,6 +2038,7 @@ const validateLayoutElementItems = ({ items, path, errorFactory }) => {
|
|
|
1921
2038
|
"variableId",
|
|
1922
2039
|
"$when",
|
|
1923
2040
|
"click",
|
|
2041
|
+
"rightClick",
|
|
1924
2042
|
"change",
|
|
1925
2043
|
],
|
|
1926
2044
|
path: itemPath,
|
|
@@ -2577,6 +2695,17 @@ const validateCollection = ({ collection, path }) => {
|
|
|
2577
2695
|
return result;
|
|
2578
2696
|
}
|
|
2579
2697
|
}
|
|
2698
|
+
} else if (path === "state.files") {
|
|
2699
|
+
{
|
|
2700
|
+
const result = validateFileItems({
|
|
2701
|
+
items: collection.items,
|
|
2702
|
+
path: `${path}.items`,
|
|
2703
|
+
errorFactory: createStateValidationError,
|
|
2704
|
+
});
|
|
2705
|
+
if (result?.valid === false) {
|
|
2706
|
+
return result;
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2580
2709
|
} else if (path === "state.sounds") {
|
|
2581
2710
|
{
|
|
2582
2711
|
const result = validateSoundItems({
|
|
@@ -2784,6 +2913,7 @@ const validateCollection = ({ collection, path }) => {
|
|
|
2784
2913
|
}
|
|
2785
2914
|
}
|
|
2786
2915
|
} else if (
|
|
2916
|
+
path === "state.files" ||
|
|
2787
2917
|
path === "state.transforms" ||
|
|
2788
2918
|
path === "state.variables" ||
|
|
2789
2919
|
path === "state.textStyles" ||
|
|
@@ -2813,6 +2943,53 @@ const validateCollection = ({ collection, path }) => {
|
|
|
2813
2943
|
}
|
|
2814
2944
|
};
|
|
2815
2945
|
|
|
2946
|
+
const validateFileReference = ({
|
|
2947
|
+
state,
|
|
2948
|
+
fileId,
|
|
2949
|
+
path,
|
|
2950
|
+
allowedTypes,
|
|
2951
|
+
details = {},
|
|
2952
|
+
errorFactory = createPreconditionValidationError,
|
|
2953
|
+
}) => {
|
|
2954
|
+
if (fileId === undefined || fileId === null) {
|
|
2955
|
+
return VALID_RESULT;
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
const expectedTypeMessage =
|
|
2959
|
+
Array.isArray(allowedTypes) && allowedTypes.length > 0
|
|
2960
|
+
? `${path} must reference an existing non-folder file with type ${allowedTypes
|
|
2961
|
+
.map((type) => `'${type}'`)
|
|
2962
|
+
.join(" or ")}`
|
|
2963
|
+
: `${path} must reference an existing non-folder file`;
|
|
2964
|
+
const file = state.files?.items?.[fileId];
|
|
2965
|
+
if (!isPlainObject(file) || file.type === "folder") {
|
|
2966
|
+
return invalidFromErrorFactory(
|
|
2967
|
+
errorFactory,
|
|
2968
|
+
expectedTypeMessage,
|
|
2969
|
+
Array.isArray(allowedTypes) && allowedTypes.length > 0
|
|
2970
|
+
? {
|
|
2971
|
+
...details,
|
|
2972
|
+
expectedFileTypes: [...allowedTypes],
|
|
2973
|
+
}
|
|
2974
|
+
: details,
|
|
2975
|
+
);
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
if (Array.isArray(allowedTypes) && allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
|
|
2979
|
+
return invalidFromErrorFactory(
|
|
2980
|
+
errorFactory,
|
|
2981
|
+
expectedTypeMessage,
|
|
2982
|
+
{
|
|
2983
|
+
...details,
|
|
2984
|
+
expectedFileTypes: [...allowedTypes],
|
|
2985
|
+
actualFileType: file.type,
|
|
2986
|
+
},
|
|
2987
|
+
);
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
return VALID_RESULT;
|
|
2991
|
+
};
|
|
2992
|
+
|
|
2816
2993
|
export const assertInvariants = ({ state }) => {
|
|
2817
2994
|
if (!isPlainObject(state)) {
|
|
2818
2995
|
return invalidInvariant("state must be an object");
|
|
@@ -2924,6 +3101,166 @@ export const assertInvariants = ({ state }) => {
|
|
|
2924
3101
|
}
|
|
2925
3102
|
}
|
|
2926
3103
|
|
|
3104
|
+
for (const [imageId, image] of Object.entries(state.images.items)) {
|
|
3105
|
+
if (image.type !== "image") {
|
|
3106
|
+
continue;
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
{
|
|
3110
|
+
const result = validateFileReference({
|
|
3111
|
+
state,
|
|
3112
|
+
fileId: image.fileId,
|
|
3113
|
+
path: "image.fileId",
|
|
3114
|
+
allowedTypes: IMAGE_FILE_REFERENCE_TYPES.fileId,
|
|
3115
|
+
details: { imageId, fileId: image.fileId },
|
|
3116
|
+
errorFactory: createInvariantValidationError,
|
|
3117
|
+
});
|
|
3118
|
+
if (!result.valid) {
|
|
3119
|
+
return result;
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
if (image.thumbnailFileId !== undefined) {
|
|
3124
|
+
const result = validateFileReference({
|
|
3125
|
+
state,
|
|
3126
|
+
fileId: image.thumbnailFileId,
|
|
3127
|
+
path: "image.thumbnailFileId",
|
|
3128
|
+
allowedTypes: IMAGE_FILE_REFERENCE_TYPES.thumbnailFileId,
|
|
3129
|
+
details: { imageId, thumbnailFileId: image.thumbnailFileId },
|
|
3130
|
+
errorFactory: createInvariantValidationError,
|
|
3131
|
+
});
|
|
3132
|
+
if (!result.valid) {
|
|
3133
|
+
return result;
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
for (const [soundId, sound] of Object.entries(state.sounds.items)) {
|
|
3139
|
+
if (sound.type !== "sound") {
|
|
3140
|
+
continue;
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
{
|
|
3144
|
+
const result = validateFileReference({
|
|
3145
|
+
state,
|
|
3146
|
+
fileId: sound.fileId,
|
|
3147
|
+
path: "sound.fileId",
|
|
3148
|
+
allowedTypes: SOUND_FILE_REFERENCE_TYPES.fileId,
|
|
3149
|
+
details: { soundId, fileId: sound.fileId },
|
|
3150
|
+
errorFactory: createInvariantValidationError,
|
|
3151
|
+
});
|
|
3152
|
+
if (!result.valid) {
|
|
3153
|
+
return result;
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
if (sound.waveformDataFileId !== undefined && sound.waveformDataFileId !== null) {
|
|
3158
|
+
const result = validateFileReference({
|
|
3159
|
+
state,
|
|
3160
|
+
fileId: sound.waveformDataFileId,
|
|
3161
|
+
path: "sound.waveformDataFileId",
|
|
3162
|
+
allowedTypes: SOUND_FILE_REFERENCE_TYPES.waveformDataFileId,
|
|
3163
|
+
details: { soundId, waveformDataFileId: sound.waveformDataFileId },
|
|
3164
|
+
errorFactory: createInvariantValidationError,
|
|
3165
|
+
});
|
|
3166
|
+
if (!result.valid) {
|
|
3167
|
+
return result;
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
for (const [videoId, video] of Object.entries(state.videos.items)) {
|
|
3173
|
+
if (video.type !== "video") {
|
|
3174
|
+
continue;
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
{
|
|
3178
|
+
const result = validateFileReference({
|
|
3179
|
+
state,
|
|
3180
|
+
fileId: video.fileId,
|
|
3181
|
+
path: "video.fileId",
|
|
3182
|
+
allowedTypes: VIDEO_FILE_REFERENCE_TYPES.fileId,
|
|
3183
|
+
details: { videoId, fileId: video.fileId },
|
|
3184
|
+
errorFactory: createInvariantValidationError,
|
|
3185
|
+
});
|
|
3186
|
+
if (!result.valid) {
|
|
3187
|
+
return result;
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
{
|
|
3192
|
+
const result = validateFileReference({
|
|
3193
|
+
state,
|
|
3194
|
+
fileId: video.thumbnailFileId,
|
|
3195
|
+
path: "video.thumbnailFileId",
|
|
3196
|
+
allowedTypes: VIDEO_FILE_REFERENCE_TYPES.thumbnailFileId,
|
|
3197
|
+
details: { videoId, thumbnailFileId: video.thumbnailFileId },
|
|
3198
|
+
errorFactory: createInvariantValidationError,
|
|
3199
|
+
});
|
|
3200
|
+
if (!result.valid) {
|
|
3201
|
+
return result;
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
for (const [fontId, font] of Object.entries(state.fonts.items)) {
|
|
3207
|
+
if (font.type !== "font") {
|
|
3208
|
+
continue;
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
const result = validateFileReference({
|
|
3212
|
+
state,
|
|
3213
|
+
fileId: font.fileId,
|
|
3214
|
+
path: "font.fileId",
|
|
3215
|
+
allowedTypes: FONT_FILE_REFERENCE_TYPES.fileId,
|
|
3216
|
+
details: { fontId, fileId: font.fileId },
|
|
3217
|
+
errorFactory: createInvariantValidationError,
|
|
3218
|
+
});
|
|
3219
|
+
if (!result.valid) {
|
|
3220
|
+
return result;
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
for (const [characterId, character] of Object.entries(state.characters.items)) {
|
|
3225
|
+
if (character.type !== "character") {
|
|
3226
|
+
continue;
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
if (character.fileId !== undefined) {
|
|
3230
|
+
const result = validateFileReference({
|
|
3231
|
+
state,
|
|
3232
|
+
fileId: character.fileId,
|
|
3233
|
+
path: "character.fileId",
|
|
3234
|
+
allowedTypes: CHARACTER_FILE_REFERENCE_TYPES.fileId,
|
|
3235
|
+
details: { characterId, fileId: character.fileId },
|
|
3236
|
+
errorFactory: createInvariantValidationError,
|
|
3237
|
+
});
|
|
3238
|
+
if (!result.valid) {
|
|
3239
|
+
return result;
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
for (const [spriteId, sprite] of Object.entries(
|
|
3244
|
+
character.sprites?.items || {},
|
|
3245
|
+
)) {
|
|
3246
|
+
if (sprite.type !== "image") {
|
|
3247
|
+
continue;
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
const result = validateFileReference({
|
|
3251
|
+
state,
|
|
3252
|
+
fileId: sprite.fileId,
|
|
3253
|
+
path: "character.sprite.fileId",
|
|
3254
|
+
allowedTypes: CHARACTER_FILE_REFERENCE_TYPES.fileId,
|
|
3255
|
+
details: { characterId, spriteId, fileId: sprite.fileId },
|
|
3256
|
+
errorFactory: createInvariantValidationError,
|
|
3257
|
+
});
|
|
3258
|
+
if (!result.valid) {
|
|
3259
|
+
return result;
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
|
|
2927
3264
|
const assertImageReference = ({ layoutId, elementId, field, targetId }) => {
|
|
2928
3265
|
const image = state.images.items[targetId];
|
|
2929
3266
|
if (!isPlainObject(image) || image.type === "folder") {
|
|
@@ -3397,6 +3734,7 @@ const validateImageCreateData = ({ data, errorFactory }) => {
|
|
|
3397
3734
|
"type",
|
|
3398
3735
|
"name",
|
|
3399
3736
|
"description",
|
|
3737
|
+
"thumbnailFileId",
|
|
3400
3738
|
"fileId",
|
|
3401
3739
|
"fileType",
|
|
3402
3740
|
"fileSize",
|
|
@@ -3422,6 +3760,16 @@ const validateImageCreateData = ({ data, errorFactory }) => {
|
|
|
3422
3760
|
}
|
|
3423
3761
|
|
|
3424
3762
|
if (data.type === "image") {
|
|
3763
|
+
if (
|
|
3764
|
+
data.thumbnailFileId !== undefined &&
|
|
3765
|
+
!isNonEmptyString(data.thumbnailFileId)
|
|
3766
|
+
) {
|
|
3767
|
+
return invalidFromErrorFactory(
|
|
3768
|
+
errorFactory,
|
|
3769
|
+
"payload.data.thumbnailFileId must be a non-empty string when provided",
|
|
3770
|
+
);
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3425
3773
|
if (!isNonEmptyString(data.fileId)) {
|
|
3426
3774
|
return invalidFromErrorFactory(errorFactory, "payload.data.fileId must be a non-empty string");
|
|
3427
3775
|
}
|
|
@@ -3902,6 +4250,108 @@ const validateFontUpdateData = ({ data, errorFactory }) => {
|
|
|
3902
4250
|
}
|
|
3903
4251
|
};
|
|
3904
4252
|
|
|
4253
|
+
const validateFileCreateData = ({ data, errorFactory }) => {
|
|
4254
|
+
if (!isPlainObject(data)) {
|
|
4255
|
+
return invalidFromErrorFactory(
|
|
4256
|
+
errorFactory,
|
|
4257
|
+
"payload.data must be an object",
|
|
4258
|
+
);
|
|
4259
|
+
}
|
|
4260
|
+
|
|
4261
|
+
if (data.type !== "folder" && !FILE_ITEM_TYPES.includes(data.type)) {
|
|
4262
|
+
return invalidFromErrorFactory(
|
|
4263
|
+
errorFactory,
|
|
4264
|
+
"payload.data.type must be 'folder' or a supported file type",
|
|
4265
|
+
);
|
|
4266
|
+
}
|
|
4267
|
+
|
|
4268
|
+
{
|
|
4269
|
+
const result = validateAllowedKeys({
|
|
4270
|
+
value: data,
|
|
4271
|
+
allowedKeys:
|
|
4272
|
+
data.type === "folder"
|
|
4273
|
+
? ["type", "name"]
|
|
4274
|
+
: ["type", "mimeType", "size", "sha256"],
|
|
4275
|
+
path: "payload.data",
|
|
4276
|
+
errorFactory,
|
|
4277
|
+
});
|
|
4278
|
+
if (result?.valid === false) {
|
|
4279
|
+
return result;
|
|
4280
|
+
}
|
|
4281
|
+
}
|
|
4282
|
+
|
|
4283
|
+
if (data.type === "folder") {
|
|
4284
|
+
if (!isNonEmptyString(data.name)) {
|
|
4285
|
+
return invalidFromErrorFactory(
|
|
4286
|
+
errorFactory,
|
|
4287
|
+
"payload.data.name must be a non-empty string",
|
|
4288
|
+
);
|
|
4289
|
+
}
|
|
4290
|
+
return;
|
|
4291
|
+
}
|
|
4292
|
+
|
|
4293
|
+
if (!isNonEmptyString(data.mimeType)) {
|
|
4294
|
+
return invalidFromErrorFactory(
|
|
4295
|
+
errorFactory,
|
|
4296
|
+
"payload.data.mimeType must be a non-empty string",
|
|
4297
|
+
);
|
|
4298
|
+
}
|
|
4299
|
+
|
|
4300
|
+
if (!isFiniteNumber(data.size)) {
|
|
4301
|
+
return invalidFromErrorFactory(
|
|
4302
|
+
errorFactory,
|
|
4303
|
+
"payload.data.size must be a finite number",
|
|
4304
|
+
);
|
|
4305
|
+
}
|
|
4306
|
+
|
|
4307
|
+
if (!isNonEmptyString(data.sha256)) {
|
|
4308
|
+
return invalidFromErrorFactory(
|
|
4309
|
+
errorFactory,
|
|
4310
|
+
"payload.data.sha256 must be a non-empty string",
|
|
4311
|
+
);
|
|
4312
|
+
}
|
|
4313
|
+
};
|
|
4314
|
+
|
|
4315
|
+
const validateReferencedFilesInData = ({
|
|
4316
|
+
state,
|
|
4317
|
+
data,
|
|
4318
|
+
fields,
|
|
4319
|
+
fieldTypes = {},
|
|
4320
|
+
nullableFields = [],
|
|
4321
|
+
details = {},
|
|
4322
|
+
errorFactory = createPreconditionValidationError,
|
|
4323
|
+
}) => {
|
|
4324
|
+
for (const field of fields) {
|
|
4325
|
+
const fileId = data[field];
|
|
4326
|
+
|
|
4327
|
+
if (fileId === undefined) {
|
|
4328
|
+
continue;
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4331
|
+
if (fileId === null && nullableFields.includes(field)) {
|
|
4332
|
+
continue;
|
|
4333
|
+
}
|
|
4334
|
+
|
|
4335
|
+
const result = validateFileReference({
|
|
4336
|
+
state,
|
|
4337
|
+
fileId,
|
|
4338
|
+
path: `payload.data.${field}`,
|
|
4339
|
+
allowedTypes: fieldTypes[field],
|
|
4340
|
+
details: {
|
|
4341
|
+
...details,
|
|
4342
|
+
field,
|
|
4343
|
+
fileId,
|
|
4344
|
+
},
|
|
4345
|
+
errorFactory,
|
|
4346
|
+
});
|
|
4347
|
+
if (!result.valid) {
|
|
4348
|
+
return result;
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
|
|
4352
|
+
return VALID_RESULT;
|
|
4353
|
+
};
|
|
4354
|
+
|
|
3905
4355
|
const validateColorCreateData = ({ data, errorFactory }) => {
|
|
3906
4356
|
if (!isPlainObject(data)) {
|
|
3907
4357
|
return invalidFromErrorFactory(errorFactory, "payload.data must be an object");
|
|
@@ -5092,6 +5542,8 @@ const createFolderedCollectionCommandDefinitions = ({
|
|
|
5092
5542
|
}),
|
|
5093
5543
|
validateCreateState = () => {},
|
|
5094
5544
|
validateUpdateState = () => {},
|
|
5545
|
+
validateDeleteState = () => {},
|
|
5546
|
+
includeUpdate = true,
|
|
5095
5547
|
}) => {
|
|
5096
5548
|
const existingMessage = `payload.${idField} must reference an existing ${itemLabel}`;
|
|
5097
5549
|
const duplicateMessage = `payload.${idField} must not already exist`;
|
|
@@ -5220,68 +5672,72 @@ const createFolderedCollectionCommandDefinitions = ({
|
|
|
5220
5672
|
return state;
|
|
5221
5673
|
},
|
|
5222
5674
|
},
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5675
|
+
...(includeUpdate
|
|
5676
|
+
? [
|
|
5677
|
+
{
|
|
5678
|
+
type: `${familyName}.update`,
|
|
5679
|
+
validatePayload: ({ payload }) => {
|
|
5680
|
+
let result = captureValidation(() =>
|
|
5681
|
+
validateExactKeys({
|
|
5682
|
+
value: payload,
|
|
5683
|
+
expectedKeys: [idField, "data"],
|
|
5684
|
+
path: "payload",
|
|
5685
|
+
errorFactory: createPayloadValidationError,
|
|
5686
|
+
}),
|
|
5687
|
+
);
|
|
5688
|
+
if (!result.valid) {
|
|
5689
|
+
return result;
|
|
5690
|
+
}
|
|
5691
|
+
|
|
5692
|
+
if (!isNonEmptyString(payload[idField])) {
|
|
5693
|
+
return invalidPayload(
|
|
5694
|
+
`payload.${idField} must be a non-empty string`,
|
|
5695
|
+
);
|
|
5696
|
+
}
|
|
5697
|
+
|
|
5698
|
+
result = captureValidation(() =>
|
|
5699
|
+
updateDataValidator({
|
|
5700
|
+
data: payload.data,
|
|
5701
|
+
errorFactory: createPayloadValidationError,
|
|
5702
|
+
}),
|
|
5703
|
+
);
|
|
5704
|
+
if (!result.valid) {
|
|
5705
|
+
return result;
|
|
5706
|
+
}
|
|
5707
|
+
|
|
5708
|
+
return VALID_RESULT;
|
|
5709
|
+
},
|
|
5710
|
+
validateAgainstState: ({ state, payload }) => {
|
|
5711
|
+
const currentItem = state[collectionKey].items[payload[idField]];
|
|
5712
|
+
if (!isPlainObject(currentItem)) {
|
|
5713
|
+
return invalidPrecondition(existingMessage);
|
|
5714
|
+
}
|
|
5715
|
+
|
|
5716
|
+
const result = captureValidation(() =>
|
|
5717
|
+
validateUpdateState({
|
|
5718
|
+
state,
|
|
5719
|
+
payload,
|
|
5720
|
+
currentItem,
|
|
5721
|
+
}),
|
|
5722
|
+
);
|
|
5723
|
+
if (!result.valid) {
|
|
5724
|
+
return result;
|
|
5725
|
+
}
|
|
5726
|
+
|
|
5727
|
+
return VALID_RESULT;
|
|
5728
|
+
},
|
|
5729
|
+
reduce: ({ state, payload }) => {
|
|
5730
|
+
const currentItem = state[collectionKey].items[payload[idField]];
|
|
5731
|
+
state[collectionKey].items[payload[idField]] = updateItem({
|
|
5732
|
+
state,
|
|
5733
|
+
payload,
|
|
5734
|
+
currentItem,
|
|
5735
|
+
});
|
|
5736
|
+
return state;
|
|
5737
|
+
},
|
|
5738
|
+
},
|
|
5739
|
+
]
|
|
5740
|
+
: []),
|
|
5285
5741
|
{
|
|
5286
5742
|
type: `${familyName}.delete`,
|
|
5287
5743
|
validatePayload: ({ payload }) => {
|
|
@@ -5320,6 +5776,16 @@ const createFolderedCollectionCommandDefinitions = ({
|
|
|
5320
5776
|
}
|
|
5321
5777
|
}
|
|
5322
5778
|
|
|
5779
|
+
const result = captureValidation(() =>
|
|
5780
|
+
validateDeleteState({
|
|
5781
|
+
state,
|
|
5782
|
+
payload,
|
|
5783
|
+
}),
|
|
5784
|
+
);
|
|
5785
|
+
if (!result.valid) {
|
|
5786
|
+
return result;
|
|
5787
|
+
}
|
|
5788
|
+
|
|
5323
5789
|
return VALID_RESULT;
|
|
5324
5790
|
},
|
|
5325
5791
|
reduce: ({ state, payload }) => {
|
|
@@ -5479,6 +5945,143 @@ const getCharacterSpriteCollection = ({ state, characterId }) =>
|
|
|
5479
5945
|
const getLayoutElementCollection = ({ state, layoutId }) =>
|
|
5480
5946
|
state.layouts.items[layoutId]?.elements;
|
|
5481
5947
|
|
|
5948
|
+
const findReferencedFileUsage = ({ state, fileId }) => {
|
|
5949
|
+
for (const [imageId, image] of Object.entries(state.images.items)) {
|
|
5950
|
+
if (image.type !== "image") {
|
|
5951
|
+
continue;
|
|
5952
|
+
}
|
|
5953
|
+
|
|
5954
|
+
if (image.fileId === fileId) {
|
|
5955
|
+
return {
|
|
5956
|
+
kind: "image",
|
|
5957
|
+
field: "fileId",
|
|
5958
|
+
ownerId: imageId,
|
|
5959
|
+
};
|
|
5960
|
+
}
|
|
5961
|
+
|
|
5962
|
+
if (image.thumbnailFileId === fileId) {
|
|
5963
|
+
return {
|
|
5964
|
+
kind: "image",
|
|
5965
|
+
field: "thumbnailFileId",
|
|
5966
|
+
ownerId: imageId,
|
|
5967
|
+
};
|
|
5968
|
+
}
|
|
5969
|
+
}
|
|
5970
|
+
|
|
5971
|
+
for (const [soundId, sound] of Object.entries(state.sounds.items)) {
|
|
5972
|
+
if (sound.type !== "sound") {
|
|
5973
|
+
continue;
|
|
5974
|
+
}
|
|
5975
|
+
|
|
5976
|
+
if (sound.fileId === fileId) {
|
|
5977
|
+
return {
|
|
5978
|
+
kind: "sound",
|
|
5979
|
+
field: "fileId",
|
|
5980
|
+
ownerId: soundId,
|
|
5981
|
+
};
|
|
5982
|
+
}
|
|
5983
|
+
|
|
5984
|
+
if (sound.waveformDataFileId === fileId) {
|
|
5985
|
+
return {
|
|
5986
|
+
kind: "sound",
|
|
5987
|
+
field: "waveformDataFileId",
|
|
5988
|
+
ownerId: soundId,
|
|
5989
|
+
};
|
|
5990
|
+
}
|
|
5991
|
+
}
|
|
5992
|
+
|
|
5993
|
+
for (const [videoId, video] of Object.entries(state.videos.items)) {
|
|
5994
|
+
if (video.type !== "video") {
|
|
5995
|
+
continue;
|
|
5996
|
+
}
|
|
5997
|
+
|
|
5998
|
+
if (video.fileId === fileId) {
|
|
5999
|
+
return {
|
|
6000
|
+
kind: "video",
|
|
6001
|
+
field: "fileId",
|
|
6002
|
+
ownerId: videoId,
|
|
6003
|
+
};
|
|
6004
|
+
}
|
|
6005
|
+
|
|
6006
|
+
if (video.thumbnailFileId === fileId) {
|
|
6007
|
+
return {
|
|
6008
|
+
kind: "video",
|
|
6009
|
+
field: "thumbnailFileId",
|
|
6010
|
+
ownerId: videoId,
|
|
6011
|
+
};
|
|
6012
|
+
}
|
|
6013
|
+
}
|
|
6014
|
+
|
|
6015
|
+
for (const [fontId, font] of Object.entries(state.fonts.items)) {
|
|
6016
|
+
if (font.type !== "font") {
|
|
6017
|
+
continue;
|
|
6018
|
+
}
|
|
6019
|
+
|
|
6020
|
+
if (font.fileId === fileId) {
|
|
6021
|
+
return {
|
|
6022
|
+
kind: "font",
|
|
6023
|
+
field: "fileId",
|
|
6024
|
+
ownerId: fontId,
|
|
6025
|
+
};
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
6028
|
+
|
|
6029
|
+
for (const [characterId, character] of Object.entries(state.characters.items)) {
|
|
6030
|
+
if (character.type !== "character") {
|
|
6031
|
+
continue;
|
|
6032
|
+
}
|
|
6033
|
+
|
|
6034
|
+
if (character.fileId === fileId) {
|
|
6035
|
+
return {
|
|
6036
|
+
kind: "character",
|
|
6037
|
+
field: "fileId",
|
|
6038
|
+
ownerId: characterId,
|
|
6039
|
+
};
|
|
6040
|
+
}
|
|
6041
|
+
|
|
6042
|
+
for (const [spriteId, sprite] of Object.entries(
|
|
6043
|
+
character.sprites?.items || {},
|
|
6044
|
+
)) {
|
|
6045
|
+
if (sprite.type !== "image") {
|
|
6046
|
+
continue;
|
|
6047
|
+
}
|
|
6048
|
+
|
|
6049
|
+
if (sprite.fileId === fileId) {
|
|
6050
|
+
return {
|
|
6051
|
+
kind: "character.sprite",
|
|
6052
|
+
field: "fileId",
|
|
6053
|
+
ownerId: spriteId,
|
|
6054
|
+
characterId,
|
|
6055
|
+
};
|
|
6056
|
+
}
|
|
6057
|
+
}
|
|
6058
|
+
}
|
|
6059
|
+
|
|
6060
|
+
return null;
|
|
6061
|
+
};
|
|
6062
|
+
|
|
6063
|
+
const collectDeletedFileIds = ({ state, fileIds }) => {
|
|
6064
|
+
const deletedIds = new Set();
|
|
6065
|
+
|
|
6066
|
+
for (const fileId of fileIds) {
|
|
6067
|
+
const node = findTreeNode({
|
|
6068
|
+
nodes: state.files.tree,
|
|
6069
|
+
nodeId: fileId,
|
|
6070
|
+
});
|
|
6071
|
+
|
|
6072
|
+
if (!node) {
|
|
6073
|
+
deletedIds.add(fileId);
|
|
6074
|
+
continue;
|
|
6075
|
+
}
|
|
6076
|
+
|
|
6077
|
+
for (const deletedId of collectTreeDescendantIds({ node })) {
|
|
6078
|
+
deletedIds.add(deletedId);
|
|
6079
|
+
}
|
|
6080
|
+
}
|
|
6081
|
+
|
|
6082
|
+
return deletedIds;
|
|
6083
|
+
};
|
|
6084
|
+
|
|
5482
6085
|
const COMMAND_DEFINITIONS = [
|
|
5483
6086
|
{
|
|
5484
6087
|
type: "project.create",
|
|
@@ -5503,6 +6106,51 @@ const COMMAND_DEFINITIONS = [
|
|
|
5503
6106
|
validateAgainstState: () => {},
|
|
5504
6107
|
reduce: ({ payload }) => structuredClone(payload.state),
|
|
5505
6108
|
},
|
|
6109
|
+
...createFolderedCollectionCommandDefinitions({
|
|
6110
|
+
familyName: "file",
|
|
6111
|
+
collectionKey: "files",
|
|
6112
|
+
idField: "fileId",
|
|
6113
|
+
itemLabel: "file item",
|
|
6114
|
+
createDataValidator: validateFileCreateData,
|
|
6115
|
+
updateDataValidator: () => VALID_RESULT,
|
|
6116
|
+
includeUpdate: false,
|
|
6117
|
+
createItem: ({ payload }) =>
|
|
6118
|
+
payload.data.type === "folder"
|
|
6119
|
+
? {
|
|
6120
|
+
id: payload.fileId,
|
|
6121
|
+
type: "folder",
|
|
6122
|
+
name: payload.data.name,
|
|
6123
|
+
}
|
|
6124
|
+
: {
|
|
6125
|
+
id: payload.fileId,
|
|
6126
|
+
type: payload.data.type,
|
|
6127
|
+
mimeType: payload.data.mimeType,
|
|
6128
|
+
size: payload.data.size,
|
|
6129
|
+
sha256: payload.data.sha256,
|
|
6130
|
+
},
|
|
6131
|
+
validateDeleteState: ({ state, payload }) => {
|
|
6132
|
+
for (const fileId of collectDeletedFileIds({
|
|
6133
|
+
state,
|
|
6134
|
+
fileIds: payload.fileIds,
|
|
6135
|
+
})) {
|
|
6136
|
+
const usage = findReferencedFileUsage({ state, fileId });
|
|
6137
|
+
if (!usage) {
|
|
6138
|
+
continue;
|
|
6139
|
+
}
|
|
6140
|
+
|
|
6141
|
+
return invalidPrecondition(
|
|
6142
|
+
`payload.fileIds cannot delete a referenced file`,
|
|
6143
|
+
{
|
|
6144
|
+
fileId,
|
|
6145
|
+
referenceKind: usage.kind,
|
|
6146
|
+
referenceField: usage.field,
|
|
6147
|
+
referenceOwnerId: usage.ownerId,
|
|
6148
|
+
...(usage.characterId ? { characterId: usage.characterId } : {}),
|
|
6149
|
+
},
|
|
6150
|
+
);
|
|
6151
|
+
}
|
|
6152
|
+
},
|
|
6153
|
+
}),
|
|
5506
6154
|
{
|
|
5507
6155
|
type: "story.update",
|
|
5508
6156
|
validatePayload: ({ payload }) => {
|
|
@@ -6849,6 +7497,21 @@ const COMMAND_DEFINITIONS = [
|
|
|
6849
7497
|
);
|
|
6850
7498
|
}
|
|
6851
7499
|
}
|
|
7500
|
+
|
|
7501
|
+
if (payload.data.type === "image") {
|
|
7502
|
+
const result = validateReferencedFilesInData({
|
|
7503
|
+
state,
|
|
7504
|
+
data: payload.data,
|
|
7505
|
+
fields: ["fileId", "thumbnailFileId"],
|
|
7506
|
+
fieldTypes: IMAGE_FILE_REFERENCE_TYPES,
|
|
7507
|
+
details: {
|
|
7508
|
+
imageId: payload.imageId,
|
|
7509
|
+
},
|
|
7510
|
+
});
|
|
7511
|
+
if (!result.valid) {
|
|
7512
|
+
return result;
|
|
7513
|
+
}
|
|
7514
|
+
}
|
|
6852
7515
|
},
|
|
6853
7516
|
reduce: ({ state, payload }) => {
|
|
6854
7517
|
const nextImage = {
|
|
@@ -6863,6 +7526,9 @@ const COMMAND_DEFINITIONS = [
|
|
|
6863
7526
|
|
|
6864
7527
|
if (payload.data.type === "image") {
|
|
6865
7528
|
nextImage.fileId = payload.data.fileId;
|
|
7529
|
+
if (payload.data.thumbnailFileId !== undefined) {
|
|
7530
|
+
nextImage.thumbnailFileId = payload.data.thumbnailFileId;
|
|
7531
|
+
}
|
|
6866
7532
|
if (payload.data.fileType !== undefined) {
|
|
6867
7533
|
nextImage.fileType = payload.data.fileType;
|
|
6868
7534
|
}
|
|
@@ -6936,6 +7602,7 @@ const COMMAND_DEFINITIONS = [
|
|
|
6936
7602
|
if (
|
|
6937
7603
|
currentImage.type === "folder" &&
|
|
6938
7604
|
(payload.data.fileId !== undefined ||
|
|
7605
|
+
payload.data.thumbnailFileId !== undefined ||
|
|
6939
7606
|
payload.data.fileType !== undefined ||
|
|
6940
7607
|
payload.data.fileSize !== undefined ||
|
|
6941
7608
|
payload.data.width !== undefined ||
|
|
@@ -6945,6 +7612,21 @@ const COMMAND_DEFINITIONS = [
|
|
|
6945
7612
|
"folder image items cannot update file fields",
|
|
6946
7613
|
);
|
|
6947
7614
|
}
|
|
7615
|
+
|
|
7616
|
+
if (currentImage.type === "image") {
|
|
7617
|
+
const result = validateReferencedFilesInData({
|
|
7618
|
+
state,
|
|
7619
|
+
data: payload.data,
|
|
7620
|
+
fields: ["fileId", "thumbnailFileId"],
|
|
7621
|
+
fieldTypes: IMAGE_FILE_REFERENCE_TYPES,
|
|
7622
|
+
details: {
|
|
7623
|
+
imageId: payload.imageId,
|
|
7624
|
+
},
|
|
7625
|
+
});
|
|
7626
|
+
if (!result.valid) {
|
|
7627
|
+
return result;
|
|
7628
|
+
}
|
|
7629
|
+
}
|
|
6948
7630
|
},
|
|
6949
7631
|
reduce: ({ state, payload }) => {
|
|
6950
7632
|
const currentImage = state.images.items[payload.imageId];
|
|
@@ -7249,6 +7931,22 @@ const COMMAND_DEFINITIONS = [
|
|
|
7249
7931
|
);
|
|
7250
7932
|
}
|
|
7251
7933
|
}
|
|
7934
|
+
|
|
7935
|
+
if (payload.data.type === "sound") {
|
|
7936
|
+
const result = validateReferencedFilesInData({
|
|
7937
|
+
state,
|
|
7938
|
+
data: payload.data,
|
|
7939
|
+
fields: ["fileId", "waveformDataFileId"],
|
|
7940
|
+
fieldTypes: SOUND_FILE_REFERENCE_TYPES,
|
|
7941
|
+
nullableFields: ["waveformDataFileId"],
|
|
7942
|
+
details: {
|
|
7943
|
+
soundId: payload.soundId,
|
|
7944
|
+
},
|
|
7945
|
+
});
|
|
7946
|
+
if (!result.valid) {
|
|
7947
|
+
return result;
|
|
7948
|
+
}
|
|
7949
|
+
}
|
|
7252
7950
|
},
|
|
7253
7951
|
reduce: ({ state, payload }) => {
|
|
7254
7952
|
const nextSound = {
|
|
@@ -7345,6 +8043,22 @@ const COMMAND_DEFINITIONS = [
|
|
|
7345
8043
|
"folder sound items cannot update file fields",
|
|
7346
8044
|
);
|
|
7347
8045
|
}
|
|
8046
|
+
|
|
8047
|
+
if (currentSound.type === "sound") {
|
|
8048
|
+
const result = validateReferencedFilesInData({
|
|
8049
|
+
state,
|
|
8050
|
+
data: payload.data,
|
|
8051
|
+
fields: ["fileId", "waveformDataFileId"],
|
|
8052
|
+
fieldTypes: SOUND_FILE_REFERENCE_TYPES,
|
|
8053
|
+
nullableFields: ["waveformDataFileId"],
|
|
8054
|
+
details: {
|
|
8055
|
+
soundId: payload.soundId,
|
|
8056
|
+
},
|
|
8057
|
+
});
|
|
8058
|
+
if (!result.valid) {
|
|
8059
|
+
return result;
|
|
8060
|
+
}
|
|
8061
|
+
}
|
|
7348
8062
|
},
|
|
7349
8063
|
reduce: ({ state, payload }) => {
|
|
7350
8064
|
const currentSound = state.sounds.items[payload.soundId];
|
|
@@ -7649,6 +8363,21 @@ const COMMAND_DEFINITIONS = [
|
|
|
7649
8363
|
);
|
|
7650
8364
|
}
|
|
7651
8365
|
}
|
|
8366
|
+
|
|
8367
|
+
if (payload.data.type === "video") {
|
|
8368
|
+
const result = validateReferencedFilesInData({
|
|
8369
|
+
state,
|
|
8370
|
+
data: payload.data,
|
|
8371
|
+
fields: ["fileId", "thumbnailFileId"],
|
|
8372
|
+
fieldTypes: VIDEO_FILE_REFERENCE_TYPES,
|
|
8373
|
+
details: {
|
|
8374
|
+
videoId: payload.videoId,
|
|
8375
|
+
},
|
|
8376
|
+
});
|
|
8377
|
+
if (!result.valid) {
|
|
8378
|
+
return result;
|
|
8379
|
+
}
|
|
8380
|
+
}
|
|
7652
8381
|
},
|
|
7653
8382
|
reduce: ({ state, payload }) => {
|
|
7654
8383
|
const nextVideo = {
|
|
@@ -7747,6 +8476,21 @@ const COMMAND_DEFINITIONS = [
|
|
|
7747
8476
|
"folder video items cannot update file fields",
|
|
7748
8477
|
);
|
|
7749
8478
|
}
|
|
8479
|
+
|
|
8480
|
+
if (currentVideo.type === "video") {
|
|
8481
|
+
const result = validateReferencedFilesInData({
|
|
8482
|
+
state,
|
|
8483
|
+
data: payload.data,
|
|
8484
|
+
fields: ["fileId", "thumbnailFileId"],
|
|
8485
|
+
fieldTypes: VIDEO_FILE_REFERENCE_TYPES,
|
|
8486
|
+
details: {
|
|
8487
|
+
videoId: payload.videoId,
|
|
8488
|
+
},
|
|
8489
|
+
});
|
|
8490
|
+
if (!result.valid) {
|
|
8491
|
+
return result;
|
|
8492
|
+
}
|
|
8493
|
+
}
|
|
7750
8494
|
},
|
|
7751
8495
|
reduce: ({ state, payload }) => {
|
|
7752
8496
|
const currentVideo = state.videos.items[payload.videoId];
|
|
@@ -8431,6 +9175,21 @@ const COMMAND_DEFINITIONS = [
|
|
|
8431
9175
|
);
|
|
8432
9176
|
}
|
|
8433
9177
|
}
|
|
9178
|
+
|
|
9179
|
+
if (payload.data.type === "font") {
|
|
9180
|
+
const result = validateReferencedFilesInData({
|
|
9181
|
+
state,
|
|
9182
|
+
data: payload.data,
|
|
9183
|
+
fields: ["fileId"],
|
|
9184
|
+
fieldTypes: FONT_FILE_REFERENCE_TYPES,
|
|
9185
|
+
details: {
|
|
9186
|
+
fontId: payload.fontId,
|
|
9187
|
+
},
|
|
9188
|
+
});
|
|
9189
|
+
if (!result.valid) {
|
|
9190
|
+
return result;
|
|
9191
|
+
}
|
|
9192
|
+
}
|
|
8434
9193
|
},
|
|
8435
9194
|
reduce: ({ state, payload }) => {
|
|
8436
9195
|
const nextFont = {
|
|
@@ -8517,6 +9276,21 @@ const COMMAND_DEFINITIONS = [
|
|
|
8517
9276
|
"folder font items cannot update font fields",
|
|
8518
9277
|
);
|
|
8519
9278
|
}
|
|
9279
|
+
|
|
9280
|
+
if (currentFont.type === "font") {
|
|
9281
|
+
const result = validateReferencedFilesInData({
|
|
9282
|
+
state,
|
|
9283
|
+
data: payload.data,
|
|
9284
|
+
fields: ["fileId"],
|
|
9285
|
+
fieldTypes: FONT_FILE_REFERENCE_TYPES,
|
|
9286
|
+
details: {
|
|
9287
|
+
fontId: payload.fontId,
|
|
9288
|
+
},
|
|
9289
|
+
});
|
|
9290
|
+
if (!result.valid) {
|
|
9291
|
+
return result;
|
|
9292
|
+
}
|
|
9293
|
+
}
|
|
8520
9294
|
},
|
|
8521
9295
|
reduce: ({ state, payload }) => {
|
|
8522
9296
|
const currentFont = state.fonts.items[payload.fontId];
|
|
@@ -9294,7 +10068,50 @@ const COMMAND_DEFINITIONS = [
|
|
|
9294
10068
|
|
|
9295
10069
|
return item;
|
|
9296
10070
|
},
|
|
9297
|
-
|
|
10071
|
+
validateCreateState: ({ state, payload }) => {
|
|
10072
|
+
if (payload.data.type !== "character") {
|
|
10073
|
+
return;
|
|
10074
|
+
}
|
|
10075
|
+
|
|
10076
|
+
if (payload.data.fileId !== undefined) {
|
|
10077
|
+
const result = validateReferencedFilesInData({
|
|
10078
|
+
state,
|
|
10079
|
+
data: payload.data,
|
|
10080
|
+
fields: ["fileId"],
|
|
10081
|
+
fieldTypes: CHARACTER_FILE_REFERENCE_TYPES,
|
|
10082
|
+
details: {
|
|
10083
|
+
characterId: payload.characterId,
|
|
10084
|
+
},
|
|
10085
|
+
});
|
|
10086
|
+
if (!result.valid) {
|
|
10087
|
+
return result;
|
|
10088
|
+
}
|
|
10089
|
+
}
|
|
10090
|
+
|
|
10091
|
+
for (const [spriteId, sprite] of Object.entries(
|
|
10092
|
+
payload.data.sprites?.items || {},
|
|
10093
|
+
)) {
|
|
10094
|
+
if (sprite.type !== "image") {
|
|
10095
|
+
continue;
|
|
10096
|
+
}
|
|
10097
|
+
|
|
10098
|
+
const result = validateFileReference({
|
|
10099
|
+
state,
|
|
10100
|
+
fileId: sprite.fileId,
|
|
10101
|
+
path: "payload.data.sprites.items.*.fileId",
|
|
10102
|
+
allowedTypes: CHARACTER_FILE_REFERENCE_TYPES.fileId,
|
|
10103
|
+
details: {
|
|
10104
|
+
characterId: payload.characterId,
|
|
10105
|
+
spriteId,
|
|
10106
|
+
fileId: sprite.fileId,
|
|
10107
|
+
},
|
|
10108
|
+
});
|
|
10109
|
+
if (!result.valid) {
|
|
10110
|
+
return result;
|
|
10111
|
+
}
|
|
10112
|
+
}
|
|
10113
|
+
},
|
|
10114
|
+
validateUpdateState: ({ state, payload, currentItem }) => {
|
|
9298
10115
|
if (
|
|
9299
10116
|
currentItem.type === "folder" &&
|
|
9300
10117
|
Object.keys(payload.data).some((key) => key !== "name")
|
|
@@ -9303,6 +10120,21 @@ const COMMAND_DEFINITIONS = [
|
|
|
9303
10120
|
"folder character items cannot update character fields",
|
|
9304
10121
|
);
|
|
9305
10122
|
}
|
|
10123
|
+
|
|
10124
|
+
if (currentItem.type === "character") {
|
|
10125
|
+
const result = validateReferencedFilesInData({
|
|
10126
|
+
state,
|
|
10127
|
+
data: payload.data,
|
|
10128
|
+
fields: ["fileId"],
|
|
10129
|
+
fieldTypes: CHARACTER_FILE_REFERENCE_TYPES,
|
|
10130
|
+
details: {
|
|
10131
|
+
characterId: payload.characterId,
|
|
10132
|
+
},
|
|
10133
|
+
});
|
|
10134
|
+
if (!result.valid) {
|
|
10135
|
+
return result;
|
|
10136
|
+
}
|
|
10137
|
+
}
|
|
9306
10138
|
},
|
|
9307
10139
|
}),
|
|
9308
10140
|
...createFolderedCollectionCommandDefinitions({
|
|
@@ -9446,6 +10278,22 @@ const COMMAND_DEFINITIONS = [
|
|
|
9446
10278
|
);
|
|
9447
10279
|
}
|
|
9448
10280
|
}
|
|
10281
|
+
|
|
10282
|
+
if (payload.data.type === "image") {
|
|
10283
|
+
const result = validateReferencedFilesInData({
|
|
10284
|
+
state,
|
|
10285
|
+
data: payload.data,
|
|
10286
|
+
fields: ["fileId"],
|
|
10287
|
+
fieldTypes: CHARACTER_FILE_REFERENCE_TYPES,
|
|
10288
|
+
details: {
|
|
10289
|
+
characterId: payload.characterId,
|
|
10290
|
+
spriteId: payload.spriteId,
|
|
10291
|
+
},
|
|
10292
|
+
});
|
|
10293
|
+
if (!result.valid) {
|
|
10294
|
+
return result;
|
|
10295
|
+
}
|
|
10296
|
+
}
|
|
9449
10297
|
},
|
|
9450
10298
|
reduce: ({ state, payload }) => {
|
|
9451
10299
|
const collection = getCharacterSpriteCollection({
|
|
@@ -9538,6 +10386,22 @@ const COMMAND_DEFINITIONS = [
|
|
|
9538
10386
|
"folder sprite items cannot update image fields",
|
|
9539
10387
|
);
|
|
9540
10388
|
}
|
|
10389
|
+
|
|
10390
|
+
if (currentItem.type === "image") {
|
|
10391
|
+
const result = validateReferencedFilesInData({
|
|
10392
|
+
state,
|
|
10393
|
+
data: payload.data,
|
|
10394
|
+
fields: ["fileId"],
|
|
10395
|
+
fieldTypes: CHARACTER_FILE_REFERENCE_TYPES,
|
|
10396
|
+
details: {
|
|
10397
|
+
characterId: payload.characterId,
|
|
10398
|
+
spriteId: payload.spriteId,
|
|
10399
|
+
},
|
|
10400
|
+
});
|
|
10401
|
+
if (!result.valid) {
|
|
10402
|
+
return result;
|
|
10403
|
+
}
|
|
10404
|
+
}
|
|
9541
10405
|
},
|
|
9542
10406
|
reduce: ({ state, payload }) => {
|
|
9543
10407
|
const collection = getCharacterSpriteCollection({
|