@routevn/creator-model 1.1.11 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/package.json +5 -2
- package/src/model.js +852 -26
package/README.md
CHANGED
|
@@ -5,6 +5,9 @@ Shared RouteVN domain model package.
|
|
|
5
5
|
Repo rules and contribution expectations are in
|
|
6
6
|
[GUIDELINES.md](./GUIDELINES.md).
|
|
7
7
|
|
|
8
|
+
Schema compatibility maintenance rules are in
|
|
9
|
+
[docs/schema-compatibility.md](./docs/schema-compatibility.md).
|
|
10
|
+
|
|
8
11
|
This repo is intended to be the single source of truth for:
|
|
9
12
|
|
|
10
13
|
- state validation
|
|
@@ -79,6 +82,9 @@ Design rules:
|
|
|
79
82
|
- pure functions whenever possible
|
|
80
83
|
- command payload shape is validated separately from state-aware preconditions
|
|
81
84
|
- `SCHEMA_VERSION` is the source of truth for persisted command schema versioning
|
|
85
|
+
- `SCHEMA_VERSION` must stay aligned with the minor version from `package.json`
|
|
86
|
+
- patch releases must not change persisted schema compatibility
|
|
87
|
+
- `bun run test:compat` is the required compatibility gate for model changes
|
|
82
88
|
- `processCommand()` is the authoritative state transition
|
|
83
89
|
- model state should contain project-owned runtime data only
|
|
84
90
|
- app-owned metadata like project id, name, and description should stay out of
|
|
@@ -190,6 +196,25 @@ YAML Puty specs use [tests/support/putyApi.js](./tests/support/putyApi.js) as a
|
|
|
190
196
|
small adapter so the declarative `throws:` assertions can stay concise while the
|
|
191
197
|
real public API returns `{ valid: ... }` result objects.
|
|
192
198
|
|
|
199
|
+
Compatibility fixtures live under `tests/compat/schema-<n>/`.
|
|
200
|
+
|
|
201
|
+
- `payloads/` fixtures are frozen command payload shapes for that schema version
|
|
202
|
+
- `states/` fixtures are frozen persisted-state snapshots for that schema version
|
|
203
|
+
- `streams/` fixtures are frozen command sequences for that schema version
|
|
204
|
+
- current tests must continue to validate/replay every archived compatibility
|
|
205
|
+
fixture from the same or older schema versions
|
|
206
|
+
- current schema payload coverage must include `minimal.yaml` and `full.yaml` for
|
|
207
|
+
every public command type
|
|
208
|
+
|
|
209
|
+
See also:
|
|
210
|
+
|
|
211
|
+
- [docs/schema-compatibility.md](./docs/schema-compatibility.md)
|
|
212
|
+
|
|
213
|
+
Current animation update tween properties support either:
|
|
214
|
+
|
|
215
|
+
- `keyframes`
|
|
216
|
+
- `auto: { duration, easing }`
|
|
217
|
+
|
|
193
218
|
## Current Scope
|
|
194
219
|
|
|
195
220
|
Currently implemented command types:
|
|
@@ -212,6 +237,10 @@ Currently implemented command types:
|
|
|
212
237
|
- `image.update`
|
|
213
238
|
- `image.delete`
|
|
214
239
|
- `image.move`
|
|
240
|
+
- `spritesheet.create`
|
|
241
|
+
- `spritesheet.update`
|
|
242
|
+
- `spritesheet.delete`
|
|
243
|
+
- `spritesheet.move`
|
|
215
244
|
- `sound.create`
|
|
216
245
|
- `sound.update`
|
|
217
246
|
- `sound.delete`
|
|
@@ -260,6 +289,14 @@ Currently implemented command types:
|
|
|
260
289
|
- `layout.element.update`
|
|
261
290
|
- `layout.element.delete`
|
|
262
291
|
- `layout.element.move`
|
|
292
|
+
- `control.create`
|
|
293
|
+
- `control.update`
|
|
294
|
+
- `control.delete`
|
|
295
|
+
- `control.move`
|
|
296
|
+
- `control.element.create`
|
|
297
|
+
- `control.element.update`
|
|
298
|
+
- `control.element.delete`
|
|
299
|
+
- `control.element.move`
|
|
263
300
|
|
|
264
301
|
The rest of the future command surface should be added only when full
|
|
265
302
|
validation, preconditions, reducer behavior, and tests are added together.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@routevn/creator-model",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,10 +27,13 @@
|
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"test": "bunx vitest run",
|
|
30
|
+
"test:compat": "bunx vitest run tests/compatibility-fixtures.test.js",
|
|
30
31
|
"test:watch": "bunx vitest",
|
|
31
|
-
"bench": "bun scripts/benchmark-process-command.js"
|
|
32
|
+
"bench": "bun scripts/benchmark-process-command.js",
|
|
33
|
+
"generate:compat-fixtures": "node scripts/generate-compat-fixtures.js"
|
|
32
34
|
},
|
|
33
35
|
"devDependencies": {
|
|
36
|
+
"js-yaml": "^4.1.0",
|
|
34
37
|
"puty": "0.1.2",
|
|
35
38
|
"vitest": "^3.2.1"
|
|
36
39
|
}
|
package/src/model.js
CHANGED
|
@@ -21,6 +21,7 @@ const COLLECTION_KEYS = [
|
|
|
21
21
|
"scenes",
|
|
22
22
|
"files",
|
|
23
23
|
"images",
|
|
24
|
+
"spritesheets",
|
|
24
25
|
"sounds",
|
|
25
26
|
"videos",
|
|
26
27
|
"animations",
|
|
@@ -44,14 +45,23 @@ const normalizeStateCollections = (state) => {
|
|
|
44
45
|
return state;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
const missingCollectionKeys = ["spritesheets", "controls"].filter(
|
|
49
|
+
(key) => state[key] === undefined,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (missingCollectionKeys.length === 0) {
|
|
48
53
|
return state;
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
|
|
56
|
+
const nextState = {
|
|
52
57
|
...state,
|
|
53
|
-
controls: createEmptyCollectionState(),
|
|
54
58
|
};
|
|
59
|
+
|
|
60
|
+
missingCollectionKeys.forEach((key) => {
|
|
61
|
+
nextState[key] = createEmptyCollectionState();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return nextState;
|
|
55
65
|
};
|
|
56
66
|
const isString = (value) => typeof value === "string";
|
|
57
67
|
const isHexColor = (value) =>
|
|
@@ -125,6 +135,7 @@ const LAYOUT_ELEMENT_BASE_TYPES = [
|
|
|
125
135
|
"container",
|
|
126
136
|
"rect",
|
|
127
137
|
"sprite",
|
|
138
|
+
"spritesheet-animation",
|
|
128
139
|
"text",
|
|
129
140
|
"text-revealing",
|
|
130
141
|
"slider",
|
|
@@ -853,6 +864,207 @@ const validateImageItems = ({ items, path, errorFactory }) => {
|
|
|
853
864
|
}
|
|
854
865
|
};
|
|
855
866
|
|
|
867
|
+
const validateSpritesheetAnimationMap = ({
|
|
868
|
+
animations,
|
|
869
|
+
path,
|
|
870
|
+
errorFactory,
|
|
871
|
+
}) => {
|
|
872
|
+
if (!isPlainObject(animations)) {
|
|
873
|
+
return invalidFromErrorFactory(errorFactory, `${path} must be an object`);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
for (const [animationName, animation] of Object.entries(animations)) {
|
|
877
|
+
const animationPath = `${path}.${animationName}`;
|
|
878
|
+
|
|
879
|
+
if (!isNonEmptyString(animationName)) {
|
|
880
|
+
return invalidFromErrorFactory(
|
|
881
|
+
errorFactory,
|
|
882
|
+
`${animationPath} must use a non-empty animation name`,
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
{
|
|
887
|
+
const result = validateAllowedKeys({
|
|
888
|
+
value: animation,
|
|
889
|
+
allowedKeys: ["frames", "animationSpeed", "loop"],
|
|
890
|
+
path: animationPath,
|
|
891
|
+
errorFactory,
|
|
892
|
+
});
|
|
893
|
+
if (result?.valid === false) {
|
|
894
|
+
return result;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (!Array.isArray(animation.frames)) {
|
|
899
|
+
return invalidFromErrorFactory(
|
|
900
|
+
errorFactory,
|
|
901
|
+
`${animationPath}.frames must be an array`,
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
for (let index = 0; index < animation.frames.length; index += 1) {
|
|
906
|
+
const frame = animation.frames[index];
|
|
907
|
+
if (!Number.isInteger(frame) || frame < 0) {
|
|
908
|
+
return invalidFromErrorFactory(
|
|
909
|
+
errorFactory,
|
|
910
|
+
`${animationPath}.frames.${index} must be an integer greater than or equal to 0`,
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (
|
|
916
|
+
animation.animationSpeed !== undefined &&
|
|
917
|
+
!isFiniteNumber(animation.animationSpeed)
|
|
918
|
+
) {
|
|
919
|
+
return invalidFromErrorFactory(
|
|
920
|
+
errorFactory,
|
|
921
|
+
`${animationPath}.animationSpeed must be a finite number when provided`,
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
if (animation.loop !== undefined && typeof animation.loop !== "boolean") {
|
|
926
|
+
return invalidFromErrorFactory(
|
|
927
|
+
errorFactory,
|
|
928
|
+
`${animationPath}.loop must be a boolean when provided`,
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
return VALID_RESULT;
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
const validateSpritesheetItems = ({ items, path, errorFactory }) => {
|
|
937
|
+
for (const [itemId, item] of Object.entries(items)) {
|
|
938
|
+
const itemPath = `${path}.${itemId}`;
|
|
939
|
+
|
|
940
|
+
if (item?.type !== "folder" && item?.type !== "spritesheet") {
|
|
941
|
+
return invalidFromErrorFactory(
|
|
942
|
+
errorFactory,
|
|
943
|
+
`${itemPath}.type must be 'folder' or 'spritesheet'`,
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
{
|
|
948
|
+
const result = validateAllowedKeys({
|
|
949
|
+
value: item,
|
|
950
|
+
allowedKeys:
|
|
951
|
+
item.type === "folder"
|
|
952
|
+
? ["id", "type", "name", "description"]
|
|
953
|
+
: [
|
|
954
|
+
"id",
|
|
955
|
+
"type",
|
|
956
|
+
"name",
|
|
957
|
+
"description",
|
|
958
|
+
"thumbnailFileId",
|
|
959
|
+
"fileId",
|
|
960
|
+
"fileType",
|
|
961
|
+
"fileSize",
|
|
962
|
+
"sheetWidth",
|
|
963
|
+
"sheetHeight",
|
|
964
|
+
"frameCount",
|
|
965
|
+
"width",
|
|
966
|
+
"height",
|
|
967
|
+
"jsonData",
|
|
968
|
+
"animations",
|
|
969
|
+
],
|
|
970
|
+
path: itemPath,
|
|
971
|
+
errorFactory,
|
|
972
|
+
});
|
|
973
|
+
if (result?.valid === false) {
|
|
974
|
+
return result;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
if (!isNonEmptyString(item.id)) {
|
|
979
|
+
return invalidFromErrorFactory(
|
|
980
|
+
errorFactory,
|
|
981
|
+
`${itemPath}.id must be a non-empty string`,
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
if (item.id !== itemId) {
|
|
986
|
+
return invalidFromErrorFactory(
|
|
987
|
+
errorFactory,
|
|
988
|
+
`${itemPath}.id must match item key '${itemId}'`,
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
if (!isNonEmptyString(item.name)) {
|
|
993
|
+
return invalidFromErrorFactory(
|
|
994
|
+
errorFactory,
|
|
995
|
+
`${itemPath}.name must be a non-empty string`,
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
if (item.description !== undefined && !isString(item.description)) {
|
|
1000
|
+
return invalidFromErrorFactory(
|
|
1001
|
+
errorFactory,
|
|
1002
|
+
`${itemPath}.description must be a string when provided`,
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
if (item.type === "spritesheet") {
|
|
1007
|
+
if (
|
|
1008
|
+
item.thumbnailFileId !== undefined &&
|
|
1009
|
+
!isNonEmptyString(item.thumbnailFileId)
|
|
1010
|
+
) {
|
|
1011
|
+
return invalidFromErrorFactory(
|
|
1012
|
+
errorFactory,
|
|
1013
|
+
`${itemPath}.thumbnailFileId must be a non-empty string when provided`,
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
if (!isNonEmptyString(item.fileId)) {
|
|
1018
|
+
return invalidFromErrorFactory(
|
|
1019
|
+
errorFactory,
|
|
1020
|
+
`${itemPath}.fileId must be a non-empty string`,
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
if (item.fileType !== undefined && !isString(item.fileType)) {
|
|
1025
|
+
return invalidFromErrorFactory(
|
|
1026
|
+
errorFactory,
|
|
1027
|
+
`${itemPath}.fileType must be a string when provided`,
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
for (const key of [
|
|
1032
|
+
"fileSize",
|
|
1033
|
+
"sheetWidth",
|
|
1034
|
+
"sheetHeight",
|
|
1035
|
+
"frameCount",
|
|
1036
|
+
"width",
|
|
1037
|
+
"height",
|
|
1038
|
+
]) {
|
|
1039
|
+
if (item[key] !== undefined && !isFiniteNumber(item[key])) {
|
|
1040
|
+
return invalidFromErrorFactory(
|
|
1041
|
+
errorFactory,
|
|
1042
|
+
`${itemPath}.${key} must be a finite number when provided`,
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
if (!isPlainObject(item.jsonData)) {
|
|
1048
|
+
return invalidFromErrorFactory(
|
|
1049
|
+
errorFactory,
|
|
1050
|
+
`${itemPath}.jsonData must be an object`,
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
{
|
|
1055
|
+
const result = validateSpritesheetAnimationMap({
|
|
1056
|
+
animations: item.animations,
|
|
1057
|
+
path: `${itemPath}.animations`,
|
|
1058
|
+
errorFactory,
|
|
1059
|
+
});
|
|
1060
|
+
if (result?.valid === false) {
|
|
1061
|
+
return result;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
|
|
856
1068
|
const validateSoundItems = ({ items, path, errorFactory }) => {
|
|
857
1069
|
for (const [itemId, item] of Object.entries(items)) {
|
|
858
1070
|
const itemPath = `${path}.${itemId}`;
|
|
@@ -1141,16 +1353,57 @@ const validateAnimationKeyframes = ({ keyframes, path, errorFactory }) => {
|
|
|
1141
1353
|
}
|
|
1142
1354
|
};
|
|
1143
1355
|
|
|
1356
|
+
const validateAutoTweenProperty = ({ auto, path, errorFactory }) => {
|
|
1357
|
+
{
|
|
1358
|
+
const result = validateAllowedKeys({
|
|
1359
|
+
value: auto,
|
|
1360
|
+
allowedKeys: ["duration", "easing"],
|
|
1361
|
+
path,
|
|
1362
|
+
errorFactory,
|
|
1363
|
+
});
|
|
1364
|
+
if (result?.valid === false) {
|
|
1365
|
+
return result;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
if (!("duration" in auto)) {
|
|
1370
|
+
return invalidFromErrorFactory(
|
|
1371
|
+
errorFactory,
|
|
1372
|
+
`${path}.duration is required`,
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
if (!isFiniteNumber(auto.duration) || auto.duration < 1) {
|
|
1377
|
+
return invalidFromErrorFactory(
|
|
1378
|
+
errorFactory,
|
|
1379
|
+
`${path}.duration must be a finite number >= 1`,
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
if (
|
|
1384
|
+
auto.easing !== undefined &&
|
|
1385
|
+
!ANIMATION_EASING_KEYS.includes(auto.easing)
|
|
1386
|
+
) {
|
|
1387
|
+
return invalidFromErrorFactory(
|
|
1388
|
+
errorFactory,
|
|
1389
|
+
`${path}.easing must be a supported Route Graphics easing`,
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
};
|
|
1393
|
+
|
|
1144
1394
|
const validateTweenProperty = ({
|
|
1145
1395
|
config,
|
|
1146
1396
|
path,
|
|
1147
1397
|
allowEmptyKeyframes = false,
|
|
1398
|
+
allowAuto = false,
|
|
1148
1399
|
errorFactory,
|
|
1149
1400
|
}) => {
|
|
1150
1401
|
{
|
|
1151
1402
|
const result = validateAllowedKeys({
|
|
1152
1403
|
value: config,
|
|
1153
|
-
allowedKeys:
|
|
1404
|
+
allowedKeys: allowAuto
|
|
1405
|
+
? ["initialValue", "keyframes", "auto"]
|
|
1406
|
+
: ["initialValue", "keyframes"],
|
|
1154
1407
|
path,
|
|
1155
1408
|
errorFactory,
|
|
1156
1409
|
});
|
|
@@ -1159,10 +1412,20 @@ const validateTweenProperty = ({
|
|
|
1159
1412
|
}
|
|
1160
1413
|
}
|
|
1161
1414
|
|
|
1162
|
-
|
|
1415
|
+
const hasKeyframes = "keyframes" in config;
|
|
1416
|
+
const hasAuto = "auto" in config;
|
|
1417
|
+
|
|
1418
|
+
if (!hasKeyframes && !hasAuto) {
|
|
1419
|
+
return invalidFromErrorFactory(
|
|
1420
|
+
errorFactory,
|
|
1421
|
+
`${path}.keyframes or ${path}.auto is required`,
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
if (hasKeyframes && hasAuto) {
|
|
1163
1426
|
return invalidFromErrorFactory(
|
|
1164
1427
|
errorFactory,
|
|
1165
|
-
`${path}.keyframes
|
|
1428
|
+
`${path}.keyframes and ${path}.auto cannot both be defined`,
|
|
1166
1429
|
);
|
|
1167
1430
|
}
|
|
1168
1431
|
|
|
@@ -1176,6 +1439,28 @@ const validateTweenProperty = ({
|
|
|
1176
1439
|
);
|
|
1177
1440
|
}
|
|
1178
1441
|
|
|
1442
|
+
if (hasAuto) {
|
|
1443
|
+
if (config.initialValue !== undefined) {
|
|
1444
|
+
return invalidFromErrorFactory(
|
|
1445
|
+
errorFactory,
|
|
1446
|
+
`${path}.initialValue is not supported when ${path}.auto is defined`,
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
{
|
|
1451
|
+
const result = validateAutoTweenProperty({
|
|
1452
|
+
auto: config.auto,
|
|
1453
|
+
path: `${path}.auto`,
|
|
1454
|
+
errorFactory,
|
|
1455
|
+
});
|
|
1456
|
+
if (result?.valid === false) {
|
|
1457
|
+
return result;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1179
1464
|
{
|
|
1180
1465
|
const result = validateAnimationKeyframes({
|
|
1181
1466
|
keyframes: config.keyframes,
|
|
@@ -1201,6 +1486,7 @@ const validateTweenDefinition = ({
|
|
|
1201
1486
|
path,
|
|
1202
1487
|
unsupportedMessage,
|
|
1203
1488
|
allowEmptyKeyframes = false,
|
|
1489
|
+
allowAuto = false,
|
|
1204
1490
|
errorFactory,
|
|
1205
1491
|
}) => {
|
|
1206
1492
|
if (!isPlainObject(tween)) {
|
|
@@ -1229,6 +1515,7 @@ const validateTweenDefinition = ({
|
|
|
1229
1515
|
config,
|
|
1230
1516
|
path: propertyPath,
|
|
1231
1517
|
allowEmptyKeyframes,
|
|
1518
|
+
allowAuto,
|
|
1232
1519
|
errorFactory,
|
|
1233
1520
|
});
|
|
1234
1521
|
if (result?.valid === false) {
|
|
@@ -1357,30 +1644,21 @@ const validateMaskDefinition = ({ mask, path, errorFactory }) => {
|
|
|
1357
1644
|
}
|
|
1358
1645
|
}
|
|
1359
1646
|
|
|
1360
|
-
if (
|
|
1361
|
-
item.texture !== undefined &&
|
|
1362
|
-
!isNonEmptyString(item.texture)
|
|
1363
|
-
) {
|
|
1647
|
+
if (item.texture !== undefined && !isNonEmptyString(item.texture)) {
|
|
1364
1648
|
return invalidFromErrorFactory(
|
|
1365
1649
|
errorFactory,
|
|
1366
1650
|
`${itemPath}.texture must be a non-empty string when provided`,
|
|
1367
1651
|
);
|
|
1368
1652
|
}
|
|
1369
1653
|
|
|
1370
|
-
if (
|
|
1371
|
-
item.imageId !== undefined &&
|
|
1372
|
-
!isNonEmptyString(item.imageId)
|
|
1373
|
-
) {
|
|
1654
|
+
if (item.imageId !== undefined && !isNonEmptyString(item.imageId)) {
|
|
1374
1655
|
return invalidFromErrorFactory(
|
|
1375
1656
|
errorFactory,
|
|
1376
1657
|
`${itemPath}.imageId must be a non-empty string when provided`,
|
|
1377
1658
|
);
|
|
1378
1659
|
}
|
|
1379
1660
|
|
|
1380
|
-
if (
|
|
1381
|
-
!isNonEmptyString(item.texture) &&
|
|
1382
|
-
!isNonEmptyString(item.imageId)
|
|
1383
|
-
) {
|
|
1661
|
+
if (!isNonEmptyString(item.texture) && !isNonEmptyString(item.imageId)) {
|
|
1384
1662
|
return invalidFromErrorFactory(
|
|
1385
1663
|
errorFactory,
|
|
1386
1664
|
`${itemPath} must define texture or imageId`,
|
|
@@ -1556,6 +1834,8 @@ const validateAnimationDefinition = ({ animation, path, errorFactory }) => {
|
|
|
1556
1834
|
allowedProperties: UPDATE_TWEEN_PROPERTY_KEYS,
|
|
1557
1835
|
path: `${path}.tween`,
|
|
1558
1836
|
unsupportedMessage: "is not a supported update tween property",
|
|
1837
|
+
allowEmptyKeyframes: true,
|
|
1838
|
+
allowAuto: true,
|
|
1559
1839
|
errorFactory,
|
|
1560
1840
|
});
|
|
1561
1841
|
if (result?.valid === false) {
|
|
@@ -1987,7 +2267,15 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
|
|
|
1987
2267
|
allowedKeys:
|
|
1988
2268
|
variableType === "folder"
|
|
1989
2269
|
? ["id", "type", "name", "description"]
|
|
1990
|
-
: [
|
|
2270
|
+
: [
|
|
2271
|
+
"id",
|
|
2272
|
+
"type",
|
|
2273
|
+
"name",
|
|
2274
|
+
"description",
|
|
2275
|
+
"scope",
|
|
2276
|
+
"default",
|
|
2277
|
+
"value",
|
|
2278
|
+
],
|
|
1991
2279
|
path: itemPath,
|
|
1992
2280
|
errorFactory,
|
|
1993
2281
|
});
|
|
@@ -2487,6 +2775,8 @@ const validateLayoutElementData = ({
|
|
|
2487
2775
|
"textStyle",
|
|
2488
2776
|
"displaySpeed",
|
|
2489
2777
|
"revealEffect",
|
|
2778
|
+
"resourceId",
|
|
2779
|
+
"animationName",
|
|
2490
2780
|
"imageId",
|
|
2491
2781
|
"hoverImageId",
|
|
2492
2782
|
"clickImageId",
|
|
@@ -2608,6 +2898,8 @@ const validateLayoutElementData = ({
|
|
|
2608
2898
|
|
|
2609
2899
|
for (const key of [
|
|
2610
2900
|
"text",
|
|
2901
|
+
"resourceId",
|
|
2902
|
+
"animationName",
|
|
2611
2903
|
"imageId",
|
|
2612
2904
|
"hoverImageId",
|
|
2613
2905
|
"clickImageId",
|
|
@@ -2634,6 +2926,28 @@ const validateLayoutElementData = ({
|
|
|
2634
2926
|
}
|
|
2635
2927
|
}
|
|
2636
2928
|
|
|
2929
|
+
if (data.type === "spritesheet-animation") {
|
|
2930
|
+
if (
|
|
2931
|
+
(!allowPartial || data.resourceId !== undefined) &&
|
|
2932
|
+
!isNonEmptyString(data.resourceId)
|
|
2933
|
+
) {
|
|
2934
|
+
return invalidFromErrorFactory(
|
|
2935
|
+
errorFactory,
|
|
2936
|
+
`${path}.resourceId must be a non-empty string`,
|
|
2937
|
+
);
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
if (
|
|
2941
|
+
(!allowPartial || data.animationName !== undefined) &&
|
|
2942
|
+
!isNonEmptyString(data.animationName)
|
|
2943
|
+
) {
|
|
2944
|
+
return invalidFromErrorFactory(
|
|
2945
|
+
errorFactory,
|
|
2946
|
+
`${path}.animationName must be a non-empty string`,
|
|
2947
|
+
);
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2637
2951
|
if (data.conditionalOverrides !== undefined) {
|
|
2638
2952
|
if (!Array.isArray(data.conditionalOverrides)) {
|
|
2639
2953
|
return invalidFromErrorFactory(
|
|
@@ -2983,6 +3297,8 @@ const validateLayoutElementItems = ({ items, path, errorFactory }) => {
|
|
|
2983
3297
|
"textStyle",
|
|
2984
3298
|
"displaySpeed",
|
|
2985
3299
|
"revealEffect",
|
|
3300
|
+
"resourceId",
|
|
3301
|
+
"animationName",
|
|
2986
3302
|
"imageId",
|
|
2987
3303
|
"hoverImageId",
|
|
2988
3304
|
"clickImageId",
|
|
@@ -3889,6 +4205,17 @@ const validateCollection = ({ collection, path }) => {
|
|
|
3889
4205
|
return result;
|
|
3890
4206
|
}
|
|
3891
4207
|
}
|
|
4208
|
+
} else if (path === "state.spritesheets") {
|
|
4209
|
+
{
|
|
4210
|
+
const result = validateSpritesheetItems({
|
|
4211
|
+
items: collection.items,
|
|
4212
|
+
path: `${path}.items`,
|
|
4213
|
+
errorFactory: createStateValidationError,
|
|
4214
|
+
});
|
|
4215
|
+
if (result?.valid === false) {
|
|
4216
|
+
return result;
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
3892
4219
|
} else if (path === "state.files") {
|
|
3893
4220
|
{
|
|
3894
4221
|
const result = validateFileItems({
|
|
@@ -4062,9 +4389,21 @@ const validateCollection = ({ collection, path }) => {
|
|
|
4062
4389
|
return result;
|
|
4063
4390
|
}
|
|
4064
4391
|
}
|
|
4065
|
-
} else if (path === "state.
|
|
4392
|
+
} else if (path === "state.spritesheets") {
|
|
4066
4393
|
{
|
|
4067
|
-
const result =
|
|
4394
|
+
const result = validateGenericFolderOwnership({
|
|
4395
|
+
nodes: collection.tree,
|
|
4396
|
+
items: collection.items,
|
|
4397
|
+
path: `${path}.tree`,
|
|
4398
|
+
folderLabel: "folder spritesheet item",
|
|
4399
|
+
});
|
|
4400
|
+
if (result?.valid === false) {
|
|
4401
|
+
return result;
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
} else if (path === "state.sounds") {
|
|
4405
|
+
{
|
|
4406
|
+
const result = validateSoundTreeFolderOwnership({
|
|
4068
4407
|
nodes: collection.tree,
|
|
4069
4408
|
items: collection.items,
|
|
4070
4409
|
path: `${path}.tree`,
|
|
@@ -4418,6 +4757,43 @@ export const assertInvariants = ({ state }) => {
|
|
|
4418
4757
|
}
|
|
4419
4758
|
}
|
|
4420
4759
|
|
|
4760
|
+
for (const [spritesheetId, spritesheet] of Object.entries(
|
|
4761
|
+
state.spritesheets.items,
|
|
4762
|
+
)) {
|
|
4763
|
+
if (spritesheet.type !== "spritesheet") {
|
|
4764
|
+
continue;
|
|
4765
|
+
}
|
|
4766
|
+
|
|
4767
|
+
const result = validateFileReference({
|
|
4768
|
+
state,
|
|
4769
|
+
fileId: spritesheet.fileId,
|
|
4770
|
+
path: "spritesheet.fileId",
|
|
4771
|
+
details: { spritesheetId, fileId: spritesheet.fileId },
|
|
4772
|
+
errorFactory: createInvariantValidationError,
|
|
4773
|
+
});
|
|
4774
|
+
if (!result.valid) {
|
|
4775
|
+
return result;
|
|
4776
|
+
}
|
|
4777
|
+
|
|
4778
|
+
if (spritesheet.thumbnailFileId === undefined) {
|
|
4779
|
+
continue;
|
|
4780
|
+
}
|
|
4781
|
+
|
|
4782
|
+
const thumbnailResult = validateFileReference({
|
|
4783
|
+
state,
|
|
4784
|
+
fileId: spritesheet.thumbnailFileId,
|
|
4785
|
+
path: "spritesheet.thumbnailFileId",
|
|
4786
|
+
details: {
|
|
4787
|
+
spritesheetId,
|
|
4788
|
+
thumbnailFileId: spritesheet.thumbnailFileId,
|
|
4789
|
+
},
|
|
4790
|
+
errorFactory: createInvariantValidationError,
|
|
4791
|
+
});
|
|
4792
|
+
if (!thumbnailResult.valid) {
|
|
4793
|
+
return thumbnailResult;
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4796
|
+
|
|
4421
4797
|
for (const [soundId, sound] of Object.entries(state.sounds.items)) {
|
|
4422
4798
|
if (sound.type !== "sound") {
|
|
4423
4799
|
continue;
|
|
@@ -4502,7 +4878,9 @@ export const assertInvariants = ({ state }) => {
|
|
|
4502
4878
|
}
|
|
4503
4879
|
}
|
|
4504
4880
|
|
|
4505
|
-
for (const [animationId, animation] of Object.entries(
|
|
4881
|
+
for (const [animationId, animation] of Object.entries(
|
|
4882
|
+
state.animations.items,
|
|
4883
|
+
)) {
|
|
4506
4884
|
if (animation.type !== "animation") {
|
|
4507
4885
|
continue;
|
|
4508
4886
|
}
|
|
@@ -4707,6 +5085,45 @@ export const assertInvariants = ({ state }) => {
|
|
|
4707
5085
|
return VALID_RESULT;
|
|
4708
5086
|
};
|
|
4709
5087
|
|
|
5088
|
+
const assertSpritesheetAnimationReference = ({
|
|
5089
|
+
ownerIdField,
|
|
5090
|
+
ownerId,
|
|
5091
|
+
ownerLabel,
|
|
5092
|
+
elementId,
|
|
5093
|
+
targetId,
|
|
5094
|
+
animationName,
|
|
5095
|
+
}) => {
|
|
5096
|
+
const spritesheet = state.spritesheets?.items?.[targetId];
|
|
5097
|
+
if (!isPlainObject(spritesheet) || spritesheet.type === "folder") {
|
|
5098
|
+
return invalidInvariant(
|
|
5099
|
+
`${ownerLabel} element resourceId must reference an existing non-folder spritesheet`,
|
|
5100
|
+
{
|
|
5101
|
+
[ownerIdField]: ownerId,
|
|
5102
|
+
elementId,
|
|
5103
|
+
field: "resourceId",
|
|
5104
|
+
targetId,
|
|
5105
|
+
},
|
|
5106
|
+
);
|
|
5107
|
+
}
|
|
5108
|
+
|
|
5109
|
+
if (
|
|
5110
|
+
!isNonEmptyString(animationName) ||
|
|
5111
|
+
!isPlainObject(spritesheet.animations?.[animationName])
|
|
5112
|
+
) {
|
|
5113
|
+
return invalidInvariant(
|
|
5114
|
+
`${ownerLabel} element animationName must reference an existing spritesheet animation`,
|
|
5115
|
+
{
|
|
5116
|
+
[ownerIdField]: ownerId,
|
|
5117
|
+
elementId,
|
|
5118
|
+
field: "animationName",
|
|
5119
|
+
targetId: animationName,
|
|
5120
|
+
},
|
|
5121
|
+
);
|
|
5122
|
+
}
|
|
5123
|
+
|
|
5124
|
+
return VALID_RESULT;
|
|
5125
|
+
};
|
|
5126
|
+
|
|
4710
5127
|
const assertElementReferencesForCollection = ({
|
|
4711
5128
|
items,
|
|
4712
5129
|
ownerIdField,
|
|
@@ -4719,6 +5136,24 @@ export const assertInvariants = ({ state }) => {
|
|
|
4719
5136
|
}
|
|
4720
5137
|
|
|
4721
5138
|
for (const [elementId, element] of Object.entries(owner.elements.items)) {
|
|
5139
|
+
if (
|
|
5140
|
+
element.type === "spritesheet-animation" ||
|
|
5141
|
+
element.resourceId !== undefined ||
|
|
5142
|
+
element.animationName !== undefined
|
|
5143
|
+
) {
|
|
5144
|
+
const result = assertSpritesheetAnimationReference({
|
|
5145
|
+
ownerIdField,
|
|
5146
|
+
ownerId,
|
|
5147
|
+
ownerLabel,
|
|
5148
|
+
elementId,
|
|
5149
|
+
targetId: element.resourceId,
|
|
5150
|
+
animationName: element.animationName,
|
|
5151
|
+
});
|
|
5152
|
+
if (!result.valid) {
|
|
5153
|
+
return result;
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
|
|
4722
5157
|
for (const field of [
|
|
4723
5158
|
"imageId",
|
|
4724
5159
|
"hoverImageId",
|
|
@@ -5459,6 +5894,233 @@ const validateImageUpdateData = ({ data, errorFactory }) => {
|
|
|
5459
5894
|
}
|
|
5460
5895
|
};
|
|
5461
5896
|
|
|
5897
|
+
const validateSpritesheetCreateData = ({ data, errorFactory }) => {
|
|
5898
|
+
if (!isPlainObject(data)) {
|
|
5899
|
+
return invalidFromErrorFactory(
|
|
5900
|
+
errorFactory,
|
|
5901
|
+
"payload.data must be an object",
|
|
5902
|
+
);
|
|
5903
|
+
}
|
|
5904
|
+
|
|
5905
|
+
if (data.type !== "folder" && data.type !== "spritesheet") {
|
|
5906
|
+
return invalidFromErrorFactory(
|
|
5907
|
+
errorFactory,
|
|
5908
|
+
"payload.data.type must be 'folder' or 'spritesheet'",
|
|
5909
|
+
);
|
|
5910
|
+
}
|
|
5911
|
+
|
|
5912
|
+
{
|
|
5913
|
+
const result = validateAllowedKeys({
|
|
5914
|
+
value: data,
|
|
5915
|
+
allowedKeys:
|
|
5916
|
+
data.type === "folder"
|
|
5917
|
+
? ["type", "name", "description"]
|
|
5918
|
+
: [
|
|
5919
|
+
"type",
|
|
5920
|
+
"name",
|
|
5921
|
+
"description",
|
|
5922
|
+
"thumbnailFileId",
|
|
5923
|
+
"fileId",
|
|
5924
|
+
"fileType",
|
|
5925
|
+
"fileSize",
|
|
5926
|
+
"sheetWidth",
|
|
5927
|
+
"sheetHeight",
|
|
5928
|
+
"frameCount",
|
|
5929
|
+
"width",
|
|
5930
|
+
"height",
|
|
5931
|
+
"jsonData",
|
|
5932
|
+
"animations",
|
|
5933
|
+
],
|
|
5934
|
+
path: "payload.data",
|
|
5935
|
+
errorFactory,
|
|
5936
|
+
});
|
|
5937
|
+
if (result?.valid === false) {
|
|
5938
|
+
return result;
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5941
|
+
|
|
5942
|
+
if (!isNonEmptyString(data.name)) {
|
|
5943
|
+
return invalidFromErrorFactory(
|
|
5944
|
+
errorFactory,
|
|
5945
|
+
"payload.data.name must be a non-empty string",
|
|
5946
|
+
);
|
|
5947
|
+
}
|
|
5948
|
+
|
|
5949
|
+
if (data.description !== undefined && !isString(data.description)) {
|
|
5950
|
+
return invalidFromErrorFactory(
|
|
5951
|
+
errorFactory,
|
|
5952
|
+
"payload.data.description must be a string when provided",
|
|
5953
|
+
);
|
|
5954
|
+
}
|
|
5955
|
+
|
|
5956
|
+
if (data.type === "spritesheet") {
|
|
5957
|
+
if (
|
|
5958
|
+
data.thumbnailFileId !== undefined &&
|
|
5959
|
+
!isNonEmptyString(data.thumbnailFileId)
|
|
5960
|
+
) {
|
|
5961
|
+
return invalidFromErrorFactory(
|
|
5962
|
+
errorFactory,
|
|
5963
|
+
"payload.data.thumbnailFileId must be a non-empty string when provided",
|
|
5964
|
+
);
|
|
5965
|
+
}
|
|
5966
|
+
|
|
5967
|
+
if (!isNonEmptyString(data.fileId)) {
|
|
5968
|
+
return invalidFromErrorFactory(
|
|
5969
|
+
errorFactory,
|
|
5970
|
+
"payload.data.fileId must be a non-empty string",
|
|
5971
|
+
);
|
|
5972
|
+
}
|
|
5973
|
+
|
|
5974
|
+
if (data.fileType !== undefined && !isString(data.fileType)) {
|
|
5975
|
+
return invalidFromErrorFactory(
|
|
5976
|
+
errorFactory,
|
|
5977
|
+
"payload.data.fileType must be a string when provided",
|
|
5978
|
+
);
|
|
5979
|
+
}
|
|
5980
|
+
|
|
5981
|
+
for (const key of [
|
|
5982
|
+
"fileSize",
|
|
5983
|
+
"sheetWidth",
|
|
5984
|
+
"sheetHeight",
|
|
5985
|
+
"frameCount",
|
|
5986
|
+
"width",
|
|
5987
|
+
"height",
|
|
5988
|
+
]) {
|
|
5989
|
+
if (data[key] !== undefined && !isFiniteNumber(data[key])) {
|
|
5990
|
+
return invalidFromErrorFactory(
|
|
5991
|
+
errorFactory,
|
|
5992
|
+
`payload.data.${key} must be a finite number`,
|
|
5993
|
+
);
|
|
5994
|
+
}
|
|
5995
|
+
}
|
|
5996
|
+
|
|
5997
|
+
if (!isPlainObject(data.jsonData)) {
|
|
5998
|
+
return invalidFromErrorFactory(
|
|
5999
|
+
errorFactory,
|
|
6000
|
+
"payload.data.jsonData must be an object",
|
|
6001
|
+
);
|
|
6002
|
+
}
|
|
6003
|
+
|
|
6004
|
+
{
|
|
6005
|
+
const result = validateSpritesheetAnimationMap({
|
|
6006
|
+
animations: data.animations,
|
|
6007
|
+
path: "payload.data.animations",
|
|
6008
|
+
errorFactory,
|
|
6009
|
+
});
|
|
6010
|
+
if (result?.valid === false) {
|
|
6011
|
+
return result;
|
|
6012
|
+
}
|
|
6013
|
+
}
|
|
6014
|
+
}
|
|
6015
|
+
};
|
|
6016
|
+
|
|
6017
|
+
const validateSpritesheetUpdateData = ({ data, errorFactory }) => {
|
|
6018
|
+
{
|
|
6019
|
+
const result = validateAllowedKeys({
|
|
6020
|
+
value: data,
|
|
6021
|
+
allowedKeys: [
|
|
6022
|
+
"name",
|
|
6023
|
+
"description",
|
|
6024
|
+
"thumbnailFileId",
|
|
6025
|
+
"fileId",
|
|
6026
|
+
"fileType",
|
|
6027
|
+
"fileSize",
|
|
6028
|
+
"sheetWidth",
|
|
6029
|
+
"sheetHeight",
|
|
6030
|
+
"frameCount",
|
|
6031
|
+
"width",
|
|
6032
|
+
"height",
|
|
6033
|
+
"jsonData",
|
|
6034
|
+
"animations",
|
|
6035
|
+
],
|
|
6036
|
+
path: "payload.data",
|
|
6037
|
+
errorFactory,
|
|
6038
|
+
});
|
|
6039
|
+
if (result?.valid === false) {
|
|
6040
|
+
return result;
|
|
6041
|
+
}
|
|
6042
|
+
}
|
|
6043
|
+
|
|
6044
|
+
if (Object.keys(data).length === 0) {
|
|
6045
|
+
return invalidFromErrorFactory(
|
|
6046
|
+
errorFactory,
|
|
6047
|
+
"payload.data must include at least one updatable field",
|
|
6048
|
+
);
|
|
6049
|
+
}
|
|
6050
|
+
|
|
6051
|
+
if (data.name !== undefined && !isNonEmptyString(data.name)) {
|
|
6052
|
+
return invalidFromErrorFactory(
|
|
6053
|
+
errorFactory,
|
|
6054
|
+
"payload.data.name must be a non-empty string when provided",
|
|
6055
|
+
);
|
|
6056
|
+
}
|
|
6057
|
+
|
|
6058
|
+
if (data.description !== undefined && !isString(data.description)) {
|
|
6059
|
+
return invalidFromErrorFactory(
|
|
6060
|
+
errorFactory,
|
|
6061
|
+
"payload.data.description must be a string when provided",
|
|
6062
|
+
);
|
|
6063
|
+
}
|
|
6064
|
+
|
|
6065
|
+
if (
|
|
6066
|
+
data.thumbnailFileId !== undefined &&
|
|
6067
|
+
!isNonEmptyString(data.thumbnailFileId)
|
|
6068
|
+
) {
|
|
6069
|
+
return invalidFromErrorFactory(
|
|
6070
|
+
errorFactory,
|
|
6071
|
+
"payload.data.thumbnailFileId must be a non-empty string when provided",
|
|
6072
|
+
);
|
|
6073
|
+
}
|
|
6074
|
+
|
|
6075
|
+
if (data.fileId !== undefined && !isNonEmptyString(data.fileId)) {
|
|
6076
|
+
return invalidFromErrorFactory(
|
|
6077
|
+
errorFactory,
|
|
6078
|
+
"payload.data.fileId must be a non-empty string when provided",
|
|
6079
|
+
);
|
|
6080
|
+
}
|
|
6081
|
+
|
|
6082
|
+
if (data.fileType !== undefined && !isString(data.fileType)) {
|
|
6083
|
+
return invalidFromErrorFactory(
|
|
6084
|
+
errorFactory,
|
|
6085
|
+
"payload.data.fileType must be a string when provided",
|
|
6086
|
+
);
|
|
6087
|
+
}
|
|
6088
|
+
|
|
6089
|
+
for (const key of [
|
|
6090
|
+
"fileSize",
|
|
6091
|
+
"sheetWidth",
|
|
6092
|
+
"sheetHeight",
|
|
6093
|
+
"frameCount",
|
|
6094
|
+
"width",
|
|
6095
|
+
"height",
|
|
6096
|
+
]) {
|
|
6097
|
+
if (data[key] !== undefined && !isFiniteNumber(data[key])) {
|
|
6098
|
+
return invalidFromErrorFactory(
|
|
6099
|
+
errorFactory,
|
|
6100
|
+
`payload.data.${key} must be a finite number`,
|
|
6101
|
+
);
|
|
6102
|
+
}
|
|
6103
|
+
}
|
|
6104
|
+
|
|
6105
|
+
if (data.jsonData !== undefined && !isPlainObject(data.jsonData)) {
|
|
6106
|
+
return invalidFromErrorFactory(
|
|
6107
|
+
errorFactory,
|
|
6108
|
+
"payload.data.jsonData must be an object when provided",
|
|
6109
|
+
);
|
|
6110
|
+
}
|
|
6111
|
+
|
|
6112
|
+
if (data.animations !== undefined) {
|
|
6113
|
+
const result = validateSpritesheetAnimationMap({
|
|
6114
|
+
animations: data.animations,
|
|
6115
|
+
path: "payload.data.animations",
|
|
6116
|
+
errorFactory,
|
|
6117
|
+
});
|
|
6118
|
+
if (result?.valid === false) {
|
|
6119
|
+
return result;
|
|
6120
|
+
}
|
|
6121
|
+
}
|
|
6122
|
+
};
|
|
6123
|
+
|
|
5462
6124
|
const validateSoundCreateData = ({ data, errorFactory }) => {
|
|
5463
6125
|
if (!isPlainObject(data)) {
|
|
5464
6126
|
return invalidFromErrorFactory(
|
|
@@ -7432,6 +8094,42 @@ const validateVisualElementReferenceTargets = ({
|
|
|
7432
8094
|
state,
|
|
7433
8095
|
errorFactory,
|
|
7434
8096
|
}) => {
|
|
8097
|
+
if (
|
|
8098
|
+
data.type === "spritesheet-animation" ||
|
|
8099
|
+
data.resourceId !== undefined ||
|
|
8100
|
+
data.animationName !== undefined
|
|
8101
|
+
) {
|
|
8102
|
+
const spritesheet = state.spritesheets?.items?.[data.resourceId];
|
|
8103
|
+
if (!isPlainObject(spritesheet) || spritesheet.type === "folder") {
|
|
8104
|
+
return invalidFromErrorFactory(
|
|
8105
|
+
errorFactory,
|
|
8106
|
+
`${ownerLabel} element resourceId must reference an existing non-folder spritesheet`,
|
|
8107
|
+
{
|
|
8108
|
+
[ownerIdField]: ownerId,
|
|
8109
|
+
elementId,
|
|
8110
|
+
field: "resourceId",
|
|
8111
|
+
targetId: data.resourceId,
|
|
8112
|
+
},
|
|
8113
|
+
);
|
|
8114
|
+
}
|
|
8115
|
+
|
|
8116
|
+
if (
|
|
8117
|
+
!isNonEmptyString(data.animationName) ||
|
|
8118
|
+
!isPlainObject(spritesheet.animations?.[data.animationName])
|
|
8119
|
+
) {
|
|
8120
|
+
return invalidFromErrorFactory(
|
|
8121
|
+
errorFactory,
|
|
8122
|
+
`${ownerLabel} element animationName must reference an existing spritesheet animation`,
|
|
8123
|
+
{
|
|
8124
|
+
[ownerIdField]: ownerId,
|
|
8125
|
+
elementId,
|
|
8126
|
+
field: "animationName",
|
|
8127
|
+
targetId: data.animationName,
|
|
8128
|
+
},
|
|
8129
|
+
);
|
|
8130
|
+
}
|
|
8131
|
+
}
|
|
8132
|
+
|
|
7435
8133
|
if (data.imageId !== undefined) {
|
|
7436
8134
|
const image = state.images.items[data.imageId];
|
|
7437
8135
|
if (!isPlainObject(image) || image.type === "folder") {
|
|
@@ -8245,6 +8943,30 @@ const findReferencedFileUsage = ({ state, fileId }) => {
|
|
|
8245
8943
|
}
|
|
8246
8944
|
}
|
|
8247
8945
|
|
|
8946
|
+
for (const [spritesheetId, spritesheet] of Object.entries(
|
|
8947
|
+
state.spritesheets.items,
|
|
8948
|
+
)) {
|
|
8949
|
+
if (spritesheet.type !== "spritesheet") {
|
|
8950
|
+
continue;
|
|
8951
|
+
}
|
|
8952
|
+
|
|
8953
|
+
if (spritesheet.fileId === fileId) {
|
|
8954
|
+
return {
|
|
8955
|
+
kind: "spritesheet",
|
|
8956
|
+
field: "fileId",
|
|
8957
|
+
ownerId: spritesheetId,
|
|
8958
|
+
};
|
|
8959
|
+
}
|
|
8960
|
+
|
|
8961
|
+
if (spritesheet.thumbnailFileId === fileId) {
|
|
8962
|
+
return {
|
|
8963
|
+
kind: "spritesheet",
|
|
8964
|
+
field: "thumbnailFileId",
|
|
8965
|
+
ownerId: spritesheetId,
|
|
8966
|
+
};
|
|
8967
|
+
}
|
|
8968
|
+
}
|
|
8969
|
+
|
|
8248
8970
|
for (const [soundId, sound] of Object.entries(state.sounds.items)) {
|
|
8249
8971
|
if (sound.type !== "sound") {
|
|
8250
8972
|
continue;
|
|
@@ -8438,6 +9160,108 @@ const COMMAND_DEFINITIONS = [
|
|
|
8438
9160
|
}
|
|
8439
9161
|
},
|
|
8440
9162
|
}),
|
|
9163
|
+
...createFolderedCollectionCommandDefinitions({
|
|
9164
|
+
familyName: "spritesheet",
|
|
9165
|
+
collectionKey: "spritesheets",
|
|
9166
|
+
idField: "spritesheetId",
|
|
9167
|
+
itemLabel: "spritesheet item",
|
|
9168
|
+
createDataValidator: validateSpritesheetCreateData,
|
|
9169
|
+
updateDataValidator: validateSpritesheetUpdateData,
|
|
9170
|
+
createItem: ({ payload }) => ({
|
|
9171
|
+
id: payload.spritesheetId,
|
|
9172
|
+
type: payload.data.type,
|
|
9173
|
+
name: payload.data.name,
|
|
9174
|
+
...(payload.data.description !== undefined
|
|
9175
|
+
? {
|
|
9176
|
+
description: payload.data.description,
|
|
9177
|
+
}
|
|
9178
|
+
: {}),
|
|
9179
|
+
...(payload.data.type === "spritesheet"
|
|
9180
|
+
? {
|
|
9181
|
+
fileId: payload.data.fileId,
|
|
9182
|
+
...(payload.data.thumbnailFileId !== undefined
|
|
9183
|
+
? {
|
|
9184
|
+
thumbnailFileId: payload.data.thumbnailFileId,
|
|
9185
|
+
}
|
|
9186
|
+
: {}),
|
|
9187
|
+
...(payload.data.fileType !== undefined
|
|
9188
|
+
? {
|
|
9189
|
+
fileType: payload.data.fileType,
|
|
9190
|
+
}
|
|
9191
|
+
: {}),
|
|
9192
|
+
...(payload.data.fileSize !== undefined
|
|
9193
|
+
? {
|
|
9194
|
+
fileSize: payload.data.fileSize,
|
|
9195
|
+
}
|
|
9196
|
+
: {}),
|
|
9197
|
+
...(payload.data.sheetWidth !== undefined
|
|
9198
|
+
? {
|
|
9199
|
+
sheetWidth: payload.data.sheetWidth,
|
|
9200
|
+
}
|
|
9201
|
+
: {}),
|
|
9202
|
+
...(payload.data.sheetHeight !== undefined
|
|
9203
|
+
? {
|
|
9204
|
+
sheetHeight: payload.data.sheetHeight,
|
|
9205
|
+
}
|
|
9206
|
+
: {}),
|
|
9207
|
+
...(payload.data.frameCount !== undefined
|
|
9208
|
+
? {
|
|
9209
|
+
frameCount: payload.data.frameCount,
|
|
9210
|
+
}
|
|
9211
|
+
: {}),
|
|
9212
|
+
...(payload.data.width !== undefined
|
|
9213
|
+
? {
|
|
9214
|
+
width: payload.data.width,
|
|
9215
|
+
}
|
|
9216
|
+
: {}),
|
|
9217
|
+
...(payload.data.height !== undefined
|
|
9218
|
+
? {
|
|
9219
|
+
height: payload.data.height,
|
|
9220
|
+
}
|
|
9221
|
+
: {}),
|
|
9222
|
+
jsonData: structuredClone(payload.data.jsonData),
|
|
9223
|
+
animations: structuredClone(payload.data.animations),
|
|
9224
|
+
}
|
|
9225
|
+
: {}),
|
|
9226
|
+
}),
|
|
9227
|
+
validateCreateState: ({ state, payload }) => {
|
|
9228
|
+
if (payload.data.type !== "spritesheet") {
|
|
9229
|
+
return;
|
|
9230
|
+
}
|
|
9231
|
+
|
|
9232
|
+
return validateReferencedFilesInData({
|
|
9233
|
+
state,
|
|
9234
|
+
data: payload.data,
|
|
9235
|
+
fields: ["fileId", "thumbnailFileId"],
|
|
9236
|
+
details: {
|
|
9237
|
+
spritesheetId: payload.spritesheetId,
|
|
9238
|
+
},
|
|
9239
|
+
});
|
|
9240
|
+
},
|
|
9241
|
+
validateUpdateState: ({ state, payload, currentItem }) => {
|
|
9242
|
+
if (
|
|
9243
|
+
currentItem.type === "folder" &&
|
|
9244
|
+
Object.keys(payload.data).some(
|
|
9245
|
+
(key) => key !== "name" && key !== "description",
|
|
9246
|
+
)
|
|
9247
|
+
) {
|
|
9248
|
+
return invalidPrecondition(
|
|
9249
|
+
"folder spritesheet items cannot update spritesheet fields",
|
|
9250
|
+
);
|
|
9251
|
+
}
|
|
9252
|
+
|
|
9253
|
+
if (currentItem.type === "spritesheet") {
|
|
9254
|
+
return validateReferencedFilesInData({
|
|
9255
|
+
state,
|
|
9256
|
+
data: payload.data,
|
|
9257
|
+
fields: ["fileId", "thumbnailFileId"],
|
|
9258
|
+
details: {
|
|
9259
|
+
spritesheetId: payload.spritesheetId,
|
|
9260
|
+
},
|
|
9261
|
+
});
|
|
9262
|
+
}
|
|
9263
|
+
},
|
|
9264
|
+
}),
|
|
8441
9265
|
{
|
|
8442
9266
|
type: "story.update",
|
|
8443
9267
|
validatePayload: ({ payload }) => {
|
|
@@ -14133,8 +14957,10 @@ export const validateAgainstState = ({ state, command }) => {
|
|
|
14133
14957
|
export const processCommand = ({ state, command }) => {
|
|
14134
14958
|
return captureValidation(() => {
|
|
14135
14959
|
const normalizedState = normalizeStateCollections(state);
|
|
14136
|
-
const
|
|
14137
|
-
typeof command?.type === "string" &&
|
|
14960
|
+
const shouldMaterializeNormalizedState =
|
|
14961
|
+
typeof command?.type === "string" &&
|
|
14962
|
+
(command.type.startsWith("control.") ||
|
|
14963
|
+
command.type.startsWith("spritesheet."));
|
|
14138
14964
|
|
|
14139
14965
|
if (!isPlainObject(command)) {
|
|
14140
14966
|
return invalidPrecondition("command must be an object");
|
|
@@ -14155,7 +14981,7 @@ export const processCommand = ({ state, command }) => {
|
|
|
14155
14981
|
|
|
14156
14982
|
const nextState = definition.reduce({
|
|
14157
14983
|
state: structuredClone(
|
|
14158
|
-
|
|
14984
|
+
shouldMaterializeNormalizedState ? normalizedState : state,
|
|
14159
14985
|
),
|
|
14160
14986
|
payload: command.payload,
|
|
14161
14987
|
});
|
|
@@ -14165,7 +14991,7 @@ export const processCommand = ({ state, command }) => {
|
|
|
14165
14991
|
|
|
14166
14992
|
const finalState =
|
|
14167
14993
|
nextState === undefined
|
|
14168
|
-
?
|
|
14994
|
+
? shouldMaterializeNormalizedState
|
|
14169
14995
|
? normalizedState
|
|
14170
14996
|
: state
|
|
14171
14997
|
: nextState;
|