@linxin666/dsh-pet 0.1.14 → 0.1.16
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 +73 -38
- package/README.zh.md +91 -55
- package/assets/whale/pet.json +12 -1
- package/cordis.patch.yml +4 -4
- package/lib/client.js +296 -347
- package/lib/client.js.map +1 -1
- package/lib/index.js +975 -317
- package/lib/invariant.js +1 -1
- package/lib/{state-P-wsVJ6O.js → state-CFyJv0sQ.js} +15 -1
- package/lib/types/affinity.d.ts +15 -0
- package/lib/types/affinity.d.ts.map +1 -1
- package/lib/types/affinity.js +14 -0
- package/lib/types/client/PetDockEntry.d.ts +14 -12
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +13 -8
- package/lib/types/client/PetSettingsCard.d.ts +22 -9
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSettingsCard.js +42 -7
- package/lib/types/client/{WhalePet.d.ts → PetSprite.d.ts} +21 -20
- package/lib/types/client/PetSprite.d.ts.map +1 -0
- package/lib/types/client/{WhalePet.js → PetSprite.js} +44 -67
- package/lib/types/client/PluginSettingsCard.d.ts +26 -11
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +30 -8
- package/lib/types/client/index.d.ts +11 -8
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +32 -14
- package/lib/types/client/locales.d.ts +10 -10
- package/lib/types/client/locales.js +10 -10
- package/lib/types/client/pet-store.d.ts +5 -0
- package/lib/types/client/pet-store.d.ts.map +1 -1
- package/lib/types/client/pet-store.js +4 -0
- package/lib/types/client/settings-form.d.ts +76 -5
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +97 -20
- package/lib/types/client/spritesheet.d.ts +14 -48
- package/lib/types/client/spritesheet.d.ts.map +1 -1
- package/lib/types/client/spritesheet.js +13 -81
- package/lib/types/dsh-home.d.ts +17 -0
- package/lib/types/dsh-home.d.ts.map +1 -0
- package/lib/types/dsh-home.js +34 -0
- package/lib/types/event-projection.d.ts +36 -0
- package/lib/types/event-projection.d.ts.map +1 -0
- package/lib/types/event-projection.js +98 -0
- package/lib/types/index.d.ts +24 -11
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +53 -33
- package/lib/types/ledger.d.ts +79 -0
- package/lib/types/ledger.d.ts.map +1 -0
- package/lib/types/ledger.js +146 -0
- package/lib/types/persist.d.ts +15 -4
- package/lib/types/persist.d.ts.map +1 -1
- package/lib/types/persist.js +39 -8
- package/lib/types/registry.d.ts +158 -0
- package/lib/types/registry.d.ts.map +1 -0
- package/lib/types/registry.js +276 -0
- package/lib/types/routes.d.ts +10 -10
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +169 -51
- package/lib/types/service.d.ts +59 -57
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +111 -211
- package/package.json +2 -2
- package/src/affinity.ts +33 -0
- package/src/client/PetDockEntry.tsx +34 -26
- package/src/client/PetSettingsCard.tsx +67 -22
- package/src/client/{WhalePet.test.tsx → PetSprite.test.tsx} +56 -8
- package/src/client/{WhalePet.tsx → PetSprite.tsx} +55 -73
- package/src/client/PluginSettingsCard.tsx +90 -18
- package/src/client/index.ts +38 -16
- package/src/client/locales.ts +10 -10
- package/src/client/pet-store.ts +9 -0
- package/src/client/settings-card.module.css +9 -0
- package/src/client/settings-form.ts +153 -19
- package/src/client/spritesheet.ts +16 -92
- package/src/dsh-home.test.ts +35 -0
- package/src/dsh-home.ts +36 -0
- package/src/event-projection.ts +128 -0
- package/src/index.test.ts +23 -0
- package/src/index.ts +89 -45
- package/src/ledger.test.ts +69 -0
- package/src/ledger.ts +190 -0
- package/src/persist.test.ts +52 -3
- package/src/persist.ts +49 -11
- package/src/registry.test.ts +132 -0
- package/src/registry.ts +400 -0
- package/src/routes.ts +166 -53
- package/src/service.ts +153 -285
- package/lib/types/client/WhalePet.d.ts.map +0 -1
package/lib/invariant.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as animationForPhase, o as AFFINITY_RANKS, u as defaultAffinityConfig } from "./state-CFyJv0sQ.js";
|
|
2
2
|
//#region src/invariant.ts
|
|
3
3
|
/**
|
|
4
4
|
* Package invariants — cheap structural checks run at import time on the
|
|
@@ -48,6 +48,20 @@ function rankOf(points) {
|
|
|
48
48
|
for (const candidate of AFFINITY_RANKS) if (points >= candidate.min) rank = candidate;
|
|
49
49
|
return rank;
|
|
50
50
|
}
|
|
51
|
+
/** Derive the read-only view of one affinity state at a wall-clock instant. */
|
|
52
|
+
function affinityViewOf(state, nowMs, config = defaultAffinityConfig) {
|
|
53
|
+
const rank = rankOf(state.points);
|
|
54
|
+
return {
|
|
55
|
+
points: state.points,
|
|
56
|
+
rank: rank.name,
|
|
57
|
+
rankEmoji: rank.emoji,
|
|
58
|
+
pets: state.pets,
|
|
59
|
+
feeds: state.feeds,
|
|
60
|
+
turns: state.turns,
|
|
61
|
+
petCooldown: nowMs - state.lastPetAt < config.petCooldownMs,
|
|
62
|
+
feedCooldown: nowMs - state.lastFeedAt < config.feedCooldownMs
|
|
63
|
+
};
|
|
64
|
+
}
|
|
51
65
|
function clamp(points) {
|
|
52
66
|
return Math.min(100, Math.max(0, points));
|
|
53
67
|
}
|
|
@@ -196,4 +210,4 @@ var PetStateMachine = class {
|
|
|
196
210
|
}
|
|
197
211
|
};
|
|
198
212
|
//#endregion
|
|
199
|
-
export { AFFINITY_MAX as a,
|
|
213
|
+
export { AFFINITY_MAX as a, applyInteraction as c, emptyAffinity as d, rankOf as f, rowOf as i, applyTurnReward as l, animationForPhase as n, AFFINITY_RANKS as o, defaultPetStateConfig as r, affinityViewOf as s, PetStateMachine as t, defaultAffinityConfig as u };
|
package/lib/types/affinity.d.ts
CHANGED
|
@@ -71,6 +71,21 @@ export interface InteractionOutcome {
|
|
|
71
71
|
}
|
|
72
72
|
/** Rank for a point total. */
|
|
73
73
|
export declare function rankOf(points: number): (typeof AFFINITY_RANKS)[number];
|
|
74
|
+
/** Read-only affinity snapshot suited for the RPC view shape. */
|
|
75
|
+
export interface PetAffinityView {
|
|
76
|
+
points: number;
|
|
77
|
+
rank: string;
|
|
78
|
+
rankEmoji: string;
|
|
79
|
+
pets: number;
|
|
80
|
+
feeds: number;
|
|
81
|
+
turns: number;
|
|
82
|
+
/** True while the pet interaction is inside its cooldown. */
|
|
83
|
+
petCooldown: boolean;
|
|
84
|
+
/** True while the feed is inside its cooldown. */
|
|
85
|
+
feedCooldown: boolean;
|
|
86
|
+
}
|
|
87
|
+
/** Derive the read-only view of one affinity state at a wall-clock instant. */
|
|
88
|
+
export declare function affinityViewOf(state: AffinityState, nowMs: number, config?: AffinityConfig): PetAffinityView;
|
|
74
89
|
/**
|
|
75
90
|
* Apply one interaction to a copy of the state (immutable style: returns a
|
|
76
91
|
* new object; the caller replaces the persisted state). Cooldowns only
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"affinity.d.ts","sourceRoot":"","sources":["../../src/affinity.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,CAAA;AAE3C,mCAAmC;AACnC,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,eAAO,MAAM,YAAY,MAAM,CAAA;AAE/B;;iEAEiE;AACjE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;EAKjB,CAAA;AAEV,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAA;IACrB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,eAAO,MAAM,qBAAqB,EAAE,cAMnC,CAAA;AAED,wBAAgB,aAAa,IAAI,aAAa,CAE7C;AAED,kCAAkC;AAClC,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,QAAQ,EAAE,aAAa,CAAA;IACvB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAA;IAChB,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,8BAA8B;AAC9B,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAMtE;AAMD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,cAAsC,GAC7C,kBAAkB,CA+BpB;AAED,gEAAgE;AAChE,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,MAAM,GAAE,cAAsC,GAC7C,aAAa,CAKf"}
|
|
1
|
+
{"version":3,"file":"affinity.d.ts","sourceRoot":"","sources":["../../src/affinity.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,CAAA;AAE3C,mCAAmC;AACnC,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,eAAO,MAAM,YAAY,MAAM,CAAA;AAE/B;;iEAEiE;AACjE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;EAKjB,CAAA;AAEV,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAA;IACrB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,eAAO,MAAM,qBAAqB,EAAE,cAMnC,CAAA;AAED,wBAAgB,aAAa,IAAI,aAAa,CAE7C;AAED,kCAAkC;AAClC,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,QAAQ,EAAE,aAAa,CAAA;IACvB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAA;IAChB,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,8BAA8B;AAC9B,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAMtE;AAED,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,WAAW,EAAE,OAAO,CAAA;IACpB,kDAAkD;IAClD,YAAY,EAAE,OAAO,CAAA;CACtB;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAC5B,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,cAAsC,GAC7C,eAAe,CAYjB;AAMD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,cAAsC,GAC7C,kBAAkB,CA+BpB;AAED,gEAAgE;AAChE,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,MAAM,GAAE,cAAsC,GAC7C,aAAa,CAKf"}
|
package/lib/types/affinity.js
CHANGED
|
@@ -34,6 +34,20 @@ export function rankOf(points) {
|
|
|
34
34
|
}
|
|
35
35
|
return rank;
|
|
36
36
|
}
|
|
37
|
+
/** Derive the read-only view of one affinity state at a wall-clock instant. */
|
|
38
|
+
export function affinityViewOf(state, nowMs, config = defaultAffinityConfig) {
|
|
39
|
+
const rank = rankOf(state.points);
|
|
40
|
+
return {
|
|
41
|
+
points: state.points,
|
|
42
|
+
rank: rank.name,
|
|
43
|
+
rankEmoji: rank.emoji,
|
|
44
|
+
pets: state.pets,
|
|
45
|
+
feeds: state.feeds,
|
|
46
|
+
turns: state.turns,
|
|
47
|
+
petCooldown: nowMs - state.lastPetAt < config.petCooldownMs,
|
|
48
|
+
feedCooldown: nowMs - state.lastFeedAt < config.feedCooldownMs,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
37
51
|
function clamp(points) {
|
|
38
52
|
return Math.min(AFFINITY_MAX, Math.max(0, points));
|
|
39
53
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Global floating pet entry. The pet is host-global (its state, display and
|
|
3
|
-
* interactions live on
|
|
3
|
+
* interactions live on '/api/pet/*' endpoints with no session dimension), so
|
|
4
4
|
* it must not ride a session-scoped slot — on the new-conversation screen no
|
|
5
5
|
* session exists to scope a slot by, and the pet would vanish (issue #48).
|
|
6
|
-
* The client half therefore mounts this entry straight onto
|
|
7
|
-
* (see index.ts): while visible it renders the floating
|
|
8
|
-
* while hidden it renders a fixed-position summon button.
|
|
6
|
+
* The client half therefore mounts this entry straight onto 'document.body'
|
|
7
|
+
* (see index.ts): while visible it renders the floating PetSprite (a
|
|
8
|
+
* portal), while hidden it renders a fixed-position summon button. Which
|
|
9
|
+
* sprite renders is decided by the host snapshot's pet id resolved against
|
|
10
|
+
* the registry list — no per-pet component exists.
|
|
9
11
|
* @module @linxin666/dsh-pet/client/PetDockEntry
|
|
10
12
|
*/
|
|
11
13
|
import { type ReactElement } from 'react';
|
|
@@ -14,21 +16,21 @@ import type { PetStoreInstance } from './pet-store.ts';
|
|
|
14
16
|
import { NS } from './locales.ts';
|
|
15
17
|
/** Injected actions handed to the dock entry component. */
|
|
16
18
|
export interface PetInjected {
|
|
17
|
-
/** The app-wide pet store instance (snapshot + feedback). */
|
|
19
|
+
/** The app-wide pet store instance (snapshot + registry list + feedback). */
|
|
18
20
|
store: PetStoreInstance;
|
|
19
|
-
/** Ensure the first snapshot is fetched (called on mount). */
|
|
21
|
+
/** Ensure the first snapshot (and registry list) is fetched (called on mount). */
|
|
20
22
|
ensure: () => void;
|
|
21
|
-
/** Pet the
|
|
23
|
+
/** Pet the sprite (click). */
|
|
22
24
|
pet: () => void;
|
|
23
|
-
/** Feed the
|
|
25
|
+
/** Feed the sprite. */
|
|
24
26
|
feed: () => void;
|
|
25
|
-
/** Hide the
|
|
27
|
+
/** Hide the sprite. */
|
|
26
28
|
hide: () => void;
|
|
27
|
-
/** Summon the hidden
|
|
29
|
+
/** Summon the hidden sprite back. */
|
|
28
30
|
summon: () => void;
|
|
29
31
|
/** Persist a drag position. */
|
|
30
32
|
dragEnd: (right: number, bottom: number) => void;
|
|
31
|
-
/** Rename the pet (persisted by the host). */
|
|
33
|
+
/** Rename the selected pet (persisted by the host). */
|
|
32
34
|
rename: (name: string) => void;
|
|
33
35
|
/** Clear the reaction bubble. */
|
|
34
36
|
feedbackDone: () => void;
|
|
@@ -36,7 +38,7 @@ export interface PetInjected {
|
|
|
36
38
|
/** Composed props of the global pet entry (locale + injected; no slot runtime share). */
|
|
37
39
|
export type PetDockEntryProps = PetInjected & PropsLocale<typeof NS>;
|
|
38
40
|
/**
|
|
39
|
-
* Dock entry: while the pet is visible, mount the floating
|
|
41
|
+
* Dock entry: while the pet is visible, mount the floating PetSprite (it
|
|
40
42
|
* portals itself onto document.body); while hidden, render the summon
|
|
41
43
|
* button so the pet can always come back. The store is the plugin-owned
|
|
42
44
|
* single instance — the slot system provides none because the pet is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PetDockEntry.d.ts","sourceRoot":"","sources":["../../../src/client/PetDockEntry.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"PetDockEntry.d.ts","sourceRoot":"","sources":["../../../src/client/PetDockEntry.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAmC,KAAK,YAAY,EAAE,MAAM,OAAO,CAAA;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAEtD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,KAAK,EAAE,gBAAgB,CAAA;IACvB,kFAAkF;IAClF,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,8BAA8B;IAC9B,GAAG,EAAE,MAAM,IAAI,CAAA;IACf,uBAAuB;IACvB,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,uBAAuB;IACvB,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,qCAAqC;IACrC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAChD,uDAAuD;IACvD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,iCAAiC;IACjC,YAAY,EAAE,MAAM,IAAI,CAAA;CACzB;AAED,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAC3B,WAAW,GACT,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;AAI1B;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CAoDnE"}
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Global floating pet entry. The pet is host-global (its state, display and
|
|
4
|
-
* interactions live on
|
|
4
|
+
* interactions live on '/api/pet/*' endpoints with no session dimension), so
|
|
5
5
|
* it must not ride a session-scoped slot — on the new-conversation screen no
|
|
6
6
|
* session exists to scope a slot by, and the pet would vanish (issue #48).
|
|
7
|
-
* The client half therefore mounts this entry straight onto
|
|
8
|
-
* (see index.ts): while visible it renders the floating
|
|
9
|
-
* while hidden it renders a fixed-position summon button.
|
|
7
|
+
* The client half therefore mounts this entry straight onto 'document.body'
|
|
8
|
+
* (see index.ts): while visible it renders the floating PetSprite (a
|
|
9
|
+
* portal), while hidden it renders a fixed-position summon button. Which
|
|
10
|
+
* sprite renders is decided by the host snapshot's pet id resolved against
|
|
11
|
+
* the registry list — no per-pet component exists.
|
|
10
12
|
* @module @linxin666/dsh-pet/client/PetDockEntry
|
|
11
13
|
*/
|
|
12
14
|
import { useEffect, useSyncExternalStore } from 'react';
|
|
13
|
-
import {
|
|
15
|
+
import { PetSprite } from "./PetSprite.js";
|
|
14
16
|
import styles from './pet.module.css';
|
|
15
17
|
const DEFAULT_DISPLAY = { visible: true, size: 160, right: 24, bottom: 20 };
|
|
16
18
|
/**
|
|
17
|
-
* Dock entry: while the pet is visible, mount the floating
|
|
19
|
+
* Dock entry: while the pet is visible, mount the floating PetSprite (it
|
|
18
20
|
* portals itself onto document.body); while hidden, render the summon
|
|
19
21
|
* button so the pet can always come back. The store is the plugin-owned
|
|
20
22
|
* single instance — the slot system provides none because the pet is
|
|
@@ -25,12 +27,15 @@ export function PetDockEntry(props) {
|
|
|
25
27
|
const ui = useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
26
28
|
const snapshot = ui.snapshot;
|
|
27
29
|
const feedback = ui.feedback;
|
|
30
|
+
const definition = ui.pets.find(entry => entry.id === snapshot?.pet.id) ?? null;
|
|
28
31
|
const visible = snapshot?.display.visible ?? true;
|
|
29
32
|
useEffect(() => {
|
|
30
33
|
ensure();
|
|
31
34
|
}, [ensure]);
|
|
32
35
|
if (visible) {
|
|
33
|
-
return (_jsx("span", { "data-pet-dock": true, "data-testid": "pet-dock", children:
|
|
36
|
+
return (_jsx("span", { "data-pet-dock": true, "data-testid": "pet-dock", children: snapshot === null || definition === null
|
|
37
|
+
? null
|
|
38
|
+
: (_jsx(PetSprite, { snapshot: snapshot, definition: definition, display: snapshot.display, feedback: feedback, onPet: props.pet, onFeed: props.feed, onHide: props.hide, onDragEnd: props.dragEnd, onRename: props.rename, onFeedbackDone: props.feedbackDone, t: props.t })) }));
|
|
34
39
|
}
|
|
35
40
|
const display = snapshot?.display ?? DEFAULT_DISPLAY;
|
|
36
41
|
return (_jsx("button", { type: "button", className: styles.summon, style: {
|
|
@@ -38,5 +43,5 @@ export function PetDockEntry(props) {
|
|
|
38
43
|
right: display.right,
|
|
39
44
|
bottom: display.bottom,
|
|
40
45
|
zIndex: 2147483000,
|
|
41
|
-
}, onClick: props.summon, "data-testid": "pet-summon", children: props.t('pet.summon', { name: snapshot?.name ?? '
|
|
46
|
+
}, onClick: props.summon, "data-testid": "pet-summon", children: props.t('pet.summon', { name: snapshot?.name ?? '' }) }));
|
|
42
47
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The pet settings card: display layout
|
|
3
|
-
* namespace the host plugin registers. Registered into the
|
|
4
|
-
*
|
|
2
|
+
* The pet settings card: pet selection plus display layout, bound to the
|
|
3
|
+
* 'pet' settings namespace the host plugin registers. Registered into the
|
|
4
|
+
* 'web-ui.plugin.item' slot the plugin-configuration section renders. The
|
|
5
|
+
* petId choices come from the registry endpoint ('/api/pet/pets') — the same
|
|
6
|
+
* list the sprite renders from — so the card carries no per-pet knowledge.
|
|
5
7
|
*/
|
|
6
8
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
7
9
|
import type { SettingsScope, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
|
|
@@ -18,8 +20,8 @@ export interface PetSettings {
|
|
|
18
20
|
right?: number;
|
|
19
21
|
/** Vertical inset from the viewport bottom edge, px. */
|
|
20
22
|
bottom?: number;
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
+
/** Selected pet id (a registry entry). */
|
|
24
|
+
petId?: string;
|
|
23
25
|
}
|
|
24
26
|
/** What the pet settings card renders. */
|
|
25
27
|
export interface PetSettingsCardState extends CardShell {
|
|
@@ -33,8 +35,13 @@ export interface PetSettingsCardState extends CardShell {
|
|
|
33
35
|
right: CardFieldState;
|
|
34
36
|
/** Bottom inset. */
|
|
35
37
|
bottom: CardFieldState;
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
+
/** Selected pet. */
|
|
39
|
+
petId: CardFieldState;
|
|
40
|
+
/** Pet choices (registry ids + display names), loaded from the host. */
|
|
41
|
+
petChoices: readonly {
|
|
42
|
+
value: string;
|
|
43
|
+
label: string;
|
|
44
|
+
}[];
|
|
38
45
|
}
|
|
39
46
|
/** The registration-side face the card's slot entry injects. */
|
|
40
47
|
export interface PetSettingsCardFace extends CardActions {
|
|
@@ -43,12 +50,18 @@ export interface PetSettingsCardFace extends CardActions {
|
|
|
43
50
|
petSettingsCard: SnapshotStore<PetSettingsCardState>;
|
|
44
51
|
};
|
|
45
52
|
}
|
|
46
|
-
/** Bridges the
|
|
53
|
+
/** Bridges the 'pet' scope onto the card's staged form. */
|
|
47
54
|
export declare class PetSettingsCardController {
|
|
48
55
|
private readonly form;
|
|
49
56
|
private readonly store;
|
|
50
|
-
|
|
57
|
+
private readonly petChoices;
|
|
58
|
+
private readonly petLabels;
|
|
59
|
+
private loaded;
|
|
60
|
+
private attempts;
|
|
61
|
+
/** @param scope - the bound settings scope for the 'pet' namespace. */
|
|
51
62
|
constructor(scope: SettingsScope<PetSettings>);
|
|
63
|
+
/** Resolve the registry choices once (retried a few times on failure). */
|
|
64
|
+
private loadPets;
|
|
52
65
|
private projection;
|
|
53
66
|
/**
|
|
54
67
|
* Build the face the card's slot registration injects.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PetSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PetSettingsCard.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"PetSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PetSettingsCard.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAC7F,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAE1F,OAAO,EAAoD,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,IAAI,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAE1J,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,qBAAqB;IACrB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,0CAA0C;AAC1C,MAAM,WAAW,oBAAqB,SAAQ,SAAS;IACrD,4BAA4B;IAC5B,OAAO,EAAE,cAAc,CAAA;IACvB,qBAAqB;IACrB,OAAO,EAAE,cAAc,CAAA;IACvB,iBAAiB;IACjB,IAAI,EAAE,cAAc,CAAA;IACpB,mBAAmB;IACnB,KAAK,EAAE,cAAc,CAAA;IACrB,oBAAoB;IACpB,MAAM,EAAE,cAAc,CAAA;IACtB,oBAAoB;IACpB,KAAK,EAAE,cAAc,CAAA;IACrB,wEAAwE;IACxE,UAAU,EAAE,SAAS;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CACxD;AAED,gEAAgE;AAChE,MAAM,WAAW,mBAAoB,SAAQ,WAAW;IACtD,KAAK,EAAE;QACL,iEAAiE;QACjE,eAAe,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAA;KACrD,CAAA;CACF;AAeD,2DAA2D;AAC3D,qBAAa,yBAAyB;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAuB;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAI3D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAe;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,QAAQ,CAAI;IAEpB,uEAAuE;gBAC3D,KAAK,EAAE,aAAa,CAAC,WAAW,CAAC;IAa7C,0EAA0E;YAC5D,QAAQ;IAgBtB,OAAO,CAAC,UAAU;IAalB;;;OAGG;IACH,MAAM,IAAI,mBAAmB;CAG9B;AAED,0DAA0D;AAC1D,MAAM,MAAM,oBAAoB,GAC9B,YAAY,CAAC,oBAAoB,CAAC,GAChC,WAAW,CAAC,KAAK,CAAC,GAClB,UAAU,CAAC,mBAAmB,CAAC,CAAA;AAEnC;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,+BAsF1D"}
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { PluginSettingsCard, ValueField, BooleanField } from "./PluginSettingsCard.js";
|
|
3
|
-
import { CardForm, booleanField,
|
|
4
|
-
/**
|
|
2
|
+
import { PluginSettingsCard, ValueField, BooleanField, ChoiceField } from "./PluginSettingsCard.js";
|
|
3
|
+
import { CardForm, booleanField, choiceField, numberField } from "./settings-form.js";
|
|
4
|
+
/** Fetch the registry list (the same data the sprite renders from). */
|
|
5
|
+
async function fetchPetChoices() {
|
|
6
|
+
const response = await fetch('/api/pet/pets');
|
|
7
|
+
if (!response.ok)
|
|
8
|
+
throw new Error('pet pets failed: ' + response.status);
|
|
9
|
+
return (await response.json());
|
|
10
|
+
}
|
|
11
|
+
/** Bridges the 'pet' scope onto the card's staged form. */
|
|
5
12
|
export class PetSettingsCardController {
|
|
6
13
|
form;
|
|
7
14
|
store;
|
|
8
|
-
|
|
15
|
+
// The choice list rides a mutable array shared with the choiceField spec,
|
|
16
|
+
// so loading the registry re-validates and re-formats the petId field
|
|
17
|
+
// without rebuilding the form.
|
|
18
|
+
petChoices = [];
|
|
19
|
+
petLabels = new Map();
|
|
20
|
+
loaded = false;
|
|
21
|
+
attempts = 0;
|
|
22
|
+
/** @param scope - the bound settings scope for the 'pet' namespace. */
|
|
9
23
|
constructor(scope) {
|
|
10
24
|
this.form = new CardForm(scope, [
|
|
11
25
|
booleanField('enabled'),
|
|
@@ -13,9 +27,29 @@ export class PetSettingsCardController {
|
|
|
13
27
|
numberField('size'),
|
|
14
28
|
numberField('right'),
|
|
15
29
|
numberField('bottom'),
|
|
16
|
-
|
|
30
|
+
choiceField('petId', this.petChoices),
|
|
17
31
|
]);
|
|
18
32
|
this.store = this.form.bind(() => this.projection());
|
|
33
|
+
void this.loadPets();
|
|
34
|
+
}
|
|
35
|
+
/** Resolve the registry choices once (retried a few times on failure). */
|
|
36
|
+
async loadPets() {
|
|
37
|
+
if (this.loaded)
|
|
38
|
+
return;
|
|
39
|
+
try {
|
|
40
|
+
const list = await fetchPetChoices();
|
|
41
|
+
this.petChoices.splice(0, this.petChoices.length, ...list.map(choice => choice.id));
|
|
42
|
+
for (const choice of list)
|
|
43
|
+
this.petLabels.set(choice.id, choice.displayName);
|
|
44
|
+
this.loaded = true;
|
|
45
|
+
this.store.set(this.projection());
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
this.attempts += 1;
|
|
49
|
+
if (this.attempts < 3) {
|
|
50
|
+
window.setTimeout(() => { void this.loadPets(); }, 3000);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
19
53
|
}
|
|
20
54
|
projection() {
|
|
21
55
|
return {
|
|
@@ -25,7 +59,8 @@ export class PetSettingsCardController {
|
|
|
25
59
|
size: this.form.field('size'),
|
|
26
60
|
right: this.form.field('right'),
|
|
27
61
|
bottom: this.form.field('bottom'),
|
|
28
|
-
|
|
62
|
+
petId: this.form.field('petId'),
|
|
63
|
+
petChoices: this.petChoices.map(id => ({ value: id, label: this.petLabels.get(id) ?? id })),
|
|
29
64
|
};
|
|
30
65
|
}
|
|
31
66
|
/**
|
|
@@ -51,5 +86,5 @@ export function PetSettingsCard(props) {
|
|
|
51
86
|
invalidLabel: t('settings.invalidNumber'),
|
|
52
87
|
disabled,
|
|
53
88
|
};
|
|
54
|
-
return (_jsxs(PluginSettingsCard, { t: t, titleKey: "settings.title", descriptionKey: "settings.description", state: state, onSave: props.save, onDiscard: props.discard, children: [_jsx(BooleanField, { id: "settings-pet-enabled", label: t('settings.enabled'), hint: t('settings.enabledHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.enabled, onEdit: (text) => { props.edit('enabled', text); }, onReset: () => { props.resetField('enabled'); } }), _jsx(
|
|
89
|
+
return (_jsxs(PluginSettingsCard, { t: t, titleKey: "settings.title", descriptionKey: "settings.description", state: state, onSave: props.save, onDiscard: props.discard, children: [_jsx(BooleanField, { id: "settings-pet-enabled", label: t('settings.enabled'), hint: t('settings.enabledHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.enabled, onEdit: (text) => { props.edit('enabled', text); }, onReset: () => { props.resetField('enabled'); } }), _jsx(ChoiceField, { id: "settings-pet-pet", label: t('settings.pet'), hint: t('settings.petHint'), inheritLabel: t('settings.inherit'), ...fieldProps, ...state.petId, choices: state.petChoices, onEdit: (text) => { props.edit('petId', text); }, onReset: () => { props.resetField('petId'); } }), _jsx(BooleanField, { id: "settings-pet-visible", label: t('settings.visible'), hint: t('settings.visibleHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.visible, onEdit: (text) => { props.edit('visible', text); }, onReset: () => { props.resetField('visible'); } }), _jsx(ValueField, { id: "settings-pet-size", label: t('settings.size'), hint: t('settings.sizeHint'), numeric: true, ...fieldProps, ...state.size, onEdit: (text) => { props.edit('size', text); }, onReset: () => { props.resetField('size'); } }), _jsx(ValueField, { id: "settings-pet-right", label: t('settings.right'), hint: t('settings.rightHint'), numeric: true, ...fieldProps, ...state.right, onEdit: (text) => { props.edit('right', text); }, onReset: () => { props.resetField('right'); } }), _jsx(ValueField, { id: "settings-pet-bottom", label: t('settings.bottom'), hint: t('settings.bottomHint'), numeric: true, ...fieldProps, ...state.bottom, onEdit: (text) => { props.edit('bottom', text); }, onReset: () => { props.resetField('bottom'); } })] }));
|
|
55
90
|
}
|
|
@@ -1,38 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Pet sprite companion component — the browser half's centerpiece. Renders a
|
|
3
3
|
* fixed-position floating sprite (React portal onto document.body), plays
|
|
4
|
-
* the
|
|
5
|
-
*
|
|
6
|
-
* reposition (persisted via setConfig).
|
|
7
|
-
*
|
|
4
|
+
* the track matching the host animation snapshot, and exposes the
|
|
5
|
+
* interaction surface: click to pet, hover panel with feed/rename/hide, drag
|
|
6
|
+
* to reposition (persisted via setConfig). Everything visual comes from the
|
|
7
|
+
* pet definition the host serves ('/api/pet/pets' + the state snapshot's
|
|
8
|
+
* pet id), so one component renders every registry entry.
|
|
9
|
+
* @module @linxin666/dsh-pet/client/PetSprite
|
|
8
10
|
*/
|
|
9
11
|
import type { ReactPortal } from 'react';
|
|
10
12
|
import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';
|
|
11
13
|
import type { PetDisplayConfig } from '../persist.ts';
|
|
12
14
|
import type { PetStateView } from '../service.ts';
|
|
15
|
+
import type { PetDefinition } from '../registry.ts';
|
|
13
16
|
import type { PetFeedback } from './pet-store.ts';
|
|
14
17
|
import { NS } from './locales.ts';
|
|
15
|
-
/**
|
|
16
|
-
export
|
|
17
|
-
/** Browser URL of the whale-girl manifest (authoritative per-row frame counts). */
|
|
18
|
-
export declare const PET_MANIFEST_URL = "/pet/whale/pet.json";
|
|
19
|
-
/** Props injected by the slot registration (store actions + locale). */
|
|
20
|
-
export interface WhalePetProps {
|
|
18
|
+
/** Props injected by the plugin apply body (store actions + locale). */
|
|
19
|
+
export interface PetSpriteProps {
|
|
21
20
|
/** Latest host snapshot; null while loading. */
|
|
22
21
|
snapshot: PetStateView | null;
|
|
22
|
+
/** The selected pet's registry definition (atlas URL + geometry + tracks). */
|
|
23
|
+
definition: PetDefinition;
|
|
23
24
|
/** Display configuration (persisted by the host). */
|
|
24
25
|
display: PetDisplayConfig;
|
|
25
26
|
/** Active reaction bubble, if any. */
|
|
26
27
|
feedback: PetFeedback | null;
|
|
27
|
-
/** Pet the
|
|
28
|
+
/** Pet the sprite (click). */
|
|
28
29
|
onPet: () => void;
|
|
29
|
-
/** Feed the
|
|
30
|
+
/** Feed the sprite (panel button). */
|
|
30
31
|
onFeed: () => void;
|
|
31
|
-
/** Hide the
|
|
32
|
+
/** Hide the sprite (panel button). */
|
|
32
33
|
onHide: () => void;
|
|
33
34
|
/** Persist a drag position. */
|
|
34
35
|
onDragEnd: (right: number, bottom: number) => void;
|
|
35
|
-
/** Rename the pet (persisted by the host). */
|
|
36
|
+
/** Rename the selected pet (persisted by the host). */
|
|
36
37
|
onRename: (name: string) => void;
|
|
37
38
|
/** Clear the reaction bubble (after its CSS animation). */
|
|
38
39
|
onFeedbackDone: () => void;
|
|
@@ -41,9 +42,9 @@ export interface WhalePetProps {
|
|
|
41
42
|
}
|
|
42
43
|
/**
|
|
43
44
|
* The floating pet. The spritesheet frame advances on requestAnimationFrame
|
|
44
|
-
* with per-frame durations from
|
|
45
|
-
* the background position is written straight to the sprite
|
|
46
|
-
* per-frame React state).
|
|
45
|
+
* with per-frame durations from the definition's tracks; the atlas image is
|
|
46
|
+
* loaded once and the background position is written straight to the sprite
|
|
47
|
+
* element (no per-frame React state).
|
|
47
48
|
*/
|
|
48
|
-
export declare function
|
|
49
|
-
//# sourceMappingURL=
|
|
49
|
+
export declare function PetSprite(props: PetSpriteProps): ReactPortal;
|
|
50
|
+
//# sourceMappingURL=PetSprite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PetSprite.d.ts","sourceRoot":"","sources":["../../../src/client/PetSprite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAqC,WAAW,EAAE,MAAM,OAAO,CAAA;AAG3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAGjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,UAAU,EAAE,aAAa,CAAA;IACzB,qDAAqD;IACrD,OAAO,EAAE,gBAAgB,CAAA;IACzB,sCAAsC;IACtC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAClD,uDAAuD;IACvD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;AAOD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,WAAW,CAuS5D"}
|