dsh-background-by-model 0.6.0 → 0.7.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/CHANGELOG.md +32 -1
- package/CHANGELOG.zh.md +31 -1
- package/README.md +74 -25
- package/README.zh.md +76 -27
- package/lib/client.js +1329 -177
- package/lib/client.js.map +1 -1
- package/lib/index.js +150 -21
- package/package.json +5 -3
package/lib/index.js
CHANGED
|
@@ -53,6 +53,26 @@ const BG_MODES = [
|
|
|
53
53
|
"tile",
|
|
54
54
|
"center"
|
|
55
55
|
];
|
|
56
|
+
/** Every accepted `rotate.order`, in UI order. */
|
|
57
|
+
const ROTATE_ORDERS = ["order", "shuffle"];
|
|
58
|
+
/** Fastest allowed dwell time — below this the cross-fade never settles. */
|
|
59
|
+
const ROTATE_MIN_MS = 5e3;
|
|
60
|
+
/** Slowest allowed dwell time (24 h): one image per day is still "rotation". */
|
|
61
|
+
const ROTATE_MAX_MS = 864e5;
|
|
62
|
+
/** Longest allowed cross-fade; 0 is a hard cut. */
|
|
63
|
+
const ROTATE_FADE_MAX_MS = 3e3;
|
|
64
|
+
/** Everything off: the shipped default, and what a config predating this feature gets. */
|
|
65
|
+
const DEFAULT_ROTATION = {
|
|
66
|
+
enabled: false,
|
|
67
|
+
intervalMs: 6e4,
|
|
68
|
+
order: "order",
|
|
69
|
+
advanceOnSwitch: false,
|
|
70
|
+
fadeMs: 320
|
|
71
|
+
};
|
|
72
|
+
/** A fresh rotation block (never hand out the shared default object). */
|
|
73
|
+
function defaultRotation() {
|
|
74
|
+
return { ...DEFAULT_ROTATION };
|
|
75
|
+
}
|
|
56
76
|
/**
|
|
57
77
|
* Main interface opacities (0..1), global (Interface page):
|
|
58
78
|
* `bg` main background (`--dsw-alias-bg-base`)
|
|
@@ -149,16 +169,21 @@ function hexToHsl(hex) {
|
|
|
149
169
|
* art was not cut for.
|
|
150
170
|
*/
|
|
151
171
|
function defaultHolidayRule(def) {
|
|
172
|
+
const color = hexToHsl(def.color);
|
|
152
173
|
return {
|
|
153
174
|
id: def.id,
|
|
154
|
-
|
|
175
|
+
images: [{
|
|
176
|
+
slot: def.slot,
|
|
177
|
+
bgState: { ...DEFAULT_BG_STATE },
|
|
178
|
+
color
|
|
179
|
+
}],
|
|
155
180
|
match: "",
|
|
156
181
|
enabled: true,
|
|
157
|
-
color
|
|
182
|
+
color,
|
|
158
183
|
bgMode: "fill",
|
|
159
184
|
wallpaperOpacity: 1,
|
|
160
185
|
blur: 0,
|
|
161
|
-
|
|
186
|
+
rotate: defaultRotation()
|
|
162
187
|
};
|
|
163
188
|
}
|
|
164
189
|
/** Slot names reach the filesystem, so they are strictly whitelisted. */
|
|
@@ -187,34 +212,115 @@ function normalizeHsl(raw) {
|
|
|
187
212
|
clamp(raw[2], 0, 1, .25)
|
|
188
213
|
];
|
|
189
214
|
}
|
|
190
|
-
/** Coerce one persisted
|
|
215
|
+
/** Coerce one persisted rotation block; a config without one gets the shipped default. */
|
|
216
|
+
function normalizeRotation(raw) {
|
|
217
|
+
const r = raw ?? {};
|
|
218
|
+
const d = DEFAULT_ROTATION;
|
|
219
|
+
return {
|
|
220
|
+
enabled: r.enabled === true,
|
|
221
|
+
intervalMs: clamp(r.intervalMs, ROTATE_MIN_MS, ROTATE_MAX_MS, d.intervalMs),
|
|
222
|
+
order: ROTATE_ORDERS.includes(r.order) ? r.order : d.order,
|
|
223
|
+
advanceOnSwitch: r.advanceOnSwitch === true,
|
|
224
|
+
fadeMs: clamp(r.fadeMs, 0, ROTATE_FADE_MAX_MS, d.fadeMs)
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/** Own-property test for values that may be anything at all (a raw JSON entry). */
|
|
228
|
+
function hasOwn(o, key) {
|
|
229
|
+
return Object.prototype.hasOwnProperty.call(o, key);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Coerce one persisted image entry, or null when it lacks a usable slot.
|
|
233
|
+
*
|
|
234
|
+
* `inherit` is the RULE's own color, and it is applied only to an entry that has
|
|
235
|
+
* no `color` key at all — the shape every release before 0.7.1 wrote, where a
|
|
236
|
+
* rule's color was the only color there was. Lifting it here, once, is what makes
|
|
237
|
+
* an upgrade a no-op on screen: a themed wallpaper keeps its theme instead of
|
|
238
|
+
* silently dropping to the system palette, and it happens in the shared sanitizer
|
|
239
|
+
* so both halves lift identically.
|
|
240
|
+
*
|
|
241
|
+
* The KEY's presence is the whole distinction, which is why `null` is not the
|
|
242
|
+
* same as absent: an entry with `color: null` is an image the user explicitly set
|
|
243
|
+
* to "follow the system theme", and re-inheriting the rule's color over that
|
|
244
|
+
* would undo a deliberate choice on every load.
|
|
245
|
+
*/
|
|
246
|
+
function normalizeImage(raw, inherit = null) {
|
|
247
|
+
const i = raw ?? {};
|
|
248
|
+
if (typeof i.slot !== "string" || !SLOT_RE.test(i.slot)) return null;
|
|
249
|
+
return {
|
|
250
|
+
slot: i.slot,
|
|
251
|
+
bgState: normalizeBgState(i.bgState),
|
|
252
|
+
color: hasOwn(i, "color") ? normalizeHsl(i.color) : inherit
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Coerce one persisted rule, or null when it names no image AT ALL.
|
|
257
|
+
*
|
|
258
|
+
* Accepts BOTH image shapes: the 0.7 list (`images`), and the single
|
|
259
|
+
* `slot`/`bgState` pair every earlier version wrote — the latter is lifted to a
|
|
260
|
+
* one-entry list here rather than at a dozen call sites, which is what keeps the
|
|
261
|
+
* rest of the codebase free of "old config" branches. Duplicate slots inside one
|
|
262
|
+
* rule are dropped: two entries pointing at the same bytes would only make the
|
|
263
|
+
* rotation look stuck.
|
|
264
|
+
*
|
|
265
|
+
* The rule's color is resolved FIRST and handed to every image entry, because it
|
|
266
|
+
* is what an entry written before 0.7.1 inherits (`normalizeImage`). The rule keeps
|
|
267
|
+
* it as well: it is not a default for its images at paint time any more, but it
|
|
268
|
+
* is still what the rule paints once its last picture is gone.
|
|
269
|
+
*
|
|
270
|
+
* An EMPTY list is a legitimate rule, not a broken one: removing a rule's last
|
|
271
|
+
* picture has to be expressible, and forcing an entry back in is what produced a
|
|
272
|
+
* phantom blank image that took slot 1 and could never be deleted again (the next
|
|
273
|
+
* upload landed at position 2 and the rule kept painting nothing). What such a
|
|
274
|
+
* rule then does is `ruleCanPaint`'s business: with a color of its own it paints
|
|
275
|
+
* the interface, with neither it is skipped by matching — which the card's own
|
|
276
|
+
* hint says in so many words. Only a rule that names no image anywhere — no list,
|
|
277
|
+
* no legacy slot, or a slot that could never be a filename — is dropped.
|
|
278
|
+
*/
|
|
191
279
|
function normalizeRule(raw) {
|
|
192
280
|
const r = raw ?? {};
|
|
193
281
|
const id = typeof r.id === "string" && r.id !== "" ? r.id : null;
|
|
194
|
-
|
|
195
|
-
|
|
282
|
+
if (id === null) return null;
|
|
283
|
+
const color = normalizeHsl(r.color);
|
|
284
|
+
const images = [];
|
|
285
|
+
const seen = /* @__PURE__ */ new Set();
|
|
286
|
+
const hadList = Array.isArray(r.images);
|
|
287
|
+
if (hadList) for (const entry of r.images) {
|
|
288
|
+
const image = normalizeImage(entry, color);
|
|
289
|
+
if (image === null || seen.has(image.slot)) continue;
|
|
290
|
+
seen.add(image.slot);
|
|
291
|
+
images.push(image);
|
|
292
|
+
}
|
|
293
|
+
const legacySlot = typeof r.slot === "string" && SLOT_RE.test(r.slot) ? r.slot : null;
|
|
294
|
+
if (images.length === 0 && legacySlot !== null) images.push({
|
|
295
|
+
slot: legacySlot,
|
|
296
|
+
bgState: normalizeBgState(r.bgState),
|
|
297
|
+
color
|
|
298
|
+
});
|
|
299
|
+
if (images.length === 0 && !hadList) return null;
|
|
196
300
|
const mode = BG_MODES.includes(r.bgMode) ? r.bgMode : "fit";
|
|
197
301
|
return {
|
|
198
302
|
id,
|
|
199
|
-
|
|
303
|
+
images,
|
|
200
304
|
match: typeof r.match === "string" ? r.match : "",
|
|
201
305
|
enabled: r.enabled !== false,
|
|
202
|
-
color
|
|
306
|
+
color,
|
|
203
307
|
bgMode: mode,
|
|
204
308
|
wallpaperOpacity: clamp01(r.wallpaperOpacity, 1),
|
|
205
309
|
blur: clamp(r.blur, 0, 60, 0),
|
|
206
|
-
|
|
310
|
+
rotate: normalizeRotation(r.rotate)
|
|
207
311
|
};
|
|
208
312
|
}
|
|
209
313
|
/**
|
|
210
314
|
* Coerce one persisted holiday entry.
|
|
211
315
|
*
|
|
212
|
-
* `id`, `
|
|
213
|
-
* slot is the only thing tying it to
|
|
214
|
-
* part of what that holiday looks like —
|
|
215
|
-
*
|
|
216
|
-
* festival in a color it does not have.
|
|
217
|
-
* a rule's field
|
|
316
|
+
* `id`, `images` and every theme color (the rule's and each image's) come from
|
|
317
|
+
* the DEFINITION, never from disk: a holiday's slot is the only thing tying it to
|
|
318
|
+
* its bytes, and its theme color is a fixed part of what that holiday looks like —
|
|
319
|
+
* so a stale or hand-edited value would either orphan the wallpaper, point the
|
|
320
|
+
* holiday at a rule's image, or paint a festival in a color it does not have.
|
|
321
|
+
* Everything else is sanitized exactly like a rule's field — except the image
|
|
322
|
+
* list and the rotation, which stay the definition's own single image and stay
|
|
323
|
+
* off, so a festival can never be swapped or cycled.
|
|
218
324
|
*/
|
|
219
325
|
function normalizeHolidayRule(def, raw) {
|
|
220
326
|
const r = raw ?? {};
|
|
@@ -225,7 +331,11 @@ function normalizeHolidayRule(def, raw) {
|
|
|
225
331
|
bgMode: BG_MODES.includes(r.bgMode) ? r.bgMode : base.bgMode,
|
|
226
332
|
wallpaperOpacity: clamp01(r.wallpaperOpacity, base.wallpaperOpacity),
|
|
227
333
|
blur: clamp(r.blur, 0, 60, base.blur),
|
|
228
|
-
|
|
334
|
+
images: base.images.map((image) => ({
|
|
335
|
+
slot: image.slot,
|
|
336
|
+
bgState: normalizeBgState(r.images?.[0]?.bgState ?? r.bgState),
|
|
337
|
+
color: image.color
|
|
338
|
+
}))
|
|
229
339
|
};
|
|
230
340
|
}
|
|
231
341
|
/** Coerce the whole holiday block; unknown ids are dropped, missing ones defaulted. */
|
|
@@ -338,14 +448,16 @@ async function migrateLegacy(raw) {
|
|
|
338
448
|
const next = {
|
|
339
449
|
rules: [{
|
|
340
450
|
id: "m1",
|
|
341
|
-
|
|
451
|
+
images: [{
|
|
452
|
+
slot: "m1",
|
|
453
|
+
bgState: r.bgState ?? {}
|
|
454
|
+
}],
|
|
342
455
|
match: "",
|
|
343
456
|
enabled: true,
|
|
344
457
|
color: Array.isArray(r.color) ? r.color : null,
|
|
345
458
|
bgMode: typeof r.bgMode === "string" ? r.bgMode : "fit",
|
|
346
459
|
wallpaperOpacity: typeof r.wallpaperOpacity === "number" ? r.wallpaperOpacity : 1,
|
|
347
|
-
blur: typeof r.blur === "number" ? r.blur : 0
|
|
348
|
-
bgState: r.bgState ?? {}
|
|
460
|
+
blur: typeof r.blur === "number" ? r.blur : 0
|
|
349
461
|
}],
|
|
350
462
|
opacities: r.opacities,
|
|
351
463
|
blurs: r.blurs,
|
|
@@ -385,6 +497,7 @@ const LEGACY_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
385
497
|
"blur",
|
|
386
498
|
"bgState"
|
|
387
499
|
]);
|
|
500
|
+
const LEGACY_RULE_KEYS = /* @__PURE__ */ new Set(["slot", "bgState"]);
|
|
388
501
|
const warnedConfigKeys = /* @__PURE__ */ new Set();
|
|
389
502
|
function warnUnknownConfigKeys(raw, normalized) {
|
|
390
503
|
if (raw === null || typeof raw !== "object") return;
|
|
@@ -406,8 +519,23 @@ function warnUnknownConfigKeys(raw, normalized) {
|
|
|
406
519
|
const sent = Array.isArray(r.rules) ? r.rules[0] : void 0;
|
|
407
520
|
const have = normalized.rules[0];
|
|
408
521
|
if (sent !== null && typeof sent === "object" && have !== void 0) {
|
|
522
|
+
const rule = sent;
|
|
409
523
|
const keys = new Set(Object.keys(have));
|
|
410
|
-
for (const key of Object.keys(
|
|
524
|
+
for (const key of Object.keys(rule)) if (!keys.has(key) && !LEGACY_RULE_KEYS.has(key)) warned(`rules[].${key}`);
|
|
525
|
+
const nested = [[
|
|
526
|
+
"rules[].rotate",
|
|
527
|
+
rule.rotate,
|
|
528
|
+
have.rotate
|
|
529
|
+
], [
|
|
530
|
+
"rules[].images[]",
|
|
531
|
+
Array.isArray(rule.images) ? rule.images[0] : void 0,
|
|
532
|
+
have.images[0]
|
|
533
|
+
]];
|
|
534
|
+
for (const [label, got, known] of nested) {
|
|
535
|
+
if (got === null || typeof got !== "object" || known === null || known === void 0) continue;
|
|
536
|
+
const knownKeys = new Set(Object.keys(known));
|
|
537
|
+
for (const key of Object.keys(got)) if (!knownKeys.has(key)) warned(`${label}.${key}`);
|
|
538
|
+
}
|
|
411
539
|
}
|
|
412
540
|
}
|
|
413
541
|
async function writeConfig(config) {
|
|
@@ -611,7 +739,8 @@ async function handleRpcMethod(ctx, endpoint, payload) {
|
|
|
611
739
|
ok: true,
|
|
612
740
|
value: {
|
|
613
741
|
config: await readConfig(),
|
|
614
|
-
slots: await listSlots()
|
|
742
|
+
slots: await listSlots(),
|
|
743
|
+
schema: 4
|
|
615
744
|
}
|
|
616
745
|
};
|
|
617
746
|
case "writeConfig": return {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-background-by-model",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Appearance plugin for DeepSeek Harness: an ordered list of model rules, each carrying its own
|
|
3
|
+
"version": "0.7.1",
|
|
4
|
+
"description": "Appearance plugin for DeepSeek Harness: an ordered list of model rules, each carrying its own wallpapers — every image with its own framing and theme color — plus layout mode, opacity and blur; the background follows the model you switch to, and a rule can rotate through several images on its own rhythm.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"dsh",
|
|
@@ -57,9 +57,11 @@
|
|
|
57
57
|
"watch": "tsdown --watch",
|
|
58
58
|
"typecheck": "tsc -p tsconfig.json",
|
|
59
59
|
"check:holiday": "node scripts/holiday-check.ts",
|
|
60
|
+
"check:rotation": "node scripts/rotation-check.ts",
|
|
61
|
+
"check:repaint": "node scripts/repaint-check.ts",
|
|
60
62
|
"check:ui": "node scripts/ui-strings-check.ts",
|
|
61
63
|
"check:node": "node scripts/node-half-check.mjs",
|
|
62
|
-
"test": "tsdown && tsc -p tsconfig.json && node scripts/holiday-check.ts && node scripts/ui-strings-check.ts && node scripts/node-half-check.mjs"
|
|
64
|
+
"test": "tsdown && tsc -p tsconfig.json && node scripts/holiday-check.ts && node scripts/rotation-check.ts && node scripts/repaint-check.ts && node scripts/ui-strings-check.ts && node scripts/node-half-check.mjs"
|
|
63
65
|
},
|
|
64
66
|
"devDependencies": {
|
|
65
67
|
"@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6",
|