dsh-pet 0.1.5 → 0.1.6
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/assets/config.jsonc +15 -10
- package/lib/client.js +580 -43
- package/lib/index.js +93 -1
- package/package.json +1 -1
- package/src/client/app.ts +76 -40
- package/src/client/config.ts +46 -1
- package/src/client/pickers.ts +3 -1
- package/src/client/settings.ts +325 -0
- package/src/host/index.ts +87 -2
package/assets/config.jsonc
CHANGED
|
@@ -2,16 +2,21 @@
|
|
|
2
2
|
// 说明:浏览器端启动时通过 /pet/config.jsonc 拉取;任何字段缺失/写错都会
|
|
3
3
|
// 回退到 client.js 的代码默认值,不会导致插件崩溃。
|
|
4
4
|
{
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
// corner:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
// ===================== 默认宠物列表(多开) =====================
|
|
6
|
+
// pets 数组定义默认宠物;「设置页 → 添加宠物」的默认模板取 pets[0](main)。
|
|
7
|
+
// 每个宠物独立:id(标识)、size(宽度 px,高度自动 = size × 9/16)、
|
|
8
|
+
// position(corner: 四角之一;marginX/marginY: 距所属角落偏移 px)
|
|
9
|
+
"pets": [
|
|
10
|
+
{
|
|
11
|
+
"id": "main",
|
|
12
|
+
"size": 462,
|
|
13
|
+
"position": {
|
|
14
|
+
"corner": "top-right",
|
|
15
|
+
"marginX": 24,
|
|
16
|
+
"marginY": 100
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
],
|
|
15
20
|
|
|
16
21
|
// ===================== 动作动画池配置 =====================
|
|
17
22
|
"animations": {
|
package/lib/client.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
//#region src/client/pickers.ts
|
|
3
3
|
const pick = (pool, exclude) => {
|
|
4
4
|
const entries = exclude ? pool.filter((n) => n !== exclude) : pool;
|
|
5
|
-
|
|
5
|
+
const src = entries.length ? entries : pool;
|
|
6
|
+
return src[Math.floor(Math.random() * src.length)];
|
|
6
7
|
};
|
|
7
8
|
const randomBetween = (min, max) => Math.floor(min + Math.random() * (max - min));
|
|
8
9
|
const pickWeightedCategory = (categories, facing) => {
|
|
@@ -19,6 +20,470 @@ const pickWeightedCategory = (categories, facing) => {
|
|
|
19
20
|
return eligible[eligible.length - 1];
|
|
20
21
|
};
|
|
21
22
|
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/client/settings.ts
|
|
25
|
+
const DEFAULT_PETS = [{
|
|
26
|
+
id: "main",
|
|
27
|
+
size: 462,
|
|
28
|
+
corner: "top-right",
|
|
29
|
+
marginX: 24,
|
|
30
|
+
marginY: 100
|
|
31
|
+
}];
|
|
32
|
+
const petBridge = {
|
|
33
|
+
current: null,
|
|
34
|
+
sync: null,
|
|
35
|
+
template: null
|
|
36
|
+
};
|
|
37
|
+
const NS = "pet.config";
|
|
38
|
+
const zh = {
|
|
39
|
+
nav: "桌宠配置",
|
|
40
|
+
intro: "管理多个桌宠:每个宠物可独立设置大小与位置(保存后即时生效)。",
|
|
41
|
+
petsLabel: "宠物列表",
|
|
42
|
+
add: "添加宠物",
|
|
43
|
+
remove: "删除",
|
|
44
|
+
atLeastOne: "至少保留一个宠物。",
|
|
45
|
+
emptyPets: "暂无宠物,点击「添加宠物」创建。",
|
|
46
|
+
sizeLabel: "大小(宽度 px)",
|
|
47
|
+
sizeHint: "高度自动 = 宽度 × 9/16。",
|
|
48
|
+
cornerLabel: "位置",
|
|
49
|
+
"corner.top-left": "左上角",
|
|
50
|
+
"corner.top-right": "右上角",
|
|
51
|
+
"corner.bottom-left": "左下角",
|
|
52
|
+
"corner.bottom-right": "右下角",
|
|
53
|
+
marginX: "水平偏移",
|
|
54
|
+
marginY: "垂直偏移",
|
|
55
|
+
save: "保存",
|
|
56
|
+
reset: "恢复默认",
|
|
57
|
+
saved: "已保存,桌宠即时生效。",
|
|
58
|
+
loadError: "加载配置失败",
|
|
59
|
+
invalid: "请检查输入:大小需为正数,边距可为任意数字。",
|
|
60
|
+
busy: "保存中…"
|
|
61
|
+
};
|
|
62
|
+
const en = {
|
|
63
|
+
nav: "Pet Config",
|
|
64
|
+
intro: "Manage multiple pets: each pet has its own size and position (applies instantly after saving).",
|
|
65
|
+
petsLabel: "Pets",
|
|
66
|
+
add: "Add pet",
|
|
67
|
+
remove: "Remove",
|
|
68
|
+
atLeastOne: "Keep at least one pet.",
|
|
69
|
+
emptyPets: "No pets yet — click \"Add pet\" to create one.",
|
|
70
|
+
sizeLabel: "Size (width px)",
|
|
71
|
+
sizeHint: "Height is automatic = width × 9/16.",
|
|
72
|
+
cornerLabel: "Position",
|
|
73
|
+
"corner.top-left": "Top-left",
|
|
74
|
+
"corner.top-right": "Top-right",
|
|
75
|
+
"corner.bottom-left": "Bottom-left",
|
|
76
|
+
"corner.bottom-right": "Bottom-right",
|
|
77
|
+
marginX: "Horizontal offset",
|
|
78
|
+
marginY: "Vertical offset",
|
|
79
|
+
save: "Save",
|
|
80
|
+
reset: "Reset to default",
|
|
81
|
+
saved: "Saved — the pets updated instantly.",
|
|
82
|
+
loadError: "Failed to load config",
|
|
83
|
+
invalid: "Check your input: size must be positive; margins can be any number.",
|
|
84
|
+
busy: "Saving…"
|
|
85
|
+
};
|
|
86
|
+
function makePetConfigSection(rt) {
|
|
87
|
+
const { h, useState, t } = rt;
|
|
88
|
+
const CORNERS$1 = [
|
|
89
|
+
"top-left",
|
|
90
|
+
"top-right",
|
|
91
|
+
"bottom-left",
|
|
92
|
+
"bottom-right"
|
|
93
|
+
];
|
|
94
|
+
const cornerLabel = (c) => t("corner." + c);
|
|
95
|
+
const inputStyle = {
|
|
96
|
+
boxSizing: "border-box",
|
|
97
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
98
|
+
borderRadius: "8px",
|
|
99
|
+
background: "var(--dsw-alias-bg-layer-1)",
|
|
100
|
+
color: "var(--dsw-alias-label-primary)",
|
|
101
|
+
padding: "5px 10px",
|
|
102
|
+
fontSize: "13px",
|
|
103
|
+
minHeight: "28px",
|
|
104
|
+
outline: "none"
|
|
105
|
+
};
|
|
106
|
+
const stripJsonc$1 = (src) => src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^\\:])\/\/.*$/gm, "$1").trim();
|
|
107
|
+
/** 生成一个未占用的宠物 id(pet-2、pet-3…) */
|
|
108
|
+
const nextId = (list) => {
|
|
109
|
+
let n = 2;
|
|
110
|
+
for (;; n++) {
|
|
111
|
+
const id = "pet-" + n;
|
|
112
|
+
if (!list.some((p) => p.id === id)) return id;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const normalizePet$1 = (p) => {
|
|
116
|
+
const pos = p && p.position && typeof p.position === "object" ? p.position : {};
|
|
117
|
+
return {
|
|
118
|
+
id: String(p && p.id ? p.id : "main"),
|
|
119
|
+
size: Number(p && p.size) > 0 ? Number(p.size) : DEFAULT_PETS[0].size,
|
|
120
|
+
corner: CORNERS$1.includes(pos.corner) ? pos.corner : DEFAULT_PETS[0].corner,
|
|
121
|
+
marginX: Number.isFinite(Number(pos.marginX)) ? Number(pos.marginX) : DEFAULT_PETS[0].marginX,
|
|
122
|
+
marginY: Number.isFinite(Number(pos.marginY)) ? Number(pos.marginY) : DEFAULT_PETS[0].marginY
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
/** 从 config.jsonc 对象提取默认宠物列表:必须为 pets 数组;缺失/为空回落代码兜底 DEFAULT_PETS */
|
|
126
|
+
const defaultsFrom = (obj) => {
|
|
127
|
+
const arr = obj && Array.isArray(obj.pets) ? obj.pets.filter((p) => p && p.id).map(normalizePet$1) : [];
|
|
128
|
+
return arr.length ? arr : DEFAULT_PETS.map((p) => ({ ...p }));
|
|
129
|
+
};
|
|
130
|
+
return function PetConfigSection() {
|
|
131
|
+
const initPets = petBridge.current ?? DEFAULT_PETS;
|
|
132
|
+
const [pets, setPets] = useState(initPets.map((p) => ({ ...p })));
|
|
133
|
+
const [selId, setSelId] = useState(initPets[0]?.id ?? "");
|
|
134
|
+
const [busy, setBusy] = useState(false);
|
|
135
|
+
const [msg, setMsg] = useState({
|
|
136
|
+
kind: "",
|
|
137
|
+
text: ""
|
|
138
|
+
});
|
|
139
|
+
const sel = pets.find((p) => p.id === selId) ?? pets[0] ?? null;
|
|
140
|
+
const updateSel = (patch) => setPets((list) => list.map((p) => p.id === (sel?.id ?? "") ? {
|
|
141
|
+
...p,
|
|
142
|
+
...patch
|
|
143
|
+
} : p));
|
|
144
|
+
const validated = () => {
|
|
145
|
+
for (const p of pets) if (!Number.isFinite(p.size) || p.size <= 0 || !Number.isFinite(p.marginX) || !Number.isFinite(p.marginY)) {
|
|
146
|
+
setMsg({
|
|
147
|
+
kind: "err",
|
|
148
|
+
text: t("invalid")
|
|
149
|
+
});
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
return pets;
|
|
153
|
+
};
|
|
154
|
+
const save = async () => {
|
|
155
|
+
const list = validated();
|
|
156
|
+
if (!list) return;
|
|
157
|
+
setBusy(true);
|
|
158
|
+
setMsg({
|
|
159
|
+
kind: "",
|
|
160
|
+
text: ""
|
|
161
|
+
});
|
|
162
|
+
try {
|
|
163
|
+
const res = await fetch("/pet/config", {
|
|
164
|
+
method: "PUT",
|
|
165
|
+
headers: { "content-type": "application/json" },
|
|
166
|
+
body: JSON.stringify({ pets: list.map((p) => ({
|
|
167
|
+
id: p.id,
|
|
168
|
+
size: p.size,
|
|
169
|
+
position: {
|
|
170
|
+
corner: p.corner,
|
|
171
|
+
marginX: p.marginX,
|
|
172
|
+
marginY: p.marginY
|
|
173
|
+
}
|
|
174
|
+
})) })
|
|
175
|
+
});
|
|
176
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
177
|
+
petBridge.current = list;
|
|
178
|
+
petBridge.sync?.(list);
|
|
179
|
+
setMsg({
|
|
180
|
+
kind: "ok",
|
|
181
|
+
text: t("saved")
|
|
182
|
+
});
|
|
183
|
+
} catch {
|
|
184
|
+
setMsg({
|
|
185
|
+
kind: "err",
|
|
186
|
+
text: t("loadError")
|
|
187
|
+
});
|
|
188
|
+
} finally {
|
|
189
|
+
setBusy(false);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const reset = async () => {
|
|
193
|
+
setBusy(true);
|
|
194
|
+
setMsg({
|
|
195
|
+
kind: "",
|
|
196
|
+
text: ""
|
|
197
|
+
});
|
|
198
|
+
try {
|
|
199
|
+
await fetch("/pet/config", { method: "DELETE" });
|
|
200
|
+
const defRes = await fetch("/pet/config.jsonc");
|
|
201
|
+
const obj = JSON.parse(stripJsonc$1(await defRes.text()));
|
|
202
|
+
const defs = defaultsFrom(obj);
|
|
203
|
+
setPets(defs.map((p) => ({ ...p })));
|
|
204
|
+
setSelId(defs[0]?.id ?? "");
|
|
205
|
+
petBridge.current = defs;
|
|
206
|
+
petBridge.sync?.(defs);
|
|
207
|
+
setMsg({
|
|
208
|
+
kind: "ok",
|
|
209
|
+
text: t("saved")
|
|
210
|
+
});
|
|
211
|
+
} catch {
|
|
212
|
+
setMsg({
|
|
213
|
+
kind: "err",
|
|
214
|
+
text: t("loadError")
|
|
215
|
+
});
|
|
216
|
+
} finally {
|
|
217
|
+
setBusy(false);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const addPet = () => {
|
|
221
|
+
const id = nextId(pets);
|
|
222
|
+
const tpl = petBridge.template ?? DEFAULT_PETS[0];
|
|
223
|
+
const np = {
|
|
224
|
+
id,
|
|
225
|
+
size: tpl.size,
|
|
226
|
+
corner: tpl.corner,
|
|
227
|
+
marginX: tpl.marginX,
|
|
228
|
+
marginY: tpl.marginY
|
|
229
|
+
};
|
|
230
|
+
setPets((list) => [...list, np]);
|
|
231
|
+
setSelId(id);
|
|
232
|
+
};
|
|
233
|
+
const removeSel = () => {
|
|
234
|
+
if (pets.length <= 1) {
|
|
235
|
+
setMsg({
|
|
236
|
+
kind: "err",
|
|
237
|
+
text: t("atLeastOne")
|
|
238
|
+
});
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const list = pets.filter((p) => p.id !== (sel?.id ?? ""));
|
|
242
|
+
setPets(list);
|
|
243
|
+
setSelId(list[0].id);
|
|
244
|
+
};
|
|
245
|
+
const field = (key, value, setter, width) => h("input", {
|
|
246
|
+
type: "number",
|
|
247
|
+
step: key === "size" ? "10" : "1",
|
|
248
|
+
min: key === "size" ? "120" : "",
|
|
249
|
+
value: String(value),
|
|
250
|
+
disabled: busy || !sel,
|
|
251
|
+
onChange: (e) => setter(Number(e.target.value)),
|
|
252
|
+
style: {
|
|
253
|
+
width,
|
|
254
|
+
...inputStyle
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
return h("section", {
|
|
258
|
+
style: {
|
|
259
|
+
maxWidth: "720px",
|
|
260
|
+
color: "var(--dsw-alias-label-primary)",
|
|
261
|
+
display: "flex",
|
|
262
|
+
flexDirection: "column",
|
|
263
|
+
gap: "6px"
|
|
264
|
+
},
|
|
265
|
+
children: [
|
|
266
|
+
h("h2", {
|
|
267
|
+
style: {
|
|
268
|
+
margin: 0,
|
|
269
|
+
fontSize: "16px",
|
|
270
|
+
fontWeight: 500,
|
|
271
|
+
lineHeight: "24px"
|
|
272
|
+
},
|
|
273
|
+
children: t("nav")
|
|
274
|
+
}),
|
|
275
|
+
h("p", {
|
|
276
|
+
style: {
|
|
277
|
+
margin: 0,
|
|
278
|
+
fontSize: "14px",
|
|
279
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
280
|
+
lineHeight: "22px"
|
|
281
|
+
},
|
|
282
|
+
children: t("intro")
|
|
283
|
+
}),
|
|
284
|
+
h("div", {
|
|
285
|
+
style: {
|
|
286
|
+
display: "flex",
|
|
287
|
+
gap: "8px",
|
|
288
|
+
flexWrap: "wrap",
|
|
289
|
+
alignItems: "center",
|
|
290
|
+
marginTop: "4px"
|
|
291
|
+
},
|
|
292
|
+
children: [
|
|
293
|
+
h("span", {
|
|
294
|
+
style: {
|
|
295
|
+
fontSize: "12px",
|
|
296
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
297
|
+
},
|
|
298
|
+
children: t("petsLabel")
|
|
299
|
+
}),
|
|
300
|
+
...pets.map((p) => h("button", {
|
|
301
|
+
key: p.id,
|
|
302
|
+
type: "button",
|
|
303
|
+
onClick: () => setSelId(p.id),
|
|
304
|
+
style: {
|
|
305
|
+
border: "1px solid " + (p.id === (sel?.id ?? "") ? "var(--dsw-alias-state-business-primary)" : "var(--dsw-alias-border-l2)"),
|
|
306
|
+
background: p.id === (sel?.id ?? "") ? "var(--dsw-alias-interactive-bg-active)" : "transparent",
|
|
307
|
+
color: "var(--dsw-alias-label-primary)",
|
|
308
|
+
borderRadius: "8px",
|
|
309
|
+
padding: "4px 12px",
|
|
310
|
+
fontSize: "13px",
|
|
311
|
+
cursor: "pointer"
|
|
312
|
+
},
|
|
313
|
+
children: p.id + " (" + p.size + "px)"
|
|
314
|
+
})),
|
|
315
|
+
h("button", {
|
|
316
|
+
type: "button",
|
|
317
|
+
onClick: addPet,
|
|
318
|
+
disabled: busy,
|
|
319
|
+
style: {
|
|
320
|
+
border: "1px dashed var(--dsw-alias-border-l2)",
|
|
321
|
+
background: "transparent",
|
|
322
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
323
|
+
borderRadius: "8px",
|
|
324
|
+
padding: "4px 12px",
|
|
325
|
+
fontSize: "13px",
|
|
326
|
+
cursor: "pointer"
|
|
327
|
+
},
|
|
328
|
+
children: "+ " + t("add")
|
|
329
|
+
})
|
|
330
|
+
]
|
|
331
|
+
}),
|
|
332
|
+
sel ? h("div", {
|
|
333
|
+
style: {
|
|
334
|
+
display: "flex",
|
|
335
|
+
gap: "16px",
|
|
336
|
+
flexWrap: "wrap",
|
|
337
|
+
marginTop: "8px",
|
|
338
|
+
padding: "12px 14px",
|
|
339
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
340
|
+
borderRadius: "12px"
|
|
341
|
+
},
|
|
342
|
+
children: [
|
|
343
|
+
h("label", {
|
|
344
|
+
style: {
|
|
345
|
+
display: "flex",
|
|
346
|
+
flexDirection: "column",
|
|
347
|
+
gap: "4px",
|
|
348
|
+
fontSize: "12px",
|
|
349
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
350
|
+
},
|
|
351
|
+
children: [
|
|
352
|
+
t("sizeLabel"),
|
|
353
|
+
field("size", sel.size, (v) => updateSel({ size: v }), "150px"),
|
|
354
|
+
h("span", {
|
|
355
|
+
style: {
|
|
356
|
+
fontSize: "11px",
|
|
357
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
358
|
+
},
|
|
359
|
+
children: t("sizeHint")
|
|
360
|
+
})
|
|
361
|
+
]
|
|
362
|
+
}),
|
|
363
|
+
h("label", {
|
|
364
|
+
style: {
|
|
365
|
+
display: "flex",
|
|
366
|
+
flexDirection: "column",
|
|
367
|
+
gap: "4px",
|
|
368
|
+
fontSize: "12px",
|
|
369
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
370
|
+
},
|
|
371
|
+
children: [t("cornerLabel"), h("select", {
|
|
372
|
+
value: sel.corner,
|
|
373
|
+
disabled: busy,
|
|
374
|
+
onChange: (e) => updateSel({ corner: e.target.value }),
|
|
375
|
+
style: {
|
|
376
|
+
width: "160px",
|
|
377
|
+
...inputStyle
|
|
378
|
+
},
|
|
379
|
+
children: CORNERS$1.map((c) => h("option", {
|
|
380
|
+
key: c,
|
|
381
|
+
value: c,
|
|
382
|
+
children: cornerLabel(c)
|
|
383
|
+
}))
|
|
384
|
+
})]
|
|
385
|
+
}),
|
|
386
|
+
h("label", {
|
|
387
|
+
style: {
|
|
388
|
+
display: "flex",
|
|
389
|
+
flexDirection: "column",
|
|
390
|
+
gap: "4px",
|
|
391
|
+
fontSize: "12px",
|
|
392
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
393
|
+
},
|
|
394
|
+
children: [t("marginX"), field("marginX", sel.marginX, (v) => updateSel({ marginX: v }), "120px")]
|
|
395
|
+
}),
|
|
396
|
+
h("label", {
|
|
397
|
+
style: {
|
|
398
|
+
display: "flex",
|
|
399
|
+
flexDirection: "column",
|
|
400
|
+
gap: "4px",
|
|
401
|
+
fontSize: "12px",
|
|
402
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
403
|
+
},
|
|
404
|
+
children: [t("marginY"), field("marginY", sel.marginY, (v) => updateSel({ marginY: v }), "120px")]
|
|
405
|
+
}),
|
|
406
|
+
h("button", {
|
|
407
|
+
type: "button",
|
|
408
|
+
onClick: removeSel,
|
|
409
|
+
disabled: busy,
|
|
410
|
+
title: t("remove"),
|
|
411
|
+
style: {
|
|
412
|
+
alignSelf: "flex-end",
|
|
413
|
+
border: "1px solid var(--dsw-alias-state-error-secondary)",
|
|
414
|
+
background: "transparent",
|
|
415
|
+
color: "var(--dsw-alias-state-error-primary)",
|
|
416
|
+
borderRadius: "8px",
|
|
417
|
+
padding: "4px 12px",
|
|
418
|
+
fontSize: "12px",
|
|
419
|
+
cursor: "pointer"
|
|
420
|
+
},
|
|
421
|
+
children: t("remove")
|
|
422
|
+
})
|
|
423
|
+
]
|
|
424
|
+
}) : h("p", {
|
|
425
|
+
style: {
|
|
426
|
+
margin: 0,
|
|
427
|
+
fontSize: "13px",
|
|
428
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
429
|
+
},
|
|
430
|
+
children: t("emptyPets")
|
|
431
|
+
}),
|
|
432
|
+
h("div", {
|
|
433
|
+
style: {
|
|
434
|
+
display: "flex",
|
|
435
|
+
gap: "8px",
|
|
436
|
+
alignItems: "center",
|
|
437
|
+
marginTop: "4px"
|
|
438
|
+
},
|
|
439
|
+
children: [
|
|
440
|
+
h("button", {
|
|
441
|
+
type: "button",
|
|
442
|
+
disabled: busy,
|
|
443
|
+
onClick: save,
|
|
444
|
+
style: {
|
|
445
|
+
border: "1px solid var(--dsw-alias-button-info-fill)",
|
|
446
|
+
background: "var(--dsw-alias-button-info-fill)",
|
|
447
|
+
color: "#fff",
|
|
448
|
+
borderRadius: "8px",
|
|
449
|
+
padding: "4px 14px",
|
|
450
|
+
fontSize: "12px",
|
|
451
|
+
cursor: "pointer",
|
|
452
|
+
opacity: busy ? .5 : 1
|
|
453
|
+
},
|
|
454
|
+
children: t("save")
|
|
455
|
+
}),
|
|
456
|
+
h("button", {
|
|
457
|
+
type: "button",
|
|
458
|
+
disabled: busy,
|
|
459
|
+
onClick: reset,
|
|
460
|
+
style: {
|
|
461
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
462
|
+
background: "transparent",
|
|
463
|
+
color: "var(--dsw-alias-label-primary)",
|
|
464
|
+
borderRadius: "8px",
|
|
465
|
+
padding: "4px 14px",
|
|
466
|
+
fontSize: "12px",
|
|
467
|
+
cursor: "pointer",
|
|
468
|
+
opacity: busy ? .5 : 1
|
|
469
|
+
},
|
|
470
|
+
children: t("reset")
|
|
471
|
+
}),
|
|
472
|
+
msg.text ? h("span", {
|
|
473
|
+
style: {
|
|
474
|
+
fontSize: "12px",
|
|
475
|
+
color: msg.kind === "err" ? "var(--dsw-alias-state-error-primary)" : "var(--dsw-alias-state-ok-primary)",
|
|
476
|
+
marginLeft: "4px"
|
|
477
|
+
},
|
|
478
|
+
children: msg.text
|
|
479
|
+
}) : null
|
|
480
|
+
]
|
|
481
|
+
})
|
|
482
|
+
]
|
|
483
|
+
});
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
22
487
|
//#endregion
|
|
23
488
|
//#region src/client/config.ts
|
|
24
489
|
const FALLBACK_IDLE = "待机呼吸休闲";
|
|
@@ -97,6 +562,43 @@ const buildAnim = (obj) => {
|
|
|
97
562
|
}
|
|
98
563
|
};
|
|
99
564
|
};
|
|
565
|
+
const CORNERS = [
|
|
566
|
+
"top-left",
|
|
567
|
+
"top-right",
|
|
568
|
+
"bottom-left",
|
|
569
|
+
"bottom-right"
|
|
570
|
+
];
|
|
571
|
+
function normalizePet(p) {
|
|
572
|
+
const pos = p && p.position && typeof p.position === "object" ? p.position : {};
|
|
573
|
+
return {
|
|
574
|
+
id: String(p && p.id ? p.id : "main"),
|
|
575
|
+
size: Number(p && p.size) > 0 ? Number(p.size) : DEFAULT_PETS[0].size,
|
|
576
|
+
corner: CORNERS.indexOf(pos.corner) !== -1 ? pos.corner : DEFAULT_PETS[0].corner,
|
|
577
|
+
marginX: Number.isFinite(Number(pos.marginX)) ? Number(pos.marginX) : DEFAULT_PETS[0].marginX,
|
|
578
|
+
marginY: Number.isFinite(Number(pos.marginY)) ? Number(pos.marginY) : DEFAULT_PETS[0].marginY
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function extractDefaultPets(obj) {
|
|
582
|
+
const arr = obj && Array.isArray(obj.pets) ? obj.pets.filter((p) => p && p.id) : [];
|
|
583
|
+
if (!arr.length) return DEFAULT_PETS.map((p) => ({ ...p }));
|
|
584
|
+
const seen = new Set();
|
|
585
|
+
const out = [];
|
|
586
|
+
for (const p of arr) {
|
|
587
|
+
const id = String(p.id);
|
|
588
|
+
if (!seen.has(id)) {
|
|
589
|
+
seen.add(id);
|
|
590
|
+
out.push(normalizePet(p));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return out.length ? out : DEFAULT_PETS.map((p) => ({ ...p }));
|
|
594
|
+
}
|
|
595
|
+
function resolvePets(defaults, user) {
|
|
596
|
+
if (user && Array.isArray(user.pets)) {
|
|
597
|
+
const list = user.pets.filter((p) => p && p.id).map(normalizePet);
|
|
598
|
+
return list.length ? list : defaults;
|
|
599
|
+
}
|
|
600
|
+
return defaults;
|
|
601
|
+
}
|
|
100
602
|
|
|
101
603
|
//#endregion
|
|
102
604
|
//#region src/client/constants.ts
|
|
@@ -112,13 +614,6 @@ const DRAG_THRESHOLD = 5;
|
|
|
112
614
|
|
|
113
615
|
//#endregion
|
|
114
616
|
//#region src/client/app.ts
|
|
115
|
-
/** 支持的角落白名单 */
|
|
116
|
-
const CORNERS = [
|
|
117
|
-
"top-left",
|
|
118
|
-
"top-right",
|
|
119
|
-
"bottom-left",
|
|
120
|
-
"bottom-right"
|
|
121
|
-
];
|
|
122
617
|
function makeFactory() {
|
|
123
618
|
return (require) => {
|
|
124
619
|
var module = { exports: {} };
|
|
@@ -147,8 +642,8 @@ function makeFactory() {
|
|
|
147
642
|
document.head.appendChild(tag);
|
|
148
643
|
}
|
|
149
644
|
let ANIM = buildMinimalAnim();
|
|
150
|
-
function
|
|
151
|
-
const [size, setSize] = useState(
|
|
645
|
+
function PetCard({ cfg }) {
|
|
646
|
+
const [size, setSize] = useState(cfg.size);
|
|
152
647
|
const halfW = size / 2;
|
|
153
648
|
const halfH = size * 9 / 16 / 2;
|
|
154
649
|
const [anim, setAnim] = useState(ANIM.idle[0] ?? "");
|
|
@@ -156,40 +651,24 @@ function makeFactory() {
|
|
|
156
651
|
const [facing, setFacing] = useState("left");
|
|
157
652
|
const [dragging, setDragging] = useState(false);
|
|
158
653
|
const [customPos, setCustomPos] = useState(null);
|
|
159
|
-
const [corner, setCorner] = useState(
|
|
654
|
+
const [corner, setCorner] = useState(cfg.corner);
|
|
160
655
|
const [margin, setMargin] = useState({
|
|
161
|
-
x:
|
|
162
|
-
y:
|
|
656
|
+
x: cfg.marginX,
|
|
657
|
+
y: cfg.marginY
|
|
163
658
|
});
|
|
164
659
|
useEffect(() => {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const my = Number(p.marginY);
|
|
178
|
-
if (!Number.isNaN(mx)) setMargin((m) => ({
|
|
179
|
-
...m,
|
|
180
|
-
x: mx
|
|
181
|
-
}));
|
|
182
|
-
if (!Number.isNaN(my)) setMargin((m) => ({
|
|
183
|
-
...m,
|
|
184
|
-
y: my
|
|
185
|
-
}));
|
|
186
|
-
}
|
|
187
|
-
if (ANIM.idle && ANIM.idle.length) {
|
|
188
|
-
setAnim(ANIM.idle[0]);
|
|
189
|
-
setSeq((s) => s + 1);
|
|
190
|
-
}
|
|
191
|
-
}).catch(() => {});
|
|
192
|
-
}, []);
|
|
660
|
+
setSize(cfg.size);
|
|
661
|
+
setCorner(cfg.corner);
|
|
662
|
+
setMargin({
|
|
663
|
+
x: cfg.marginX,
|
|
664
|
+
y: cfg.marginY
|
|
665
|
+
});
|
|
666
|
+
}, [
|
|
667
|
+
cfg.size,
|
|
668
|
+
cfg.corner,
|
|
669
|
+
cfg.marginX,
|
|
670
|
+
cfg.marginY
|
|
671
|
+
]);
|
|
193
672
|
const [seq, setSeq] = useState(0);
|
|
194
673
|
const rootRef = useRef(null);
|
|
195
674
|
const stageRef = useRef(null);
|
|
@@ -538,15 +1017,73 @@ function makeFactory() {
|
|
|
538
1017
|
})
|
|
539
1018
|
});
|
|
540
1019
|
}
|
|
1020
|
+
function PetMulti() {
|
|
1021
|
+
const [pets, setPets] = useState([]);
|
|
1022
|
+
const [ready, setReady] = useState(false);
|
|
1023
|
+
useEffect(() => {
|
|
1024
|
+
let alive = true;
|
|
1025
|
+
(async () => {
|
|
1026
|
+
try {
|
|
1027
|
+
const r1 = await fetch("/pet/config.jsonc");
|
|
1028
|
+
if (!r1.ok) return;
|
|
1029
|
+
const obj = JSON.parse(stripJsonc(await r1.text()));
|
|
1030
|
+
ANIM = buildAnim(obj);
|
|
1031
|
+
const defaults = extractDefaultPets(obj);
|
|
1032
|
+
let user = {};
|
|
1033
|
+
try {
|
|
1034
|
+
const r2 = await fetch("/pet/config");
|
|
1035
|
+
if (r2.ok && r2.status !== 204) user = await r2.json().catch(() => ({}));
|
|
1036
|
+
} catch {}
|
|
1037
|
+
const merged = resolvePets(defaults, user);
|
|
1038
|
+
if (!alive) return;
|
|
1039
|
+
petBridge.current = merged;
|
|
1040
|
+
petBridge.template = defaults.length ? defaults[0] : null;
|
|
1041
|
+
petBridge.sync = (list) => {
|
|
1042
|
+
setPets(list);
|
|
1043
|
+
petBridge.current = list;
|
|
1044
|
+
};
|
|
1045
|
+
setPets(merged);
|
|
1046
|
+
setReady(true);
|
|
1047
|
+
} catch {}
|
|
1048
|
+
})();
|
|
1049
|
+
return () => {
|
|
1050
|
+
alive = false;
|
|
1051
|
+
petBridge.sync = null;
|
|
1052
|
+
};
|
|
1053
|
+
}, []);
|
|
1054
|
+
return ready ? pets.map((p) => h(PetCard, {
|
|
1055
|
+
key: p.id,
|
|
1056
|
+
cfg: p
|
|
1057
|
+
})) : null;
|
|
1058
|
+
}
|
|
541
1059
|
const name = "pet";
|
|
542
|
-
const inject = ["slots"];
|
|
1060
|
+
const inject = ["slots", "locale"];
|
|
543
1061
|
function apply(ctx, _config) {
|
|
1062
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
1063
|
+
zh,
|
|
1064
|
+
en
|
|
1065
|
+
}), "dsh-pet: dictionaries");
|
|
1066
|
+
const t = ctx.locale.bind(NS);
|
|
544
1067
|
ctx.slots.inject("shell.overlay", function* () {
|
|
545
1068
|
yield ctx.slots.register({
|
|
546
1069
|
name: "shell.overlay",
|
|
547
1070
|
id: "pet",
|
|
548
1071
|
order: 1e3
|
|
549
|
-
}, () => h(
|
|
1072
|
+
}, () => h(PetMulti, {}));
|
|
1073
|
+
});
|
|
1074
|
+
const PetConfigSection = makePetConfigSection({
|
|
1075
|
+
h,
|
|
1076
|
+
useState,
|
|
1077
|
+
t
|
|
1078
|
+
});
|
|
1079
|
+
ctx.slots.inject("settings.section", function* () {
|
|
1080
|
+
yield ctx.slots.register({
|
|
1081
|
+
name: "settings.section",
|
|
1082
|
+
id: "pet-config",
|
|
1083
|
+
order: 30,
|
|
1084
|
+
label: () => t("nav"),
|
|
1085
|
+
inject: () => ({ t })
|
|
1086
|
+
}, PetConfigSection);
|
|
550
1087
|
});
|
|
551
1088
|
}
|
|
552
1089
|
module.exports = {
|