@linxin666/dsh-pet 0.1.15 → 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 +270 -334
- package/lib/client.js.map +1 -1
- package/lib/index.js +641 -87
- 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.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +3 -1
- 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 +30 -12
- 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 +62 -0
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +61 -12
- 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/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 +4 -2
- package/lib/types/ledger.d.ts.map +1 -1
- package/lib/types/ledger.js +11 -4
- package/lib/types/persist.d.ts +10 -3
- package/lib/types/persist.d.ts.map +1 -1
- package/lib/types/persist.js +32 -5
- 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 +46 -13
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +73 -16
- package/package.json +2 -2
- 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 +7 -1
- package/src/client/index.ts +36 -14
- package/src/client/locales.ts +10 -10
- package/src/client/pet-store.ts +9 -0
- package/src/client/settings-card.module.css +3 -0
- package/src/client/settings-form.ts +114 -12
- package/src/client/spritesheet.ts +16 -92
- package/src/index.test.ts +23 -0
- package/src/index.ts +89 -45
- package/src/ledger.test.ts +5 -3
- package/src/ledger.ts +11 -4
- package/src/persist.test.ts +52 -3
- package/src/persist.ts +42 -8
- package/src/registry.test.ts +132 -0
- package/src/registry.ts +400 -0
- package/src/routes.ts +166 -53
- package/src/service.ts +102 -20
- package/lib/types/client/WhalePet.d.ts.map +0 -1
|
@@ -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"}
|
|
@@ -1,36 +1,34 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Pet sprite companion component — the browser half's centerpiece. Renders a
|
|
4
4
|
* fixed-position floating sprite (React portal onto document.body), plays
|
|
5
|
-
* the
|
|
6
|
-
*
|
|
7
|
-
* reposition (persisted via setConfig).
|
|
8
|
-
*
|
|
5
|
+
* the track matching the host animation snapshot, and exposes the
|
|
6
|
+
* interaction surface: click to pet, hover panel with feed/rename/hide, drag
|
|
7
|
+
* to reposition (persisted via setConfig). Everything visual comes from the
|
|
8
|
+
* pet definition the host serves ('/api/pet/pets' + the state snapshot's
|
|
9
|
+
* pet id), so one component renders every registry entry.
|
|
10
|
+
* @module @linxin666/dsh-pet/client/PetSprite
|
|
9
11
|
*/
|
|
10
12
|
import { useEffect, useRef, useState } from 'react';
|
|
11
13
|
import { createPortal } from 'react-dom';
|
|
12
|
-
import
|
|
14
|
+
import clsx from 'clsx';
|
|
15
|
+
import { framePosition, rowOfTrack, trimTrack } from "./spritesheet.js";
|
|
13
16
|
import styles from './pet.module.css';
|
|
14
|
-
/** Browser URL of the whale-girl atlas (served by the host half's own route). */
|
|
15
|
-
export const PET_SPRITESHEET_URL = '/pet/whale/spritesheet.webp';
|
|
16
|
-
/** Browser URL of the whale-girl manifest (authoritative per-row frame counts). */
|
|
17
|
-
export const PET_MANIFEST_URL = '/pet/whale/pet.json';
|
|
18
17
|
/** Clamp a drag offset inside the viewport with a margin. */
|
|
19
18
|
function clampOffset(value, max) {
|
|
20
19
|
return Math.max(0, Math.min(max, value));
|
|
21
20
|
}
|
|
22
21
|
/**
|
|
23
22
|
* The floating pet. The spritesheet frame advances on requestAnimationFrame
|
|
24
|
-
* with per-frame durations from
|
|
25
|
-
* the background position is written straight to the sprite
|
|
26
|
-
* per-frame React state).
|
|
23
|
+
* with per-frame durations from the definition's tracks; the atlas image is
|
|
24
|
+
* loaded once and the background position is written straight to the sprite
|
|
25
|
+
* element (no per-frame React state).
|
|
27
26
|
*/
|
|
28
|
-
export function
|
|
29
|
-
const { snapshot, display, feedback } = props;
|
|
27
|
+
export function PetSprite(props) {
|
|
28
|
+
const { snapshot, definition, display, feedback } = props;
|
|
30
29
|
const spriteRef = useRef(null);
|
|
31
30
|
const floatRef = useRef(null);
|
|
32
31
|
const [imageReady, setImageReady] = useState(false);
|
|
33
|
-
const [frameCounts, setFrameCounts] = useState(null);
|
|
34
32
|
const [hovered, setHovered] = useState(false);
|
|
35
33
|
const [renaming, setRenaming] = useState(false);
|
|
36
34
|
const [nameDraft, setNameDraft] = useState('');
|
|
@@ -42,66 +40,46 @@ export function WhalePet(props) {
|
|
|
42
40
|
index: 0,
|
|
43
41
|
elapsed: 0,
|
|
44
42
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
//
|
|
43
|
+
const cell = definition.cell;
|
|
44
|
+
const columns = definition.columns;
|
|
45
|
+
const rows = definition.rows;
|
|
46
|
+
const tracks = definition.tracks;
|
|
47
|
+
// Load the atlas once; the definition carries the authoritative per-row
|
|
48
|
+
// frame counts and per-track durations, so nothing else is fetched.
|
|
51
49
|
useEffect(() => {
|
|
52
50
|
let cancelled = false;
|
|
53
51
|
const img = new Image();
|
|
54
52
|
img.onload = () => {
|
|
55
|
-
if (cancelled)
|
|
56
|
-
|
|
57
|
-
setImageReady(true);
|
|
58
|
-
fetch(PET_MANIFEST_URL)
|
|
59
|
-
.then((res) => (res.ok ? res.json() : Promise.resolve({})))
|
|
60
|
-
.then((manifest) => {
|
|
61
|
-
if (cancelled)
|
|
62
|
-
return;
|
|
63
|
-
const frames = manifest.frames;
|
|
64
|
-
if (Array.isArray(frames) && frames.length === 9 && frames.every((n) => typeof n === 'number')) {
|
|
65
|
-
setFrameCounts(frames);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
setFrameCounts(detectFrameCounts(img));
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
.catch(() => {
|
|
72
|
-
if (!cancelled)
|
|
73
|
-
setFrameCounts(detectFrameCounts(img));
|
|
74
|
-
});
|
|
53
|
+
if (!cancelled)
|
|
54
|
+
setImageReady(true);
|
|
75
55
|
};
|
|
76
|
-
img.src =
|
|
56
|
+
img.src = definition.atlasUrl;
|
|
77
57
|
return () => {
|
|
78
58
|
cancelled = true;
|
|
79
59
|
img.onload = null;
|
|
80
60
|
};
|
|
81
|
-
}, []);
|
|
61
|
+
}, [definition.atlasUrl]);
|
|
82
62
|
// Frame loop: advance the current track and write background-position.
|
|
83
63
|
// Offsets must be in SCALED coordinates (background-position applies to the
|
|
84
64
|
// scaled background image), so the current sprite scale rides a ref that
|
|
85
65
|
// the loop reads every tick. Under prefers-reduced-motion the sprite holds
|
|
86
66
|
// its track's first frame instead of animating (presentation-only; the
|
|
87
67
|
// animation state machine is untouched).
|
|
88
|
-
const spriteScale = display.size /
|
|
68
|
+
const spriteScale = display.size / cell.height;
|
|
89
69
|
const animation = snapshot?.animation ?? 'idle';
|
|
90
70
|
const scaleRef = useRef(spriteScale);
|
|
91
71
|
scaleRef.current = spriteScale;
|
|
92
72
|
useEffect(() => {
|
|
93
73
|
const reduceMotion = typeof window !== 'undefined'
|
|
94
74
|
&& window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true;
|
|
75
|
+
const row = rowOfTrack(animation);
|
|
76
|
+
const track = trimTrack(tracks[animation], rows[row] ?? tracks[animation].frames.length);
|
|
95
77
|
// Paint one static sprite frame up front either way, so the pet is never
|
|
96
78
|
// blank while the loop heat-up runs.
|
|
97
|
-
const row = rowOfTrack(animation);
|
|
98
|
-
const track = frameCounts === null
|
|
99
|
-
? TRACKS[animation]
|
|
100
|
-
: trimTrack(TRACKS[animation], frameCounts[row] ?? TRACKS[animation].frames.length);
|
|
101
79
|
const leadCol = track.frames[0];
|
|
102
|
-
const lead = framePosition(row, leadCol, scaleRef.current);
|
|
80
|
+
const lead = framePosition(cell, columns, row, leadCol, scaleRef.current);
|
|
103
81
|
if (spriteRef.current !== null) {
|
|
104
|
-
spriteRef.current.style.backgroundPosition =
|
|
82
|
+
spriteRef.current.style.backgroundPosition = lead.x + 'px ' + lead.y + 'px';
|
|
105
83
|
}
|
|
106
84
|
if (reduceMotion)
|
|
107
85
|
return;
|
|
@@ -113,9 +91,7 @@ export function WhalePet(props) {
|
|
|
113
91
|
// Trim the track to the row's real frame count (transparent cells
|
|
114
92
|
// would render as a vanishing pet).
|
|
115
93
|
const row = rowOfTrack(animation);
|
|
116
|
-
const track =
|
|
117
|
-
? TRACKS[animation]
|
|
118
|
-
: trimTrack(TRACKS[animation], frameCounts[row] ?? TRACKS[animation].frames.length);
|
|
94
|
+
const track = trimTrack(tracks[animation], rows[row] ?? tracks[animation].frames.length);
|
|
119
95
|
const st = frameRef.current;
|
|
120
96
|
if (st.track !== animation) {
|
|
121
97
|
st.track = animation;
|
|
@@ -138,17 +114,17 @@ export function WhalePet(props) {
|
|
|
138
114
|
}
|
|
139
115
|
}
|
|
140
116
|
const col = track.frames[st.index];
|
|
141
|
-
const
|
|
117
|
+
const pos = framePosition(cell, columns, row, col, scaleRef.current);
|
|
142
118
|
if (spriteRef.current !== null) {
|
|
143
|
-
spriteRef.current.style.backgroundPosition =
|
|
119
|
+
spriteRef.current.style.backgroundPosition = pos.x + 'px ' + pos.y + 'px';
|
|
144
120
|
}
|
|
145
121
|
raf = requestAnimationFrame(tick);
|
|
146
122
|
};
|
|
147
123
|
raf = requestAnimationFrame(tick);
|
|
148
124
|
return () => cancelAnimationFrame(raf);
|
|
149
|
-
}, [animation,
|
|
125
|
+
}, [animation, cell, columns, rows, tracks]);
|
|
150
126
|
// Auto-clear the feedback bubble after its CSS animation. The callback
|
|
151
|
-
// rides a ref so re-renders never reset the timer: the
|
|
127
|
+
// rides a ref so re-renders never reset the timer: the 2s poll rebuilds
|
|
152
128
|
// `props` every tick, and depending on it would starve the timeout.
|
|
153
129
|
const feedbackDoneRef = useRef(props.onFeedbackDone);
|
|
154
130
|
feedbackDoneRef.current = props.onFeedbackDone;
|
|
@@ -160,7 +136,7 @@ export function WhalePet(props) {
|
|
|
160
136
|
}, [feedback]);
|
|
161
137
|
// Dragging: pointer events on the sprite; position is right/bottom based.
|
|
162
138
|
// `draggedRef` records whether the pointer actually moved, so the browser's
|
|
163
|
-
// trailing click (fired after pointerup) does not pet the
|
|
139
|
+
// trailing click (fired after pointerup) does not pet the sprite.
|
|
164
140
|
const draggedRef = useRef(false);
|
|
165
141
|
const clearHideTimer = () => {
|
|
166
142
|
if (hideTimerRef.current !== null) {
|
|
@@ -196,9 +172,10 @@ export function WhalePet(props) {
|
|
|
196
172
|
props.onDragEnd(dragPos.right, dragPos.bottom);
|
|
197
173
|
};
|
|
198
174
|
const pos = dragPos ?? { right: display.right, bottom: display.bottom };
|
|
199
|
-
const spriteWidth = Math.round(
|
|
200
|
-
const spriteHeight = Math.round(
|
|
175
|
+
const spriteWidth = Math.round(cell.width * spriteScale);
|
|
176
|
+
const spriteHeight = Math.round(cell.height * spriteScale);
|
|
201
177
|
const statusBubble = feedback === null && !hovered ? snapshot?.bubble : undefined;
|
|
178
|
+
const displayName = snapshot?.name ?? definition.displayName;
|
|
202
179
|
const float = (_jsxs("div", { ref: floatRef, className: styles.float, style: { right: pos.right, bottom: pos.bottom, zIndex: 2147483000 }, onPointerEnter: () => {
|
|
203
180
|
clearHideTimer();
|
|
204
181
|
setHovered(true);
|
|
@@ -208,7 +185,7 @@ export function WhalePet(props) {
|
|
|
208
185
|
// container. Treat a target still inside the container's DOM (the
|
|
209
186
|
// overflowed panel) as "still hovering"; otherwise give the pointer a
|
|
210
187
|
// short grace period to reach the panel across the gap above it. The
|
|
211
|
-
// bridge (
|
|
188
|
+
// bridge ('.panel::after') keeps the pointer inside the hit area, and
|
|
212
189
|
// the grace period covers a slow mouse crossing the remaining sliver.
|
|
213
190
|
const next = e.relatedTarget;
|
|
214
191
|
if (next instanceof Node && floatRef.current?.contains(next))
|
|
@@ -218,8 +195,8 @@ export function WhalePet(props) {
|
|
|
218
195
|
}, children: [_jsx("div", { ref: spriteRef, className: styles.sprite, style: {
|
|
219
196
|
width: spriteWidth,
|
|
220
197
|
height: spriteHeight,
|
|
221
|
-
backgroundImage: imageReady ?
|
|
222
|
-
backgroundSize:
|
|
198
|
+
backgroundImage: imageReady ? 'url(' + definition.atlasUrl + ')' : undefined,
|
|
199
|
+
backgroundSize: (cell.width * columns * spriteScale) + 'px ' + (cell.height * rows.length * spriteScale) + 'px',
|
|
223
200
|
backgroundRepeat: 'no-repeat',
|
|
224
201
|
backgroundPosition: '0 0',
|
|
225
202
|
cursor: dragRef.current === null ? 'grab' : 'grabbing',
|
|
@@ -229,7 +206,7 @@ export function WhalePet(props) {
|
|
|
229
206
|
if (draggedRef.current)
|
|
230
207
|
return;
|
|
231
208
|
props.onPet();
|
|
232
|
-
}, role: "button", "aria-label":
|
|
209
|
+
}, role: "button", "aria-label": definition.displayName }), feedback !== null && (_jsx("div", { className: clsx(styles.bubble, feedback.kind === 'feed' ? styles.bubbleFeed : styles.bubblePet), children: feedback.text }, feedback.at)), statusBubble !== undefined && (_jsx("div", { className: clsx(styles.bubble, styles.bubbleStatus), role: "status", "aria-live": "polite", children: statusBubble })), hovered && dragRef.current === null && (_jsx("div", { className: styles.panel, onPointerEnter: () => {
|
|
233
210
|
// Reaching the panel (or its bridge) must cancel any hide timer
|
|
234
211
|
// the container's pointerleave may have armed while the pointer
|
|
235
212
|
// crossed the sliver between the sprite and the panel.
|
|
@@ -257,8 +234,8 @@ export function WhalePet(props) {
|
|
|
257
234
|
props.onRename(trimmed);
|
|
258
235
|
setRenaming(false);
|
|
259
236
|
}
|
|
260
|
-
}, children: props.t('pet.confirm') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.rankRow, children: [_jsx("span", { className: styles.nameCell, children:
|
|
261
|
-
setNameDraft(
|
|
237
|
+
}, children: props.t('pet.confirm') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.rankRow, children: [_jsx("span", { className: styles.nameCell, children: displayName }), _jsx("span", { children: props.t('pet.rank', { rank: snapshot?.affinity.rank ?? '?' }) })] }), _jsxs("div", { className: styles.rankRow, children: [_jsx("span", { children: props.t('pet.treats', { n: snapshot?.treats.stocked ?? 0 }) }), _jsx("span", { children: props.t('pet.points', { points: snapshot?.affinity.points ?? 0 }) })] }), _jsxs("div", { className: styles.actions, children: [_jsx("button", { type: "button", className: styles.action, onClick: props.onFeed, children: props.t('pet.feed') }), _jsx("button", { type: "button", className: styles.action, onClick: () => {
|
|
238
|
+
setNameDraft(displayName);
|
|
262
239
|
setRenaming(true);
|
|
263
240
|
}, children: props.t('pet.rename') }), _jsx("button", { type: "button", className: styles.action, onClick: props.onHide, children: props.t('pet.hide') })] })] })) }))] }));
|
|
264
241
|
return createPortal(float, document.body);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAGnD,oGAAoG;AACpG,eAAO,MAAM,cAAc,kMAUjB,CAAA;AAEV,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzD,oEAAoE;AACpE,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,IAAI,IAAI,GAAG,WAAW,CAAA;AAE9E,wDAAwD;AACxD,MAAM,WAAW,uBAAuB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACnE,0CAA0C;IAC1C,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAA;IACnF,uCAAuC;IACvC,QAAQ,EAAE,IAAI,CAAA;IACd,4EAA4E;IAC5E,cAAc,EAAE,IAAI,CAAA;IACpB,kFAAkF;IAClF,KAAK,EAAE,SAAS,CAAA;IAChB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,6BAA6B;IAC7B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,uBAAuB,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAGnD,oGAAoG;AACpG,eAAO,MAAM,cAAc,kMAUjB,CAAA;AAEV,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzD,oEAAoE;AACpE,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,IAAI,IAAI,GAAG,WAAW,CAAA;AAE9E,wDAAwD;AACxD,MAAM,WAAW,uBAAuB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACnE,0CAA0C;IAC1C,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAA;IACnF,uCAAuC;IACvC,QAAQ,EAAE,IAAI,CAAA;IACd,4EAA4E;IAC5E,cAAc,EAAE,IAAI,CAAA;IACpB,kFAAkF;IAClF,KAAK,EAAE,SAAS,CAAA;IAChB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,6BAA6B;IAC7B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,uBAAuB,CAAC,IAAI,CAAC,sCAiHpG;AAED,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAA;IACV,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,UAAU,EAAE,OAAO,CAAA;IACnB,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAA;IAChB,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAA;IACvB,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,QAAQ,EAAE,OAAO,CAAA;IACjB,wBAAwB;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,oEAAoE;IACpE,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,kHAAkH;AAClH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG;IAC7C,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,+BAqCA;AAED,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG;IAC/C,mCAAmC;IACnC,YAAY,EAAE,MAAM,CAAA;IACpB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;CACjB,+BAmCA;AAED,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG;IAC9C,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAA;IACpB,mEAAmE;IACnE,OAAO,EAAE,aAAa,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACzD,+BAsCA"}
|
|
@@ -46,7 +46,9 @@ export function PluginSettingsCard(props) {
|
|
|
46
46
|
: null] }));
|
|
47
47
|
}
|
|
48
48
|
return (_jsxs("li", { className: cardClass, children: [_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null, _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }), open
|
|
49
|
-
? (_jsxs("div", { className: css.body, children: [!state.writable ? _jsx("p", { className: css.readOnly, role: "status", children: props.t('settings.readOnly') }) : null, props.children, _jsxs("div", { className: css.footer, children: [state.failed
|
|
49
|
+
? (_jsxs("div", { className: css.body, children: [!state.writable ? _jsx("p", { className: css.readOnly, role: "status", children: props.t('settings.readOnly') }) : null, props.children, _jsxs("div", { className: css.footer, children: [state.failed
|
|
50
|
+
? (_jsxs("p", { className: css.failed, role: "status", children: [props.t('settings.saveFailed'), state.failedReason ? ' - ' + state.failedReason : ''] }))
|
|
51
|
+
: null, _jsx("button", { type: "button", className: css.discard, disabled: !state.dirty || state.saving, onClick: props.onDiscard, children: props.t('settings.discard') }), _jsx("button", { type: "button", className: css.save, disabled: blocked, onClick: props.onSave, children: props.t(!state.saving ? 'settings.save' : 'settings.saving') })] })] }))
|
|
50
52
|
: null] }));
|
|
51
53
|
}
|
|
52
54
|
/** A staged value field. `numeric` only hints the keypad: which drafts a field accepts is decided by its spec. */
|