@routevn/creator-model 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/model.js +1539 -225
package/src/model.js CHANGED
@@ -49,7 +49,16 @@ const TAG_SCOPE_BASE_KEYS = [
49
49
  "sounds",
50
50
  "videos",
51
51
  "characters",
52
+ "fonts",
52
53
  "transforms",
54
+ "colors",
55
+ "textStyles",
56
+ "variables",
57
+ "layouts",
58
+ "controls",
59
+ "animations",
60
+ "particles",
61
+ "spritesheets",
53
62
  ];
54
63
  const CHARACTER_SPRITE_TAG_SCOPE_PREFIX = "characterSprites:";
55
64
  const createEmptyTagsState = () => ({
@@ -57,7 +66,16 @@ const createEmptyTagsState = () => ({
57
66
  sounds: createEmptyCollectionState(),
58
67
  videos: createEmptyCollectionState(),
59
68
  characters: createEmptyCollectionState(),
69
+ fonts: createEmptyCollectionState(),
60
70
  transforms: createEmptyCollectionState(),
71
+ colors: createEmptyCollectionState(),
72
+ textStyles: createEmptyCollectionState(),
73
+ variables: createEmptyCollectionState(),
74
+ layouts: createEmptyCollectionState(),
75
+ controls: createEmptyCollectionState(),
76
+ animations: createEmptyCollectionState(),
77
+ particles: createEmptyCollectionState(),
78
+ spritesheets: createEmptyCollectionState(),
61
79
  });
62
80
  const isCharacterSpriteTagScopeKey = (value) =>
63
81
  isNonEmptyString(value) &&
@@ -1024,6 +1042,7 @@ const validateSpritesheetItems = ({ items, path, errorFactory }) => {
1024
1042
  "type",
1025
1043
  "name",
1026
1044
  "description",
1045
+ "tagIds",
1027
1046
  "thumbnailFileId",
1028
1047
  "fileId",
1029
1048
  "sheetWidth",
@@ -1071,6 +1090,18 @@ const validateSpritesheetItems = ({ items, path, errorFactory }) => {
1071
1090
  }
1072
1091
 
1073
1092
  if (item.type === "spritesheet") {
1093
+ {
1094
+ const result = validateOptionalUniqueIdArray({
1095
+ value: item.tagIds,
1096
+ path: `${itemPath}.tagIds`,
1097
+ errorFactory,
1098
+ allowEmpty: false,
1099
+ });
1100
+ if (result?.valid === false) {
1101
+ return result;
1102
+ }
1103
+ }
1104
+
1074
1105
  if (
1075
1106
  item.thumbnailFileId !== undefined &&
1076
1107
  !isNonEmptyString(item.thumbnailFileId)
@@ -1970,7 +2001,7 @@ const validateAnimationItems = ({ items, path, errorFactory }) => {
1970
2001
  allowedKeys:
1971
2002
  item.type === "folder"
1972
2003
  ? ["id", "type", "name", "description"]
1973
- : ["id", "type", "name", "description", "animation"],
2004
+ : ["id", "type", "name", "description", "tagIds", "animation"],
1974
2005
  path: itemPath,
1975
2006
  errorFactory,
1976
2007
  });
@@ -2008,6 +2039,18 @@ const validateAnimationItems = ({ items, path, errorFactory }) => {
2008
2039
  }
2009
2040
 
2010
2041
  if (item.type === "animation") {
2042
+ {
2043
+ const result = validateOptionalUniqueIdArray({
2044
+ value: item.tagIds,
2045
+ path: `${itemPath}.tagIds`,
2046
+ errorFactory,
2047
+ allowEmpty: false,
2048
+ });
2049
+ if (result?.valid === false) {
2050
+ return result;
2051
+ }
2052
+ }
2053
+
2011
2054
  {
2012
2055
  const result = validateAnimationDefinition({
2013
2056
  animation: item.animation,
@@ -2044,6 +2087,7 @@ const validateFontItems = ({ items, path, errorFactory }) => {
2044
2087
  "type",
2045
2088
  "name",
2046
2089
  "description",
2090
+ "tagIds",
2047
2091
  "fileId",
2048
2092
  "fontFamily",
2049
2093
  ],
@@ -2084,6 +2128,18 @@ const validateFontItems = ({ items, path, errorFactory }) => {
2084
2128
  }
2085
2129
 
2086
2130
  if (item.type === "font") {
2131
+ {
2132
+ const result = validateOptionalUniqueIdArray({
2133
+ value: item.tagIds,
2134
+ path: `${itemPath}.tagIds`,
2135
+ errorFactory,
2136
+ allowEmpty: false,
2137
+ });
2138
+ if (result?.valid === false) {
2139
+ return result;
2140
+ }
2141
+ }
2142
+
2087
2143
  if (!isNonEmptyString(item.fileId)) {
2088
2144
  return invalidFromErrorFactory(
2089
2145
  errorFactory,
@@ -2097,7 +2153,6 @@ const validateFontItems = ({ items, path, errorFactory }) => {
2097
2153
  `${itemPath}.fontFamily must be a non-empty string`,
2098
2154
  );
2099
2155
  }
2100
-
2101
2156
  }
2102
2157
  }
2103
2158
  };
@@ -2119,7 +2174,7 @@ const validateColorItems = ({ items, path, errorFactory }) => {
2119
2174
  allowedKeys:
2120
2175
  item.type === "folder"
2121
2176
  ? ["id", "type", "name", "description"]
2122
- : ["id", "type", "name", "description", "hex"],
2177
+ : ["id", "type", "name", "description", "tagIds", "hex"],
2123
2178
  path: itemPath,
2124
2179
  errorFactory,
2125
2180
  });
@@ -2156,11 +2211,25 @@ const validateColorItems = ({ items, path, errorFactory }) => {
2156
2211
  );
2157
2212
  }
2158
2213
 
2159
- if (item.type === "color" && !isHexColor(item.hex)) {
2160
- return invalidFromErrorFactory(
2161
- errorFactory,
2162
- `${itemPath}.hex must be a #RRGGBB string`,
2163
- );
2214
+ if (item.type === "color") {
2215
+ {
2216
+ const result = validateOptionalUniqueIdArray({
2217
+ value: item.tagIds,
2218
+ path: `${itemPath}.tagIds`,
2219
+ errorFactory,
2220
+ allowEmpty: false,
2221
+ });
2222
+ if (result?.valid === false) {
2223
+ return result;
2224
+ }
2225
+ }
2226
+
2227
+ if (!isHexColor(item.hex)) {
2228
+ return invalidFromErrorFactory(
2229
+ errorFactory,
2230
+ `${itemPath}.hex must be a #RRGGBB string`,
2231
+ );
2232
+ }
2164
2233
  }
2165
2234
  }
2166
2235
  };
@@ -2321,6 +2390,7 @@ const validateParticleItems = ({ items, path, errorFactory }) => {
2321
2390
  "type",
2322
2391
  "name",
2323
2392
  "description",
2393
+ "tagIds",
2324
2394
  "width",
2325
2395
  "height",
2326
2396
  "seed",
@@ -2366,6 +2436,18 @@ const validateParticleItems = ({ items, path, errorFactory }) => {
2366
2436
  continue;
2367
2437
  }
2368
2438
 
2439
+ {
2440
+ const result = validateOptionalUniqueIdArray({
2441
+ value: item.tagIds,
2442
+ path: `${itemPath}.tagIds`,
2443
+ errorFactory,
2444
+ allowEmpty: false,
2445
+ });
2446
+ if (result?.valid === false) {
2447
+ return result;
2448
+ }
2449
+ }
2450
+
2369
2451
  if (!isFiniteNumber(item.width) || item.width <= 0) {
2370
2452
  return invalidFromErrorFactory(
2371
2453
  errorFactory,
@@ -2448,6 +2530,7 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
2448
2530
  "type",
2449
2531
  "name",
2450
2532
  "description",
2533
+ "tagIds",
2451
2534
  "scope",
2452
2535
  "default",
2453
2536
  "value",
@@ -2489,6 +2572,18 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
2489
2572
  }
2490
2573
 
2491
2574
  if (variableType !== "folder") {
2575
+ {
2576
+ const result = validateOptionalUniqueIdArray({
2577
+ value: item.tagIds,
2578
+ path: `${itemPath}.tagIds`,
2579
+ errorFactory,
2580
+ allowEmpty: false,
2581
+ });
2582
+ if (result?.valid === false) {
2583
+ return result;
2584
+ }
2585
+ }
2586
+
2492
2587
  if (!VARIABLE_SCOPE_KEYS.includes(item.scope)) {
2493
2588
  return invalidFromErrorFactory(
2494
2589
  errorFactory,
@@ -2544,6 +2639,7 @@ const validateTextStyleItems = ({ items, path, errorFactory }) => {
2544
2639
  "type",
2545
2640
  "name",
2546
2641
  "description",
2642
+ "tagIds",
2547
2643
  "fontId",
2548
2644
  "colorId",
2549
2645
  "fontSize",
@@ -2596,6 +2692,18 @@ const validateTextStyleItems = ({ items, path, errorFactory }) => {
2596
2692
  }
2597
2693
 
2598
2694
  if (item.type === "textStyle") {
2695
+ {
2696
+ const result = validateOptionalUniqueIdArray({
2697
+ value: item.tagIds,
2698
+ path: `${itemPath}.tagIds`,
2699
+ errorFactory,
2700
+ allowEmpty: false,
2701
+ });
2702
+ if (result?.valid === false) {
2703
+ return result;
2704
+ }
2705
+ }
2706
+
2599
2707
  if (!isNonEmptyString(item.fontId)) {
2600
2708
  return invalidFromErrorFactory(
2601
2709
  errorFactory,
@@ -2960,6 +3068,8 @@ const validateLayoutElementData = ({
2960
3068
  "imageId",
2961
3069
  "hoverImageId",
2962
3070
  "clickImageId",
3071
+ "hoverSoundId",
3072
+ "clickSoundId",
2963
3073
  "textStyleId",
2964
3074
  "hoverTextStyleId",
2965
3075
  "clickTextStyleId",
@@ -3087,6 +3197,8 @@ const validateLayoutElementData = ({
3087
3197
  "imageId",
3088
3198
  "hoverImageId",
3089
3199
  "clickImageId",
3200
+ "hoverSoundId",
3201
+ "clickSoundId",
3090
3202
  "textStyleId",
3091
3203
  "hoverTextStyleId",
3092
3204
  "clickTextStyleId",
@@ -3243,6 +3355,8 @@ const validateLayoutElementData = ({
3243
3355
  "imageId",
3244
3356
  "hoverImageId",
3245
3357
  "clickImageId",
3358
+ "hoverSoundId",
3359
+ "clickSoundId",
3246
3360
  "opacity",
3247
3361
  "anchorX",
3248
3362
  "anchorY",
@@ -3264,6 +3378,8 @@ const validateLayoutElementData = ({
3264
3378
  "imageId",
3265
3379
  "hoverImageId",
3266
3380
  "clickImageId",
3381
+ "hoverSoundId",
3382
+ "clickSoundId",
3267
3383
  ]) {
3268
3384
  if (
3269
3385
  rule.set[field] !== undefined &&
@@ -3500,6 +3616,8 @@ const validateLayoutElementItems = ({ items, path, errorFactory }) => {
3500
3616
  "imageId",
3501
3617
  "hoverImageId",
3502
3618
  "clickImageId",
3619
+ "hoverSoundId",
3620
+ "clickSoundId",
3503
3621
  "textStyleId",
3504
3622
  "hoverTextStyleId",
3505
3623
  "clickTextStyleId",
@@ -3589,6 +3707,7 @@ const validateCharacterItems = ({ items, path, errorFactory }) => {
3589
3707
  "name",
3590
3708
  "description",
3591
3709
  "tagIds",
3710
+ "spriteGroups",
3592
3711
  "shortcut",
3593
3712
  "fileId",
3594
3713
  "sprites",
@@ -3642,6 +3761,19 @@ const validateCharacterItems = ({ items, path, errorFactory }) => {
3642
3761
  }
3643
3762
  }
3644
3763
 
3764
+ {
3765
+ const result = validateCharacterSpriteGroups({
3766
+ value: item.spriteGroups,
3767
+ path: `${itemPath}.spriteGroups`,
3768
+ errorFactory,
3769
+ allowEmpty: false,
3770
+ allowMissingId: true,
3771
+ });
3772
+ if (result?.valid === false) {
3773
+ return result;
3774
+ }
3775
+ }
3776
+
3645
3777
  if (item.shortcut !== undefined && !isString(item.shortcut)) {
3646
3778
  return invalidFromErrorFactory(
3647
3779
  errorFactory,
@@ -3777,10 +3909,7 @@ const validateTagCreateData = ({
3777
3909
  }
3778
3910
 
3779
3911
  if (data?.type !== "tag") {
3780
- return invalidFromErrorFactory(
3781
- errorFactory,
3782
- `${path}.type must be 'tag'`,
3783
- );
3912
+ return invalidFromErrorFactory(errorFactory, `${path}.type must be 'tag'`);
3784
3913
  }
3785
3914
 
3786
3915
  if (!isNonEmptyString(data.name)) {
@@ -3847,7 +3976,10 @@ const validateTagsRoot = ({ state, tags, path, errorFactory }) => {
3847
3976
  }
3848
3977
 
3849
3978
  for (const scopeKey of Object.keys(tags)) {
3850
- if (!isBaseTagScopeKey(scopeKey) && !isCharacterSpriteTagScopeKey(scopeKey)) {
3979
+ if (
3980
+ !isBaseTagScopeKey(scopeKey) &&
3981
+ !isCharacterSpriteTagScopeKey(scopeKey)
3982
+ ) {
3851
3983
  return invalidFromErrorFactory(
3852
3984
  errorFactory,
3853
3985
  `${path}.${scopeKey} is not allowed`,
@@ -3992,6 +4124,39 @@ const validateTagIdsAgainstScope = ({
3992
4124
  return VALID_RESULT;
3993
4125
  };
3994
4126
 
4127
+ const validateCharacterSpriteGroupsAgainstScope = ({
4128
+ state,
4129
+ spriteGroups,
4130
+ scopeKey,
4131
+ path,
4132
+ details = {},
4133
+ errorFactory = createPreconditionValidationError,
4134
+ }) => {
4135
+ if (spriteGroups === undefined) {
4136
+ return VALID_RESULT;
4137
+ }
4138
+
4139
+ for (const [index, spriteGroup] of spriteGroups.entries()) {
4140
+ const result = validateTagIdsAgainstScope({
4141
+ state,
4142
+ tagIds: spriteGroup.tags,
4143
+ scopeKey,
4144
+ path: `${path}[${index}].tags`,
4145
+ details: {
4146
+ ...details,
4147
+ spriteGroupIndex: index,
4148
+ spriteGroupName: spriteGroup.name,
4149
+ },
4150
+ errorFactory,
4151
+ });
4152
+ if (!result.valid) {
4153
+ return result;
4154
+ }
4155
+ }
4156
+
4157
+ return VALID_RESULT;
4158
+ };
4159
+
3995
4160
  const validateUniqueTagNameInScope = ({
3996
4161
  collection,
3997
4162
  name,
@@ -4029,6 +4194,12 @@ const assignOptionalTagIds = ({ target, tagIds }) => {
4029
4194
  }
4030
4195
  };
4031
4196
 
4197
+ const assignOptionalCharacterSpriteGroups = ({ target, spriteGroups }) => {
4198
+ if (Array.isArray(spriteGroups) && spriteGroups.length > 0) {
4199
+ target.spriteGroups = structuredClone(spriteGroups);
4200
+ }
4201
+ };
4202
+
4032
4203
  const applyTagIdsUpdate = ({ currentItem, data }) => {
4033
4204
  const nextData = structuredClone(data);
4034
4205
  if (nextData.tagIds === undefined) {
@@ -4051,12 +4222,31 @@ const applyTagIdsUpdate = ({ currentItem, data }) => {
4051
4222
  return nextItem;
4052
4223
  };
4053
4224
 
4225
+ const applyCharacterUpdate = ({ currentItem, data }) => {
4226
+ const nextItem = applyTagIdsUpdate({
4227
+ currentItem,
4228
+ data,
4229
+ });
4230
+
4231
+ if (data.spriteGroups !== undefined) {
4232
+ if (Array.isArray(data.spriteGroups) && data.spriteGroups.length > 0) {
4233
+ nextItem.spriteGroups = structuredClone(data.spriteGroups);
4234
+ } else {
4235
+ delete nextItem.spriteGroups;
4236
+ }
4237
+ }
4238
+
4239
+ return nextItem;
4240
+ };
4241
+
4054
4242
  const stripDeletedTagIdsFromItem = ({ item, deletedTagIds }) => {
4055
4243
  if (!Array.isArray(item?.tagIds) || item.tagIds.length === 0) {
4056
4244
  return;
4057
4245
  }
4058
4246
 
4059
- const remainingTagIds = item.tagIds.filter((tagId) => !deletedTagIds.has(tagId));
4247
+ const remainingTagIds = item.tagIds.filter(
4248
+ (tagId) => !deletedTagIds.has(tagId),
4249
+ );
4060
4250
  if (remainingTagIds.length === item.tagIds.length) {
4061
4251
  return;
4062
4252
  }
@@ -4069,7 +4259,62 @@ const stripDeletedTagIdsFromItem = ({ item, deletedTagIds }) => {
4069
4259
  item.tagIds = remainingTagIds;
4070
4260
  };
4071
4261
 
4072
- const stripDeletedTagIdsFromScopeItems = ({ state, scopeKey, deletedTagIds }) => {
4262
+ const stripDeletedTagIdsFromCharacterSpriteGroups = ({
4263
+ item,
4264
+ deletedTagIds,
4265
+ }) => {
4266
+ if (!Array.isArray(item?.spriteGroups) || item.spriteGroups.length === 0) {
4267
+ return;
4268
+ }
4269
+
4270
+ const nextSpriteGroups = [];
4271
+ let didChange = false;
4272
+
4273
+ for (const spriteGroup of item.spriteGroups) {
4274
+ const currentTags = Array.isArray(spriteGroup?.tags)
4275
+ ? spriteGroup.tags
4276
+ : [];
4277
+ const remainingTags = currentTags.filter(
4278
+ (tagId) => !deletedTagIds.has(tagId),
4279
+ );
4280
+
4281
+ if (remainingTags.length !== currentTags.length) {
4282
+ didChange = true;
4283
+ }
4284
+
4285
+ if (remainingTags.length === 0) {
4286
+ didChange = true;
4287
+ continue;
4288
+ }
4289
+
4290
+ if (remainingTags.length === currentTags.length) {
4291
+ nextSpriteGroups.push(spriteGroup);
4292
+ continue;
4293
+ }
4294
+
4295
+ nextSpriteGroups.push({
4296
+ ...spriteGroup,
4297
+ tags: remainingTags,
4298
+ });
4299
+ }
4300
+
4301
+ if (!didChange) {
4302
+ return;
4303
+ }
4304
+
4305
+ if (nextSpriteGroups.length === 0) {
4306
+ delete item.spriteGroups;
4307
+ return;
4308
+ }
4309
+
4310
+ item.spriteGroups = nextSpriteGroups;
4311
+ };
4312
+
4313
+ const stripDeletedTagIdsFromScopeItems = ({
4314
+ state,
4315
+ scopeKey,
4316
+ deletedTagIds,
4317
+ }) => {
4073
4318
  if (deletedTagIds.size === 0) {
4074
4319
  return;
4075
4320
  }
@@ -4119,16 +4364,105 @@ const stripDeletedTagIdsFromScopeItems = ({ state, scopeKey, deletedTagIds }) =>
4119
4364
  return;
4120
4365
  }
4121
4366
 
4367
+ if (scopeKey === "fonts") {
4368
+ for (const item of Object.values(state.fonts.items)) {
4369
+ if (item?.type === "font") {
4370
+ stripDeletedTagIdsFromItem({ item, deletedTagIds });
4371
+ }
4372
+ }
4373
+ return;
4374
+ }
4375
+
4376
+ if (scopeKey === "colors") {
4377
+ for (const item of Object.values(state.colors.items)) {
4378
+ if (item?.type === "color") {
4379
+ stripDeletedTagIdsFromItem({ item, deletedTagIds });
4380
+ }
4381
+ }
4382
+ return;
4383
+ }
4384
+
4385
+ if (scopeKey === "textStyles") {
4386
+ for (const item of Object.values(state.textStyles.items)) {
4387
+ if (item?.type === "textStyle") {
4388
+ stripDeletedTagIdsFromItem({ item, deletedTagIds });
4389
+ }
4390
+ }
4391
+ return;
4392
+ }
4393
+
4394
+ if (scopeKey === "variables") {
4395
+ for (const item of Object.values(state.variables.items)) {
4396
+ if (item?.type !== "folder") {
4397
+ stripDeletedTagIdsFromItem({ item, deletedTagIds });
4398
+ }
4399
+ }
4400
+ return;
4401
+ }
4402
+
4403
+ if (scopeKey === "layouts") {
4404
+ for (const item of Object.values(state.layouts.items)) {
4405
+ if (item?.type === "layout") {
4406
+ stripDeletedTagIdsFromItem({ item, deletedTagIds });
4407
+ }
4408
+ }
4409
+ return;
4410
+ }
4411
+
4412
+ if (scopeKey === "controls") {
4413
+ for (const item of Object.values(state.controls.items)) {
4414
+ if (item?.type === "control") {
4415
+ stripDeletedTagIdsFromItem({ item, deletedTagIds });
4416
+ }
4417
+ }
4418
+ return;
4419
+ }
4420
+
4421
+ if (scopeKey === "animations") {
4422
+ for (const item of Object.values(state.animations.items)) {
4423
+ if (item?.type === "animation") {
4424
+ stripDeletedTagIdsFromItem({ item, deletedTagIds });
4425
+ }
4426
+ }
4427
+ return;
4428
+ }
4429
+
4430
+ if (scopeKey === "particles") {
4431
+ for (const item of Object.values(state.particles.items)) {
4432
+ if (item?.type === "particle") {
4433
+ stripDeletedTagIdsFromItem({ item, deletedTagIds });
4434
+ }
4435
+ }
4436
+ return;
4437
+ }
4438
+
4439
+ if (scopeKey === "spritesheets") {
4440
+ for (const item of Object.values(state.spritesheets.items)) {
4441
+ if (item?.type === "spritesheet") {
4442
+ stripDeletedTagIdsFromItem({ item, deletedTagIds });
4443
+ }
4444
+ }
4445
+ return;
4446
+ }
4447
+
4122
4448
  if (!isCharacterSpriteTagScopeKey(scopeKey)) {
4123
4449
  return;
4124
4450
  }
4125
4451
 
4126
4452
  const characterId = getCharacterSpriteTagScopeCharacterId(scopeKey);
4453
+ const characterItem = state.characters?.items?.[characterId];
4127
4454
  const collection = getCharacterSpriteCollection({
4128
4455
  state,
4129
4456
  characterId,
4130
4457
  });
4131
4458
 
4459
+ if (characterItem?.type === "character") {
4460
+ stripDeletedTagIdsFromCharacterSpriteGroups({
4461
+ item: characterItem,
4462
+ deletedTagIds,
4463
+ });
4464
+ }
4465
+
4132
4466
  for (const item of Object.values(collection?.items || {})) {
4133
4467
  if (item?.type === "image") {
4134
4468
  stripDeletedTagIdsFromItem({ item, deletedTagIds });
@@ -4204,6 +4538,7 @@ const validateLayoutItems = ({ items, path, errorFactory }) => {
4204
4538
  "type",
4205
4539
  "name",
4206
4540
  "description",
4541
+ "tagIds",
4207
4542
  "layoutType",
4208
4543
  "isFragment",
4209
4544
  "thumbnailFileId",
@@ -4268,6 +4603,18 @@ const validateLayoutItems = ({ items, path, errorFactory }) => {
4268
4603
  }
4269
4604
 
4270
4605
  if (item.type === "layout") {
4606
+ {
4607
+ const result = validateOptionalUniqueIdArray({
4608
+ value: item.tagIds,
4609
+ path: `${itemPath}.tagIds`,
4610
+ errorFactory,
4611
+ allowEmpty: false,
4612
+ });
4613
+ if (result?.valid === false) {
4614
+ return result;
4615
+ }
4616
+ }
4617
+
4271
4618
  if (!LAYOUT_TYPE_KEYS.includes(item.layoutType)) {
4272
4619
  return invalidFromErrorFactory(
4273
4620
  errorFactory,
@@ -4323,6 +4670,7 @@ const validateControlItems = ({ items, path, errorFactory }) => {
4323
4670
  "type",
4324
4671
  "name",
4325
4672
  "description",
4673
+ "tagIds",
4326
4674
  "thumbnailFileId",
4327
4675
  "preview",
4328
4676
  "elements",
@@ -4387,9 +4735,21 @@ const validateControlItems = ({ items, path, errorFactory }) => {
4387
4735
 
4388
4736
  if (item.type === "control") {
4389
4737
  {
4390
- const result = validateNestedCollection({
4391
- collection: item.elements,
4392
- path: `${itemPath}.elements`,
4738
+ const result = validateOptionalUniqueIdArray({
4739
+ value: item.tagIds,
4740
+ path: `${itemPath}.tagIds`,
4741
+ errorFactory,
4742
+ allowEmpty: false,
4743
+ });
4744
+ if (result?.valid === false) {
4745
+ return result;
4746
+ }
4747
+ }
4748
+
4749
+ {
4750
+ const result = validateNestedCollection({
4751
+ collection: item.elements,
4752
+ path: `${itemPath}.elements`,
4393
4753
  itemValidator: validateLayoutElementItems,
4394
4754
  treeValidator: validateLayoutElementTreeOwnership,
4395
4755
  treeNodeLabel: "control element",
@@ -5501,6 +5861,22 @@ export const assertInvariants = ({ state }) => {
5501
5861
  return result;
5502
5862
  }
5503
5863
 
5864
+ {
5865
+ const tagResult = validateTagIdsAgainstScope({
5866
+ state,
5867
+ tagIds: spritesheet.tagIds,
5868
+ scopeKey: "spritesheets",
5869
+ path: "spritesheet.tagIds",
5870
+ details: {
5871
+ spritesheetId,
5872
+ },
5873
+ errorFactory: createInvariantValidationError,
5874
+ });
5875
+ if (!tagResult.valid) {
5876
+ return tagResult;
5877
+ }
5878
+ }
5879
+
5504
5880
  if (spritesheet.thumbnailFileId === undefined) {
5505
5881
  continue;
5506
5882
  }
@@ -5624,6 +6000,22 @@ export const assertInvariants = ({ state }) => {
5624
6000
  continue;
5625
6001
  }
5626
6002
 
6003
+ {
6004
+ const result = validateTagIdsAgainstScope({
6005
+ state,
6006
+ tagIds: font.tagIds,
6007
+ scopeKey: "fonts",
6008
+ path: "font.tagIds",
6009
+ details: {
6010
+ fontId,
6011
+ },
6012
+ errorFactory: createInvariantValidationError,
6013
+ });
6014
+ if (!result.valid) {
6015
+ return result;
6016
+ }
6017
+ }
6018
+
5627
6019
  const result = validateFileReference({
5628
6020
  state,
5629
6021
  fileId: font.fileId,
@@ -5643,6 +6035,22 @@ export const assertInvariants = ({ state }) => {
5643
6035
  continue;
5644
6036
  }
5645
6037
 
6038
+ {
6039
+ const tagResult = validateTagIdsAgainstScope({
6040
+ state,
6041
+ tagIds: animation.tagIds,
6042
+ scopeKey: "animations",
6043
+ path: "animation.tagIds",
6044
+ details: {
6045
+ animationId,
6046
+ },
6047
+ errorFactory: createInvariantValidationError,
6048
+ });
6049
+ if (!tagResult.valid) {
6050
+ return tagResult;
6051
+ }
6052
+ }
6053
+
5646
6054
  const result = validateAnimationMaskImageReferences({
5647
6055
  state,
5648
6056
  animation: animation.animation,
@@ -5691,6 +6099,22 @@ export const assertInvariants = ({ state }) => {
5691
6099
  }
5692
6100
  }
5693
6101
 
6102
+ {
6103
+ const result = validateCharacterSpriteGroupsAgainstScope({
6104
+ state,
6105
+ spriteGroups: character.spriteGroups,
6106
+ scopeKey: `${CHARACTER_SPRITE_TAG_SCOPE_PREFIX}${characterId}`,
6107
+ path: "character.spriteGroups",
6108
+ details: {
6109
+ characterId,
6110
+ },
6111
+ errorFactory: createInvariantValidationError,
6112
+ });
6113
+ if (!result.valid) {
6114
+ return result;
6115
+ }
6116
+ }
6117
+
5694
6118
  for (const [spriteId, sprite] of Object.entries(
5695
6119
  character.sprites?.items || {},
5696
6120
  )) {
@@ -5771,25 +6195,153 @@ export const assertInvariants = ({ state }) => {
5771
6195
  }
5772
6196
  }
5773
6197
 
6198
+ for (const [particleId, particle] of Object.entries(state.particles.items)) {
6199
+ if (particle.type !== "particle") {
6200
+ continue;
6201
+ }
6202
+
6203
+ {
6204
+ const result = validateTagIdsAgainstScope({
6205
+ state,
6206
+ tagIds: particle.tagIds,
6207
+ scopeKey: "particles",
6208
+ path: "particle.tagIds",
6209
+ details: {
6210
+ particleId,
6211
+ },
6212
+ errorFactory: createInvariantValidationError,
6213
+ });
6214
+ if (!result.valid) {
6215
+ return result;
6216
+ }
6217
+ }
6218
+ }
6219
+
6220
+ for (const [colorId, color] of Object.entries(state.colors.items)) {
6221
+ if (color.type !== "color") {
6222
+ continue;
6223
+ }
6224
+
6225
+ {
6226
+ const result = validateTagIdsAgainstScope({
6227
+ state,
6228
+ tagIds: color.tagIds,
6229
+ scopeKey: "colors",
6230
+ path: "color.tagIds",
6231
+ details: {
6232
+ colorId,
6233
+ },
6234
+ errorFactory: createInvariantValidationError,
6235
+ });
6236
+ if (!result.valid) {
6237
+ return result;
6238
+ }
6239
+ }
6240
+ }
6241
+
6242
+ for (const [textStyleId, textStyle] of Object.entries(
6243
+ state.textStyles.items,
6244
+ )) {
6245
+ if (textStyle.type !== "textStyle") {
6246
+ continue;
6247
+ }
6248
+
6249
+ {
6250
+ const result = validateTagIdsAgainstScope({
6251
+ state,
6252
+ tagIds: textStyle.tagIds,
6253
+ scopeKey: "textStyles",
6254
+ path: "textStyle.tagIds",
6255
+ details: {
6256
+ textStyleId,
6257
+ },
6258
+ errorFactory: createInvariantValidationError,
6259
+ });
6260
+ if (!result.valid) {
6261
+ return result;
6262
+ }
6263
+ }
6264
+ }
6265
+
5774
6266
  for (const [layoutId, layout] of Object.entries(state.layouts.items)) {
5775
- if (layout.type !== "layout" || layout.thumbnailFileId === undefined) {
6267
+ if (layout.type !== "layout") {
5776
6268
  continue;
5777
6269
  }
5778
6270
 
5779
- const result = validateFileReference({
5780
- state,
5781
- fileId: layout.thumbnailFileId,
5782
- path: "layout.thumbnailFileId",
5783
- details: { layoutId, thumbnailFileId: layout.thumbnailFileId },
5784
- errorFactory: createInvariantValidationError,
5785
- });
5786
- if (!result.valid) {
5787
- return result;
6271
+ {
6272
+ const result = validateTagIdsAgainstScope({
6273
+ state,
6274
+ tagIds: layout.tagIds,
6275
+ scopeKey: "layouts",
6276
+ path: "layout.tagIds",
6277
+ details: {
6278
+ layoutId,
6279
+ },
6280
+ errorFactory: createInvariantValidationError,
6281
+ });
6282
+ if (!result.valid) {
6283
+ return result;
6284
+ }
6285
+ }
6286
+
6287
+ if (layout.thumbnailFileId !== undefined) {
6288
+ const result = validateFileReference({
6289
+ state,
6290
+ fileId: layout.thumbnailFileId,
6291
+ path: "layout.thumbnailFileId",
6292
+ details: { layoutId, thumbnailFileId: layout.thumbnailFileId },
6293
+ errorFactory: createInvariantValidationError,
6294
+ });
6295
+ if (!result.valid) {
6296
+ return result;
6297
+ }
6298
+ }
6299
+ }
6300
+
6301
+ for (const [variableId, variable] of Object.entries(state.variables.items)) {
6302
+ if (variable.type === "folder") {
6303
+ continue;
6304
+ }
6305
+
6306
+ {
6307
+ const result = validateTagIdsAgainstScope({
6308
+ state,
6309
+ tagIds: variable.tagIds,
6310
+ scopeKey: "variables",
6311
+ path: "variable.tagIds",
6312
+ details: {
6313
+ variableId,
6314
+ },
6315
+ errorFactory: createInvariantValidationError,
6316
+ });
6317
+ if (!result.valid) {
6318
+ return result;
6319
+ }
5788
6320
  }
5789
6321
  }
5790
6322
 
5791
6323
  for (const [controlId, control] of Object.entries(state.controls.items)) {
5792
- if (control.type !== "control" || control.thumbnailFileId === undefined) {
6324
+ if (control.type !== "control") {
6325
+ continue;
6326
+ }
6327
+
6328
+ {
6329
+ const result = validateTagIdsAgainstScope({
6330
+ state,
6331
+ tagIds: control.tagIds,
6332
+ scopeKey: "controls",
6333
+ path: "control.tagIds",
6334
+ details: {
6335
+ controlId,
6336
+ },
6337
+ errorFactory: createInvariantValidationError,
6338
+ });
6339
+ if (!result.valid) {
6340
+ return result;
6341
+ }
6342
+ }
6343
+
6344
+ if (control.thumbnailFileId === undefined) {
5793
6345
  continue;
5794
6346
  }
5795
6347
 
@@ -5853,6 +6405,30 @@ export const assertInvariants = ({ state }) => {
5853
6405
  return VALID_RESULT;
5854
6406
  };
5855
6407
 
6408
+ const assertSoundReference = ({
6409
+ ownerIdField,
6410
+ ownerId,
6411
+ ownerLabel,
6412
+ elementId,
6413
+ field,
6414
+ targetId,
6415
+ }) => {
6416
+ const sound = state.sounds.items[targetId];
6417
+ if (!isPlainObject(sound) || sound.type === "folder") {
6418
+ return invalidInvariant(
6419
+ `${ownerLabel} element ${field} must reference an existing non-folder sound`,
6420
+ {
6421
+ [ownerIdField]: ownerId,
6422
+ elementId,
6423
+ field,
6424
+ targetId,
6425
+ },
6426
+ );
6427
+ }
6428
+
6429
+ return VALID_RESULT;
6430
+ };
6431
+
5856
6432
  const assertVariableReference = ({
5857
6433
  ownerIdField,
5858
6434
  ownerId,
@@ -6029,6 +6605,22 @@ export const assertInvariants = ({ state }) => {
6029
6605
  }
6030
6606
  }
6031
6607
 
6608
+ for (const field of ["hoverSoundId", "clickSoundId"]) {
6609
+ if (element[field] !== undefined) {
6610
+ const result = assertSoundReference({
6611
+ ownerIdField,
6612
+ ownerId,
6613
+ ownerLabel,
6614
+ elementId,
6615
+ field,
6616
+ targetId: element[field],
6617
+ });
6618
+ if (!result.valid) {
6619
+ return result;
6620
+ }
6621
+ }
6622
+ }
6623
+
6032
6624
  for (const field of [
6033
6625
  "textStyleId",
6034
6626
  "hoverTextStyleId",
@@ -6057,6 +6649,22 @@ export const assertInvariants = ({ state }) => {
6057
6649
  ) {
6058
6650
  const rule = element.conditionalOverrides[index];
6059
6651
 
6652
+ for (const field of ["hoverSoundId", "clickSoundId"]) {
6653
+ if (rule?.set?.[field] !== undefined) {
6654
+ const result = assertSoundReference({
6655
+ ownerIdField,
6656
+ ownerId,
6657
+ ownerLabel,
6658
+ elementId,
6659
+ field: `conditionalOverrides.${index}.set.${field}`,
6660
+ targetId: rule.set[field],
6661
+ });
6662
+ if (!result.valid) {
6663
+ return result;
6664
+ }
6665
+ }
6666
+ }
6667
+
6060
6668
  for (const field of [
6061
6669
  "textStyleId",
6062
6670
  "hoverTextStyleId",
@@ -6494,41 +7102,132 @@ const validateOptionalUniqueIdArray = ({
6494
7102
  }
6495
7103
  };
6496
7104
 
6497
- const validateSectionCreateData = ({ data, errorFactory }) => {
6498
- {
6499
- const result = validateExactKeys({
6500
- value: data,
6501
- expectedKeys: ["name"],
6502
- path: "payload.data",
6503
- errorFactory,
6504
- });
6505
- if (result?.valid === false) {
6506
- return result;
6507
- }
7105
+ const validateCharacterSpriteGroups = ({
7106
+ value,
7107
+ path,
7108
+ errorFactory,
7109
+ allowEmpty = true,
7110
+ allowMissingId = false,
7111
+ }) => {
7112
+ if (value === undefined) {
7113
+ return VALID_RESULT;
6508
7114
  }
6509
7115
 
6510
- if (!isNonEmptyString(data.name)) {
7116
+ if (!Array.isArray(value) || (!allowEmpty && value.length === 0)) {
6511
7117
  return invalidFromErrorFactory(
6512
7118
  errorFactory,
6513
- "payload.data.name must be a non-empty string",
7119
+ allowEmpty
7120
+ ? `${path} must be an array when provided`
7121
+ : `${path} must be a non-empty array when provided`,
6514
7122
  );
6515
7123
  }
6516
- };
6517
7124
 
6518
- const validateSectionUpdateData = ({ data, errorFactory }) => {
6519
- {
6520
- const result = validateExactKeys({
6521
- value: data,
6522
- expectedKeys: ["name"],
6523
- path: "payload.data",
6524
- errorFactory,
6525
- });
6526
- if (result?.valid === false) {
6527
- return result;
7125
+ const seenIds = new Set();
7126
+
7127
+ for (const [index, spriteGroup] of value.entries()) {
7128
+ const itemPath = `${path}[${index}]`;
7129
+
7130
+ if (!isPlainObject(spriteGroup)) {
7131
+ return invalidFromErrorFactory(
7132
+ errorFactory,
7133
+ `${itemPath} must be an object`,
7134
+ );
6528
7135
  }
6529
- }
6530
7136
 
6531
- if (!isNonEmptyString(data.name)) {
7137
+ {
7138
+ const result = validateAllowedKeys({
7139
+ value: spriteGroup,
7140
+ allowedKeys: ["id", "name", "tags"],
7141
+ path: itemPath,
7142
+ errorFactory,
7143
+ });
7144
+ if (result?.valid === false) {
7145
+ return result;
7146
+ }
7147
+ }
7148
+
7149
+ if (spriteGroup.id === undefined) {
7150
+ if (!allowMissingId) {
7151
+ return invalidFromErrorFactory(
7152
+ errorFactory,
7153
+ `${itemPath}.id must be a non-empty string`,
7154
+ );
7155
+ }
7156
+ } else {
7157
+ if (!isNonEmptyString(spriteGroup.id)) {
7158
+ return invalidFromErrorFactory(
7159
+ errorFactory,
7160
+ `${itemPath}.id must be a non-empty string`,
7161
+ );
7162
+ }
7163
+
7164
+ if (seenIds.has(spriteGroup.id)) {
7165
+ return invalidFromErrorFactory(
7166
+ errorFactory,
7167
+ `${itemPath}.id must be unique within spriteGroups`,
7168
+ );
7169
+ }
7170
+
7171
+ seenIds.add(spriteGroup.id);
7172
+ }
7173
+
7174
+ if (!isNonEmptyString(spriteGroup.name)) {
7175
+ return invalidFromErrorFactory(
7176
+ errorFactory,
7177
+ `${itemPath}.name must be a non-empty string`,
7178
+ );
7179
+ }
7180
+
7181
+ {
7182
+ const result = validateRequiredUniqueIdArray({
7183
+ value: spriteGroup.tags,
7184
+ path: `${itemPath}.tags`,
7185
+ errorFactory,
7186
+ });
7187
+ if (result?.valid === false) {
7188
+ return result;
7189
+ }
7190
+ }
7191
+ }
7192
+
7193
+ return VALID_RESULT;
7194
+ };
7195
+
7196
+ const validateSectionCreateData = ({ data, errorFactory }) => {
7197
+ {
7198
+ const result = validateExactKeys({
7199
+ value: data,
7200
+ expectedKeys: ["name"],
7201
+ path: "payload.data",
7202
+ errorFactory,
7203
+ });
7204
+ if (result?.valid === false) {
7205
+ return result;
7206
+ }
7207
+ }
7208
+
7209
+ if (!isNonEmptyString(data.name)) {
7210
+ return invalidFromErrorFactory(
7211
+ errorFactory,
7212
+ "payload.data.name must be a non-empty string",
7213
+ );
7214
+ }
7215
+ };
7216
+
7217
+ const validateSectionUpdateData = ({ data, errorFactory }) => {
7218
+ {
7219
+ const result = validateExactKeys({
7220
+ value: data,
7221
+ expectedKeys: ["name"],
7222
+ path: "payload.data",
7223
+ errorFactory,
7224
+ });
7225
+ if (result?.valid === false) {
7226
+ return result;
7227
+ }
7228
+ }
7229
+
7230
+ if (!isNonEmptyString(data.name)) {
6532
7231
  return invalidFromErrorFactory(
6533
7232
  errorFactory,
6534
7233
  "payload.data.name must be a non-empty string",
@@ -6899,6 +7598,7 @@ const validateSpritesheetCreateData = ({ data, errorFactory }) => {
6899
7598
  "type",
6900
7599
  "name",
6901
7600
  "description",
7601
+ "tagIds",
6902
7602
  "thumbnailFileId",
6903
7603
  "fileId",
6904
7604
  "sheetWidth",
@@ -6932,6 +7632,17 @@ const validateSpritesheetCreateData = ({ data, errorFactory }) => {
6932
7632
  }
6933
7633
 
6934
7634
  if (data.type === "spritesheet") {
7635
+ {
7636
+ const result = validateOptionalUniqueIdArray({
7637
+ value: data.tagIds,
7638
+ path: "payload.data.tagIds",
7639
+ errorFactory,
7640
+ });
7641
+ if (result?.valid === false) {
7642
+ return result;
7643
+ }
7644
+ }
7645
+
6935
7646
  if (
6936
7647
  data.thumbnailFileId !== undefined &&
6937
7648
  !isNonEmptyString(data.thumbnailFileId)
@@ -6991,6 +7702,7 @@ const validateSpritesheetUpdateData = ({ data, errorFactory }) => {
6991
7702
  allowedKeys: [
6992
7703
  "name",
6993
7704
  "description",
7705
+ "tagIds",
6994
7706
  "thumbnailFileId",
6995
7707
  "fileId",
6996
7708
  "sheetWidth",
@@ -7030,6 +7742,17 @@ const validateSpritesheetUpdateData = ({ data, errorFactory }) => {
7030
7742
  );
7031
7743
  }
7032
7744
 
7745
+ {
7746
+ const result = validateOptionalUniqueIdArray({
7747
+ value: data.tagIds,
7748
+ path: "payload.data.tagIds",
7749
+ errorFactory,
7750
+ });
7751
+ if (result?.valid === false) {
7752
+ return result;
7753
+ }
7754
+ }
7755
+
7033
7756
  if (
7034
7757
  data.thumbnailFileId !== undefined &&
7035
7758
  !isNonEmptyString(data.thumbnailFileId)
@@ -7467,13 +8190,7 @@ const validateFontCreateData = ({ data, errorFactory }) => {
7467
8190
  allowedKeys:
7468
8191
  data.type === "folder"
7469
8192
  ? ["type", "name", "description"]
7470
- : [
7471
- "type",
7472
- "name",
7473
- "description",
7474
- "fileId",
7475
- "fontFamily",
7476
- ],
8193
+ : ["type", "name", "description", "tagIds", "fileId", "fontFamily"],
7477
8194
  path: "payload.data",
7478
8195
  errorFactory,
7479
8196
  });
@@ -7497,6 +8214,17 @@ const validateFontCreateData = ({ data, errorFactory }) => {
7497
8214
  }
7498
8215
 
7499
8216
  if (data.type === "font") {
8217
+ {
8218
+ const result = validateOptionalUniqueIdArray({
8219
+ value: data.tagIds,
8220
+ path: "payload.data.tagIds",
8221
+ errorFactory,
8222
+ });
8223
+ if (result?.valid === false) {
8224
+ return result;
8225
+ }
8226
+ }
8227
+
7500
8228
  if (!isNonEmptyString(data.fileId)) {
7501
8229
  return invalidFromErrorFactory(
7502
8230
  errorFactory,
@@ -7510,7 +8238,6 @@ const validateFontCreateData = ({ data, errorFactory }) => {
7510
8238
  "payload.data.fontFamily must be a non-empty string",
7511
8239
  );
7512
8240
  }
7513
-
7514
8241
  }
7515
8242
  };
7516
8243
 
@@ -7518,12 +8245,7 @@ const validateFontUpdateData = ({ data, errorFactory }) => {
7518
8245
  {
7519
8246
  const result = validateAllowedKeys({
7520
8247
  value: data,
7521
- allowedKeys: [
7522
- "name",
7523
- "description",
7524
- "fileId",
7525
- "fontFamily",
7526
- ],
8248
+ allowedKeys: ["name", "description", "tagIds", "fileId", "fontFamily"],
7527
8249
  path: "payload.data",
7528
8250
  errorFactory,
7529
8251
  });
@@ -7553,6 +8275,17 @@ const validateFontUpdateData = ({ data, errorFactory }) => {
7553
8275
  );
7554
8276
  }
7555
8277
 
8278
+ {
8279
+ const result = validateOptionalUniqueIdArray({
8280
+ value: data.tagIds,
8281
+ path: "payload.data.tagIds",
8282
+ errorFactory,
8283
+ });
8284
+ if (result?.valid === false) {
8285
+ return result;
8286
+ }
8287
+ }
8288
+
7556
8289
  if (data.fileId !== undefined && !isNonEmptyString(data.fileId)) {
7557
8290
  return invalidFromErrorFactory(
7558
8291
  errorFactory,
@@ -7566,7 +8299,6 @@ const validateFontUpdateData = ({ data, errorFactory }) => {
7566
8299
  "payload.data.fontFamily must be a non-empty string when provided",
7567
8300
  );
7568
8301
  }
7569
-
7570
8302
  };
7571
8303
 
7572
8304
  const validateFileCreateData = ({ data, errorFactory }) => {
@@ -7690,7 +8422,7 @@ const validateColorCreateData = ({ data, errorFactory }) => {
7690
8422
  allowedKeys:
7691
8423
  data.type === "folder"
7692
8424
  ? ["type", "name", "description"]
7693
- : ["type", "name", "description", "hex"],
8425
+ : ["type", "name", "description", "tagIds", "hex"],
7694
8426
  path: "payload.data",
7695
8427
  errorFactory,
7696
8428
  });
@@ -7713,11 +8445,24 @@ const validateColorCreateData = ({ data, errorFactory }) => {
7713
8445
  );
7714
8446
  }
7715
8447
 
7716
- if (data.type === "color" && !isHexColor(data.hex)) {
7717
- return invalidFromErrorFactory(
7718
- errorFactory,
7719
- "payload.data.hex must be a #RRGGBB string",
7720
- );
8448
+ if (data.type === "color") {
8449
+ {
8450
+ const result = validateOptionalUniqueIdArray({
8451
+ value: data.tagIds,
8452
+ path: "payload.data.tagIds",
8453
+ errorFactory,
8454
+ });
8455
+ if (result?.valid === false) {
8456
+ return result;
8457
+ }
8458
+ }
8459
+
8460
+ if (!isHexColor(data.hex)) {
8461
+ return invalidFromErrorFactory(
8462
+ errorFactory,
8463
+ "payload.data.hex must be a #RRGGBB string",
8464
+ );
8465
+ }
7721
8466
  }
7722
8467
  };
7723
8468
 
@@ -7725,7 +8470,7 @@ const validateColorUpdateData = ({ data, errorFactory }) => {
7725
8470
  {
7726
8471
  const result = validateAllowedKeys({
7727
8472
  value: data,
7728
- allowedKeys: ["name", "description", "hex"],
8473
+ allowedKeys: ["name", "description", "tagIds", "hex"],
7729
8474
  path: "payload.data",
7730
8475
  errorFactory,
7731
8476
  });
@@ -7755,6 +8500,17 @@ const validateColorUpdateData = ({ data, errorFactory }) => {
7755
8500
  );
7756
8501
  }
7757
8502
 
8503
+ {
8504
+ const result = validateOptionalUniqueIdArray({
8505
+ value: data.tagIds,
8506
+ path: "payload.data.tagIds",
8507
+ errorFactory,
8508
+ });
8509
+ if (result?.valid === false) {
8510
+ return result;
8511
+ }
8512
+ }
8513
+
7758
8514
  if (data.hex !== undefined && !isHexColor(data.hex)) {
7759
8515
  return invalidFromErrorFactory(
7760
8516
  errorFactory,
@@ -7784,7 +8540,7 @@ const validateAnimationCreateData = ({ data, errorFactory }) => {
7784
8540
  allowedKeys:
7785
8541
  data.type === "folder"
7786
8542
  ? ["type", "name", "description"]
7787
- : ["type", "name", "description", "animation"],
8543
+ : ["type", "name", "description", "tagIds", "animation"],
7788
8544
  path: "payload.data",
7789
8545
  errorFactory,
7790
8546
  });
@@ -7808,6 +8564,17 @@ const validateAnimationCreateData = ({ data, errorFactory }) => {
7808
8564
  }
7809
8565
 
7810
8566
  if (data.type === "animation") {
8567
+ {
8568
+ const result = validateOptionalUniqueIdArray({
8569
+ value: data.tagIds,
8570
+ path: "payload.data.tagIds",
8571
+ errorFactory,
8572
+ });
8573
+ if (result?.valid === false) {
8574
+ return result;
8575
+ }
8576
+ }
8577
+
7811
8578
  {
7812
8579
  const result = validateAnimationDefinition({
7813
8580
  animation: data.animation,
@@ -7825,7 +8592,7 @@ const validateAnimationUpdateData = ({ data, errorFactory }) => {
7825
8592
  {
7826
8593
  const result = validateAllowedKeys({
7827
8594
  value: data,
7828
- allowedKeys: ["name", "description", "animation"],
8595
+ allowedKeys: ["name", "description", "tagIds", "animation"],
7829
8596
  path: "payload.data",
7830
8597
  errorFactory,
7831
8598
  });
@@ -7855,6 +8622,17 @@ const validateAnimationUpdateData = ({ data, errorFactory }) => {
7855
8622
  );
7856
8623
  }
7857
8624
 
8625
+ {
8626
+ const result = validateOptionalUniqueIdArray({
8627
+ value: data.tagIds,
8628
+ path: "payload.data.tagIds",
8629
+ errorFactory,
8630
+ });
8631
+ if (result?.valid === false) {
8632
+ return result;
8633
+ }
8634
+ }
8635
+
7858
8636
  if (data.animation !== undefined) {
7859
8637
  {
7860
8638
  const result = validateAnimationDefinition({
@@ -7981,6 +8759,7 @@ const validateParticleCreateData = ({ data, errorFactory }) => {
7981
8759
  "type",
7982
8760
  "name",
7983
8761
  "description",
8762
+ "tagIds",
7984
8763
  "width",
7985
8764
  "height",
7986
8765
  "seed",
@@ -8012,6 +8791,17 @@ const validateParticleCreateData = ({ data, errorFactory }) => {
8012
8791
  return;
8013
8792
  }
8014
8793
 
8794
+ {
8795
+ const result = validateOptionalUniqueIdArray({
8796
+ value: data.tagIds,
8797
+ path: "payload.data.tagIds",
8798
+ errorFactory,
8799
+ });
8800
+ if (result?.valid === false) {
8801
+ return result;
8802
+ }
8803
+ }
8804
+
8015
8805
  if (!isFiniteNumber(data.width) || data.width <= 0) {
8016
8806
  return invalidFromErrorFactory(
8017
8807
  errorFactory,
@@ -8056,6 +8846,7 @@ const validateParticleUpdateData = ({ data, errorFactory }) => {
8056
8846
  allowedKeys: [
8057
8847
  "name",
8058
8848
  "description",
8849
+ "tagIds",
8059
8850
  "width",
8060
8851
  "height",
8061
8852
  "seed",
@@ -8090,6 +8881,17 @@ const validateParticleUpdateData = ({ data, errorFactory }) => {
8090
8881
  );
8091
8882
  }
8092
8883
 
8884
+ {
8885
+ const result = validateOptionalUniqueIdArray({
8886
+ value: data.tagIds,
8887
+ path: "payload.data.tagIds",
8888
+ errorFactory,
8889
+ });
8890
+ if (result?.valid === false) {
8891
+ return result;
8892
+ }
8893
+ }
8894
+
8093
8895
  if (
8094
8896
  data.width !== undefined &&
8095
8897
  (!isFiniteNumber(data.width) || data.width <= 0)
@@ -8230,7 +9032,15 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
8230
9032
  allowedKeys:
8231
9033
  data.type === "folder"
8232
9034
  ? ["type", "name", "description"]
8233
- : ["type", "name", "description", "scope", "default", "value"],
9035
+ : [
9036
+ "type",
9037
+ "name",
9038
+ "description",
9039
+ "tagIds",
9040
+ "scope",
9041
+ "default",
9042
+ "value",
9043
+ ],
8234
9044
  path: "payload.data",
8235
9045
  errorFactory,
8236
9046
  });
@@ -8254,6 +9064,18 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
8254
9064
  }
8255
9065
 
8256
9066
  if (data.type !== "folder") {
9067
+ {
9068
+ const result = validateOptionalUniqueIdArray({
9069
+ value: data.tagIds,
9070
+ path: "payload.data.tagIds",
9071
+ errorFactory,
9072
+ allowEmpty: false,
9073
+ });
9074
+ if (result?.valid === false) {
9075
+ return result;
9076
+ }
9077
+ }
9078
+
8257
9079
  if (!VARIABLE_SCOPE_KEYS.includes(data.scope)) {
8258
9080
  return invalidFromErrorFactory(
8259
9081
  errorFactory,
@@ -8290,7 +9112,14 @@ const validateVariableUpdateData = ({ data, errorFactory }) => {
8290
9112
  {
8291
9113
  const result = validateAllowedKeys({
8292
9114
  value: data,
8293
- allowedKeys: ["name", "description", "scope", "default", "value"],
9115
+ allowedKeys: [
9116
+ "name",
9117
+ "description",
9118
+ "tagIds",
9119
+ "scope",
9120
+ "default",
9121
+ "value",
9122
+ ],
8294
9123
  path: "payload.data",
8295
9124
  errorFactory,
8296
9125
  });
@@ -8326,6 +9155,18 @@ const validateVariableUpdateData = ({ data, errorFactory }) => {
8326
9155
  "payload.data.scope must be 'context', 'device', or 'account' when provided",
8327
9156
  );
8328
9157
  }
9158
+
9159
+ {
9160
+ const result = validateOptionalUniqueIdArray({
9161
+ value: data.tagIds,
9162
+ path: "payload.data.tagIds",
9163
+ errorFactory,
9164
+ allowEmpty: false,
9165
+ });
9166
+ if (result?.valid === false) {
9167
+ return result;
9168
+ }
9169
+ }
8329
9170
  };
8330
9171
 
8331
9172
  const validateTextStyleCreateData = ({ data, errorFactory }) => {
@@ -8353,6 +9194,7 @@ const validateTextStyleCreateData = ({ data, errorFactory }) => {
8353
9194
  "type",
8354
9195
  "name",
8355
9196
  "description",
9197
+ "tagIds",
8356
9198
  "fontId",
8357
9199
  "colorId",
8358
9200
  "fontSize",
@@ -8392,14 +9234,25 @@ const validateTextStyleCreateData = ({ data, errorFactory }) => {
8392
9234
 
8393
9235
  if (data.type === "textStyle") {
8394
9236
  {
8395
- const result = validateTextStyleItems({
8396
- items: {
8397
- draft: {
8398
- id: "draft",
8399
- ...structuredClone(data),
8400
- },
8401
- },
8402
- path: "payload.data",
9237
+ const result = validateOptionalUniqueIdArray({
9238
+ value: data.tagIds,
9239
+ path: "payload.data.tagIds",
9240
+ errorFactory,
9241
+ });
9242
+ if (result?.valid === false) {
9243
+ return result;
9244
+ }
9245
+ }
9246
+
9247
+ {
9248
+ const result = validateTextStyleItems({
9249
+ items: {
9250
+ draft: {
9251
+ id: "draft",
9252
+ ...structuredClone(data),
9253
+ },
9254
+ },
9255
+ path: "payload.data",
8403
9256
  errorFactory,
8404
9257
  });
8405
9258
  if (result?.valid === false) {
@@ -8416,6 +9269,7 @@ const validateTextStyleUpdateData = ({ data, errorFactory }) => {
8416
9269
  allowedKeys: [
8417
9270
  "name",
8418
9271
  "description",
9272
+ "tagIds",
8419
9273
  "fontId",
8420
9274
  "colorId",
8421
9275
  "fontSize",
@@ -8460,6 +9314,17 @@ const validateTextStyleUpdateData = ({ data, errorFactory }) => {
8460
9314
  );
8461
9315
  }
8462
9316
 
9317
+ {
9318
+ const result = validateOptionalUniqueIdArray({
9319
+ value: data.tagIds,
9320
+ path: "payload.data.tagIds",
9321
+ errorFactory,
9322
+ });
9323
+ if (result?.valid === false) {
9324
+ return result;
9325
+ }
9326
+ }
9327
+
8463
9328
  for (const key of ["fontId", "colorId", "strokeColorId"]) {
8464
9329
  if (data[key] !== undefined && !isNonEmptyString(data[key])) {
8465
9330
  return invalidFromErrorFactory(
@@ -8728,6 +9593,7 @@ const validateCharacterCreateData = ({ data, errorFactory }) => {
8728
9593
  "name",
8729
9594
  "description",
8730
9595
  "tagIds",
9596
+ "spriteGroups",
8731
9597
  "shortcut",
8732
9598
  "fileId",
8733
9599
  "sprites",
@@ -8766,6 +9632,17 @@ const validateCharacterCreateData = ({ data, errorFactory }) => {
8766
9632
  }
8767
9633
  }
8768
9634
 
9635
+ {
9636
+ const result = validateCharacterSpriteGroups({
9637
+ value: data.spriteGroups,
9638
+ path: "payload.data.spriteGroups",
9639
+ errorFactory,
9640
+ });
9641
+ if (result?.valid === false) {
9642
+ return result;
9643
+ }
9644
+ }
9645
+
8769
9646
  if (data.shortcut !== undefined && !isString(data.shortcut)) {
8770
9647
  return invalidFromErrorFactory(
8771
9648
  errorFactory,
@@ -8812,6 +9689,7 @@ const validateCharacterUpdateData = ({ data, errorFactory }) => {
8812
9689
  "name",
8813
9690
  "description",
8814
9691
  "tagIds",
9692
+ "spriteGroups",
8815
9693
  "shortcut",
8816
9694
  "fileId",
8817
9695
  ],
@@ -8862,13 +9740,23 @@ const validateCharacterUpdateData = ({ data, errorFactory }) => {
8862
9740
  }
8863
9741
  }
8864
9742
 
9743
+ {
9744
+ const result = validateCharacterSpriteGroups({
9745
+ value: data.spriteGroups,
9746
+ path: "payload.data.spriteGroups",
9747
+ errorFactory,
9748
+ });
9749
+ if (result?.valid === false) {
9750
+ return result;
9751
+ }
9752
+ }
9753
+
8865
9754
  if (data.fileId !== undefined && !isNonEmptyString(data.fileId)) {
8866
9755
  return invalidFromErrorFactory(
8867
9756
  errorFactory,
8868
9757
  "payload.data.fileId must be a non-empty string when provided",
8869
9758
  );
8870
9759
  }
8871
-
8872
9760
  };
8873
9761
 
8874
9762
  const validateLayoutCreateData = ({ data, errorFactory }) => {
@@ -8896,6 +9784,7 @@ const validateLayoutCreateData = ({ data, errorFactory }) => {
8896
9784
  "type",
8897
9785
  "name",
8898
9786
  "description",
9787
+ "tagIds",
8899
9788
  "layoutType",
8900
9789
  "isFragment",
8901
9790
  "thumbnailFileId",
@@ -8946,6 +9835,17 @@ const validateLayoutCreateData = ({ data, errorFactory }) => {
8946
9835
  }
8947
9836
 
8948
9837
  if (data.type === "layout") {
9838
+ {
9839
+ const result = validateOptionalUniqueIdArray({
9840
+ value: data.tagIds,
9841
+ path: "payload.data.tagIds",
9842
+ errorFactory,
9843
+ });
9844
+ if (result?.valid === false) {
9845
+ return result;
9846
+ }
9847
+ }
9848
+
8949
9849
  if (!LAYOUT_TYPE_KEYS.includes(data.layoutType)) {
8950
9850
  return invalidFromErrorFactory(
8951
9851
  errorFactory,
@@ -8982,6 +9882,7 @@ const validateLayoutUpdateData = ({ data, errorFactory }) => {
8982
9882
  allowedKeys: [
8983
9883
  "name",
8984
9884
  "description",
9885
+ "tagIds",
8985
9886
  "layoutType",
8986
9887
  "isFragment",
8987
9888
  "thumbnailFileId",
@@ -9016,6 +9917,17 @@ const validateLayoutUpdateData = ({ data, errorFactory }) => {
9016
9917
  );
9017
9918
  }
9018
9919
 
9920
+ {
9921
+ const result = validateOptionalUniqueIdArray({
9922
+ value: data.tagIds,
9923
+ path: "payload.data.tagIds",
9924
+ errorFactory,
9925
+ });
9926
+ if (result?.valid === false) {
9927
+ return result;
9928
+ }
9929
+ }
9930
+
9019
9931
  if (
9020
9932
  data.thumbnailFileId !== undefined &&
9021
9933
  !isNonEmptyString(data.thumbnailFileId)
@@ -9080,6 +9992,7 @@ const validateControlCreateData = ({ data, errorFactory }) => {
9080
9992
  "type",
9081
9993
  "name",
9082
9994
  "description",
9995
+ "tagIds",
9083
9996
  "thumbnailFileId",
9084
9997
  "preview",
9085
9998
  "elements",
@@ -9129,6 +10042,18 @@ const validateControlCreateData = ({ data, errorFactory }) => {
9129
10042
  }
9130
10043
 
9131
10044
  if (data.type === "control") {
10045
+ {
10046
+ const result = validateOptionalUniqueIdArray({
10047
+ value: data.tagIds,
10048
+ path: "payload.data.tagIds",
10049
+ errorFactory,
10050
+ allowEmpty: false,
10051
+ });
10052
+ if (result?.valid === false) {
10053
+ return result;
10054
+ }
10055
+ }
10056
+
9132
10057
  {
9133
10058
  const result = validateNestedCollection({
9134
10059
  collection: data.elements,
@@ -9162,6 +10087,7 @@ const validateControlUpdateData = ({ data, errorFactory }) => {
9162
10087
  allowedKeys: [
9163
10088
  "name",
9164
10089
  "description",
10090
+ "tagIds",
9165
10091
  "keyboard",
9166
10092
  "thumbnailFileId",
9167
10093
  "preview",
@@ -9226,6 +10152,18 @@ const validateControlUpdateData = ({ data, errorFactory }) => {
9226
10152
  return result;
9227
10153
  }
9228
10154
  }
10155
+
10156
+ {
10157
+ const result = validateOptionalUniqueIdArray({
10158
+ value: data.tagIds,
10159
+ path: "payload.data.tagIds",
10160
+ errorFactory,
10161
+ allowEmpty: false,
10162
+ });
10163
+ if (result?.valid === false) {
10164
+ return result;
10165
+ }
10166
+ }
9229
10167
  };
9230
10168
 
9231
10169
  const validateLayoutElementCreateData = ({ data, errorFactory }) => {
@@ -9371,6 +10309,38 @@ const validateVisualElementReferenceTargets = ({
9371
10309
  }
9372
10310
  }
9373
10311
 
10312
+ if (data.hoverSoundId !== undefined) {
10313
+ const sound = state.sounds.items[data.hoverSoundId];
10314
+ if (!isPlainObject(sound) || sound.type === "folder") {
10315
+ return invalidFromErrorFactory(
10316
+ errorFactory,
10317
+ `${ownerLabel} element hoverSoundId must reference an existing non-folder sound`,
10318
+ {
10319
+ [ownerIdField]: ownerId,
10320
+ elementId,
10321
+ field: "hoverSoundId",
10322
+ targetId: data.hoverSoundId,
10323
+ },
10324
+ );
10325
+ }
10326
+ }
10327
+
10328
+ if (data.clickSoundId !== undefined) {
10329
+ const sound = state.sounds.items[data.clickSoundId];
10330
+ if (!isPlainObject(sound) || sound.type === "folder") {
10331
+ return invalidFromErrorFactory(
10332
+ errorFactory,
10333
+ `${ownerLabel} element clickSoundId must reference an existing non-folder sound`,
10334
+ {
10335
+ [ownerIdField]: ownerId,
10336
+ elementId,
10337
+ field: "clickSoundId",
10338
+ targetId: data.clickSoundId,
10339
+ },
10340
+ );
10341
+ }
10342
+ }
10343
+
9374
10344
  if (data.thumbImageId !== undefined) {
9375
10345
  const image = state.images.items[data.thumbImageId];
9376
10346
  if (!isPlainObject(image) || image.type === "folder") {
@@ -9511,6 +10481,26 @@ const validateVisualElementReferenceTargets = ({
9511
10481
  }
9512
10482
  }
9513
10483
 
10484
+ for (const field of ["hoverSoundId", "clickSoundId"]) {
10485
+ if (rule?.set?.[field] === undefined) {
10486
+ continue;
10487
+ }
10488
+
10489
+ const sound = state.sounds.items[rule.set[field]];
10490
+ if (!isPlainObject(sound) || sound.type === "folder") {
10491
+ return invalidFromErrorFactory(
10492
+ errorFactory,
10493
+ `${ownerLabel} element conditionalOverrides.${index}.set.${field} must reference an existing non-folder sound`,
10494
+ {
10495
+ [ownerIdField]: ownerId,
10496
+ elementId,
10497
+ field: `conditionalOverrides.${index}.set.${field}`,
10498
+ targetId: rule.set[field],
10499
+ },
10500
+ );
10501
+ }
10502
+ }
10503
+
9514
10504
  for (const field of ["imageId", "hoverImageId", "clickImageId"]) {
9515
10505
  if (rule?.set?.[field] === undefined) {
9516
10506
  continue;
@@ -10374,59 +11364,68 @@ const COMMAND_DEFINITIONS = [
10374
11364
  itemLabel: "spritesheet item",
10375
11365
  createDataValidator: validateSpritesheetCreateData,
10376
11366
  updateDataValidator: validateSpritesheetUpdateData,
10377
- createItem: ({ payload }) => ({
10378
- id: payload.spritesheetId,
10379
- type: payload.data.type,
10380
- name: payload.data.name,
10381
- ...(payload.data.description !== undefined
10382
- ? {
10383
- description: payload.data.description,
10384
- }
10385
- : {}),
10386
- ...(payload.data.type === "spritesheet"
10387
- ? {
10388
- fileId: payload.data.fileId,
10389
- ...(payload.data.thumbnailFileId !== undefined
10390
- ? {
10391
- thumbnailFileId: payload.data.thumbnailFileId,
10392
- }
10393
- : {}),
10394
- ...(payload.data.sheetWidth !== undefined
10395
- ? {
10396
- sheetWidth: payload.data.sheetWidth,
10397
- }
10398
- : {}),
10399
- ...(payload.data.sheetHeight !== undefined
10400
- ? {
10401
- sheetHeight: payload.data.sheetHeight,
10402
- }
10403
- : {}),
10404
- ...(payload.data.frameCount !== undefined
10405
- ? {
10406
- frameCount: payload.data.frameCount,
10407
- }
10408
- : {}),
10409
- ...(payload.data.width !== undefined
10410
- ? {
10411
- width: payload.data.width,
10412
- }
10413
- : {}),
10414
- ...(payload.data.height !== undefined
10415
- ? {
10416
- height: payload.data.height,
10417
- }
10418
- : {}),
10419
- jsonData: structuredClone(payload.data.jsonData),
10420
- animations: structuredClone(payload.data.animations),
10421
- }
10422
- : {}),
10423
- }),
11367
+ createItem: ({ payload }) => {
11368
+ const item = {
11369
+ id: payload.spritesheetId,
11370
+ type: payload.data.type,
11371
+ name: payload.data.name,
11372
+ };
11373
+
11374
+ if (payload.data.description !== undefined) {
11375
+ item.description = payload.data.description;
11376
+ }
11377
+
11378
+ if (payload.data.type !== "spritesheet") {
11379
+ return item;
11380
+ }
11381
+
11382
+ item.fileId = payload.data.fileId;
11383
+
11384
+ if (payload.data.thumbnailFileId !== undefined) {
11385
+ item.thumbnailFileId = payload.data.thumbnailFileId;
11386
+ }
11387
+
11388
+ if (payload.data.sheetWidth !== undefined) {
11389
+ item.sheetWidth = payload.data.sheetWidth;
11390
+ }
11391
+
11392
+ if (payload.data.sheetHeight !== undefined) {
11393
+ item.sheetHeight = payload.data.sheetHeight;
11394
+ }
11395
+
11396
+ if (payload.data.frameCount !== undefined) {
11397
+ item.frameCount = payload.data.frameCount;
11398
+ }
11399
+
11400
+ if (payload.data.width !== undefined) {
11401
+ item.width = payload.data.width;
11402
+ }
11403
+
11404
+ if (payload.data.height !== undefined) {
11405
+ item.height = payload.data.height;
11406
+ }
11407
+
11408
+ assignOptionalTagIds({
11409
+ target: item,
11410
+ tagIds: payload.data.tagIds,
11411
+ });
11412
+
11413
+ item.jsonData = structuredClone(payload.data.jsonData);
11414
+ item.animations = structuredClone(payload.data.animations);
11415
+
11416
+ return item;
11417
+ },
11418
+ updateItem: ({ currentItem, payload }) =>
11419
+ applyTagIdsUpdate({
11420
+ currentItem,
11421
+ data: payload.data,
11422
+ }),
10424
11423
  validateCreateState: ({ state, payload }) => {
10425
11424
  if (payload.data.type !== "spritesheet") {
10426
11425
  return;
10427
11426
  }
10428
11427
 
10429
- return validateReferencedFilesInData({
11428
+ const fileResult = validateReferencedFilesInData({
10430
11429
  state,
10431
11430
  data: payload.data,
10432
11431
  fields: ["fileId", "thumbnailFileId"],
@@ -10434,6 +11433,19 @@ const COMMAND_DEFINITIONS = [
10434
11433
  spritesheetId: payload.spritesheetId,
10435
11434
  },
10436
11435
  });
11436
+ if (!fileResult.valid) {
11437
+ return fileResult;
11438
+ }
11439
+
11440
+ return validateTagIdsAgainstScope({
11441
+ state,
11442
+ tagIds: payload.data.tagIds,
11443
+ scopeKey: "spritesheets",
11444
+ path: "payload.data.tagIds",
11445
+ details: {
11446
+ spritesheetId: payload.spritesheetId,
11447
+ },
11448
+ });
10437
11449
  },
10438
11450
  validateUpdateState: ({ state, payload, currentItem }) => {
10439
11451
  if (
@@ -10448,7 +11460,7 @@ const COMMAND_DEFINITIONS = [
10448
11460
  }
10449
11461
 
10450
11462
  if (currentItem.type === "spritesheet") {
10451
- return validateReferencedFilesInData({
11463
+ const fileResult = validateReferencedFilesInData({
10452
11464
  state,
10453
11465
  data: payload.data,
10454
11466
  fields: ["fileId", "thumbnailFileId"],
@@ -10456,6 +11468,19 @@ const COMMAND_DEFINITIONS = [
10456
11468
  spritesheetId: payload.spritesheetId,
10457
11469
  },
10458
11470
  });
11471
+ if (!fileResult.valid) {
11472
+ return fileResult;
11473
+ }
11474
+
11475
+ return validateTagIdsAgainstScope({
11476
+ state,
11477
+ tagIds: payload.data.tagIds,
11478
+ scopeKey: "spritesheets",
11479
+ path: "payload.data.tagIds",
11480
+ details: {
11481
+ spritesheetId: payload.spritesheetId,
11482
+ },
11483
+ });
10459
11484
  }
10460
11485
  },
10461
11486
  }),
@@ -13140,6 +14165,16 @@ const COMMAND_DEFINITIONS = [
13140
14165
  if (!result.valid) {
13141
14166
  return result;
13142
14167
  }
14168
+
14169
+ return validateTagIdsAgainstScope({
14170
+ state,
14171
+ tagIds: payload.data.tagIds,
14172
+ scopeKey: "animations",
14173
+ path: "payload.data.tagIds",
14174
+ details: {
14175
+ animationId: payload.animationId,
14176
+ },
14177
+ });
13143
14178
  }
13144
14179
  },
13145
14180
  reduce: ({ state, payload }) => {
@@ -13154,6 +14189,10 @@ const COMMAND_DEFINITIONS = [
13154
14189
  }
13155
14190
 
13156
14191
  if (payload.data.type === "animation") {
14192
+ assignOptionalTagIds({
14193
+ target: nextAnimation,
14194
+ tagIds: payload.data.tagIds,
14195
+ });
13157
14196
  nextAnimation.animation = structuredClone(payload.data.animation);
13158
14197
  }
13159
14198
 
@@ -13213,32 +14252,45 @@ const COMMAND_DEFINITIONS = [
13213
14252
 
13214
14253
  if (
13215
14254
  currentAnimation.type === "folder" &&
13216
- payload.data.animation !== undefined
14255
+ Object.keys(payload.data).some(
14256
+ (key) => key !== "name" && key !== "description",
14257
+ )
13217
14258
  ) {
13218
14259
  return invalidPrecondition(
13219
14260
  "folder animation items cannot update animation fields",
13220
14261
  );
13221
14262
  }
13222
14263
 
13223
- if (payload.data.animation !== undefined) {
13224
- const result = validateAnimationMaskImageReferences({
14264
+ if (currentAnimation.type === "animation") {
14265
+ if (payload.data.animation !== undefined) {
14266
+ const result = validateAnimationMaskImageReferences({
14267
+ state,
14268
+ animation: payload.data.animation,
14269
+ path: "payload.data.animation",
14270
+ details: { animationId: payload.animationId },
14271
+ errorFactory: createPreconditionValidationError,
14272
+ });
14273
+ if (!result.valid) {
14274
+ return result;
14275
+ }
14276
+ }
14277
+
14278
+ return validateTagIdsAgainstScope({
13225
14279
  state,
13226
- animation: payload.data.animation,
13227
- path: "payload.data.animation",
14280
+ tagIds: payload.data.tagIds,
14281
+ scopeKey: "animations",
14282
+ path: "payload.data.tagIds",
13228
14283
  details: { animationId: payload.animationId },
13229
14284
  errorFactory: createPreconditionValidationError,
13230
14285
  });
13231
- if (!result.valid) {
13232
- return result;
13233
- }
13234
14286
  }
13235
14287
  },
13236
14288
  reduce: ({ state, payload }) => {
13237
14289
  const currentAnimation = state.animations.items[payload.animationId];
13238
- state.animations.items[payload.animationId] = {
13239
- ...structuredClone(currentAnimation),
13240
- ...structuredClone(payload.data),
13241
- };
14290
+ state.animations.items[payload.animationId] = applyTagIdsUpdate({
14291
+ currentItem: currentAnimation,
14292
+ data: payload.data,
14293
+ });
13242
14294
  return state;
13243
14295
  },
13244
14296
  },
@@ -13532,6 +14584,21 @@ const COMMAND_DEFINITIONS = [
13532
14584
  }
13533
14585
 
13534
14586
  if (payload.data.type === "font") {
14587
+ {
14588
+ const result = validateTagIdsAgainstScope({
14589
+ state,
14590
+ tagIds: payload.data.tagIds,
14591
+ scopeKey: "fonts",
14592
+ path: "payload.data.tagIds",
14593
+ details: {
14594
+ fontId: payload.fontId,
14595
+ },
14596
+ });
14597
+ if (!result.valid) {
14598
+ return result;
14599
+ }
14600
+ }
14601
+
13535
14602
  const result = validateReferencedFilesInData({
13536
14603
  state,
13537
14604
  data: payload.data,
@@ -13557,6 +14624,10 @@ const COMMAND_DEFINITIONS = [
13557
14624
  }
13558
14625
 
13559
14626
  if (payload.data.type === "font") {
14627
+ assignOptionalTagIds({
14628
+ target: nextFont,
14629
+ tagIds: payload.data.tagIds,
14630
+ });
13560
14631
  nextFont.fileId = payload.data.fileId;
13561
14632
  nextFont.fontFamily = payload.data.fontFamily;
13562
14633
  }
@@ -13617,7 +14688,8 @@ const COMMAND_DEFINITIONS = [
13617
14688
 
13618
14689
  if (
13619
14690
  currentFont.type === "folder" &&
13620
- (payload.data.fileId !== undefined ||
14691
+ (payload.data.tagIds !== undefined ||
14692
+ payload.data.fileId !== undefined ||
13621
14693
  payload.data.fontFamily !== undefined)
13622
14694
  ) {
13623
14695
  return invalidPrecondition(
@@ -13626,6 +14698,21 @@ const COMMAND_DEFINITIONS = [
13626
14698
  }
13627
14699
 
13628
14700
  if (currentFont.type === "font") {
14701
+ {
14702
+ const result = validateTagIdsAgainstScope({
14703
+ state,
14704
+ tagIds: payload.data.tagIds,
14705
+ scopeKey: "fonts",
14706
+ path: "payload.data.tagIds",
14707
+ details: {
14708
+ fontId: payload.fontId,
14709
+ },
14710
+ });
14711
+ if (!result.valid) {
14712
+ return result;
14713
+ }
14714
+ }
14715
+
13629
14716
  const result = validateReferencedFilesInData({
13630
14717
  state,
13631
14718
  data: payload.data,
@@ -13641,10 +14728,10 @@ const COMMAND_DEFINITIONS = [
13641
14728
  },
13642
14729
  reduce: ({ state, payload }) => {
13643
14730
  const currentFont = state.fonts.items[payload.fontId];
13644
- state.fonts.items[payload.fontId] = {
13645
- ...structuredClone(currentFont),
13646
- ...structuredClone(payload.data),
13647
- };
14731
+ state.fonts.items[payload.fontId] = applyTagIdsUpdate({
14732
+ currentItem: currentFont,
14733
+ data: payload.data,
14734
+ });
13648
14735
  return state;
13649
14736
  },
13650
14737
  },
@@ -13936,6 +15023,18 @@ const COMMAND_DEFINITIONS = [
13936
15023
  );
13937
15024
  }
13938
15025
  }
15026
+
15027
+ if (payload.data.type === "color") {
15028
+ return validateTagIdsAgainstScope({
15029
+ state,
15030
+ tagIds: payload.data.tagIds,
15031
+ scopeKey: "colors",
15032
+ path: "payload.data.tagIds",
15033
+ details: {
15034
+ colorId: payload.colorId,
15035
+ },
15036
+ });
15037
+ }
13939
15038
  },
13940
15039
  reduce: ({ state, payload }) => {
13941
15040
  const nextColor = {
@@ -13950,6 +15049,10 @@ const COMMAND_DEFINITIONS = [
13950
15049
 
13951
15050
  if (payload.data.type === "color") {
13952
15051
  nextColor.hex = payload.data.hex;
15052
+ assignOptionalTagIds({
15053
+ target: nextColor,
15054
+ tagIds: payload.data.tagIds,
15055
+ });
13953
15056
  }
13954
15057
 
13955
15058
  state.colors.items[payload.colorId] = nextColor;
@@ -14006,18 +15109,33 @@ const COMMAND_DEFINITIONS = [
14006
15109
  );
14007
15110
  }
14008
15111
 
14009
- if (currentColor.type === "folder" && payload.data.hex !== undefined) {
15112
+ if (
15113
+ currentColor.type === "folder" &&
15114
+ (payload.data.tagIds !== undefined || payload.data.hex !== undefined)
15115
+ ) {
14010
15116
  return invalidPrecondition(
14011
15117
  "folder color items cannot update color fields",
14012
15118
  );
14013
15119
  }
15120
+
15121
+ if (currentColor.type === "color") {
15122
+ return validateTagIdsAgainstScope({
15123
+ state,
15124
+ tagIds: payload.data.tagIds,
15125
+ scopeKey: "colors",
15126
+ path: "payload.data.tagIds",
15127
+ details: {
15128
+ colorId: payload.colorId,
15129
+ },
15130
+ });
15131
+ }
14014
15132
  },
14015
15133
  reduce: ({ state, payload }) => {
14016
15134
  const currentColor = state.colors.items[payload.colorId];
14017
- state.colors.items[payload.colorId] = {
14018
- ...structuredClone(currentColor),
14019
- ...structuredClone(payload.data),
14020
- };
15135
+ state.colors.items[payload.colorId] = applyTagIdsUpdate({
15136
+ currentItem: currentColor,
15137
+ data: payload.data,
15138
+ });
14021
15139
  return state;
14022
15140
  },
14023
15141
  },
@@ -14239,6 +15357,10 @@ const COMMAND_DEFINITIONS = [
14239
15357
  item.width = payload.data.width;
14240
15358
  item.height = payload.data.height;
14241
15359
  item.modules = structuredClone(payload.data.modules);
15360
+ assignOptionalTagIds({
15361
+ target: item,
15362
+ tagIds: payload.data.tagIds,
15363
+ });
14242
15364
 
14243
15365
  if (payload.data.seed !== undefined && payload.data.seed !== null) {
14244
15366
  item.seed = payload.data.seed;
@@ -14247,10 +15369,10 @@ const COMMAND_DEFINITIONS = [
14247
15369
  return item;
14248
15370
  },
14249
15371
  updateItem: ({ currentItem, payload }) => {
14250
- const nextItem = {
14251
- ...structuredClone(currentItem),
14252
- ...structuredClone(payload.data),
14253
- };
15372
+ const nextItem = applyTagIdsUpdate({
15373
+ currentItem,
15374
+ data: payload.data,
15375
+ });
14254
15376
 
14255
15377
  if (payload.data.seed === null) {
14256
15378
  delete nextItem.seed;
@@ -14269,6 +15391,33 @@ const COMMAND_DEFINITIONS = [
14269
15391
  "folder particle items cannot update particle fields",
14270
15392
  );
14271
15393
  }
15394
+
15395
+ if (currentItem.type === "particle") {
15396
+ return validateTagIdsAgainstScope({
15397
+ state,
15398
+ tagIds: payload.data.tagIds,
15399
+ scopeKey: "particles",
15400
+ path: "payload.data.tagIds",
15401
+ details: {
15402
+ particleId: payload.particleId,
15403
+ },
15404
+ });
15405
+ }
15406
+ },
15407
+ validateCreateState: ({ state, payload }) => {
15408
+ if (payload.data.type !== "particle") {
15409
+ return;
15410
+ }
15411
+
15412
+ return validateTagIdsAgainstScope({
15413
+ state,
15414
+ tagIds: payload.data.tagIds,
15415
+ scopeKey: "particles",
15416
+ path: "payload.data.tagIds",
15417
+ details: {
15418
+ particleId: payload.particleId,
15419
+ },
15420
+ });
14272
15421
  },
14273
15422
  }),
14274
15423
  ...createFolderedCollectionCommandDefinitions({
@@ -14360,23 +15509,37 @@ const COMMAND_DEFINITIONS = [
14360
15509
  itemLabel: "variable item",
14361
15510
  createDataValidator: validateVariableCreateData,
14362
15511
  updateDataValidator: validateVariableUpdateData,
14363
- createItem: ({ payload }) => ({
14364
- id: payload.variableId,
14365
- type: payload.data.type,
14366
- name: payload.data.name,
14367
- ...(payload.data.description !== undefined
14368
- ? {
14369
- description: payload.data.description,
14370
- }
14371
- : {}),
14372
- ...(payload.data.type === "folder"
14373
- ? {}
14374
- : {
14375
- scope: payload.data.scope,
14376
- default: payload.data.default,
14377
- value: payload.data.value,
14378
- }),
14379
- }),
15512
+ createItem: ({ payload }) => {
15513
+ const data = structuredClone(payload.data);
15514
+ if (!Array.isArray(data.tagIds) || data.tagIds.length === 0) {
15515
+ delete data.tagIds;
15516
+ }
15517
+
15518
+ return {
15519
+ id: payload.variableId,
15520
+ ...data,
15521
+ };
15522
+ },
15523
+ updateItem: ({ currentItem, payload }) =>
15524
+ applyTagIdsUpdate({
15525
+ currentItem,
15526
+ data: payload.data,
15527
+ }),
15528
+ validateCreateState: ({ state, payload }) => {
15529
+ if (payload.data.type === "folder") {
15530
+ return;
15531
+ }
15532
+
15533
+ return validateTagIdsAgainstScope({
15534
+ state,
15535
+ tagIds: payload.data.tagIds,
15536
+ scopeKey: "variables",
15537
+ path: "payload.data.tagIds",
15538
+ details: {
15539
+ variableId: payload.variableId,
15540
+ },
15541
+ });
15542
+ },
14380
15543
  validateUpdateState: ({ state, payload, currentItem }) => {
14381
15544
  if (
14382
15545
  currentItem.type === "folder" &&
@@ -14390,6 +15553,21 @@ const COMMAND_DEFINITIONS = [
14390
15553
  }
14391
15554
 
14392
15555
  if (currentItem.type !== "folder") {
15556
+ {
15557
+ const result = validateTagIdsAgainstScope({
15558
+ state,
15559
+ tagIds: payload.data.tagIds,
15560
+ scopeKey: "variables",
15561
+ path: "payload.data.tagIds",
15562
+ details: {
15563
+ variableId: payload.variableId,
15564
+ },
15565
+ });
15566
+ if (!result.valid) {
15567
+ return result;
15568
+ }
15569
+ }
15570
+
14393
15571
  if (payload.data.default !== undefined) {
14394
15572
  {
14395
15573
  const result = validateVariableTypedValue({
@@ -14427,16 +15605,43 @@ const COMMAND_DEFINITIONS = [
14427
15605
  itemLabel: "text style item",
14428
15606
  createDataValidator: validateTextStyleCreateData,
14429
15607
  updateDataValidator: validateTextStyleUpdateData,
14430
- createItem: ({ payload }) => ({
14431
- id: payload.textStyleId,
14432
- ...structuredClone(payload.data),
14433
- }),
15608
+ createItem: ({ payload }) => {
15609
+ const data = structuredClone(payload.data);
15610
+ if (!Array.isArray(data.tagIds) || data.tagIds.length === 0) {
15611
+ delete data.tagIds;
15612
+ }
15613
+
15614
+ return {
15615
+ id: payload.textStyleId,
15616
+ ...data,
15617
+ };
15618
+ },
15619
+ updateItem: ({ currentItem, payload }) =>
15620
+ applyTagIdsUpdate({
15621
+ currentItem,
15622
+ data: payload.data,
15623
+ }),
14434
15624
  validateCreateState: ({ state, payload }) => {
14435
15625
  const data = payload.data;
14436
15626
  if (data.type !== "textStyle") {
14437
15627
  return;
14438
15628
  }
14439
15629
 
15630
+ {
15631
+ const result = validateTagIdsAgainstScope({
15632
+ state,
15633
+ tagIds: payload.data.tagIds,
15634
+ scopeKey: "textStyles",
15635
+ path: "payload.data.tagIds",
15636
+ details: {
15637
+ textStyleId: payload.textStyleId,
15638
+ },
15639
+ });
15640
+ if (!result.valid) {
15641
+ return result;
15642
+ }
15643
+ }
15644
+
14440
15645
  for (const field of ["fontId", "colorId", "strokeColorId"]) {
14441
15646
  if (data[field] === undefined) {
14442
15647
  continue;
@@ -14463,6 +15668,23 @@ const COMMAND_DEFINITIONS = [
14463
15668
  );
14464
15669
  }
14465
15670
 
15671
+ if (currentItem.type === "textStyle") {
15672
+ {
15673
+ const result = validateTagIdsAgainstScope({
15674
+ state,
15675
+ tagIds: payload.data.tagIds,
15676
+ scopeKey: "textStyles",
15677
+ path: "payload.data.tagIds",
15678
+ details: {
15679
+ textStyleId: payload.textStyleId,
15680
+ },
15681
+ });
15682
+ if (!result.valid) {
15683
+ return result;
15684
+ }
15685
+ }
15686
+ }
15687
+
14466
15688
  for (const field of ["fontId", "colorId", "strokeColorId"]) {
14467
15689
  if (payload.data[field] === undefined) {
14468
15690
  continue;
@@ -14512,6 +15734,10 @@ const COMMAND_DEFINITIONS = [
14512
15734
  target: item,
14513
15735
  tagIds: payload.data.tagIds,
14514
15736
  });
15737
+ assignOptionalCharacterSpriteGroups({
15738
+ target: item,
15739
+ spriteGroups: payload.data.spriteGroups,
15740
+ });
14515
15741
 
14516
15742
  item.sprites =
14517
15743
  payload.data.sprites === undefined
@@ -14521,7 +15747,7 @@ const COMMAND_DEFINITIONS = [
14521
15747
  return item;
14522
15748
  },
14523
15749
  updateItem: ({ currentItem, payload }) =>
14524
- applyTagIdsUpdate({
15750
+ applyCharacterUpdate({
14525
15751
  currentItem,
14526
15752
  data: payload.data,
14527
15753
  }),
@@ -14559,6 +15785,21 @@ const COMMAND_DEFINITIONS = [
14559
15785
  }
14560
15786
  }
14561
15787
 
15788
+ {
15789
+ const result = validateCharacterSpriteGroupsAgainstScope({
15790
+ state,
15791
+ spriteGroups: payload.data.spriteGroups,
15792
+ scopeKey: `${CHARACTER_SPRITE_TAG_SCOPE_PREFIX}${payload.characterId}`,
15793
+ path: "payload.data.spriteGroups",
15794
+ details: {
15795
+ characterId: payload.characterId,
15796
+ },
15797
+ });
15798
+ if (!result.valid) {
15799
+ return result;
15800
+ }
15801
+ }
15802
+
14562
15803
  for (const [spriteId, sprite] of Object.entries(
14563
15804
  payload.data.sprites?.items || {},
14564
15805
  )) {
@@ -14634,11 +15875,26 @@ const COMMAND_DEFINITIONS = [
14634
15875
  return result;
14635
15876
  }
14636
15877
 
14637
- return validateTagIdsAgainstScope({
15878
+ {
15879
+ const tagResult = validateTagIdsAgainstScope({
15880
+ state,
15881
+ tagIds: payload.data.tagIds,
15882
+ scopeKey: "characters",
15883
+ path: "payload.data.tagIds",
15884
+ details: {
15885
+ characterId: payload.characterId,
15886
+ },
15887
+ });
15888
+ if (!tagResult.valid) {
15889
+ return tagResult;
15890
+ }
15891
+ }
15892
+
15893
+ return validateCharacterSpriteGroupsAgainstScope({
14638
15894
  state,
14639
- tagIds: payload.data.tagIds,
14640
- scopeKey: "characters",
14641
- path: "payload.data.tagIds",
15895
+ spriteGroups: payload.data.spriteGroups,
15896
+ scopeKey: `${CHARACTER_SPRITE_TAG_SCOPE_PREFIX}${payload.characterId}`,
15897
+ path: "payload.data.spriteGroups",
14642
15898
  details: {
14643
15899
  characterId: payload.characterId,
14644
15900
  },
@@ -14651,9 +15907,7 @@ const COMMAND_DEFINITIONS = [
14651
15907
  continue;
14652
15908
  }
14653
15909
 
14654
- delete state.tags[
14655
- `${CHARACTER_SPRITE_TAG_SCOPE_PREFIX}${characterId}`
14656
- ];
15910
+ delete state.tags[`${CHARACTER_SPRITE_TAG_SCOPE_PREFIX}${characterId}`];
14657
15911
  }
14658
15912
  },
14659
15913
  }),
@@ -14675,6 +15929,12 @@ const COMMAND_DEFINITIONS = [
14675
15929
  : {}),
14676
15930
  ...(payload.data.type === "layout"
14677
15931
  ? {
15932
+ ...(Array.isArray(payload.data.tagIds) &&
15933
+ payload.data.tagIds.length > 0
15934
+ ? {
15935
+ tagIds: structuredClone(payload.data.tagIds),
15936
+ }
15937
+ : {}),
14678
15938
  layoutType: payload.data.layoutType,
14679
15939
  isFragment: payload.data.isFragment,
14680
15940
  ...(payload.data.thumbnailFileId !== undefined
@@ -14691,11 +15951,31 @@ const COMMAND_DEFINITIONS = [
14691
15951
  }
14692
15952
  : {}),
14693
15953
  }),
15954
+ updateItem: ({ currentItem, payload }) =>
15955
+ applyTagIdsUpdate({
15956
+ currentItem,
15957
+ data: payload.data,
15958
+ }),
14694
15959
  validateCreateState: ({ state, payload }) => {
14695
15960
  if (payload.data.type !== "layout") {
14696
15961
  return;
14697
15962
  }
14698
15963
 
15964
+ {
15965
+ const result = validateTagIdsAgainstScope({
15966
+ state,
15967
+ tagIds: payload.data.tagIds,
15968
+ scopeKey: "layouts",
15969
+ path: "payload.data.tagIds",
15970
+ details: {
15971
+ layoutId: payload.layoutId,
15972
+ },
15973
+ });
15974
+ if (!result.valid) {
15975
+ return result;
15976
+ }
15977
+ }
15978
+
14699
15979
  return validateReferencedFilesInData({
14700
15980
  state,
14701
15981
  data: payload.data,
@@ -14718,6 +15998,21 @@ const COMMAND_DEFINITIONS = [
14718
15998
  }
14719
15999
 
14720
16000
  if (currentItem.type === "layout") {
16001
+ {
16002
+ const result = validateTagIdsAgainstScope({
16003
+ state,
16004
+ tagIds: payload.data.tagIds,
16005
+ scopeKey: "layouts",
16006
+ path: "payload.data.tagIds",
16007
+ details: {
16008
+ layoutId: payload.layoutId,
16009
+ },
16010
+ });
16011
+ if (!result.valid) {
16012
+ return result;
16013
+ }
16014
+ }
16015
+
14721
16016
  return validateReferencedFilesInData({
14722
16017
  state,
14723
16018
  data: payload.data,
@@ -14736,41 +16031,42 @@ const COMMAND_DEFINITIONS = [
14736
16031
  itemLabel: "control item",
14737
16032
  createDataValidator: validateControlCreateData,
14738
16033
  updateDataValidator: validateControlUpdateData,
14739
- createItem: ({ payload }) => ({
14740
- id: payload.controlId,
14741
- type: payload.data.type,
14742
- name: payload.data.name,
14743
- ...(payload.data.description !== undefined
14744
- ? {
14745
- description: payload.data.description,
14746
- }
14747
- : {}),
14748
- ...(payload.data.type === "control"
14749
- ? {
14750
- ...(payload.data.thumbnailFileId !== undefined
14751
- ? {
14752
- thumbnailFileId: payload.data.thumbnailFileId,
14753
- }
14754
- : {}),
14755
- ...(payload.data.preview !== undefined
14756
- ? {
14757
- preview: structuredClone(payload.data.preview),
14758
- }
14759
- : {}),
14760
- elements: structuredClone(payload.data.elements),
14761
- ...(payload.data.keyboard !== undefined
14762
- ? {
14763
- keyboard: structuredClone(payload.data.keyboard),
14764
- }
14765
- : {}),
14766
- }
14767
- : {}),
14768
- }),
16034
+ createItem: ({ payload }) => {
16035
+ const data = structuredClone(payload.data);
16036
+ if (!Array.isArray(data.tagIds) || data.tagIds.length === 0) {
16037
+ delete data.tagIds;
16038
+ }
16039
+
16040
+ return {
16041
+ id: payload.controlId,
16042
+ ...data,
16043
+ };
16044
+ },
16045
+ updateItem: ({ currentItem, payload }) =>
16046
+ applyTagIdsUpdate({
16047
+ currentItem,
16048
+ data: payload.data,
16049
+ }),
14769
16050
  validateCreateState: ({ state, payload }) => {
14770
16051
  if (payload.data.type !== "control") {
14771
16052
  return;
14772
16053
  }
14773
16054
 
16055
+ {
16056
+ const result = validateTagIdsAgainstScope({
16057
+ state,
16058
+ tagIds: payload.data.tagIds,
16059
+ scopeKey: "controls",
16060
+ path: "payload.data.tagIds",
16061
+ details: {
16062
+ controlId: payload.controlId,
16063
+ },
16064
+ });
16065
+ if (!result.valid) {
16066
+ return result;
16067
+ }
16068
+ }
16069
+
14774
16070
  return validateReferencedFilesInData({
14775
16071
  state,
14776
16072
  data: payload.data,
@@ -14793,6 +16089,21 @@ const COMMAND_DEFINITIONS = [
14793
16089
  }
14794
16090
 
14795
16091
  if (currentItem.type === "control") {
16092
+ {
16093
+ const result = validateTagIdsAgainstScope({
16094
+ state,
16095
+ tagIds: payload.data.tagIds,
16096
+ scopeKey: "controls",
16097
+ path: "payload.data.tagIds",
16098
+ details: {
16099
+ controlId: payload.controlId,
16100
+ },
16101
+ });
16102
+ if (!result.valid) {
16103
+ return result;
16104
+ }
16105
+ }
16106
+
14796
16107
  return validateReferencedFilesInData({
14797
16108
  state,
14798
16109
  data: payload.data,
@@ -14947,7 +16258,10 @@ const COMMAND_DEFINITIONS = [
14947
16258
  id: payload.spriteId,
14948
16259
  ...structuredClone(payload.data),
14949
16260
  };
14950
- if (payload.data.tagIds !== undefined && payload.data.tagIds.length === 0) {
16261
+ if (
16262
+ payload.data.tagIds !== undefined &&
16263
+ payload.data.tagIds.length === 0
16264
+ ) {
14951
16265
  delete nextSprite.tagIds;
14952
16266
  }
14953
16267