@routevn/creator-model 1.3.4 → 1.3.7

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/model.js +383 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@routevn/creator-model",
3
- "version": "1.3.4",
3
+ "version": "1.3.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/model.js CHANGED
@@ -211,6 +211,17 @@ const LAYOUT_TYPE_KEYS = [
211
211
  ];
212
212
  const LAYOUT_ELEMENT_TEXT_STYLE_ALIGN_KEYS = ["left", "center", "right"];
213
213
  const LAYOUT_TEXT_REVEAL_EFFECT_KEYS = ["typewriter", "softWipe", "none"];
214
+ const CONTROL_KEYBOARD_KEYS = [
215
+ "enter",
216
+ "space",
217
+ "esc",
218
+ "ctrl",
219
+ "left",
220
+ "right",
221
+ "up",
222
+ "down",
223
+ ];
224
+ const CONTROL_KEYBOARD_KEY_SET = new Set(CONTROL_KEYBOARD_KEYS);
214
225
  const LAYOUT_ELEMENT_BASE_TYPES = [
215
226
  "folder",
216
227
  "container",
@@ -1010,10 +1021,7 @@ const validateSpritesheetAnimationMap = ({
1010
1021
  );
1011
1022
  }
1012
1023
 
1013
- if (
1014
- animation.fps !== undefined &&
1015
- !isPositiveFiniteNumber(animation.fps)
1016
- ) {
1024
+ if (animation.fps !== undefined && !isPositiveFiniteNumber(animation.fps)) {
1017
1025
  return invalidFromErrorFactory(
1018
1026
  errorFactory,
1019
1027
  `${animationPath}.fps must be a positive finite number when provided`,
@@ -2012,7 +2020,16 @@ const validateAnimationItems = ({ items, path, errorFactory }) => {
2012
2020
  allowedKeys:
2013
2021
  item.type === "folder"
2014
2022
  ? ["id", "type", "name", "description"]
2015
- : ["id", "type", "name", "description", "tagIds", "animation"],
2023
+ : [
2024
+ "id",
2025
+ "type",
2026
+ "name",
2027
+ "description",
2028
+ "tagIds",
2029
+ "thumbnailFileId",
2030
+ "preview",
2031
+ "animation",
2032
+ ],
2016
2033
  path: itemPath,
2017
2034
  errorFactory,
2018
2035
  });
@@ -2050,6 +2067,27 @@ const validateAnimationItems = ({ items, path, errorFactory }) => {
2050
2067
  }
2051
2068
 
2052
2069
  if (item.type === "animation") {
2070
+ if (
2071
+ item.thumbnailFileId !== undefined &&
2072
+ !isNonEmptyString(item.thumbnailFileId)
2073
+ ) {
2074
+ return invalidFromErrorFactory(
2075
+ errorFactory,
2076
+ `${itemPath}.thumbnailFileId must be a non-empty string when provided`,
2077
+ );
2078
+ }
2079
+
2080
+ {
2081
+ const result = validatePreviewObject({
2082
+ value: item.preview,
2083
+ path: `${itemPath}.preview`,
2084
+ errorFactory,
2085
+ });
2086
+ if (result?.valid === false) {
2087
+ return result;
2088
+ }
2089
+ }
2090
+
2053
2091
  {
2054
2092
  const result = validateOptionalUniqueIdArray({
2055
2093
  value: item.tagIds,
@@ -2526,6 +2564,85 @@ const validateVariableTypedValue = ({
2526
2564
  }
2527
2565
  };
2528
2566
 
2567
+ const normalizeVariableEnumValues = (values = []) => {
2568
+ const normalizedValues = Array.isArray(values) ? values : [];
2569
+ const seen = new Set();
2570
+ const result = [];
2571
+
2572
+ for (const value of normalizedValues) {
2573
+ const stringValue = String(value ?? "").trim();
2574
+ if (!stringValue || seen.has(stringValue)) {
2575
+ continue;
2576
+ }
2577
+
2578
+ seen.add(stringValue);
2579
+ result.push(stringValue);
2580
+ }
2581
+
2582
+ return result;
2583
+ };
2584
+
2585
+ const validateVariableEnumValues = ({ value, path, errorFactory }) => {
2586
+ if (value === undefined) {
2587
+ return VALID_RESULT;
2588
+ }
2589
+
2590
+ if (!Array.isArray(value)) {
2591
+ return invalidFromErrorFactory(
2592
+ errorFactory,
2593
+ `${path} must be an array when provided`,
2594
+ );
2595
+ }
2596
+
2597
+ for (const [index, enumValue] of value.entries()) {
2598
+ if (!isString(enumValue)) {
2599
+ return invalidFromErrorFactory(
2600
+ errorFactory,
2601
+ `${path}[${index}] must be a string`,
2602
+ );
2603
+ }
2604
+ }
2605
+
2606
+ return VALID_RESULT;
2607
+ };
2608
+
2609
+ const validateVariableEnumMetadata = ({
2610
+ data,
2611
+ variableType,
2612
+ path,
2613
+ errorFactory,
2614
+ }) => {
2615
+ if (data.isEnum !== undefined && typeof data.isEnum !== "boolean") {
2616
+ return invalidFromErrorFactory(
2617
+ errorFactory,
2618
+ `${path}.isEnum must be a boolean when provided`,
2619
+ );
2620
+ }
2621
+
2622
+ {
2623
+ const result = validateVariableEnumValues({
2624
+ value: data.enumValues,
2625
+ path: `${path}.enumValues`,
2626
+ errorFactory,
2627
+ });
2628
+ if (result?.valid === false) {
2629
+ return result;
2630
+ }
2631
+ }
2632
+
2633
+ if (
2634
+ variableType !== "string" &&
2635
+ (data.isEnum !== undefined || data.enumValues !== undefined)
2636
+ ) {
2637
+ return invalidFromErrorFactory(
2638
+ errorFactory,
2639
+ `${path}.isEnum and ${path}.enumValues are only supported for string variables`,
2640
+ );
2641
+ }
2642
+
2643
+ return VALID_RESULT;
2644
+ };
2645
+
2529
2646
  const validateVariableItems = ({ items, path, errorFactory }) => {
2530
2647
  for (const [itemId, item] of Object.entries(items)) {
2531
2648
  const itemPath = `${path}.${itemId}`;
@@ -2556,6 +2673,8 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
2556
2673
  "scope",
2557
2674
  "default",
2558
2675
  "value",
2676
+ "isEnum",
2677
+ "enumValues",
2559
2678
  ],
2560
2679
  path: itemPath,
2561
2680
  errorFactory,
@@ -2606,6 +2725,18 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
2606
2725
  }
2607
2726
  }
2608
2727
 
2728
+ {
2729
+ const result = validateVariableEnumMetadata({
2730
+ data: item,
2731
+ variableType,
2732
+ path: itemPath,
2733
+ errorFactory,
2734
+ });
2735
+ if (result?.valid === false) {
2736
+ return result;
2737
+ }
2738
+ }
2739
+
2609
2740
  if (!VARIABLE_SCOPE_KEYS.includes(item.scope)) {
2610
2741
  return invalidFromErrorFactory(
2611
2742
  errorFactory,
@@ -4261,6 +4392,44 @@ const applyCharacterUpdate = ({ currentItem, data }) => {
4261
4392
  return nextItem;
4262
4393
  };
4263
4394
 
4395
+ const applyVariableEnumMetadata = ({ item, data }) => {
4396
+ if (!isPlainObject(item)) {
4397
+ return item;
4398
+ }
4399
+
4400
+ const enumEnabled =
4401
+ item.type === "string" &&
4402
+ data.isEnum !== false &&
4403
+ (data.isEnum === true ||
4404
+ data.enumValues !== undefined ||
4405
+ (data.isEnum === undefined && item.isEnum === true));
4406
+
4407
+ if (!enumEnabled) {
4408
+ delete item.isEnum;
4409
+ delete item.enumValues;
4410
+ return item;
4411
+ }
4412
+
4413
+ item.isEnum = true;
4414
+ item.enumValues = normalizeVariableEnumValues(
4415
+ data.enumValues ?? item.enumValues,
4416
+ );
4417
+
4418
+ return item;
4419
+ };
4420
+
4421
+ const applyVariableUpdate = ({ currentItem, data }) => {
4422
+ const nextItem = applyTagIdsUpdate({
4423
+ currentItem,
4424
+ data,
4425
+ });
4426
+
4427
+ return applyVariableEnumMetadata({
4428
+ item: nextItem,
4429
+ data,
4430
+ });
4431
+ };
4432
+
4264
4433
  const stripDeletedTagIdsFromItem = ({ item, deletedTagIds }) => {
4265
4434
  if (!Array.isArray(item?.tagIds) || item.tagIds.length === 0) {
4266
4435
  return;
@@ -4512,6 +4681,13 @@ const validateKeyboardMap = ({ value, path, errorFactory }) => {
4512
4681
  );
4513
4682
  }
4514
4683
 
4684
+ if (!CONTROL_KEYBOARD_KEY_SET.has(key)) {
4685
+ return invalidFromErrorFactory(
4686
+ errorFactory,
4687
+ `${path}.${key} must be one of: ${CONTROL_KEYBOARD_KEYS.map((value) => `'${value}'`).join(", ")}`,
4688
+ );
4689
+ }
4690
+
4515
4691
  if (!isPlainObject(interaction)) {
4516
4692
  return invalidFromErrorFactory(
4517
4693
  errorFactory,
@@ -4697,6 +4873,7 @@ const validateControlItems = ({ items, path, errorFactory }) => {
4697
4873
  "preview",
4698
4874
  "elements",
4699
4875
  "keyboard",
4876
+ "keyup",
4700
4877
  ],
4701
4878
  path: itemPath,
4702
4879
  errorFactory,
@@ -4791,6 +4968,17 @@ const validateControlItems = ({ items, path, errorFactory }) => {
4791
4968
  return result;
4792
4969
  }
4793
4970
  }
4971
+
4972
+ {
4973
+ const result = validateKeyboardMap({
4974
+ value: item.keyup,
4975
+ path: `${itemPath}.keyup`,
4976
+ errorFactory,
4977
+ });
4978
+ if (result?.valid === false) {
4979
+ return result;
4980
+ }
4981
+ }
4794
4982
  }
4795
4983
  }
4796
4984
  };
@@ -6073,6 +6261,19 @@ export const assertInvariants = ({ state }) => {
6073
6261
  }
6074
6262
  }
6075
6263
 
6264
+ if (animation.thumbnailFileId !== undefined) {
6265
+ const fileResult = validateFileReference({
6266
+ state,
6267
+ fileId: animation.thumbnailFileId,
6268
+ path: "animation.thumbnailFileId",
6269
+ details: { animationId, thumbnailFileId: animation.thumbnailFileId },
6270
+ errorFactory: createInvariantValidationError,
6271
+ });
6272
+ if (!fileResult.valid) {
6273
+ return fileResult;
6274
+ }
6275
+ }
6276
+
6076
6277
  const result = validateAnimationMaskImageReferences({
6077
6278
  state,
6078
6279
  animation: animation.animation,
@@ -8575,7 +8776,15 @@ const validateAnimationCreateData = ({ data, errorFactory }) => {
8575
8776
  allowedKeys:
8576
8777
  data.type === "folder"
8577
8778
  ? ["type", "name", "description"]
8578
- : ["type", "name", "description", "tagIds", "animation"],
8779
+ : [
8780
+ "type",
8781
+ "name",
8782
+ "description",
8783
+ "tagIds",
8784
+ "thumbnailFileId",
8785
+ "preview",
8786
+ "animation",
8787
+ ],
8579
8788
  path: "payload.data",
8580
8789
  errorFactory,
8581
8790
  });
@@ -8599,6 +8808,27 @@ const validateAnimationCreateData = ({ data, errorFactory }) => {
8599
8808
  }
8600
8809
 
8601
8810
  if (data.type === "animation") {
8811
+ if (
8812
+ data.thumbnailFileId !== undefined &&
8813
+ !isNonEmptyString(data.thumbnailFileId)
8814
+ ) {
8815
+ return invalidFromErrorFactory(
8816
+ errorFactory,
8817
+ "payload.data.thumbnailFileId must be a non-empty string when provided",
8818
+ );
8819
+ }
8820
+
8821
+ {
8822
+ const result = validatePreviewObject({
8823
+ value: data.preview,
8824
+ path: "payload.data.preview",
8825
+ errorFactory,
8826
+ });
8827
+ if (result?.valid === false) {
8828
+ return result;
8829
+ }
8830
+ }
8831
+
8602
8832
  {
8603
8833
  const result = validateOptionalUniqueIdArray({
8604
8834
  value: data.tagIds,
@@ -8627,7 +8857,14 @@ const validateAnimationUpdateData = ({ data, errorFactory }) => {
8627
8857
  {
8628
8858
  const result = validateAllowedKeys({
8629
8859
  value: data,
8630
- allowedKeys: ["name", "description", "tagIds", "animation"],
8860
+ allowedKeys: [
8861
+ "name",
8862
+ "description",
8863
+ "tagIds",
8864
+ "thumbnailFileId",
8865
+ "preview",
8866
+ "animation",
8867
+ ],
8631
8868
  path: "payload.data",
8632
8869
  errorFactory,
8633
8870
  });
@@ -8657,6 +8894,27 @@ const validateAnimationUpdateData = ({ data, errorFactory }) => {
8657
8894
  );
8658
8895
  }
8659
8896
 
8897
+ if (
8898
+ data.thumbnailFileId !== undefined &&
8899
+ !isNonEmptyString(data.thumbnailFileId)
8900
+ ) {
8901
+ return invalidFromErrorFactory(
8902
+ errorFactory,
8903
+ "payload.data.thumbnailFileId must be a non-empty string when provided",
8904
+ );
8905
+ }
8906
+
8907
+ {
8908
+ const result = validatePreviewObject({
8909
+ value: data.preview,
8910
+ path: "payload.data.preview",
8911
+ errorFactory,
8912
+ });
8913
+ if (result?.valid === false) {
8914
+ return result;
8915
+ }
8916
+ }
8917
+
8660
8918
  {
8661
8919
  const result = validateOptionalUniqueIdArray({
8662
8920
  value: data.tagIds,
@@ -9097,6 +9355,8 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
9097
9355
  "scope",
9098
9356
  "default",
9099
9357
  "value",
9358
+ "isEnum",
9359
+ "enumValues",
9100
9360
  ],
9101
9361
  path: "payload.data",
9102
9362
  errorFactory,
@@ -9133,6 +9393,18 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
9133
9393
  }
9134
9394
  }
9135
9395
 
9396
+ {
9397
+ const result = validateVariableEnumMetadata({
9398
+ data,
9399
+ variableType: data.type,
9400
+ path: "payload.data",
9401
+ errorFactory,
9402
+ });
9403
+ if (result?.valid === false) {
9404
+ return result;
9405
+ }
9406
+ }
9407
+
9136
9408
  if (!VARIABLE_SCOPE_KEYS.includes(data.scope)) {
9137
9409
  return invalidFromErrorFactory(
9138
9410
  errorFactory,
@@ -9176,6 +9448,8 @@ const validateVariableUpdateData = ({ data, errorFactory }) => {
9176
9448
  "scope",
9177
9449
  "default",
9178
9450
  "value",
9451
+ "isEnum",
9452
+ "enumValues",
9179
9453
  ],
9180
9454
  path: "payload.data",
9181
9455
  errorFactory,
@@ -9206,6 +9480,24 @@ const validateVariableUpdateData = ({ data, errorFactory }) => {
9206
9480
  );
9207
9481
  }
9208
9482
 
9483
+ if (data.isEnum !== undefined && typeof data.isEnum !== "boolean") {
9484
+ return invalidFromErrorFactory(
9485
+ errorFactory,
9486
+ "payload.data.isEnum must be a boolean when provided",
9487
+ );
9488
+ }
9489
+
9490
+ {
9491
+ const result = validateVariableEnumValues({
9492
+ value: data.enumValues,
9493
+ path: "payload.data.enumValues",
9494
+ errorFactory,
9495
+ });
9496
+ if (result?.valid === false) {
9497
+ return result;
9498
+ }
9499
+ }
9500
+
9209
9501
  if (data.scope !== undefined && !VARIABLE_SCOPE_KEYS.includes(data.scope)) {
9210
9502
  return invalidFromErrorFactory(
9211
9503
  errorFactory,
@@ -10054,6 +10346,7 @@ const validateControlCreateData = ({ data, errorFactory }) => {
10054
10346
  "preview",
10055
10347
  "elements",
10056
10348
  "keyboard",
10349
+ "keyup",
10057
10350
  ],
10058
10351
  path: "payload.data",
10059
10352
  errorFactory,
@@ -10134,6 +10427,17 @@ const validateControlCreateData = ({ data, errorFactory }) => {
10134
10427
  return result;
10135
10428
  }
10136
10429
  }
10430
+
10431
+ {
10432
+ const result = validateKeyboardMap({
10433
+ value: data.keyup,
10434
+ path: "payload.data.keyup",
10435
+ errorFactory,
10436
+ });
10437
+ if (result?.valid === false) {
10438
+ return result;
10439
+ }
10440
+ }
10137
10441
  }
10138
10442
  };
10139
10443
 
@@ -10146,6 +10450,7 @@ const validateControlUpdateData = ({ data, errorFactory }) => {
10146
10450
  "description",
10147
10451
  "tagIds",
10148
10452
  "keyboard",
10453
+ "keyup",
10149
10454
  "thumbnailFileId",
10150
10455
  "preview",
10151
10456
  ],
@@ -10210,6 +10515,17 @@ const validateControlUpdateData = ({ data, errorFactory }) => {
10210
10515
  }
10211
10516
  }
10212
10517
 
10518
+ {
10519
+ const result = validateKeyboardMap({
10520
+ value: data.keyup,
10521
+ path: "payload.data.keyup",
10522
+ errorFactory,
10523
+ });
10524
+ if (result?.valid === false) {
10525
+ return result;
10526
+ }
10527
+ }
10528
+
10213
10529
  {
10214
10530
  const result = validateOptionalUniqueIdArray({
10215
10531
  value: data.tagIds,
@@ -11278,6 +11594,22 @@ const findReferencedFileUsage = ({ state, fileId }) => {
11278
11594
  }
11279
11595
  }
11280
11596
 
11597
+ for (const [animationId, animation] of Object.entries(
11598
+ state.animations.items,
11599
+ )) {
11600
+ if (animation.type !== "animation") {
11601
+ continue;
11602
+ }
11603
+
11604
+ if (animation.thumbnailFileId === fileId) {
11605
+ return {
11606
+ kind: "animation",
11607
+ field: "thumbnailFileId",
11608
+ ownerId: animationId,
11609
+ };
11610
+ }
11611
+ }
11612
+
11281
11613
  for (const [characterId, character] of Object.entries(
11282
11614
  state.characters.items,
11283
11615
  )) {
@@ -14237,6 +14569,16 @@ const COMMAND_DEFINITIONS = [
14237
14569
  return result;
14238
14570
  }
14239
14571
 
14572
+ const fileResult = validateReferencedFilesInData({
14573
+ state,
14574
+ data: payload.data,
14575
+ fields: ["thumbnailFileId"],
14576
+ details: { animationId: payload.animationId },
14577
+ });
14578
+ if (!fileResult.valid) {
14579
+ return fileResult;
14580
+ }
14581
+
14240
14582
  return validateTagIdsAgainstScope({
14241
14583
  state,
14242
14584
  tagIds: payload.data.tagIds,
@@ -14264,6 +14606,12 @@ const COMMAND_DEFINITIONS = [
14264
14606
  target: nextAnimation,
14265
14607
  tagIds: payload.data.tagIds,
14266
14608
  });
14609
+ if (payload.data.thumbnailFileId !== undefined) {
14610
+ nextAnimation.thumbnailFileId = payload.data.thumbnailFileId;
14611
+ }
14612
+ if (payload.data.preview !== undefined) {
14613
+ nextAnimation.preview = structuredClone(payload.data.preview);
14614
+ }
14267
14615
  nextAnimation.animation = structuredClone(payload.data.animation);
14268
14616
  }
14269
14617
 
@@ -14346,6 +14694,16 @@ const COMMAND_DEFINITIONS = [
14346
14694
  }
14347
14695
  }
14348
14696
 
14697
+ const fileResult = validateReferencedFilesInData({
14698
+ state,
14699
+ data: payload.data,
14700
+ fields: ["thumbnailFileId"],
14701
+ details: { animationId: payload.animationId },
14702
+ });
14703
+ if (!fileResult.valid) {
14704
+ return fileResult;
14705
+ }
14706
+
14349
14707
  return validateTagIdsAgainstScope({
14350
14708
  state,
14351
14709
  tagIds: payload.data.tagIds,
@@ -15614,13 +15972,16 @@ const COMMAND_DEFINITIONS = [
15614
15972
  delete data.tagIds;
15615
15973
  }
15616
15974
 
15617
- return {
15618
- id: payload.variableId,
15619
- ...data,
15620
- };
15975
+ return applyVariableEnumMetadata({
15976
+ item: {
15977
+ id: payload.variableId,
15978
+ ...data,
15979
+ },
15980
+ data,
15981
+ });
15621
15982
  },
15622
15983
  updateItem: ({ currentItem, payload }) =>
15623
- applyTagIdsUpdate({
15984
+ applyVariableUpdate({
15624
15985
  currentItem,
15625
15986
  data: payload.data,
15626
15987
  }),
@@ -15651,6 +16012,16 @@ const COMMAND_DEFINITIONS = [
15651
16012
  );
15652
16013
  }
15653
16014
 
16015
+ if (
16016
+ currentItem.type !== "string" &&
16017
+ (payload.data.isEnum !== undefined ||
16018
+ payload.data.enumValues !== undefined)
16019
+ ) {
16020
+ return invalidPrecondition(
16021
+ "variable enum fields can only update string variables",
16022
+ );
16023
+ }
16024
+
15654
16025
  if (currentItem.type !== "folder") {
15655
16026
  {
15656
16027
  const result = validateTagIdsAgainstScope({