@routevn/creator-model 1.0.4 → 1.1.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 +114 -93
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@routevn/creator-model",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/model.js CHANGED
@@ -52,33 +52,6 @@ const normalizeStateCollections = (state) => {
52
52
  };
53
53
  };
54
54
  const isString = (value) => typeof value === "string";
55
- const FILE_ITEM_TYPES = [
56
- "image",
57
- "image-thumbnail",
58
- "audio",
59
- "audio-waveform",
60
- "video",
61
- "video-thumbnail",
62
- "font",
63
- ];
64
- const IMAGE_FILE_REFERENCE_TYPES = {
65
- fileId: ["image"],
66
- thumbnailFileId: ["image-thumbnail"],
67
- };
68
- const SOUND_FILE_REFERENCE_TYPES = {
69
- fileId: ["audio"],
70
- waveformDataFileId: ["audio-waveform"],
71
- };
72
- const VIDEO_FILE_REFERENCE_TYPES = {
73
- fileId: ["video"],
74
- thumbnailFileId: ["video-thumbnail"],
75
- };
76
- const FONT_FILE_REFERENCE_TYPES = {
77
- fileId: ["font"],
78
- };
79
- const CHARACTER_FILE_REFERENCE_TYPES = {
80
- fileId: ["image"],
81
- };
82
55
  const isHexColor = (value) =>
83
56
  typeof value === "string" && /^#[0-9a-fA-F]{6}$/.test(value);
84
57
  const LIVE_TWEEN_PROPERTY_KEYS = [
@@ -139,6 +112,7 @@ const LAYOUT_ELEMENT_TEXT_STYLE_ALIGN_KEYS = ["left", "center", "right"];
139
112
  const LAYOUT_ELEMENT_BASE_TYPES = [
140
113
  "folder",
141
114
  "container",
115
+ "rect",
142
116
  "sprite",
143
117
  "text",
144
118
  "text-revealing",
@@ -414,8 +388,8 @@ const validateSceneItems = ({ items, path, errorFactory }) => {
414
388
  value: item,
415
389
  allowedKeys:
416
390
  item?.type === "scene"
417
- ? ["id", "type", "name", "position", "sections"]
418
- : ["id", "type", "name", "position"],
391
+ ? ["id", "type", "name", "description", "position", "sections"]
392
+ : ["id", "type", "name", "description", "position"],
419
393
  path: itemPath,
420
394
  errorFactory,
421
395
  });
@@ -452,6 +426,13 @@ const validateSceneItems = ({ items, path, errorFactory }) => {
452
426
  );
453
427
  }
454
428
 
429
+ if (item.description !== undefined && !isString(item.description)) {
430
+ return invalidFromErrorFactory(
431
+ errorFactory,
432
+ `${itemPath}.description must be a string when provided`,
433
+ );
434
+ }
435
+
455
436
  {
456
437
  const result = validateOptionalPosition({
457
438
  value: item.position,
@@ -581,10 +562,10 @@ const validateFileItems = ({ items, path, errorFactory }) => {
581
562
  for (const [itemId, item] of Object.entries(items)) {
582
563
  const itemPath = `${path}.${itemId}`;
583
564
 
584
- if (item?.type !== "folder" && !FILE_ITEM_TYPES.includes(item?.type)) {
565
+ if (item?.type !== undefined && !isNonEmptyString(item.type)) {
585
566
  return invalidFromErrorFactory(
586
567
  errorFactory,
587
- `${itemPath}.type must be 'folder' or a supported file type`,
568
+ `${itemPath}.type must be a non-empty string when provided`,
588
569
  );
589
570
  }
590
571
 
@@ -2148,6 +2129,47 @@ const validateLayoutElementStyle = ({ style, path, errorFactory }) => {
2148
2129
  }
2149
2130
  };
2150
2131
 
2132
+ const validateLayoutElementBorder = ({ border, path, errorFactory }) => {
2133
+ {
2134
+ const result = validateAllowedKeys({
2135
+ value: border,
2136
+ allowedKeys: ["color", "alpha", "width"],
2137
+ path,
2138
+ errorFactory,
2139
+ });
2140
+ if (result?.valid === false) {
2141
+ return result;
2142
+ }
2143
+ }
2144
+
2145
+ if (border.color !== undefined && !isString(border.color)) {
2146
+ return invalidFromErrorFactory(
2147
+ errorFactory,
2148
+ `${path}.color must be a string when provided`,
2149
+ );
2150
+ }
2151
+
2152
+ if (
2153
+ border.alpha !== undefined &&
2154
+ (!isFiniteNumber(border.alpha) || border.alpha < 0 || border.alpha > 1)
2155
+ ) {
2156
+ return invalidFromErrorFactory(
2157
+ errorFactory,
2158
+ `${path}.alpha must be a finite number between 0 and 1 when provided`,
2159
+ );
2160
+ }
2161
+
2162
+ if (
2163
+ border.width !== undefined &&
2164
+ (!isFiniteNumber(border.width) || border.width < 0)
2165
+ ) {
2166
+ return invalidFromErrorFactory(
2167
+ errorFactory,
2168
+ `${path}.width must be a finite number greater than or equal to 0 when provided`,
2169
+ );
2170
+ }
2171
+ };
2172
+
2151
2173
  const validateLayoutElementData = ({
2152
2174
  data,
2153
2175
  path,
@@ -2171,6 +2193,8 @@ const validateLayoutElementData = ({
2171
2193
  "scaleY",
2172
2194
  "rotation",
2173
2195
  "opacity",
2196
+ "fill",
2197
+ "border",
2174
2198
  "text",
2175
2199
  "style",
2176
2200
  "displaySpeed",
@@ -2300,6 +2324,13 @@ const validateLayoutElementData = ({
2300
2324
  }
2301
2325
  }
2302
2326
 
2327
+ if (data.fill !== undefined && !isString(data.fill)) {
2328
+ return invalidFromErrorFactory(
2329
+ errorFactory,
2330
+ `${path}.fill must be a string when provided`,
2331
+ );
2332
+ }
2333
+
2303
2334
  if (
2304
2335
  data.direction !== undefined &&
2305
2336
  data.direction !== "horizontal" &&
@@ -2341,6 +2372,26 @@ const validateLayoutElementData = ({
2341
2372
  }
2342
2373
  }
2343
2374
 
2375
+ if (data.border !== undefined) {
2376
+ if (!isPlainObject(data.border)) {
2377
+ return invalidFromErrorFactory(
2378
+ errorFactory,
2379
+ `${path}.border must be an object when provided`,
2380
+ );
2381
+ }
2382
+
2383
+ {
2384
+ const result = validateLayoutElementBorder({
2385
+ border: data.border,
2386
+ path: `${path}.border`,
2387
+ errorFactory,
2388
+ });
2389
+ if (result?.valid === false) {
2390
+ return result;
2391
+ }
2392
+ }
2393
+ }
2394
+
2344
2395
  if (data.click !== undefined && !isPlainObject(data.click)) {
2345
2396
  return invalidFromErrorFactory(
2346
2397
  errorFactory,
@@ -2384,6 +2435,8 @@ const validateLayoutElementItems = ({ items, path, errorFactory }) => {
2384
2435
  "scaleY",
2385
2436
  "rotation",
2386
2437
  "opacity",
2438
+ "fill",
2439
+ "border",
2387
2440
  "text",
2388
2441
  "style",
2389
2442
  "displaySpeed",
@@ -2657,7 +2710,6 @@ const validateLayoutItems = ({ items, path, errorFactory }) => {
2657
2710
  return result;
2658
2711
  }
2659
2712
  }
2660
-
2661
2713
  }
2662
2714
  }
2663
2715
  };
@@ -3493,7 +3545,6 @@ const validateFileReference = ({
3493
3545
  state,
3494
3546
  fileId,
3495
3547
  path,
3496
- allowedTypes,
3497
3548
  details = {},
3498
3549
  errorFactory = createPreconditionValidationError,
3499
3550
  }) => {
@@ -3501,36 +3552,10 @@ const validateFileReference = ({
3501
3552
  return VALID_RESULT;
3502
3553
  }
3503
3554
 
3504
- const expectedTypeMessage =
3505
- Array.isArray(allowedTypes) && allowedTypes.length > 0
3506
- ? `${path} must reference an existing non-folder file with type ${allowedTypes
3507
- .map((type) => `'${type}'`)
3508
- .join(" or ")}`
3509
- : `${path} must reference an existing non-folder file`;
3555
+ const expectedTypeMessage = `${path} must reference an existing non-folder file`;
3510
3556
  const file = state.files?.items?.[fileId];
3511
3557
  if (!isPlainObject(file) || file.type === "folder") {
3512
- return invalidFromErrorFactory(
3513
- errorFactory,
3514
- expectedTypeMessage,
3515
- Array.isArray(allowedTypes) && allowedTypes.length > 0
3516
- ? {
3517
- ...details,
3518
- expectedFileTypes: [...allowedTypes],
3519
- }
3520
- : details,
3521
- );
3522
- }
3523
-
3524
- if (
3525
- Array.isArray(allowedTypes) &&
3526
- allowedTypes.length > 0 &&
3527
- !allowedTypes.includes(file.type)
3528
- ) {
3529
- return invalidFromErrorFactory(errorFactory, expectedTypeMessage, {
3530
- ...details,
3531
- expectedFileTypes: [...allowedTypes],
3532
- actualFileType: file.type,
3533
- });
3558
+ return invalidFromErrorFactory(errorFactory, expectedTypeMessage, details);
3534
3559
  }
3535
3560
 
3536
3561
  return VALID_RESULT;
@@ -3659,7 +3684,6 @@ export const assertInvariants = ({ state }) => {
3659
3684
  state,
3660
3685
  fileId: image.fileId,
3661
3686
  path: "image.fileId",
3662
- allowedTypes: IMAGE_FILE_REFERENCE_TYPES.fileId,
3663
3687
  details: { imageId, fileId: image.fileId },
3664
3688
  errorFactory: createInvariantValidationError,
3665
3689
  });
@@ -3673,7 +3697,6 @@ export const assertInvariants = ({ state }) => {
3673
3697
  state,
3674
3698
  fileId: image.thumbnailFileId,
3675
3699
  path: "image.thumbnailFileId",
3676
- allowedTypes: IMAGE_FILE_REFERENCE_TYPES.thumbnailFileId,
3677
3700
  details: { imageId, thumbnailFileId: image.thumbnailFileId },
3678
3701
  errorFactory: createInvariantValidationError,
3679
3702
  });
@@ -3693,7 +3716,6 @@ export const assertInvariants = ({ state }) => {
3693
3716
  state,
3694
3717
  fileId: sound.fileId,
3695
3718
  path: "sound.fileId",
3696
- allowedTypes: SOUND_FILE_REFERENCE_TYPES.fileId,
3697
3719
  details: { soundId, fileId: sound.fileId },
3698
3720
  errorFactory: createInvariantValidationError,
3699
3721
  });
@@ -3710,7 +3732,6 @@ export const assertInvariants = ({ state }) => {
3710
3732
  state,
3711
3733
  fileId: sound.waveformDataFileId,
3712
3734
  path: "sound.waveformDataFileId",
3713
- allowedTypes: SOUND_FILE_REFERENCE_TYPES.waveformDataFileId,
3714
3735
  details: { soundId, waveformDataFileId: sound.waveformDataFileId },
3715
3736
  errorFactory: createInvariantValidationError,
3716
3737
  });
@@ -3730,7 +3751,6 @@ export const assertInvariants = ({ state }) => {
3730
3751
  state,
3731
3752
  fileId: video.fileId,
3732
3753
  path: "video.fileId",
3733
- allowedTypes: VIDEO_FILE_REFERENCE_TYPES.fileId,
3734
3754
  details: { videoId, fileId: video.fileId },
3735
3755
  errorFactory: createInvariantValidationError,
3736
3756
  });
@@ -3744,7 +3764,6 @@ export const assertInvariants = ({ state }) => {
3744
3764
  state,
3745
3765
  fileId: video.thumbnailFileId,
3746
3766
  path: "video.thumbnailFileId",
3747
- allowedTypes: VIDEO_FILE_REFERENCE_TYPES.thumbnailFileId,
3748
3767
  details: { videoId, thumbnailFileId: video.thumbnailFileId },
3749
3768
  errorFactory: createInvariantValidationError,
3750
3769
  });
@@ -3763,7 +3782,6 @@ export const assertInvariants = ({ state }) => {
3763
3782
  state,
3764
3783
  fileId: font.fileId,
3765
3784
  path: "font.fileId",
3766
- allowedTypes: FONT_FILE_REFERENCE_TYPES.fileId,
3767
3785
  details: { fontId, fileId: font.fileId },
3768
3786
  errorFactory: createInvariantValidationError,
3769
3787
  });
@@ -3784,7 +3802,6 @@ export const assertInvariants = ({ state }) => {
3784
3802
  state,
3785
3803
  fileId: character.fileId,
3786
3804
  path: "character.fileId",
3787
- allowedTypes: CHARACTER_FILE_REFERENCE_TYPES.fileId,
3788
3805
  details: { characterId, fileId: character.fileId },
3789
3806
  errorFactory: createInvariantValidationError,
3790
3807
  });
@@ -3804,7 +3821,6 @@ export const assertInvariants = ({ state }) => {
3804
3821
  state,
3805
3822
  fileId: sprite.fileId,
3806
3823
  path: "character.sprite.fileId",
3807
- allowedTypes: CHARACTER_FILE_REFERENCE_TYPES.fileId,
3808
3824
  details: { characterId, spriteId, fileId: sprite.fileId },
3809
3825
  errorFactory: createInvariantValidationError,
3810
3826
  });
@@ -4148,7 +4164,7 @@ const validateSceneCreateData = ({ data, errorFactory }) => {
4148
4164
  {
4149
4165
  const result = validateAllowedKeys({
4150
4166
  value: data,
4151
- allowedKeys: ["name", "type", "position"],
4167
+ allowedKeys: ["name", "description", "type", "position"],
4152
4168
  path: "payload.data",
4153
4169
  errorFactory,
4154
4170
  });
@@ -4164,6 +4180,13 @@ const validateSceneCreateData = ({ data, errorFactory }) => {
4164
4180
  );
4165
4181
  }
4166
4182
 
4183
+ if (data.description !== undefined && !isString(data.description)) {
4184
+ return invalidFromErrorFactory(
4185
+ errorFactory,
4186
+ "payload.data.description must be a string when provided",
4187
+ );
4188
+ }
4189
+
4167
4190
  if (
4168
4191
  data.type !== undefined &&
4169
4192
  data.type !== "scene" &&
@@ -4191,7 +4214,7 @@ const validateSceneUpdateData = ({ data, errorFactory }) => {
4191
4214
  {
4192
4215
  const result = validateAllowedKeys({
4193
4216
  value: data,
4194
- allowedKeys: ["name", "position"],
4217
+ allowedKeys: ["name", "description", "position"],
4195
4218
  path: "payload.data",
4196
4219
  errorFactory,
4197
4220
  });
@@ -4201,9 +4224,10 @@ const validateSceneUpdateData = ({ data, errorFactory }) => {
4201
4224
  }
4202
4225
 
4203
4226
  const hasName = data.name !== undefined;
4227
+ const hasDescription = data.description !== undefined;
4204
4228
  const hasPosition = data.position !== undefined;
4205
4229
 
4206
- if (!hasName && !hasPosition) {
4230
+ if (!hasName && !hasDescription && !hasPosition) {
4207
4231
  return invalidFromErrorFactory(
4208
4232
  errorFactory,
4209
4233
  "payload.data must include at least one updatable field",
@@ -4217,6 +4241,13 @@ const validateSceneUpdateData = ({ data, errorFactory }) => {
4217
4241
  );
4218
4242
  }
4219
4243
 
4244
+ if (hasDescription && !isString(data.description)) {
4245
+ return invalidFromErrorFactory(
4246
+ errorFactory,
4247
+ "payload.data.description must be a string when provided",
4248
+ );
4249
+ }
4250
+
4220
4251
  if (hasPosition) {
4221
4252
  {
4222
4253
  const result = validateOptionalPosition({
@@ -5060,10 +5091,10 @@ const validateFileCreateData = ({ data, errorFactory }) => {
5060
5091
  );
5061
5092
  }
5062
5093
 
5063
- if (data.type !== "folder" && !FILE_ITEM_TYPES.includes(data.type)) {
5094
+ if (data.type !== undefined && !isNonEmptyString(data.type)) {
5064
5095
  return invalidFromErrorFactory(
5065
5096
  errorFactory,
5066
- "payload.data.type must be 'folder' or a supported file type",
5097
+ "payload.data.type must be a non-empty string when provided",
5067
5098
  );
5068
5099
  }
5069
5100
 
@@ -5118,7 +5149,6 @@ const validateReferencedFilesInData = ({
5118
5149
  state,
5119
5150
  data,
5120
5151
  fields,
5121
- fieldTypes = {},
5122
5152
  nullableFields = [],
5123
5153
  details = {},
5124
5154
  errorFactory = createPreconditionValidationError,
@@ -5138,7 +5168,6 @@ const validateReferencedFilesInData = ({
5138
5168
  state,
5139
5169
  fileId,
5140
5170
  path: `payload.data.${field}`,
5141
- allowedTypes: fieldTypes[field],
5142
5171
  details: {
5143
5172
  ...details,
5144
5173
  field,
@@ -6120,7 +6149,6 @@ const validateLayoutCreateData = ({ data, errorFactory }) => {
6120
6149
  return result;
6121
6150
  }
6122
6151
  }
6123
-
6124
6152
  }
6125
6153
  };
6126
6154
 
@@ -6160,7 +6188,6 @@ const validateLayoutUpdateData = ({ data, errorFactory }) => {
6160
6188
  "payload.data.layoutType must be 'normal', 'dialogue', 'nvl', or 'choice' when provided",
6161
6189
  );
6162
6190
  }
6163
-
6164
6191
  };
6165
6192
 
6166
6193
  const validateControlCreateData = ({ data, errorFactory }) => {
@@ -7198,7 +7225,6 @@ const COMMAND_DEFINITIONS = [
7198
7225
  }
7199
7226
  : {
7200
7227
  id: payload.fileId,
7201
- type: payload.data.type,
7202
7228
  mimeType: payload.data.mimeType,
7203
7229
  size: payload.data.size,
7204
7230
  sha256: payload.data.sha256,
@@ -7392,6 +7418,10 @@ const COMMAND_DEFINITIONS = [
7392
7418
  name: payload.data.name,
7393
7419
  };
7394
7420
 
7421
+ if (payload.data.description !== undefined) {
7422
+ nextScene.description = payload.data.description;
7423
+ }
7424
+
7395
7425
  if (nextScene.type === "scene") {
7396
7426
  nextScene.sections = createEmptyNestedCollection();
7397
7427
  }
@@ -7461,6 +7491,10 @@ const COMMAND_DEFINITIONS = [
7461
7491
  nextScene.name = payload.data.name;
7462
7492
  }
7463
7493
 
7494
+ if (payload.data.description !== undefined) {
7495
+ nextScene.description = payload.data.description;
7496
+ }
7497
+
7464
7498
  if (payload.data.position !== undefined) {
7465
7499
  nextScene.position = {
7466
7500
  ...(isPlainObject(nextScene.position) ? nextScene.position : {}),
@@ -8548,7 +8582,6 @@ const COMMAND_DEFINITIONS = [
8548
8582
  state,
8549
8583
  data: payload.data,
8550
8584
  fields: ["fileId", "thumbnailFileId"],
8551
- fieldTypes: IMAGE_FILE_REFERENCE_TYPES,
8552
8585
  details: {
8553
8586
  imageId: payload.imageId,
8554
8587
  },
@@ -8661,7 +8694,6 @@ const COMMAND_DEFINITIONS = [
8661
8694
  state,
8662
8695
  data: payload.data,
8663
8696
  fields: ["fileId", "thumbnailFileId"],
8664
- fieldTypes: IMAGE_FILE_REFERENCE_TYPES,
8665
8697
  details: {
8666
8698
  imageId: payload.imageId,
8667
8699
  },
@@ -8974,7 +9006,6 @@ const COMMAND_DEFINITIONS = [
8974
9006
  state,
8975
9007
  data: payload.data,
8976
9008
  fields: ["fileId", "waveformDataFileId"],
8977
- fieldTypes: SOUND_FILE_REFERENCE_TYPES,
8978
9009
  nullableFields: ["waveformDataFileId"],
8979
9010
  details: {
8980
9011
  soundId: payload.soundId,
@@ -9084,7 +9115,6 @@ const COMMAND_DEFINITIONS = [
9084
9115
  state,
9085
9116
  data: payload.data,
9086
9117
  fields: ["fileId", "waveformDataFileId"],
9087
- fieldTypes: SOUND_FILE_REFERENCE_TYPES,
9088
9118
  nullableFields: ["waveformDataFileId"],
9089
9119
  details: {
9090
9120
  soundId: payload.soundId,
@@ -9398,7 +9428,6 @@ const COMMAND_DEFINITIONS = [
9398
9428
  state,
9399
9429
  data: payload.data,
9400
9430
  fields: ["fileId", "thumbnailFileId"],
9401
- fieldTypes: VIDEO_FILE_REFERENCE_TYPES,
9402
9431
  details: {
9403
9432
  videoId: payload.videoId,
9404
9433
  },
@@ -9509,7 +9538,6 @@ const COMMAND_DEFINITIONS = [
9509
9538
  state,
9510
9539
  data: payload.data,
9511
9540
  fields: ["fileId", "thumbnailFileId"],
9512
- fieldTypes: VIDEO_FILE_REFERENCE_TYPES,
9513
9541
  details: {
9514
9542
  videoId: payload.videoId,
9515
9543
  },
@@ -10196,7 +10224,6 @@ const COMMAND_DEFINITIONS = [
10196
10224
  state,
10197
10225
  data: payload.data,
10198
10226
  fields: ["fileId"],
10199
- fieldTypes: FONT_FILE_REFERENCE_TYPES,
10200
10227
  details: {
10201
10228
  fontId: payload.fontId,
10202
10229
  },
@@ -10295,7 +10322,6 @@ const COMMAND_DEFINITIONS = [
10295
10322
  state,
10296
10323
  data: payload.data,
10297
10324
  fields: ["fileId"],
10298
- fieldTypes: FONT_FILE_REFERENCE_TYPES,
10299
10325
  details: {
10300
10326
  fontId: payload.fontId,
10301
10327
  },
@@ -11081,7 +11107,6 @@ const COMMAND_DEFINITIONS = [
11081
11107
  state,
11082
11108
  data: payload.data,
11083
11109
  fields: ["fileId"],
11084
- fieldTypes: CHARACTER_FILE_REFERENCE_TYPES,
11085
11110
  details: {
11086
11111
  characterId: payload.characterId,
11087
11112
  },
@@ -11102,7 +11127,6 @@ const COMMAND_DEFINITIONS = [
11102
11127
  state,
11103
11128
  fileId: sprite.fileId,
11104
11129
  path: "payload.data.sprites.items.*.fileId",
11105
- allowedTypes: CHARACTER_FILE_REFERENCE_TYPES.fileId,
11106
11130
  details: {
11107
11131
  characterId: payload.characterId,
11108
11132
  spriteId,
@@ -11129,7 +11153,6 @@ const COMMAND_DEFINITIONS = [
11129
11153
  state,
11130
11154
  data: payload.data,
11131
11155
  fields: ["fileId"],
11132
- fieldTypes: CHARACTER_FILE_REFERENCE_TYPES,
11133
11156
  details: {
11134
11157
  characterId: payload.characterId,
11135
11158
  },
@@ -11314,7 +11337,6 @@ const COMMAND_DEFINITIONS = [
11314
11337
  state,
11315
11338
  data: payload.data,
11316
11339
  fields: ["fileId"],
11317
- fieldTypes: CHARACTER_FILE_REFERENCE_TYPES,
11318
11340
  details: {
11319
11341
  characterId: payload.characterId,
11320
11342
  spriteId: payload.spriteId,
@@ -11418,7 +11440,6 @@ const COMMAND_DEFINITIONS = [
11418
11440
  state,
11419
11441
  data: payload.data,
11420
11442
  fields: ["fileId"],
11421
- fieldTypes: CHARACTER_FILE_REFERENCE_TYPES,
11422
11443
  details: {
11423
11444
  characterId: payload.characterId,
11424
11445
  spriteId: payload.spriteId,