@pixi-ui-editor/schema 0.20.0 → 0.22.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/index.js CHANGED
@@ -7,7 +7,7 @@ import { collectLocalizationIssues, createDefaultLocalization, LocalizationSchem
7
7
  export { collectBindingKeys, collectBindingKeyScopes, OWNER_BINDING_KEY_SCOPE } from "./bindingKeys.js";
8
8
  export { rewriteNodeReferences, rewritePrefabInstanceReferencePatches } from "./nodeReferences.js";
9
9
  export { clearNodeAssetReferences } from "./assetReferences.js";
10
- export const CURRENT_SCHEMA_VERSION = 44;
10
+ export const CURRENT_SCHEMA_VERSION = 45;
11
11
  const Id = Type.String({ format: "uuid" });
12
12
  const Name = Type.String({ minLength: 1 });
13
13
  /**
@@ -80,6 +80,8 @@ export const TextCaseSchema = Type.Union([Type.Literal("none"), Type.Literal("up
80
80
  // Omitted/`"none"` keeps historical documents byte-for-byte unchanged without a migration.
81
81
  const Text = Type.Composite([NodeBase, Type.Object({ type: Type.Literal("text"), text: Type.String(), textKey: Type.Optional(Type.String()), style: Type.Optional(TextStyleDefinitionSchema), textStyleOverrides: Type.Optional(TextStyleOverrides), autoSize: Type.Optional(Type.Boolean()), minFontSize: Type.Optional(Type.Number({ exclusiveMinimum: 0 })), textCase: Type.Optional(TextCaseSchema) })]);
82
82
  // Optional preserves existing documents; omitted animationSpeed is interpreted as 1 by every runtime.
83
+ // `animationMix` — длительность кроссфейда между анимациями в секундах (0..1). Отсутствие поля
84
+ // читается как 0, то есть исторический резкий переход, поэтому миграция документам не нужна.
83
85
  // `autoplay` is the serialized initial playback decision. Older documents are migrated to true,
84
86
  // preserving the runtime behavior they had before this field became explicit.
85
87
  const SpineProfileSettings = Type.Object({
@@ -88,6 +90,7 @@ const SpineProfileSettings = Type.Object({
88
90
  autoplay: Type.Boolean(),
89
91
  loop: Type.Boolean(),
90
92
  animationSpeed: Type.Number({ exclusiveMinimum: 0 }),
93
+ animationMix: Type.Number({ minimum: 0, maximum: 1 }),
91
94
  });
92
95
  const SpineOverrides = Type.Partial(Type.Object({ desktop: Type.Partial(SpineProfileSettings), mobile: Type.Partial(SpineProfileSettings) }));
93
96
  /** A presentation-time mount point: game code decides when the target follows its Spine part. */
@@ -95,19 +98,8 @@ export const SpineConnectorSchema = Type.Object({
95
98
  partType: Type.Union([Type.Literal("bone"), Type.Literal("slot")]),
96
99
  partName: Type.String({ minLength: 1 }),
97
100
  targetNodeId: NodeAddress,
98
- // Множитель проецируемой позиции части по осям (1 = следовать за костью ровно, 2 = вдвое дальше
99
- // от origin ноды Spine, отрицательное значение — зеркально). Отсутствует — `{ x: 1, y: 1 }`.
100
- // Profile-aware тем же sparse-mobile приёмом, что `spineOverrides` (ADR 0022): base редактируется
101
- // в Horizontal, `positionMultiplierOverrides.mobile` — в Vertical.
102
- positionMultiplier: Type.Optional(Type.Object({ x: Type.Number(), y: Type.Number() })),
103
- positionMultiplierOverrides: Type.Optional(Type.Object({ mobile: Type.Optional(Type.Object({ x: Type.Optional(Type.Number()), y: Type.Optional(Type.Number()) })) })),
104
101
  });
105
- export function resolveSpineConnectorPositionMultiplier(connector, profile) {
106
- const base = connector.positionMultiplier ?? { x: 1, y: 1 };
107
- const override = profile === "desktop" ? undefined : connector.positionMultiplierOverrides?.mobile;
108
- return { x: override?.x ?? base.x, y: override?.y ?? base.y };
109
- }
110
- const Spine = Type.Composite([NodeBase, Type.Object({ type: Type.Literal("spine"), assetId: Id, opacity: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })), animation: Type.Optional(Type.String({ minLength: 1 })), autoplay: Type.Optional(Type.Boolean()), loop: Type.Optional(Type.Boolean()), animationSpeed: Type.Optional(Type.Number({ exclusiveMinimum: 0 })), spineOverrides: Type.Optional(SpineOverrides), connectors: Type.Optional(Type.Array(SpineConnectorSchema)) })]);
102
+ const Spine = Type.Composite([NodeBase, Type.Object({ type: Type.Literal("spine"), assetId: Id, opacity: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })), animation: Type.Optional(Type.String({ minLength: 1 })), autoplay: Type.Optional(Type.Boolean()), loop: Type.Optional(Type.Boolean()), animationSpeed: Type.Optional(Type.Number({ exclusiveMinimum: 0 })), animationMix: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })), spineOverrides: Type.Optional(SpineOverrides), connectors: Type.Optional(Type.Array(SpineConnectorSchema)) })]);
111
103
  const BitmapTextAlign = Type.Union([Type.Literal("left"), Type.Literal("center"), Type.Literal("right")]);
112
104
  // A bitmap font's glyphs come pre-rendered from its texture, so unlike `Text` there is no system
113
105
  // fallback: `assetId` is required (like Spine's), and the "Bitmap Text" add-node entry stays disabled
@@ -1333,6 +1325,44 @@ function migrateV42ToV43(document) {
1333
1325
  }
1334
1326
  /** v44 adds an optional Spine opacity; existing Spine nodes remain fully opaque. */
1335
1327
  function migrateV43ToV44(_document) { }
1328
+ /**
1329
+ * v45 удаляет `positionMultiplier`/`positionMultiplierOverrides` у Spine-коннекторов. Множитель был
1330
+ * обходным путём вокруг того, что цель уезжала в дерево спайна и получала его масштаб; коннектор
1331
+ * теперь просто перезаписывает позицию цели мировой позицией части (см.
1332
+ * `docs/spine-bone-attachment-thing-editor-reference.md`), и умножать стало нечего. Заменить поля
1333
+ * нечем — авторские значения подбирались под старую проекцию, поэтому они просто удаляются.
1334
+ */
1335
+ function migrateV44ToV45(document) {
1336
+ const obsolete = ["positionMultiplier", "positionMultiplierOverrides"];
1337
+ const strip = (record) => {
1338
+ if (!Array.isArray(record.connectors))
1339
+ return;
1340
+ for (const connector of record.connectors) {
1341
+ if (typeof connector !== "object" || connector === null)
1342
+ continue;
1343
+ for (const key of obsolete)
1344
+ delete connector[key];
1345
+ }
1346
+ };
1347
+ const visit = (node) => {
1348
+ strip(node);
1349
+ if (Array.isArray(node.propertyOverrides))
1350
+ for (const override of node.propertyOverrides) {
1351
+ const patch = override.patch;
1352
+ if (typeof patch === "object" && patch !== null)
1353
+ strip(patch);
1354
+ }
1355
+ if (Array.isArray(node.locallyAddedNodes))
1356
+ for (const local of node.locallyAddedNodes)
1357
+ if (typeof local === "object" && local !== null)
1358
+ visit(local);
1359
+ };
1360
+ const owners = [...(document.scenes ?? []), ...(document.prefabs ?? [])];
1361
+ for (const owner of owners)
1362
+ for (const node of owner.nodes ?? [])
1363
+ if (typeof node === "object" && node !== null)
1364
+ visit(node);
1365
+ }
1336
1366
  export function migrateProjectDocument(input) {
1337
1367
  if (typeof input !== "object" || input === null || !Object.hasOwn(input, "schemaVersion"))
1338
1368
  throw new ProjectDocumentMigrationError("A schemaVersion is required for migration.");
@@ -1398,6 +1428,8 @@ export function migrateProjectDocument(input) {
1398
1428
  migrateV42ToV43(migrated);
1399
1429
  if (version <= 43)
1400
1430
  migrateV43ToV44(migrated);
1431
+ if (version <= 44)
1432
+ migrateV44ToV45(migrated);
1401
1433
  // v36 → v37 (position multiplier у spine-коннекторов) не требует переписывания данных:
1402
1434
  // добавлены только опциональные поля коннектора, и любой документ v36 уже валиден как v37.
1403
1435
  // v34 → v35 (`states.normalAssetId` стал опциональным у button/staged-button) не требует