dsh-background-by-model 0.5.4 → 0.6.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 +23 -1
- package/CHANGELOG.zh.md +23 -1
- package/README.md +30 -15
- package/README.zh.md +30 -15
- package/holiday/mid-autumn.webp +0 -0
- package/holiday/national-day.webp +0 -0
- package/lib/client.js +425 -64
- package/lib/client.js.map +1 -1
- package/lib/index.js +180 -4
- package/package.json +7 -3
package/lib/client.js
CHANGED
|
@@ -21,7 +21,6 @@ window.__ModuleLoader__.load({
|
|
|
21
21
|
const zh = {
|
|
22
22
|
nav: "模型背景",
|
|
23
23
|
brandTag: "按模型换背景",
|
|
24
|
-
close: "关闭",
|
|
25
24
|
pageInterface: "界面",
|
|
26
25
|
pageModelBg: "模型背景",
|
|
27
26
|
pageProfile: "配置",
|
|
@@ -58,7 +57,6 @@ window.__ModuleLoader__.load({
|
|
|
58
57
|
autoExtractHint: "只在规则还没有颜色时自动取色,绝不覆盖你手动选的颜色",
|
|
59
58
|
ruleAdd: "新增规则",
|
|
60
59
|
ruleFallbackBadge: "兜底",
|
|
61
|
-
ruleMatchLabel: "匹配串",
|
|
62
60
|
ruleMatchPlaceholder: "如 flash / glm(忽略大小写,留空则不参与匹配)",
|
|
63
61
|
ruleEnabled: "启用",
|
|
64
62
|
ruleUp: "上移",
|
|
@@ -77,21 +75,16 @@ window.__ModuleLoader__.load({
|
|
|
77
75
|
ruleImageRemove: "移除图片",
|
|
78
76
|
ruleEmptyHint: "这条规则还没有图片,不会被匹配到,也不会作为兜底",
|
|
79
77
|
ruleColor: "主题色",
|
|
80
|
-
ruleColorNone: "
|
|
78
|
+
ruleColorNone: "跟随系统主题",
|
|
81
79
|
ruleColorNoneHint: "未设颜色时跟随系统主题:界面用宿主的配色,壁纸与各部位的透明度、模糊照旧生效",
|
|
82
80
|
ruleColorPick: "自定义颜色",
|
|
83
81
|
ruleColorPickHint: "优先从本图取色,取不到就用默认色",
|
|
84
82
|
ruleColorExtract: "从本图提取",
|
|
85
83
|
ruleLayout: "布局模式",
|
|
86
|
-
ruleFraming: "取景",
|
|
87
84
|
ruleFramingEdit: "编辑位置",
|
|
88
|
-
ruleFramingLocked: "仅「适应」模式可编辑位置",
|
|
89
85
|
ruleOpacity: "背景透明度",
|
|
90
86
|
ruleBlur: "背景模糊",
|
|
91
87
|
ruleBlurHint: "背景模糊作用于壁纸层;界面页里的模糊作用于界面各区域,两者互不影响",
|
|
92
|
-
colorHint: "在色轮外圈选择色相,在内部方形中调整饱和度和明度;双击滑块可恢复默认值",
|
|
93
|
-
swatchTitle: "灵感色板",
|
|
94
|
-
hexCaption: "当前颜色",
|
|
95
88
|
bgModeFit: "适应",
|
|
96
89
|
bgModeFill: "填充",
|
|
97
90
|
bgModeStretch: "拉伸",
|
|
@@ -102,9 +95,7 @@ window.__ModuleLoader__.load({
|
|
|
102
95
|
editorCommit: "确认",
|
|
103
96
|
editorCancel: "取消",
|
|
104
97
|
editorReset: "重置",
|
|
105
|
-
extractColor: "从图片提取主题色",
|
|
106
98
|
extracting: "取色中…",
|
|
107
|
-
extractNoWp: "请先为该规则选择图片",
|
|
108
99
|
extractDone: "已应用图片配色",
|
|
109
100
|
extractFail: "未在这张图里找到鲜明的颜色,请换一张",
|
|
110
101
|
eyedropper: "从图片取色",
|
|
@@ -123,12 +114,13 @@ window.__ModuleLoader__.load({
|
|
|
123
114
|
toastExportDone: "配置文件已开始下载",
|
|
124
115
|
importDone: "已导入配置",
|
|
125
116
|
importFail: "导入失败,文件格式不正确",
|
|
126
|
-
footerTag: "按模型换背景插件"
|
|
117
|
+
footerTag: "按模型换背景插件",
|
|
118
|
+
holidayTitle: "节日特殊背景",
|
|
119
|
+
holidayEnable: "启用节日特殊背景(中秋当天、国庆 10 月 1–7 日)"
|
|
127
120
|
};
|
|
128
121
|
const en = {
|
|
129
122
|
nav: "Model background",
|
|
130
123
|
brandTag: "Per-model wallpaper",
|
|
131
|
-
close: "Close",
|
|
132
124
|
pageInterface: "Interface",
|
|
133
125
|
pageModelBg: "Model background",
|
|
134
126
|
pageProfile: "Profile",
|
|
@@ -165,7 +157,6 @@ window.__ModuleLoader__.load({
|
|
|
165
157
|
autoExtractHint: "Only fills a rule that has no color yet — a color you picked is never overwritten",
|
|
166
158
|
ruleAdd: "Add rule",
|
|
167
159
|
ruleFallbackBadge: "fallback",
|
|
168
|
-
ruleMatchLabel: "Match",
|
|
169
160
|
ruleMatchPlaceholder: "e.g. flash / glm (case-insensitive, empty never matches)",
|
|
170
161
|
ruleEnabled: "Enabled",
|
|
171
162
|
ruleUp: "Move up",
|
|
@@ -184,21 +175,16 @@ window.__ModuleLoader__.load({
|
|
|
184
175
|
ruleImageRemove: "Remove image",
|
|
185
176
|
ruleEmptyHint: "This rule has no image — it can neither match nor serve as the fallback",
|
|
186
177
|
ruleColor: "Theme color",
|
|
187
|
-
ruleColorNone: "
|
|
178
|
+
ruleColorNone: "Follow system theme",
|
|
188
179
|
ruleColorNoneHint: "Without a color this rule follows the system theme: the host palette paints the interface while the wallpaper and every opacity/blur slider keep working",
|
|
189
180
|
ruleColorPick: "Custom color",
|
|
190
181
|
ruleColorPickHint: "Extracts from this image when it can, otherwise uses a default color",
|
|
191
182
|
ruleColorExtract: "Extract from image",
|
|
192
183
|
ruleLayout: "Layout mode",
|
|
193
|
-
ruleFraming: "Framing",
|
|
194
184
|
ruleFramingEdit: "Edit position",
|
|
195
|
-
ruleFramingLocked: "Position editing is only available in Fit mode",
|
|
196
185
|
ruleOpacity: "Background opacity",
|
|
197
186
|
ruleBlur: "Background blur",
|
|
198
187
|
ruleBlurHint: "Background blur affects the wallpaper layer; the blur on the Interface page affects interface surfaces — they are independent",
|
|
199
|
-
colorHint: "Pick hue on the outer ring, adjust saturation & lightness in the square; double-click a slider to reset",
|
|
200
|
-
swatchTitle: "Quick swatches",
|
|
201
|
-
hexCaption: "Current color",
|
|
202
188
|
bgModeFit: "Fit",
|
|
203
189
|
bgModeFill: "Fill",
|
|
204
190
|
bgModeStretch: "Stretch",
|
|
@@ -209,9 +195,7 @@ window.__ModuleLoader__.load({
|
|
|
209
195
|
editorCommit: "Confirm",
|
|
210
196
|
editorCancel: "Cancel",
|
|
211
197
|
editorReset: "Reset",
|
|
212
|
-
extractColor: "Extract from image",
|
|
213
198
|
extracting: "Extracting…",
|
|
214
|
-
extractNoWp: "Choose an image for this rule first",
|
|
215
199
|
extractDone: "Image colors applied",
|
|
216
200
|
extractFail: "No vivid color found in this image, try another",
|
|
217
201
|
eyedropper: "Eyedropper",
|
|
@@ -230,10 +214,168 @@ window.__ModuleLoader__.load({
|
|
|
230
214
|
toastExportDone: "Export started",
|
|
231
215
|
importDone: "Profile imported",
|
|
232
216
|
importFail: "Import failed — invalid file",
|
|
233
|
-
footerTag: "Per-model wallpaper plugin"
|
|
217
|
+
footerTag: "Per-model wallpaper plugin",
|
|
218
|
+
holidayTitle: "Holiday backgrounds",
|
|
219
|
+
holidayEnable: "Enable holiday backgrounds (Mid-Autumn day, Oct 1–7)"
|
|
234
220
|
};
|
|
235
221
|
//#endregion
|
|
222
|
+
//#region src/holiday.ts
|
|
223
|
+
const HOLIDAYS = [{
|
|
224
|
+
id: "mid-autumn",
|
|
225
|
+
slot: "h-midautumn",
|
|
226
|
+
asset: "mid-autumn.webp",
|
|
227
|
+
color: "#384A77",
|
|
228
|
+
kind: "lunar"
|
|
229
|
+
}, {
|
|
230
|
+
id: "national-day",
|
|
231
|
+
slot: "h-nationalday",
|
|
232
|
+
asset: "national-day.webp",
|
|
233
|
+
color: "#FFF6EB",
|
|
234
|
+
kind: "gregorian"
|
|
235
|
+
}];
|
|
236
|
+
const TZ = "Asia/Shanghai";
|
|
237
|
+
let civilFormatter;
|
|
238
|
+
function civilFmt() {
|
|
239
|
+
if (civilFormatter === void 0) try {
|
|
240
|
+
civilFormatter = new Intl.DateTimeFormat("en-US", {
|
|
241
|
+
timeZone: TZ,
|
|
242
|
+
year: "numeric",
|
|
243
|
+
month: "numeric",
|
|
244
|
+
day: "numeric"
|
|
245
|
+
});
|
|
246
|
+
} catch {
|
|
247
|
+
civilFormatter = null;
|
|
248
|
+
}
|
|
249
|
+
return civilFormatter;
|
|
250
|
+
}
|
|
251
|
+
let lunarFormatter;
|
|
252
|
+
function lunarFmt() {
|
|
253
|
+
if (lunarFormatter === void 0) try {
|
|
254
|
+
lunarFormatter = new Intl.DateTimeFormat("en-u-ca-chinese", {
|
|
255
|
+
timeZone: TZ,
|
|
256
|
+
year: "numeric",
|
|
257
|
+
month: "numeric",
|
|
258
|
+
day: "numeric"
|
|
259
|
+
});
|
|
260
|
+
} catch {
|
|
261
|
+
lunarFormatter = null;
|
|
262
|
+
}
|
|
263
|
+
return lunarFormatter;
|
|
264
|
+
}
|
|
265
|
+
function partsOf(fmt, date) {
|
|
266
|
+
try {
|
|
267
|
+
const out = {};
|
|
268
|
+
for (const p of fmt.formatToParts(date)) if (p.type !== "literal") out[p.type] = p.value;
|
|
269
|
+
return out;
|
|
270
|
+
} catch {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/** The civil (Gregorian) date in Asia/Shanghai, or null when ICU cannot answer. */
|
|
275
|
+
function shanghaiDate(date) {
|
|
276
|
+
const fmt = civilFmt();
|
|
277
|
+
if (fmt === null) return null;
|
|
278
|
+
const p = partsOf(fmt, date);
|
|
279
|
+
if (p === null) return null;
|
|
280
|
+
const y = Number(p.year);
|
|
281
|
+
const m = Number(p.month);
|
|
282
|
+
const d = Number(p.day);
|
|
283
|
+
if (!isFinite(y) || !isFinite(m) || !isFinite(d)) return null;
|
|
284
|
+
return {
|
|
285
|
+
y,
|
|
286
|
+
m,
|
|
287
|
+
d
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The Chinese-calendar month and day in Asia/Shanghai.
|
|
292
|
+
*
|
|
293
|
+
* ICU marks a **leap** month by suffixing `bis` — a leap fourth month reads
|
|
294
|
+
* `4bis`, a leap eighth month would read `8bis`. 中秋 is the FIFTEENTH DAY OF THE
|
|
295
|
+
* REGULAR EIGHTH MONTH, so the exact `month === '8'` test below is what keeps a
|
|
296
|
+
* leap eighth month (which does occur, roughly once every few decades) from
|
|
297
|
+
* dragging the festival a month late.
|
|
298
|
+
*/
|
|
299
|
+
function lunarMonthDay(date) {
|
|
300
|
+
const fmt = lunarFmt();
|
|
301
|
+
if (fmt === null) return null;
|
|
302
|
+
const p = partsOf(fmt, date);
|
|
303
|
+
if (p === null || p.month === void 0 || p.day === void 0) return null;
|
|
304
|
+
return {
|
|
305
|
+
month: p.month,
|
|
306
|
+
day: p.day
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/** 农历八月十五, in the regular eighth month — i.e. 中秋 itself. */
|
|
310
|
+
function isMidAutumn(date = /* @__PURE__ */ new Date()) {
|
|
311
|
+
const lunar = lunarMonthDay(date);
|
|
312
|
+
return lunar !== null && lunar.month === "8" && lunar.day === "15";
|
|
313
|
+
}
|
|
314
|
+
/** 公历 10 月 1–7 日 — the National Day holiday period. */
|
|
315
|
+
function isNationalDay(date = /* @__PURE__ */ new Date()) {
|
|
316
|
+
const civil = shanghaiDate(date);
|
|
317
|
+
return civil !== null && civil.m === 10 && civil.d >= 1 && civil.d <= 7;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* The holiday whose window contains `date`, or null.
|
|
321
|
+
*
|
|
322
|
+
* 中秋 wins the overlap: it is a single precise day, while National Day is a
|
|
323
|
+
* seven-day window, and the two do collide (2025-10-06 was both). On a tie the
|
|
324
|
+
* rarer, more specific answer is the better one.
|
|
325
|
+
*/
|
|
326
|
+
function activeHoliday(date = /* @__PURE__ */ new Date()) {
|
|
327
|
+
if (isMidAutumn(date)) return "mid-autumn";
|
|
328
|
+
if (isNationalDay(date)) return "national-day";
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* The holiday the background must follow right now, or null.
|
|
333
|
+
*
|
|
334
|
+
* Four gates, every one of them deliberate:
|
|
335
|
+
*
|
|
336
|
+
* 1. the master switch — an override that discards the user's rule list must
|
|
337
|
+
* never happen without being asked for;
|
|
338
|
+
* 2. a built-in holiday whose window contains today;
|
|
339
|
+
* 3. that holiday's own switch;
|
|
340
|
+
* 4. actual bytes in its slot.
|
|
341
|
+
*
|
|
342
|
+
* The last one matters most, and it is the reason this is not a one-liner at the
|
|
343
|
+
* call site: a switched-on holiday with no image at all has to fall through to
|
|
344
|
+
* the model rules, not blank the wallpaper.
|
|
345
|
+
*
|
|
346
|
+
* It lives here, as a pure function, so the decision is covered by
|
|
347
|
+
* `scripts/holiday-check.ts` instead of hiding inside the plugin's apply closure
|
|
348
|
+
* where nothing can reach it.
|
|
349
|
+
*/
|
|
350
|
+
function pickHoliday(master, items, todayId, hasImage) {
|
|
351
|
+
if (!master || todayId === null) return null;
|
|
352
|
+
const item = items.find((i) => i.id === todayId);
|
|
353
|
+
if (item === void 0 || !item.enabled) return null;
|
|
354
|
+
return hasImage(item.slot) ? item : null;
|
|
355
|
+
}
|
|
356
|
+
//#endregion
|
|
236
357
|
//#region src/schema.ts
|
|
358
|
+
/**
|
|
359
|
+
* The persisted configuration shape — declared exactly ONCE, for both halves.
|
|
360
|
+
*
|
|
361
|
+
* This shape used to be written out twice: the node half owns persistence and
|
|
362
|
+
* sanitizes every write, the browser half owns the UI and its own reading of the
|
|
363
|
+
* same file. A field added to only one side was silently dropped by the other
|
|
364
|
+
* side's sanitizer — the matching slider stayed live in memory, `writeConfig`
|
|
365
|
+
* wrote a config without it, and the next load fell back to the default.
|
|
366
|
+
* `blurs.rightbar` / `rightbarOpacity` in 0.3.2 was exactly that bug, and the key
|
|
367
|
+
* lists were duplicated 6 times across the two halves, so it was due to happen.
|
|
368
|
+
*
|
|
369
|
+
* The types, the key lists, the defaults and the pure normalizers therefore live
|
|
370
|
+
* here and both halves import them: adding a field means editing this file, and
|
|
371
|
+
* the two halves cannot disagree about the shape. The host additionally logs one
|
|
372
|
+
* warning per unknown key while sanitizing a write (see `warnUnknownConfigKeys`
|
|
373
|
+
* in `src/index.ts`), so a field that reaches the node half without being
|
|
374
|
+
* declared here shows up in the host log instead of quietly disappearing.
|
|
375
|
+
*
|
|
376
|
+
* Nothing in this module may touch the DOM, Node or the Cordis context: it is
|
|
377
|
+
* imported by the browser bundle and by the node half alike.
|
|
378
|
+
*/
|
|
237
379
|
const DEFAULT_BG_STATE = {
|
|
238
380
|
zoom: 1,
|
|
239
381
|
x: 0,
|
|
@@ -300,6 +442,67 @@ window.__ModuleLoader__.load({
|
|
|
300
442
|
rightbar: 0,
|
|
301
443
|
input: 0
|
|
302
444
|
};
|
|
445
|
+
/**
|
|
446
|
+
* `#RRGGBB` → the `[h, s, l]` triple every rule stores (`s` and `l` in 0..1), or
|
|
447
|
+
* null when the string is not a hex color.
|
|
448
|
+
*
|
|
449
|
+
* A holiday names its color as hex because that is how a color is quoted, and a
|
|
450
|
+
* hand-converted triple is a magic number nothing downstream can check. The
|
|
451
|
+
* conversion lives here rather than in ./holiday because `[h, s, l]` is this
|
|
452
|
+
* module's own rule shape.
|
|
453
|
+
*/
|
|
454
|
+
function hexToHsl(hex) {
|
|
455
|
+
const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
|
|
456
|
+
if (m === null) return null;
|
|
457
|
+
const n = parseInt(m[1], 16);
|
|
458
|
+
const r = (n >> 16 & 255) / 255;
|
|
459
|
+
const g = (n >> 8 & 255) / 255;
|
|
460
|
+
const b = (n & 255) / 255;
|
|
461
|
+
const max = Math.max(r, g, b);
|
|
462
|
+
const min = Math.min(r, g, b);
|
|
463
|
+
const l = (max + min) / 2;
|
|
464
|
+
const d = max - min;
|
|
465
|
+
if (d === 0) return [
|
|
466
|
+
0,
|
|
467
|
+
0,
|
|
468
|
+
l
|
|
469
|
+
];
|
|
470
|
+
const s = d / (1 - Math.abs(2 * l - 1));
|
|
471
|
+
let h;
|
|
472
|
+
if (max === r) h = 60 * ((g - b) / d % 6);
|
|
473
|
+
else if (max === g) h = 60 * ((b - r) / d + 2);
|
|
474
|
+
else h = 60 * ((r - g) / d + 4);
|
|
475
|
+
if (h < 0) h += 360;
|
|
476
|
+
return [
|
|
477
|
+
clamp$1(h, 0, 360, 0),
|
|
478
|
+
clamp01(s, 0),
|
|
479
|
+
clamp01(l, .5)
|
|
480
|
+
];
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* One holiday's appearance, untouched.
|
|
484
|
+
*
|
|
485
|
+
* Holidays default to `fill` rather than a rule's `fit`: these are full-bleed
|
|
486
|
+
* festival wallpapers, and `fit` would letterbox them on every aspect ratio the
|
|
487
|
+
* art was not cut for.
|
|
488
|
+
*/
|
|
489
|
+
function defaultHolidayRule(def) {
|
|
490
|
+
return {
|
|
491
|
+
id: def.id,
|
|
492
|
+
slot: def.slot,
|
|
493
|
+
match: "",
|
|
494
|
+
enabled: true,
|
|
495
|
+
color: hexToHsl(def.color),
|
|
496
|
+
bgMode: "fill",
|
|
497
|
+
wallpaperOpacity: 1,
|
|
498
|
+
blur: 0,
|
|
499
|
+
bgState: { ...DEFAULT_BG_STATE }
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
/** Every built-in holiday at its default, in `HOLIDAYS` order. */
|
|
503
|
+
function defaultHolidayRules() {
|
|
504
|
+
return HOLIDAYS.map(defaultHolidayRule);
|
|
505
|
+
}
|
|
303
506
|
/** A default config with independent nested objects (never hand out the shared ones). */
|
|
304
507
|
function freshThemeConfig() {
|
|
305
508
|
return {
|
|
@@ -310,7 +513,11 @@ window.__ModuleLoader__.load({
|
|
|
310
513
|
chatTextOpacity: 0,
|
|
311
514
|
trajectoryOpacity: 1,
|
|
312
515
|
rightbarOpacity: null,
|
|
313
|
-
autoExtract: true
|
|
516
|
+
autoExtract: true,
|
|
517
|
+
holidays: {
|
|
518
|
+
enabled: true,
|
|
519
|
+
items: defaultHolidayRules()
|
|
520
|
+
}
|
|
314
521
|
};
|
|
315
522
|
}
|
|
316
523
|
/** Slot names reach the filesystem, so they are strictly whitelisted. */
|
|
@@ -358,6 +565,41 @@ window.__ModuleLoader__.load({
|
|
|
358
565
|
bgState: normalizeBgState(r.bgState)
|
|
359
566
|
};
|
|
360
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* Coerce one persisted holiday entry.
|
|
570
|
+
*
|
|
571
|
+
* `id`, `slot` and `color` come from the DEFINITION, never from disk: a holiday's
|
|
572
|
+
* slot is the only thing tying it to its bytes, and its theme color is a fixed
|
|
573
|
+
* part of what that holiday looks like — so a stale or hand-edited value would
|
|
574
|
+
* either orphan the wallpaper, point the holiday at a rule's image, or paint a
|
|
575
|
+
* festival in a color it does not have. Everything else is sanitized exactly like
|
|
576
|
+
* a rule's field.
|
|
577
|
+
*/
|
|
578
|
+
function normalizeHolidayRule(def, raw) {
|
|
579
|
+
const r = raw ?? {};
|
|
580
|
+
const base = defaultHolidayRule(def);
|
|
581
|
+
return {
|
|
582
|
+
...base,
|
|
583
|
+
enabled: r.enabled !== false,
|
|
584
|
+
bgMode: BG_MODES$1.includes(r.bgMode) ? r.bgMode : base.bgMode,
|
|
585
|
+
wallpaperOpacity: clamp01(r.wallpaperOpacity, base.wallpaperOpacity),
|
|
586
|
+
blur: clamp$1(r.blur, 0, 60, base.blur),
|
|
587
|
+
bgState: normalizeBgState(r.bgState)
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
/** Coerce the whole holiday block; unknown ids are dropped, missing ones defaulted. */
|
|
591
|
+
function normalizeHolidays(raw) {
|
|
592
|
+
const r = raw ?? {};
|
|
593
|
+
const stored = /* @__PURE__ */ new Map();
|
|
594
|
+
if (Array.isArray(r.items)) for (const item of r.items) {
|
|
595
|
+
const id = item?.id;
|
|
596
|
+
if (typeof id === "string") stored.set(id, item);
|
|
597
|
+
}
|
|
598
|
+
return {
|
|
599
|
+
enabled: r.enabled !== false,
|
|
600
|
+
items: HOLIDAYS.map((def) => normalizeHolidayRule(def, stored.get(def.id)))
|
|
601
|
+
};
|
|
602
|
+
}
|
|
361
603
|
/** Coerce an unknown persisted value into a valid ThemeConfig, falling back per-field. */
|
|
362
604
|
function normalizeConfig(raw) {
|
|
363
605
|
const r = raw ?? {};
|
|
@@ -376,7 +618,8 @@ window.__ModuleLoader__.load({
|
|
|
376
618
|
chatTextOpacity: clamp01(r.chatTextOpacity, 0),
|
|
377
619
|
trajectoryOpacity: clamp01(r.trajectoryOpacity, 1),
|
|
378
620
|
rightbarOpacity: typeof r.rightbarOpacity === "number" && isFinite(r.rightbarOpacity) ? clamp01(r.rightbarOpacity, 1) : null,
|
|
379
|
-
autoExtract: r.autoExtract !== false
|
|
621
|
+
autoExtract: r.autoExtract !== false,
|
|
622
|
+
holidays: normalizeHolidays(r.holidays)
|
|
380
623
|
};
|
|
381
624
|
}
|
|
382
625
|
//#endregion
|
|
@@ -484,12 +727,24 @@ window.__ModuleLoader__.load({
|
|
|
484
727
|
function setModelLabel(label) {
|
|
485
728
|
modelLabel = label;
|
|
486
729
|
}
|
|
730
|
+
/**
|
|
731
|
+
* One rule by id — from the USER's list first, then from the holiday overrides.
|
|
732
|
+
*
|
|
733
|
+
* Every render accessor (`rWp` / `rColor` / `rBgMode` / `rBgState` …) resolves
|
|
734
|
+
* through here, so teaching this one function about holidays is what makes an
|
|
735
|
+
* active holiday paint through the entire existing pipeline instead of needing a
|
|
736
|
+
* parallel one.
|
|
737
|
+
*/
|
|
487
738
|
function ruleById(id) {
|
|
488
|
-
return cfg.rules.find((r) => r.id === id) ?? null;
|
|
739
|
+
return cfg.rules.find((r) => r.id === id) ?? cfg.holidays.items.find((r) => r.id === id) ?? null;
|
|
489
740
|
}
|
|
490
741
|
function activeRule() {
|
|
491
742
|
return activeRuleId === null ? null : ruleById(activeRuleId);
|
|
492
743
|
}
|
|
744
|
+
/** One holiday override entry, or null for an unknown id. */
|
|
745
|
+
function holidayById(id) {
|
|
746
|
+
return cfg.holidays.items.find((r) => r.id === id) ?? null;
|
|
747
|
+
}
|
|
493
748
|
function newRule(id, slot) {
|
|
494
749
|
return {
|
|
495
750
|
id,
|
|
@@ -577,7 +832,7 @@ window.__ModuleLoader__.load({
|
|
|
577
832
|
/** Move a possibly-absent partial config into the shape the UI reads. */
|
|
578
833
|
function adoptConfig(raw) {
|
|
579
834
|
cfg = normalizeConfig(raw);
|
|
580
|
-
if (activeRuleId !== null &&
|
|
835
|
+
if (activeRuleId !== null && ruleById(activeRuleId) === null) {
|
|
581
836
|
activeRuleId = null;
|
|
582
837
|
activeMatched = false;
|
|
583
838
|
}
|
|
@@ -1100,7 +1355,6 @@ window.__ModuleLoader__.load({
|
|
|
1100
1355
|
.dab-orb-wrap{position:relative;width:118px;height:118px;flex:none}
|
|
1101
1356
|
.dab-orb{position:absolute;inset:11px;border-radius:50%;background:radial-gradient(circle at 32% 28%,rgba(255,255,255,.5),rgba(255,255,255,0) 44%),var(--c,#888);box-shadow:0 16px 36px -10px var(--c-soft,transparent),inset 0 -10px 20px rgba(0,0,0,.16);animation:dab-orb-in .7s cubic-bezier(.22,1,.36,1) both}
|
|
1102
1357
|
.dab-orb-ring{position:absolute;inset:0;border-radius:50%;background:conic-gradient(from 0deg,transparent 0 30%,var(--c,#888) 46%,transparent 62%,transparent 76%,var(--c,#888) 90%,transparent 100%);-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - 3.5px),#000 calc(100% - 2.5px));mask:radial-gradient(farthest-side,transparent calc(100% - 3.5px),#000 calc(100% - 2.5px));animation:dab-spin 7s linear infinite;opacity:.9}
|
|
1103
|
-
.dab-hex-caption{font-size:11px;color:var(--dsw-alias-label-tertiary);letter-spacing:.04em}
|
|
1104
1358
|
.dab-hex{font-family:var(--dab-mono);font-size:24px;font-weight:600;letter-spacing:.02em;line-height:1.2;margin-top:2px}
|
|
1105
1359
|
.dab-hsl-row{display:flex;gap:16px;margin-top:7px;font-family:var(--dab-mono);font-size:11px;color:var(--dsw-alias-label-tertiary)}
|
|
1106
1360
|
.dab-hsl-row b{font-weight:600;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary))}
|
|
@@ -1129,8 +1383,12 @@ window.__ModuleLoader__.load({
|
|
|
1129
1383
|
.dab-urlinput{flex:1;min-width:180px;height:34px;padding:0 12px;border-radius:10px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:12.5px;outline:none;transition:border-color .2s}
|
|
1130
1384
|
.dab-urlinput::placeholder{color:var(--dsw-alias-label-quaternary)}
|
|
1131
1385
|
.dab-urlinput:focus{border-color:var(--dsw-alias-brand-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-brand-primary) 18%,transparent)}
|
|
1132
|
-
|
|
1133
|
-
|
|
1386
|
+
/* Prints the current hex on the color itself. Without the value this was an
|
|
1387
|
+
* empty bordered rectangle sitting in the same column as the H/S/L number
|
|
1388
|
+
* fields, which made it read as one more — blank — text input; nothing is ever
|
|
1389
|
+
* typed here, so nothing may look typable. The hover scale went with that
|
|
1390
|
+
* reading: it advertised an interaction this field does not have. */
|
|
1391
|
+
.dab-swatch-lg{display:flex;align-items:center;justify-content:center;height:38px;border-radius:10px;border:1px solid var(--dsw-alias-border-l2);box-shadow:inset 0 0 14px rgba(0,0,0,.1);font-family:var(--dab-mono);font-size:12.5px;font-weight:600;letter-spacing:.06em;user-select:all}
|
|
1134
1392
|
|
|
1135
1393
|
/* ── buttons & chips ─────────────────────────────────────────────────────── */
|
|
1136
1394
|
/* Text/icon painted ON --dsw-alias-brand-primary reads
|
|
@@ -1297,6 +1555,11 @@ window.__ModuleLoader__.load({
|
|
|
1297
1555
|
.dab-check-where code{font-family:var(--dab-mono);font-size:10.5px;overflow-wrap:anywhere}
|
|
1298
1556
|
.dab-check-note{margin:12px 0 0;font-size:11.5px;line-height:1.55;color:var(--dsw-alias-label-tertiary);position:relative;z-index:1}
|
|
1299
1557
|
|
|
1558
|
+
/* ── holiday override (Profile page) ─────────────────────────────────────── */
|
|
1559
|
+
/* Only a label: the whole feature is one switch, and dab-toggle already exists.
|
|
1560
|
+
The per-holiday cards that used to live here are gone by design. */
|
|
1561
|
+
.dab-holiday-label{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
1562
|
+
|
|
1300
1563
|
/* ── keyframes ───────────────────────────────────────────────────────────── */
|
|
1301
1564
|
@keyframes dab-fade-in{from{opacity:0}to{opacity:1}}
|
|
1302
1565
|
@keyframes dab-page-in{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
|
|
@@ -3334,6 +3597,10 @@ window.__ModuleLoader__.load({
|
|
|
3334
3597
|
function clamp(v, min, max) {
|
|
3335
3598
|
return Math.min(max, Math.max(min, v));
|
|
3336
3599
|
}
|
|
3600
|
+
/** '#rrggbb' for a 0-255 RGB triple, in the caller's case. */
|
|
3601
|
+
function toHex$2(rgb) {
|
|
3602
|
+
return "#" + rgb.map((v) => Math.round(v).toString(16).padStart(2, "0")).join("");
|
|
3603
|
+
}
|
|
3337
3604
|
/**
|
|
3338
3605
|
* A single numeric field that edits one color channel. Keeps its own text
|
|
3339
3606
|
* while focused so typing never gets clobbered by the parent re-rendering the
|
|
@@ -3376,15 +3643,22 @@ window.__ModuleLoader__.load({
|
|
|
3376
3643
|
}
|
|
3377
3644
|
/**
|
|
3378
3645
|
* Precise color entry next to the wheel: a HSL/RGB segmented toggle plus three
|
|
3379
|
-
* numeric channel fields and a live
|
|
3380
|
-
* this panel converts at the boundary — HSL fields
|
|
3381
|
-
* stored HSL, RGB fields round-trip through rgbToHsl —
|
|
3382
|
-
* the same onChange the wheel uses, keeping one canonical
|
|
3646
|
+
* numeric channel fields and a live preview field that prints its own hex. The
|
|
3647
|
+
* wheel is HSV end-to-end, so this panel converts at the boundary — HSL fields
|
|
3648
|
+
* map straight onto the stored HSL, RGB fields round-trip through rgbToHsl —
|
|
3649
|
+
* and both emit HSV via the same onChange the wheel uses, keeping one canonical
|
|
3650
|
+
* color.
|
|
3383
3651
|
*/
|
|
3384
3652
|
function ColorInputs({ hue, sat, lit, onChange }) {
|
|
3385
3653
|
const [mode, setMode] = (0, react.useState)("hsl");
|
|
3386
3654
|
const [h, s, l] = hsvToHsl(hue, sat, lit);
|
|
3387
3655
|
const [r, g, b] = hslToRgb(h, s, l);
|
|
3656
|
+
const hex = toHex$2([
|
|
3657
|
+
r,
|
|
3658
|
+
g,
|
|
3659
|
+
b
|
|
3660
|
+
]).toUpperCase();
|
|
3661
|
+
const ink = (.2126 * r + .7152 * g + .0722 * b) / 255 > .55 ? "rgba(0,0,0,.78)" : "rgba(255,255,255,.92)";
|
|
3388
3662
|
const setHsl = (nh, ns, nl) => onChange(...hslToHsv(nh, ns, nl));
|
|
3389
3663
|
const setRgb = (nr, ng, nb) => {
|
|
3390
3664
|
const [nh, ns, nl] = rgbToHsl(nr, ng, nb);
|
|
@@ -3475,7 +3749,11 @@ window.__ModuleLoader__.load({
|
|
|
3475
3749
|
}),
|
|
3476
3750
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3477
3751
|
className: "dab-swatch-lg",
|
|
3478
|
-
style: {
|
|
3752
|
+
style: {
|
|
3753
|
+
background: `hsl(${Math.round(h)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%)`,
|
|
3754
|
+
color: ink
|
|
3755
|
+
},
|
|
3756
|
+
children: hex
|
|
3479
3757
|
})
|
|
3480
3758
|
]
|
|
3481
3759
|
});
|
|
@@ -4377,17 +4655,14 @@ window.__ModuleLoader__.load({
|
|
|
4377
4655
|
sat: wheel[1],
|
|
4378
4656
|
lit: wheel[2],
|
|
4379
4657
|
onChange: onColor
|
|
4380
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
4658
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4381
4659
|
className: "dab-inputs",
|
|
4382
|
-
children:
|
|
4383
|
-
className: "dab-hex-caption",
|
|
4384
|
-
children: toHex(hslToRgb(h, s, l)).toUpperCase()
|
|
4385
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ColorInputs, {
|
|
4660
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ColorInputs, {
|
|
4386
4661
|
hue: wheel[0],
|
|
4387
4662
|
sat: wheel[1],
|
|
4388
4663
|
lit: wheel[2],
|
|
4389
4664
|
onChange: onColor
|
|
4390
|
-
})
|
|
4665
|
+
})
|
|
4391
4666
|
})]
|
|
4392
4667
|
}),
|
|
4393
4668
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -4408,11 +4683,11 @@ window.__ModuleLoader__.load({
|
|
|
4408
4683
|
onClick: () => setPickerOpen(true),
|
|
4409
4684
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PipetteIcon, { size: 13 }), t("eyedropper")]
|
|
4410
4685
|
}),
|
|
4411
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
4686
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4412
4687
|
type: "button",
|
|
4413
4688
|
className: "dab-btn dab-btn-ghost",
|
|
4414
4689
|
onClick: () => p.setRule(rule.id, { color: null }),
|
|
4415
|
-
children: t("ruleColorNone")
|
|
4690
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SunIcon, { size: 13 }), t("ruleColorNone")]
|
|
4416
4691
|
})
|
|
4417
4692
|
]
|
|
4418
4693
|
}),
|
|
@@ -4508,6 +4783,8 @@ window.__ModuleLoader__.load({
|
|
|
4508
4783
|
function ProfilePage({ p, notify }) {
|
|
4509
4784
|
const { t, exportTheme, importTheme } = p;
|
|
4510
4785
|
const importRef = (0, react.useRef)(null);
|
|
4786
|
+
p.useStore((s) => s.rev + s.rulesRev);
|
|
4787
|
+
const holidays = cfg.holidays;
|
|
4511
4788
|
const onImport = async (file) => {
|
|
4512
4789
|
try {
|
|
4513
4790
|
const ok = await importTheme(file);
|
|
@@ -4535,11 +4812,31 @@ window.__ModuleLoader__.load({
|
|
|
4535
4812
|
})
|
|
4536
4813
|
]
|
|
4537
4814
|
}),
|
|
4815
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
4816
|
+
className: "dab-card dab-rise",
|
|
4817
|
+
style: { "--d": 1 },
|
|
4818
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4819
|
+
className: "dab-chip-row",
|
|
4820
|
+
style: { alignItems: "center" },
|
|
4821
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4822
|
+
type: "button",
|
|
4823
|
+
className: `dab-toggle${holidays.enabled ? " is-on" : ""}`,
|
|
4824
|
+
role: "switch",
|
|
4825
|
+
"aria-checked": holidays.enabled,
|
|
4826
|
+
title: t("holidayEnable"),
|
|
4827
|
+
onClick: () => p.setHolidaysEnabled(!holidays.enabled),
|
|
4828
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dab-toggle-knob" })
|
|
4829
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4830
|
+
className: "dab-holiday-label",
|
|
4831
|
+
children: t("holidayTitle")
|
|
4832
|
+
})]
|
|
4833
|
+
})
|
|
4834
|
+
}),
|
|
4538
4835
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4539
|
-
className: "dab-profile-grid",
|
|
4836
|
+
className: "dab-profile-grid dab-rise",
|
|
4837
|
+
style: { "--d": 2 },
|
|
4540
4838
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4541
|
-
className: "dab-card dab-card-hover
|
|
4542
|
-
style: { "--d": 1 },
|
|
4839
|
+
className: "dab-card dab-card-hover",
|
|
4543
4840
|
children: [
|
|
4544
4841
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4545
4842
|
className: "dab-profile-ico",
|
|
@@ -4564,8 +4861,7 @@ window.__ModuleLoader__.load({
|
|
|
4564
4861
|
})
|
|
4565
4862
|
]
|
|
4566
4863
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4567
|
-
className: "dab-card dab-card-hover
|
|
4568
|
-
style: { "--d": 2 },
|
|
4864
|
+
className: "dab-card dab-card-hover",
|
|
4569
4865
|
children: [
|
|
4570
4866
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4571
4867
|
className: "dab-profile-ico",
|
|
@@ -4814,7 +5110,11 @@ window.__ModuleLoader__.load({
|
|
|
4814
5110
|
};
|
|
4815
5111
|
/** Resolve the active rule for the current model and repaint everything. */
|
|
4816
5112
|
const applyActive = () => {
|
|
4817
|
-
const
|
|
5113
|
+
const holiday = activeHolidayRule();
|
|
5114
|
+
const { rule, matched } = holiday !== null ? {
|
|
5115
|
+
rule: holiday,
|
|
5116
|
+
matched: false
|
|
5117
|
+
} : matchRule(cfg.rules, modelText);
|
|
4818
5118
|
setActive(rule === null ? null : rule.id, matched);
|
|
4819
5119
|
const color = rule === null ? null : rule.color;
|
|
4820
5120
|
if (skinTimer !== null) window.clearTimeout(skinTimer);
|
|
@@ -4829,6 +5129,14 @@ window.__ModuleLoader__.load({
|
|
|
4829
5129
|
applyWp();
|
|
4830
5130
|
sync();
|
|
4831
5131
|
};
|
|
5132
|
+
/**
|
|
5133
|
+
* The holiday the background must follow right now, or null.
|
|
5134
|
+
*
|
|
5135
|
+
* The decision itself is `pickHoliday` (pure, and covered by
|
|
5136
|
+
* `scripts/holiday-check.ts`); this only supplies the two things it cannot
|
|
5137
|
+
* know — today's holiday from the calendar, and whether the slot has bytes.
|
|
5138
|
+
*/
|
|
5139
|
+
const activeHolidayRule = () => pickHoliday(cfg.holidays.enabled, cfg.holidays.items, activeHoliday(), (slot) => imageOf(slot) !== null);
|
|
4832
5140
|
syncHostScheme();
|
|
4833
5141
|
applyWp();
|
|
4834
5142
|
sync();
|
|
@@ -4858,18 +5166,63 @@ window.__ModuleLoader__.load({
|
|
|
4858
5166
|
applyActive();
|
|
4859
5167
|
}
|
|
4860
5168
|
})();
|
|
5169
|
+
let holidayKey = activeHoliday() ?? "";
|
|
5170
|
+
const holidayTimer = window.setInterval(() => {
|
|
5171
|
+
const next = activeHoliday() ?? "";
|
|
5172
|
+
if (next === holidayKey) return;
|
|
5173
|
+
holidayKey = next;
|
|
5174
|
+
const slot = todayHolidaySlot();
|
|
5175
|
+
if (slot === null) applyActive();
|
|
5176
|
+
else loadSlot(slot).then(() => applyActive());
|
|
5177
|
+
}, 3e4);
|
|
5178
|
+
ctx.effect(() => () => {
|
|
5179
|
+
window.clearInterval(holidayTimer);
|
|
5180
|
+
}, "dsh-background-by-model: holiday rollover");
|
|
5181
|
+
/** Read one slot's bytes into the cache unless they are already there. */
|
|
5182
|
+
const loadSlot = async (slot) => {
|
|
5183
|
+
if (imageOf(slot) !== null) return;
|
|
5184
|
+
const url = await readImage(slot);
|
|
5185
|
+
if (url !== null) setImage(slot, url);
|
|
5186
|
+
};
|
|
5187
|
+
/**
|
|
5188
|
+
* The slot of the holiday that could paint TODAY, or null.
|
|
5189
|
+
*
|
|
5190
|
+
* Gated on the CALENDAR, not merely on the switch. Now that the override is on
|
|
5191
|
+
* by default and invisible, a profile that simply is not on a holiday must not
|
|
5192
|
+
* pull ~730 KB of bundled art (≈970 KB of base64 over this channel) at every
|
|
5193
|
+
* boot just because the feature is armed.
|
|
5194
|
+
*/
|
|
5195
|
+
const todayHolidaySlot = () => {
|
|
5196
|
+
if (!cfg.holidays.enabled) return null;
|
|
5197
|
+
const id = activeHoliday();
|
|
5198
|
+
if (id === null) return null;
|
|
5199
|
+
const item = holidayById(id);
|
|
5200
|
+
return item !== null && item.enabled ? item.slot : null;
|
|
5201
|
+
};
|
|
5202
|
+
/** Every slot that can paint: the stored ones plus every slot the config names. */
|
|
5203
|
+
const allSlots = (stored) => {
|
|
5204
|
+
const holiday = todayHolidaySlot();
|
|
5205
|
+
return Array.from(/* @__PURE__ */ new Set([
|
|
5206
|
+
...stored,
|
|
5207
|
+
...cfg.rules.map((r) => r.slot),
|
|
5208
|
+
...holiday === null ? [] : [holiday]
|
|
5209
|
+
]));
|
|
5210
|
+
};
|
|
5211
|
+
/**
|
|
5212
|
+
* The slot that paints before anything else: the holiday in force when there
|
|
5213
|
+
* is one, otherwise the slot the current model's rule uses. A holiday outranks
|
|
5214
|
+
* the rule here for the same reason it outranks it when painting.
|
|
5215
|
+
*/
|
|
5216
|
+
const prioritySlot = () => todayHolidaySlot() ?? matchRule(cfg.rules, modelText).rule?.slot ?? null;
|
|
4861
5217
|
(async () => {
|
|
4862
5218
|
const persisted = await loadPersisted();
|
|
4863
5219
|
if (persisted !== null) {
|
|
4864
5220
|
adoptConfig(persisted.config);
|
|
4865
|
-
const
|
|
4866
|
-
const
|
|
4867
|
-
const first = priority === null ? null : priority.slot;
|
|
5221
|
+
const first = prioritySlot();
|
|
5222
|
+
const slots = allSlots(persisted.slots);
|
|
4868
5223
|
const order = first === null ? slots : [first, ...slots.filter((s) => s !== first)];
|
|
4869
5224
|
for (const slot of order) {
|
|
4870
|
-
|
|
4871
|
-
const url = await readImage(slot);
|
|
4872
|
-
if (url !== null) setImage(slot, url);
|
|
5225
|
+
await loadSlot(slot);
|
|
4873
5226
|
if (slot === first) applyActive();
|
|
4874
5227
|
}
|
|
4875
5228
|
}
|
|
@@ -5074,11 +5427,19 @@ window.__ModuleLoader__.load({
|
|
|
5074
5427
|
saveConfig();
|
|
5075
5428
|
sync();
|
|
5076
5429
|
},
|
|
5430
|
+
setHolidaysEnabled: (v) => {
|
|
5431
|
+
cfg.holidays.enabled = v;
|
|
5432
|
+
persistConfig();
|
|
5433
|
+
const slot = todayHolidaySlot();
|
|
5434
|
+
if (v && slot !== null) loadSlot(slot).then(() => applyActive());
|
|
5435
|
+
applyActive();
|
|
5436
|
+
},
|
|
5077
5437
|
exportTheme: () => {
|
|
5078
5438
|
const images = {};
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5439
|
+
const slots = cfg.rules.map((r) => r.slot);
|
|
5440
|
+
for (const slot of slots) {
|
|
5441
|
+
const url = imageOf(slot);
|
|
5442
|
+
if (url !== null) images[slot] = url;
|
|
5082
5443
|
}
|
|
5083
5444
|
const payload = {
|
|
5084
5445
|
version: 3,
|
|
@@ -5103,15 +5464,15 @@ window.__ModuleLoader__.load({
|
|
|
5103
5464
|
adoptConfig(d.config);
|
|
5104
5465
|
const incoming = d.images ?? {};
|
|
5105
5466
|
const keep = /* @__PURE__ */ new Set();
|
|
5106
|
-
for (const
|
|
5107
|
-
keep.add(
|
|
5108
|
-
const raw = incoming[
|
|
5467
|
+
for (const slot of cfg.rules.map((r) => r.slot)) {
|
|
5468
|
+
keep.add(slot);
|
|
5469
|
+
const raw = incoming[slot];
|
|
5109
5470
|
if (typeof raw === "string" && /^data:image\//.test(raw)) {
|
|
5110
|
-
setImage(
|
|
5111
|
-
writeImage(
|
|
5471
|
+
setImage(slot, raw);
|
|
5472
|
+
writeImage(slot, raw);
|
|
5112
5473
|
} else {
|
|
5113
|
-
setImage(
|
|
5114
|
-
deleteImage(
|
|
5474
|
+
setImage(slot, null);
|
|
5475
|
+
deleteImage(slot);
|
|
5115
5476
|
}
|
|
5116
5477
|
}
|
|
5117
5478
|
for (const slot of Object.keys(incoming)) if (!keep.has(slot) && /^[A-Za-z0-9_-]{1,32}$/.test(slot)) deleteImage(slot);
|