@routevn/creator-model 1.3.5 → 1.4.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/model.js +645 -24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@routevn/creator-model",
3
- "version": "1.3.5",
3
+ "version": "1.4.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/model.js CHANGED
@@ -249,7 +249,7 @@ const LAYOUT_ELEMENT_BASE_TYPES = [
249
249
  "container-ref-confirm-dialog-ok",
250
250
  "container-ref-confirm-dialog-cancel",
251
251
  ];
252
- export const SCHEMA_VERSION = 3;
252
+ export const SCHEMA_VERSION = 4;
253
253
  const LAYOUT_CONTAINER_ELEMENT_TYPES = [
254
254
  "folder",
255
255
  "container",
@@ -2020,7 +2020,16 @@ const validateAnimationItems = ({ items, path, errorFactory }) => {
2020
2020
  allowedKeys:
2021
2021
  item.type === "folder"
2022
2022
  ? ["id", "type", "name", "description"]
2023
- : ["id", "type", "name", "description", "tagIds", "animation"],
2023
+ : [
2024
+ "id",
2025
+ "type",
2026
+ "name",
2027
+ "description",
2028
+ "tagIds",
2029
+ "thumbnailFileId",
2030
+ "preview",
2031
+ "animation",
2032
+ ],
2024
2033
  path: itemPath,
2025
2034
  errorFactory,
2026
2035
  });
@@ -2058,6 +2067,27 @@ const validateAnimationItems = ({ items, path, errorFactory }) => {
2058
2067
  }
2059
2068
 
2060
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
+
2061
2091
  {
2062
2092
  const result = validateOptionalUniqueIdArray({
2063
2093
  value: item.tagIds,
@@ -2283,6 +2313,9 @@ const validateTransformItems = ({ items, path, errorFactory }) => {
2283
2313
  "anchorX",
2284
2314
  "anchorY",
2285
2315
  "rotation",
2316
+ "thumbnailFileId",
2317
+ "previewFileId",
2318
+ "preview",
2286
2319
  ],
2287
2320
  path: itemPath,
2288
2321
  errorFactory,
@@ -2321,6 +2354,27 @@ const validateTransformItems = ({ items, path, errorFactory }) => {
2321
2354
  }
2322
2355
 
2323
2356
  if (item.type === "transform") {
2357
+ for (const fieldName of ["thumbnailFileId", "previewFileId"]) {
2358
+ const fileId = item[fieldName];
2359
+ if (fileId !== undefined && !isNonEmptyString(fileId)) {
2360
+ return invalidFromErrorFactory(
2361
+ errorFactory,
2362
+ `${itemPath}.${fieldName} must be a non-empty string when provided`,
2363
+ );
2364
+ }
2365
+ }
2366
+
2367
+ {
2368
+ const result = validateTransformPreviewObject({
2369
+ value: item.preview,
2370
+ path: `${itemPath}.preview`,
2371
+ errorFactory,
2372
+ });
2373
+ if (result?.valid === false) {
2374
+ return result;
2375
+ }
2376
+ }
2377
+
2324
2378
  {
2325
2379
  const result = validateOptionalUniqueIdArray({
2326
2380
  value: item.tagIds,
@@ -2534,18 +2588,95 @@ const validateVariableTypedValue = ({
2534
2588
  }
2535
2589
  };
2536
2590
 
2591
+ const normalizeVariableEnumValues = (values = []) => {
2592
+ const normalizedValues = Array.isArray(values) ? values : [];
2593
+ const seen = new Set();
2594
+ const result = [];
2595
+
2596
+ for (const value of normalizedValues) {
2597
+ const stringValue = String(value ?? "").trim();
2598
+ if (!stringValue || seen.has(stringValue)) {
2599
+ continue;
2600
+ }
2601
+
2602
+ seen.add(stringValue);
2603
+ result.push(stringValue);
2604
+ }
2605
+
2606
+ return result;
2607
+ };
2608
+
2609
+ const validateVariableEnumValues = ({ value, path, errorFactory }) => {
2610
+ if (value === undefined) {
2611
+ return VALID_RESULT;
2612
+ }
2613
+
2614
+ if (!Array.isArray(value)) {
2615
+ return invalidFromErrorFactory(
2616
+ errorFactory,
2617
+ `${path} must be an array when provided`,
2618
+ );
2619
+ }
2620
+
2621
+ for (const [index, enumValue] of value.entries()) {
2622
+ if (!isString(enumValue)) {
2623
+ return invalidFromErrorFactory(
2624
+ errorFactory,
2625
+ `${path}[${index}] must be a string`,
2626
+ );
2627
+ }
2628
+ }
2629
+
2630
+ return VALID_RESULT;
2631
+ };
2632
+
2633
+ const validateVariableEnumMetadata = ({
2634
+ data,
2635
+ variableType,
2636
+ path,
2637
+ errorFactory,
2638
+ }) => {
2639
+ if (data.isEnum !== undefined && typeof data.isEnum !== "boolean") {
2640
+ return invalidFromErrorFactory(
2641
+ errorFactory,
2642
+ `${path}.isEnum must be a boolean when provided`,
2643
+ );
2644
+ }
2645
+
2646
+ {
2647
+ const result = validateVariableEnumValues({
2648
+ value: data.enumValues,
2649
+ path: `${path}.enumValues`,
2650
+ errorFactory,
2651
+ });
2652
+ if (result?.valid === false) {
2653
+ return result;
2654
+ }
2655
+ }
2656
+
2657
+ if (
2658
+ variableType !== "string" &&
2659
+ (data.isEnum !== undefined || data.enumValues !== undefined)
2660
+ ) {
2661
+ return invalidFromErrorFactory(
2662
+ errorFactory,
2663
+ `${path}.isEnum and ${path}.enumValues are only supported for string variables`,
2664
+ );
2665
+ }
2666
+
2667
+ return VALID_RESULT;
2668
+ };
2669
+
2537
2670
  const validateVariableItems = ({ items, path, errorFactory }) => {
2538
2671
  for (const [itemId, item] of Object.entries(items)) {
2539
2672
  const itemPath = `${path}.${itemId}`;
2540
- const variableType = item?.type;
2673
+ const itemType = item?.type;
2674
+ const variableType = item?.variableType;
2541
2675
 
2542
- if (
2543
- variableType !== "folder" &&
2544
- !VARIABLE_TYPE_KEYS.includes(variableType)
2545
- ) {
2676
+ if (itemType !== "folder" && itemType !== "variable") {
2546
2677
  return invalidFromErrorFactory(
2547
2678
  errorFactory,
2548
- `${itemPath}.type must be 'folder', 'string', 'number', or 'boolean'`,
2679
+ `${itemPath}.type must be 'folder' or 'variable'`,
2549
2680
  );
2550
2681
  }
2551
2682
 
@@ -2553,17 +2684,20 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
2553
2684
  const result = validateAllowedKeys({
2554
2685
  value: item,
2555
2686
  allowedKeys:
2556
- variableType === "folder"
2687
+ itemType === "folder"
2557
2688
  ? ["id", "type", "name", "description"]
2558
2689
  : [
2559
2690
  "id",
2560
2691
  "type",
2692
+ "variableType",
2561
2693
  "name",
2562
2694
  "description",
2563
2695
  "tagIds",
2564
2696
  "scope",
2565
2697
  "default",
2566
2698
  "value",
2699
+ "isEnum",
2700
+ "enumValues",
2567
2701
  ],
2568
2702
  path: itemPath,
2569
2703
  errorFactory,
@@ -2601,7 +2735,14 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
2601
2735
  );
2602
2736
  }
2603
2737
 
2604
- if (variableType !== "folder") {
2738
+ if (itemType === "variable") {
2739
+ if (!VARIABLE_TYPE_KEYS.includes(variableType)) {
2740
+ return invalidFromErrorFactory(
2741
+ errorFactory,
2742
+ `${itemPath}.variableType must be 'string', 'number', or 'boolean'`,
2743
+ );
2744
+ }
2745
+
2605
2746
  {
2606
2747
  const result = validateOptionalUniqueIdArray({
2607
2748
  value: item.tagIds,
@@ -2614,6 +2755,18 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
2614
2755
  }
2615
2756
  }
2616
2757
 
2758
+ {
2759
+ const result = validateVariableEnumMetadata({
2760
+ data: item,
2761
+ variableType,
2762
+ path: itemPath,
2763
+ errorFactory,
2764
+ });
2765
+ if (result?.valid === false) {
2766
+ return result;
2767
+ }
2768
+ }
2769
+
2617
2770
  if (!VARIABLE_SCOPE_KEYS.includes(item.scope)) {
2618
2771
  return invalidFromErrorFactory(
2619
2772
  errorFactory,
@@ -4269,6 +4422,44 @@ const applyCharacterUpdate = ({ currentItem, data }) => {
4269
4422
  return nextItem;
4270
4423
  };
4271
4424
 
4425
+ const applyVariableEnumMetadata = ({ item, data }) => {
4426
+ if (!isPlainObject(item)) {
4427
+ return item;
4428
+ }
4429
+
4430
+ const enumEnabled =
4431
+ item.variableType === "string" &&
4432
+ data.isEnum !== false &&
4433
+ (data.isEnum === true ||
4434
+ data.enumValues !== undefined ||
4435
+ (data.isEnum === undefined && item.isEnum === true));
4436
+
4437
+ if (!enumEnabled) {
4438
+ delete item.isEnum;
4439
+ delete item.enumValues;
4440
+ return item;
4441
+ }
4442
+
4443
+ item.isEnum = true;
4444
+ item.enumValues = normalizeVariableEnumValues(
4445
+ data.enumValues ?? item.enumValues,
4446
+ );
4447
+
4448
+ return item;
4449
+ };
4450
+
4451
+ const applyVariableUpdate = ({ currentItem, data }) => {
4452
+ const nextItem = applyTagIdsUpdate({
4453
+ currentItem,
4454
+ data,
4455
+ });
4456
+
4457
+ return applyVariableEnumMetadata({
4458
+ item: nextItem,
4459
+ data,
4460
+ });
4461
+ };
4462
+
4272
4463
  const stripDeletedTagIdsFromItem = ({ item, deletedTagIds }) => {
4273
4464
  if (!Array.isArray(item?.tagIds) || item.tagIds.length === 0) {
4274
4465
  return;
@@ -4553,6 +4744,78 @@ const validatePreviewObject = ({ value, path, errorFactory }) => {
4553
4744
  return VALID_RESULT;
4554
4745
  };
4555
4746
 
4747
+ const validateTransformPreviewSlot = ({ value, path, errorFactory }) => {
4748
+ if (value === undefined) {
4749
+ return VALID_RESULT;
4750
+ }
4751
+
4752
+ if (!isPlainObject(value)) {
4753
+ return invalidFromErrorFactory(
4754
+ errorFactory,
4755
+ `${path} must be an object when provided`,
4756
+ );
4757
+ }
4758
+
4759
+ {
4760
+ const result = validateAllowedKeys({
4761
+ value,
4762
+ allowedKeys: ["imageId"],
4763
+ path,
4764
+ errorFactory,
4765
+ });
4766
+ if (result?.valid === false) {
4767
+ return result;
4768
+ }
4769
+ }
4770
+
4771
+ if (value.imageId !== undefined && !isNonEmptyString(value.imageId)) {
4772
+ return invalidFromErrorFactory(
4773
+ errorFactory,
4774
+ `${path}.imageId must be a non-empty string when provided`,
4775
+ );
4776
+ }
4777
+
4778
+ return VALID_RESULT;
4779
+ };
4780
+
4781
+ const validateTransformPreviewObject = ({ value, path, errorFactory }) => {
4782
+ if (value === undefined) {
4783
+ return VALID_RESULT;
4784
+ }
4785
+
4786
+ if (!isPlainObject(value)) {
4787
+ return invalidFromErrorFactory(
4788
+ errorFactory,
4789
+ `${path} must be an object when provided`,
4790
+ );
4791
+ }
4792
+
4793
+ {
4794
+ const result = validateAllowedKeys({
4795
+ value,
4796
+ allowedKeys: ["background", "target"],
4797
+ path,
4798
+ errorFactory,
4799
+ });
4800
+ if (result?.valid === false) {
4801
+ return result;
4802
+ }
4803
+ }
4804
+
4805
+ for (const key of ["background", "target"]) {
4806
+ const result = validateTransformPreviewSlot({
4807
+ value: value[key],
4808
+ path: `${path}.${key}`,
4809
+ errorFactory,
4810
+ });
4811
+ if (result?.valid === false) {
4812
+ return result;
4813
+ }
4814
+ }
4815
+
4816
+ return VALID_RESULT;
4817
+ };
4818
+
4556
4819
  const validateLayoutItems = ({ items, path, errorFactory }) => {
4557
4820
  for (const [itemId, item] of Object.entries(items)) {
4558
4821
  const itemPath = `${path}.${itemId}`;
@@ -5731,6 +5994,38 @@ const validateAnimationMaskImageReferences = ({
5731
5994
  return VALID_RESULT;
5732
5995
  };
5733
5996
 
5997
+ const validateTransformPreviewImageReferences = ({
5998
+ state,
5999
+ preview,
6000
+ path,
6001
+ details = {},
6002
+ errorFactory = createPreconditionValidationError,
6003
+ }) => {
6004
+ if (!isPlainObject(preview)) {
6005
+ return VALID_RESULT;
6006
+ }
6007
+
6008
+ for (const slotKey of ["background", "target"]) {
6009
+ const imageId = preview[slotKey]?.imageId;
6010
+ const result = validateImageReference({
6011
+ state,
6012
+ imageId,
6013
+ path: `${path}.${slotKey}.imageId`,
6014
+ details: {
6015
+ ...details,
6016
+ slot: slotKey,
6017
+ imageId,
6018
+ },
6019
+ errorFactory,
6020
+ });
6021
+ if (!result.valid) {
6022
+ return result;
6023
+ }
6024
+ }
6025
+
6026
+ return VALID_RESULT;
6027
+ };
6028
+
5734
6029
  export const assertInvariants = ({ state }) => {
5735
6030
  if (!isPlainObject(state)) {
5736
6031
  return invalidInvariant("state must be an object");
@@ -6100,6 +6395,19 @@ export const assertInvariants = ({ state }) => {
6100
6395
  }
6101
6396
  }
6102
6397
 
6398
+ if (animation.thumbnailFileId !== undefined) {
6399
+ const fileResult = validateFileReference({
6400
+ state,
6401
+ fileId: animation.thumbnailFileId,
6402
+ path: "animation.thumbnailFileId",
6403
+ details: { animationId, thumbnailFileId: animation.thumbnailFileId },
6404
+ errorFactory: createInvariantValidationError,
6405
+ });
6406
+ if (!fileResult.valid) {
6407
+ return fileResult;
6408
+ }
6409
+ }
6410
+
6103
6411
  const result = validateAnimationMaskImageReferences({
6104
6412
  state,
6105
6413
  animation: animation.animation,
@@ -6227,6 +6535,42 @@ export const assertInvariants = ({ state }) => {
6227
6535
  continue;
6228
6536
  }
6229
6537
 
6538
+ for (const fieldName of ["thumbnailFileId", "previewFileId"]) {
6539
+ const fileId = transform[fieldName];
6540
+ if (fileId === undefined) {
6541
+ continue;
6542
+ }
6543
+
6544
+ const fileResult = validateFileReference({
6545
+ state,
6546
+ fileId,
6547
+ path: `transform.${fieldName}`,
6548
+ details: {
6549
+ transformId,
6550
+ [fieldName]: fileId,
6551
+ },
6552
+ errorFactory: createInvariantValidationError,
6553
+ });
6554
+ if (!fileResult.valid) {
6555
+ return fileResult;
6556
+ }
6557
+ }
6558
+
6559
+ {
6560
+ const previewResult = validateTransformPreviewImageReferences({
6561
+ state,
6562
+ preview: transform.preview,
6563
+ path: "transform.preview",
6564
+ details: {
6565
+ transformId,
6566
+ },
6567
+ errorFactory: createInvariantValidationError,
6568
+ });
6569
+ if (!previewResult.valid) {
6570
+ return previewResult;
6571
+ }
6572
+ }
6573
+
6230
6574
  {
6231
6575
  const result = validateTagIdsAgainstScope({
6232
6576
  state,
@@ -8602,7 +8946,15 @@ const validateAnimationCreateData = ({ data, errorFactory }) => {
8602
8946
  allowedKeys:
8603
8947
  data.type === "folder"
8604
8948
  ? ["type", "name", "description"]
8605
- : ["type", "name", "description", "tagIds", "animation"],
8949
+ : [
8950
+ "type",
8951
+ "name",
8952
+ "description",
8953
+ "tagIds",
8954
+ "thumbnailFileId",
8955
+ "preview",
8956
+ "animation",
8957
+ ],
8606
8958
  path: "payload.data",
8607
8959
  errorFactory,
8608
8960
  });
@@ -8626,6 +8978,27 @@ const validateAnimationCreateData = ({ data, errorFactory }) => {
8626
8978
  }
8627
8979
 
8628
8980
  if (data.type === "animation") {
8981
+ if (
8982
+ data.thumbnailFileId !== undefined &&
8983
+ !isNonEmptyString(data.thumbnailFileId)
8984
+ ) {
8985
+ return invalidFromErrorFactory(
8986
+ errorFactory,
8987
+ "payload.data.thumbnailFileId must be a non-empty string when provided",
8988
+ );
8989
+ }
8990
+
8991
+ {
8992
+ const result = validatePreviewObject({
8993
+ value: data.preview,
8994
+ path: "payload.data.preview",
8995
+ errorFactory,
8996
+ });
8997
+ if (result?.valid === false) {
8998
+ return result;
8999
+ }
9000
+ }
9001
+
8629
9002
  {
8630
9003
  const result = validateOptionalUniqueIdArray({
8631
9004
  value: data.tagIds,
@@ -8654,7 +9027,14 @@ const validateAnimationUpdateData = ({ data, errorFactory }) => {
8654
9027
  {
8655
9028
  const result = validateAllowedKeys({
8656
9029
  value: data,
8657
- allowedKeys: ["name", "description", "tagIds", "animation"],
9030
+ allowedKeys: [
9031
+ "name",
9032
+ "description",
9033
+ "tagIds",
9034
+ "thumbnailFileId",
9035
+ "preview",
9036
+ "animation",
9037
+ ],
8658
9038
  path: "payload.data",
8659
9039
  errorFactory,
8660
9040
  });
@@ -8684,6 +9064,27 @@ const validateAnimationUpdateData = ({ data, errorFactory }) => {
8684
9064
  );
8685
9065
  }
8686
9066
 
9067
+ if (
9068
+ data.thumbnailFileId !== undefined &&
9069
+ !isNonEmptyString(data.thumbnailFileId)
9070
+ ) {
9071
+ return invalidFromErrorFactory(
9072
+ errorFactory,
9073
+ "payload.data.thumbnailFileId must be a non-empty string when provided",
9074
+ );
9075
+ }
9076
+
9077
+ {
9078
+ const result = validatePreviewObject({
9079
+ value: data.preview,
9080
+ path: "payload.data.preview",
9081
+ errorFactory,
9082
+ });
9083
+ if (result?.valid === false) {
9084
+ return result;
9085
+ }
9086
+ }
9087
+
8687
9088
  {
8688
9089
  const result = validateOptionalUniqueIdArray({
8689
9090
  value: data.tagIds,
@@ -8742,6 +9143,9 @@ const validateTransformCreateData = ({ data, errorFactory }) => {
8742
9143
  "anchorX",
8743
9144
  "anchorY",
8744
9145
  "rotation",
9146
+ "thumbnailFileId",
9147
+ "previewFileId",
9148
+ "preview",
8745
9149
  ],
8746
9150
  path: "payload.data",
8747
9151
  errorFactory,
@@ -8766,6 +9170,27 @@ const validateTransformCreateData = ({ data, errorFactory }) => {
8766
9170
  }
8767
9171
 
8768
9172
  if (data.type === "transform") {
9173
+ for (const fieldName of ["thumbnailFileId", "previewFileId"]) {
9174
+ const fileId = data[fieldName];
9175
+ if (fileId !== undefined && !isNonEmptyString(fileId)) {
9176
+ return invalidFromErrorFactory(
9177
+ errorFactory,
9178
+ `payload.data.${fieldName} must be a non-empty string when provided`,
9179
+ );
9180
+ }
9181
+ }
9182
+
9183
+ {
9184
+ const result = validateTransformPreviewObject({
9185
+ value: data.preview,
9186
+ path: "payload.data.preview",
9187
+ errorFactory,
9188
+ });
9189
+ if (result?.valid === false) {
9190
+ return result;
9191
+ }
9192
+ }
9193
+
8769
9194
  {
8770
9195
  const result = validateOptionalUniqueIdArray({
8771
9196
  value: data.tagIds,
@@ -9036,6 +9461,9 @@ const validateTransformUpdateData = ({ data, errorFactory }) => {
9036
9461
  "anchorX",
9037
9462
  "anchorY",
9038
9463
  "rotation",
9464
+ "thumbnailFileId",
9465
+ "previewFileId",
9466
+ "preview",
9039
9467
  ],
9040
9468
  path: "payload.data",
9041
9469
  errorFactory,
@@ -9066,6 +9494,27 @@ const validateTransformUpdateData = ({ data, errorFactory }) => {
9066
9494
  );
9067
9495
  }
9068
9496
 
9497
+ for (const fieldName of ["thumbnailFileId", "previewFileId"]) {
9498
+ const fileId = data[fieldName];
9499
+ if (fileId !== undefined && !isNonEmptyString(fileId)) {
9500
+ return invalidFromErrorFactory(
9501
+ errorFactory,
9502
+ `payload.data.${fieldName} must be a non-empty string when provided`,
9503
+ );
9504
+ }
9505
+ }
9506
+
9507
+ {
9508
+ const result = validateTransformPreviewObject({
9509
+ value: data.preview,
9510
+ path: "payload.data.preview",
9511
+ errorFactory,
9512
+ });
9513
+ if (result?.valid === false) {
9514
+ return result;
9515
+ }
9516
+ }
9517
+
9069
9518
  {
9070
9519
  const result = validateOptionalUniqueIdArray({
9071
9520
  value: data.tagIds,
@@ -9103,10 +9552,10 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
9103
9552
  );
9104
9553
  }
9105
9554
 
9106
- if (data.type !== "folder" && !VARIABLE_TYPE_KEYS.includes(data.type)) {
9555
+ if (data.type !== "folder" && data.type !== "variable") {
9107
9556
  return invalidFromErrorFactory(
9108
9557
  errorFactory,
9109
- "payload.data.type must be 'folder', 'string', 'number', or 'boolean'",
9558
+ "payload.data.type must be 'folder' or 'variable'",
9110
9559
  );
9111
9560
  }
9112
9561
 
@@ -9118,12 +9567,15 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
9118
9567
  ? ["type", "name", "description"]
9119
9568
  : [
9120
9569
  "type",
9570
+ "variableType",
9121
9571
  "name",
9122
9572
  "description",
9123
9573
  "tagIds",
9124
9574
  "scope",
9125
9575
  "default",
9126
9576
  "value",
9577
+ "isEnum",
9578
+ "enumValues",
9127
9579
  ],
9128
9580
  path: "payload.data",
9129
9581
  errorFactory,
@@ -9147,7 +9599,14 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
9147
9599
  );
9148
9600
  }
9149
9601
 
9150
- if (data.type !== "folder") {
9602
+ if (data.type === "variable") {
9603
+ if (!VARIABLE_TYPE_KEYS.includes(data.variableType)) {
9604
+ return invalidFromErrorFactory(
9605
+ errorFactory,
9606
+ "payload.data.variableType must be 'string', 'number', or 'boolean'",
9607
+ );
9608
+ }
9609
+
9151
9610
  {
9152
9611
  const result = validateOptionalUniqueIdArray({
9153
9612
  value: data.tagIds,
@@ -9160,6 +9619,18 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
9160
9619
  }
9161
9620
  }
9162
9621
 
9622
+ {
9623
+ const result = validateVariableEnumMetadata({
9624
+ data,
9625
+ variableType: data.variableType,
9626
+ path: "payload.data",
9627
+ errorFactory,
9628
+ });
9629
+ if (result?.valid === false) {
9630
+ return result;
9631
+ }
9632
+ }
9633
+
9163
9634
  if (!VARIABLE_SCOPE_KEYS.includes(data.scope)) {
9164
9635
  return invalidFromErrorFactory(
9165
9636
  errorFactory,
@@ -9170,7 +9641,7 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
9170
9641
  {
9171
9642
  const result = validateVariableTypedValue({
9172
9643
  value: data.default,
9173
- variableType: data.type,
9644
+ variableType: data.variableType,
9174
9645
  path: "payload.data.default",
9175
9646
  errorFactory,
9176
9647
  });
@@ -9181,7 +9652,7 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
9181
9652
  {
9182
9653
  const result = validateVariableTypedValue({
9183
9654
  value: data.value,
9184
- variableType: data.type,
9655
+ variableType: data.variableType,
9185
9656
  path: "payload.data.value",
9186
9657
  errorFactory,
9187
9658
  });
@@ -9203,6 +9674,8 @@ const validateVariableUpdateData = ({ data, errorFactory }) => {
9203
9674
  "scope",
9204
9675
  "default",
9205
9676
  "value",
9677
+ "isEnum",
9678
+ "enumValues",
9206
9679
  ],
9207
9680
  path: "payload.data",
9208
9681
  errorFactory,
@@ -9233,6 +9706,24 @@ const validateVariableUpdateData = ({ data, errorFactory }) => {
9233
9706
  );
9234
9707
  }
9235
9708
 
9709
+ if (data.isEnum !== undefined && typeof data.isEnum !== "boolean") {
9710
+ return invalidFromErrorFactory(
9711
+ errorFactory,
9712
+ "payload.data.isEnum must be a boolean when provided",
9713
+ );
9714
+ }
9715
+
9716
+ {
9717
+ const result = validateVariableEnumValues({
9718
+ value: data.enumValues,
9719
+ path: "payload.data.enumValues",
9720
+ errorFactory,
9721
+ });
9722
+ if (result?.valid === false) {
9723
+ return result;
9724
+ }
9725
+ }
9726
+
9236
9727
  if (data.scope !== undefined && !VARIABLE_SCOPE_KEYS.includes(data.scope)) {
9237
9728
  return invalidFromErrorFactory(
9238
9729
  errorFactory,
@@ -11329,6 +11820,40 @@ const findReferencedFileUsage = ({ state, fileId }) => {
11329
11820
  }
11330
11821
  }
11331
11822
 
11823
+ for (const [animationId, animation] of Object.entries(
11824
+ state.animations.items,
11825
+ )) {
11826
+ if (animation.type !== "animation") {
11827
+ continue;
11828
+ }
11829
+
11830
+ if (animation.thumbnailFileId === fileId) {
11831
+ return {
11832
+ kind: "animation",
11833
+ field: "thumbnailFileId",
11834
+ ownerId: animationId,
11835
+ };
11836
+ }
11837
+ }
11838
+
11839
+ for (const [transformId, transform] of Object.entries(
11840
+ state.transforms.items,
11841
+ )) {
11842
+ if (transform.type !== "transform") {
11843
+ continue;
11844
+ }
11845
+
11846
+ for (const fieldName of ["thumbnailFileId", "previewFileId"]) {
11847
+ if (transform[fieldName] === fileId) {
11848
+ return {
11849
+ kind: "transform",
11850
+ field: fieldName,
11851
+ ownerId: transformId,
11852
+ };
11853
+ }
11854
+ }
11855
+ }
11856
+
11332
11857
  for (const [characterId, character] of Object.entries(
11333
11858
  state.characters.items,
11334
11859
  )) {
@@ -14288,6 +14813,16 @@ const COMMAND_DEFINITIONS = [
14288
14813
  return result;
14289
14814
  }
14290
14815
 
14816
+ const fileResult = validateReferencedFilesInData({
14817
+ state,
14818
+ data: payload.data,
14819
+ fields: ["thumbnailFileId"],
14820
+ details: { animationId: payload.animationId },
14821
+ });
14822
+ if (!fileResult.valid) {
14823
+ return fileResult;
14824
+ }
14825
+
14291
14826
  return validateTagIdsAgainstScope({
14292
14827
  state,
14293
14828
  tagIds: payload.data.tagIds,
@@ -14315,6 +14850,12 @@ const COMMAND_DEFINITIONS = [
14315
14850
  target: nextAnimation,
14316
14851
  tagIds: payload.data.tagIds,
14317
14852
  });
14853
+ if (payload.data.thumbnailFileId !== undefined) {
14854
+ nextAnimation.thumbnailFileId = payload.data.thumbnailFileId;
14855
+ }
14856
+ if (payload.data.preview !== undefined) {
14857
+ nextAnimation.preview = structuredClone(payload.data.preview);
14858
+ }
14318
14859
  nextAnimation.animation = structuredClone(payload.data.animation);
14319
14860
  }
14320
14861
 
@@ -14397,6 +14938,16 @@ const COMMAND_DEFINITIONS = [
14397
14938
  }
14398
14939
  }
14399
14940
 
14941
+ const fileResult = validateReferencedFilesInData({
14942
+ state,
14943
+ data: payload.data,
14944
+ fields: ["thumbnailFileId"],
14945
+ details: { animationId: payload.animationId },
14946
+ });
14947
+ if (!fileResult.valid) {
14948
+ return fileResult;
14949
+ }
14950
+
14400
14951
  return validateTagIdsAgainstScope({
14401
14952
  state,
14402
14953
  tagIds: payload.data.tagIds,
@@ -15604,6 +16155,15 @@ const COMMAND_DEFINITIONS = [
15604
16155
  target: item,
15605
16156
  tagIds: payload.data.tagIds,
15606
16157
  });
16158
+ if (payload.data.thumbnailFileId !== undefined) {
16159
+ item.thumbnailFileId = payload.data.thumbnailFileId;
16160
+ }
16161
+ if (payload.data.previewFileId !== undefined) {
16162
+ item.previewFileId = payload.data.previewFileId;
16163
+ }
16164
+ if (payload.data.preview !== undefined) {
16165
+ item.preview = structuredClone(payload.data.preview);
16166
+ }
15607
16167
 
15608
16168
  return item;
15609
16169
  },
@@ -15617,6 +16177,30 @@ const COMMAND_DEFINITIONS = [
15617
16177
  return;
15618
16178
  }
15619
16179
 
16180
+ const fileResult = validateReferencedFilesInData({
16181
+ state,
16182
+ data: payload.data,
16183
+ fields: ["thumbnailFileId", "previewFileId"],
16184
+ details: {
16185
+ transformId: payload.transformId,
16186
+ },
16187
+ });
16188
+ if (!fileResult.valid) {
16189
+ return fileResult;
16190
+ }
16191
+
16192
+ const previewResult = validateTransformPreviewImageReferences({
16193
+ state,
16194
+ preview: payload.data.preview,
16195
+ path: "payload.data.preview",
16196
+ details: {
16197
+ transformId: payload.transformId,
16198
+ },
16199
+ });
16200
+ if (!previewResult.valid) {
16201
+ return previewResult;
16202
+ }
16203
+
15620
16204
  return validateTagIdsAgainstScope({
15621
16205
  state,
15622
16206
  tagIds: payload.data.tagIds,
@@ -15640,6 +16224,30 @@ const COMMAND_DEFINITIONS = [
15640
16224
  }
15641
16225
 
15642
16226
  if (currentItem.type === "transform") {
16227
+ const fileResult = validateReferencedFilesInData({
16228
+ state,
16229
+ data: payload.data,
16230
+ fields: ["thumbnailFileId", "previewFileId"],
16231
+ details: {
16232
+ transformId: payload.transformId,
16233
+ },
16234
+ });
16235
+ if (!fileResult.valid) {
16236
+ return fileResult;
16237
+ }
16238
+
16239
+ const previewResult = validateTransformPreviewImageReferences({
16240
+ state,
16241
+ preview: payload.data.preview,
16242
+ path: "payload.data.preview",
16243
+ details: {
16244
+ transformId: payload.transformId,
16245
+ },
16246
+ });
16247
+ if (!previewResult.valid) {
16248
+ return previewResult;
16249
+ }
16250
+
15643
16251
  return validateTagIdsAgainstScope({
15644
16252
  state,
15645
16253
  tagIds: payload.data.tagIds,
@@ -15665,13 +16273,16 @@ const COMMAND_DEFINITIONS = [
15665
16273
  delete data.tagIds;
15666
16274
  }
15667
16275
 
15668
- return {
15669
- id: payload.variableId,
15670
- ...data,
15671
- };
16276
+ return applyVariableEnumMetadata({
16277
+ item: {
16278
+ id: payload.variableId,
16279
+ ...data,
16280
+ },
16281
+ data,
16282
+ });
15672
16283
  },
15673
16284
  updateItem: ({ currentItem, payload }) =>
15674
- applyTagIdsUpdate({
16285
+ applyVariableUpdate({
15675
16286
  currentItem,
15676
16287
  data: payload.data,
15677
16288
  }),
@@ -15702,6 +16313,16 @@ const COMMAND_DEFINITIONS = [
15702
16313
  );
15703
16314
  }
15704
16315
 
16316
+ if (
16317
+ currentItem.variableType !== "string" &&
16318
+ (payload.data.isEnum !== undefined ||
16319
+ payload.data.enumValues !== undefined)
16320
+ ) {
16321
+ return invalidPrecondition(
16322
+ "variable enum fields can only update string variables",
16323
+ );
16324
+ }
16325
+
15705
16326
  if (currentItem.type !== "folder") {
15706
16327
  {
15707
16328
  const result = validateTagIdsAgainstScope({
@@ -15722,7 +16343,7 @@ const COMMAND_DEFINITIONS = [
15722
16343
  {
15723
16344
  const result = validateVariableTypedValue({
15724
16345
  value: payload.data.default,
15725
- variableType: currentItem.type,
16346
+ variableType: currentItem.variableType,
15726
16347
  path: "payload.data.default",
15727
16348
  errorFactory: createPreconditionValidationError,
15728
16349
  });
@@ -15736,7 +16357,7 @@ const COMMAND_DEFINITIONS = [
15736
16357
  {
15737
16358
  const result = validateVariableTypedValue({
15738
16359
  value: payload.data.value,
15739
- variableType: currentItem.type,
16360
+ variableType: currentItem.variableType,
15740
16361
  path: "payload.data.value",
15741
16362
  errorFactory: createPreconditionValidationError,
15742
16363
  });