@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
|
@@ -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,22 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* plugin and what its settings govern, the controls inside, and the save
|
|
4
|
-
* writes them. Renders nothing while the namespace is unavailable — a
|
|
2
|
+
* Family-shared chrome for plugin settings cards: a disclosure header naming
|
|
3
|
+
* the plugin and what its settings govern, the controls inside, and the save
|
|
4
|
+
* that writes them. Renders nothing while the namespace is unavailable — a
|
|
5
5
|
* deployment that does not compose the owning plugin should show no trace of
|
|
6
|
-
* it.
|
|
7
|
-
*
|
|
6
|
+
* it. Inlined into each consumer's client bundle; mirrors the official
|
|
7
|
+
* ui-plugin-config PluginCard in a self-contained slice.
|
|
8
8
|
*/
|
|
9
9
|
import { type ReactNode } from 'react';
|
|
10
10
|
import type { CardShell } from './settings-form.ts';
|
|
11
|
-
|
|
11
|
+
/** Copy keys the card chrome itself reads; every consumer locale carries this shared vocabulary. */
|
|
12
|
+
export declare const CARD_COPY_KEYS: readonly ["settings.collapse", "settings.expand", "settings.notExposed", "settings.unsaved", "settings.readOnly", "settings.saveFailed", "settings.discard", "settings.save", "settings.saving"];
|
|
13
|
+
/** Copy key the card chrome itself reads. */
|
|
14
|
+
export type CardCopyKey = (typeof CARD_COPY_KEYS)[number];
|
|
15
|
+
/** Key domain for the plugin's own copy (inferred per consumer). */
|
|
16
|
+
export type SettingsCardKey<TKey extends string = string> = TKey | CardCopyKey;
|
|
12
17
|
/** Card chrome shared by every plugin settings card. */
|
|
13
|
-
export interface PluginSettingsCardProps {
|
|
18
|
+
export interface PluginSettingsCardProps<TKey extends string = string> {
|
|
14
19
|
/** Locale reader for this card's copy. */
|
|
15
|
-
t: (key: SettingsCardKey) => string;
|
|
20
|
+
t: (key: SettingsCardKey<TKey>, params?: Record<string, string | number>) => string;
|
|
16
21
|
/** Locale key of the plugin's name. */
|
|
17
|
-
titleKey:
|
|
22
|
+
titleKey: TKey;
|
|
18
23
|
/** Locale key of the line describing what this plugin's settings govern. */
|
|
19
|
-
descriptionKey:
|
|
24
|
+
descriptionKey: TKey;
|
|
20
25
|
/** The card's form state: availability, writability, and what a save would do. */
|
|
21
26
|
state: CardShell;
|
|
22
27
|
/** Write every staged edit. */
|
|
@@ -31,7 +36,7 @@ export interface PluginSettingsCardProps {
|
|
|
31
36
|
* @param props - the plugin's copy keys, its form state, and its controls.
|
|
32
37
|
* @returns the card, or nothing while the namespace is still loading.
|
|
33
38
|
*/
|
|
34
|
-
export declare function PluginSettingsCard(props: PluginSettingsCardProps): import("react").JSX.Element | null;
|
|
39
|
+
export declare function PluginSettingsCard<TKey extends string = string>(props: PluginSettingsCardProps<TKey>): import("react").JSX.Element | null;
|
|
35
40
|
/** Props every field control needs regardless of its value type. */
|
|
36
41
|
export interface FieldProps {
|
|
37
42
|
/** Stable id associating the label with its control. */
|
|
@@ -75,4 +80,14 @@ export declare function BooleanField(props: FieldProps & {
|
|
|
75
80
|
/** Copy for the off option. */
|
|
76
81
|
offLabel: string;
|
|
77
82
|
}): import("react").JSX.Element;
|
|
83
|
+
/** A staged enumerated field rendered as a select. */
|
|
84
|
+
export declare function ChoiceField(props: FieldProps & {
|
|
85
|
+
/** Copy for the inherit option (draft text is the empty string). */
|
|
86
|
+
inheritLabel: string;
|
|
87
|
+
/** Choices rendered in order; `value` is the draft/stored text. */
|
|
88
|
+
choices: ReadonlyArray<{
|
|
89
|
+
value: string;
|
|
90
|
+
label: string;
|
|
91
|
+
}>;
|
|
92
|
+
}): import("react").JSX.Element;
|
|
78
93
|
//# sourceMappingURL=PluginSettingsCard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Generated by scripts/sync-shared.mjs from shared/client/settings/PluginSettingsCard.tsx. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
* plugin and what its settings govern, the controls inside, and the save
|
|
5
|
-
* writes them. Renders nothing while the namespace is unavailable — a
|
|
4
|
+
* Family-shared chrome for plugin settings cards: a disclosure header naming
|
|
5
|
+
* the plugin and what its settings govern, the controls inside, and the save
|
|
6
|
+
* that writes them. Renders nothing while the namespace is unavailable — a
|
|
6
7
|
* deployment that does not compose the owning plugin should show no trace of
|
|
7
|
-
* it.
|
|
8
|
-
*
|
|
8
|
+
* it. Inlined into each consumer's client bundle; mirrors the official
|
|
9
|
+
* ui-plugin-config PluginCard in a self-contained slice.
|
|
9
10
|
*/
|
|
10
11
|
import { useState } from 'react';
|
|
11
12
|
import css from './settings-card.module.css';
|
|
13
|
+
/** Copy keys the card chrome itself reads; every consumer locale carries this shared vocabulary. */
|
|
14
|
+
export const CARD_COPY_KEYS = [
|
|
15
|
+
'settings.collapse',
|
|
16
|
+
'settings.expand',
|
|
17
|
+
'settings.notExposed',
|
|
18
|
+
'settings.unsaved',
|
|
19
|
+
'settings.readOnly',
|
|
20
|
+
'settings.saveFailed',
|
|
21
|
+
'settings.discard',
|
|
22
|
+
'settings.save',
|
|
23
|
+
'settings.saving',
|
|
24
|
+
];
|
|
12
25
|
/**
|
|
13
26
|
* Render one plugin settings card.
|
|
14
27
|
* @param props - the plugin's copy keys, its form state, and its controls.
|
|
@@ -20,6 +33,7 @@ export function PluginSettingsCard(props) {
|
|
|
20
33
|
if (!state.available)
|
|
21
34
|
return null;
|
|
22
35
|
const title = props.t(props.titleKey);
|
|
36
|
+
const description = props.t(props.descriptionKey);
|
|
23
37
|
const blocked = !state.dirty || state.invalid || state.saving;
|
|
24
38
|
const cardClass = open ? `${css.cardOpen} ${css.card}` : css.card;
|
|
25
39
|
// The namespace exists but the Host does not serve it to this client (the
|
|
@@ -27,12 +41,14 @@ export function PluginSettingsCard(props) {
|
|
|
27
41
|
// that explains the gap instead of vanishing, so a missing card never
|
|
28
42
|
// reads as a missing plugin.
|
|
29
43
|
if (!state.exposed) {
|
|
30
|
-
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, children: title }), _jsx("span", { className: css.description, children:
|
|
44
|
+
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 })] }), _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
|
|
31
45
|
? (_jsx("div", { className: css.body, children: _jsx("p", { className: css.notExposed, role: "status", children: props.t('settings.notExposed') }) }))
|
|
32
46
|
: null] }));
|
|
33
47
|
}
|
|
34
|
-
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, children: title }), _jsx("span", { className: css.description, children:
|
|
35
|
-
? (_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
|
|
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
|
|
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') })] })] }))
|
|
36
52
|
: null] }));
|
|
37
53
|
}
|
|
38
54
|
/** A staged value field. `numeric` only hints the keypad: which drafts a field accepts is decided by its spec. */
|
|
@@ -47,3 +63,9 @@ export function BooleanField(props) {
|
|
|
47
63
|
? (_jsxs("span", { className: css.badges, children: [_jsx("span", { className: css.badge, children: props.overriddenLabel }), _jsx("button", { type: "button", className: css.reset, disabled: props.disabled, onClick: props.onReset, children: props.resetLabel })] }))
|
|
48
64
|
: null] }), _jsxs("select", { id: props.id, className: css.select, value: props.text, disabled: props.disabled, onChange: (event) => { props.onEdit(event.target.value); }, children: [_jsx("option", { value: "", children: props.inheritLabel }), _jsx("option", { value: "true", children: props.onLabel }), _jsx("option", { value: "false", children: props.offLabel })] }), _jsx("p", { className: css.hint, children: props.hint })] }));
|
|
49
65
|
}
|
|
66
|
+
/** A staged enumerated field rendered as a select. */
|
|
67
|
+
export function ChoiceField(props) {
|
|
68
|
+
return (_jsxs("div", { className: css.field, children: [_jsxs("div", { className: css.head, children: [_jsx("label", { className: css.label, htmlFor: props.id, children: props.label }), props.overridden
|
|
69
|
+
? (_jsxs("span", { className: css.badges, children: [_jsx("span", { className: css.badge, children: props.overriddenLabel }), _jsx("button", { type: "button", className: css.reset, disabled: props.disabled, onClick: props.onReset, children: props.resetLabel })] }))
|
|
70
|
+
: null] }), _jsxs("select", { id: props.id, className: css.select, value: props.text, disabled: props.disabled, onChange: (event) => { props.onEdit(event.target.value); }, children: [_jsx("option", { value: "", children: props.inheritLabel }), props.choices.map(choice => (_jsx("option", { value: choice.value, children: choice.label }, choice.value)))] }), _jsx("p", { className: props.invalid ? css.invalid : css.hint, children: props.invalid ? props.invalidLabel : props.hint })] }));
|
|
71
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-pet browser half — mounts the
|
|
3
|
-
* and drives it from the host's same-origin
|
|
4
|
-
* the host snapshot (~
|
|
5
|
-
* The pet is host-global (no
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* dsh-pet browser half — mounts the selected pet as a global floating
|
|
3
|
+
* surface and drives it from the host's same-origin '/api/pet/*' JSON
|
|
4
|
+
* endpoints: fetch the registry list once, poll the host snapshot (~2 s),
|
|
5
|
+
* forward interactions, persist drag positions. The pet is host-global (no
|
|
6
|
+
* session dimension), so it mounts directly onto 'document.body' via a
|
|
7
|
+
* single React root rather than a session-scoped slot — on the
|
|
8
|
+
* new-conversation screen no session exists, and a dock-mounted pet would
|
|
9
|
+
* vanish there (issue #48). When the pet is hidden the entry becomes a
|
|
9
10
|
* fixed-position summon button.
|
|
10
11
|
* @module @linxin666/dsh-pet/client
|
|
11
12
|
*/
|
|
@@ -14,13 +15,15 @@ import type { ClientContext, SettingsScope, SettingsScopeSpec } from '@deepseek-
|
|
|
14
15
|
export declare const inject: string[];
|
|
15
16
|
/** Re-exported for consumers that type against the injected face. */
|
|
16
17
|
export type { PetInjected, PetDockEntryProps } from './PetDockEntry.tsx';
|
|
18
|
+
export type { PetSpriteProps } from './PetSprite.tsx';
|
|
17
19
|
export type { PetUiState, PetFeedback } from './pet-store.ts';
|
|
18
20
|
export type { PetSettingsCardFace, PetSettingsCardState } from './PetSettingsCard.tsx';
|
|
21
|
+
export type { PetDefinition } from '../registry.ts';
|
|
19
22
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
20
23
|
interface SlotMap {
|
|
21
24
|
/**
|
|
22
25
|
* The child slot the Web UI plugin group declares; this card registers
|
|
23
|
-
* into the group instead of the top-level
|
|
26
|
+
* into the group instead of the top-level 'settings.plugin.item' list.
|
|
24
27
|
* Spelled here with the same shape so this package can register without
|
|
25
28
|
* depending on the sibling UI package.
|
|
26
29
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;AA6D7G,yBAAyB;AACzB,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF,qEAAqE;AACrE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACxE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACtF,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,OAAO;QACf;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,sEAAsE;AACtE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAmL9C"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-pet browser half — mounts the
|
|
3
|
-
* and drives it from the host's same-origin
|
|
4
|
-
* the host snapshot (~
|
|
5
|
-
* The pet is host-global (no
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* dsh-pet browser half — mounts the selected pet as a global floating
|
|
3
|
+
* surface and drives it from the host's same-origin '/api/pet/*' JSON
|
|
4
|
+
* endpoints: fetch the registry list once, poll the host snapshot (~2 s),
|
|
5
|
+
* forward interactions, persist drag positions. The pet is host-global (no
|
|
6
|
+
* session dimension), so it mounts directly onto 'document.body' via a
|
|
7
|
+
* single React root rather than a session-scoped slot — on the
|
|
8
|
+
* new-conversation screen no session exists, and a dock-mounted pet would
|
|
9
|
+
* vanish there (issue #48). When the pet is hidden the entry becomes a
|
|
9
10
|
* fixed-position summon button.
|
|
10
11
|
* @module @linxin666/dsh-pet/client
|
|
11
12
|
*/
|
|
@@ -25,20 +26,22 @@ async function petFetch(path, body) {
|
|
|
25
26
|
body: JSON.stringify(body),
|
|
26
27
|
});
|
|
27
28
|
if (!response.ok) {
|
|
28
|
-
throw new Error(
|
|
29
|
+
throw new Error('pet ' + path + ' failed: ' + response.status);
|
|
29
30
|
}
|
|
30
31
|
return (await response.json());
|
|
31
32
|
}
|
|
32
33
|
/** The live host API instance (always defined; failures surface per call). */
|
|
33
34
|
const petApi = {
|
|
34
35
|
state: () => petFetch('/api/pet/state'),
|
|
36
|
+
pets: () => petFetch('/api/pet/pets'),
|
|
35
37
|
interact: (kind) => petFetch('/api/pet/interact', { kind }),
|
|
36
38
|
setVisible: (visible) => petFetch('/api/pet/set-visible', { visible }),
|
|
37
39
|
setConfig: (patch) => petFetch('/api/pet/set-config', patch),
|
|
38
40
|
setName: (name) => petFetch('/api/pet/set-name', { name }),
|
|
41
|
+
setPet: (petId) => petFetch('/api/pet/set-pet', { petId }),
|
|
39
42
|
};
|
|
40
43
|
/** Poll interval for the host snapshot. */
|
|
41
|
-
const POLL_MS =
|
|
44
|
+
const POLL_MS = 2000;
|
|
42
45
|
/** Settings namespace the pet settings card edits (the Host plugin registers it). */
|
|
43
46
|
const PET_SETTINGS_NS = 'pet';
|
|
44
47
|
/** Required services. */
|
|
@@ -59,8 +62,9 @@ export function apply(ctx) {
|
|
|
59
62
|
? snapshot.value?.enabled ?? true
|
|
60
63
|
: snapshot.status === 'unavailable';
|
|
61
64
|
};
|
|
62
|
-
// Plugin configuration card: one staged form over the
|
|
63
|
-
// namespace, contributed to the Web UI plugin group.
|
|
65
|
+
// Plugin configuration card: one staged form over the 'pet' settings
|
|
66
|
+
// namespace, contributed to the Web UI plugin group. The controller loads
|
|
67
|
+
// the petId choices from the registry endpoint itself.
|
|
64
68
|
const petSettings = new PetSettingsCardController(settingsScope);
|
|
65
69
|
ctx.slots.inject('web-ui.plugin.item', () => ctx.slots.register({
|
|
66
70
|
name: 'web-ui.plugin.item',
|
|
@@ -81,9 +85,23 @@ export function apply(ctx) {
|
|
|
81
85
|
// it stateless on the new-conversation screen (no session to scope by).
|
|
82
86
|
const petStore = createPetStore().create();
|
|
83
87
|
const setSnapshot = petStore.actions.setSnapshot;
|
|
88
|
+
const setPets = petStore.actions.setPets;
|
|
84
89
|
const setState = petStore.actions.setState;
|
|
85
90
|
const setFeedback = petStore.actions.setFeedback;
|
|
91
|
+
// The registry list is fetched lazily with retries baked into the poll
|
|
92
|
+
// cycle: until it lands, the dock entry renders nothing and every 2s
|
|
93
|
+
// tick tries again. After it lands, one list feeds both the sprite and
|
|
94
|
+
// the settings card's choices.
|
|
95
|
+
let petsLoaded = false;
|
|
86
96
|
const pollNow = () => {
|
|
97
|
+
if (!petsLoaded) {
|
|
98
|
+
petApi.pets().then((list) => {
|
|
99
|
+
petsLoaded = true;
|
|
100
|
+
setPets(list);
|
|
101
|
+
}, () => {
|
|
102
|
+
// Retry on the next poll tick.
|
|
103
|
+
});
|
|
104
|
+
}
|
|
87
105
|
petApi.state().then((snapshot) => {
|
|
88
106
|
setSnapshot(snapshot);
|
|
89
107
|
}, () => {
|
|
@@ -95,7 +113,7 @@ export function apply(ctx) {
|
|
|
95
113
|
// change while the page is hidden, so a background interval would
|
|
96
114
|
// only burn RPCs (browser throttling is an unreliable backstop).
|
|
97
115
|
// Coming back to the tab refreshes the pet immediately instead of
|
|
98
|
-
// waiting out the next
|
|
116
|
+
// waiting out the next 2 s cycle.
|
|
99
117
|
let timer;
|
|
100
118
|
const stop = () => {
|
|
101
119
|
if (timer !== undefined) {
|
|
@@ -187,8 +205,8 @@ export function apply(ctx) {
|
|
|
187
205
|
// for a global floating surface — the dock is session-scoped, so a pet
|
|
188
206
|
// mounted there would vanish on the new-conversation screen (issue #48).
|
|
189
207
|
// The entry therefore mounts straight onto document.body via a single
|
|
190
|
-
// React root for the page lifetime:
|
|
191
|
-
// visible, and the hidden-state summon button is fixed-positioned.
|
|
208
|
+
// React root for the page lifetime: PetSprite portals itself to body
|
|
209
|
+
// when visible, and the hidden-state summon button is fixed-positioned.
|
|
192
210
|
const container = document.createElement('div');
|
|
193
211
|
container.dataset.dshPetRoot = '';
|
|
194
212
|
document.body.appendChild(container);
|
|
@@ -15,10 +15,12 @@ export declare const zh: {
|
|
|
15
15
|
readonly 'pet.rank': "亲密度 {rank}";
|
|
16
16
|
readonly 'pet.points': "{points} 点";
|
|
17
17
|
readonly 'pet.treats': "小鱼干 ×{n}";
|
|
18
|
-
readonly 'pet.state.loading': "
|
|
19
|
-
readonly 'pet.state.error': "
|
|
18
|
+
readonly 'pet.state.loading': "宠物正在赶来…";
|
|
19
|
+
readonly 'pet.state.error': "宠物迷路了(连接失败)";
|
|
20
20
|
readonly 'settings.title': "宠物";
|
|
21
|
-
readonly 'settings.description': "
|
|
21
|
+
readonly 'settings.description': "选择宠物并调整它的显示布局。";
|
|
22
|
+
readonly 'settings.pet': "宠物";
|
|
23
|
+
readonly 'settings.petHint': "选择显示哪只宠物;每只宠物独立命名,可在宠物悬浮面板改名。";
|
|
22
24
|
readonly 'settings.enabled': "启用宠物";
|
|
23
25
|
readonly 'settings.enabledHint': "关闭后隐藏宠物并停止轮询,可在设置里重新启用。";
|
|
24
26
|
readonly 'settings.visible': "显示宠物";
|
|
@@ -29,8 +31,6 @@ export declare const zh: {
|
|
|
29
31
|
readonly 'settings.rightHint': "距视口右边缘的水平内缩距离。";
|
|
30
32
|
readonly 'settings.bottom': "距底部(px)";
|
|
31
33
|
readonly 'settings.bottomHint': "距视口底边的垂直内缩距离。";
|
|
32
|
-
readonly 'settings.name': "名字";
|
|
33
|
-
readonly 'settings.nameHint': "宠物显示名,1–20 个字符。";
|
|
34
34
|
readonly 'settings.inherit': "继承";
|
|
35
35
|
readonly 'settings.on': "开";
|
|
36
36
|
readonly 'settings.off': "关";
|
|
@@ -58,10 +58,12 @@ export declare const en: {
|
|
|
58
58
|
readonly 'pet.rank': "Affinity {rank}";
|
|
59
59
|
readonly 'pet.points': "{points} pts";
|
|
60
60
|
readonly 'pet.treats': "Treats ×{n}";
|
|
61
|
-
readonly 'pet.state.loading': "The
|
|
62
|
-
readonly 'pet.state.error': "The
|
|
61
|
+
readonly 'pet.state.loading': "The pet is on its way…";
|
|
62
|
+
readonly 'pet.state.error': "The pet is lost (connection failed)";
|
|
63
63
|
readonly 'settings.title': "Pet";
|
|
64
|
-
readonly 'settings.description': "
|
|
64
|
+
readonly 'settings.description': "Pick a pet and tune its display layout.";
|
|
65
|
+
readonly 'settings.pet': "Pet";
|
|
66
|
+
readonly 'settings.petHint': "Choose which pet shows. Names are stored per pet; rename from the pet hover panel.";
|
|
65
67
|
readonly 'settings.enabled': "Enable the pet";
|
|
66
68
|
readonly 'settings.enabledHint': "When off, the pet hides and polling stops; re-enable it here.";
|
|
67
69
|
readonly 'settings.visible': "Show the pet";
|
|
@@ -72,8 +74,6 @@ export declare const en: {
|
|
|
72
74
|
readonly 'settings.rightHint': "Horizontal inset from the viewport right edge.";
|
|
73
75
|
readonly 'settings.bottom': "Bottom inset (px)";
|
|
74
76
|
readonly 'settings.bottomHint': "Vertical inset from the viewport bottom edge.";
|
|
75
|
-
readonly 'settings.name': "Name";
|
|
76
|
-
readonly 'settings.nameHint': "The pet’s display name, 1–20 characters.";
|
|
77
77
|
readonly 'settings.inherit': "Inherit";
|
|
78
78
|
readonly 'settings.on': "On";
|
|
79
79
|
readonly 'settings.off': "Off";
|