@routevn/creator-model 1.1.3 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +6 -0
- package/src/model.js +617 -29
- package/src/systemVariables.js +51 -0
package/package.json
CHANGED
package/src/index.js
CHANGED
package/src/model.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
isPlainObject,
|
|
16
16
|
removeTreeNode,
|
|
17
17
|
} from "./helpers.js";
|
|
18
|
+
import { isSystemVariableId } from "./systemVariables.js";
|
|
18
19
|
|
|
19
20
|
const COLLECTION_KEYS = [
|
|
20
21
|
"scenes",
|
|
@@ -37,6 +38,7 @@ const createEmptyCollectionState = () => ({
|
|
|
37
38
|
items: {},
|
|
38
39
|
tree: [],
|
|
39
40
|
});
|
|
41
|
+
|
|
40
42
|
const normalizeStateCollections = (state) => {
|
|
41
43
|
if (!isPlainObject(state)) {
|
|
42
44
|
return state;
|
|
@@ -107,7 +109,14 @@ const ANIMATION_EASING_KEYS = [
|
|
|
107
109
|
];
|
|
108
110
|
const VARIABLE_SCOPE_KEYS = ["context", "global-device", "global-account"];
|
|
109
111
|
const VARIABLE_TYPE_KEYS = ["string", "number", "boolean"];
|
|
110
|
-
const LAYOUT_TYPE_KEYS = [
|
|
112
|
+
const LAYOUT_TYPE_KEYS = [
|
|
113
|
+
"normal",
|
|
114
|
+
"save-load",
|
|
115
|
+
"confirmDialog",
|
|
116
|
+
"dialogue",
|
|
117
|
+
"nvl",
|
|
118
|
+
"choice",
|
|
119
|
+
];
|
|
111
120
|
const LAYOUT_ELEMENT_TEXT_STYLE_ALIGN_KEYS = ["left", "center", "right"];
|
|
112
121
|
const LAYOUT_TEXT_REVEAL_EFFECT_KEYS = ["typewriter", "softWipe", "none"];
|
|
113
122
|
const LAYOUT_ELEMENT_BASE_TYPES = [
|
|
@@ -121,17 +130,26 @@ const LAYOUT_ELEMENT_BASE_TYPES = [
|
|
|
121
130
|
"text-ref-character-name",
|
|
122
131
|
"text-revealing-ref-dialogue-content",
|
|
123
132
|
"text-ref-choice-item-content",
|
|
133
|
+
"text-ref-save-load-slot-date",
|
|
124
134
|
"text-ref-dialogue-line-character-name",
|
|
125
135
|
"text-ref-dialogue-line-content",
|
|
136
|
+
"sprite-ref-save-load-slot-image",
|
|
137
|
+
"fragment-ref",
|
|
126
138
|
"container-ref-choice-item",
|
|
139
|
+
"container-ref-save-load-slot",
|
|
127
140
|
"container-ref-dialogue-line",
|
|
141
|
+
"container-ref-confirm-dialog-ok",
|
|
142
|
+
"container-ref-confirm-dialog-cancel",
|
|
128
143
|
];
|
|
129
144
|
export const SCHEMA_VERSION = 2;
|
|
130
145
|
const LAYOUT_CONTAINER_ELEMENT_TYPES = [
|
|
131
146
|
"folder",
|
|
132
147
|
"container",
|
|
133
148
|
"container-ref-choice-item",
|
|
149
|
+
"container-ref-save-load-slot",
|
|
134
150
|
"container-ref-dialogue-line",
|
|
151
|
+
"container-ref-confirm-dialog-ok",
|
|
152
|
+
"container-ref-confirm-dialog-cancel",
|
|
135
153
|
];
|
|
136
154
|
const DOMAIN_ERROR_KIND_BY_NAME = {
|
|
137
155
|
PayloadValidationError: "payload",
|
|
@@ -220,6 +238,90 @@ const invalidInvariant = (message, details = {}) =>
|
|
|
220
238
|
details,
|
|
221
239
|
});
|
|
222
240
|
|
|
241
|
+
const isVariableReferenceTarget = (state, variableId) => {
|
|
242
|
+
if (isSystemVariableId(variableId)) {
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const variable = state.variables.items[variableId];
|
|
247
|
+
return isPlainObject(variable) && variable.type !== "folder";
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const LAYOUT_CONDITION_TARGET_SET = new Set([
|
|
251
|
+
"item.savedAt",
|
|
252
|
+
"isLineCompleted",
|
|
253
|
+
"autoMode",
|
|
254
|
+
"skipMode",
|
|
255
|
+
]);
|
|
256
|
+
const VARIABLE_TARGET_DOT_PATTERN = /^variables\.([A-Za-z_$][A-Za-z0-9_$]*)$/;
|
|
257
|
+
const VARIABLE_TARGET_BRACKET_PATTERN = /^variables\[(.+)\]$/;
|
|
258
|
+
|
|
259
|
+
const parseLayoutConditionTarget = (target) => {
|
|
260
|
+
if (!isNonEmptyString(target)) {
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (LAYOUT_CONDITION_TARGET_SET.has(target)) {
|
|
265
|
+
return {
|
|
266
|
+
kind: "runtime",
|
|
267
|
+
target,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const dotMatch = target.match(VARIABLE_TARGET_DOT_PATTERN);
|
|
272
|
+
if (dotMatch) {
|
|
273
|
+
return {
|
|
274
|
+
kind: "variable",
|
|
275
|
+
target,
|
|
276
|
+
variableId: dotMatch[1],
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const bracketMatch = target.match(VARIABLE_TARGET_BRACKET_PATTERN);
|
|
281
|
+
if (!bracketMatch) {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const rawValue = bracketMatch[1].trim();
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
const variableId = JSON.parse(rawValue);
|
|
289
|
+
if (isNonEmptyString(variableId)) {
|
|
290
|
+
return {
|
|
291
|
+
kind: "variable",
|
|
292
|
+
target,
|
|
293
|
+
variableId,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
} catch {}
|
|
297
|
+
|
|
298
|
+
if (rawValue.startsWith("'") && rawValue.endsWith("'")) {
|
|
299
|
+
const variableId = rawValue.slice(1, -1);
|
|
300
|
+
if (isNonEmptyString(variableId)) {
|
|
301
|
+
return {
|
|
302
|
+
kind: "variable",
|
|
303
|
+
target,
|
|
304
|
+
variableId,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return undefined;
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const isLayoutConditionTarget = (state, target) => {
|
|
313
|
+
const parsedTarget = parseLayoutConditionTarget(target);
|
|
314
|
+
if (!parsedTarget) {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (parsedTarget.kind === "runtime") {
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return isVariableReferenceTarget(state, parsedTarget.variableId);
|
|
323
|
+
};
|
|
324
|
+
|
|
223
325
|
const toDomainErrorDetails = (publicError) => {
|
|
224
326
|
const details = isPlainObject(publicError?.details)
|
|
225
327
|
? { ...publicError.details }
|
|
@@ -2171,6 +2273,29 @@ const validateLayoutElementBorder = ({ border, path, errorFactory }) => {
|
|
|
2171
2273
|
}
|
|
2172
2274
|
};
|
|
2173
2275
|
|
|
2276
|
+
const validateLayoutElementInteraction = ({
|
|
2277
|
+
interaction,
|
|
2278
|
+
path,
|
|
2279
|
+
errorFactory,
|
|
2280
|
+
}) => {
|
|
2281
|
+
if (!isPlainObject(interaction)) {
|
|
2282
|
+
return invalidFromErrorFactory(
|
|
2283
|
+
errorFactory,
|
|
2284
|
+
`${path} must be an object when provided`,
|
|
2285
|
+
);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
if (
|
|
2289
|
+
interaction.inheritToChildren !== undefined &&
|
|
2290
|
+
typeof interaction.inheritToChildren !== "boolean"
|
|
2291
|
+
) {
|
|
2292
|
+
return invalidFromErrorFactory(
|
|
2293
|
+
errorFactory,
|
|
2294
|
+
`${path}.inheritToChildren must be a boolean when provided`,
|
|
2295
|
+
);
|
|
2296
|
+
}
|
|
2297
|
+
};
|
|
2298
|
+
|
|
2174
2299
|
const validateLayoutElementData = ({
|
|
2175
2300
|
data,
|
|
2176
2301
|
path,
|
|
@@ -2206,10 +2331,14 @@ const validateLayoutElementData = ({
|
|
|
2206
2331
|
"textStyleId",
|
|
2207
2332
|
"hoverTextStyleId",
|
|
2208
2333
|
"clickTextStyleId",
|
|
2334
|
+
"conditionalOverrides",
|
|
2209
2335
|
"direction",
|
|
2210
2336
|
"gap",
|
|
2211
2337
|
"containerType",
|
|
2212
2338
|
"scroll",
|
|
2339
|
+
"hover",
|
|
2340
|
+
"click",
|
|
2341
|
+
"rightClick",
|
|
2213
2342
|
"anchorToBottom",
|
|
2214
2343
|
"thumbImageId",
|
|
2215
2344
|
"barImageId",
|
|
@@ -2220,9 +2349,11 @@ const validateLayoutElementData = ({
|
|
|
2220
2349
|
"step",
|
|
2221
2350
|
"initialValue",
|
|
2222
2351
|
"variableId",
|
|
2352
|
+
"fragmentLayoutId",
|
|
2353
|
+
"paginationMode",
|
|
2354
|
+
"paginationVariableId",
|
|
2355
|
+
"paginationSize",
|
|
2223
2356
|
"$when",
|
|
2224
|
-
"click",
|
|
2225
|
-
"rightClick",
|
|
2226
2357
|
"change",
|
|
2227
2358
|
];
|
|
2228
2359
|
|
|
@@ -2271,6 +2402,7 @@ const validateLayoutElementData = ({
|
|
|
2271
2402
|
"min",
|
|
2272
2403
|
"max",
|
|
2273
2404
|
"step",
|
|
2405
|
+
"paginationSize",
|
|
2274
2406
|
"opacity",
|
|
2275
2407
|
]) {
|
|
2276
2408
|
if (data[key] !== undefined && !isFiniteNumber(data[key])) {
|
|
@@ -2312,6 +2444,9 @@ const validateLayoutElementData = ({
|
|
|
2312
2444
|
"clickTextStyleId",
|
|
2313
2445
|
"containerType",
|
|
2314
2446
|
"variableId",
|
|
2447
|
+
"fragmentLayoutId",
|
|
2448
|
+
"paginationMode",
|
|
2449
|
+
"paginationVariableId",
|
|
2315
2450
|
"revealEffect",
|
|
2316
2451
|
"thumbImageId",
|
|
2317
2452
|
"barImageId",
|
|
@@ -2327,6 +2462,191 @@ const validateLayoutElementData = ({
|
|
|
2327
2462
|
}
|
|
2328
2463
|
}
|
|
2329
2464
|
|
|
2465
|
+
if (data.conditionalOverrides !== undefined) {
|
|
2466
|
+
if (!Array.isArray(data.conditionalOverrides)) {
|
|
2467
|
+
return invalidFromErrorFactory(
|
|
2468
|
+
errorFactory,
|
|
2469
|
+
`${path}.conditionalOverrides must be an array when provided`,
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
for (let index = 0; index < data.conditionalOverrides.length; index += 1) {
|
|
2474
|
+
const rule = data.conditionalOverrides[index];
|
|
2475
|
+
const rulePath = `${path}.conditionalOverrides.${index}`;
|
|
2476
|
+
|
|
2477
|
+
if (!isPlainObject(rule)) {
|
|
2478
|
+
return invalidFromErrorFactory(
|
|
2479
|
+
errorFactory,
|
|
2480
|
+
`${rulePath} must be an object`,
|
|
2481
|
+
);
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
{
|
|
2485
|
+
const result = validateAllowedKeys({
|
|
2486
|
+
value: rule,
|
|
2487
|
+
allowedKeys: ["when", "set"],
|
|
2488
|
+
path: rulePath,
|
|
2489
|
+
errorFactory,
|
|
2490
|
+
});
|
|
2491
|
+
if (result?.valid === false) {
|
|
2492
|
+
return result;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
if (!isPlainObject(rule.when)) {
|
|
2497
|
+
return invalidFromErrorFactory(
|
|
2498
|
+
errorFactory,
|
|
2499
|
+
`${rulePath}.when must be an object`,
|
|
2500
|
+
);
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
{
|
|
2504
|
+
const result = validateAllowedKeys({
|
|
2505
|
+
value: rule.when,
|
|
2506
|
+
allowedKeys: ["target", "op", "value"],
|
|
2507
|
+
path: `${rulePath}.when`,
|
|
2508
|
+
errorFactory,
|
|
2509
|
+
});
|
|
2510
|
+
if (result?.valid === false) {
|
|
2511
|
+
return result;
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
if (!isNonEmptyString(rule.when.target)) {
|
|
2516
|
+
return invalidFromErrorFactory(
|
|
2517
|
+
errorFactory,
|
|
2518
|
+
`${rulePath}.when.target must be a non-empty string`,
|
|
2519
|
+
);
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
if (!parseLayoutConditionTarget(rule.when.target)) {
|
|
2523
|
+
return invalidFromErrorFactory(
|
|
2524
|
+
errorFactory,
|
|
2525
|
+
`${rulePath}.when.target must be a supported layout condition target`,
|
|
2526
|
+
);
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
if (
|
|
2530
|
+
!isString(rule.when.value) &&
|
|
2531
|
+
typeof rule.when.value !== "boolean" &&
|
|
2532
|
+
!isFiniteNumber(rule.when.value)
|
|
2533
|
+
) {
|
|
2534
|
+
return invalidFromErrorFactory(
|
|
2535
|
+
errorFactory,
|
|
2536
|
+
`${rulePath}.when.value must be a string, boolean, or finite number`,
|
|
2537
|
+
);
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
if (rule.when.op !== "eq") {
|
|
2541
|
+
return invalidFromErrorFactory(
|
|
2542
|
+
errorFactory,
|
|
2543
|
+
`${rulePath}.when.op must be "eq"`,
|
|
2544
|
+
);
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
if (!isPlainObject(rule.set)) {
|
|
2548
|
+
return invalidFromErrorFactory(
|
|
2549
|
+
errorFactory,
|
|
2550
|
+
`${rulePath}.set must be an object`,
|
|
2551
|
+
);
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
{
|
|
2555
|
+
const result = validateAllowedKeys({
|
|
2556
|
+
value: rule.set,
|
|
2557
|
+
allowedKeys: [
|
|
2558
|
+
"textStyleId",
|
|
2559
|
+
"hoverTextStyleId",
|
|
2560
|
+
"clickTextStyleId",
|
|
2561
|
+
"imageId",
|
|
2562
|
+
"hoverImageId",
|
|
2563
|
+
"clickImageId",
|
|
2564
|
+
"opacity",
|
|
2565
|
+
"anchorX",
|
|
2566
|
+
"anchorY",
|
|
2567
|
+
"visible",
|
|
2568
|
+
"textStyle",
|
|
2569
|
+
],
|
|
2570
|
+
path: `${rulePath}.set`,
|
|
2571
|
+
errorFactory,
|
|
2572
|
+
});
|
|
2573
|
+
if (result?.valid === false) {
|
|
2574
|
+
return result;
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
for (const field of [
|
|
2579
|
+
"textStyleId",
|
|
2580
|
+
"hoverTextStyleId",
|
|
2581
|
+
"clickTextStyleId",
|
|
2582
|
+
"imageId",
|
|
2583
|
+
"hoverImageId",
|
|
2584
|
+
"clickImageId",
|
|
2585
|
+
]) {
|
|
2586
|
+
if (
|
|
2587
|
+
rule.set[field] !== undefined &&
|
|
2588
|
+
!isNonEmptyString(rule.set[field])
|
|
2589
|
+
) {
|
|
2590
|
+
return invalidFromErrorFactory(
|
|
2591
|
+
errorFactory,
|
|
2592
|
+
`${rulePath}.set.${field} must be a non-empty string when provided`,
|
|
2593
|
+
);
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
if (
|
|
2598
|
+
rule.set.opacity !== undefined &&
|
|
2599
|
+
(!isFiniteNumber(rule.set.opacity) ||
|
|
2600
|
+
rule.set.opacity < 0 ||
|
|
2601
|
+
rule.set.opacity > 1)
|
|
2602
|
+
) {
|
|
2603
|
+
return invalidFromErrorFactory(
|
|
2604
|
+
errorFactory,
|
|
2605
|
+
`${rulePath}.set.opacity must be a finite number between 0 and 1 when provided`,
|
|
2606
|
+
);
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
for (const field of ["anchorX", "anchorY"]) {
|
|
2610
|
+
if (rule.set[field] !== undefined && !isFiniteNumber(rule.set[field])) {
|
|
2611
|
+
return invalidFromErrorFactory(
|
|
2612
|
+
errorFactory,
|
|
2613
|
+
`${rulePath}.set.${field} must be a finite number when provided`,
|
|
2614
|
+
);
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
if (
|
|
2619
|
+
rule.set.visible !== undefined &&
|
|
2620
|
+
typeof rule.set.visible !== "boolean"
|
|
2621
|
+
) {
|
|
2622
|
+
return invalidFromErrorFactory(
|
|
2623
|
+
errorFactory,
|
|
2624
|
+
`${rulePath}.set.visible must be a boolean when provided`,
|
|
2625
|
+
);
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
if (rule.set.textStyle !== undefined) {
|
|
2629
|
+
if (!isPlainObject(rule.set.textStyle)) {
|
|
2630
|
+
return invalidFromErrorFactory(
|
|
2631
|
+
errorFactory,
|
|
2632
|
+
`${rulePath}.set.textStyle must be an object when provided`,
|
|
2633
|
+
);
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
{
|
|
2637
|
+
const result = validateLayoutElementTextStyle({
|
|
2638
|
+
textStyle: rule.set.textStyle,
|
|
2639
|
+
path: `${rulePath}.set.textStyle`,
|
|
2640
|
+
errorFactory,
|
|
2641
|
+
});
|
|
2642
|
+
if (result?.valid === false) {
|
|
2643
|
+
return result;
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2330
2650
|
if (data.fill !== undefined && !isString(data.fill)) {
|
|
2331
2651
|
return invalidFromErrorFactory(
|
|
2332
2652
|
errorFactory,
|
|
@@ -2355,6 +2675,17 @@ const validateLayoutElementData = ({
|
|
|
2355
2675
|
);
|
|
2356
2676
|
}
|
|
2357
2677
|
|
|
2678
|
+
if (
|
|
2679
|
+
data.paginationMode !== undefined &&
|
|
2680
|
+
data.paginationMode !== "continuous" &&
|
|
2681
|
+
data.paginationMode !== "paginated"
|
|
2682
|
+
) {
|
|
2683
|
+
return invalidFromErrorFactory(
|
|
2684
|
+
errorFactory,
|
|
2685
|
+
`${path}.paginationMode must be 'continuous' or 'paginated' when provided`,
|
|
2686
|
+
);
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2358
2689
|
if (data.scroll !== undefined && typeof data.scroll !== "boolean") {
|
|
2359
2690
|
return invalidFromErrorFactory(
|
|
2360
2691
|
errorFactory,
|
|
@@ -2362,6 +2693,45 @@ const validateLayoutElementData = ({
|
|
|
2362
2693
|
);
|
|
2363
2694
|
}
|
|
2364
2695
|
|
|
2696
|
+
if (data.hover !== undefined) {
|
|
2697
|
+
{
|
|
2698
|
+
const result = validateLayoutElementInteraction({
|
|
2699
|
+
interaction: data.hover,
|
|
2700
|
+
path: `${path}.hover`,
|
|
2701
|
+
errorFactory,
|
|
2702
|
+
});
|
|
2703
|
+
if (result?.valid === false) {
|
|
2704
|
+
return result;
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
if (data.click !== undefined) {
|
|
2710
|
+
{
|
|
2711
|
+
const result = validateLayoutElementInteraction({
|
|
2712
|
+
interaction: data.click,
|
|
2713
|
+
path: `${path}.click`,
|
|
2714
|
+
errorFactory,
|
|
2715
|
+
});
|
|
2716
|
+
if (result?.valid === false) {
|
|
2717
|
+
return result;
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
if (data.rightClick !== undefined) {
|
|
2723
|
+
{
|
|
2724
|
+
const result = validateLayoutElementInteraction({
|
|
2725
|
+
interaction: data.rightClick,
|
|
2726
|
+
path: `${path}.rightClick`,
|
|
2727
|
+
errorFactory,
|
|
2728
|
+
});
|
|
2729
|
+
if (result?.valid === false) {
|
|
2730
|
+
return result;
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2365
2735
|
if (
|
|
2366
2736
|
data.anchorToBottom !== undefined &&
|
|
2367
2737
|
typeof data.anchorToBottom !== "boolean"
|
|
@@ -2405,20 +2775,6 @@ const validateLayoutElementData = ({
|
|
|
2405
2775
|
}
|
|
2406
2776
|
}
|
|
2407
2777
|
|
|
2408
|
-
if (data.click !== undefined && !isPlainObject(data.click)) {
|
|
2409
|
-
return invalidFromErrorFactory(
|
|
2410
|
-
errorFactory,
|
|
2411
|
-
`${path}.click must be an object when provided`,
|
|
2412
|
-
);
|
|
2413
|
-
}
|
|
2414
|
-
|
|
2415
|
-
if (data.rightClick !== undefined && !isPlainObject(data.rightClick)) {
|
|
2416
|
-
return invalidFromErrorFactory(
|
|
2417
|
-
errorFactory,
|
|
2418
|
-
`${path}.rightClick must be an object when provided`,
|
|
2419
|
-
);
|
|
2420
|
-
}
|
|
2421
|
-
|
|
2422
2778
|
if (data.change !== undefined && !isPlainObject(data.change)) {
|
|
2423
2779
|
return invalidFromErrorFactory(
|
|
2424
2780
|
errorFactory,
|
|
@@ -2460,10 +2816,14 @@ const validateLayoutElementItems = ({ items, path, errorFactory }) => {
|
|
|
2460
2816
|
"textStyleId",
|
|
2461
2817
|
"hoverTextStyleId",
|
|
2462
2818
|
"clickTextStyleId",
|
|
2819
|
+
"conditionalOverrides",
|
|
2463
2820
|
"direction",
|
|
2464
2821
|
"gap",
|
|
2465
2822
|
"containerType",
|
|
2466
2823
|
"scroll",
|
|
2824
|
+
"hover",
|
|
2825
|
+
"click",
|
|
2826
|
+
"rightClick",
|
|
2467
2827
|
"anchorToBottom",
|
|
2468
2828
|
"thumbImageId",
|
|
2469
2829
|
"barImageId",
|
|
@@ -2474,9 +2834,11 @@ const validateLayoutElementItems = ({ items, path, errorFactory }) => {
|
|
|
2474
2834
|
"step",
|
|
2475
2835
|
"initialValue",
|
|
2476
2836
|
"variableId",
|
|
2837
|
+
"fragmentLayoutId",
|
|
2838
|
+
"paginationMode",
|
|
2839
|
+
"paginationVariableId",
|
|
2840
|
+
"paginationSize",
|
|
2477
2841
|
"$when",
|
|
2478
|
-
"click",
|
|
2479
|
-
"rightClick",
|
|
2480
2842
|
"change",
|
|
2481
2843
|
],
|
|
2482
2844
|
path: itemPath,
|
|
@@ -2674,7 +3036,15 @@ const validateLayoutItems = ({ items, path, errorFactory }) => {
|
|
|
2674
3036
|
allowedKeys:
|
|
2675
3037
|
item.type === "folder"
|
|
2676
3038
|
? ["id", "type", "name"]
|
|
2677
|
-
: [
|
|
3039
|
+
: [
|
|
3040
|
+
"id",
|
|
3041
|
+
"type",
|
|
3042
|
+
"name",
|
|
3043
|
+
"description",
|
|
3044
|
+
"layoutType",
|
|
3045
|
+
"isFragment",
|
|
3046
|
+
"elements",
|
|
3047
|
+
],
|
|
2678
3048
|
path: itemPath,
|
|
2679
3049
|
errorFactory,
|
|
2680
3050
|
});
|
|
@@ -2704,11 +3074,28 @@ const validateLayoutItems = ({ items, path, errorFactory }) => {
|
|
|
2704
3074
|
);
|
|
2705
3075
|
}
|
|
2706
3076
|
|
|
3077
|
+
if (item.description !== undefined && !isString(item.description)) {
|
|
3078
|
+
return invalidFromErrorFactory(
|
|
3079
|
+
errorFactory,
|
|
3080
|
+
`${itemPath}.description must be a string when provided`,
|
|
3081
|
+
);
|
|
3082
|
+
}
|
|
3083
|
+
|
|
2707
3084
|
if (item.type === "layout") {
|
|
2708
3085
|
if (!LAYOUT_TYPE_KEYS.includes(item.layoutType)) {
|
|
2709
3086
|
return invalidFromErrorFactory(
|
|
2710
3087
|
errorFactory,
|
|
2711
|
-
`${itemPath}.layoutType must be 'normal', 'dialogue', 'nvl', or 'choice'`,
|
|
3088
|
+
`${itemPath}.layoutType must be 'normal', 'save-load', 'confirmDialog', 'dialogue', 'nvl', or 'choice'`,
|
|
3089
|
+
);
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
if (
|
|
3093
|
+
item.isFragment !== undefined &&
|
|
3094
|
+
typeof item.isFragment !== "boolean"
|
|
3095
|
+
) {
|
|
3096
|
+
return invalidFromErrorFactory(
|
|
3097
|
+
errorFactory,
|
|
3098
|
+
`${itemPath}.isFragment must be a boolean when provided`,
|
|
2712
3099
|
);
|
|
2713
3100
|
}
|
|
2714
3101
|
|
|
@@ -3899,8 +4286,7 @@ export const assertInvariants = ({ state }) => {
|
|
|
3899
4286
|
elementId,
|
|
3900
4287
|
targetId,
|
|
3901
4288
|
}) => {
|
|
3902
|
-
|
|
3903
|
-
if (!isPlainObject(variable) || variable.type === "folder") {
|
|
4289
|
+
if (!isVariableReferenceTarget(state, targetId)) {
|
|
3904
4290
|
return invalidInvariant(
|
|
3905
4291
|
`${ownerLabel} element variableId must reference an existing non-folder variable`,
|
|
3906
4292
|
{
|
|
@@ -3914,6 +4300,32 @@ export const assertInvariants = ({ state }) => {
|
|
|
3914
4300
|
return VALID_RESULT;
|
|
3915
4301
|
};
|
|
3916
4302
|
|
|
4303
|
+
const assertFragmentLayoutReference = ({
|
|
4304
|
+
ownerIdField,
|
|
4305
|
+
ownerId,
|
|
4306
|
+
ownerLabel,
|
|
4307
|
+
elementId,
|
|
4308
|
+
targetId,
|
|
4309
|
+
}) => {
|
|
4310
|
+
const layout = state.layouts.items[targetId];
|
|
4311
|
+
if (
|
|
4312
|
+
!isPlainObject(layout) ||
|
|
4313
|
+
layout.type !== "layout" ||
|
|
4314
|
+
layout.isFragment !== true
|
|
4315
|
+
) {
|
|
4316
|
+
return invalidInvariant(
|
|
4317
|
+
`${ownerLabel} element fragmentLayoutId must reference an existing fragment layout`,
|
|
4318
|
+
{
|
|
4319
|
+
[ownerIdField]: ownerId,
|
|
4320
|
+
elementId,
|
|
4321
|
+
fragmentLayoutId: targetId,
|
|
4322
|
+
},
|
|
4323
|
+
);
|
|
4324
|
+
}
|
|
4325
|
+
|
|
4326
|
+
return VALID_RESULT;
|
|
4327
|
+
};
|
|
4328
|
+
|
|
3917
4329
|
const assertElementReferencesForCollection = ({
|
|
3918
4330
|
items,
|
|
3919
4331
|
ownerIdField,
|
|
@@ -3970,6 +4382,51 @@ export const assertInvariants = ({ state }) => {
|
|
|
3970
4382
|
}
|
|
3971
4383
|
}
|
|
3972
4384
|
|
|
4385
|
+
if (Array.isArray(element.conditionalOverrides)) {
|
|
4386
|
+
for (
|
|
4387
|
+
let index = 0;
|
|
4388
|
+
index < element.conditionalOverrides.length;
|
|
4389
|
+
index += 1
|
|
4390
|
+
) {
|
|
4391
|
+
const rule = element.conditionalOverrides[index];
|
|
4392
|
+
|
|
4393
|
+
for (const field of [
|
|
4394
|
+
"textStyleId",
|
|
4395
|
+
"hoverTextStyleId",
|
|
4396
|
+
"clickTextStyleId",
|
|
4397
|
+
]) {
|
|
4398
|
+
if (rule?.set?.[field] !== undefined) {
|
|
4399
|
+
const result = assertTextStyleReference({
|
|
4400
|
+
ownerIdField,
|
|
4401
|
+
ownerId,
|
|
4402
|
+
ownerLabel,
|
|
4403
|
+
elementId,
|
|
4404
|
+
field: `conditionalOverrides.${index}.set.${field}`,
|
|
4405
|
+
targetId: rule.set[field],
|
|
4406
|
+
});
|
|
4407
|
+
if (!result.valid) {
|
|
4408
|
+
return result;
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4412
|
+
|
|
4413
|
+
if (
|
|
4414
|
+
rule?.when?.target !== undefined &&
|
|
4415
|
+
!isLayoutConditionTarget(state, rule.when.target)
|
|
4416
|
+
) {
|
|
4417
|
+
return invalidInvariant(
|
|
4418
|
+
`${ownerLabel} element conditionalOverrides when target must reference an existing variable or supported runtime condition`,
|
|
4419
|
+
{
|
|
4420
|
+
[ownerIdField]: ownerId,
|
|
4421
|
+
elementId,
|
|
4422
|
+
field: `conditionalOverrides.${index}.when.target`,
|
|
4423
|
+
targetId: rule.when.target,
|
|
4424
|
+
},
|
|
4425
|
+
);
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
|
|
3973
4430
|
if (element.variableId !== undefined) {
|
|
3974
4431
|
const result = assertVariableReference({
|
|
3975
4432
|
ownerIdField,
|
|
@@ -3982,6 +4439,19 @@ export const assertInvariants = ({ state }) => {
|
|
|
3982
4439
|
return result;
|
|
3983
4440
|
}
|
|
3984
4441
|
}
|
|
4442
|
+
|
|
4443
|
+
if (element.fragmentLayoutId !== undefined) {
|
|
4444
|
+
const result = assertFragmentLayoutReference({
|
|
4445
|
+
ownerIdField,
|
|
4446
|
+
ownerId,
|
|
4447
|
+
ownerLabel,
|
|
4448
|
+
elementId,
|
|
4449
|
+
targetId: element.fragmentLayoutId,
|
|
4450
|
+
});
|
|
4451
|
+
if (!result.valid) {
|
|
4452
|
+
return result;
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
3985
4455
|
}
|
|
3986
4456
|
}
|
|
3987
4457
|
|
|
@@ -6127,7 +6597,14 @@ const validateLayoutCreateData = ({ data, errorFactory }) => {
|
|
|
6127
6597
|
allowedKeys:
|
|
6128
6598
|
data.type === "folder"
|
|
6129
6599
|
? ["type", "name"]
|
|
6130
|
-
: [
|
|
6600
|
+
: [
|
|
6601
|
+
"type",
|
|
6602
|
+
"name",
|
|
6603
|
+
"description",
|
|
6604
|
+
"layoutType",
|
|
6605
|
+
"isFragment",
|
|
6606
|
+
"elements",
|
|
6607
|
+
],
|
|
6131
6608
|
path: "payload.data",
|
|
6132
6609
|
errorFactory,
|
|
6133
6610
|
});
|
|
@@ -6143,11 +6620,25 @@ const validateLayoutCreateData = ({ data, errorFactory }) => {
|
|
|
6143
6620
|
);
|
|
6144
6621
|
}
|
|
6145
6622
|
|
|
6623
|
+
if (data.description !== undefined && !isString(data.description)) {
|
|
6624
|
+
return invalidFromErrorFactory(
|
|
6625
|
+
errorFactory,
|
|
6626
|
+
"payload.data.description must be a string when provided",
|
|
6627
|
+
);
|
|
6628
|
+
}
|
|
6629
|
+
|
|
6146
6630
|
if (data.type === "layout") {
|
|
6147
6631
|
if (!LAYOUT_TYPE_KEYS.includes(data.layoutType)) {
|
|
6148
6632
|
return invalidFromErrorFactory(
|
|
6149
6633
|
errorFactory,
|
|
6150
|
-
"payload.data.layoutType must be 'normal', 'dialogue', 'nvl', or 'choice'",
|
|
6634
|
+
"payload.data.layoutType must be 'normal', 'save-load', 'confirmDialog', 'dialogue', 'nvl', or 'choice'",
|
|
6635
|
+
);
|
|
6636
|
+
}
|
|
6637
|
+
|
|
6638
|
+
if (data.isFragment !== undefined && typeof data.isFragment !== "boolean") {
|
|
6639
|
+
return invalidFromErrorFactory(
|
|
6640
|
+
errorFactory,
|
|
6641
|
+
"payload.data.isFragment must be a boolean when provided",
|
|
6151
6642
|
);
|
|
6152
6643
|
}
|
|
6153
6644
|
|
|
@@ -6170,7 +6661,7 @@ const validateLayoutUpdateData = ({ data, errorFactory }) => {
|
|
|
6170
6661
|
{
|
|
6171
6662
|
const result = validateAllowedKeys({
|
|
6172
6663
|
value: data,
|
|
6173
|
-
allowedKeys: ["name", "layoutType"],
|
|
6664
|
+
allowedKeys: ["name", "description", "layoutType", "isFragment"],
|
|
6174
6665
|
path: "payload.data",
|
|
6175
6666
|
errorFactory,
|
|
6176
6667
|
});
|
|
@@ -6193,13 +6684,27 @@ const validateLayoutUpdateData = ({ data, errorFactory }) => {
|
|
|
6193
6684
|
);
|
|
6194
6685
|
}
|
|
6195
6686
|
|
|
6687
|
+
if (data.description !== undefined && !isString(data.description)) {
|
|
6688
|
+
return invalidFromErrorFactory(
|
|
6689
|
+
errorFactory,
|
|
6690
|
+
"payload.data.description must be a string when provided",
|
|
6691
|
+
);
|
|
6692
|
+
}
|
|
6693
|
+
|
|
6196
6694
|
if (
|
|
6197
6695
|
data.layoutType !== undefined &&
|
|
6198
6696
|
!LAYOUT_TYPE_KEYS.includes(data.layoutType)
|
|
6199
6697
|
) {
|
|
6200
6698
|
return invalidFromErrorFactory(
|
|
6201
6699
|
errorFactory,
|
|
6202
|
-
"payload.data.layoutType must be 'normal', 'dialogue', 'nvl', or 'choice' when provided",
|
|
6700
|
+
"payload.data.layoutType must be 'normal', 'save-load', 'confirmDialog', 'dialogue', 'nvl', or 'choice' when provided",
|
|
6701
|
+
);
|
|
6702
|
+
}
|
|
6703
|
+
|
|
6704
|
+
if (data.isFragment !== undefined && typeof data.isFragment !== "boolean") {
|
|
6705
|
+
return invalidFromErrorFactory(
|
|
6706
|
+
errorFactory,
|
|
6707
|
+
"payload.data.isFragment must be a boolean when provided",
|
|
6203
6708
|
);
|
|
6204
6709
|
}
|
|
6205
6710
|
};
|
|
@@ -6510,9 +7015,71 @@ const validateVisualElementReferenceTargets = ({
|
|
|
6510
7015
|
}
|
|
6511
7016
|
}
|
|
6512
7017
|
|
|
7018
|
+
if (Array.isArray(data.conditionalOverrides)) {
|
|
7019
|
+
for (let index = 0; index < data.conditionalOverrides.length; index += 1) {
|
|
7020
|
+
const rule = data.conditionalOverrides[index];
|
|
7021
|
+
|
|
7022
|
+
for (const field of [
|
|
7023
|
+
"textStyleId",
|
|
7024
|
+
"hoverTextStyleId",
|
|
7025
|
+
"clickTextStyleId",
|
|
7026
|
+
]) {
|
|
7027
|
+
if (rule?.set?.[field] === undefined) {
|
|
7028
|
+
continue;
|
|
7029
|
+
}
|
|
7030
|
+
|
|
7031
|
+
const textStyle = state.textStyles.items[rule.set[field]];
|
|
7032
|
+
if (!isPlainObject(textStyle) || textStyle.type === "folder") {
|
|
7033
|
+
return invalidFromErrorFactory(
|
|
7034
|
+
errorFactory,
|
|
7035
|
+
`${ownerLabel} element conditionalOverrides.${index}.set.${field} must reference an existing non-folder text style`,
|
|
7036
|
+
{
|
|
7037
|
+
[ownerIdField]: ownerId,
|
|
7038
|
+
elementId,
|
|
7039
|
+
field: `conditionalOverrides.${index}.set.${field}`,
|
|
7040
|
+
targetId: rule.set[field],
|
|
7041
|
+
},
|
|
7042
|
+
);
|
|
7043
|
+
}
|
|
7044
|
+
}
|
|
7045
|
+
|
|
7046
|
+
for (const field of ["imageId", "hoverImageId", "clickImageId"]) {
|
|
7047
|
+
if (rule?.set?.[field] === undefined) {
|
|
7048
|
+
continue;
|
|
7049
|
+
}
|
|
7050
|
+
|
|
7051
|
+
const image = state.images.items[rule.set[field]];
|
|
7052
|
+
if (!isPlainObject(image) || image.type === "folder") {
|
|
7053
|
+
return invalidFromErrorFactory(
|
|
7054
|
+
errorFactory,
|
|
7055
|
+
`${ownerLabel} element conditionalOverrides.${index}.set.${field} must reference an existing non-folder image`,
|
|
7056
|
+
{
|
|
7057
|
+
[ownerIdField]: ownerId,
|
|
7058
|
+
elementId,
|
|
7059
|
+
field: `conditionalOverrides.${index}.set.${field}`,
|
|
7060
|
+
targetId: rule.set[field],
|
|
7061
|
+
},
|
|
7062
|
+
);
|
|
7063
|
+
}
|
|
7064
|
+
}
|
|
7065
|
+
|
|
7066
|
+
if (!isLayoutConditionTarget(state, rule?.when?.target)) {
|
|
7067
|
+
return invalidFromErrorFactory(
|
|
7068
|
+
errorFactory,
|
|
7069
|
+
`${ownerLabel} element conditionalOverrides.${index}.when.target must reference an existing variable or supported runtime condition`,
|
|
7070
|
+
{
|
|
7071
|
+
[ownerIdField]: ownerId,
|
|
7072
|
+
elementId,
|
|
7073
|
+
field: `conditionalOverrides.${index}.when.target`,
|
|
7074
|
+
targetId: rule?.when?.target,
|
|
7075
|
+
},
|
|
7076
|
+
);
|
|
7077
|
+
}
|
|
7078
|
+
}
|
|
7079
|
+
}
|
|
7080
|
+
|
|
6513
7081
|
if (data.variableId !== undefined) {
|
|
6514
|
-
|
|
6515
|
-
if (!isPlainObject(variable) || variable.type === "folder") {
|
|
7082
|
+
if (!isVariableReferenceTarget(state, data.variableId)) {
|
|
6516
7083
|
return invalidFromErrorFactory(
|
|
6517
7084
|
errorFactory,
|
|
6518
7085
|
`${ownerLabel} element variableId must reference an existing non-folder variable`,
|
|
@@ -6524,6 +7091,25 @@ const validateVisualElementReferenceTargets = ({
|
|
|
6524
7091
|
);
|
|
6525
7092
|
}
|
|
6526
7093
|
}
|
|
7094
|
+
|
|
7095
|
+
if (data.fragmentLayoutId !== undefined) {
|
|
7096
|
+
const fragmentLayout = state.layouts.items[data.fragmentLayoutId];
|
|
7097
|
+
if (
|
|
7098
|
+
!isPlainObject(fragmentLayout) ||
|
|
7099
|
+
fragmentLayout.type !== "layout" ||
|
|
7100
|
+
fragmentLayout.isFragment !== true
|
|
7101
|
+
) {
|
|
7102
|
+
return invalidFromErrorFactory(
|
|
7103
|
+
errorFactory,
|
|
7104
|
+
`${ownerLabel} element fragmentLayoutId must reference an existing fragment layout`,
|
|
7105
|
+
{
|
|
7106
|
+
[ownerIdField]: ownerId,
|
|
7107
|
+
elementId,
|
|
7108
|
+
fragmentLayoutId: data.fragmentLayoutId,
|
|
7109
|
+
},
|
|
7110
|
+
);
|
|
7111
|
+
}
|
|
7112
|
+
}
|
|
6527
7113
|
};
|
|
6528
7114
|
|
|
6529
7115
|
const getTreeIdsInOrder = ({ nodes }) => {
|
|
@@ -11190,7 +11776,9 @@ const COMMAND_DEFINITIONS = [
|
|
|
11190
11776
|
name: payload.data.name,
|
|
11191
11777
|
...(payload.data.type === "layout"
|
|
11192
11778
|
? {
|
|
11779
|
+
description: payload.data.description,
|
|
11193
11780
|
layoutType: payload.data.layoutType,
|
|
11781
|
+
isFragment: payload.data.isFragment,
|
|
11194
11782
|
elements: structuredClone(payload.data.elements),
|
|
11195
11783
|
}
|
|
11196
11784
|
: {}),
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const SYSTEM_VARIABLE_GROUPS = Object.freeze([
|
|
2
|
+
{
|
|
3
|
+
id: "routeEngine",
|
|
4
|
+
name: "Route Engine",
|
|
5
|
+
variables: Object.freeze([
|
|
6
|
+
{
|
|
7
|
+
id: "_skipUnseenText",
|
|
8
|
+
name: "Skip Unseen Text",
|
|
9
|
+
scope: "global-device",
|
|
10
|
+
type: "boolean",
|
|
11
|
+
default: false,
|
|
12
|
+
description:
|
|
13
|
+
"When enabled, skip mode can continue through lines the player has not viewed yet.",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "_dialogueTextSpeed",
|
|
17
|
+
name: "Dialogue Text Speed",
|
|
18
|
+
scope: "global-device",
|
|
19
|
+
type: "number",
|
|
20
|
+
default: 50,
|
|
21
|
+
description:
|
|
22
|
+
"Controls the default dialogue text speed stored for this device.",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "_currentSaveLoadPagination",
|
|
26
|
+
name: "Current Save/Load Pagination",
|
|
27
|
+
scope: "runtime-screen",
|
|
28
|
+
type: "number",
|
|
29
|
+
default: 0,
|
|
30
|
+
description:
|
|
31
|
+
"The current save/load pagination index. Resolves to 0, 1, 2, 3, and so on for the active save/load page.",
|
|
32
|
+
},
|
|
33
|
+
]),
|
|
34
|
+
},
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
export const SYSTEM_VARIABLE_IDS = Object.freeze(
|
|
38
|
+
SYSTEM_VARIABLE_GROUPS.flatMap((group) =>
|
|
39
|
+
(group.variables || []).map((variable) => variable.id),
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const SYSTEM_VARIABLE_ID_SET = new Set(SYSTEM_VARIABLE_IDS);
|
|
44
|
+
|
|
45
|
+
export const isSystemVariableId = (value) => {
|
|
46
|
+
return typeof value === "string" && SYSTEM_VARIABLE_ID_SET.has(value);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const getSystemVariableDefinitions = () => {
|
|
50
|
+
return SYSTEM_VARIABLE_GROUPS.flatMap((group) => group.variables || []);
|
|
51
|
+
};
|