@linxin666/dsh-pet 0.2.3 → 0.2.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/README.i18n.yaml +2 -2
- package/README.md +65 -2
- package/README.zh.md +65 -2
- package/assets/decorations/whale/decoration.json +20 -0
- package/assets/decorations/whale/whale-frames.png +0 -0
- package/contracts/pet-manifest-v2.schema.json +281 -0
- package/contracts/status-decoration-v1.schema.json +144 -0
- package/contracts/voice-pack-v1.schema.json +234 -0
- package/lib/client.js +251 -43
- package/lib/client.js.map +1 -1
- package/lib/index.js +1139 -100
- package/lib/types/access.d.ts +16 -0
- package/lib/types/access.d.ts.map +1 -0
- package/lib/types/access.js +20 -0
- package/lib/types/chatter.d.ts +61 -3
- package/lib/types/chatter.d.ts.map +1 -1
- package/lib/types/chatter.js +71 -12
- package/lib/types/client/PetSettingsCard.d.ts +4 -0
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSettingsCard.js +11 -3
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +111 -4
- package/lib/types/client/locales.d.ts +4 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +4 -0
- package/lib/types/client/renderers/live2d/Live2dVisualMount.d.ts.map +1 -1
- package/lib/types/client/renderers/live2d/Live2dVisualMount.js +1 -0
- package/lib/types/client/renderers/live2d/runtime.d.ts +13 -1
- package/lib/types/client/renderers/live2d/runtime.d.ts.map +1 -1
- package/lib/types/client/renderers/live2d.d.ts.map +1 -1
- package/lib/types/client/renderers/live2d.js +133 -25
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +9 -6
- package/lib/types/contracts/status-decoration.d.ts +85 -0
- package/lib/types/contracts/status-decoration.d.ts.map +1 -0
- package/lib/types/contracts/status-decoration.js +21 -0
- package/lib/types/decoration.d.ts +39 -0
- package/lib/types/decoration.d.ts.map +1 -0
- package/lib/types/decoration.js +210 -0
- package/lib/types/event-projection.d.ts +8 -3
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +9 -4
- package/lib/types/image-dimensions.d.ts +26 -0
- package/lib/types/image-dimensions.d.ts.map +1 -0
- package/lib/types/image-dimensions.js +77 -0
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +3 -1
- package/lib/types/registry.d.ts +55 -2
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +240 -16
- package/lib/types/routes.d.ts +6 -1
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +163 -32
- package/lib/types/service.d.ts +33 -0
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +42 -3
- package/lib/types/voice-pack.d.ts +98 -0
- package/lib/types/voice-pack.d.ts.map +1 -0
- package/lib/types/voice-pack.js +384 -0
- package/package.json +11 -10
- package/src/access.ts +40 -0
- package/src/chatter.test.ts +89 -2
- package/src/chatter.ts +120 -14
- package/src/client/PetSettingsCard.tsx +26 -2
- package/src/client/PetSprite.test.tsx +325 -12
- package/src/client/PetSprite.tsx +150 -25
- package/src/client/locales.ts +4 -0
- package/src/client/renderers/live2d/Live2dVisualMount.test.tsx +72 -0
- package/src/client/renderers/live2d/Live2dVisualMount.tsx +1 -0
- package/src/client/renderers/live2d/runtime.ts +8 -2
- package/src/client/renderers/live2d.test.ts +233 -9
- package/src/client/renderers/live2d.ts +143 -30
- package/src/client/settings-form.ts +8 -6
- package/src/contracts/status-decoration.ts +78 -0
- package/src/decoration.test.ts +178 -0
- package/src/decoration.ts +220 -0
- package/src/event-projection.ts +10 -5
- package/src/image-dimensions.test.ts +85 -0
- package/src/image-dimensions.ts +76 -0
- package/src/index.ts +3 -1
- package/src/registry.test.ts +276 -0
- package/src/registry.ts +274 -15
- package/src/routes.ts +162 -33
- package/src/service.ts +59 -3
- package/src/voice-pack.test.ts +216 -0
- package/src/voice-pack.ts +413 -0
package/lib/client.js
CHANGED
|
@@ -220,6 +220,78 @@ window.__ModuleLoader__.load({
|
|
|
220
220
|
return Math.max(0, Math.min(max, value));
|
|
221
221
|
}
|
|
222
222
|
/**
|
|
223
|
+
* The status decoration ornament (pet-center M5, #567). Renders the active
|
|
224
|
+
* phase's frame segment as a CSS-background strip at a compact bubble
|
|
225
|
+
* height; prefers-reduced-motion holds the segment's first frame, and a
|
|
226
|
+
* missing or undecodable asset simply paints nothing (CSS background
|
|
227
|
+
* failure) — the bubble text is never disturbed. The span is aria-hidden;
|
|
228
|
+
* the bubble keeps its own semantics untouched.
|
|
229
|
+
*/
|
|
230
|
+
function StatusOrnament(props) {
|
|
231
|
+
const { decoration, phase } = props;
|
|
232
|
+
const segment = decoration.phases[phase];
|
|
233
|
+
const shown = segment !== void 0 && segment !== "hide";
|
|
234
|
+
const segmentKey = segment !== void 0 && segment !== "hide" ? segment.from + ":" + segment.to : "none";
|
|
235
|
+
const spanRef = (0, react.useRef)(null);
|
|
236
|
+
const scale = 18 / decoration.cell.height;
|
|
237
|
+
const frameWidth = Math.round(decoration.cell.width * scale);
|
|
238
|
+
const stripWidth = decoration.columns * frameWidth;
|
|
239
|
+
const durationsKey = decoration.durations.join(",");
|
|
240
|
+
(0, react.useEffect)(() => {
|
|
241
|
+
if (segment === void 0 || segment === "hide") return;
|
|
242
|
+
const el = spanRef.current;
|
|
243
|
+
if (el === null) return;
|
|
244
|
+
const position = (index) => -index * frameWidth + "px 0px";
|
|
245
|
+
const reduceMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches === true;
|
|
246
|
+
el.style.backgroundPosition = position(segment.from);
|
|
247
|
+
if (reduceMotion || segment.from === segment.to) return;
|
|
248
|
+
let raf = 0;
|
|
249
|
+
let index = segment.from;
|
|
250
|
+
let elapsed = 0;
|
|
251
|
+
let last = performance.now();
|
|
252
|
+
const tick = (ts) => {
|
|
253
|
+
const delta = ts - last;
|
|
254
|
+
last = ts;
|
|
255
|
+
elapsed += delta;
|
|
256
|
+
const duration = decoration.durations[index] ?? 160;
|
|
257
|
+
if (elapsed >= duration) {
|
|
258
|
+
elapsed = 0;
|
|
259
|
+
if (index < segment.to) index += 1;
|
|
260
|
+
else if (decoration.loop) index = segment.from;
|
|
261
|
+
el.style.backgroundPosition = position(index);
|
|
262
|
+
}
|
|
263
|
+
if (!decoration.loop && index === segment.to) return;
|
|
264
|
+
raf = requestAnimationFrame(tick);
|
|
265
|
+
};
|
|
266
|
+
raf = requestAnimationFrame(tick);
|
|
267
|
+
return () => cancelAnimationFrame(raf);
|
|
268
|
+
}, [
|
|
269
|
+
shown,
|
|
270
|
+
segmentKey,
|
|
271
|
+
frameWidth,
|
|
272
|
+
decoration.loop,
|
|
273
|
+
durationsKey
|
|
274
|
+
]);
|
|
275
|
+
if (!shown) return null;
|
|
276
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
277
|
+
ref: spanRef,
|
|
278
|
+
"aria-hidden": "true",
|
|
279
|
+
"data-dsh-pet-decoration": decoration.id,
|
|
280
|
+
style: {
|
|
281
|
+
display: "inline-block",
|
|
282
|
+
width: frameWidth,
|
|
283
|
+
height: 18,
|
|
284
|
+
marginRight: 6,
|
|
285
|
+
verticalAlign: "middle",
|
|
286
|
+
flexShrink: 0,
|
|
287
|
+
backgroundImage: "url(" + decoration.entryUrl + ")",
|
|
288
|
+
backgroundSize: stripWidth + "px 18px",
|
|
289
|
+
backgroundRepeat: "no-repeat",
|
|
290
|
+
backgroundPosition: "0px 0px"
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
223
295
|
* The floating pet. The spritesheet frame advances on requestAnimationFrame
|
|
224
296
|
* with per-frame durations from the definition's tracks; the atlas image is
|
|
225
297
|
* loaded once and the background position is written straight to the sprite
|
|
@@ -253,6 +325,21 @@ window.__ModuleLoader__.load({
|
|
|
253
325
|
const rows = definition.rows;
|
|
254
326
|
const tracks = definition.tracks;
|
|
255
327
|
const sequences = definition.sequences;
|
|
328
|
+
const panel = definition.panel;
|
|
329
|
+
const panelLabel = (slot, i18n) => panel?.labels?.[slot] ?? i18n;
|
|
330
|
+
const panelStat = (slot, i18nKey, values) => {
|
|
331
|
+
const format = panel?.stats?.[slot] ?? props.t(i18nKey, values);
|
|
332
|
+
if (panel?.stats?.[slot] === void 0) return format;
|
|
333
|
+
const all = {
|
|
334
|
+
rank: snapshot?.affinity.rank ?? "?",
|
|
335
|
+
n: snapshot?.treats.stocked ?? 0,
|
|
336
|
+
points: snapshot?.affinity.points ?? 0
|
|
337
|
+
};
|
|
338
|
+
let text = format;
|
|
339
|
+
for (const [name, value] of Object.entries(all)) text = text.replaceAll("{" + name + "}", String(value));
|
|
340
|
+
return text;
|
|
341
|
+
};
|
|
342
|
+
const panelShows = (action) => panel?.actions === void 0 || panel.actions.includes(action);
|
|
256
343
|
(0, react.useEffect)(() => {
|
|
257
344
|
if (props.visual !== void 0) return;
|
|
258
345
|
let cancelled = false;
|
|
@@ -392,6 +479,7 @@ window.__ModuleLoader__.load({
|
|
|
392
479
|
const whisper = feedback === null ? snapshot?.whisper : void 0;
|
|
393
480
|
const bubblePresent = feedback !== null || sessionBubbles.length > 0 || statusBubble !== void 0 || whisper !== void 0;
|
|
394
481
|
const displayName = snapshot?.name ?? definition.displayName;
|
|
482
|
+
const decoration = snapshot?.decoration;
|
|
395
483
|
(0, react.useEffect)(() => {
|
|
396
484
|
if (sessionBubbles.length <= 1) setStackPinned(false);
|
|
397
485
|
}, [sessionBubbles.length]);
|
|
@@ -481,14 +569,17 @@ window.__ModuleLoader__.load({
|
|
|
481
569
|
onPointerLeave: () => setStackPeek(false),
|
|
482
570
|
children: [visibleSessions.map((session, index) => {
|
|
483
571
|
const speaksWhisper = index === 0 && whisper !== void 0;
|
|
484
|
-
const bubble = /* @__PURE__ */ (0, react_jsx_runtime.
|
|
572
|
+
const bubble = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
485
573
|
type: "button",
|
|
486
574
|
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleStatus, pet_module_css_default.bubbleClickable, speaksWhisper && pet_module_css_default.bubbleWhisper),
|
|
487
575
|
title: props.t("pet.openSessionHint"),
|
|
488
576
|
onClick: () => {
|
|
489
577
|
props.onOpenSession(session.sessionId);
|
|
490
578
|
},
|
|
491
|
-
children: speaksWhisper
|
|
579
|
+
children: [index === 0 && !speaksWhisper && decoration !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusOrnament, {
|
|
580
|
+
decoration,
|
|
581
|
+
phase
|
|
582
|
+
}), speaksWhisper ? whisper : session.bubble]
|
|
492
583
|
}, speaksWhisper ? "whisper:" + whisper : session.sessionId);
|
|
493
584
|
if (index !== 0 || sessionBubbles.length <= 1) return bubble;
|
|
494
585
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -506,11 +597,14 @@ window.__ModuleLoader__.load({
|
|
|
506
597
|
children: stackOpen ? "×" : "+" + String(sessionBubbles.length - 1)
|
|
507
598
|
})]
|
|
508
599
|
}, "primary");
|
|
509
|
-
}), sessionBubbles.length === 0 && (statusBubble !== void 0 || whisper !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.
|
|
600
|
+
}), sessionBubbles.length === 0 && (statusBubble !== void 0 || whisper !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
510
601
|
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleStatus, whisper !== void 0 && pet_module_css_default.bubbleWhisper),
|
|
511
602
|
role: "status",
|
|
512
603
|
"aria-live": "polite",
|
|
513
|
-
children: whisper
|
|
604
|
+
children: [whisper === void 0 && decoration !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusOrnament, {
|
|
605
|
+
decoration,
|
|
606
|
+
phase
|
|
607
|
+
}), whisper ?? statusBubble]
|
|
514
608
|
}, whisper === void 0 ? "status" : "whisper:" + whisper)]
|
|
515
609
|
}),
|
|
516
610
|
hovered && dragRef.current === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -556,7 +650,7 @@ window.__ModuleLoader__.load({
|
|
|
556
650
|
setRenaming(false);
|
|
557
651
|
}
|
|
558
652
|
},
|
|
559
|
-
children: props.t("pet.confirm")
|
|
653
|
+
children: panelLabel("confirm", props.t("pet.confirm"))
|
|
560
654
|
})]
|
|
561
655
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
562
656
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -566,29 +660,29 @@ window.__ModuleLoader__.load({
|
|
|
566
660
|
children: displayName
|
|
567
661
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
568
662
|
className: pet_module_css_default.statRank,
|
|
569
|
-
children:
|
|
663
|
+
children: panelStat("rank", "pet.rank", { rank: snapshot?.affinity.rank ?? "?" })
|
|
570
664
|
})]
|
|
571
665
|
}),
|
|
572
666
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
573
667
|
className: pet_module_css_default.rankRow,
|
|
574
668
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
575
669
|
className: pet_module_css_default.statTreats,
|
|
576
|
-
children:
|
|
670
|
+
children: panelStat("treats", "pet.treats", { n: snapshot?.treats.stocked ?? 0 })
|
|
577
671
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
578
672
|
className: pet_module_css_default.statPoints,
|
|
579
|
-
children:
|
|
673
|
+
children: panelStat("points", "pet.points", { points: snapshot?.affinity.points ?? 0 })
|
|
580
674
|
})]
|
|
581
675
|
}),
|
|
582
676
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
583
677
|
className: pet_module_css_default.actions,
|
|
584
678
|
children: [
|
|
585
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
679
|
+
panelShows("feed") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
586
680
|
type: "button",
|
|
587
681
|
className: pet_module_css_default.action,
|
|
588
682
|
onClick: props.onFeed,
|
|
589
|
-
children: props.t("pet.feed")
|
|
683
|
+
children: panelLabel("feed", props.t("pet.feed"))
|
|
590
684
|
}),
|
|
591
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
685
|
+
panelShows("rename") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
592
686
|
type: "button",
|
|
593
687
|
className: pet_module_css_default.action,
|
|
594
688
|
onClick: () => {
|
|
@@ -596,13 +690,13 @@ window.__ModuleLoader__.load({
|
|
|
596
690
|
setNameDraft(displayName);
|
|
597
691
|
setRenaming(true);
|
|
598
692
|
},
|
|
599
|
-
children: props.t("pet.rename")
|
|
693
|
+
children: panelLabel("rename", props.t("pet.rename"))
|
|
600
694
|
}),
|
|
601
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
695
|
+
panelShows("hide") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
602
696
|
type: "button",
|
|
603
697
|
className: pet_module_css_default.action,
|
|
604
698
|
onClick: props.onHide,
|
|
605
|
-
children: props.t("pet.hide")
|
|
699
|
+
children: panelLabel("hide", props.t("pet.hide"))
|
|
606
700
|
})
|
|
607
701
|
]
|
|
608
702
|
})
|
|
@@ -695,6 +789,7 @@ window.__ModuleLoader__.load({
|
|
|
695
789
|
const downRef = (0, react.useRef)(null);
|
|
696
790
|
const [error, setError] = (0, react.useState)(null);
|
|
697
791
|
(0, react.useEffect)(() => {
|
|
792
|
+
setError(null);
|
|
698
793
|
const container = containerRef.current;
|
|
699
794
|
const live2d = props.definition.live2d;
|
|
700
795
|
if (container === null || live2d === void 0) return void 0;
|
|
@@ -917,6 +1012,33 @@ window.__ModuleLoader__.load({
|
|
|
917
1012
|
//#region src/client/renderers/live2d.ts
|
|
918
1013
|
/** The de-facto tap-motion group of Cubism sample models. */
|
|
919
1014
|
const TAP_GROUP = "TapBody";
|
|
1015
|
+
/**
|
|
1016
|
+
* Keep one screen-appropriate atlas LOD instead of asking Pixi for the
|
|
1017
|
+
* engine's default full mip chain. A user model can legitimately carry an
|
|
1018
|
+
* 8192px texture while the pet itself is only a few hundred pixels tall;
|
|
1019
|
+
* `single-auto` preserves the source for larger renders and generates one
|
|
1020
|
+
* downsampled atlas only when the effective on-screen scale warrants it.
|
|
1021
|
+
*/
|
|
1022
|
+
const TEXTURE_OPTIONS = { lod: "single-auto" };
|
|
1023
|
+
/** Recursively release the activation without invalidating shared texture caches. */
|
|
1024
|
+
const DESTROY_OPTIONS = { children: true };
|
|
1025
|
+
/** Remove only this activation's canvas; `true` would release Pixi globals. */
|
|
1026
|
+
const RENDERER_DESTROY_OPTIONS = { removeView: true };
|
|
1027
|
+
/** Ignore hidden/zero boxes and keep Pixi dimensions stable and integral. */
|
|
1028
|
+
function normalizeRendererSize(width, height) {
|
|
1029
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return void 0;
|
|
1030
|
+
return {
|
|
1031
|
+
width: Math.max(1, Math.round(width)),
|
|
1032
|
+
height: Math.max(1, Math.round(height))
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
/** Fit the model from its unscaled dimensions into the current Pixi screen. */
|
|
1036
|
+
function layoutModel(app, model, sourceSize, config) {
|
|
1037
|
+
const fit = Math.min(app.renderer.width / sourceSize.width, app.renderer.height / sourceSize.height) * .92;
|
|
1038
|
+
model.scale.set(fit * (config.scale ?? 1));
|
|
1039
|
+
model.anchor.set(.5);
|
|
1040
|
+
model.position.set(app.renderer.width / 2 + (config.translate?.x ?? 0), app.renderer.height / 2 + (config.translate?.y ?? 0));
|
|
1041
|
+
}
|
|
920
1042
|
let vendorConfigured = false;
|
|
921
1043
|
/** Configure pixi extensions + the Cubism SDK once per page. */
|
|
922
1044
|
function configureOnce(vendor) {
|
|
@@ -943,11 +1065,56 @@ window.__ModuleLoader__.load({
|
|
|
943
1065
|
let disposed = false;
|
|
944
1066
|
let app;
|
|
945
1067
|
let model;
|
|
1068
|
+
let modelAttached = false;
|
|
1069
|
+
let modelSourceSize;
|
|
1070
|
+
let resizeObserver;
|
|
1071
|
+
let resizeTracking = false;
|
|
946
1072
|
let errorListener;
|
|
947
1073
|
let unsubscribe;
|
|
948
1074
|
/** The motion group the current phase maps to (resume target after taps). */
|
|
949
1075
|
let phaseGroup = config.motions.idle;
|
|
950
1076
|
let tapPlaying = false;
|
|
1077
|
+
const stopResizeTracking = () => {
|
|
1078
|
+
resizeTracking = false;
|
|
1079
|
+
resizeObserver?.disconnect();
|
|
1080
|
+
resizeObserver = void 0;
|
|
1081
|
+
};
|
|
1082
|
+
const resizeRenderer = (pixiApp, width, height) => {
|
|
1083
|
+
const next = normalizeRendererSize(width, height);
|
|
1084
|
+
if (disposed || !resizeTracking || next === void 0) return;
|
|
1085
|
+
if (pixiApp.renderer.width === next.width && pixiApp.renderer.height === next.height) return;
|
|
1086
|
+
pixiApp.renderer.resize(next.width, next.height);
|
|
1087
|
+
if (model !== void 0 && modelSourceSize !== void 0) layoutModel(pixiApp, model, modelSourceSize, config);
|
|
1088
|
+
};
|
|
1089
|
+
const trackContainerSize = (pixiApp) => {
|
|
1090
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
1091
|
+
resizeTracking = true;
|
|
1092
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
1093
|
+
const entry = entries.find((candidate) => candidate.target === ctx.container);
|
|
1094
|
+
if (entry === void 0) return;
|
|
1095
|
+
resizeRenderer(pixiApp, entry.contentRect.width, entry.contentRect.height);
|
|
1096
|
+
});
|
|
1097
|
+
resizeObserver.observe(ctx.container);
|
|
1098
|
+
resizeRenderer(pixiApp, ctx.container.clientWidth, ctx.container.clientHeight);
|
|
1099
|
+
};
|
|
1100
|
+
/** Release every resource currently owned by this activation exactly once. */
|
|
1101
|
+
const destroyResources = () => {
|
|
1102
|
+
stopResizeTracking();
|
|
1103
|
+
unsubscribe?.();
|
|
1104
|
+
unsubscribe = void 0;
|
|
1105
|
+
const currentApp = app;
|
|
1106
|
+
const currentModel = model;
|
|
1107
|
+
const modelOwnedByApp = currentApp !== void 0 && modelAttached;
|
|
1108
|
+
app = void 0;
|
|
1109
|
+
model = void 0;
|
|
1110
|
+
modelSourceSize = void 0;
|
|
1111
|
+
modelAttached = false;
|
|
1112
|
+
try {
|
|
1113
|
+
if (currentModel !== void 0 && !modelOwnedByApp) currentModel.destroy(DESTROY_OPTIONS);
|
|
1114
|
+
} finally {
|
|
1115
|
+
currentApp?.destroy(RENDERER_DESTROY_OPTIONS, DESTROY_OPTIONS);
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
951
1118
|
const playGroup = (group) => {
|
|
952
1119
|
if (model === void 0) return;
|
|
953
1120
|
const groups = model.internalModel.settings.motions ?? {};
|
|
@@ -977,37 +1144,54 @@ window.__ModuleLoader__.load({
|
|
|
977
1144
|
}
|
|
978
1145
|
configureOnce(vendor);
|
|
979
1146
|
const pixiApp = new vendor.Application();
|
|
980
|
-
|
|
981
|
-
width:
|
|
982
|
-
height:
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1147
|
+
const initialSize = normalizeRendererSize(ctx.container.clientWidth, ctx.container.clientHeight) ?? {
|
|
1148
|
+
width: 160,
|
|
1149
|
+
height: 174
|
|
1150
|
+
};
|
|
1151
|
+
try {
|
|
1152
|
+
await pixiApp.init({
|
|
1153
|
+
width: initialSize.width,
|
|
1154
|
+
height: initialSize.height,
|
|
1155
|
+
backgroundAlpha: 0,
|
|
1156
|
+
antialias: true,
|
|
1157
|
+
autoDensity: true,
|
|
1158
|
+
preference: "webgl"
|
|
1159
|
+
});
|
|
1160
|
+
} catch (error) {
|
|
1161
|
+
try {
|
|
1162
|
+
pixiApp.destroy(RENDERER_DESTROY_OPTIONS, DESTROY_OPTIONS);
|
|
1163
|
+
} catch {}
|
|
1164
|
+
throw error;
|
|
1165
|
+
}
|
|
988
1166
|
if (disposed) {
|
|
989
|
-
pixiApp.destroy(
|
|
1167
|
+
pixiApp.destroy(RENDERER_DESTROY_OPTIONS, DESTROY_OPTIONS);
|
|
990
1168
|
return;
|
|
991
1169
|
}
|
|
1170
|
+
app = pixiApp;
|
|
992
1171
|
pixiApp.canvas.style.display = "block";
|
|
993
1172
|
pixiApp.canvas.style.width = "100%";
|
|
994
1173
|
pixiApp.canvas.style.height = "100%";
|
|
995
1174
|
ctx.container.appendChild(pixiApp.canvas);
|
|
1175
|
+
trackContainerSize(pixiApp);
|
|
996
1176
|
const loaded = await vendor.Live2DModel.from(config.modelUrl, {
|
|
1177
|
+
autoUpdate: false,
|
|
997
1178
|
autoHitTest: false,
|
|
998
|
-
autoFocus: false
|
|
1179
|
+
autoFocus: false,
|
|
1180
|
+
textureOptions: TEXTURE_OPTIONS
|
|
999
1181
|
});
|
|
1182
|
+
model = loaded;
|
|
1000
1183
|
if (disposed) {
|
|
1001
|
-
|
|
1184
|
+
destroyResources();
|
|
1002
1185
|
return;
|
|
1003
1186
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
loaded
|
|
1009
|
-
loaded.position.set(pixiApp.renderer.width / 2 + (config.translate?.x ?? 0), pixiApp.renderer.height / 2 + (config.translate?.y ?? 0));
|
|
1187
|
+
modelSourceSize = {
|
|
1188
|
+
width: Math.max(1, loaded.width),
|
|
1189
|
+
height: Math.max(1, loaded.height)
|
|
1190
|
+
};
|
|
1191
|
+
layoutModel(pixiApp, loaded, modelSourceSize, config);
|
|
1010
1192
|
pixiApp.stage.addChild(loaded);
|
|
1193
|
+
modelAttached = true;
|
|
1194
|
+
loaded.automator.autoUpdate = true;
|
|
1011
1195
|
loaded.on("motionFinish", () => {
|
|
1012
1196
|
if (tapPlaying) {
|
|
1013
1197
|
tapPlaying = false;
|
|
@@ -1018,18 +1202,17 @@ window.__ModuleLoader__.load({
|
|
|
1018
1202
|
unsubscribe = ctx.phase.subscribe(applyPhase);
|
|
1019
1203
|
};
|
|
1020
1204
|
boot().catch(() => {
|
|
1021
|
-
|
|
1205
|
+
try {
|
|
1206
|
+
destroyResources();
|
|
1207
|
+
} finally {
|
|
1208
|
+
if (!disposed) errorListener?.("load-failed");
|
|
1209
|
+
}
|
|
1022
1210
|
});
|
|
1023
1211
|
return {
|
|
1024
1212
|
dispose() {
|
|
1025
1213
|
if (disposed) return;
|
|
1026
1214
|
disposed = true;
|
|
1027
|
-
|
|
1028
|
-
model = void 0;
|
|
1029
|
-
if (app !== void 0) {
|
|
1030
|
-
app.destroy(true, { children: true });
|
|
1031
|
-
app = void 0;
|
|
1032
|
-
}
|
|
1215
|
+
destroyResources();
|
|
1033
1216
|
},
|
|
1034
1217
|
tap(x, y) {
|
|
1035
1218
|
const current = model;
|
|
@@ -1717,7 +1900,8 @@ window.__ModuleLoader__.load({
|
|
|
1717
1900
|
const valid = plan.filter((item) => item.run !== void 0);
|
|
1718
1901
|
if (plan.length === 0 || this.saving || valid.length !== plan.length) return;
|
|
1719
1902
|
const plannedWrites = valid.map((item) => item.op);
|
|
1720
|
-
const
|
|
1903
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1904
|
+
for (const item of plan) pending.set(item.field, this.staged.get(item.field));
|
|
1721
1905
|
this.saving = true;
|
|
1722
1906
|
this.failed = false;
|
|
1723
1907
|
this.failedReason = void 0;
|
|
@@ -1730,9 +1914,9 @@ window.__ModuleLoader__.load({
|
|
|
1730
1914
|
for (const field of result.fields) if (field.landed) landed.add(field.field);
|
|
1731
1915
|
} else this.failedReason = result.message;
|
|
1732
1916
|
} else for (const item of valid) if (await item.run()) landed.add(item.field);
|
|
1733
|
-
for (const field of
|
|
1917
|
+
for (const [field, before] of pending) if (landed.has(field) && this.staged.get(field) === before) this.staged.delete(field);
|
|
1734
1918
|
this.saving = false;
|
|
1735
|
-
this.failed = landed.size !==
|
|
1919
|
+
this.failed = landed.size !== pending.size;
|
|
1736
1920
|
this.publish();
|
|
1737
1921
|
}
|
|
1738
1922
|
/** The scope's batch surface when it supports one; undefined conservatively otherwise. */
|
|
@@ -1875,6 +2059,7 @@ window.__ModuleLoader__.load({
|
|
|
1875
2059
|
constructor(scope) {
|
|
1876
2060
|
this.form = new CardForm(scope, [
|
|
1877
2061
|
booleanField("enabled"),
|
|
2062
|
+
booleanField("decorationEnabled"),
|
|
1878
2063
|
booleanField("visible"),
|
|
1879
2064
|
numberField("size"),
|
|
1880
2065
|
numberField("right"),
|
|
@@ -1882,8 +2067,10 @@ window.__ModuleLoader__.load({
|
|
|
1882
2067
|
choiceField("petId", this.petChoices)
|
|
1883
2068
|
]);
|
|
1884
2069
|
this.store = this.form.bind(() => this.projection());
|
|
1885
|
-
|
|
1886
|
-
|
|
2070
|
+
window.setTimeout(() => {
|
|
2071
|
+
this.loadPets();
|
|
2072
|
+
this.loadDiagnostics();
|
|
2073
|
+
}, 0);
|
|
1887
2074
|
}
|
|
1888
2075
|
/** Fetch registry diagnostics once (soft-fail: an empty list on error). */
|
|
1889
2076
|
async loadDiagnostics() {
|
|
@@ -1914,6 +2101,7 @@ window.__ModuleLoader__.load({
|
|
|
1914
2101
|
return {
|
|
1915
2102
|
...this.form.shell(),
|
|
1916
2103
|
enabled: this.form.field("enabled"),
|
|
2104
|
+
decorationEnabled: this.form.field("decorationEnabled"),
|
|
1917
2105
|
visible: this.form.field("visible"),
|
|
1918
2106
|
size: this.form.field("size"),
|
|
1919
2107
|
right: this.form.field("right"),
|
|
@@ -1984,6 +2172,22 @@ window.__ModuleLoader__.load({
|
|
|
1984
2172
|
props.resetField("enabled");
|
|
1985
2173
|
}
|
|
1986
2174
|
}),
|
|
2175
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
|
|
2176
|
+
id: "settings-pet-decoration",
|
|
2177
|
+
label: t("settings.decoration"),
|
|
2178
|
+
hint: t("settings.decorationHint"),
|
|
2179
|
+
inheritLabel: t("settings.inherit"),
|
|
2180
|
+
onLabel: t("settings.on"),
|
|
2181
|
+
offLabel: t("settings.off"),
|
|
2182
|
+
...fieldProps,
|
|
2183
|
+
...state.decorationEnabled,
|
|
2184
|
+
onEdit: (text) => {
|
|
2185
|
+
props.edit("decorationEnabled", text);
|
|
2186
|
+
},
|
|
2187
|
+
onReset: () => {
|
|
2188
|
+
props.resetField("decorationEnabled");
|
|
2189
|
+
}
|
|
2190
|
+
}),
|
|
1987
2191
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChoiceField, {
|
|
1988
2192
|
id: "settings-pet-pet",
|
|
1989
2193
|
label: t("settings.pet"),
|
|
@@ -2115,6 +2319,8 @@ window.__ModuleLoader__.load({
|
|
|
2115
2319
|
"settings.petHint": "选择显示哪只宠物;每只宠物独立命名,可在宠物悬浮面板改名。",
|
|
2116
2320
|
"settings.enabled": "启用宠物",
|
|
2117
2321
|
"settings.enabledHint": "关闭后隐藏宠物并停止轮询,可在设置里重新启用。",
|
|
2322
|
+
"settings.decoration": "状态装饰",
|
|
2323
|
+
"settings.decorationHint": "在宠物状态气泡里显示喷水鲸鱼等状态装饰;关闭后气泡只剩文字。",
|
|
2118
2324
|
"settings.visible": "显示宠物",
|
|
2119
2325
|
"settings.visibleHint": "关闭后宠物隐藏,可从聊天输入区重新召唤。",
|
|
2120
2326
|
"settings.size": "大小(px)",
|
|
@@ -2166,6 +2372,8 @@ window.__ModuleLoader__.load({
|
|
|
2166
2372
|
"settings.petHint": "Choose which pet shows. Names are stored per pet; rename from the pet hover panel.",
|
|
2167
2373
|
"settings.enabled": "Enable the pet",
|
|
2168
2374
|
"settings.enabledHint": "When off, the pet hides and polling stops; re-enable it here.",
|
|
2375
|
+
"settings.decoration": "Status decoration",
|
|
2376
|
+
"settings.decorationHint": "Show ornaments like the spouting whale inside the pet status bubbles; when off, bubbles stay text-only.",
|
|
2169
2377
|
"settings.visible": "Show the pet",
|
|
2170
2378
|
"settings.visibleHint": "When off, the pet hides; summon it again from the input row.",
|
|
2171
2379
|
"settings.size": "Size (px)",
|