@linxin666/dsh-pet 0.3.0 → 0.3.1
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/lib/client.js +39 -20
- package/lib/client.js.map +1 -1
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +14 -5
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +25 -13
- package/package.json +1 -1
- package/src/client/PetSprite.test.tsx +31 -0
- package/src/client/PetSprite.tsx +14 -5
- package/src/client/index.ts +22 -12
- package/src/client/pet-css.test.ts +9 -0
- package/src/client/pet.module.css +3 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PetSprite.d.ts","sourceRoot":"","sources":["../../../src/client/PetSprite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAkE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnH,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;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAIjD,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,4DAA4D;IAC5D,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;AAgGD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"PetSprite.d.ts","sourceRoot":"","sources":["../../../src/client/PetSprite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAkE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnH,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;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAIjD,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,4DAA4D;IAC5D,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;AAgGD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,WAAW,CA0iB5D"}
|
|
@@ -219,8 +219,9 @@ export function PetSprite(props) {
|
|
|
219
219
|
// blank while the loop heat-up runs.
|
|
220
220
|
const leadCol = track.frames[0];
|
|
221
221
|
const lead = framePosition(cell, row, leadCol, scaleRef.current);
|
|
222
|
+
let lastPosStr = lead.x + 'px ' + lead.y + 'px';
|
|
222
223
|
if (spriteRef.current !== null) {
|
|
223
|
-
spriteRef.current.style.backgroundPosition =
|
|
224
|
+
spriteRef.current.style.backgroundPosition = lastPosStr;
|
|
224
225
|
}
|
|
225
226
|
if (reduceMotion)
|
|
226
227
|
return;
|
|
@@ -237,8 +238,12 @@ export function PetSprite(props) {
|
|
|
237
238
|
const currentTrack = trimTrack(tracks[current.animation], rows[currentRow] ?? tracks[current.animation].frames.length);
|
|
238
239
|
const col = currentTrack.frames[current.frameIndex];
|
|
239
240
|
const pos = framePosition(cell, currentRow, col, scaleRef.current);
|
|
240
|
-
|
|
241
|
-
|
|
241
|
+
const posStr = pos.x + 'px ' + pos.y + 'px';
|
|
242
|
+
if (posStr !== lastPosStr) {
|
|
243
|
+
lastPosStr = posStr;
|
|
244
|
+
if (spriteRef.current !== null) {
|
|
245
|
+
spriteRef.current.style.backgroundPosition = posStr;
|
|
246
|
+
}
|
|
242
247
|
}
|
|
243
248
|
raf = requestAnimationFrame(tick);
|
|
244
249
|
return;
|
|
@@ -269,8 +274,12 @@ export function PetSprite(props) {
|
|
|
269
274
|
}
|
|
270
275
|
const col = track.frames[st.index];
|
|
271
276
|
const pos = framePosition(cell, row, col, scaleRef.current);
|
|
272
|
-
|
|
273
|
-
|
|
277
|
+
const posStr = pos.x + 'px ' + pos.y + 'px';
|
|
278
|
+
if (posStr !== lastPosStr) {
|
|
279
|
+
lastPosStr = posStr;
|
|
280
|
+
if (spriteRef.current !== null) {
|
|
281
|
+
spriteRef.current.style.backgroundPosition = posStr;
|
|
282
|
+
}
|
|
274
283
|
}
|
|
275
284
|
raf = requestAnimationFrame(tick);
|
|
276
285
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAwB,aAAa,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;AAgEnI,wEAAwE;AACxE,eAAO,MAAM,MAAM,UAA2E,CAAA;AAE9F,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,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,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,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAwB,aAAa,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;AAgEnI,wEAAwE;AACxE,eAAO,MAAM,MAAM,UAA2E,CAAA;AAE9F,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,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,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,CAoR9C"}
|
|
@@ -56,7 +56,14 @@ export const inject = ['slots', 'locale', 'connection', 'settingsScope', 'remote
|
|
|
56
56
|
* @param ctx - client root context.
|
|
57
57
|
*/
|
|
58
58
|
export function apply(ctx) {
|
|
59
|
-
ctx.effect(() =>
|
|
59
|
+
ctx.effect(() => {
|
|
60
|
+
try {
|
|
61
|
+
return ctx.locale.register(NS, { zh, en });
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return () => { };
|
|
65
|
+
}
|
|
66
|
+
}, 'pet: dictionaries');
|
|
60
67
|
// Built-in renderers dispatch through the plugin-wide registry (pet-center
|
|
61
68
|
// M3). Registration is idempotent (id wins), so re-applies stay clean.
|
|
62
69
|
defaultPetRendererRegistry.register(live2dRenderer);
|
|
@@ -78,18 +85,23 @@ export function apply(ctx) {
|
|
|
78
85
|
// The section entry owns the controller: unregistering it (fiber disposal,
|
|
79
86
|
// hot reload) releases the scope subscription through petSettings.dispose.
|
|
80
87
|
ctx.slots.inject('settings.section', () => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
try {
|
|
89
|
+
const unregister = ctx.slots.register({
|
|
90
|
+
name: 'settings.section',
|
|
91
|
+
id: 'pet',
|
|
92
|
+
order: 130,
|
|
93
|
+
label: () => ctx.locale.bind('pet')('settings.title'),
|
|
94
|
+
locale: 'pet',
|
|
95
|
+
inject: () => petSettings.inject(),
|
|
96
|
+
}, PetSettingsSection);
|
|
97
|
+
return () => {
|
|
98
|
+
unregister();
|
|
99
|
+
petSettings.dispose();
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return () => { };
|
|
104
|
+
}
|
|
93
105
|
});
|
|
94
106
|
// The global pet entry, its store, and the poll loop live while the plugin
|
|
95
107
|
// is enabled; toggling the setting off hides the pet and stops polling.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-pet",
|
|
3
3
|
"description": "Multi-pet companion plugin for the dsh web GUI: a registry-driven floating pet that reacts to model activity, with per-pet naming, petting/feeding interactions and an affinity score",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -576,6 +576,37 @@ describe('PetSprite definition-driven render', () => {
|
|
|
576
576
|
act(() => { nextFrame?.(1_500) })
|
|
577
577
|
expect(sprite.style.backgroundPosition).toBe('0px -960px')
|
|
578
578
|
})
|
|
579
|
+
|
|
580
|
+
it('skips redundant backgroundPosition style assignments when frame coordinates do not change (issue #1013)', () => {
|
|
581
|
+
vi.spyOn(window, 'matchMedia').mockReturnValue({
|
|
582
|
+
matches: false,
|
|
583
|
+
media: '(prefers-reduced-motion: reduce)',
|
|
584
|
+
onchange: null,
|
|
585
|
+
addEventListener: () => {},
|
|
586
|
+
removeEventListener: () => {},
|
|
587
|
+
addListener: () => {},
|
|
588
|
+
removeListener: () => {},
|
|
589
|
+
dispatchEvent: () => false,
|
|
590
|
+
})
|
|
591
|
+
vi.spyOn(performance, 'now').mockReturnValue(0)
|
|
592
|
+
let nextFrame: FrameRequestCallback | undefined
|
|
593
|
+
vi.spyOn(window, 'requestAnimationFrame').mockImplementation(callback => {
|
|
594
|
+
nextFrame = callback
|
|
595
|
+
return 1
|
|
596
|
+
})
|
|
597
|
+
vi.spyOn(window, 'cancelAnimationFrame').mockImplementation(() => {})
|
|
598
|
+
renderPet({
|
|
599
|
+
snapshot: { ...snapshot, animation: 'idle', phase: 'idle' },
|
|
600
|
+
})
|
|
601
|
+
const sprite = screen.getByRole('button', { name: '鲸鱼娘' })
|
|
602
|
+
const styleSetterSpy = vi.spyOn(sprite.style, 'backgroundPosition', 'set')
|
|
603
|
+
// Idle frame 0 duration is 400ms. An intermediate tick at 16ms does not advance the frame.
|
|
604
|
+
act(() => { nextFrame?.(16) })
|
|
605
|
+
expect(styleSetterSpy).not.toHaveBeenCalled()
|
|
606
|
+
// A tick past 400ms advances to frame 1 and should set backgroundPosition once.
|
|
607
|
+
act(() => { nextFrame?.(410) })
|
|
608
|
+
expect(styleSetterSpy).toHaveBeenCalledTimes(1)
|
|
609
|
+
})
|
|
579
610
|
})
|
|
580
611
|
|
|
581
612
|
describe('PetSprite panel chrome from the voice pack (pet-center M4)', () => {
|
package/src/client/PetSprite.tsx
CHANGED
|
@@ -268,8 +268,9 @@ export function PetSprite(props: PetSpriteProps): ReactPortal {
|
|
|
268
268
|
// blank while the loop heat-up runs.
|
|
269
269
|
const leadCol = track.frames[0]!
|
|
270
270
|
const lead = framePosition(cell, row, leadCol, scaleRef.current)
|
|
271
|
+
let lastPosStr = lead.x + 'px ' + lead.y + 'px'
|
|
271
272
|
if (spriteRef.current !== null) {
|
|
272
|
-
spriteRef.current.style.backgroundPosition =
|
|
273
|
+
spriteRef.current.style.backgroundPosition = lastPosStr
|
|
273
274
|
}
|
|
274
275
|
if (reduceMotion) return
|
|
275
276
|
let raf = 0
|
|
@@ -288,8 +289,12 @@ export function PetSprite(props: PetSpriteProps): ReactPortal {
|
|
|
288
289
|
)
|
|
289
290
|
const col = currentTrack.frames[current.frameIndex]!
|
|
290
291
|
const pos = framePosition(cell, currentRow, col, scaleRef.current)
|
|
291
|
-
|
|
292
|
-
|
|
292
|
+
const posStr = pos.x + 'px ' + pos.y + 'px'
|
|
293
|
+
if (posStr !== lastPosStr) {
|
|
294
|
+
lastPosStr = posStr
|
|
295
|
+
if (spriteRef.current !== null) {
|
|
296
|
+
spriteRef.current.style.backgroundPosition = posStr
|
|
297
|
+
}
|
|
293
298
|
}
|
|
294
299
|
raf = requestAnimationFrame(tick)
|
|
295
300
|
return
|
|
@@ -319,8 +324,12 @@ export function PetSprite(props: PetSpriteProps): ReactPortal {
|
|
|
319
324
|
}
|
|
320
325
|
const col = track.frames[st.index]!
|
|
321
326
|
const pos = framePosition(cell, row, col, scaleRef.current)
|
|
322
|
-
|
|
323
|
-
|
|
327
|
+
const posStr = pos.x + 'px ' + pos.y + 'px'
|
|
328
|
+
if (posStr !== lastPosStr) {
|
|
329
|
+
lastPosStr = posStr
|
|
330
|
+
if (spriteRef.current !== null) {
|
|
331
|
+
spriteRef.current.style.backgroundPosition = posStr
|
|
332
|
+
}
|
|
324
333
|
}
|
|
325
334
|
raf = requestAnimationFrame(tick)
|
|
326
335
|
}
|
package/src/client/index.ts
CHANGED
|
@@ -104,7 +104,13 @@ declare module '@deepseek-ai/cordis' {
|
|
|
104
104
|
* @param ctx - client root context.
|
|
105
105
|
*/
|
|
106
106
|
export function apply(ctx: ClientContext): void {
|
|
107
|
-
ctx.effect(() =>
|
|
107
|
+
ctx.effect(() => {
|
|
108
|
+
try {
|
|
109
|
+
return ctx.locale.register(NS, { zh, en })
|
|
110
|
+
} catch {
|
|
111
|
+
return () => {}
|
|
112
|
+
}
|
|
113
|
+
}, 'pet: dictionaries')
|
|
108
114
|
|
|
109
115
|
// Built-in renderers dispatch through the plugin-wide registry (pet-center
|
|
110
116
|
// M3). Registration is idempotent (id wins), so re-applies stay clean.
|
|
@@ -129,17 +135,21 @@ export function apply(ctx: ClientContext): void {
|
|
|
129
135
|
// The section entry owns the controller: unregistering it (fiber disposal,
|
|
130
136
|
// hot reload) releases the scope subscription through petSettings.dispose.
|
|
131
137
|
ctx.slots.inject('settings.section', () => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
try {
|
|
139
|
+
const unregister = ctx.slots.register({
|
|
140
|
+
name: 'settings.section',
|
|
141
|
+
id: 'pet',
|
|
142
|
+
order: 130,
|
|
143
|
+
label: () => ctx.locale.bind('pet')('settings.title'),
|
|
144
|
+
locale: 'pet',
|
|
145
|
+
inject: () => petSettings.inject(),
|
|
146
|
+
}, PetSettingsSection)
|
|
147
|
+
return () => {
|
|
148
|
+
unregister()
|
|
149
|
+
petSettings.dispose()
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
return () => {}
|
|
143
153
|
}
|
|
144
154
|
})
|
|
145
155
|
|
|
@@ -51,4 +51,13 @@ describe('pet hover panel css', () => {
|
|
|
51
51
|
expect(bridge).toContain('top: 100%')
|
|
52
52
|
expect(bridge).toContain('bottom: auto')
|
|
53
53
|
})
|
|
54
|
+
|
|
55
|
+
it('isolates the pet float container and sprite on their own compositor layer (issue #1013)', () => {
|
|
56
|
+
const float = css.match(/\.float\s*\{([^}]*)\}/)?.[1] ?? ''
|
|
57
|
+
expect(float).toContain('will-change: transform')
|
|
58
|
+
expect(float).toContain('contain: layout style')
|
|
59
|
+
|
|
60
|
+
const sprite = css.match(/\.sprite\s*\{([^}]*)\}/)?.[1] ?? ''
|
|
61
|
+
expect(sprite).toContain('contain: paint')
|
|
62
|
+
})
|
|
54
63
|
})
|