dsh-background-by-model 0.9.2 → 0.11.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/CHANGELOG.md +30 -1
- package/CHANGELOG.zh.md +30 -1
- package/README.md +38 -27
- package/README.zh.md +38 -27
- package/lib/client.js +586 -226
- package/lib/index.js +471 -285
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -103,12 +103,14 @@ window.__ModuleLoader__.load({
|
|
|
103
103
|
id: "mid-autumn",
|
|
104
104
|
slot: "h-midautumn",
|
|
105
105
|
asset: "mid-autumn.webp",
|
|
106
|
+
source: "/bfs/new_dyn/42e22801827aff71267b03c95fcfd7834168597.png@1672w.webp",
|
|
106
107
|
color: "#384A77",
|
|
107
108
|
kind: "lunar"
|
|
108
109
|
}, {
|
|
109
110
|
id: "national-day",
|
|
110
111
|
slot: "h-nationalday",
|
|
111
112
|
asset: "national-day.webp",
|
|
113
|
+
source: "/bfs/new_dyn/30e97211189f7869dae83b909d2a3cc84168597.png@3456w.webp",
|
|
112
114
|
color: "#FFF6EB",
|
|
113
115
|
kind: "gregorian"
|
|
114
116
|
}];
|
|
@@ -330,6 +332,28 @@ window.__ModuleLoader__.load({
|
|
|
330
332
|
function defaultTransition() {
|
|
331
333
|
return { ...DEFAULT_TRANSITION };
|
|
332
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* One image entry for a place that CREATES a picture rather than reads one back
|
|
337
|
+
* (a fresh holiday, and every upload in the panel).
|
|
338
|
+
*
|
|
339
|
+
* The two effects whose neutral value is also the shipped default are written
|
|
340
|
+
* explicitly (`opacity`, `blur`), even though `normalizeImage` would default them:
|
|
341
|
+
* the read-time lifts key off the ABSENCE of a key, so an entry created without one
|
|
342
|
+
* would come back as "written before this field existed" and inherit whatever the
|
|
343
|
+
* rule happened to carry. `brightness` is the one field whose absence is meaningful
|
|
344
|
+
* (see `BgImage.brightness`), so it is left out — "untouched" is exactly what a new
|
|
345
|
+
* picture is.
|
|
346
|
+
*/
|
|
347
|
+
function defaultImage(slot, color = null, bgMode = "fit") {
|
|
348
|
+
return {
|
|
349
|
+
slot,
|
|
350
|
+
bgMode,
|
|
351
|
+
bgState: { ...DEFAULT_BG_STATE },
|
|
352
|
+
color,
|
|
353
|
+
opacity: 1,
|
|
354
|
+
blur: 0
|
|
355
|
+
};
|
|
356
|
+
}
|
|
333
357
|
/** Every slot a rule owns, in order. The one thing the render and store layers
|
|
334
358
|
* ask of the image list, so a rule's slots are never enumerated ad hoc. */
|
|
335
359
|
function ruleSlots(rule) {
|
|
@@ -399,18 +423,10 @@ window.__ModuleLoader__.load({
|
|
|
399
423
|
const color = hexToHsl(def.color);
|
|
400
424
|
return {
|
|
401
425
|
id: def.id,
|
|
402
|
-
images: [
|
|
403
|
-
slot: def.slot,
|
|
404
|
-
bgMode: "fill",
|
|
405
|
-
bgState: { ...DEFAULT_BG_STATE },
|
|
406
|
-
color
|
|
407
|
-
}],
|
|
426
|
+
images: [defaultImage(def.slot, color, "fill")],
|
|
408
427
|
match: "",
|
|
409
428
|
enabled: true,
|
|
410
429
|
color,
|
|
411
|
-
wallpaperOpacity: 1,
|
|
412
|
-
brightness: 100,
|
|
413
|
-
blur: 0,
|
|
414
430
|
rotate: defaultRotation()
|
|
415
431
|
};
|
|
416
432
|
}
|
|
@@ -434,9 +450,6 @@ window.__ModuleLoader__.load({
|
|
|
434
450
|
match: "",
|
|
435
451
|
enabled: true,
|
|
436
452
|
color: null,
|
|
437
|
-
wallpaperOpacity: 1,
|
|
438
|
-
brightness: 100,
|
|
439
|
-
blur: 0,
|
|
440
453
|
rotate: defaultRotation()
|
|
441
454
|
};
|
|
442
455
|
}
|
|
@@ -547,28 +560,41 @@ window.__ModuleLoader__.load({
|
|
|
547
560
|
durationMs: clamp$1(t.durationMs, 0, TRANSITION_MAX_MS, fallback)
|
|
548
561
|
};
|
|
549
562
|
}
|
|
563
|
+
/** The inherit block for a caller that has no rule-level values at all. */
|
|
564
|
+
const DEFAULT_IMAGE_INHERIT = {
|
|
565
|
+
color: null,
|
|
566
|
+
opacity: 1,
|
|
567
|
+
brightness: void 0,
|
|
568
|
+
blur: 0
|
|
569
|
+
};
|
|
550
570
|
/**
|
|
551
571
|
* Coerce one persisted image entry, or null when it lacks a usable slot.
|
|
552
572
|
*
|
|
553
|
-
* `inherit`
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
*
|
|
573
|
+
* `inherit` is the rule-level block this entry may take its values from, and each
|
|
574
|
+
* field is lifted ONLY when the entry has no key of its own — that absence is the
|
|
575
|
+
* whole signal. `color: null` is a deliberate "follow the system theme" and must
|
|
576
|
+
* not be re-inherited on every load; `brightness` keeps its key absent when neither
|
|
577
|
+
* side ever named one, so an upgrade never writes a default nobody chose.
|
|
557
578
|
*
|
|
558
579
|
* `inheritMode` is the same one-shot lift for the layout mode. It is the caller's to
|
|
559
580
|
* resolve because the callers answer differently: a user rule passes the mode its
|
|
560
581
|
* config carried at the RULE level (schema 5), a holiday passes its own `fill`. An
|
|
561
582
|
* entry that HAS a `bgMode` key keeps it, even when it equals the default.
|
|
562
583
|
*/
|
|
563
|
-
function normalizeImage(raw, inherit =
|
|
584
|
+
function normalizeImage(raw, inherit = DEFAULT_IMAGE_INHERIT, inheritMode = "fit") {
|
|
564
585
|
const i = raw ?? {};
|
|
565
586
|
if (typeof i.slot !== "string" || !SLOT_RE$1.test(i.slot)) return null;
|
|
566
|
-
|
|
587
|
+
const image = {
|
|
567
588
|
slot: i.slot,
|
|
568
589
|
bgMode: BG_MODES$1.includes(i.bgMode) ? i.bgMode : inheritMode,
|
|
569
590
|
bgState: normalizeBgState(i.bgState),
|
|
570
|
-
color: hasOwn(i, "color") ? normalizeHsl(i.color) : inherit
|
|
591
|
+
color: hasOwn(i, "color") ? normalizeHsl(i.color) : inherit.color,
|
|
592
|
+
opacity: clamp01(i.opacity, inherit.opacity),
|
|
593
|
+
blur: clamp$1(i.blur, 0, 60, inherit.blur)
|
|
571
594
|
};
|
|
595
|
+
const brightness = i.brightness === void 0 ? inherit.brightness : clamp$1(i.brightness, 0, 200, 100);
|
|
596
|
+
if (brightness !== void 0) image.brightness = brightness;
|
|
597
|
+
return image;
|
|
572
598
|
}
|
|
573
599
|
/**
|
|
574
600
|
* Coerce one persisted rule, or null when it names no image ANYWHERE.
|
|
@@ -593,38 +619,45 @@ window.__ModuleLoader__.load({
|
|
|
593
619
|
if (id === null) return null;
|
|
594
620
|
const color = normalizeHsl(r.color);
|
|
595
621
|
const mode = BG_MODES$1.includes(r.bgMode) ? r.bgMode : "fit";
|
|
622
|
+
const inherit = {
|
|
623
|
+
color,
|
|
624
|
+
opacity: clamp01(r.wallpaperOpacity, 1),
|
|
625
|
+
brightness: r.brightness === void 0 ? void 0 : clamp$1(r.brightness, 0, 200, 100),
|
|
626
|
+
blur: clamp$1(r.blur, 0, 60, 0)
|
|
627
|
+
};
|
|
596
628
|
const images = [];
|
|
597
629
|
const seen = /* @__PURE__ */ new Set();
|
|
598
630
|
const hadList = Array.isArray(r.images);
|
|
599
631
|
if (hadList) for (const entry of r.images) {
|
|
600
|
-
const image = normalizeImage(entry,
|
|
632
|
+
const image = normalizeImage(entry, inherit, mode);
|
|
601
633
|
if (image === null || seen.has(image.slot)) continue;
|
|
602
634
|
seen.add(image.slot);
|
|
603
635
|
images.push(image);
|
|
604
636
|
}
|
|
605
637
|
const legacySlot = typeof r.slot === "string" && SLOT_RE$1.test(r.slot) ? r.slot : null;
|
|
606
|
-
if (images.length === 0 && legacySlot !== null)
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
638
|
+
if (images.length === 0 && legacySlot !== null) {
|
|
639
|
+
const image = {
|
|
640
|
+
slot: legacySlot,
|
|
641
|
+
bgMode: mode,
|
|
642
|
+
bgState: normalizeBgState(r.bgState),
|
|
643
|
+
color,
|
|
644
|
+
opacity: inherit.opacity,
|
|
645
|
+
blur: inherit.blur
|
|
646
|
+
};
|
|
647
|
+
if (inherit.brightness !== void 0) image.brightness = inherit.brightness;
|
|
648
|
+
images.push(image);
|
|
649
|
+
}
|
|
612
650
|
if (images.length === 0 && !hadList) return null;
|
|
613
651
|
const rotate = normalizeRotation(r.rotate);
|
|
614
652
|
if (images.length < 2) rotate.enabled = false;
|
|
615
|
-
|
|
616
|
-
const rule = {
|
|
653
|
+
return {
|
|
617
654
|
id,
|
|
618
655
|
images,
|
|
619
656
|
match: typeof r.match === "string" ? r.match : "",
|
|
620
657
|
enabled: r.enabled !== false,
|
|
621
658
|
color,
|
|
622
|
-
wallpaperOpacity: clamp01(r.wallpaperOpacity, 1),
|
|
623
|
-
blur: clamp$1(r.blur, 0, 60, 0),
|
|
624
659
|
rotate
|
|
625
660
|
};
|
|
626
|
-
if (brightness !== void 0) rule.brightness = brightness;
|
|
627
|
-
return rule;
|
|
628
661
|
}
|
|
629
662
|
/**
|
|
630
663
|
* Coerce one persisted holiday entry.
|
|
@@ -641,17 +674,29 @@ window.__ModuleLoader__.load({
|
|
|
641
674
|
return {
|
|
642
675
|
...base,
|
|
643
676
|
enabled: r.enabled !== false,
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
677
|
+
images: base.images.map((image) => {
|
|
678
|
+
const stored = r.images?.[0];
|
|
679
|
+
const merged = {
|
|
680
|
+
slot: image.slot,
|
|
681
|
+
bgMode: image.bgMode,
|
|
682
|
+
bgState: normalizeBgState(stored?.bgState ?? r.bgState),
|
|
683
|
+
color: image.color,
|
|
684
|
+
opacity: clamp01(stored?.opacity ?? legacyWallpaperOpacity(r.wallpaperOpacity), image.opacity),
|
|
685
|
+
blur: clamp$1(stored?.blur ?? r.blur, 0, 60, image.blur)
|
|
686
|
+
};
|
|
687
|
+
const brightness = stored?.brightness === void 0 ? r.brightness === void 0 ? void 0 : clamp$1(r.brightness, 0, 200, 100) : clamp$1(stored.brightness, 0, 200, 100);
|
|
688
|
+
if (brightness !== void 0) merged.brightness = brightness;
|
|
689
|
+
return merged;
|
|
690
|
+
})
|
|
653
691
|
};
|
|
654
692
|
}
|
|
693
|
+
/**
|
|
694
|
+
* The rule-level opacity a schema-9 holiday entry carried, or `undefined` so the
|
|
695
|
+
* caller can tell "absent" from "set to 1" and leave `clamp01` its default.
|
|
696
|
+
*/
|
|
697
|
+
function legacyWallpaperOpacity(raw) {
|
|
698
|
+
return typeof raw === "number" && isFinite(raw) ? raw : void 0;
|
|
699
|
+
}
|
|
655
700
|
/** Coerce the whole holiday block; unknown ids are dropped, missing ones defaulted. */
|
|
656
701
|
function normalizeHolidays(raw) {
|
|
657
702
|
const r = raw ?? {};
|
|
@@ -892,24 +937,34 @@ window.__ModuleLoader__.load({
|
|
|
892
937
|
function rBgMode() {
|
|
893
938
|
return activeImage()?.bgMode ?? "fit";
|
|
894
939
|
}
|
|
940
|
+
/**
|
|
941
|
+
* Opacity of the wallpaper layer, 0..1 (per image, see BgImage).
|
|
942
|
+
*
|
|
943
|
+
* Read from the image being painted, not from the rule: one rule's rotation can go
|
|
944
|
+
* through a full-bleed photo and a faint decorative plate, and a rule-level answer
|
|
945
|
+
* would be wrong for one of them. A rule with no image paints nothing at all, so
|
|
946
|
+
* there is no layer for the value to describe.
|
|
947
|
+
*/
|
|
895
948
|
function rWop() {
|
|
896
|
-
return clamp01(
|
|
949
|
+
return clamp01(activeImage()?.opacity, 1);
|
|
897
950
|
}
|
|
898
951
|
/**
|
|
899
952
|
* Brightness of the wallpaper layer, in percent (100 = the picture exactly as the
|
|
900
953
|
* file has it).
|
|
901
954
|
*
|
|
902
|
-
* Read from the
|
|
903
|
-
* same way:
|
|
904
|
-
* the setting existed
|
|
905
|
-
*
|
|
906
|
-
* numbers of its own (see
|
|
955
|
+
* Read from the CURRENT image, like the opacity and blur beside it, and defaulted
|
|
956
|
+
* the same way: an image whose `brightness` key is absent — every config written
|
|
957
|
+
* before the setting existed, and every picture whose rule never named one — is
|
|
958
|
+
* left alone, so an upgrade changes nothing on screen. The default and the ceiling
|
|
959
|
+
* live in ../schema, so the render layer holds no numbers of its own (see
|
|
960
|
+
* ./wallpaper's `wpFilter`).
|
|
907
961
|
*/
|
|
908
962
|
function rBrightness() {
|
|
909
|
-
return clamp$1(
|
|
963
|
+
return clamp$1(activeImage()?.brightness, 0, 200, 100);
|
|
910
964
|
}
|
|
965
|
+
/** Blur of the CURRENT image, in px (0 = the picture as its file has it). */
|
|
911
966
|
function rBl() {
|
|
912
|
-
return clamp$1(
|
|
967
|
+
return clamp$1(activeImage()?.blur, 0, 60, 0);
|
|
913
968
|
}
|
|
914
969
|
/** Framing of the CURRENT image (per image, see BgImage). */
|
|
915
970
|
function rBgState() {
|
|
@@ -1064,7 +1119,7 @@ window.__ModuleLoader__.load({
|
|
|
1064
1119
|
const d = data;
|
|
1065
1120
|
const slots = Array.isArray(d.slots) ? d.slots.filter((s) => typeof s === "string") : [];
|
|
1066
1121
|
const schema = typeof d.schema === "number" && isFinite(d.schema) ? d.schema : null;
|
|
1067
|
-
hostShape = typeof schema === "number" && schema >=
|
|
1122
|
+
hostShape = typeof schema === "number" && schema >= 10 ? "current" : "legacy";
|
|
1068
1123
|
if (hostShape === "legacy") holdWrite();
|
|
1069
1124
|
return {
|
|
1070
1125
|
config: d.config,
|
|
@@ -1114,17 +1169,13 @@ window.__ModuleLoader__.load({
|
|
|
1114
1169
|
};
|
|
1115
1170
|
}
|
|
1116
1171
|
/**
|
|
1117
|
-
*
|
|
1172
|
+
* One of the profile's wallpapers, addressed by the slot the built-in config names.
|
|
1118
1173
|
*
|
|
1119
1174
|
* Raw rather than `rpcCall` for the same reason `fetchImageUrl` is: the node half
|
|
1120
|
-
* answers this one with a value that CARRIES its own failure, and the `error`
|
|
1121
|
-
*
|
|
1122
|
-
*
|
|
1175
|
+
* answers this one with a value that CARRIES its own failure, and the `error` inside
|
|
1176
|
+
* it is the only thing worth showing the user — the generic "rpc failed" log would
|
|
1177
|
+
* throw that distinction away.
|
|
1123
1178
|
*/
|
|
1124
|
-
async function fetchPresetConfig() {
|
|
1125
|
-
return unwrapPreset(await rpcRaw("fetchPresetConfig", {}));
|
|
1126
|
-
}
|
|
1127
|
-
/** One of the profile's wallpapers, addressed by the slot the config names. */
|
|
1128
1179
|
async function fetchPresetImage(slot) {
|
|
1129
1180
|
return unwrapPreset(await rpcRaw("fetchPresetImage", { slot }));
|
|
1130
1181
|
}
|
|
@@ -3197,13 +3248,25 @@ window.__ModuleLoader__.load({
|
|
|
3197
3248
|
/** Index of the layer fading in right now, if any. */
|
|
3198
3249
|
let pending = null;
|
|
3199
3250
|
let fadeTimer = null;
|
|
3251
|
+
/** The wallpaper's steady opacity — the ACTIVE picture's, as last committed.
|
|
3252
|
+
*
|
|
3253
|
+
* Mirrored rather than read back off the style string, because a repaint that
|
|
3254
|
+
* runs in the middle of a fade (a theme re-registration, a panel re-render) has
|
|
3255
|
+
* to re-write the value the fade is HEADING for; reading `rWop()` there would
|
|
3256
|
+
* snap the whole layer to the incoming picture's alpha and cut the fade short.
|
|
3257
|
+
*/
|
|
3258
|
+
let wpAlpha = 1;
|
|
3200
3259
|
function createLayer() {
|
|
3201
3260
|
const el = document.createElement("div");
|
|
3202
3261
|
el.dataset.dabWpLayer = "";
|
|
3203
3262
|
el.style.cssText = "position:absolute;inset:0;opacity:0;background-repeat:no-repeat;background-position:center;";
|
|
3204
3263
|
return {
|
|
3205
3264
|
el,
|
|
3206
|
-
url: null
|
|
3265
|
+
url: null,
|
|
3266
|
+
posX: null,
|
|
3267
|
+
posY: null,
|
|
3268
|
+
brightness: 100,
|
|
3269
|
+
blur: 0
|
|
3207
3270
|
};
|
|
3208
3271
|
}
|
|
3209
3272
|
function ensureWpContainer() {
|
|
@@ -3221,6 +3284,7 @@ window.__ModuleLoader__.load({
|
|
|
3221
3284
|
layers = [a, b];
|
|
3222
3285
|
front = 0;
|
|
3223
3286
|
pending = null;
|
|
3287
|
+
wpAlpha = 1;
|
|
3224
3288
|
document.body.prepend(wpEl);
|
|
3225
3289
|
}
|
|
3226
3290
|
/** Drop the whole wallpaper layer (no rule carries an image). */
|
|
@@ -3232,6 +3296,7 @@ window.__ModuleLoader__.load({
|
|
|
3232
3296
|
pending = null;
|
|
3233
3297
|
front = 0;
|
|
3234
3298
|
layers = null;
|
|
3299
|
+
wpAlpha = 1;
|
|
3235
3300
|
wpEl?.remove();
|
|
3236
3301
|
wpEl = null;
|
|
3237
3302
|
}
|
|
@@ -3239,9 +3304,14 @@ window.__ModuleLoader__.load({
|
|
|
3239
3304
|
function clearLayer(l) {
|
|
3240
3305
|
if (l.url === null) return;
|
|
3241
3306
|
l.url = null;
|
|
3307
|
+
l.posX = null;
|
|
3308
|
+
l.posY = null;
|
|
3309
|
+
l.brightness = 100;
|
|
3310
|
+
l.blur = 0;
|
|
3242
3311
|
l.el.style.backgroundImage = "";
|
|
3243
3312
|
l.el.style.backgroundSize = "";
|
|
3244
3313
|
l.el.style.backgroundPosition = "";
|
|
3314
|
+
l.el.style.inset = "0px";
|
|
3245
3315
|
l.el.style.transform = "none";
|
|
3246
3316
|
}
|
|
3247
3317
|
/** Write the two properties a switch animates. Both are always written, so a
|
|
@@ -3269,6 +3339,34 @@ window.__ModuleLoader__.load({
|
|
|
3269
3339
|
img.onerror = () => cb(0, 0);
|
|
3270
3340
|
img.src = url;
|
|
3271
3341
|
}
|
|
3342
|
+
/** How far a layer's box is grown on every side to hide a blurred edge.
|
|
3343
|
+
*
|
|
3344
|
+
* `filter: blur(σ)` convolves the layer's own rendering with a Gaussian of
|
|
3345
|
+
* standard deviation σ and treats everything outside the box as transparent, so
|
|
3346
|
+
* the outermost pixels come out half opaque and the image fades inward over
|
|
3347
|
+
* ~2σ — composited over the page behind it, that half-transparent rim is the
|
|
3348
|
+
* black border the blur slider paints, and it grows thicker with the slider.
|
|
3349
|
+
* Growing the box by 2σ puts every visible pixel at least 2σ inside the edge,
|
|
3350
|
+
* where ~2% of the page still shows through the outermost row: gone to the eye.
|
|
3351
|
+
* Two sigma rather than three is the trade for the `cover` modes, which pay for
|
|
3352
|
+
* the growth by framing slightly tighter — a fifth of the image at most, at the
|
|
3353
|
+
* slider's maximum, where the detail a wider frame would reveal is blurred out.
|
|
3354
|
+
*/
|
|
3355
|
+
function wpBlurPad(blur) {
|
|
3356
|
+
return blur > 0 ? Math.ceil(blur * 2) + 2 : 0;
|
|
3357
|
+
}
|
|
3358
|
+
/** Grow a layer's box by `pad` on every side while keeping its image on the same
|
|
3359
|
+
* pixels. The background positioning area starts at the border box, so a box
|
|
3360
|
+
* whose origin moved by -pad needs a numeric placement moved by +pad; `center`
|
|
3361
|
+
* (and the box-relative `cover` / `100% 100%` sizes) sit in the middle of a
|
|
3362
|
+
* symmetrically grown box and need no compensation. The container clips the
|
|
3363
|
+
* growth, so nothing about the visible layout changes: it exists only to give
|
|
3364
|
+
* the blur pixels to sample beyond the viewport. */
|
|
3365
|
+
function writeLayerBox(l, pad) {
|
|
3366
|
+
const { posX, posY } = l;
|
|
3367
|
+
l.el.style.inset = `${-pad}px`;
|
|
3368
|
+
l.el.style.backgroundPosition = posX === null || posY === null ? "center" : `${posX + pad}px ${posY + pad}px`;
|
|
3369
|
+
}
|
|
3272
3370
|
/** Paint one layer's image and its placement. The placement always follows the
|
|
3273
3371
|
* ACTIVE rule: the incoming layer is painted right after a switch, and a
|
|
3274
3372
|
* repaint of the same URL (framing edit, viewport resize) must use the rule's
|
|
@@ -3280,6 +3378,8 @@ window.__ModuleLoader__.load({
|
|
|
3280
3378
|
const mode = rBgMode();
|
|
3281
3379
|
const next = `url("${url}")`;
|
|
3282
3380
|
if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
|
|
3381
|
+
l.posX = null;
|
|
3382
|
+
l.posY = null;
|
|
3283
3383
|
if (mode === "fit") {
|
|
3284
3384
|
el.style.backgroundRepeat = "no-repeat";
|
|
3285
3385
|
if (bg.iw > 0) {
|
|
@@ -3287,35 +3387,31 @@ window.__ModuleLoader__.load({
|
|
|
3287
3387
|
const w = bg.iw * fit * bg.zoom;
|
|
3288
3388
|
const h = bg.ih * fit * bg.zoom;
|
|
3289
3389
|
el.style.backgroundSize = `${w}px ${h}px`;
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
el.style.backgroundPosition = "center";
|
|
3294
|
-
}
|
|
3390
|
+
l.posX = bg.x * window.innerWidth - w / 2;
|
|
3391
|
+
l.posY = bg.y * window.innerHeight - h / 2;
|
|
3392
|
+
} else el.style.backgroundSize = "contain";
|
|
3295
3393
|
} else if (mode === "fill") {
|
|
3296
3394
|
el.style.backgroundRepeat = "no-repeat";
|
|
3297
3395
|
el.style.backgroundSize = "cover";
|
|
3298
|
-
el.style.backgroundPosition = "center";
|
|
3299
3396
|
} else if (mode === "stretch") {
|
|
3300
3397
|
el.style.backgroundRepeat = "no-repeat";
|
|
3301
3398
|
el.style.backgroundSize = "100% 100%";
|
|
3302
|
-
el.style.backgroundPosition = "center";
|
|
3303
3399
|
} else if (mode === "tile") {
|
|
3304
3400
|
el.style.backgroundRepeat = "repeat";
|
|
3305
3401
|
el.style.backgroundSize = "auto";
|
|
3306
|
-
|
|
3402
|
+
l.posX = 0;
|
|
3403
|
+
l.posY = 0;
|
|
3307
3404
|
} else {
|
|
3308
3405
|
el.style.backgroundRepeat = "no-repeat";
|
|
3309
3406
|
el.style.backgroundSize = "contain";
|
|
3310
|
-
el.style.backgroundPosition = "center";
|
|
3311
3407
|
imageNatSize(url, (w, h) => {
|
|
3312
3408
|
if (l.url !== url || el.style.backgroundImage !== next || rBgMode() !== "center") return;
|
|
3313
|
-
if (w > 0 && h > 0) {
|
|
3314
|
-
el.style.backgroundSize = `${w}px ${h}px`;
|
|
3315
|
-
el.style.backgroundPosition = "center";
|
|
3316
|
-
}
|
|
3409
|
+
if (w > 0 && h > 0) el.style.backgroundSize = `${w}px ${h}px`;
|
|
3317
3410
|
});
|
|
3318
3411
|
}
|
|
3412
|
+
l.brightness = rBrightness();
|
|
3413
|
+
l.blur = rBl();
|
|
3414
|
+
applyLayerEffects(l);
|
|
3319
3415
|
}
|
|
3320
3416
|
/** Whether there is anything to animate FROM: a wallpaper on screen that is
|
|
3321
3417
|
* actually visible. The duration question belongs to `rFadeMs`, and the
|
|
@@ -3339,6 +3435,10 @@ window.__ModuleLoader__.load({
|
|
|
3339
3435
|
}
|
|
3340
3436
|
pending = null;
|
|
3341
3437
|
front = idx;
|
|
3438
|
+
if (wpEl !== null) {
|
|
3439
|
+
wpEl.style.transition = "none";
|
|
3440
|
+
wpEl.style.opacity = String(wpAlpha);
|
|
3441
|
+
}
|
|
3342
3442
|
const el = layers[idx].el;
|
|
3343
3443
|
el.style.opacity = "1";
|
|
3344
3444
|
el.style.transform = "none";
|
|
@@ -3385,12 +3485,14 @@ window.__ModuleLoader__.load({
|
|
|
3385
3485
|
if (pending !== null) {
|
|
3386
3486
|
if (ls[pending].url === url) {
|
|
3387
3487
|
paintLayer(ls[pending], url);
|
|
3488
|
+
wpAlpha = rWop();
|
|
3388
3489
|
applyWpEffects();
|
|
3389
3490
|
return;
|
|
3390
3491
|
}
|
|
3391
3492
|
if (ls[front].url === url) {
|
|
3392
3493
|
cancelFade();
|
|
3393
3494
|
paintLayer(ls[front], url);
|
|
3495
|
+
wpAlpha = rWop();
|
|
3394
3496
|
applyWpEffects();
|
|
3395
3497
|
return;
|
|
3396
3498
|
}
|
|
@@ -3398,6 +3500,7 @@ window.__ModuleLoader__.load({
|
|
|
3398
3500
|
}
|
|
3399
3501
|
if (ls[front].url === url) {
|
|
3400
3502
|
paintLayer(ls[front], url);
|
|
3503
|
+
wpAlpha = rWop();
|
|
3401
3504
|
applyWpEffects();
|
|
3402
3505
|
return;
|
|
3403
3506
|
}
|
|
@@ -3418,7 +3521,11 @@ window.__ModuleLoader__.load({
|
|
|
3418
3521
|
applyLayerStyle(el, plan.from);
|
|
3419
3522
|
el.style.zIndex = "1";
|
|
3420
3523
|
from.el.style.zIndex = "0";
|
|
3524
|
+
const toAlpha = rWop();
|
|
3525
|
+
const fadeAlpha = plan.animate && Math.abs(toAlpha - wpAlpha) > .001;
|
|
3526
|
+
if (fadeAlpha) wpEl.style.transition = "none";
|
|
3421
3527
|
paintLayer(target, url);
|
|
3528
|
+
if (!fadeAlpha) wpAlpha = toAlpha;
|
|
3422
3529
|
applyWpEffects();
|
|
3423
3530
|
if (!plan.animate) {
|
|
3424
3531
|
applyLayerStyle(el, plan.to);
|
|
@@ -3431,15 +3538,24 @@ window.__ModuleLoader__.load({
|
|
|
3431
3538
|
if (pending !== idx || layers === null) return;
|
|
3432
3539
|
el.style.transition = plan.transition;
|
|
3433
3540
|
applyLayerStyle(el, plan.to);
|
|
3541
|
+
if (fadeAlpha) {
|
|
3542
|
+
wpEl.style.transition = plan.transition;
|
|
3543
|
+
wpEl.style.opacity = String(toAlpha);
|
|
3544
|
+
wpAlpha = toAlpha;
|
|
3545
|
+
}
|
|
3434
3546
|
});
|
|
3435
3547
|
fadeTimer = window.setTimeout(() => finishFade(idx), plan.durationMs + 160);
|
|
3436
3548
|
}
|
|
3437
3549
|
/**
|
|
3438
|
-
* The `filter`
|
|
3439
|
-
*
|
|
3550
|
+
* The `filter` ONE LAYER carries, from the brightness and blur of the picture
|
|
3551
|
+
* painted on IT.
|
|
3552
|
+
*
|
|
3553
|
+
* Both are per image (see BgImage): one rule's rotation can run a daylight photo
|
|
3554
|
+
* and a night shot, and a rule-level answer forced the second one to live at the
|
|
3555
|
+
* first one's exposure.
|
|
3440
3556
|
*
|
|
3441
3557
|
* ONE declaration, because the two are one CSS property: assigning `filter` twice
|
|
3442
|
-
* would leave whichever ran last, so
|
|
3558
|
+
* would leave whichever ran last, so an image that is both brightened and blurred
|
|
3443
3559
|
* would silently lose one of them. Brightness is written first and blur second for
|
|
3444
3560
|
* a reason that is visible: blurring a dimmed picture softens the highlights the
|
|
3445
3561
|
* dimming just brought down, while dimming an already-blurred one simply darkens
|
|
@@ -3449,21 +3565,34 @@ window.__ModuleLoader__.load({
|
|
|
3449
3565
|
* The neutral brightness is LEFT OUT rather than written as `brightness(100%)`.
|
|
3450
3566
|
* A layer with neither effect gets `''`, which is what an untouched wallpaper has
|
|
3451
3567
|
* always carried, and keeps the layer off the compositor's filter path entirely.
|
|
3568
|
+
*
|
|
3569
|
+
* The numbers are the LAYER's, not live reads: during a switch the two layers
|
|
3570
|
+
* hold two different photos, and a live read made the outgoing one jump to the
|
|
3571
|
+
* incoming one's exposure at the first frame of every fade.
|
|
3452
3572
|
*/
|
|
3453
|
-
function wpFilter() {
|
|
3573
|
+
function wpFilter(brightness, blur) {
|
|
3454
3574
|
const parts = [];
|
|
3455
|
-
const brightness = rBrightness();
|
|
3456
3575
|
if (brightness !== 100) parts.push(`brightness(${brightness}%)`);
|
|
3457
|
-
const blur = rBl();
|
|
3458
3576
|
if (blur > 0) parts.push(`blur(${blur}px)`);
|
|
3459
3577
|
return parts.join(" ");
|
|
3460
3578
|
}
|
|
3461
3579
|
function applyWpEffects() {
|
|
3462
3580
|
if (wpEl === null || layers === null) return;
|
|
3463
|
-
wpEl.style.opacity = String(
|
|
3464
|
-
|
|
3465
|
-
layers[
|
|
3466
|
-
|
|
3581
|
+
wpEl.style.opacity = String(wpAlpha);
|
|
3582
|
+
applyLayerEffects(layers[0]);
|
|
3583
|
+
applyLayerEffects(layers[1]);
|
|
3584
|
+
}
|
|
3585
|
+
/** Write what a layer takes from ITS picture: the filter, and the box growth that
|
|
3586
|
+
* keeps a blurred edge from drawing a dark rim.
|
|
3587
|
+
*
|
|
3588
|
+
* ONE function for both, because the two are one visible decision — the same
|
|
3589
|
+
* blur that makes `filter` non-empty is what makes the pad non-zero — and
|
|
3590
|
+
* because BOTH update paths (a full repaint and a drag frame) write it, so a
|
|
3591
|
+
* blur dragged to its top cannot leave the rim behind until the slider is
|
|
3592
|
+
* released. */
|
|
3593
|
+
function applyLayerEffects(l) {
|
|
3594
|
+
l.el.style.filter = wpFilter(l.brightness, l.blur);
|
|
3595
|
+
writeLayerBox(l, wpBlurPad(l.blur));
|
|
3467
3596
|
}
|
|
3468
3597
|
/** Repaint the wallpaper layer, the token palette and every interface part from
|
|
3469
3598
|
* the ACTIVE rule. This is the single entry point a rule switch goes through. */
|
|
@@ -3512,6 +3641,38 @@ window.__ModuleLoader__.load({
|
|
|
3512
3641
|
tableFixStyleEl = null;
|
|
3513
3642
|
stopWatchingParts();
|
|
3514
3643
|
}
|
|
3644
|
+
/** Live wallpaper-opacity updates during slider drag (no full re-apply).
|
|
3645
|
+
*
|
|
3646
|
+
* The container is the one holder of this value (see `applyWpEffects`), so the
|
|
3647
|
+
* mirror and the style are written together — a later repaint then re-asserts
|
|
3648
|
+
* what the drag ended on rather than the value it started from. */
|
|
3649
|
+
function setWpOpacity(v) {
|
|
3650
|
+
wpAlpha = v;
|
|
3651
|
+
if (wpEl) wpEl.style.opacity = String(v);
|
|
3652
|
+
}
|
|
3653
|
+
/**
|
|
3654
|
+
* Live wallpaper-filter updates during a slide, with no full re-apply.
|
|
3655
|
+
*
|
|
3656
|
+
* Deliberately parameterless: the filter composes TWO settings (brightness and
|
|
3657
|
+
* blur), so a function trusting one caller's value would write half of it and
|
|
3658
|
+
* drop the other. Both are read back from the store instead (./state), which is
|
|
3659
|
+
* the same store the drag has already written to before calling this.
|
|
3660
|
+
*
|
|
3661
|
+
* Written onto the layer PAINTING that picture rather than onto both: during a
|
|
3662
|
+
* switch the other layer is still showing the previous photo, and giving it this
|
|
3663
|
+
* one's brightness and blur is exactly the snap this snapshot exists to prevent.
|
|
3664
|
+
*/
|
|
3665
|
+
function setWpFilter() {
|
|
3666
|
+
if (layers === null) return;
|
|
3667
|
+
const url = rWp();
|
|
3668
|
+
if (url === null) return;
|
|
3669
|
+
for (const l of layers) {
|
|
3670
|
+
if (l.url !== url) continue;
|
|
3671
|
+
l.brightness = rBrightness();
|
|
3672
|
+
l.blur = rBl();
|
|
3673
|
+
applyLayerEffects(l);
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3515
3676
|
//#endregion
|
|
3516
3677
|
//#region src/client/cadence.ts
|
|
3517
3678
|
/**
|
|
@@ -4526,7 +4687,7 @@ window.__ModuleLoader__.load({
|
|
|
4526
4687
|
if (url !== null) images[slot] = url;
|
|
4527
4688
|
}
|
|
4528
4689
|
return {
|
|
4529
|
-
version:
|
|
4690
|
+
version: 10,
|
|
4530
4691
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4531
4692
|
config: cfg,
|
|
4532
4693
|
images
|
|
@@ -4565,7 +4726,7 @@ window.__ModuleLoader__.load({
|
|
|
4565
4726
|
* "replace everything with this" literally true.
|
|
4566
4727
|
*/
|
|
4567
4728
|
async function applyThemeFile(host, data, wipeFirst) {
|
|
4568
|
-
if (typeof data.version === "number" && data.version >
|
|
4729
|
+
if (typeof data.version === "number" && data.version > 10) return false;
|
|
4569
4730
|
if (typeof data.config !== "object" || data.config === null) return false;
|
|
4570
4731
|
if (wipeFirst) {
|
|
4571
4732
|
await resetStore();
|
|
@@ -4753,6 +4914,7 @@ window.__ModuleLoader__.load({
|
|
|
4753
4914
|
ruleBrightness: "图片亮度",
|
|
4754
4915
|
ruleBlur: "图片模糊",
|
|
4755
4916
|
ruleEffectPick: "调节项",
|
|
4917
|
+
ruleEffectNoImage: "添加图片后可分别设置每张图的透明度、亮度与模糊",
|
|
4756
4918
|
rotTitle: "多图轮换",
|
|
4757
4919
|
rotEnable: "轮换这组图",
|
|
4758
4920
|
rotNeedTwo: "至少两张图才能轮换",
|
|
@@ -4806,7 +4968,7 @@ window.__ModuleLoader__.load({
|
|
|
4806
4968
|
importDone: "已导入配置",
|
|
4807
4969
|
importFail: "导入失败,文件格式不正确",
|
|
4808
4970
|
recCardTitle: "推荐配置",
|
|
4809
|
-
recCardDesc: "
|
|
4971
|
+
recCardDesc: "换上作者维护的一整套配置(规则与界面数值随插件内置,壁纸从图床下载),一键替换当前的全部内容",
|
|
4810
4972
|
recAdopt: "采用推荐配置",
|
|
4811
4973
|
recBusy: "正在下载",
|
|
4812
4974
|
recConfirmTitle: "替换当前的全部配置?",
|
|
@@ -4816,12 +4978,9 @@ window.__ModuleLoader__.load({
|
|
|
4816
4978
|
recConfirmOk: "删除并替换",
|
|
4817
4979
|
recConfirmCancel: "取消",
|
|
4818
4980
|
recDone: "已采用推荐配置",
|
|
4819
|
-
recFailNetwork: "
|
|
4820
|
-
recFailNotProfile: "素材库返回的内容不是配置,请稍后重试",
|
|
4821
|
-
recFailNewer: "这份推荐配置需要更新的插件版本,未做任何改动",
|
|
4822
|
-
recFailTooMany: "这份推荐配置列出的图片数量超出限制,未做任何改动",
|
|
4981
|
+
recFailNetwork: "壁纸下载失败,请检查网络后重试(已下到的会留到下一次)",
|
|
4823
4982
|
recFailTooLarge: "这份推荐配置体积过大,未做任何改动",
|
|
4824
|
-
recFailImage: "
|
|
4983
|
+
recFailImage: "这张壁纸没拿到(图床上这个地址没有回应):",
|
|
4825
4984
|
recFailApply: "未能写入推荐配置,你原来的配置没有被改动",
|
|
4826
4985
|
storeCardTitle: "图片存储",
|
|
4827
4986
|
storeCardDesc: "因意外导致的孤儿图片这里可以清掉",
|
|
@@ -4936,6 +5095,7 @@ window.__ModuleLoader__.load({
|
|
|
4936
5095
|
ruleBrightness: "Image brightness",
|
|
4937
5096
|
ruleBlur: "Image blur",
|
|
4938
5097
|
ruleEffectPick: "Effect",
|
|
5098
|
+
ruleEffectNoImage: "Add an image to give each one its own opacity, brightness and blur",
|
|
4939
5099
|
rotTitle: "Image rotation",
|
|
4940
5100
|
rotEnable: "Rotate these images",
|
|
4941
5101
|
rotNeedTwo: "At least two images are needed to rotate",
|
|
@@ -4989,7 +5149,7 @@ window.__ModuleLoader__.load({
|
|
|
4989
5149
|
importDone: "Profile imported",
|
|
4990
5150
|
importFail: "Import failed — invalid file",
|
|
4991
5151
|
recCardTitle: "Recommended profile",
|
|
4992
|
-
recCardDesc: "
|
|
5152
|
+
recCardDesc: "Install the author's own full setup — rules and interface numbers ship with the plugin, wallpapers are downloaded from the image host — replacing everything you have",
|
|
4993
5153
|
recAdopt: "Use recommended profile",
|
|
4994
5154
|
recBusy: "Downloading",
|
|
4995
5155
|
recConfirmTitle: "Replace your entire profile?",
|
|
@@ -4999,12 +5159,9 @@ window.__ModuleLoader__.load({
|
|
|
4999
5159
|
recConfirmOk: "Delete and replace",
|
|
5000
5160
|
recConfirmCancel: "Cancel",
|
|
5001
5161
|
recDone: "Recommended profile applied",
|
|
5002
|
-
recFailNetwork: "Could not download the
|
|
5003
|
-
recFailNotProfile: "The asset host answered with something that is not a profile — try again later",
|
|
5004
|
-
recFailNewer: "This profile needs a newer plugin version. Nothing was changed.",
|
|
5005
|
-
recFailTooMany: "The recommended profile names more images than the plugin will fetch. Nothing was changed.",
|
|
5162
|
+
recFailNetwork: "Could not download the wallpapers — check your connection and retry (what arrived is kept for the next try)",
|
|
5006
5163
|
recFailTooLarge: "The recommended profile is too large. Nothing was changed.",
|
|
5007
|
-
recFailImage: "Could not fetch this wallpaper — the
|
|
5164
|
+
recFailImage: "Could not fetch this wallpaper — the image host does not answer for that address:",
|
|
5008
5165
|
recFailApply: "Could not write the recommended profile — your own is untouched",
|
|
5009
5166
|
storeCardTitle: "Image storage",
|
|
5010
5167
|
storeCardDesc: "Orphaned image files left behind by an accident can be cleared here",
|
|
@@ -5041,41 +5198,6 @@ window.__ModuleLoader__.load({
|
|
|
5041
5198
|
//#endregion
|
|
5042
5199
|
//#region src/preset.ts
|
|
5043
5200
|
/**
|
|
5044
|
-
* JSON with object keys in a fixed (sorted) order — the same string for the same
|
|
5045
|
-
* value, whatever order the object that carried it happened to build its keys in.
|
|
5046
|
-
*
|
|
5047
|
-
* `JSON.stringify` alone is not an identity for a value that came off the wire and
|
|
5048
|
-
* through a sanitizer: key order is an accident of how the object was assembled,
|
|
5049
|
-
* and a resume that missed on it would silently re-download everything (the safe
|
|
5050
|
-
* direction) while a resume that MATCHED on a value it should not have would be
|
|
5051
|
-
* the unsafe one — so this is deliberately total as well: anything JSON cannot
|
|
5052
|
-
* carry (a function, a `Symbol`, `undefined`) serializes as `null` and an
|
|
5053
|
-
* `undefined` property is skipped, exactly as `JSON.stringify` treats it.
|
|
5054
|
-
*/
|
|
5055
|
-
function canonical(value) {
|
|
5056
|
-
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
5057
|
-
if (value !== null && typeof value === "object") return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(",")}}`;
|
|
5058
|
-
return JSON.stringify(value) ?? "null";
|
|
5059
|
-
}
|
|
5060
|
-
/**
|
|
5061
|
-
* The identity of one revision of the profile: the manifest's shape version plus
|
|
5062
|
-
* the config, in canonical form.
|
|
5063
|
-
*
|
|
5064
|
-
* The profile is served at ONE version of the assets repository, but a version
|
|
5065
|
-
* reference is only as fixed as the tag behind it: re-point the tag and every
|
|
5066
|
-
* edge that has not been purged keeps answering with the previous bytes of the
|
|
5067
|
-
* same URL. So "the profile" is still not guaranteed to be one fixed thing, and
|
|
5068
|
-
* the bytes a previous attempt is holding are only known to belong to what is
|
|
5069
|
-
* being installed when that config comes back the same. Order-insensitivity is
|
|
5070
|
-
* what lets this be a comparison of VALUES rather than of spellings —
|
|
5071
|
-
* `normalizeConfig` (./schema) rebuilds the config it reads, so pinning the key
|
|
5072
|
-
* to the exact key order the wire happened to use would make the hold useless
|
|
5073
|
-
* the moment anything upstream normalized differently.
|
|
5074
|
-
*/
|
|
5075
|
-
function presetKey(version, config) {
|
|
5076
|
-
return `${version}\n${canonical(config)}`;
|
|
5077
|
-
}
|
|
5078
|
-
/**
|
|
5079
5201
|
* The bytes of a failed attempt that may be reused for the revision `key` names.
|
|
5080
5202
|
*
|
|
5081
5203
|
* A fresh object every time, never the held record itself: the caller fills it in
|
|
@@ -5099,6 +5221,145 @@ window.__ModuleLoader__.load({
|
|
|
5099
5221
|
function presetMissing(slots, images) {
|
|
5100
5222
|
return slots.filter((slot) => images[slot] === void 0);
|
|
5101
5223
|
}
|
|
5224
|
+
/** Every slot the profile installs, in list order — the download set. */
|
|
5225
|
+
const PROFILE_SLOTS = [
|
|
5226
|
+
{
|
|
5227
|
+
slot: "m1",
|
|
5228
|
+
path: "/bfs/new_dyn/e4565384e7e5b2d75756e577f505534b4168597.png"
|
|
5229
|
+
},
|
|
5230
|
+
{
|
|
5231
|
+
slot: "m2",
|
|
5232
|
+
path: "/bfs/new_dyn/aa6e8649701c91e5ac8f43ff28b97a924168597.png"
|
|
5233
|
+
},
|
|
5234
|
+
{
|
|
5235
|
+
slot: "m3",
|
|
5236
|
+
path: "/bfs/new_dyn/5da92b31df5ee2a20b17ec6be353cf5c4168597.png"
|
|
5237
|
+
},
|
|
5238
|
+
{
|
|
5239
|
+
slot: "m4",
|
|
5240
|
+
path: "/bfs/new_dyn/14bb1e53cb2c13ca80bd23e3c1d2a04e4168597.png"
|
|
5241
|
+
},
|
|
5242
|
+
{
|
|
5243
|
+
slot: "m5",
|
|
5244
|
+
path: "/bfs/new_dyn/f51fff40de2d6eaa5a73d177f7bbfa794168597.png"
|
|
5245
|
+
},
|
|
5246
|
+
{
|
|
5247
|
+
slot: "m6",
|
|
5248
|
+
path: "/bfs/new_dyn/cfa71d4e60a2f12aaeec3efe1aa680b34168597.png"
|
|
5249
|
+
},
|
|
5250
|
+
{
|
|
5251
|
+
slot: "m7",
|
|
5252
|
+
path: "/bfs/new_dyn/c09d34b79d9b447d7db98da68b97afc54168597.png"
|
|
5253
|
+
}
|
|
5254
|
+
].map((i) => i.slot);
|
|
5255
|
+
/**
|
|
5256
|
+
* Identity of this generation of the built-in profile.
|
|
5257
|
+
*
|
|
5258
|
+
* Only one thing reads it: what a failed download KEEPS for the next press
|
|
5259
|
+
* (`PresetHeld.key` in ./preset). Editing the rules or the pictures below must bump
|
|
5260
|
+
* it, or a retry could staple a wallpaper from the old set to a config from the new
|
|
5261
|
+
* one — the same reason the remote profile was compared by value rather than
|
|
5262
|
+
* assumed. Nothing else in the plugin persists it.
|
|
5263
|
+
*/
|
|
5264
|
+
const RECOMMENDED_REVISION = "builtin-1";
|
|
5265
|
+
/**
|
|
5266
|
+
* One rule of the profile: the match string, its picture and that picture's color.
|
|
5267
|
+
*
|
|
5268
|
+
* The color is a per-image value (schema 10), and the triple here is the one the
|
|
5269
|
+
* profile has always carried — an HSL accent per model, used to tint the interface
|
|
5270
|
+
* while its wallpaper is on screen.
|
|
5271
|
+
*/
|
|
5272
|
+
function profileRule(id, match, slot, color) {
|
|
5273
|
+
return {
|
|
5274
|
+
id,
|
|
5275
|
+
images: [defaultImage(slot, color, "fill")],
|
|
5276
|
+
match,
|
|
5277
|
+
enabled: true,
|
|
5278
|
+
color: null,
|
|
5279
|
+
rotate: defaultRotation()
|
|
5280
|
+
};
|
|
5281
|
+
}
|
|
5282
|
+
/**
|
|
5283
|
+
* The profile, as a fresh config every call.
|
|
5284
|
+
*
|
|
5285
|
+
* ORDER IS THE FALLBACK: matching runs top→bottom and the first usable rule is what
|
|
5286
|
+
* an unmatched model gets, so `deepseek` leads — the model this plugin was written
|
|
5287
|
+
* for. Everything below the rules is the interface numbers a fresh install already
|
|
5288
|
+
* starts on (`DEFAULT_PART_OPACITIES` and friends), which is what makes installing
|
|
5289
|
+
* the profile "add the rules and the pictures" on a clean store.
|
|
5290
|
+
*
|
|
5291
|
+
* Deliberately NOT taken from `freshThemeConfig()`: that one seeds a single BLANK
|
|
5292
|
+
* rule, and this one must not inherit it.
|
|
5293
|
+
*/
|
|
5294
|
+
function recommendedConfig() {
|
|
5295
|
+
return {
|
|
5296
|
+
rules: [
|
|
5297
|
+
profileRule("r1", "deepseek", "m3", [
|
|
5298
|
+
191.88931210132625,
|
|
5299
|
+
.5046198735613555,
|
|
5300
|
+
.82
|
|
5301
|
+
]),
|
|
5302
|
+
profileRule("r2", "gpt", "m1", [
|
|
5303
|
+
263.5122459614382,
|
|
5304
|
+
.31076923076923096,
|
|
5305
|
+
.82
|
|
5306
|
+
]),
|
|
5307
|
+
profileRule("r3", "claude", "m2", [
|
|
5308
|
+
18.923814597762384,
|
|
5309
|
+
.5486699795381468,
|
|
5310
|
+
.6
|
|
5311
|
+
]),
|
|
5312
|
+
profileRule("r4", "qwen", "m4", [
|
|
5313
|
+
26.25977410947,
|
|
5314
|
+
.6228354978354975,
|
|
5315
|
+
.82
|
|
5316
|
+
]),
|
|
5317
|
+
profileRule("r5", "glm", "m5", [
|
|
5318
|
+
30.15559737458117,
|
|
5319
|
+
.6552284141830319,
|
|
5320
|
+
.7646280172718907
|
|
5321
|
+
]),
|
|
5322
|
+
profileRule("r6", "kimi", "m6", [
|
|
5323
|
+
229.39645097591443,
|
|
5324
|
+
.5752269829617896,
|
|
5325
|
+
.2
|
|
5326
|
+
]),
|
|
5327
|
+
profileRule("r7", "grok", "m7", [
|
|
5328
|
+
217.3791348600509,
|
|
5329
|
+
.7702645947081058,
|
|
5330
|
+
.2
|
|
5331
|
+
])
|
|
5332
|
+
],
|
|
5333
|
+
opacities: { ...DEFAULT_PART_OPACITIES },
|
|
5334
|
+
transition: defaultTransition(),
|
|
5335
|
+
settingsOpacity: 1,
|
|
5336
|
+
chatTextOpacity: DEFAULT_CHAT_TEXT_OPACITY,
|
|
5337
|
+
trajectoryOpacity: DEFAULT_TRAJECTORY_OPACITY,
|
|
5338
|
+
rightbarOpacity: DEFAULT_RIGHTBAR_OPACITY,
|
|
5339
|
+
pageOpacity: 0,
|
|
5340
|
+
autoExtract: true,
|
|
5341
|
+
holidays: {
|
|
5342
|
+
enabled: true,
|
|
5343
|
+
items: defaultHolidayRules()
|
|
5344
|
+
}
|
|
5345
|
+
};
|
|
5346
|
+
}
|
|
5347
|
+
/**
|
|
5348
|
+
* The profile as the document both imports consume — the same shape `exportTheme`
|
|
5349
|
+
* writes, so everything downstream (the wipe, the slot loop) is the hand-picked
|
|
5350
|
+
* file's path.
|
|
5351
|
+
*
|
|
5352
|
+
* `images` is filled by the caller as the downloads land, which is what makes a
|
|
5353
|
+
* partially downloaded profile a resumable one rather than an all-or-nothing press.
|
|
5354
|
+
*/
|
|
5355
|
+
function recommendedThemeFile(images = {}) {
|
|
5356
|
+
return {
|
|
5357
|
+
version: 10,
|
|
5358
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5359
|
+
config: recommendedConfig(),
|
|
5360
|
+
images
|
|
5361
|
+
};
|
|
5362
|
+
}
|
|
5102
5363
|
//#endregion
|
|
5103
5364
|
//#region src/client/repaint.ts
|
|
5104
5365
|
/**
|
|
@@ -5222,37 +5483,80 @@ window.__ModuleLoader__.load({
|
|
|
5222
5483
|
//#region src/client/components/LiveSlider.tsx
|
|
5223
5484
|
/**
|
|
5224
5485
|
* Zero-lag slider: the thumb and value label update through DOM refs while
|
|
5225
|
-
* dragging
|
|
5226
|
-
*
|
|
5227
|
-
*
|
|
5228
|
-
*
|
|
5486
|
+
* dragging, so the caller can preview the change without a React re-render; the
|
|
5487
|
+
* settled value is committed ONCE, when the gesture ends.
|
|
5488
|
+
*
|
|
5489
|
+
* The split is the whole point and it is not an optimisation detail. A range
|
|
5490
|
+
* input fires `input` on every frame of a drag, and React's `change` handler for
|
|
5491
|
+
* one is wired to that same event — so a slider that commits from `onChange`
|
|
5492
|
+
* commits dozens of times a second, and every commit is a full repaint (skin,
|
|
5493
|
+
* palette, wallpaper layer). That is why dragging a wallpaper effect cost a whole
|
|
5494
|
+
* `applyActive()` per frame while the cheap live writers beside it were never
|
|
5495
|
+
* reached.
|
|
5496
|
+
*
|
|
5497
|
+
* The gesture's end is therefore read from the events that actually end one: the
|
|
5498
|
+
* native `change` event, a pointer released over the track, and a blur. No timer
|
|
5499
|
+
* is armed — nothing here may compete with the plugin's one scheduler.
|
|
5229
5500
|
*/
|
|
5230
5501
|
function LiveSlider({ min, max, step, def, fmt, label, ariaLabel, onInput, onChange }) {
|
|
5231
5502
|
const inputRef = (0, react.useRef)(null);
|
|
5232
5503
|
const valRef = (0, react.useRef)(null);
|
|
5504
|
+
/**
|
|
5505
|
+
* The value the current gesture has reached but has not committed yet.
|
|
5506
|
+
*
|
|
5507
|
+
* Non-null is also the answer to "is a gesture in flight?", which the `def` sync
|
|
5508
|
+
* below has to ask: the model watch re-emits on its own beat, and the render that
|
|
5509
|
+
* causes would otherwise snap the knob back to the committed value mid-drag.
|
|
5510
|
+
*/
|
|
5511
|
+
const pending = (0, react.useRef)(null);
|
|
5512
|
+
/** `onChange` is read through a ref so the native listener cannot go stale. */
|
|
5513
|
+
const commitRef = (0, react.useRef)(onChange);
|
|
5514
|
+
commitRef.current = onChange;
|
|
5233
5515
|
const paint = (el, v) => {
|
|
5234
5516
|
el.style.setProperty("--pct", String((v - min) / (max - min) * 100));
|
|
5235
5517
|
};
|
|
5236
|
-
|
|
5518
|
+
/** Move the knob and the label without calling anybody back. */
|
|
5519
|
+
const show = (v) => {
|
|
5237
5520
|
if (inputRef.current) {
|
|
5238
|
-
inputRef.current.value = String(
|
|
5239
|
-
paint(inputRef.current,
|
|
5521
|
+
inputRef.current.value = String(v);
|
|
5522
|
+
paint(inputRef.current, v);
|
|
5240
5523
|
}
|
|
5241
|
-
if (valRef.current) valRef.current.textContent = fmt(
|
|
5524
|
+
if (valRef.current) valRef.current.textContent = fmt(v);
|
|
5525
|
+
};
|
|
5526
|
+
const commit = () => {
|
|
5527
|
+
const v = pending.current;
|
|
5528
|
+
if (v === null) return;
|
|
5529
|
+
pending.current = null;
|
|
5530
|
+
commitRef.current(v);
|
|
5531
|
+
};
|
|
5532
|
+
/** One frame of a gesture: the caller's cheap path, then remember the value. */
|
|
5533
|
+
const live = (v) => {
|
|
5534
|
+
show(v);
|
|
5535
|
+
pending.current = v;
|
|
5536
|
+
onInput?.(v);
|
|
5537
|
+
};
|
|
5538
|
+
(0, react.useEffect)(() => {
|
|
5539
|
+
if (pending.current !== null) return;
|
|
5540
|
+
show(def);
|
|
5242
5541
|
}, [
|
|
5243
5542
|
def,
|
|
5244
5543
|
fmt,
|
|
5245
5544
|
min,
|
|
5246
5545
|
max
|
|
5247
5546
|
]);
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5547
|
+
(0, react.useEffect)(() => {
|
|
5548
|
+
const el = inputRef.current;
|
|
5549
|
+
if (el === null) return;
|
|
5550
|
+
const onEnd = () => commit();
|
|
5551
|
+
el.addEventListener("change", onEnd);
|
|
5552
|
+
return () => {
|
|
5553
|
+
el.removeEventListener("change", onEnd);
|
|
5554
|
+
commit();
|
|
5555
|
+
};
|
|
5556
|
+
}, []);
|
|
5557
|
+
const reset = () => {
|
|
5558
|
+
live(def);
|
|
5559
|
+
commit();
|
|
5256
5560
|
};
|
|
5257
5561
|
const name = label ?? ariaLabel;
|
|
5258
5562
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -5277,15 +5581,10 @@ window.__ModuleLoader__.load({
|
|
|
5277
5581
|
defaultValue: def,
|
|
5278
5582
|
"aria-label": name,
|
|
5279
5583
|
title: name !== void 0 ? `${name} · ${fmt(def)}` : fmt(def),
|
|
5280
|
-
onDoubleClick:
|
|
5281
|
-
onInput: (e) =>
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
paint(el, v);
|
|
5285
|
-
onInput?.(v);
|
|
5286
|
-
if (valRef.current) valRef.current.textContent = fmt(v);
|
|
5287
|
-
},
|
|
5288
|
-
onChange: (e) => onChange(Number(e.target.value))
|
|
5584
|
+
onDoubleClick: reset,
|
|
5585
|
+
onInput: (e) => live(Number(e.target.value)),
|
|
5586
|
+
onPointerUp: commit,
|
|
5587
|
+
onBlur: commit
|
|
5289
5588
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5290
5589
|
ref: valRef,
|
|
5291
5590
|
className: "dab-slider-val",
|
|
@@ -6547,49 +6846,92 @@ window.__ModuleLoader__.load({
|
|
|
6547
6846
|
});
|
|
6548
6847
|
}
|
|
6549
6848
|
/**
|
|
6550
|
-
* The
|
|
6551
|
-
* single track edits it.
|
|
6849
|
+
* The selected image's three paint effects on ONE row: the menu names the target
|
|
6850
|
+
* and a single track edits it.
|
|
6851
|
+
*
|
|
6852
|
+
* Per IMAGE, like the layout mode, the framing and the colour above it. The move
|
|
6853
|
+
* is the same argument: a rotation through a daylight photo and a night shot does
|
|
6854
|
+
* not want one exposure, and a rule-level value forced one of them to live at the
|
|
6855
|
+
* other's compromise. A rule with no picture has nothing to expose, so the row is
|
|
6856
|
+
* DISABLED (not hidden) there — the same way the layout chips behave.
|
|
6552
6857
|
*
|
|
6553
6858
|
* The three ranges are not interchangeable — opacity and brightness are
|
|
6554
6859
|
* percentages (brightness ceilings at the shared `WALLPAPER_BRIGHTNESS_MAX`),
|
|
6555
6860
|
* blur is pixels — so changing the menu swaps the whole track: limits, formatter
|
|
6556
6861
|
* and writer together. It never reinterprets one track under another name. The
|
|
6557
|
-
* `key` remounts the input so `defaultValue` starts at the new
|
|
6558
|
-
*
|
|
6559
|
-
*
|
|
6862
|
+
* `key` remounts the input so `defaultValue` starts at the new target's value.
|
|
6863
|
+
*
|
|
6864
|
+
* A drag has TWO writers, and which one runs is the whole reason this row is not
|
|
6865
|
+
* a one-liner. `onInput` runs on every frame and must cost a wallpaper-layer
|
|
6866
|
+
* property write (./wallpaper's `setWpOpacity` / `setWpFilter`) — the same cheap
|
|
6867
|
+
* path the Interface page uses. `onChange` runs once, when the gesture ends, and
|
|
6868
|
+
* is the one that goes through `setImageEffects`: that is a full repaint (skin,
|
|
6869
|
+
* palette, wallpaper), and paying it per frame made a drag stall on a big image.
|
|
6560
6870
|
*/
|
|
6561
|
-
function WallpaperEffectRow({ p, rule, field, onField }) {
|
|
6871
|
+
function WallpaperEffectRow({ p, rule, image, painted, field, onField, active }) {
|
|
6562
6872
|
const { t } = p;
|
|
6873
|
+
/**
|
|
6874
|
+
* One frame of a drag: write the image in place, then touch only the layer
|
|
6875
|
+
* property that field owns.
|
|
6876
|
+
*
|
|
6877
|
+
* Gated on `active && painted`: the wallpaper layer paints the ACTIVE rule's
|
|
6878
|
+
* CURRENT image, so previewing any other one would write this card's numbers onto
|
|
6879
|
+
* a different picture's pixels — the visible lie the per-image split makes
|
|
6880
|
+
* possible and this gate prevents. The commit still lands, addressed by slot.
|
|
6881
|
+
*
|
|
6882
|
+
* Writing in place rather than through `setImageEffects` is deliberate and narrow:
|
|
6883
|
+
* that one bumps `rulesRev` and closes with a repaint, which is exactly the
|
|
6884
|
+
* per-frame cost being removed. Nothing is lost — the gesture's `onChange` hands
|
|
6885
|
+
* the settled value over, and that path clamps and saves.
|
|
6886
|
+
*/
|
|
6887
|
+
const preview = (patch, paint) => {
|
|
6888
|
+
if (!active || !painted || image === void 0) return;
|
|
6889
|
+
Object.assign(image, patch);
|
|
6890
|
+
paint();
|
|
6891
|
+
};
|
|
6892
|
+
const slot = image?.slot;
|
|
6563
6893
|
const spec = field === "opacity" ? {
|
|
6564
6894
|
label: t("ruleOpacity"),
|
|
6565
6895
|
min: 0,
|
|
6566
6896
|
max: 100,
|
|
6567
6897
|
step: 1,
|
|
6568
|
-
def: Math.round(
|
|
6898
|
+
def: Math.round((image?.opacity ?? 1) * 100),
|
|
6569
6899
|
fmt: (v) => `${v}%`,
|
|
6570
|
-
|
|
6900
|
+
preview: (v) => preview({ opacity: v / 100 }, () => setWpOpacity(v / 100)),
|
|
6901
|
+
onChange: (v) => {
|
|
6902
|
+
if (slot !== void 0) p.setImageEffects(rule.id, slot, { opacity: v / 100 });
|
|
6903
|
+
}
|
|
6571
6904
|
} : field === "brightness" ? {
|
|
6572
6905
|
label: t("ruleBrightness"),
|
|
6573
6906
|
min: 0,
|
|
6574
6907
|
max: 200,
|
|
6575
6908
|
step: 1,
|
|
6576
|
-
def:
|
|
6909
|
+
def: image?.brightness ?? 100,
|
|
6577
6910
|
fmt: (v) => `${v}%`,
|
|
6578
|
-
|
|
6911
|
+
preview: (v) => preview({ brightness: v }, setWpFilter),
|
|
6912
|
+
onChange: (v) => {
|
|
6913
|
+
if (slot !== void 0) p.setImageEffects(rule.id, slot, { brightness: v });
|
|
6914
|
+
}
|
|
6579
6915
|
} : {
|
|
6580
6916
|
label: t("ruleBlur"),
|
|
6581
6917
|
min: 0,
|
|
6582
6918
|
max: 60,
|
|
6583
6919
|
step: 1,
|
|
6584
|
-
def:
|
|
6920
|
+
def: image?.blur ?? 0,
|
|
6585
6921
|
fmt: (v) => `${v}px`,
|
|
6586
|
-
|
|
6922
|
+
preview: (v) => preview({ blur: v }, setWpFilter),
|
|
6923
|
+
onChange: (v) => {
|
|
6924
|
+
if (slot !== void 0) p.setImageEffects(rule.id, slot, { blur: v });
|
|
6925
|
+
}
|
|
6587
6926
|
};
|
|
6927
|
+
const disabled = image === void 0;
|
|
6588
6928
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6589
6929
|
className: "dab-wp-row",
|
|
6590
6930
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
6591
6931
|
className: "dab-select",
|
|
6592
6932
|
value: field,
|
|
6933
|
+
disabled,
|
|
6934
|
+
title: disabled ? t("ruleEffectNoImage") : void 0,
|
|
6593
6935
|
"aria-label": t("ruleEffectPick"),
|
|
6594
6936
|
onChange: (e) => onField(e.target.value),
|
|
6595
6937
|
children: [
|
|
@@ -6613,8 +6955,9 @@ window.__ModuleLoader__.load({
|
|
|
6613
6955
|
step: spec.step,
|
|
6614
6956
|
def: spec.def,
|
|
6615
6957
|
fmt: spec.fmt,
|
|
6958
|
+
onInput: spec.preview,
|
|
6616
6959
|
onChange: spec.onChange
|
|
6617
|
-
}, field)]
|
|
6960
|
+
}, `${field}:${slot ?? ""}`)]
|
|
6618
6961
|
});
|
|
6619
6962
|
}
|
|
6620
6963
|
function RuleCard({ p, rule, index, total, active, liveIndex, notify, sel, onSel, field, onField, onRemove }) {
|
|
@@ -6642,6 +6985,15 @@ window.__ModuleLoader__.load({
|
|
|
6642
6985
|
const selIdx = rule.images.length === 0 ? 0 : Math.min(sel, rule.images.length - 1);
|
|
6643
6986
|
const image = rule.images[selIdx];
|
|
6644
6987
|
const url = image === void 0 ? null : p.imageOf(image.slot);
|
|
6988
|
+
/**
|
|
6989
|
+
* Whether the image being EDITED is also the one on screen.
|
|
6990
|
+
*
|
|
6991
|
+
* The wallpaper layer paints the active rule's CURRENT image, so a drag preview
|
|
6992
|
+
* may only write a layer property when the two coincide — otherwise the card's
|
|
6993
|
+
* numbers would land on another picture's pixels. This is the per-image split's
|
|
6994
|
+
* own version of the `active` gate beside it.
|
|
6995
|
+
*/
|
|
6996
|
+
const painted = active && selIdx === liveIndex;
|
|
6645
6997
|
const ownColor = image === void 0 ? rule.color : image.color;
|
|
6646
6998
|
const setColor = (color) => {
|
|
6647
6999
|
if (image === void 0) p.setRule(rule.id, { color });
|
|
@@ -7215,14 +7567,21 @@ window.__ModuleLoader__.load({
|
|
|
7215
7567
|
})
|
|
7216
7568
|
]
|
|
7217
7569
|
})] }),
|
|
7218
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
7570
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7219
7571
|
style: { marginTop: 16 },
|
|
7220
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WallpaperEffectRow, {
|
|
7572
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WallpaperEffectRow, {
|
|
7221
7573
|
p,
|
|
7222
7574
|
rule,
|
|
7575
|
+
image,
|
|
7576
|
+
painted,
|
|
7223
7577
|
field,
|
|
7224
|
-
onField
|
|
7225
|
-
|
|
7578
|
+
onField,
|
|
7579
|
+
active
|
|
7580
|
+
}), image === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7581
|
+
className: "dab-hint",
|
|
7582
|
+
style: { marginTop: 8 },
|
|
7583
|
+
children: t("ruleEffectNoImage")
|
|
7584
|
+
}) : null]
|
|
7226
7585
|
}),
|
|
7227
7586
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7228
7587
|
style: { marginTop: 14 },
|
|
@@ -7459,19 +7818,15 @@ window.__ModuleLoader__.load({
|
|
|
7459
7818
|
/**
|
|
7460
7819
|
* The ways the recommended-profile download can fail, mapped to their copy.
|
|
7461
7820
|
*
|
|
7462
|
-
*
|
|
7463
|
-
*
|
|
7464
|
-
*
|
|
7465
|
-
*
|
|
7466
|
-
*
|
|
7467
|
-
*
|
|
7468
|
-
* one is the author's file naming, and only the slot tells them which.
|
|
7821
|
+
* Two cases, because the profile is now only PICTURES: the rules and their numbers
|
|
7822
|
+
* are compiled in, so there is no manifest to be malformed, no shape to be too new
|
|
7823
|
+
* and no config to name too many slots. What is left is the network ("retry") and
|
|
7824
|
+
* the cap on the total ("nobody should install this"). A failure that names a SLOT
|
|
7825
|
+
* is rendered separately — that one is about one BFS address, and the slot is what
|
|
7826
|
+
* says which.
|
|
7469
7827
|
*/
|
|
7470
7828
|
const REC_ERROR_KEYS = {
|
|
7471
7829
|
"download failed": "recFailNetwork",
|
|
7472
|
-
"not a profile": "recFailNotProfile",
|
|
7473
|
-
"newer version": "recFailNewer",
|
|
7474
|
-
"too many images": "recFailTooMany",
|
|
7475
7830
|
"too large": "recFailTooLarge"
|
|
7476
7831
|
};
|
|
7477
7832
|
function ProfilePage({ p, notify }) {
|
|
@@ -8155,12 +8510,12 @@ window.__ModuleLoader__.load({
|
|
|
8155
8510
|
/**
|
|
8156
8511
|
* Ceiling on what one recommended profile may add up to, across every wallpaper.
|
|
8157
8512
|
*
|
|
8158
|
-
* The node half caps
|
|
8159
|
-
*
|
|
8160
|
-
*
|
|
8161
|
-
*
|
|
8162
|
-
*
|
|
8163
|
-
* button press into half a gigabyte
|
|
8513
|
+
* The node half caps ONE file, but it answers one independent call at a time and
|
|
8514
|
+
* never sees a running total — the client is the only side that does, so the total
|
|
8515
|
+
* is bounded here. The real profile is 2.2 MB of images across seven of them, so
|
|
8516
|
+
* this is generous by design: it exists so that a build which grew a wrong URL
|
|
8517
|
+
* (or an edge that served an upload instead of the transform) cannot turn one
|
|
8518
|
+
* button press into half a gigabyte.
|
|
8164
8519
|
*/
|
|
8165
8520
|
const PRESET_TOTAL_MAX = 33554432;
|
|
8166
8521
|
/**
|
|
@@ -8171,7 +8526,8 @@ window.__ModuleLoader__.load({
|
|
|
8171
8526
|
* CDN edge was still cold). What is being paid is per-request latency, and the
|
|
8172
8527
|
* host speaks HTTP/2 — the bytes are identical either way and the connection is
|
|
8173
8528
|
* reused — so overlapping them buys wall-clock time and risks nothing but a
|
|
8174
|
-
* little contention.
|
|
8529
|
+
* little contention. The same holds for the Bilibili edges the pictures come from
|
|
8530
|
+
* now (see `PROFILE_HOSTS` in ../profile).
|
|
8175
8531
|
*
|
|
8176
8532
|
* Four rather than all seven: a burst that wide competes for the same bandwidth
|
|
8177
8533
|
* and makes the per-file timeout harder to reason about, and four already hides
|
|
@@ -8550,13 +8906,7 @@ window.__ModuleLoader__.load({
|
|
|
8550
8906
|
const added = [];
|
|
8551
8907
|
for (const dataUrl of dataUrls) {
|
|
8552
8908
|
const slot = nextSlot();
|
|
8553
|
-
|
|
8554
|
-
slot,
|
|
8555
|
-
bgMode: "fit",
|
|
8556
|
-
bgState: { ...DEFAULT_BG_STATE },
|
|
8557
|
-
color: null
|
|
8558
|
-
};
|
|
8559
|
-
rule.images.push(image);
|
|
8909
|
+
rule.images.push(defaultImage(slot));
|
|
8560
8910
|
added.push(slot);
|
|
8561
8911
|
setImage(slot, dataUrl);
|
|
8562
8912
|
writeImage(slot, dataUrl);
|
|
@@ -8632,6 +8982,28 @@ window.__ModuleLoader__.load({
|
|
|
8632
8982
|
saveConfig();
|
|
8633
8983
|
repaintIfMoved(id, winner);
|
|
8634
8984
|
},
|
|
8985
|
+
/**
|
|
8986
|
+
* Store ONE image's three paint effects. Addressed by slot like the mode and
|
|
8987
|
+
* the framing: how a picture is exposed belongs to that picture, and a rule's
|
|
8988
|
+
* rotation can run a daylight photo next to a night shot.
|
|
8989
|
+
*
|
|
8990
|
+
* `normalizeRuleInPlace` is the clamp, so a drag can never write a value the
|
|
8991
|
+
* next load would trim; the layer's own preview path (`setWpOpacity` /
|
|
8992
|
+
* `setWpFilter`) has already painted the value for the frames of the gesture.
|
|
8993
|
+
*/
|
|
8994
|
+
setImageEffects: (id, slot, patch) => {
|
|
8995
|
+
const rule = ruleById(id);
|
|
8996
|
+
const image = rule?.images.find((i) => i.slot === slot);
|
|
8997
|
+
if (rule === null || image === void 0) return;
|
|
8998
|
+
const winner = winnerId();
|
|
8999
|
+
if (patch.opacity !== void 0) image.opacity = patch.opacity;
|
|
9000
|
+
if (patch.brightness !== void 0) image.brightness = patch.brightness;
|
|
9001
|
+
if (patch.blur !== void 0) image.blur = patch.blur;
|
|
9002
|
+
normalizeRuleInPlace(rule);
|
|
9003
|
+
rulesRev++;
|
|
9004
|
+
saveConfig();
|
|
9005
|
+
repaintIfMoved(id, winner);
|
|
9006
|
+
},
|
|
8635
9007
|
setRuleRotation: (id, patch) => {
|
|
8636
9008
|
const rule = ruleById(id);
|
|
8637
9009
|
if (rule === null) return;
|
|
@@ -8684,12 +9056,7 @@ window.__ModuleLoader__.load({
|
|
|
8684
9056
|
const res = await fetchImageUrl(slot, url);
|
|
8685
9057
|
if (!res.ok) return res;
|
|
8686
9058
|
const winner = winnerId();
|
|
8687
|
-
rule.images.push(
|
|
8688
|
-
slot,
|
|
8689
|
-
bgMode: "fit",
|
|
8690
|
-
bgState: { ...DEFAULT_BG_STATE },
|
|
8691
|
-
color: null
|
|
8692
|
-
});
|
|
9059
|
+
rule.images.push(defaultImage(slot));
|
|
8693
9060
|
setImage(slot, res.dataUrl ?? null);
|
|
8694
9061
|
rulesRev++;
|
|
8695
9062
|
persistConfig();
|
|
@@ -8786,30 +9153,28 @@ window.__ModuleLoader__.load({
|
|
|
8786
9153
|
}
|
|
8787
9154
|
},
|
|
8788
9155
|
/**
|
|
8789
|
-
* Fetch the
|
|
8790
|
-
* separate, because the page has to ask before anything is destroyed and
|
|
8791
|
-
* question should describe what is actually in hand.
|
|
9156
|
+
* Fetch the recommended profile's wallpapers. Downloads only — applying them
|
|
9157
|
+
* is separate, because the page has to ask before anything is destroyed and
|
|
9158
|
+
* the question should describe what is actually in hand.
|
|
8792
9159
|
*
|
|
8793
|
-
* The
|
|
8794
|
-
*
|
|
8795
|
-
*
|
|
8796
|
-
*
|
|
8797
|
-
*
|
|
9160
|
+
* The RULES are not fetched at all: they are compiled into the bundle
|
|
9161
|
+
* (./profile), so this is a set of picture transfers and nothing else. That
|
|
9162
|
+
* is why there is no manifest round, no shape gate and no "not a profile"
|
|
9163
|
+
* failure left — nothing remote can decide what this installs.
|
|
9164
|
+
*
|
|
9165
|
+
* Each slot is one request, up to `PRESET_CONCURRENCY` at a time, and each
|
|
9166
|
+
* file keeps its own host fallback, retry rounds (the node half's business,
|
|
9167
|
+
* ./preset), validation and slot name in a failure.
|
|
8798
9168
|
*
|
|
8799
9169
|
* STRICT: the first wallpaper that cannot be had stops the queue. What had
|
|
8800
9170
|
* already arrived is kept for the next press (`presetHeld` below) — the
|
|
8801
9171
|
* difference between retrying one file and retrying the lot. Nothing is
|
|
8802
|
-
* touched here, so
|
|
8803
|
-
*
|
|
9172
|
+
* touched here, so a wrong URL arrives as an error naming the slot rather
|
|
9173
|
+
* than as a profile quietly missing a picture.
|
|
8804
9174
|
*/
|
|
8805
9175
|
fetchRecommended: async (onProgress) => {
|
|
8806
|
-
const
|
|
8807
|
-
|
|
8808
|
-
ok: false,
|
|
8809
|
-
error: head.error
|
|
8810
|
-
};
|
|
8811
|
-
const slots = head.slots;
|
|
8812
|
-
const key = presetKey(head.version, head.config);
|
|
9176
|
+
const slots = PROFILE_SLOTS;
|
|
9177
|
+
const key = RECOMMENDED_REVISION;
|
|
8813
9178
|
const images = presetResume(presetHeld, key);
|
|
8814
9179
|
let bytes = 0;
|
|
8815
9180
|
for (const url of Object.values(images)) bytes += url.length;
|
|
@@ -8857,12 +9222,7 @@ window.__ModuleLoader__.load({
|
|
|
8857
9222
|
presetHeld = null;
|
|
8858
9223
|
return {
|
|
8859
9224
|
ok: true,
|
|
8860
|
-
preset:
|
|
8861
|
-
version: head.version,
|
|
8862
|
-
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8863
|
-
config: head.config,
|
|
8864
|
-
images
|
|
8865
|
-
}
|
|
9225
|
+
preset: recommendedThemeFile(images)
|
|
8866
9226
|
};
|
|
8867
9227
|
},
|
|
8868
9228
|
/** Replace EVERYTHING with a downloaded profile: empty the store, then write it. */
|