@pixi-ui-editor/schema 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assetReferences.d.ts +17 -0
- package/dist/assetReferences.d.ts.map +1 -0
- package/dist/assetReferences.js +79 -0
- package/dist/assetReferences.js.map +1 -0
- package/dist/index.d.ts +213 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +64 -34
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,8 @@ import { collectBindingKeys, collectBindingKeyScopes } from "./bindingKeys.js";
|
|
|
6
6
|
import { collectLocalizationIssues, createDefaultLocalization, LocalizationSchema } from "./localization.js";
|
|
7
7
|
export { collectBindingKeys, collectBindingKeyScopes, OWNER_BINDING_KEY_SCOPE } from "./bindingKeys.js";
|
|
8
8
|
export { rewriteNodeReferences, rewritePrefabInstanceReferencePatches } from "./nodeReferences.js";
|
|
9
|
-
export
|
|
9
|
+
export { clearNodeAssetReferences } from "./assetReferences.js";
|
|
10
|
+
export const CURRENT_SCHEMA_VERSION = 40;
|
|
10
11
|
const Id = Type.String({ format: "uuid" });
|
|
11
12
|
const Name = Type.String({ minLength: 1 });
|
|
12
13
|
/**
|
|
@@ -109,18 +110,25 @@ const BitmapTextAlign = Type.Union([Type.Literal("left"), Type.Literal("center")
|
|
|
109
110
|
// A bitmap font's glyphs come pre-rendered from its texture, so unlike `Text` there is no system
|
|
110
111
|
// fallback: `assetId` is required (like Spine's), and the "Bitmap Text" add-node entry stays disabled
|
|
111
112
|
// until at least one `bitmapFont` asset exists — a bitmap-text node with no asset has nothing to draw.
|
|
113
|
+
const BitmapTextStyle = Type.Object({
|
|
114
|
+
assetId: Id,
|
|
115
|
+
// The authored/preferred size. With `autoSize` on, this doubles as the upper bound the runtime
|
|
116
|
+
// shrinks down from — never grows past it — the same "Best Fit" contract Unity's legacy Text uses.
|
|
117
|
+
fontSize: Type.Number({ exclusiveMinimum: 0 }),
|
|
118
|
+
fill: Type.String({ pattern: "^#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$" }),
|
|
119
|
+
align: BitmapTextAlign,
|
|
120
|
+
verticalAlign: Type.Union([Type.Literal("top"), Type.Literal("middle"), Type.Literal("bottom")]),
|
|
121
|
+
letterSpacing: Type.Number(),
|
|
122
|
+
wordWrap: Type.Boolean(),
|
|
123
|
+
});
|
|
124
|
+
const BitmapTextOverrides = Type.Partial(Type.Object({ desktop: Type.Partial(BitmapTextStyle), mobile: Type.Partial(BitmapTextStyle) }));
|
|
112
125
|
const BitmapText = Type.Composite([NodeBase, Type.Object({
|
|
113
126
|
type: Type.Literal("bitmap-text"),
|
|
127
|
+
// Content and runtime fit are shared. The visual font presentation follows the same base/mobile
|
|
128
|
+
// override convention as `Text.style`; text remains one localization/game-data value.
|
|
114
129
|
text: Type.String(),
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// shrinks down from — never grows past it — the same "Best Fit" contract Unity's legacy Text uses.
|
|
118
|
-
fontSize: Type.Number({ exclusiveMinimum: 0 }),
|
|
119
|
-
fill: Type.String({ pattern: "^#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$" }),
|
|
120
|
-
align: BitmapTextAlign,
|
|
121
|
-
verticalAlign: Type.Union([Type.Literal("top"), Type.Literal("middle"), Type.Literal("bottom")]),
|
|
122
|
-
letterSpacing: Type.Number(),
|
|
123
|
-
wordWrap: Type.Boolean(),
|
|
130
|
+
...BitmapTextStyle.properties,
|
|
131
|
+
bitmapTextOverrides: Type.Optional(BitmapTextOverrides),
|
|
124
132
|
// Omitted/false keeps the historical behavior: fixed `fontSize`, overflow simply clips/overruns.
|
|
125
133
|
autoSize: Type.Optional(Type.Boolean()),
|
|
126
134
|
// The floor `autoSize` won't shrink past; omitted defaults to 1 at the runtime/view layer, not here,
|
|
@@ -422,6 +430,12 @@ const structural = documentAjv.compile(ProjectDocumentSchema);
|
|
|
422
430
|
const structuralNode = createAjv().compile(UINodeSchema);
|
|
423
431
|
export function createStableId() { return crypto.randomUUID(); }
|
|
424
432
|
const add = (issues, code, path, message) => issues.push({ code, path, message, severity: "error" });
|
|
433
|
+
// A missing asset reference is downgraded to a warning: deleting an asset must always be possible
|
|
434
|
+
// (see AssetPanel's delete action), and after deletion any node still pointing at it necessarily
|
|
435
|
+
// dangles. Blocking the whole document as invalid would make asset deletion silently no-op instead —
|
|
436
|
+
// exactly the confusing "it won't delete" experience this severity exists to avoid. A type mismatch
|
|
437
|
+
// (`INCOMPATIBLE_ASSET_REFERENCE`) stays an error: that is real corruption, not a dangling reference.
|
|
438
|
+
const addWarning = (issues, code, path, message) => issues.push({ code, path, message, severity: "warning" });
|
|
425
439
|
/** Общая проверка владельца: одинаковые issue получают и сцена, и определение пресета. */
|
|
426
440
|
function checkBindingKeys(document, owner, path, issues) {
|
|
427
441
|
const graph = resolveOwnerNodeGraph(document, owner);
|
|
@@ -571,14 +585,14 @@ function hierarchy(owner, path, assets, prefabs, effects, issues) {
|
|
|
571
585
|
if ((node.type === "image" || node.type === "spine") && node.assetId !== undefined) {
|
|
572
586
|
const asset = assets.get(node.assetId);
|
|
573
587
|
if (!asset)
|
|
574
|
-
|
|
588
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", `${nodePath}/assetId`, `Asset '${node.assetId}' does not exist.`);
|
|
575
589
|
else if (asset.type !== node.type)
|
|
576
590
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", `${nodePath}/assetId`, `A ${node.type} node requires a ${node.type} asset.`);
|
|
577
591
|
}
|
|
578
592
|
if (isLayoutGroup(node) && node.backgroundAssetId !== undefined) {
|
|
579
593
|
const asset = assets.get(node.backgroundAssetId);
|
|
580
594
|
if (!asset)
|
|
581
|
-
|
|
595
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", `${nodePath}/backgroundAssetId`, `Background asset '${node.backgroundAssetId}' does not exist.`);
|
|
582
596
|
else if (asset.type !== "image")
|
|
583
597
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", `${nodePath}/backgroundAssetId`, "A layout group background requires an image asset.");
|
|
584
598
|
}
|
|
@@ -586,7 +600,7 @@ function hierarchy(owner, path, assets, prefabs, effects, issues) {
|
|
|
586
600
|
if (node.type === "button")
|
|
587
601
|
BUTTON_STATE_KEYS.forEach((state) => { const field = `${state}AssetId`; const assetId = node.states[field]; if (assetId === undefined)
|
|
588
602
|
return; const asset = assets.get(assetId), path = `${nodePath}/states/${field}`; if (!asset)
|
|
589
|
-
|
|
603
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", path, `Asset '${assetId}' does not exist.`);
|
|
590
604
|
else if (asset.type !== "image")
|
|
591
605
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", path, `A button '${state}' state requires an image asset.`); });
|
|
592
606
|
if (node.type === "button")
|
|
@@ -596,7 +610,7 @@ function hierarchy(owner, path, assets, prefabs, effects, issues) {
|
|
|
596
610
|
continue;
|
|
597
611
|
const asset = assets.get(assetId), soundPath = `${nodePath}/sounds/${field}`;
|
|
598
612
|
if (!asset)
|
|
599
|
-
|
|
613
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", soundPath, `Asset '${assetId}' does not exist.`);
|
|
600
614
|
else if (asset.type !== "sound")
|
|
601
615
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", soundPath, `A button ${field} requires a sound asset.`);
|
|
602
616
|
}
|
|
@@ -608,7 +622,7 @@ function hierarchy(owner, path, assets, prefabs, effects, issues) {
|
|
|
608
622
|
stageIds.add(stage.id);
|
|
609
623
|
BUTTON_STATE_KEYS.forEach((state) => { const field = `${state}AssetId`; const assetId = stage.states[field]; if (assetId === undefined)
|
|
610
624
|
return; const asset = assets.get(assetId), statePath = `${nodePath}/stages/${stageIndex}/states/${field}`; if (!asset)
|
|
611
|
-
|
|
625
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", statePath, `Asset '${assetId}' does not exist.`);
|
|
612
626
|
else if (asset.type !== "image")
|
|
613
627
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", statePath, `A staged button stage '${state}' state requires an image asset.`); });
|
|
614
628
|
});
|
|
@@ -620,7 +634,7 @@ function hierarchy(owner, path, assets, prefabs, effects, issues) {
|
|
|
620
634
|
continue;
|
|
621
635
|
const asset = assets.get(assetId), soundPath = `${nodePath}/sounds/${field}`;
|
|
622
636
|
if (!asset)
|
|
623
|
-
|
|
637
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", soundPath, `Asset '${assetId}' does not exist.`);
|
|
624
638
|
else if (asset.type !== "sound")
|
|
625
639
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", soundPath, `A staged button ${field} requires a sound asset.`);
|
|
626
640
|
}
|
|
@@ -628,7 +642,7 @@ function hierarchy(owner, path, assets, prefabs, effects, issues) {
|
|
|
628
642
|
if (node.type === "checkbox")
|
|
629
643
|
CHECKBOX_STATE_KEYS.forEach((state) => { const field = `${state}AssetId`; const assetId = node.states[field]; if (assetId === undefined)
|
|
630
644
|
return; const asset = assets.get(assetId), path = `${nodePath}/states/${field}`; if (!asset)
|
|
631
|
-
|
|
645
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", path, `Asset '${assetId}' does not exist.`);
|
|
632
646
|
else if (asset.type !== "image")
|
|
633
647
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", path, `A checkbox '${state}' state requires an image asset.`); });
|
|
634
648
|
if (node.type === "checkbox")
|
|
@@ -638,16 +652,25 @@ function hierarchy(owner, path, assets, prefabs, effects, issues) {
|
|
|
638
652
|
continue;
|
|
639
653
|
const asset = assets.get(assetId), soundPath = `${nodePath}/sounds/${field}`;
|
|
640
654
|
if (!asset)
|
|
641
|
-
|
|
655
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", soundPath, `Asset '${assetId}' does not exist.`);
|
|
642
656
|
else if (asset.type !== "sound")
|
|
643
657
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", soundPath, `A checkbox ${field} requires a sound asset.`);
|
|
644
658
|
}
|
|
645
659
|
if (node.type === "bitmap-text") {
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
660
|
+
const fontReferences = [
|
|
661
|
+
[node.assetId, `${nodePath}/assetId`],
|
|
662
|
+
[node.bitmapTextOverrides?.desktop?.assetId, `${nodePath}/bitmapTextOverrides/desktop/assetId`],
|
|
663
|
+
[node.bitmapTextOverrides?.mobile?.assetId, `${nodePath}/bitmapTextOverrides/mobile/assetId`],
|
|
664
|
+
];
|
|
665
|
+
for (const [assetId, assetPath] of fontReferences) {
|
|
666
|
+
if (assetId === undefined)
|
|
667
|
+
continue;
|
|
668
|
+
const asset = assets.get(assetId);
|
|
669
|
+
if (!asset)
|
|
670
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", assetPath, `Asset '${assetId}' does not exist.`);
|
|
671
|
+
else if (asset.type !== "bitmapFont")
|
|
672
|
+
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", assetPath, "A bitmap text node requires a bitmap font asset.");
|
|
673
|
+
}
|
|
651
674
|
}
|
|
652
675
|
if (node.type === "text") {
|
|
653
676
|
const fontReferences = [
|
|
@@ -660,7 +683,7 @@ function hierarchy(owner, path, assets, prefabs, effects, issues) {
|
|
|
660
683
|
continue;
|
|
661
684
|
const asset = assets.get(fontAssetId);
|
|
662
685
|
if (!asset)
|
|
663
|
-
|
|
686
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", fontPath, `Asset '${fontAssetId}' does not exist.`);
|
|
664
687
|
else if (asset.type !== "font")
|
|
665
688
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", fontPath, "A text node fontAssetId requires a font asset.");
|
|
666
689
|
}
|
|
@@ -668,14 +691,14 @@ function hierarchy(owner, path, assets, prefabs, effects, issues) {
|
|
|
668
691
|
if (node.type === "input" && node.backgroundAssetId !== undefined) {
|
|
669
692
|
const asset = assets.get(node.backgroundAssetId);
|
|
670
693
|
if (!asset)
|
|
671
|
-
|
|
694
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", `${nodePath}/backgroundAssetId`, `Background asset '${node.backgroundAssetId}' does not exist.`);
|
|
672
695
|
else if (asset.type !== "image")
|
|
673
696
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", `${nodePath}/backgroundAssetId`, "An input background requires an image asset.");
|
|
674
697
|
}
|
|
675
698
|
if (node.type === "input" && node.textStyle.fontAssetId !== undefined) {
|
|
676
699
|
const asset = assets.get(node.textStyle.fontAssetId), path = `${nodePath}/textStyle/fontAssetId`;
|
|
677
700
|
if (!asset)
|
|
678
|
-
|
|
701
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", path, `Asset '${node.textStyle.fontAssetId}' does not exist.`);
|
|
679
702
|
else if (asset.type !== "font")
|
|
680
703
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", path, "An input node textStyle fontAssetId requires a font asset.");
|
|
681
704
|
}
|
|
@@ -687,7 +710,7 @@ function hierarchy(owner, path, assets, prefabs, effects, issues) {
|
|
|
687
710
|
const asset = assets.get(assetId);
|
|
688
711
|
const assetPath = `${nodePath}/${field}`;
|
|
689
712
|
if (!asset)
|
|
690
|
-
|
|
713
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", assetPath, `Asset '${assetId}' does not exist.`);
|
|
691
714
|
else if (asset.type !== "image")
|
|
692
715
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", assetPath, `A ${node.type} ${field} requires an image asset.`);
|
|
693
716
|
}
|
|
@@ -700,7 +723,7 @@ function hierarchy(owner, path, assets, prefabs, effects, issues) {
|
|
|
700
723
|
if (node.valueTextStyle?.fontAssetId !== undefined) {
|
|
701
724
|
const asset = assets.get(node.valueTextStyle.fontAssetId), fontPath = `${nodePath}/valueTextStyle/fontAssetId`;
|
|
702
725
|
if (!asset)
|
|
703
|
-
|
|
726
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", fontPath, `Asset '${node.valueTextStyle.fontAssetId}' does not exist.`);
|
|
704
727
|
else if (asset.type !== "font")
|
|
705
728
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", fontPath, "A slider valueTextStyle fontAssetId requires a font asset.");
|
|
706
729
|
}
|
|
@@ -718,7 +741,7 @@ function hierarchy(owner, path, assets, prefabs, effects, issues) {
|
|
|
718
741
|
}
|
|
719
742
|
const asset = assets.get(assetId);
|
|
720
743
|
if (!asset)
|
|
721
|
-
|
|
744
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", assetPath, `Asset '${assetId}' does not exist.`);
|
|
722
745
|
else if (asset.type !== "image")
|
|
723
746
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", assetPath, `A pagination ${field} requires an image asset.`);
|
|
724
747
|
}
|
|
@@ -728,7 +751,7 @@ function hierarchy(owner, path, assets, prefabs, effects, issues) {
|
|
|
728
751
|
const asset = assets.get(node.numberStyle.fontAssetId);
|
|
729
752
|
const fontPath = `${nodePath}/numberStyle/fontAssetId`;
|
|
730
753
|
if (!asset)
|
|
731
|
-
|
|
754
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", fontPath, `Asset '${node.numberStyle.fontAssetId}' does not exist.`);
|
|
732
755
|
else if (asset.type !== "font")
|
|
733
756
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", fontPath, "A pagination numberStyle fontAssetId requires a font asset.");
|
|
734
757
|
}
|
|
@@ -736,7 +759,7 @@ function hierarchy(owner, path, assets, prefabs, effects, issues) {
|
|
|
736
759
|
const asset = assets.get(node.numberActiveBackgroundAssetId);
|
|
737
760
|
const bgPath = `${nodePath}/numberActiveBackgroundAssetId`;
|
|
738
761
|
if (!asset)
|
|
739
|
-
|
|
762
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", bgPath, `Asset '${node.numberActiveBackgroundAssetId}' does not exist.`);
|
|
740
763
|
else if (asset.type !== "image")
|
|
741
764
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", bgPath, "A pagination numberActiveBackgroundAssetId requires an image asset.");
|
|
742
765
|
}
|
|
@@ -801,7 +824,7 @@ function semantic(document) {
|
|
|
801
824
|
add(issues, "INVALID_PARTICLE_RANGE", `${p}/${field}`, "Particle range min must not exceed max."); }); if (e.rate === 0 && e.bursts.length === 0)
|
|
802
825
|
add(issues, "EMPTY_PARTICLE_EMISSION", `${p}/emission`, "Particle emission needs a rate or a burst."); e.bursts.forEach((burst, j) => { if (burst.time > e.duration)
|
|
803
826
|
add(issues, "PARTICLE_BURST_OUTSIDE_DURATION", `${p}/emission/bursts/${j}/time`, "Particle burst time must be inside emission duration."); }); const source = v.source; particleAssetIds(effect).forEach((id, j) => { const asset = assets.get(id), sourcePath = source.type === "single" ? `${p}/particle/visual/source/assetId` : `${p}/particle/visual/source/assetIds/${j}`; if (!asset)
|
|
804
|
-
|
|
827
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", sourcePath, `Asset '${id}' does not exist.`);
|
|
805
828
|
else if (asset.type !== "image")
|
|
806
829
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", sourcePath, "Particle visual source requires an image asset or atlas frame."); }); });
|
|
807
830
|
document.prefabs.forEach((prefab, i) => { register(prefab.id, `/prefabs/${i}/id`); prefabs.add(prefab.id); });
|
|
@@ -856,7 +879,7 @@ function semantic(document) {
|
|
|
856
879
|
validateInstance(node, `/scenes/${i}/nodes/${j}`); }); const musicAssetId = scene.audio?.backgroundMusicAssetId; if (musicAssetId !== undefined) {
|
|
857
880
|
const asset = assets.get(musicAssetId), musicPath = `/scenes/${i}/audio/backgroundMusicAssetId`;
|
|
858
881
|
if (!asset)
|
|
859
|
-
|
|
882
|
+
addWarning(issues, "MISSING_ASSET_REFERENCE", musicPath, `Asset '${musicAssetId}' does not exist.`);
|
|
860
883
|
else if (asset.type !== "sound")
|
|
861
884
|
add(issues, "INCOMPATIBLE_ASSET_REFERENCE", musicPath, "Background music requires a sound asset.");
|
|
862
885
|
} checkBindingKeys(document, scene, `/scenes/${i}`, issues); checkNodeReferences(document, scene, `/scenes/${i}`, issues); });
|
|
@@ -1271,11 +1294,15 @@ function migrateV32ToV33(document) {
|
|
|
1271
1294
|
normalizeLegacySpineConnectors(document);
|
|
1272
1295
|
normalizeLegacyPagination(document);
|
|
1273
1296
|
}
|
|
1297
|
+
/** v40 adds sparse bitmap-text presentation overrides; existing values remain the shared base. */
|
|
1298
|
+
function migrateV39ToV40(_document) { }
|
|
1274
1299
|
export function migrateProjectDocument(input) {
|
|
1275
1300
|
if (typeof input !== "object" || input === null || !Object.hasOwn(input, "schemaVersion"))
|
|
1276
1301
|
throw new ProjectDocumentMigrationError("A schemaVersion is required for migration.");
|
|
1277
1302
|
const version = input.schemaVersion;
|
|
1278
|
-
if (typeof version
|
|
1303
|
+
if (typeof version === "number" && Number.isInteger(version) && version > CURRENT_SCHEMA_VERSION)
|
|
1304
|
+
throw new ProjectDocumentMigrationError(`This project was saved with schema version ${version}, which is newer than schema version ${CURRENT_SCHEMA_VERSION} supported by this editor. Update the editor to open it.`);
|
|
1305
|
+
if (typeof version !== "number" || !Number.isInteger(version) || version < 1)
|
|
1279
1306
|
throw new ProjectDocumentMigrationError(`Unsupported schemaVersion '${String(version)}'.`);
|
|
1280
1307
|
const migrated = structuredClone(input);
|
|
1281
1308
|
if (version <= 1)
|
|
@@ -1324,6 +1351,8 @@ export function migrateProjectDocument(input) {
|
|
|
1324
1351
|
migrateV31ToV32(migrated);
|
|
1325
1352
|
if (version <= 32)
|
|
1326
1353
|
migrateV32ToV33(migrated);
|
|
1354
|
+
if (version <= 39)
|
|
1355
|
+
migrateV39ToV40(migrated);
|
|
1327
1356
|
// v36 → v37 (position multiplier у spine-коннекторов) не требует переписывания данных:
|
|
1328
1357
|
// добавлены только опциональные поля коннектора, и любой документ v36 уже валиден как v37.
|
|
1329
1358
|
// v34 → v35 (`states.normalAssetId` стал опциональным у button/staged-button) не требует
|
|
@@ -1338,6 +1367,7 @@ export function migrateProjectDocument(input) {
|
|
|
1338
1367
|
// v15 хранить не мог, поэтому запрещённых значений в старых документах не бывает.
|
|
1339
1368
|
// v38 → v39 (per-state button tint/opacity) не требует переписывания данных: новые appearance
|
|
1340
1369
|
// fields optional and therefore resolve to white / 1 for every existing button.
|
|
1370
|
+
// v39 → v40 (bitmap-text presentation overrides) likewise adds only optional sparse fields.
|
|
1341
1371
|
// v37 → v38 (page-group carousel arrows) не требует переписывания данных: добавлены только
|
|
1342
1372
|
// опциональные поля `pageUpButtonNodeId`/`pageDownButtonNodeId`, любой документ v37 уже валиден как v38.
|
|
1343
1373
|
migrated.schemaVersion = CURRENT_SCHEMA_VERSION;
|