@linxin666/dsh-pet 0.1.19 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +19 -12
- package/README.zh.md +19 -12
- package/assets/whale/pet.json +9 -0
- package/lib/client.js +202 -38
- package/lib/client.js.map +1 -1
- package/lib/index.js +897 -42
- package/lib/invariant.js +1 -1
- package/lib/{state-Diiq8vjk.js → state-Ch3f4luZ.js} +51 -23
- package/lib/types/affinity.d.ts +22 -18
- package/lib/types/affinity.d.ts.map +1 -1
- package/lib/types/affinity.js +32 -6
- package/lib/types/chatter.d.ts +111 -0
- package/lib/types/chatter.d.ts.map +1 -0
- package/lib/types/chatter.js +666 -0
- package/lib/types/client/PetDockEntry.d.ts +2 -0
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +2 -2
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSettingsCard.js +2 -2
- package/lib/types/client/PetSprite.d.ts +2 -0
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +100 -25
- package/lib/types/client/PluginSettingsCard.d.ts +7 -1
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +5 -3
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +31 -6
- package/lib/types/client/locales.d.ts +82 -80
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +2 -0
- package/lib/types/client/sequences.d.ts +10 -0
- package/lib/types/client/sequences.d.ts.map +1 -0
- package/lib/types/client/sequences.js +20 -0
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/spritesheet.d.ts +1 -1
- package/lib/types/client/spritesheet.d.ts.map +1 -1
- package/lib/types/client/spritesheet.js +3 -12
- package/lib/types/event-projection.d.ts +14 -1
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +38 -17
- package/lib/types/index.d.ts +2 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +15 -13
- package/lib/types/invariant.js +2 -2
- package/lib/types/ledger.d.ts.map +1 -1
- package/lib/types/ledger.js +18 -10
- package/lib/types/mount-once.d.ts +25 -0
- package/lib/types/mount-once.d.ts.map +1 -0
- package/lib/types/mount-once.js +42 -0
- package/lib/types/persist.d.ts.map +1 -1
- package/lib/types/persist.js +6 -4
- package/lib/types/registry.d.ts +7 -1
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +37 -1
- package/lib/types/remarks.d.ts +2 -0
- package/lib/types/remarks.d.ts.map +1 -1
- package/lib/types/remarks.js +5 -0
- package/lib/types/routes.js +2 -2
- package/lib/types/service.d.ts +16 -6
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +26 -8
- package/lib/types/state.d.ts +7 -4
- package/lib/types/state.d.ts.map +1 -1
- package/lib/types/state.js +20 -20
- package/package.json +9 -9
- package/src/affinity.test.ts +4 -2
- package/src/affinity.ts +37 -6
- package/src/chatter.test.ts +154 -0
- package/src/chatter.ts +697 -0
- package/src/client/PetDockEntry.tsx +3 -0
- package/src/client/PetSprite.test.tsx +246 -4
- package/src/client/PetSprite.tsx +132 -36
- package/src/client/PluginSettingsCard.tsx +10 -0
- package/src/client/index.ts +26 -3
- package/src/client/locales.ts +2 -0
- package/src/client/pet-css.test.ts +42 -0
- package/src/client/pet.module.css +173 -22
- package/src/client/sequences.test.ts +33 -0
- package/src/client/sequences.ts +33 -0
- package/src/client/spritesheet.ts +3 -13
- package/src/event-projection.ts +49 -16
- package/src/index.ts +4 -1
- package/src/ledger.test.ts +42 -1
- package/src/ledger.ts +15 -7
- package/src/mount-once.ts +48 -0
- package/src/persist.test.ts +18 -1
- package/src/persist.ts +3 -1
- package/src/registry.test.ts +50 -3
- package/src/registry.ts +47 -3
- package/src/remarks.ts +6 -0
- package/src/service.ts +42 -9
- package/src/state.test.ts +13 -0
- package/src/state.ts +24 -18
package/lib/client.js
CHANGED
|
@@ -62,21 +62,55 @@ window.__ModuleLoader__.load({
|
|
|
62
62
|
return n;
|
|
63
63
|
}
|
|
64
64
|
//#endregion
|
|
65
|
-
//#region src/
|
|
66
|
-
/**
|
|
67
|
-
|
|
65
|
+
//#region src/state.ts
|
|
66
|
+
/**
|
|
67
|
+
* Map one activity phase onto the animation contract.
|
|
68
|
+
* - thinking → `running` and tool → `running-right` (focused work).
|
|
69
|
+
* - review → `review` while answer text is streaming.
|
|
70
|
+
* - waiting → `waiting` (expectant pose, needs user input).
|
|
71
|
+
* - done → `jumping` (celebration), then back to `idle` after the window.
|
|
72
|
+
* - failed → `failed` briefly, then back to `idle`.
|
|
73
|
+
* - idle → `idle` (calm breathing loop).
|
|
74
|
+
*/
|
|
75
|
+
function animationForPhase(phase) {
|
|
76
|
+
switch (phase) {
|
|
77
|
+
case "thinking": return "running";
|
|
78
|
+
case "tool": return "running-right";
|
|
79
|
+
case "review": return "review";
|
|
80
|
+
case "waiting": return "waiting";
|
|
81
|
+
case "done": return "jumping";
|
|
82
|
+
case "failed": return "failed";
|
|
83
|
+
case "idle": return "idle";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** The spritesheet row index for one animation track. */
|
|
87
|
+
function rowOf(animation) {
|
|
68
88
|
return {
|
|
69
|
-
idle: 0,
|
|
89
|
+
"idle": 0,
|
|
70
90
|
"running-right": 1,
|
|
71
91
|
"running-left": 2,
|
|
72
|
-
waving: 3,
|
|
73
|
-
jumping: 4,
|
|
74
|
-
failed: 5,
|
|
75
|
-
waiting: 6,
|
|
76
|
-
running: 7,
|
|
77
|
-
review: 8
|
|
92
|
+
"waving": 3,
|
|
93
|
+
"jumping": 4,
|
|
94
|
+
"failed": 5,
|
|
95
|
+
"waiting": 6,
|
|
96
|
+
"running": 7,
|
|
97
|
+
"review": 8
|
|
78
98
|
}[animation];
|
|
79
99
|
}
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/client/spritesheet.ts
|
|
102
|
+
/**
|
|
103
|
+
* Spritesheet geometry helpers — parameterized by the pet definition the
|
|
104
|
+
* host serves over '/api/pet/pets', so the browser half renders any registry
|
|
105
|
+
* entry without per-pet code. The per-track tables (frames, durations, loop,
|
|
106
|
+
* fallback) also come from the registry; these helpers only place frames,
|
|
107
|
+
* guard track lengths, and map the fixed 9-row animation contract.
|
|
108
|
+
* @module @linxin666/dsh-pet/client/spritesheet
|
|
109
|
+
*/
|
|
110
|
+
/** Row index of one animation track (the fixed 9-row contract). */
|
|
111
|
+
function rowOfTrack(animation) {
|
|
112
|
+
return rowOf(animation);
|
|
113
|
+
}
|
|
80
114
|
/**
|
|
81
115
|
* Background-position (px) of one frame cell within the scaled atlas.
|
|
82
116
|
* The background image is scaled by `scale` (element size ÷ cell size), and
|
|
@@ -106,8 +140,32 @@ window.__ModuleLoader__.load({
|
|
|
106
140
|
};
|
|
107
141
|
}
|
|
108
142
|
//#endregion
|
|
143
|
+
//#region src/client/sequences.ts
|
|
144
|
+
/** Resolve the active track and frame after elapsed milliseconds of a looping sequence. */
|
|
145
|
+
function sequenceFrameAt(sequence, tracks, elapsedMs) {
|
|
146
|
+
const itemDurations = sequence.map((animation) => tracks[animation].durations.reduce((sum, value) => sum + value, 0));
|
|
147
|
+
const sequenceDuration = itemDurations.reduce((sum, value) => sum + value, 0);
|
|
148
|
+
let offset = Math.max(0, elapsedMs) % sequenceDuration;
|
|
149
|
+
let itemIndex = 0;
|
|
150
|
+
while (itemIndex < sequence.length - 1 && offset >= itemDurations[itemIndex]) {
|
|
151
|
+
offset -= itemDurations[itemIndex];
|
|
152
|
+
itemIndex += 1;
|
|
153
|
+
}
|
|
154
|
+
const animation = sequence[itemIndex];
|
|
155
|
+
const track = tracks[animation];
|
|
156
|
+
let frameIndex = 0;
|
|
157
|
+
while (frameIndex < track.frames.length - 1 && offset >= track.durations[frameIndex]) {
|
|
158
|
+
offset -= track.durations[frameIndex];
|
|
159
|
+
frameIndex += 1;
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
animation,
|
|
163
|
+
frameIndex
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
109
167
|
//#region \0dsh-css:packages/dsh-pet/src/client/pet.module.css.mjs
|
|
110
|
-
const css$2 = ".kz2Bea_float{pointer-events:auto;-webkit-user-select:none;user-select:none;flex-direction:column;align-items:center;display:flex;position:fixed}.kz2Bea_sprite{image-rendering:auto;touch-action:none;position:relative}.kz2Bea_bubble{white-space:nowrap;color:#fff;pointer-events:none;border-radius:999px;margin-bottom:6px;padding:4px 10px;font-size:12px;line-height:1.4;animation:2.6s ease-out forwards kz2Bea_pet-bubble-pop;position:absolute;bottom:100%;box-shadow:0 2px 8px #00000040}.kz2Bea_bubblePet{background:#f472b6eb}.kz2Bea_bubbleFeed{background:#38bdf8eb}.kz2Bea_bubbleStatus{text-overflow:ellipsis;background
|
|
168
|
+
const css$2 = ".kz2Bea_float{pointer-events:auto;-webkit-user-select:none;user-select:none;flex-direction:column;align-items:center;display:flex;position:fixed}.kz2Bea_sprite{image-rendering:auto;touch-action:none;position:relative}.kz2Bea_bubble{white-space:nowrap;color:#fff;pointer-events:none;border-radius:999px;margin-bottom:6px;padding:4px 10px;font-size:12px;line-height:1.4;animation:2.6s ease-out forwards kz2Bea_pet-bubble-pop;position:absolute;bottom:100%;box-shadow:0 2px 8px #00000040}.kz2Bea_bubblePet{background:#f472b6eb}.kz2Bea_bubbleFeed{background:#38bdf8eb}.kz2Bea_bubbleStatus{text-overflow:ellipsis;backdrop-filter:blur(8px);letter-spacing:.02em;background:linear-gradient(160deg,#1e293be0,#0f172aeb);border:1px solid #7dd3fc8c;max-width:min(280px,100vw - 24px);animation:.24s ease-out kz2Bea_pet-bubble-in;overflow:hidden;box-shadow:0 4px 14px #02061766,0 0 0 1px #7dd3fc1f,0 0 18px #38bdf82e}.kz2Bea_bubbleWhisper{letter-spacing:.03em;white-space:nowrap;text-overflow:ellipsis;color:#f5d0fe;backdrop-filter:blur(8px);pointer-events:none;background:linear-gradient(150deg,#581c8785,#9d174d70);border:1px dashed #f0abfc8c;border-radius:999px;max-width:min(260px,100vw - 24px);padding:4px 12px;font-size:11.5px;font-style:italic;line-height:1.45;animation:.32s cubic-bezier(.22,1,.36,1) kz2Bea_pet-whisper-in;position:relative;overflow:hidden;box-shadow:0 3px 12px #2e106566,0 0 14px #d946ef29}.kz2Bea_bubbleWhisper:before{content:\"「\";opacity:.75;margin-right:2px}.kz2Bea_bubbleWhisper:after{content:\"」\";opacity:.75;margin-left:2px}.kz2Bea_bubbleStack{pointer-events:auto;flex-direction:column;align-items:center;gap:4px;display:flex;position:absolute;bottom:100%}.kz2Bea_bubbleStack .kz2Bea_bubble{pointer-events:auto;margin-bottom:0;position:relative;bottom:auto}.kz2Bea_bubbleClickable{cursor:pointer;font:inherit;text-align:center;appearance:none;transition:filter .12s,box-shadow .12s}.kz2Bea_bubbleClickable:hover{filter:brightness(1.12)}.kz2Bea_bubbleClickable:focus-visible{outline:none;box-shadow:0 0 0 2px #38bdf8d9}@keyframes kz2Bea_pet-bubble-in{0%{opacity:0;transform:translateY(6px)scale(.96)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes kz2Bea_pet-whisper-in{0%{opacity:0;transform:translateY(8px)scale(.9)}60%{transform:translateY(-1px)scale(1.02)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes kz2Bea_pet-panel-in{0%{opacity:0;transform:translateY(4px)scale(.97)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes kz2Bea_pet-bubble-pop{0%{opacity:0;transform:translateY(6px)scale(.85)}15%{opacity:1;transform:translateY(0)scale(1.05)}25%{transform:translateY(0)scale(1)}75%{opacity:1}to{opacity:0;transform:translateY(-8px)scale(.95)}}.kz2Bea_panel{color:#e2e8f0;backdrop-filter:blur(10px);transform-origin:50% 0;background:linear-gradient(165deg,#1e293bf0,#0f172af5);border:1px solid #94a3b866;border-radius:12px;flex-direction:column;gap:7px;min-width:148px;margin-top:8px;padding:10px 12px;font-size:12px;animation:.2s cubic-bezier(.22,1,.36,1) kz2Bea_pet-panel-in;display:flex;position:absolute;top:100%;box-shadow:0 8px 24px #02061773,0 0 0 1px #7dd3fc14,inset 0 1px #e2e8f014}.kz2Bea_panel:after{content:\"\";height:14px;position:absolute;bottom:100%;left:0;right:0}.kz2Bea_panelAbove{transform-origin:50% 100%;margin-top:0;margin-bottom:8px;top:auto;bottom:100%}.kz2Bea_panelAbove:after{top:100%;bottom:auto}.kz2Bea_rankRow{white-space:nowrap;justify-content:space-between;gap:10px;display:flex}.kz2Bea_nameCell{letter-spacing:.02em;background:linear-gradient(90deg,#7dd3fc,#38bdf8 60%,#818cf8);color:#0000;-webkit-background-clip:text;background-clip:text;font-weight:600}.kz2Bea_statRank{color:#c4b5fd;font-weight:600}.kz2Bea_statTreats{color:#7dd3fc;font-weight:600}.kz2Bea_statPoints{color:#fcd34d;font-weight:600}.kz2Bea_renameRow{align-items:center;gap:6px;display:flex}.kz2Bea_nameInput{color:#e2e8f0;background:#1e293be6;border:1px solid #7dd3fc80;border-radius:6px;outline:none;flex:1;min-width:0;padding:3px 6px;font-size:12px}.kz2Bea_nameInput:focus{border-color:#38bdf8;box-shadow:0 0 0 2px #38bdf873}.kz2Bea_actions{gap:6px;display:flex}.kz2Bea_action{cursor:pointer;color:#0f172a;background:linear-gradient(#7dd3fc,#38bdf8);border:none;border-radius:6px;flex:1;padding:4px 8px;font-size:12px;transition:filter .12s,box-shadow .12s,transform .12s;box-shadow:0 2px 6px #38bdf840}.kz2Bea_action:hover{filter:brightness(1.08);transform:translateY(-1px);box-shadow:0 4px 10px #38bdf859}.kz2Bea_action:active{filter:brightness(.94);transform:translateY(0);box-shadow:0 1px 4px #38bdf840}.kz2Bea_action:focus-visible{outline:none;box-shadow:0 0 0 2px #38bdf8d9}.kz2Bea_summon{color:#7dd3fc;cursor:pointer;background:#0f172abf;border:1px dashed #7dd3fc99;border-radius:999px;padding:2px 10px;font-size:11px;transition:border-color .12s,color .12s,background .12s,box-shadow .12s}.kz2Bea_summon:hover{color:#bae6fd;background:#0f172ae6;border-color:#7dd3fcf2}.kz2Bea_summon:active{color:#7dd3fc;border-color:#7dd3fccc}.kz2Bea_summon:focus-visible{outline:none;box-shadow:0 0 0 2px #38bdf8d9}@media (prefers-reduced-motion:reduce){.kz2Bea_bubble,.kz2Bea_bubbleStatus,.kz2Bea_bubbleWhisper,.kz2Bea_panel{opacity:1;animation:none}.kz2Bea_action,.kz2Bea_summon{transition:none}}";
|
|
111
169
|
const tagId$2 = "@linxin666/dsh-pet/pet.module.css";
|
|
112
170
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
113
171
|
const tag = document.createElement("style");
|
|
@@ -120,18 +178,27 @@ window.__ModuleLoader__.load({
|
|
|
120
178
|
"action": "kz2Bea_action",
|
|
121
179
|
"actions": "kz2Bea_actions",
|
|
122
180
|
"bubble": "kz2Bea_bubble",
|
|
181
|
+
"bubbleClickable": "kz2Bea_bubbleClickable",
|
|
123
182
|
"bubbleFeed": "kz2Bea_bubbleFeed",
|
|
124
183
|
"bubblePet": "kz2Bea_bubblePet",
|
|
125
184
|
"bubbleStack": "kz2Bea_bubbleStack",
|
|
126
185
|
"bubbleStatus": "kz2Bea_bubbleStatus",
|
|
186
|
+
"bubbleWhisper": "kz2Bea_bubbleWhisper",
|
|
127
187
|
"float": "kz2Bea_float",
|
|
128
188
|
"nameCell": "kz2Bea_nameCell",
|
|
129
189
|
"nameInput": "kz2Bea_nameInput",
|
|
130
190
|
"panel": "kz2Bea_panel",
|
|
191
|
+
"panelAbove": "kz2Bea_panelAbove",
|
|
192
|
+
"pet-bubble-in": "kz2Bea_pet-bubble-in",
|
|
131
193
|
"pet-bubble-pop": "kz2Bea_pet-bubble-pop",
|
|
194
|
+
"pet-panel-in": "kz2Bea_pet-panel-in",
|
|
195
|
+
"pet-whisper-in": "kz2Bea_pet-whisper-in",
|
|
132
196
|
"rankRow": "kz2Bea_rankRow",
|
|
133
197
|
"renameRow": "kz2Bea_renameRow",
|
|
134
198
|
"sprite": "kz2Bea_sprite",
|
|
199
|
+
"statPoints": "kz2Bea_statPoints",
|
|
200
|
+
"statRank": "kz2Bea_statRank",
|
|
201
|
+
"statTreats": "kz2Bea_statTreats",
|
|
135
202
|
"summon": "kz2Bea_summon"
|
|
136
203
|
};
|
|
137
204
|
//#endregion
|
|
@@ -160,10 +227,15 @@ window.__ModuleLoader__.load({
|
|
|
160
227
|
const { snapshot, definition, display, feedback } = props;
|
|
161
228
|
const spriteRef = (0, react.useRef)(null);
|
|
162
229
|
const floatRef = (0, react.useRef)(null);
|
|
230
|
+
const panelRef = (0, react.useRef)(null);
|
|
231
|
+
const bubbleRef = (0, react.useRef)(null);
|
|
163
232
|
const [imageReady, setImageReady] = (0, react.useState)(false);
|
|
164
233
|
const [hovered, setHovered] = (0, react.useState)(false);
|
|
165
234
|
const [renaming, setRenaming] = (0, react.useState)(false);
|
|
235
|
+
const [panelAbove, setPanelAbove] = (0, react.useState)(false);
|
|
236
|
+
const [panelLift, setPanelLift] = (0, react.useState)(0);
|
|
166
237
|
const [nameDraft, setNameDraft] = (0, react.useState)("");
|
|
238
|
+
const composingRef = (0, react.useRef)(false);
|
|
167
239
|
const [dragPos, setDragPos] = (0, react.useState)(null);
|
|
168
240
|
const dragRef = (0, react.useRef)(null);
|
|
169
241
|
const hideTimerRef = (0, react.useRef)(null);
|
|
@@ -176,6 +248,7 @@ window.__ModuleLoader__.load({
|
|
|
176
248
|
const columns = definition.columns;
|
|
177
249
|
const rows = definition.rows;
|
|
178
250
|
const tracks = definition.tracks;
|
|
251
|
+
const sequences = definition.sequences;
|
|
179
252
|
(0, react.useEffect)(() => {
|
|
180
253
|
let cancelled = false;
|
|
181
254
|
const img = new Image();
|
|
@@ -189,23 +262,36 @@ window.__ModuleLoader__.load({
|
|
|
189
262
|
};
|
|
190
263
|
}, [definition.atlasUrl]);
|
|
191
264
|
const spriteScale = display.size / cell.height;
|
|
265
|
+
const phase = snapshot?.phase ?? "idle";
|
|
192
266
|
const animation = snapshot?.animation ?? "idle";
|
|
193
267
|
const scaleRef = (0, react.useRef)(spriteScale);
|
|
194
268
|
scaleRef.current = spriteScale;
|
|
195
269
|
(0, react.useEffect)(() => {
|
|
196
270
|
const reduceMotion = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches === true;
|
|
197
|
-
const
|
|
198
|
-
const
|
|
271
|
+
const sequence = animation === animationForPhase(phase) ? sequences?.[phase] : void 0;
|
|
272
|
+
const leadAnimation = sequence?.[0] ?? animation;
|
|
273
|
+
const row = rowOfTrack(leadAnimation);
|
|
274
|
+
const track = trimTrack(tracks[leadAnimation], rows[row] ?? tracks[leadAnimation].frames.length);
|
|
275
|
+
const leadCol = track.frames[0];
|
|
199
276
|
const lead = framePosition(cell, columns, row, leadCol, scaleRef.current);
|
|
200
277
|
if (spriteRef.current !== null) spriteRef.current.style.backgroundPosition = lead.x + "px " + lead.y + "px";
|
|
201
278
|
if (reduceMotion) return;
|
|
202
279
|
let raf = 0;
|
|
203
280
|
let last = performance.now();
|
|
281
|
+
let sequenceElapsed = 0;
|
|
204
282
|
const tick = (ts) => {
|
|
205
283
|
const delta = ts - last;
|
|
206
284
|
last = ts;
|
|
207
|
-
|
|
208
|
-
|
|
285
|
+
if (sequence !== void 0) {
|
|
286
|
+
sequenceElapsed += delta;
|
|
287
|
+
const current = sequenceFrameAt(sequence, tracks, sequenceElapsed);
|
|
288
|
+
const currentRow = rowOfTrack(current.animation);
|
|
289
|
+
const col = trimTrack(tracks[current.animation], rows[currentRow] ?? tracks[current.animation].frames.length).frames[current.frameIndex];
|
|
290
|
+
const pos = framePosition(cell, columns, currentRow, col, scaleRef.current);
|
|
291
|
+
if (spriteRef.current !== null) spriteRef.current.style.backgroundPosition = pos.x + "px " + pos.y + "px";
|
|
292
|
+
raf = requestAnimationFrame(tick);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
209
295
|
const st = frameRef.current;
|
|
210
296
|
if (st.track !== animation) {
|
|
211
297
|
st.track = animation;
|
|
@@ -231,10 +317,12 @@ window.__ModuleLoader__.load({
|
|
|
231
317
|
return () => cancelAnimationFrame(raf);
|
|
232
318
|
}, [
|
|
233
319
|
animation,
|
|
320
|
+
phase,
|
|
234
321
|
cell,
|
|
235
322
|
columns,
|
|
236
323
|
rows,
|
|
237
|
-
tracks
|
|
324
|
+
tracks,
|
|
325
|
+
sequences
|
|
238
326
|
]);
|
|
239
327
|
const feedbackDoneRef = (0, react.useRef)(props.onFeedbackDone);
|
|
240
328
|
feedbackDoneRef.current = props.onFeedbackDone;
|
|
@@ -290,8 +378,38 @@ window.__ModuleLoader__.load({
|
|
|
290
378
|
const spriteWidth = Math.round(cell.width * spriteScale);
|
|
291
379
|
const spriteHeight = Math.round(cell.height * spriteScale);
|
|
292
380
|
const sessionBubbles = snapshot?.sessions ?? [];
|
|
293
|
-
const statusBubble = feedback === null &&
|
|
381
|
+
const statusBubble = feedback === null && sessionBubbles.length === 0 ? snapshot?.bubble : void 0;
|
|
382
|
+
const whisper = feedback === null ? snapshot?.whisper : void 0;
|
|
383
|
+
const bubblePresent = feedback !== null || sessionBubbles.length > 0 || statusBubble !== void 0 || whisper !== void 0;
|
|
294
384
|
const displayName = snapshot?.name ?? definition.displayName;
|
|
385
|
+
(0, react.useLayoutEffect)(() => {
|
|
386
|
+
if (!hovered) {
|
|
387
|
+
setPanelAbove(false);
|
|
388
|
+
setPanelLift(0);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const updatePanelPlacement = () => {
|
|
392
|
+
const sprite = spriteRef.current;
|
|
393
|
+
const panel = panelRef.current;
|
|
394
|
+
if (sprite === null || panel === null) return;
|
|
395
|
+
const above = window.innerHeight - sprite.getBoundingClientRect().bottom < panel.getBoundingClientRect().height + 8;
|
|
396
|
+
setPanelAbove(above);
|
|
397
|
+
const bubbleHeight = above ? bubbleRef.current?.getBoundingClientRect().height ?? 0 : 0;
|
|
398
|
+
setPanelLift(bubbleHeight > 0 ? Math.ceil(bubbleHeight) + 14 : 0);
|
|
399
|
+
};
|
|
400
|
+
updatePanelPlacement();
|
|
401
|
+
window.addEventListener("resize", updatePanelPlacement);
|
|
402
|
+
return () => window.removeEventListener("resize", updatePanelPlacement);
|
|
403
|
+
}, [
|
|
404
|
+
hovered,
|
|
405
|
+
renaming,
|
|
406
|
+
pos.right,
|
|
407
|
+
pos.bottom,
|
|
408
|
+
display.size,
|
|
409
|
+
bubblePresent,
|
|
410
|
+
sessionBubbles.length,
|
|
411
|
+
feedback
|
|
412
|
+
]);
|
|
295
413
|
return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
296
414
|
ref: floatRef,
|
|
297
415
|
className: pet_module_css_default.float,
|
|
@@ -307,6 +425,7 @@ window.__ModuleLoader__.load({
|
|
|
307
425
|
onPointerLeave: (e) => {
|
|
308
426
|
const next = e.relatedTarget;
|
|
309
427
|
if (next instanceof Node && floatRef.current?.contains(next)) return;
|
|
428
|
+
if (renaming) return;
|
|
310
429
|
clearHideTimer();
|
|
311
430
|
hideTimerRef.current = window.setTimeout(() => setHovered(false), 300);
|
|
312
431
|
},
|
|
@@ -318,7 +437,7 @@ window.__ModuleLoader__.load({
|
|
|
318
437
|
width: spriteWidth,
|
|
319
438
|
height: spriteHeight,
|
|
320
439
|
backgroundImage: imageReady ? "url(" + definition.atlasUrl + ")" : void 0,
|
|
321
|
-
backgroundSize: cell.width * columns * spriteScale + "px " + cell.height * rows.length * spriteScale + "px",
|
|
440
|
+
backgroundSize: cell.width * columns * spriteScale + "px " + cell.height * (definition.atlasRows ?? rows.length) * spriteScale + "px",
|
|
322
441
|
backgroundRepeat: "no-repeat",
|
|
323
442
|
backgroundPosition: "0 0",
|
|
324
443
|
cursor: dragRef.current === null ? "grab" : "grabbing"
|
|
@@ -334,26 +453,40 @@ window.__ModuleLoader__.load({
|
|
|
334
453
|
"aria-label": definition.displayName
|
|
335
454
|
}),
|
|
336
455
|
feedback !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
456
|
+
ref: bubbleRef,
|
|
337
457
|
className: clsx(pet_module_css_default.bubble, feedback.kind === "feed" ? pet_module_css_default.bubbleFeed : pet_module_css_default.bubblePet),
|
|
338
458
|
children: feedback.text
|
|
339
459
|
}, feedback.at),
|
|
340
|
-
feedback === null &&
|
|
460
|
+
feedback === null && (sessionBubbles.length > 0 || statusBubble !== void 0 || whisper !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
461
|
+
ref: bubbleRef,
|
|
341
462
|
className: pet_module_css_default.bubbleStack,
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
463
|
+
children: [
|
|
464
|
+
sessionBubbles.map((session) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
465
|
+
type: "button",
|
|
466
|
+
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleStatus, pet_module_css_default.bubbleClickable),
|
|
467
|
+
title: props.t("pet.openSessionHint"),
|
|
468
|
+
onClick: () => {
|
|
469
|
+
props.onOpenSession(session.sessionId);
|
|
470
|
+
},
|
|
471
|
+
children: session.bubble
|
|
472
|
+
}, session.sessionId)),
|
|
473
|
+
sessionBubbles.length === 0 && statusBubble !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
474
|
+
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleStatus),
|
|
475
|
+
role: "status",
|
|
476
|
+
"aria-live": "polite",
|
|
477
|
+
children: statusBubble
|
|
478
|
+
}),
|
|
479
|
+
whisper !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
480
|
+
className: pet_module_css_default.bubbleWhisper,
|
|
481
|
+
children: whisper
|
|
482
|
+
}, whisper)
|
|
483
|
+
]
|
|
354
484
|
}),
|
|
355
485
|
hovered && dragRef.current === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
356
|
-
|
|
486
|
+
ref: panelRef,
|
|
487
|
+
className: clsx(pet_module_css_default.panel, panelAbove && pet_module_css_default.panelAbove),
|
|
488
|
+
"data-placement": panelAbove ? "above" : "below",
|
|
489
|
+
style: panelAbove && panelLift > 0 ? { marginBottom: panelLift } : void 0,
|
|
357
490
|
onPointerEnter: () => {
|
|
358
491
|
clearHideTimer();
|
|
359
492
|
},
|
|
@@ -366,8 +499,14 @@ window.__ModuleLoader__.load({
|
|
|
366
499
|
placeholder: props.t("pet.namePlaceholder"),
|
|
367
500
|
autoFocus: true,
|
|
368
501
|
onChange: (e) => setNameDraft(e.target.value),
|
|
502
|
+
onCompositionStart: () => {
|
|
503
|
+
composingRef.current = true;
|
|
504
|
+
},
|
|
505
|
+
onCompositionEnd: () => {
|
|
506
|
+
composingRef.current = false;
|
|
507
|
+
},
|
|
369
508
|
onKeyDown: (e) => {
|
|
370
|
-
if (e.nativeEvent.isComposing) return;
|
|
509
|
+
if (composingRef.current || e.nativeEvent.isComposing || e.key === "Process") return;
|
|
371
510
|
if (e.key === "Enter") {
|
|
372
511
|
const trimmed = nameDraft.trim();
|
|
373
512
|
if (trimmed !== "") {
|
|
@@ -394,11 +533,20 @@ window.__ModuleLoader__.load({
|
|
|
394
533
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
395
534
|
className: pet_module_css_default.nameCell,
|
|
396
535
|
children: displayName
|
|
397
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
536
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
537
|
+
className: pet_module_css_default.statRank,
|
|
538
|
+
children: props.t("pet.rank", { rank: snapshot?.affinity.rank ?? "?" })
|
|
539
|
+
})]
|
|
398
540
|
}),
|
|
399
541
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
400
542
|
className: pet_module_css_default.rankRow,
|
|
401
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
543
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
544
|
+
className: pet_module_css_default.statTreats,
|
|
545
|
+
children: props.t("pet.treats", { n: snapshot?.treats.stocked ?? 0 })
|
|
546
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
547
|
+
className: pet_module_css_default.statPoints,
|
|
548
|
+
children: props.t("pet.points", { points: snapshot?.affinity.points ?? 0 })
|
|
549
|
+
})]
|
|
402
550
|
}),
|
|
403
551
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
404
552
|
className: pet_module_css_default.actions,
|
|
@@ -413,6 +561,7 @@ window.__ModuleLoader__.load({
|
|
|
413
561
|
type: "button",
|
|
414
562
|
className: pet_module_css_default.action,
|
|
415
563
|
onClick: () => {
|
|
564
|
+
clearHideTimer();
|
|
416
565
|
setNameDraft(displayName);
|
|
417
566
|
setRenaming(true);
|
|
418
567
|
},
|
|
@@ -481,6 +630,7 @@ window.__ModuleLoader__.load({
|
|
|
481
630
|
onHide: props.hide,
|
|
482
631
|
onDragEnd: props.dragEnd,
|
|
483
632
|
onRename: props.rename,
|
|
633
|
+
onOpenSession: props.openSession,
|
|
484
634
|
onFeedbackDone: props.feedbackDone,
|
|
485
635
|
t: props.t
|
|
486
636
|
})
|
|
@@ -645,7 +795,7 @@ window.__ModuleLoader__.load({
|
|
|
645
795
|
children: props.t("settings.readOnly")
|
|
646
796
|
}) : null,
|
|
647
797
|
props.children,
|
|
648
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
798
|
+
props.hideFooter === true ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
649
799
|
className: settings_card_module_css_default.footer,
|
|
650
800
|
children: [
|
|
651
801
|
state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
@@ -1346,6 +1496,7 @@ window.__ModuleLoader__.load({
|
|
|
1346
1496
|
"pet.treats": "小鱼干 ×{n}",
|
|
1347
1497
|
"pet.state.loading": "宠物正在赶来…",
|
|
1348
1498
|
"pet.state.error": "宠物迷路了(连接失败)",
|
|
1499
|
+
"pet.openSessionHint": "点击跳转到对应会话",
|
|
1349
1500
|
"settings.title": "宠物",
|
|
1350
1501
|
"settings.description": "选择宠物并调整它的显示布局。",
|
|
1351
1502
|
"settings.pet": "宠物",
|
|
@@ -1389,6 +1540,7 @@ window.__ModuleLoader__.load({
|
|
|
1389
1540
|
"pet.treats": "Treats ×{n}",
|
|
1390
1541
|
"pet.state.loading": "The pet is on its way…",
|
|
1391
1542
|
"pet.state.error": "The pet is lost (connection failed)",
|
|
1543
|
+
"pet.openSessionHint": "Click to jump to this session",
|
|
1392
1544
|
"settings.title": "Pet",
|
|
1393
1545
|
"settings.description": "Pick a pet and tune its display layout.",
|
|
1394
1546
|
"settings.pet": "Pet",
|
|
@@ -1467,13 +1619,14 @@ window.__ModuleLoader__.load({
|
|
|
1467
1619
|
const POLL_MS = 2e3;
|
|
1468
1620
|
/** Settings namespace the pet settings card edits (the Host plugin registers it). */
|
|
1469
1621
|
const PET_SETTINGS_NS = "pet";
|
|
1470
|
-
/** Required services. */
|
|
1622
|
+
/** Required services (sessions powers bubble-to-session navigation). */
|
|
1471
1623
|
const inject = [
|
|
1472
1624
|
"slots",
|
|
1473
1625
|
"locale",
|
|
1474
1626
|
"connection",
|
|
1475
1627
|
"settingsScope",
|
|
1476
|
-
"remote"
|
|
1628
|
+
"remote",
|
|
1629
|
+
"sessions"
|
|
1477
1630
|
];
|
|
1478
1631
|
/**
|
|
1479
1632
|
* Client plugin body: register dictionaries, mount the global pet entry and
|
|
@@ -1515,14 +1668,19 @@ window.__ModuleLoader__.load({
|
|
|
1515
1668
|
const setState = petStore.actions.setState;
|
|
1516
1669
|
const setFeedback = petStore.actions.setFeedback;
|
|
1517
1670
|
let petsLoaded = false;
|
|
1671
|
+
let stateSeq = 0;
|
|
1518
1672
|
const pollNow = () => {
|
|
1519
1673
|
if (!petsLoaded) petApi.pets().then((list) => {
|
|
1520
1674
|
petsLoaded = true;
|
|
1521
1675
|
setPets(list);
|
|
1522
1676
|
}, () => {});
|
|
1677
|
+
const seq = stateSeq + 1;
|
|
1678
|
+
stateSeq = seq;
|
|
1523
1679
|
petApi.state().then((snapshot) => {
|
|
1680
|
+
if (seq !== stateSeq) return;
|
|
1524
1681
|
setSnapshot(snapshot);
|
|
1525
1682
|
}, () => {
|
|
1683
|
+
if (seq !== stateSeq) return;
|
|
1526
1684
|
setState("error", "pet.state transport error");
|
|
1527
1685
|
});
|
|
1528
1686
|
};
|
|
@@ -1550,9 +1708,15 @@ window.__ModuleLoader__.load({
|
|
|
1550
1708
|
document.removeEventListener("visibilitychange", onVisibility);
|
|
1551
1709
|
};
|
|
1552
1710
|
}, "pet: poll");
|
|
1711
|
+
const sessions = ctx.sessions;
|
|
1712
|
+
const openSession = (sessionId) => {
|
|
1713
|
+
if (sessions.list.getSnapshot().byId[sessionId] === void 0) return;
|
|
1714
|
+
sessions.open(sessionId);
|
|
1715
|
+
};
|
|
1553
1716
|
const injected = () => ({
|
|
1554
1717
|
store: petStore,
|
|
1555
1718
|
ensure: pollNow,
|
|
1719
|
+
openSession,
|
|
1556
1720
|
pet: () => {
|
|
1557
1721
|
petApi.interact("pet").then((result) => {
|
|
1558
1722
|
setFeedback({
|