dsh-any-background 0.2.4 → 0.2.5
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/README.md +18 -9
- package/README.zh.md +18 -9
- package/lib/client.js +1505 -73
- package/lib/client.js.map +1 -1
- package/lib/index.js +317 -11
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -29,7 +29,7 @@ window.__ModuleLoader__.load({
|
|
|
29
29
|
descColor: "拖动色轮或输入精确数值,整个界面的配色将实时跟随变化",
|
|
30
30
|
descInterface: "为主界面的各个区域单独调节透明度与模糊,营造空间层次感",
|
|
31
31
|
descBackground: "上传一张图片或一段视频作为壁纸并自由摆放,或让算法为你实时生成动态背景",
|
|
32
|
-
descProfile: "
|
|
32
|
+
descProfile: "一键套用外观预设,保存多套配置随时切换,设置昼夜自动换装,或导出/导入完整主题",
|
|
33
33
|
colorTitle: "主题色",
|
|
34
34
|
colorHint: "在色轮外圈选择色相,在内部方形中调整饱和度和明度;双击滑块可恢复默认值",
|
|
35
35
|
swatchTitle: "灵感色板",
|
|
@@ -121,7 +121,58 @@ window.__ModuleLoader__.load({
|
|
|
121
121
|
presetNoise: "流动噪声",
|
|
122
122
|
presetDots: "点阵",
|
|
123
123
|
presetWaves: "波浪",
|
|
124
|
-
presetPoly: "低多边形"
|
|
124
|
+
presetPoly: "低多边形",
|
|
125
|
+
schemeTitle: "界面明暗",
|
|
126
|
+
schemeLight: "亮色",
|
|
127
|
+
schemeDark: "暗色",
|
|
128
|
+
schemeAuto: "自动",
|
|
129
|
+
schemeHint: "「自动」优先跟随背景画面的实际明暗决定界面明暗(无背景时依据主题色明度);强制亮色/暗色后整套配色按所选方向生成,未选主题色时使用中性灰配色",
|
|
130
|
+
presetGalleryTitle: "外观预设",
|
|
131
|
+
presetName_default: "默认",
|
|
132
|
+
presetDesc_default: "恢复插件的初始外观",
|
|
133
|
+
presetName_glass: "毛玻璃",
|
|
134
|
+
presetDesc_glass: "通透表面 + 磨砂玻璃层次",
|
|
135
|
+
presetName_minimal: "极简白",
|
|
136
|
+
presetDesc_minimal: "接近实色的表面,无模糊",
|
|
137
|
+
presetName_midnight: "暗夜紫",
|
|
138
|
+
presetDesc_midnight: "深色强调,重磨砂低透明度",
|
|
139
|
+
presetName_cyber: "赛博",
|
|
140
|
+
presetDesc_cyber: "艳丽洋红,界面近乎消失",
|
|
141
|
+
presetName_warm: "暖阳",
|
|
142
|
+
presetDesc_warm: "琥珀色调的明亮实色界面",
|
|
143
|
+
presetApplied: "预设已应用",
|
|
144
|
+
profilesTitle: "我的配置",
|
|
145
|
+
profileSave: "保存当前配置",
|
|
146
|
+
profileNamePlaceholder: "输入配置名称…",
|
|
147
|
+
profileNameRequired: "请先输入配置名称",
|
|
148
|
+
profileSaved: "配置已保存",
|
|
149
|
+
profileApplied: "配置已应用",
|
|
150
|
+
profileApply: "应用",
|
|
151
|
+
profileDelete: "删除配置",
|
|
152
|
+
profileDeleteConfirm: "确认删除",
|
|
153
|
+
profileNone: "不切换",
|
|
154
|
+
profilesEmpty: "还没有保存的配置。调整好外观后点击「保存当前配置」,即可随时一键切换。",
|
|
155
|
+
scheduleTitle: "昼夜自动切换",
|
|
156
|
+
scheduleHint: "按时间或系统深色模式,在两套已保存的配置之间自动切换(切换的是配色与表面参数,壁纸不变)",
|
|
157
|
+
scheduleMode: "切换依据",
|
|
158
|
+
scheduleModeTime: "固定时段",
|
|
159
|
+
scheduleModeSystem: "跟随系统",
|
|
160
|
+
scheduleTimes: "切换时间",
|
|
161
|
+
scheduleDayStart: "日间开始",
|
|
162
|
+
scheduleNightStart: "夜间开始",
|
|
163
|
+
scheduleProfiles: "对应配置",
|
|
164
|
+
scheduleDayProfile: "日间",
|
|
165
|
+
scheduleNightProfile: "夜间",
|
|
166
|
+
rotTitle: "壁纸轮换",
|
|
167
|
+
rotHint: "把多张图片加入轮换池,按所选频率自动更换壁纸;启用后立即生效",
|
|
168
|
+
rotAdd: "添加图片",
|
|
169
|
+
rotRemove: "移除",
|
|
170
|
+
rotShuffle: "随机",
|
|
171
|
+
rotOrder: "顺序",
|
|
172
|
+
rotReload: "每次刷新",
|
|
173
|
+
rotDaily: "每天",
|
|
174
|
+
rotWeekly: "每周",
|
|
175
|
+
rotNow: "立即切换"
|
|
125
176
|
};
|
|
126
177
|
const en = {
|
|
127
178
|
nav: "Theme",
|
|
@@ -134,7 +185,7 @@ window.__ModuleLoader__.load({
|
|
|
134
185
|
descColor: "Drag the wheel or type exact values — the whole UI follows live",
|
|
135
186
|
descInterface: "Tune opacity and blur per surface to build depth",
|
|
136
187
|
descBackground: "Upload an image or a video as wallpaper and place it freely, or let the algorithm paint a live background",
|
|
137
|
-
descProfile: "
|
|
188
|
+
descProfile: "Apply a one-click preset, save multiple named profiles, schedule a day/night switch, or export/import the full theme",
|
|
138
189
|
colorTitle: "Theme color",
|
|
139
190
|
colorHint: "Pick hue on the outer ring, adjust saturation & lightness in the square; double-click a slider to reset",
|
|
140
191
|
swatchTitle: "Quick swatches",
|
|
@@ -226,7 +277,58 @@ window.__ModuleLoader__.load({
|
|
|
226
277
|
presetNoise: "Flowing noise",
|
|
227
278
|
presetDots: "Dots",
|
|
228
279
|
presetWaves: "Waves",
|
|
229
|
-
presetPoly: "Low poly"
|
|
280
|
+
presetPoly: "Low poly",
|
|
281
|
+
schemeTitle: "Interface scheme",
|
|
282
|
+
schemeLight: "Light",
|
|
283
|
+
schemeDark: "Dark",
|
|
284
|
+
schemeAuto: "Auto",
|
|
285
|
+
schemeHint: "\"Auto\" follows the background brightness first (accent lightness as fallback only); forcing light/dark regenerates the palette in that direction - with no accent picked, a neutral gray palette is used",
|
|
286
|
+
presetGalleryTitle: "Appearance presets",
|
|
287
|
+
presetName_default: "Default",
|
|
288
|
+
presetDesc_default: "Restore the plugin's original look",
|
|
289
|
+
presetName_glass: "Frosted glass",
|
|
290
|
+
presetDesc_glass: "Translucent surfaces + frosted depth",
|
|
291
|
+
presetName_minimal: "Minimal",
|
|
292
|
+
presetDesc_minimal: "Near-solid surfaces, no frost",
|
|
293
|
+
presetName_midnight: "Midnight",
|
|
294
|
+
presetDesc_midnight: "Deep violet accent, heavy frost",
|
|
295
|
+
presetName_cyber: "Cyber",
|
|
296
|
+
presetDesc_cyber: "Vivid magenta, vanishing surfaces",
|
|
297
|
+
presetName_warm: "Warm daylight",
|
|
298
|
+
presetDesc_warm: "Amber accent on solid light surfaces",
|
|
299
|
+
presetApplied: "Preset applied",
|
|
300
|
+
profilesTitle: "Saved profiles",
|
|
301
|
+
profileSave: "Save current",
|
|
302
|
+
profileNamePlaceholder: "Profile name…",
|
|
303
|
+
profileNameRequired: "Enter a name first",
|
|
304
|
+
profileSaved: "Profile saved",
|
|
305
|
+
profileApplied: "Profile applied",
|
|
306
|
+
profileApply: "Apply",
|
|
307
|
+
profileDelete: "Delete profile",
|
|
308
|
+
profileDeleteConfirm: "Sure?",
|
|
309
|
+
profileNone: "None",
|
|
310
|
+
profilesEmpty: "No saved profiles yet. Tune the look, then hit \"Save current\" to switch back anytime.",
|
|
311
|
+
scheduleTitle: "Day/night auto switch",
|
|
312
|
+
scheduleHint: "Automatically swap between two saved profiles by clock time or the OS dark mode (colors and surfaces switch; the wallpaper stays)",
|
|
313
|
+
scheduleMode: "Trigger",
|
|
314
|
+
scheduleModeTime: "Fixed times",
|
|
315
|
+
scheduleModeSystem: "Follow system",
|
|
316
|
+
scheduleTimes: "Switch times",
|
|
317
|
+
scheduleDayStart: "Day starts",
|
|
318
|
+
scheduleNightStart: "Night starts",
|
|
319
|
+
scheduleProfiles: "Profiles",
|
|
320
|
+
scheduleDayProfile: "Day",
|
|
321
|
+
scheduleNightProfile: "Night",
|
|
322
|
+
rotTitle: "Wallpaper rotation",
|
|
323
|
+
rotHint: "Add images to a rotation pool and let the wallpaper change on your cadence; takes effect immediately when enabled",
|
|
324
|
+
rotAdd: "Add images",
|
|
325
|
+
rotRemove: "Remove",
|
|
326
|
+
rotShuffle: "Shuffle",
|
|
327
|
+
rotOrder: "In order",
|
|
328
|
+
rotReload: "Every refresh",
|
|
329
|
+
rotDaily: "Daily",
|
|
330
|
+
rotWeekly: "Weekly",
|
|
331
|
+
rotNow: "Switch now"
|
|
230
332
|
};
|
|
231
333
|
//#endregion
|
|
232
334
|
//#region src/client/state.ts
|
|
@@ -270,7 +372,26 @@ window.__ModuleLoader__.load({
|
|
|
270
372
|
generatedBg: null,
|
|
271
373
|
regenerateOnReload: false,
|
|
272
374
|
chatTextOpacity: 0,
|
|
273
|
-
trajectoryOpacity: 1
|
|
375
|
+
trajectoryOpacity: 1,
|
|
376
|
+
profiles: [],
|
|
377
|
+
rotation: {
|
|
378
|
+
enabled: false,
|
|
379
|
+
mode: "shuffle",
|
|
380
|
+
interval: "daily",
|
|
381
|
+
current: 0,
|
|
382
|
+
items: [],
|
|
383
|
+
lastRotate: null
|
|
384
|
+
},
|
|
385
|
+
schedule: {
|
|
386
|
+
enabled: false,
|
|
387
|
+
mode: "time",
|
|
388
|
+
dayProfile: null,
|
|
389
|
+
nightProfile: null,
|
|
390
|
+
dayStart: "07:00",
|
|
391
|
+
nightStart: "19:00"
|
|
392
|
+
},
|
|
393
|
+
schemeOverride: "auto",
|
|
394
|
+
activeProfile: null
|
|
274
395
|
};
|
|
275
396
|
const clamp01 = (n, def) => typeof n === "number" ? Math.min(1, Math.max(0, n)) : def;
|
|
276
397
|
let cfg = {
|
|
@@ -403,8 +524,144 @@ window.__ModuleLoader__.load({
|
|
|
403
524
|
function rVideoBgState() {
|
|
404
525
|
return cfg.videoBgState;
|
|
405
526
|
}
|
|
527
|
+
function rProfiles() {
|
|
528
|
+
return Array.isArray(cfg.profiles) ? cfg.profiles : [];
|
|
529
|
+
}
|
|
530
|
+
function rRotation() {
|
|
531
|
+
const r = cfg.rotation;
|
|
532
|
+
return r && typeof r === "object" ? {
|
|
533
|
+
...DEFAULT_CONFIG.rotation,
|
|
534
|
+
...r,
|
|
535
|
+
items: Array.isArray(r.items) ? r.items : []
|
|
536
|
+
} : {
|
|
537
|
+
...DEFAULT_CONFIG.rotation,
|
|
538
|
+
items: []
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
function rSchedule() {
|
|
542
|
+
return cfg.schedule && typeof cfg.schedule === "object" ? {
|
|
543
|
+
...DEFAULT_CONFIG.schedule,
|
|
544
|
+
...cfg.schedule
|
|
545
|
+
} : { ...DEFAULT_CONFIG.schedule };
|
|
546
|
+
}
|
|
547
|
+
function rSchemeOverride() {
|
|
548
|
+
return cfg.schemeOverride === "light" || cfg.schemeOverride === "dark" ? cfg.schemeOverride : "auto";
|
|
549
|
+
}
|
|
550
|
+
/** Effective interface scheme (drives data-ds-dark-theme / color-scheme): a
|
|
551
|
+
* forced override wins; in auto a picked color decides through its palette
|
|
552
|
+
* direction (keeps native controls aligned with the drawn surfaces), and
|
|
553
|
+
* without a pick the background's brightness verdict does. Stay light as the
|
|
554
|
+
* last fallback. */
|
|
555
|
+
function rScheme() {
|
|
556
|
+
const o = rSchemeOverride();
|
|
557
|
+
if (o !== "auto") return o;
|
|
558
|
+
if (rHasColor()) return rColorScheme();
|
|
559
|
+
return rBgDark() ? "dark" : "light";
|
|
560
|
+
}
|
|
561
|
+
/** Direction the color palette itself is built in: a forced override wins; in
|
|
562
|
+
* auto the theme color's own lightness decides (0.2.4 behavior) so surfaces
|
|
563
|
+
* always contrast with the palette's label fonts — the wallpaper verdict must
|
|
564
|
+
* not drag a dark color's surfaces into a light build (and vice versa) or
|
|
565
|
+
* text and surfaces converge. */
|
|
566
|
+
function rColorScheme() {
|
|
567
|
+
const o = rSchemeOverride();
|
|
568
|
+
if (o !== "auto") return o;
|
|
569
|
+
return rHasColor() && rColor()[2] < .55 ? "dark" : "light";
|
|
570
|
+
}
|
|
571
|
+
/** Snapshot of the appearance fields a profile/preset restores. */
|
|
572
|
+
function currentAppearance() {
|
|
573
|
+
return {
|
|
574
|
+
color: cfg.color,
|
|
575
|
+
opacities: { ...rOps() },
|
|
576
|
+
blurs: { ...rBlurs() },
|
|
577
|
+
settingsOpacity: rSop(),
|
|
578
|
+
wallpaperOpacity: rWop(),
|
|
579
|
+
blur: rBl(),
|
|
580
|
+
chatTextOpacity: rChatTextOpacity(),
|
|
581
|
+
trajectoryOpacity: rTrajectoryOpacity()
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
/** Apply an appearance snapshot onto cfg (meta fields untouched). */
|
|
585
|
+
function applyAppearance(ap) {
|
|
586
|
+
cfg.color = Array.isArray(ap.color) && ap.color.length === 3 ? [...ap.color] : null;
|
|
587
|
+
cfg.opacities = {
|
|
588
|
+
...DEFAULT_CONFIG.opacities,
|
|
589
|
+
...ap.opacities ?? {}
|
|
590
|
+
};
|
|
591
|
+
cfg.blurs = {
|
|
592
|
+
...DEFAULT_CONFIG.blurs,
|
|
593
|
+
...ap.blurs ?? {}
|
|
594
|
+
};
|
|
595
|
+
cfg.settingsOpacity = clamp01(ap.settingsOpacity, DEFAULT_CONFIG.settingsOpacity);
|
|
596
|
+
cfg.wallpaperOpacity = clamp01(ap.wallpaperOpacity, DEFAULT_CONFIG.wallpaperOpacity);
|
|
597
|
+
cfg.blur = typeof ap.blur === "number" ? Math.min(60, Math.max(0, ap.blur)) : DEFAULT_CONFIG.blur;
|
|
598
|
+
cfg.chatTextOpacity = clamp01(ap.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity);
|
|
599
|
+
cfg.trajectoryOpacity = clamp01(ap.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity);
|
|
600
|
+
}
|
|
406
601
|
const num = (n, def) => typeof n === "number" ? n : def;
|
|
407
602
|
const cl = (n, lo, hi, def) => typeof n === "number" ? Math.min(hi, Math.max(lo, n)) : def;
|
|
603
|
+
const HHMM_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
604
|
+
function adoptProfiles(raw) {
|
|
605
|
+
if (!Array.isArray(raw)) return [];
|
|
606
|
+
const out = [];
|
|
607
|
+
for (const item of raw.slice(0, 20)) {
|
|
608
|
+
const p = item ?? {};
|
|
609
|
+
if (typeof p.id !== "string" || p.id.length === 0 || p.id.length > 64) continue;
|
|
610
|
+
if (out.some((e) => e.id === p.id)) continue;
|
|
611
|
+
const ac = p.config ?? {};
|
|
612
|
+
out.push({
|
|
613
|
+
id: p.id,
|
|
614
|
+
name: typeof p.name === "string" && p.name.trim() ? p.name.slice(0, 60) : "Profile",
|
|
615
|
+
createdAt: typeof p.createdAt === "string" ? p.createdAt : "",
|
|
616
|
+
config: {
|
|
617
|
+
color: Array.isArray(ac.color) && ac.color.length === 3 ? [...ac.color] : null,
|
|
618
|
+
opacities: {
|
|
619
|
+
...DEFAULT_CONFIG.opacities,
|
|
620
|
+
...ac.opacities ?? {}
|
|
621
|
+
},
|
|
622
|
+
blurs: {
|
|
623
|
+
...DEFAULT_CONFIG.blurs,
|
|
624
|
+
...ac.blurs ?? {}
|
|
625
|
+
},
|
|
626
|
+
settingsOpacity: clamp01(ac.settingsOpacity, DEFAULT_CONFIG.settingsOpacity),
|
|
627
|
+
wallpaperOpacity: clamp01(ac.wallpaperOpacity, DEFAULT_CONFIG.wallpaperOpacity),
|
|
628
|
+
blur: num(ac.blur, DEFAULT_CONFIG.blur),
|
|
629
|
+
chatTextOpacity: clamp01(ac.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity),
|
|
630
|
+
trajectoryOpacity: clamp01(ac.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity)
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
return out;
|
|
635
|
+
}
|
|
636
|
+
function adoptRotation(raw) {
|
|
637
|
+
const r = raw ?? {};
|
|
638
|
+
const items = Array.isArray(r.items) ? r.items.filter((it) => {
|
|
639
|
+
const i = it ?? {};
|
|
640
|
+
return typeof i?.file === "string" && /^[\w-]+\.(jpg|jpeg|png|gif|webp)$/i.test(i.file);
|
|
641
|
+
}).slice(0, 30).map((it) => ({
|
|
642
|
+
file: it.file,
|
|
643
|
+
thumb: typeof it.thumb === "string" && it.thumb.startsWith("data:image/") && it.thumb.length <= 65536 ? it.thumb : ""
|
|
644
|
+
})) : [];
|
|
645
|
+
return {
|
|
646
|
+
enabled: r.enabled === true,
|
|
647
|
+
mode: r.mode === "order" ? "order" : "shuffle",
|
|
648
|
+
interval: r.interval === "reload" || r.interval === "weekly" ? r.interval : "daily",
|
|
649
|
+
current: num(r.current, 0),
|
|
650
|
+
items,
|
|
651
|
+
lastRotate: typeof r.lastRotate === "string" ? r.lastRotate : null
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
function adoptSchedule(raw) {
|
|
655
|
+
const r = raw ?? {};
|
|
656
|
+
return {
|
|
657
|
+
enabled: r.enabled === true,
|
|
658
|
+
mode: r.mode === "system" ? "system" : "time",
|
|
659
|
+
dayProfile: typeof r.dayProfile === "string" ? r.dayProfile : null,
|
|
660
|
+
nightProfile: typeof r.nightProfile === "string" ? r.nightProfile : null,
|
|
661
|
+
dayStart: typeof r.dayStart === "string" && HHMM_RE.test(r.dayStart) ? r.dayStart : DEFAULT_CONFIG.schedule.dayStart,
|
|
662
|
+
nightStart: typeof r.nightStart === "string" && HHMM_RE.test(r.nightStart) ? r.nightStart : DEFAULT_CONFIG.schedule.nightStart
|
|
663
|
+
};
|
|
664
|
+
}
|
|
408
665
|
function adoptBgState(s) {
|
|
409
666
|
return {
|
|
410
667
|
zoom: num(s.zoom, 1),
|
|
@@ -470,7 +727,12 @@ window.__ModuleLoader__.load({
|
|
|
470
727
|
generatedBg: generatedBg ? normalizeGeneratedBg(generatedBg) : null,
|
|
471
728
|
regenerateOnReload: typeof c.regenerateOnReload === "boolean" ? c.regenerateOnReload : DEFAULT_CONFIG.regenerateOnReload,
|
|
472
729
|
chatTextOpacity: clamp01(c.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity),
|
|
473
|
-
trajectoryOpacity: clamp01(c.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity)
|
|
730
|
+
trajectoryOpacity: clamp01(c.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity),
|
|
731
|
+
profiles: adoptProfiles(c.profiles),
|
|
732
|
+
rotation: adoptRotation(c.rotation),
|
|
733
|
+
schedule: adoptSchedule(c.schedule),
|
|
734
|
+
schemeOverride: c.schemeOverride === "light" || c.schemeOverride === "dark" ? c.schemeOverride : "auto",
|
|
735
|
+
activeProfile: typeof c.activeProfile === "string" ? c.activeProfile : null
|
|
474
736
|
};
|
|
475
737
|
}
|
|
476
738
|
function normalizeGeneratedBg(p) {
|
|
@@ -548,7 +810,9 @@ window.__ModuleLoader__.load({
|
|
|
548
810
|
function persistConfig() {
|
|
549
811
|
rpcCall("writeConfig", { config: cfg });
|
|
550
812
|
}
|
|
551
|
-
/** Load the persisted theme (config + wallpaper + video URL) from the node half.
|
|
813
|
+
/** Load the persisted theme (config + wallpaper + video URL) from the node half.
|
|
814
|
+
* Resolves true when the server advanced a due wallpaper rotation during the
|
|
815
|
+
* read — the restored wallpaper is then already the new pick. */
|
|
552
816
|
async function loadPersisted() {
|
|
553
817
|
const data = await rpcCall("read", {});
|
|
554
818
|
if (data && typeof data === "object") {
|
|
@@ -559,7 +823,9 @@ window.__ModuleLoader__.load({
|
|
|
559
823
|
if (typeof d.videoUrl === "string") setWpVideoUrl(d.videoUrl, cfg.videoMime);
|
|
560
824
|
else if (d.videoUrl === null) setWpVideoUrl(null, null);
|
|
561
825
|
if (cfg.backgroundType === "image") setWpUrl(d.wallpaper === null ? null : d.wallpaper);
|
|
826
|
+
return d.rotated === true;
|
|
562
827
|
}
|
|
828
|
+
return false;
|
|
563
829
|
}
|
|
564
830
|
/** Persist a wallpaper (null removes it); one-shot, no debounce. */
|
|
565
831
|
function persistWallpaper(dataUrl) {
|
|
@@ -618,6 +884,79 @@ window.__ModuleLoader__.load({
|
|
|
618
884
|
return false;
|
|
619
885
|
}
|
|
620
886
|
}
|
|
887
|
+
/** Add an image (data URL + small thumbnail) to the server-side rotation pool. */
|
|
888
|
+
async function rotationAdd(dataUrl, thumb) {
|
|
889
|
+
if (!rpcCallFn) return {
|
|
890
|
+
ok: false,
|
|
891
|
+
error: "rpc not ready"
|
|
892
|
+
};
|
|
893
|
+
try {
|
|
894
|
+
const res = await rpcCallFn(rpcEndpoint("rotationAdd"), {
|
|
895
|
+
dataUrl,
|
|
896
|
+
thumb
|
|
897
|
+
});
|
|
898
|
+
if (res && res.ok === true) return res.value ?? {
|
|
899
|
+
ok: false,
|
|
900
|
+
error: "no value"
|
|
901
|
+
};
|
|
902
|
+
return {
|
|
903
|
+
ok: false,
|
|
904
|
+
error: res?.error?.message ?? "request failed"
|
|
905
|
+
};
|
|
906
|
+
} catch (e) {
|
|
907
|
+
return {
|
|
908
|
+
ok: false,
|
|
909
|
+
error: e instanceof Error ? e.message : String(e)
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
/** Remove a rotation item by index. Returns the updated items list. */
|
|
914
|
+
async function rotationRemove(index) {
|
|
915
|
+
if (!rpcCallFn) return {
|
|
916
|
+
ok: false,
|
|
917
|
+
error: "rpc not ready"
|
|
918
|
+
};
|
|
919
|
+
try {
|
|
920
|
+
const res = await rpcCallFn(rpcEndpoint("rotationRemove"), { index });
|
|
921
|
+
if (res && res.ok === true) return res.value ?? {
|
|
922
|
+
ok: false,
|
|
923
|
+
error: "no value"
|
|
924
|
+
};
|
|
925
|
+
return {
|
|
926
|
+
ok: false,
|
|
927
|
+
error: res?.error?.message ?? "request failed"
|
|
928
|
+
};
|
|
929
|
+
} catch (e) {
|
|
930
|
+
return {
|
|
931
|
+
ok: false,
|
|
932
|
+
error: e instanceof Error ? e.message : String(e)
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
/** Activate a rotation item: the server copies its bytes into the wallpaper
|
|
937
|
+
* slot and returns the freshly sniffed data URL for immediate display. */
|
|
938
|
+
async function rotationActivate(index) {
|
|
939
|
+
if (!rpcCallFn) return {
|
|
940
|
+
ok: false,
|
|
941
|
+
error: "rpc not ready"
|
|
942
|
+
};
|
|
943
|
+
try {
|
|
944
|
+
const res = await rpcCallFn(rpcEndpoint("rotationSet"), { index });
|
|
945
|
+
if (res && res.ok === true) return res.value ?? {
|
|
946
|
+
ok: false,
|
|
947
|
+
error: "no value"
|
|
948
|
+
};
|
|
949
|
+
return {
|
|
950
|
+
ok: false,
|
|
951
|
+
error: res?.error?.message ?? "request failed"
|
|
952
|
+
};
|
|
953
|
+
} catch (e) {
|
|
954
|
+
return {
|
|
955
|
+
ok: false,
|
|
956
|
+
error: e instanceof Error ? e.message : String(e)
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
}
|
|
621
960
|
//#endregion
|
|
622
961
|
//#region src/client/utils/color.ts
|
|
623
962
|
function hsvToHsl(h, s, v) {
|
|
@@ -639,20 +978,23 @@ window.__ModuleLoader__.load({
|
|
|
639
978
|
let tokensCacheKey = "";
|
|
640
979
|
let tokensCache = null;
|
|
641
980
|
/**
|
|
642
|
-
* Memoized token generation: the same (hue, sat, lit) input always
|
|
643
|
-
* same token set, and applyWp / applyCustomTokens /
|
|
644
|
-
* this repeatedly (slider drags, viewport
|
|
645
|
-
* and skip the 30+ hsl() string builds
|
|
981
|
+
* Memoized token generation: the same (hue, sat, lit, scheme) input always
|
|
982
|
+
* yields the same token set, and applyWp / applyCustomTokens /
|
|
983
|
+
* applySettingsOverrides call this repeatedly (slider drags, viewport
|
|
984
|
+
* re-applies), so cache the last result and skip the 30+ hsl() string builds
|
|
985
|
+
* when nothing changed. The optional scheme forces the palette direction
|
|
986
|
+
* (light/dark) independently of the color's own lightness.
|
|
646
987
|
*/
|
|
647
|
-
function genTokens(hue, sat, lit) {
|
|
648
|
-
const key = `${hue}|${sat}|${lit}`;
|
|
988
|
+
function genTokens(hue, sat, lit, scheme) {
|
|
989
|
+
const key = `${hue}|${sat}|${lit}|${scheme ?? "auto"}`;
|
|
649
990
|
if (tokensCacheKey === key && tokensCache) return tokensCache;
|
|
650
991
|
tokensCacheKey = key;
|
|
651
|
-
tokensCache = buildTokens(hue, sat, lit);
|
|
992
|
+
tokensCache = buildTokens(hue, sat, lit, scheme);
|
|
652
993
|
return tokensCache;
|
|
653
994
|
}
|
|
654
|
-
function buildTokens(hue, sat, lit) {
|
|
655
|
-
const dark = lit < .55;
|
|
995
|
+
function buildTokens(hue, sat, lit, scheme) {
|
|
996
|
+
const dark = scheme !== void 0 ? scheme === "dark" : lit < .55;
|
|
997
|
+
if (scheme !== void 0 && lit < .55 !== dark) lit = dark ? Math.min(.44, Math.max(.14, 1 - lit)) : Math.max(.6, Math.min(.88, 1 - lit));
|
|
656
998
|
const h = (d) => ((hue + d) % 360 + 360) % 360;
|
|
657
999
|
const s = (d) => Math.max(0, Math.min(1, sat + d));
|
|
658
1000
|
const l = (d) => Math.max(0, Math.min(1, lit + d));
|
|
@@ -861,7 +1203,7 @@ window.__ModuleLoader__.load({
|
|
|
861
1203
|
const max = Math.max(r, gg, b), min = Math.min(r, gg, b);
|
|
862
1204
|
const v = max / 255;
|
|
863
1205
|
const s = max === 0 ? 0 : (max - min) / max;
|
|
864
|
-
totalLum +=
|
|
1206
|
+
totalLum += (.2126 * r + .7152 * gg + .0722 * b) / 255;
|
|
865
1207
|
sampled++;
|
|
866
1208
|
if (s < .08 || v < .12 || v > .97) continue;
|
|
867
1209
|
const key = r >> 4 << 8 | gg >> 4 << 4 | b >> 4;
|
|
@@ -1589,10 +1931,20 @@ window.__ModuleLoader__.load({
|
|
|
1589
1931
|
for (const name of appliedTokenNames) document.body.style.removeProperty(name);
|
|
1590
1932
|
appliedTokenNames = [];
|
|
1591
1933
|
}
|
|
1592
|
-
/**
|
|
1593
|
-
*
|
|
1594
|
-
|
|
1595
|
-
|
|
1934
|
+
/** Drop every applied custom token and forget the token fingerprint, so a
|
|
1935
|
+
* later color-less profile (system theme) leaves no stale rule behind. */
|
|
1936
|
+
function clearThemeTokens() {
|
|
1937
|
+
clearCustomTokens();
|
|
1938
|
+
baseTokenKey = "";
|
|
1939
|
+
lastBgKey = "";
|
|
1940
|
+
document.body.removeAttribute("data-ds-dark-theme");
|
|
1941
|
+
document.body.style.removeProperty("color-scheme");
|
|
1942
|
+
}
|
|
1943
|
+
/** Label tokens flipped by the background brightness verdict. The faint tiers
|
|
1944
|
+
* (caption/dimmed) are deliberately NOT flipped: they back placeholder/hint
|
|
1945
|
+
* text, which must stay visibly weaker than real input even when the wallpaper
|
|
1946
|
+
* brightness flips the main label direction. Exported so the skin registration
|
|
1947
|
+
* (index) can flip fonts through the host theme service too. */
|
|
1596
1948
|
const LABEL_TOKENS = [
|
|
1597
1949
|
"--dsw-alias-label-primary",
|
|
1598
1950
|
"--dsw-alias-label-secondary",
|
|
@@ -1645,19 +1997,41 @@ window.__ModuleLoader__.load({
|
|
|
1645
1997
|
});
|
|
1646
1998
|
}
|
|
1647
1999
|
let lastBgKey = "";
|
|
2000
|
+
/** Palette source for the token rule: the picked color; a forced scheme
|
|
2001
|
+
* without a picked color builds a neutral near-gray palette in that
|
|
2002
|
+
* direction; auto without a color keeps the host's own palette (null — only
|
|
2003
|
+
* the label direction gets asserted). The palette direction follows the
|
|
2004
|
+
* color's own lightness (rColorScheme), not the wallpaper verdict, so the
|
|
2005
|
+
* surfaces keep contrasting with the fonts. */
|
|
2006
|
+
function paletteTokens() {
|
|
2007
|
+
const scheme = rColorScheme();
|
|
2008
|
+
if (rHasColor()) {
|
|
2009
|
+
const [h, s, l] = rColor();
|
|
2010
|
+
return genTokens(h, s, l, scheme).tokens;
|
|
2011
|
+
}
|
|
2012
|
+
if (rSchemeOverride() !== "auto") return genTokens(220, .04, scheme === "dark" ? .14 : .92, scheme).tokens;
|
|
2013
|
+
return null;
|
|
2014
|
+
}
|
|
1648
2015
|
function applyCustomTokensNow(ops) {
|
|
2016
|
+
const hasColor = rHasColor();
|
|
2017
|
+
const override = rSchemeOverride();
|
|
1649
2018
|
const [h, s, l] = rColor();
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
2019
|
+
const scheme = rScheme();
|
|
2020
|
+
const verdict = rBgDark();
|
|
2021
|
+
const palette = paletteTokens();
|
|
2022
|
+
const tokens = { ...palette };
|
|
2023
|
+
if (override === "auto") {
|
|
2024
|
+
const fontDark = hasColor ? rColorScheme() === "dark" : verdict;
|
|
2025
|
+
if (fontDark !== null && fontDark !== void 0) {
|
|
2026
|
+
const font = fontDark ? "#fff" : "#000";
|
|
1656
2027
|
for (const name of LABEL_TOKENS) tokens[name] = font;
|
|
1657
2028
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
2029
|
+
}
|
|
2030
|
+
try {
|
|
2031
|
+
const forceDark = scheme === "dark";
|
|
2032
|
+
const key = `${h}|${s}|${l}|${verdict}|${scheme}|${hasColor}`;
|
|
2033
|
+
if (key !== baseTokenKey) {
|
|
2034
|
+
baseTokenKey = key;
|
|
1661
2035
|
if (forceDark) document.body.setAttribute("data-ds-dark-theme", "dsh-any-background");
|
|
1662
2036
|
else document.body.removeAttribute("data-ds-dark-theme");
|
|
1663
2037
|
const decls = [`color-scheme:${forceDark ? "dark" : "light"}`];
|
|
@@ -1669,6 +2043,7 @@ window.__ModuleLoader__.load({
|
|
|
1669
2043
|
for (const name of appliedTokenNames) document.body.style.removeProperty(name);
|
|
1670
2044
|
appliedTokenNames = Object.keys(tokens);
|
|
1671
2045
|
}
|
|
2046
|
+
if (palette === null) return;
|
|
1672
2047
|
const root = document.documentElement;
|
|
1673
2048
|
for (const g of OPACITY_TOKEN_GROUPS) for (const name of g.names) root.style.setProperty(OPACITY_VARS[name], toRgba(tokens[name] ?? "#000", ops[g.part]));
|
|
1674
2049
|
root.style.setProperty("--dsh-any-op-menu-cordis", toRgba(tokens["--dsw-specific-menu"] ?? "#000", ops.input));
|
|
@@ -1688,7 +2063,7 @@ window.__ModuleLoader__.load({
|
|
|
1688
2063
|
}
|
|
1689
2064
|
function applySettingsOverrides(op) {
|
|
1690
2065
|
const [h, s, l] = rColor();
|
|
1691
|
-
const tokens = genTokens(h, s, l).tokens;
|
|
2066
|
+
const tokens = genTokens(h, s, l, rColorScheme()).tokens;
|
|
1692
2067
|
const layer1 = tokens["--dsw-alias-bg-layer-1"];
|
|
1693
2068
|
const layer2 = tokens["--dsw-alias-bg-layer-2"];
|
|
1694
2069
|
const layer3 = tokens["--dsw-alias-bg-layer-3"];
|
|
@@ -1700,7 +2075,7 @@ window.__ModuleLoader__.load({
|
|
|
1700
2075
|
const TRAJECTORY_STYLE_RULE = "[data-conversation-composer-overlay]{--dsw-alias-bg-layer-1:var(--dsh-any-traj-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-traj-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-traj-layer-3)}";
|
|
1701
2076
|
function applyTrajectoryOverrides(op) {
|
|
1702
2077
|
const [h, s, l] = rColor();
|
|
1703
|
-
const tokens = genTokens(h, s, l).tokens;
|
|
2078
|
+
const tokens = genTokens(h, s, l, rColorScheme()).tokens;
|
|
1704
2079
|
const layer1 = tokens["--dsw-alias-bg-layer-1"];
|
|
1705
2080
|
const layer2 = tokens["--dsw-alias-bg-layer-2"];
|
|
1706
2081
|
const layer3 = tokens["--dsw-alias-bg-layer-3"];
|
|
@@ -1708,6 +2083,14 @@ window.__ModuleLoader__.load({
|
|
|
1708
2083
|
if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-2", toRgba(layer2, op));
|
|
1709
2084
|
if (layer3 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-3", toRgba(layer3, op));
|
|
1710
2085
|
}
|
|
2086
|
+
/** Register a callback fired when a wallpaper-extracted theme color is
|
|
2087
|
+
* adopted by the auto-adaptation path, so the section can re-register the
|
|
2088
|
+
* host skin, sync the editor UI and persist the pick (wallpaper.ts cannot
|
|
2089
|
+
* do those itself — they live in the section). */
|
|
2090
|
+
let colorAdoptedListener = null;
|
|
2091
|
+
function onColorAdopted(cb) {
|
|
2092
|
+
colorAdoptedListener = cb;
|
|
2093
|
+
}
|
|
1711
2094
|
/** Apply the theme color: use the saved pick directly, or fall back to
|
|
1712
2095
|
* extracting a dominant color from the current wallpaper. */
|
|
1713
2096
|
function applyThemeColor() {
|
|
@@ -1717,7 +2100,10 @@ window.__ModuleLoader__.load({
|
|
|
1717
2100
|
}
|
|
1718
2101
|
const url = rWp();
|
|
1719
2102
|
if (url) extractWallpaperColor(url, cfg.backgroundType === "video" ? rVideoBgState() : rBgState()).then((hsl) => {
|
|
1720
|
-
if (hsl)
|
|
2103
|
+
if (hsl) {
|
|
2104
|
+
cfg.color = hsl;
|
|
2105
|
+
colorAdoptedListener?.(hsl);
|
|
2106
|
+
}
|
|
1721
2107
|
applyWp();
|
|
1722
2108
|
});
|
|
1723
2109
|
else applyWp();
|
|
@@ -1782,7 +2168,7 @@ window.__ModuleLoader__.load({
|
|
|
1782
2168
|
setBgDark(null);
|
|
1783
2169
|
analyzeFrameDark(frame).then((dark) => {
|
|
1784
2170
|
if (dark === null || wpController !== controller) return;
|
|
1785
|
-
|
|
2171
|
+
applyVerdict(dark);
|
|
1786
2172
|
applyCustomTokens(rOps());
|
|
1787
2173
|
});
|
|
1788
2174
|
});
|
|
@@ -1848,11 +2234,11 @@ window.__ModuleLoader__.load({
|
|
|
1848
2234
|
* reducing the main-bg opacity stacked a second alpha onto the sidebar; moving
|
|
1849
2235
|
* the alpha onto the columns keeps the sidebar owned by its own slider. */
|
|
1850
2236
|
function applyPartOpacities(ops) {
|
|
1851
|
-
|
|
2237
|
+
const tokens = paletteTokens();
|
|
2238
|
+
if (tokens === null) return;
|
|
1852
2239
|
discoverParts();
|
|
1853
2240
|
if (frameEl === null) return;
|
|
1854
|
-
const
|
|
1855
|
-
const base = genTokens(h, s, l).tokens["--dsw-alias-bg-base"];
|
|
2241
|
+
const base = tokens["--dsw-alias-bg-base"];
|
|
1856
2242
|
frameEl.style.background = "transparent";
|
|
1857
2243
|
if (centerEl !== null) centerEl.style.background = base !== void 0 ? toRgba(base, ops.bg) : "transparent";
|
|
1858
2244
|
if (detailsEl !== null) detailsEl.style.background = base !== void 0 ? toRgba(base, ops.bg) : "transparent";
|
|
@@ -2071,7 +2457,7 @@ window.__ModuleLoader__.load({
|
|
|
2071
2457
|
discoverParts();
|
|
2072
2458
|
if (centerEl === null) return;
|
|
2073
2459
|
const [h, s, l] = rColor();
|
|
2074
|
-
const surface = genTokens(h, s, l).tokens["--dsw-alias-bg-layer-1"];
|
|
2460
|
+
const surface = genTokens(h, s, l, rColorScheme()).tokens["--dsw-alias-bg-layer-1"];
|
|
2075
2461
|
VIEW_CARDS.forEach((spec, i) => {
|
|
2076
2462
|
const target = discoverViewTarget(i, spec);
|
|
2077
2463
|
if (target === null) return;
|
|
@@ -2114,8 +2500,7 @@ window.__ModuleLoader__.load({
|
|
|
2114
2500
|
let themeObserver = null;
|
|
2115
2501
|
let themeRaf = 0;
|
|
2116
2502
|
function reassertScheme() {
|
|
2117
|
-
|
|
2118
|
-
if (rBgDark() ?? l < .55) document.body.setAttribute("data-ds-dark-theme", "dsh-any-background");
|
|
2503
|
+
if (rScheme() === "dark") document.body.setAttribute("data-ds-dark-theme", "dsh-any-background");
|
|
2119
2504
|
else document.body.removeAttribute("data-ds-dark-theme");
|
|
2120
2505
|
applyCustomTokens(rOps());
|
|
2121
2506
|
}
|
|
@@ -2126,7 +2511,7 @@ window.__ModuleLoader__.load({
|
|
|
2126
2511
|
if (themeObserver !== null || typeof MutationObserver === "undefined") return () => void 0;
|
|
2127
2512
|
themeObserver = new MutationObserver(() => {
|
|
2128
2513
|
if (document.body.getAttribute("data-ds-dark-theme") === "dsh-any-background") return;
|
|
2129
|
-
if (!(rHasColor() || rBgDark() !== null)) return;
|
|
2514
|
+
if (!(rHasColor() || rBgDark() !== null || rSchemeOverride() !== "auto")) return;
|
|
2130
2515
|
if (themeRaf !== 0) return;
|
|
2131
2516
|
themeRaf = requestAnimationFrame(() => {
|
|
2132
2517
|
themeRaf = 0;
|
|
@@ -2253,6 +2638,38 @@ window.__ModuleLoader__.load({
|
|
|
2253
2638
|
if (dragLow) setDragLow(false);
|
|
2254
2639
|
};
|
|
2255
2640
|
}
|
|
2641
|
+
let wpVerdict = null;
|
|
2642
|
+
let verdictListener = null;
|
|
2643
|
+
/** Register a callback fired when the background brightness verdict CHANGES
|
|
2644
|
+
* (a new wallpaper was analyzed, a generated bg regenerated), so the skin can
|
|
2645
|
+
* be re-registered through the host theme service. */
|
|
2646
|
+
function onVerdictApplied(cb) {
|
|
2647
|
+
verdictListener = cb;
|
|
2648
|
+
}
|
|
2649
|
+
function applyVerdict(dark) {
|
|
2650
|
+
if (rBgDark() === dark) return;
|
|
2651
|
+
setBgDark(dark);
|
|
2652
|
+
if (dark !== null) verdictListener?.();
|
|
2653
|
+
}
|
|
2654
|
+
function updateWpVerdict(url) {
|
|
2655
|
+
if (url === null) {
|
|
2656
|
+
applyVerdict(null);
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
|
+
if (wpVerdict !== null && wpVerdict.url === url) {
|
|
2660
|
+
applyVerdict(wpVerdict.dark);
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
analyzeFrameDark(url).then((dark) => {
|
|
2664
|
+
if (dark === null) return;
|
|
2665
|
+
wpVerdict = {
|
|
2666
|
+
url,
|
|
2667
|
+
dark
|
|
2668
|
+
};
|
|
2669
|
+
applyVerdict(dark);
|
|
2670
|
+
applyCustomTokens(rOps());
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2256
2673
|
function applyImageWp(url) {
|
|
2257
2674
|
clearDynamicBg();
|
|
2258
2675
|
clearVideoEl();
|
|
@@ -2299,6 +2716,7 @@ window.__ModuleLoader__.load({
|
|
|
2299
2716
|
}
|
|
2300
2717
|
captureLowRes(url, () => void 0);
|
|
2301
2718
|
applyWpEffects();
|
|
2719
|
+
updateWpVerdict(url);
|
|
2302
2720
|
}
|
|
2303
2721
|
/** Video wallpaper: a muted looping <video> inside the wallpaper layer.
|
|
2304
2722
|
* Placement modes map onto object-fit (tile has no video equivalent and
|
|
@@ -2335,6 +2753,7 @@ window.__ModuleLoader__.load({
|
|
|
2335
2753
|
videoEl.style.objectFit = mode === "stretch" ? "fill" : mode === "fill" || mode === "tile" ? "cover" : "contain";
|
|
2336
2754
|
}
|
|
2337
2755
|
applyWpEffects();
|
|
2756
|
+
updateWpVerdict(rWp());
|
|
2338
2757
|
}
|
|
2339
2758
|
function applyWpEffects() {
|
|
2340
2759
|
if (!wpEl) return;
|
|
@@ -2368,8 +2787,10 @@ window.__ModuleLoader__.load({
|
|
|
2368
2787
|
clearVideoEl();
|
|
2369
2788
|
wpEl?.remove();
|
|
2370
2789
|
wpEl = null;
|
|
2790
|
+
wpVerdict = null;
|
|
2791
|
+
setBgDark(null);
|
|
2371
2792
|
}
|
|
2372
|
-
if (rHasColor() || rBgDark() !== null) applyCustomTokens(rOps());
|
|
2793
|
+
if (rHasColor() || rBgDark() !== null || rSchemeOverride() !== "auto") applyCustomTokens(rOps());
|
|
2373
2794
|
if (rHasColor()) {
|
|
2374
2795
|
applySettingsOverrides(rSop());
|
|
2375
2796
|
applyTrajectoryOverrides(rTrajectoryOpacity());
|
|
@@ -2381,6 +2802,7 @@ window.__ModuleLoader__.load({
|
|
|
2381
2802
|
clearVideoEl();
|
|
2382
2803
|
disposeVideoObjectUrl();
|
|
2383
2804
|
setBgDark(null);
|
|
2805
|
+
wpVerdict = null;
|
|
2384
2806
|
wpEl?.remove();
|
|
2385
2807
|
wpEl = null;
|
|
2386
2808
|
clearCustomTokens();
|
|
@@ -2490,6 +2912,56 @@ window.__ModuleLoader__.load({
|
|
|
2490
2912
|
v.src = url;
|
|
2491
2913
|
});
|
|
2492
2914
|
}
|
|
2915
|
+
//#endregion
|
|
2916
|
+
//#region src/client/utils/image.ts
|
|
2917
|
+
/**
|
|
2918
|
+
* Read a chosen image file as a data URL WITHOUT re-encoding: the original
|
|
2919
|
+
* pixels are kept as-is (no canvas downscale / JPEG re-compression), so the
|
|
2920
|
+
* wallpaper is stored and displayed at full fidelity. The tradeoff is a larger
|
|
2921
|
+
* payload over the RPC channel and on disk for big images.
|
|
2922
|
+
*/
|
|
2923
|
+
function readImg(file, cb) {
|
|
2924
|
+
const r = new FileReader();
|
|
2925
|
+
r.onerror = () => cb(null);
|
|
2926
|
+
r.onload = () => cb(r.result);
|
|
2927
|
+
r.readAsDataURL(file);
|
|
2928
|
+
}
|
|
2929
|
+
/** Promised readImg variant for async flows. */
|
|
2930
|
+
function readImgAsync(file) {
|
|
2931
|
+
return new Promise((resolve) => readImg(file, resolve));
|
|
2932
|
+
}
|
|
2933
|
+
/** Build a small JPEG thumbnail (rotation-pool picker preview). Resolves null
|
|
2934
|
+
* when the image cannot be decoded. */
|
|
2935
|
+
function makeThumb(dataUrl, maxSide = 96) {
|
|
2936
|
+
return new Promise((resolve) => {
|
|
2937
|
+
const img = new Image();
|
|
2938
|
+
img.onerror = () => resolve(null);
|
|
2939
|
+
img.onload = () => {
|
|
2940
|
+
try {
|
|
2941
|
+
const iw = img.naturalWidth || img.width;
|
|
2942
|
+
const ih = img.naturalHeight || img.height;
|
|
2943
|
+
if (iw <= 0 || ih <= 0) {
|
|
2944
|
+
resolve(null);
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2947
|
+
const k = Math.min(1, maxSide / Math.max(iw, ih));
|
|
2948
|
+
const c = document.createElement("canvas");
|
|
2949
|
+
c.width = Math.max(1, Math.round(iw * k));
|
|
2950
|
+
c.height = Math.max(1, Math.round(ih * k));
|
|
2951
|
+
const g = c.getContext("2d");
|
|
2952
|
+
if (!g) {
|
|
2953
|
+
resolve(null);
|
|
2954
|
+
return;
|
|
2955
|
+
}
|
|
2956
|
+
g.drawImage(img, 0, 0, c.width, c.height);
|
|
2957
|
+
resolve(c.toDataURL("image/jpeg", .72));
|
|
2958
|
+
} catch {
|
|
2959
|
+
resolve(null);
|
|
2960
|
+
}
|
|
2961
|
+
};
|
|
2962
|
+
img.src = dataUrl;
|
|
2963
|
+
});
|
|
2964
|
+
}
|
|
2493
2965
|
const UI_CSS = `
|
|
2494
2966
|
/* The section is rendered INLINE inside the host settings dialog's content
|
|
2495
2967
|
* column: the host provides the modal chrome (backdrop, centering, closing).
|
|
@@ -2651,6 +3123,43 @@ window.__ModuleLoader__.load({
|
|
|
2651
3123
|
.dab-footer{margin-top:6px;padding:14px 4px 0;border-top:1px solid var(--dsw-alias-border-l2);display:flex;align-items:center;justify-content:space-between;font-size:11px;color:var(--dsw-alias-label-tertiary)}
|
|
2652
3124
|
.dab-footer-mono{font-family:var(--dab-mono);letter-spacing:.02em}
|
|
2653
3125
|
|
|
3126
|
+
/* ── presets / profiles / schedule / rotation ────────────────────────────── */
|
|
3127
|
+
.dab-row-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
|
|
3128
|
+
.dab-preset-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px}
|
|
3129
|
+
.dab-preset{display:flex;flex-direction:column;align-items:flex-start;gap:3px;padding:12px;border:1.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:13px;cursor:pointer;font:inherit;text-align:left;transition:border-color .25s,transform .25s,box-shadow .25s}
|
|
3130
|
+
.dab-preset:hover{transform:translateY(-2px);border-color:var(--dsw-alias-brand-primary);box-shadow:0 8px 22px -12px rgba(0,0,0,.3)}
|
|
3131
|
+
.dab-preset-dot{width:22px;height:22px;border-radius:50%;margin-bottom:4px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.12),0 4px 10px -4px rgba(0,0,0,.35);background:var(--dsw-alias-bg-layer-2);display:grid;place-items:center;color:var(--dsw-alias-label-tertiary)}
|
|
3132
|
+
.dab-preset-name{font-size:12.5px;font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
3133
|
+
.dab-preset-desc{font-size:10.5px;line-height:1.45;color:var(--dsw-alias-label-tertiary)}
|
|
3134
|
+
.dab-profile-list{display:flex;flex-direction:column;gap:8px;margin-top:12px}
|
|
3135
|
+
.dab-profile-row{display:flex;align-items:center;gap:11px;padding:9px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-2);transition:border-color .22s ease}
|
|
3136
|
+
.dab-profile-row.is-active{border-color:color-mix(in srgb,var(--dsw-alias-brand-primary) 45%,transparent)}
|
|
3137
|
+
.dab-profile-dot{width:22px;height:22px;flex:none;border-radius:50%;background:var(--dsw-alias-bg-layer-1);box-shadow:inset 0 0 0 1px rgba(0,0,0,.12);display:grid;place-items:center;color:var(--dsw-alias-label-tertiary)}
|
|
3138
|
+
.dab-profile-meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
|
|
3139
|
+
.dab-profile-name{font-size:12.5px;font-weight:600;color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3140
|
+
.dab-profile-sub{font-family:var(--dab-mono);font-size:10px;color:var(--dsw-alias-label-tertiary)}
|
|
3141
|
+
.dab-btn-confirm{background:var(--dsw-alias-state-error-primary)!important;color:#fff!important;border-color:transparent!important}
|
|
3142
|
+
.dab-select{height:32px;padding:0 10px;border-radius:9px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:12px;outline:none;cursor:pointer;max-width:180px}
|
|
3143
|
+
.dab-select:focus{border-color:var(--dsw-alias-brand-primary)}
|
|
3144
|
+
.dab-timeinput{height:32px;padding:0 8px;border-radius:9px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-family:var(--dab-mono);font-size:12px;outline:none}
|
|
3145
|
+
.dab-timeinput:focus{border-color:var(--dsw-alias-brand-primary)}
|
|
3146
|
+
.dab-schedule-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-top:12px}
|
|
3147
|
+
.dab-schedule-cell{min-width:0}
|
|
3148
|
+
.dab-time-row{display:flex;flex-direction:column;gap:9px}
|
|
3149
|
+
.dab-time-label{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary))}
|
|
3150
|
+
.dab-time-label svg{color:var(--dsw-alias-brand-primary);flex:none}
|
|
3151
|
+
.dab-time-label .dab-select,.dab-time-label .dab-timeinput{flex:1;min-width:0}
|
|
3152
|
+
.dab-thumbstrip{display:flex;flex-wrap:wrap;gap:9px;margin-top:13px}
|
|
3153
|
+
.dab-thumb{position:relative;width:64px;height:44px;border-radius:9px;overflow:hidden;border:1.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);display:grid;place-items:center;color:var(--dsw-alias-label-tertiary)}
|
|
3154
|
+
.dab-thumb.is-current{border-color:var(--dsw-alias-brand-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-brand-primary) 25%,transparent)}
|
|
3155
|
+
.dab-thumb img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
|
|
3156
|
+
.dab-thumb-x{position:absolute;top:2px;right:2px;width:16px;height:16px;border:0;border-radius:50%;background:rgba(0,0,0,.55);color:#fff;cursor:pointer;display:grid;place-items:center;opacity:0;transition:opacity .18s ease,background .18s ease;padding:0}
|
|
3157
|
+
.dab-thumb:hover .dab-thumb-x{opacity:1}
|
|
3158
|
+
.dab-thumb-x:hover{background:var(--dsw-alias-state-error-primary)}
|
|
3159
|
+
.dab-thumb-add{border-style:dashed;cursor:pointer;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;transition:border-color .22s ease,color .22s ease}
|
|
3160
|
+
.dab-thumb-add:hover{border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-brand-primary)}
|
|
3161
|
+
.dab-chip-sep{width:1px;height:18px;background:var(--dsw-alias-border-l2);margin:0 4px;flex:none}
|
|
3162
|
+
|
|
2654
3163
|
/* ── toast ───────────────────────────────────────────────────────────────── */
|
|
2655
3164
|
.dab-toast{position:fixed;left:50%;bottom:30px;transform:translateX(-50%);display:flex;align-items:center;gap:8px;height:38px;padding:0 16px;border-radius:99px;background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2));border:1px solid var(--dsw-alias-border-l2);box-shadow:0 10px 30px -8px rgba(0,0,0,.38);font-size:12.5px;z-index:10001;animation:dab-toast-in .32s cubic-bezier(.22,1,.36,1) both}
|
|
2656
3165
|
.dab-toast-ok{color:var(--dsw-alias-brand-primary);display:grid;place-items:center}
|
|
@@ -2958,6 +3467,30 @@ window.__ModuleLoader__.load({
|
|
|
2958
3467
|
rx: "2"
|
|
2959
3468
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 6.6v2M7 8h2" })]
|
|
2960
3469
|
});
|
|
3470
|
+
const PlusIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
|
|
3471
|
+
size,
|
|
3472
|
+
className,
|
|
3473
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 3v10M3 8h10" })
|
|
3474
|
+
});
|
|
3475
|
+
const MoonIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
|
|
3476
|
+
size,
|
|
3477
|
+
className,
|
|
3478
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M13.2 9.6A5.6 5.6 0 0 1 6.4 2.8a5.6 5.6 0 1 0 6.8 6.8z" })
|
|
3479
|
+
});
|
|
3480
|
+
const ClockIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Glyph, {
|
|
3481
|
+
size,
|
|
3482
|
+
className,
|
|
3483
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
3484
|
+
cx: "8",
|
|
3485
|
+
cy: "8",
|
|
3486
|
+
r: "5.8"
|
|
3487
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 4.8V8l2.2 1.6" })]
|
|
3488
|
+
});
|
|
3489
|
+
const XIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
|
|
3490
|
+
size,
|
|
3491
|
+
className,
|
|
3492
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4 4l8 8M12 4l-8 8" })
|
|
3493
|
+
});
|
|
2961
3494
|
//#endregion
|
|
2962
3495
|
//#region src/client/components/ErrorBoundary.tsx
|
|
2963
3496
|
/**
|
|
@@ -3600,9 +4133,10 @@ window.__ModuleLoader__.load({
|
|
|
3600
4133
|
return "#" + rgb.map((v) => Math.round(v).toString(16).padStart(2, "0")).join("");
|
|
3601
4134
|
}
|
|
3602
4135
|
function ColorPage({ p, notify }) {
|
|
3603
|
-
const { t, hue, sat, lit, setColor, extractColor, useStore } = p;
|
|
4136
|
+
const { t, hue, sat, lit, setColor, extractColor, setSchemeOverride, useStore } = p;
|
|
3604
4137
|
const store = useStore((s) => s);
|
|
3605
4138
|
const storeUrl = store.url;
|
|
4139
|
+
const schemeOverride = store.schemeOverride;
|
|
3606
4140
|
const [pickerOpen, setPickerOpen] = (0, react.useState)(false);
|
|
3607
4141
|
const [extracting, setExtracting] = (0, react.useState)(false);
|
|
3608
4142
|
const wheel = store.color ?? [
|
|
@@ -3748,6 +4282,49 @@ window.__ModuleLoader__.load({
|
|
|
3748
4282
|
})
|
|
3749
4283
|
})]
|
|
3750
4284
|
}),
|
|
4285
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4286
|
+
className: "dab-card dab-rise",
|
|
4287
|
+
style: { "--d": 4 },
|
|
4288
|
+
children: [
|
|
4289
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4290
|
+
className: "dab-swatch-title",
|
|
4291
|
+
children: t("schemeTitle")
|
|
4292
|
+
}),
|
|
4293
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4294
|
+
className: "dab-seg",
|
|
4295
|
+
style: { "--w": "86px" },
|
|
4296
|
+
children: [
|
|
4297
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4298
|
+
className: "dab-seg-thumb",
|
|
4299
|
+
style: { transform: `translateX(${schemeOverride === "light" ? 0 : schemeOverride === "dark" ? 86 : 172}px)` }
|
|
4300
|
+
}),
|
|
4301
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4302
|
+
type: "button",
|
|
4303
|
+
className: `dab-seg-item${schemeOverride === "light" ? " is-active" : ""}`,
|
|
4304
|
+
onClick: () => setSchemeOverride("light"),
|
|
4305
|
+
children: t("schemeLight")
|
|
4306
|
+
}),
|
|
4307
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4308
|
+
type: "button",
|
|
4309
|
+
className: `dab-seg-item${schemeOverride === "dark" ? " is-active" : ""}`,
|
|
4310
|
+
onClick: () => setSchemeOverride("dark"),
|
|
4311
|
+
children: t("schemeDark")
|
|
4312
|
+
}),
|
|
4313
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4314
|
+
type: "button",
|
|
4315
|
+
className: `dab-seg-item${schemeOverride === "auto" ? " is-active" : ""}`,
|
|
4316
|
+
onClick: () => setSchemeOverride("auto"),
|
|
4317
|
+
children: t("schemeAuto")
|
|
4318
|
+
})
|
|
4319
|
+
]
|
|
4320
|
+
}),
|
|
4321
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4322
|
+
className: "dab-hint",
|
|
4323
|
+
style: { marginTop: 10 },
|
|
4324
|
+
children: t("schemeHint")
|
|
4325
|
+
})
|
|
4326
|
+
]
|
|
4327
|
+
}),
|
|
3751
4328
|
pickerOpen && storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ColorPicker, {
|
|
3752
4329
|
url: storeUrl,
|
|
3753
4330
|
t,
|
|
@@ -3989,20 +4566,6 @@ window.__ModuleLoader__.load({
|
|
|
3989
4566
|
})] });
|
|
3990
4567
|
}
|
|
3991
4568
|
//#endregion
|
|
3992
|
-
//#region src/client/utils/image.ts
|
|
3993
|
-
/**
|
|
3994
|
-
* Read a chosen image file as a data URL WITHOUT re-encoding: the original
|
|
3995
|
-
* pixels are kept as-is (no canvas downscale / JPEG re-compression), so the
|
|
3996
|
-
* wallpaper is stored and displayed at full fidelity. The tradeoff is a larger
|
|
3997
|
-
* payload over the RPC channel and on disk for big images.
|
|
3998
|
-
*/
|
|
3999
|
-
function readImg(file, cb) {
|
|
4000
|
-
const r = new FileReader();
|
|
4001
|
-
r.onerror = () => cb(null);
|
|
4002
|
-
r.onload = () => cb(r.result);
|
|
4003
|
-
r.readAsDataURL(file);
|
|
4004
|
-
}
|
|
4005
|
-
//#endregion
|
|
4006
4569
|
//#region src/client/components/BgEditor.tsx
|
|
4007
4570
|
/** Placement slot for the active background type: video framing lives in its
|
|
4008
4571
|
* own state so image and video edits never overwrite each other. */
|
|
@@ -4218,16 +4781,19 @@ window.__ModuleLoader__.load({
|
|
|
4218
4781
|
}
|
|
4219
4782
|
];
|
|
4220
4783
|
function BackgroundPage({ p }) {
|
|
4221
|
-
const { t, setWp, setVideo, setWop, setBl, setBgType, setGeneratedBg, regenerateBg, setRegenerateOnReload, useStore } = p;
|
|
4784
|
+
const { t, setWp, setVideo, setWop, setBl, setBgType, setGeneratedBg, regenerateBg, setRegenerateOnReload, setRotation, addRotationItems, removeRotationItem, rotateNow, useStore } = p;
|
|
4222
4785
|
const store = useStore((s) => s);
|
|
4223
4786
|
const storeUrl = store.url;
|
|
4224
4787
|
const backgroundType = store.backgroundType;
|
|
4225
4788
|
const generatedBg = store.generatedBg;
|
|
4226
4789
|
const regenerateOnReload = store.regenerateOnReload;
|
|
4790
|
+
const rotation = store.rotation;
|
|
4227
4791
|
const fileRef = (0, react.useRef)(null);
|
|
4792
|
+
const rotFileRef = (0, react.useRef)(null);
|
|
4228
4793
|
const [editorOpen, setEditorOpen] = (0, react.useState)(false);
|
|
4229
4794
|
const [dragOver, setDragOver] = (0, react.useState)(false);
|
|
4230
4795
|
const [spinTick, setSpinTick] = (0, react.useState)(0);
|
|
4796
|
+
const [rotBusy, setRotBusy] = (0, react.useState)(false);
|
|
4231
4797
|
const [urlOpen, setUrlOpen] = (0, react.useState)(false);
|
|
4232
4798
|
const [urlVal, setUrlVal] = (0, react.useState)("");
|
|
4233
4799
|
const [urlBusy, setUrlBusy] = (0, react.useState)(false);
|
|
@@ -4425,7 +4991,7 @@ window.__ModuleLoader__.load({
|
|
|
4425
4991
|
]
|
|
4426
4992
|
})
|
|
4427
4993
|
}),
|
|
4428
|
-
!isGenerated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4994
|
+
!isGenerated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4429
4995
|
className: "dab-card dab-card-hover dab-rise",
|
|
4430
4996
|
style: { "--d": 3 },
|
|
4431
4997
|
children: [
|
|
@@ -4527,7 +5093,121 @@ window.__ModuleLoader__.load({
|
|
|
4527
5093
|
})]
|
|
4528
5094
|
})
|
|
4529
5095
|
]
|
|
4530
|
-
})
|
|
5096
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5097
|
+
className: "dab-card dab-rise",
|
|
5098
|
+
style: { "--d": 4 },
|
|
5099
|
+
children: [
|
|
5100
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5101
|
+
className: "dab-row-head",
|
|
5102
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5103
|
+
className: "dab-swatch-title",
|
|
5104
|
+
style: { marginBottom: 0 },
|
|
5105
|
+
children: t("rotTitle")
|
|
5106
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5107
|
+
type: "button",
|
|
5108
|
+
className: `dab-toggle${rotation.enabled ? " is-on" : ""}`,
|
|
5109
|
+
role: "switch",
|
|
5110
|
+
"aria-checked": rotation.enabled,
|
|
5111
|
+
onClick: () => setRotation({ enabled: !rotation.enabled }),
|
|
5112
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dab-toggle-knob" })
|
|
5113
|
+
})]
|
|
5114
|
+
}),
|
|
5115
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5116
|
+
className: "dab-hint",
|
|
5117
|
+
style: { marginTop: 8 },
|
|
5118
|
+
children: t("rotHint")
|
|
5119
|
+
}),
|
|
5120
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5121
|
+
className: "dab-thumbstrip",
|
|
5122
|
+
children: [rotation.items.map((it, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5123
|
+
className: `dab-thumb${rotation.enabled && i === rotation.current ? " is-current" : ""}`,
|
|
5124
|
+
children: [it.thumb ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
5125
|
+
src: it.thumb,
|
|
5126
|
+
alt: "",
|
|
5127
|
+
draggable: false
|
|
5128
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PhotoIcon, { size: 15 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5129
|
+
type: "button",
|
|
5130
|
+
className: "dab-thumb-x",
|
|
5131
|
+
title: t("rotRemove"),
|
|
5132
|
+
disabled: rotBusy,
|
|
5133
|
+
onClick: () => {
|
|
5134
|
+
setRotBusy(true);
|
|
5135
|
+
removeRotationItem(i).finally(() => setRotBusy(false));
|
|
5136
|
+
},
|
|
5137
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(XIcon, { size: 10 })
|
|
5138
|
+
})]
|
|
5139
|
+
}, it.file)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5140
|
+
type: "button",
|
|
5141
|
+
className: "dab-thumb dab-thumb-add",
|
|
5142
|
+
title: t("rotAdd"),
|
|
5143
|
+
disabled: rotBusy,
|
|
5144
|
+
onClick: () => rotFileRef.current?.click(),
|
|
5145
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlusIcon, { size: 16 })
|
|
5146
|
+
})]
|
|
5147
|
+
}),
|
|
5148
|
+
rotation.items.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5149
|
+
className: "dab-chip-row",
|
|
5150
|
+
style: { marginTop: 12 },
|
|
5151
|
+
children: [
|
|
5152
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5153
|
+
type: "button",
|
|
5154
|
+
className: `dab-chip${rotation.mode === "shuffle" ? " is-active" : ""}`,
|
|
5155
|
+
onClick: () => setRotation({ mode: "shuffle" }),
|
|
5156
|
+
children: t("rotShuffle")
|
|
5157
|
+
}),
|
|
5158
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5159
|
+
type: "button",
|
|
5160
|
+
className: `dab-chip${rotation.mode === "order" ? " is-active" : ""}`,
|
|
5161
|
+
onClick: () => setRotation({ mode: "order" }),
|
|
5162
|
+
children: t("rotOrder")
|
|
5163
|
+
}),
|
|
5164
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dab-chip-sep" }),
|
|
5165
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5166
|
+
type: "button",
|
|
5167
|
+
className: `dab-chip${rotation.interval === "reload" ? " is-active" : ""}`,
|
|
5168
|
+
onClick: () => setRotation({ interval: "reload" }),
|
|
5169
|
+
children: t("rotReload")
|
|
5170
|
+
}),
|
|
5171
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5172
|
+
type: "button",
|
|
5173
|
+
className: `dab-chip${rotation.interval === "daily" ? " is-active" : ""}`,
|
|
5174
|
+
onClick: () => setRotation({ interval: "daily" }),
|
|
5175
|
+
children: t("rotDaily")
|
|
5176
|
+
}),
|
|
5177
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5178
|
+
type: "button",
|
|
5179
|
+
className: `dab-chip${rotation.interval === "weekly" ? " is-active" : ""}`,
|
|
5180
|
+
onClick: () => setRotation({ interval: "weekly" }),
|
|
5181
|
+
children: t("rotWeekly")
|
|
5182
|
+
}),
|
|
5183
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5184
|
+
type: "button",
|
|
5185
|
+
className: "dab-btn",
|
|
5186
|
+
disabled: rotBusy,
|
|
5187
|
+
onClick: () => {
|
|
5188
|
+
setRotBusy(true);
|
|
5189
|
+
rotateNow().finally(() => setRotBusy(false));
|
|
5190
|
+
},
|
|
5191
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RefreshIcon, { size: 13 }), t("rotNow")]
|
|
5192
|
+
})
|
|
5193
|
+
]
|
|
5194
|
+
}) : null,
|
|
5195
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5196
|
+
ref: rotFileRef,
|
|
5197
|
+
type: "file",
|
|
5198
|
+
accept: "image/*",
|
|
5199
|
+
multiple: true,
|
|
5200
|
+
style: { display: "none" },
|
|
5201
|
+
onChange: (e) => {
|
|
5202
|
+
const files = Array.from(e.target.files ?? []);
|
|
5203
|
+
e.target.value = "";
|
|
5204
|
+
if (files.length === 0) return;
|
|
5205
|
+
setRotBusy(true);
|
|
5206
|
+
addRotationItems(files).finally(() => setRotBusy(false));
|
|
5207
|
+
}
|
|
5208
|
+
})
|
|
5209
|
+
]
|
|
5210
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4531
5211
|
className: "dab-card dab-rise",
|
|
4532
5212
|
style: { "--d": 3 },
|
|
4533
5213
|
children: [
|
|
@@ -4692,7 +5372,7 @@ window.__ModuleLoader__.load({
|
|
|
4692
5372
|
}),
|
|
4693
5373
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4694
5374
|
className: "dab-card dab-card-hover dab-rise",
|
|
4695
|
-
style: { "--d":
|
|
5375
|
+
style: { "--d": 5 },
|
|
4696
5376
|
children: [
|
|
4697
5377
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
|
|
4698
5378
|
label: t("wpOpacity"),
|
|
@@ -4764,10 +5444,188 @@ window.__ModuleLoader__.load({
|
|
|
4764
5444
|
] });
|
|
4765
5445
|
}
|
|
4766
5446
|
//#endregion
|
|
5447
|
+
//#region src/client/utils/presets.ts
|
|
5448
|
+
const zeroBlurs = {
|
|
5449
|
+
bg: 0,
|
|
5450
|
+
sidebar: 0,
|
|
5451
|
+
card: 0,
|
|
5452
|
+
settings: 0,
|
|
5453
|
+
chat: 0,
|
|
5454
|
+
trajectory: 0,
|
|
5455
|
+
input: 0
|
|
5456
|
+
};
|
|
5457
|
+
const BUILTIN_PRESETS = [
|
|
5458
|
+
{
|
|
5459
|
+
key: "default",
|
|
5460
|
+
appearance: {
|
|
5461
|
+
color: null,
|
|
5462
|
+
opacities: {
|
|
5463
|
+
bg: .85,
|
|
5464
|
+
sidebar: .93,
|
|
5465
|
+
card: 1,
|
|
5466
|
+
input: 1
|
|
5467
|
+
},
|
|
5468
|
+
blurs: { ...zeroBlurs },
|
|
5469
|
+
settingsOpacity: 1,
|
|
5470
|
+
wallpaperOpacity: 1,
|
|
5471
|
+
blur: 0,
|
|
5472
|
+
chatTextOpacity: 0,
|
|
5473
|
+
trajectoryOpacity: 1
|
|
5474
|
+
}
|
|
5475
|
+
},
|
|
5476
|
+
{
|
|
5477
|
+
key: "glass",
|
|
5478
|
+
appearance: {
|
|
5479
|
+
color: [
|
|
5480
|
+
212,
|
|
5481
|
+
.5,
|
|
5482
|
+
.38
|
|
5483
|
+
],
|
|
5484
|
+
opacities: {
|
|
5485
|
+
bg: .62,
|
|
5486
|
+
sidebar: .55,
|
|
5487
|
+
card: .62,
|
|
5488
|
+
input: .58
|
|
5489
|
+
},
|
|
5490
|
+
blurs: {
|
|
5491
|
+
...zeroBlurs,
|
|
5492
|
+
bg: 20,
|
|
5493
|
+
sidebar: 14,
|
|
5494
|
+
card: 12,
|
|
5495
|
+
settings: 20,
|
|
5496
|
+
trajectory: 8,
|
|
5497
|
+
input: 14
|
|
5498
|
+
},
|
|
5499
|
+
settingsOpacity: .88,
|
|
5500
|
+
wallpaperOpacity: 1,
|
|
5501
|
+
blur: 0,
|
|
5502
|
+
chatTextOpacity: 0,
|
|
5503
|
+
trajectoryOpacity: .85
|
|
5504
|
+
}
|
|
5505
|
+
},
|
|
5506
|
+
{
|
|
5507
|
+
key: "minimal",
|
|
5508
|
+
appearance: {
|
|
5509
|
+
color: null,
|
|
5510
|
+
opacities: {
|
|
5511
|
+
bg: .97,
|
|
5512
|
+
sidebar: .97,
|
|
5513
|
+
card: 1,
|
|
5514
|
+
input: 1
|
|
5515
|
+
},
|
|
5516
|
+
blurs: { ...zeroBlurs },
|
|
5517
|
+
settingsOpacity: 1,
|
|
5518
|
+
wallpaperOpacity: 1,
|
|
5519
|
+
blur: 0,
|
|
5520
|
+
chatTextOpacity: 0,
|
|
5521
|
+
trajectoryOpacity: 1
|
|
5522
|
+
}
|
|
5523
|
+
},
|
|
5524
|
+
{
|
|
5525
|
+
key: "midnight",
|
|
5526
|
+
appearance: {
|
|
5527
|
+
color: [
|
|
5528
|
+
262,
|
|
5529
|
+
.45,
|
|
5530
|
+
.16
|
|
5531
|
+
],
|
|
5532
|
+
opacities: {
|
|
5533
|
+
bg: .5,
|
|
5534
|
+
sidebar: .45,
|
|
5535
|
+
card: .55,
|
|
5536
|
+
input: .6
|
|
5537
|
+
},
|
|
5538
|
+
blurs: {
|
|
5539
|
+
...zeroBlurs,
|
|
5540
|
+
bg: 24,
|
|
5541
|
+
sidebar: 18,
|
|
5542
|
+
card: 14,
|
|
5543
|
+
settings: 22,
|
|
5544
|
+
trajectory: 10,
|
|
5545
|
+
input: 16
|
|
5546
|
+
},
|
|
5547
|
+
settingsOpacity: .85,
|
|
5548
|
+
wallpaperOpacity: .92,
|
|
5549
|
+
blur: 2,
|
|
5550
|
+
chatTextOpacity: 0,
|
|
5551
|
+
trajectoryOpacity: .8
|
|
5552
|
+
}
|
|
5553
|
+
},
|
|
5554
|
+
{
|
|
5555
|
+
key: "cyber",
|
|
5556
|
+
appearance: {
|
|
5557
|
+
color: [
|
|
5558
|
+
315,
|
|
5559
|
+
.7,
|
|
5560
|
+
.22
|
|
5561
|
+
],
|
|
5562
|
+
opacities: {
|
|
5563
|
+
bg: .42,
|
|
5564
|
+
sidebar: .5,
|
|
5565
|
+
card: .45,
|
|
5566
|
+
input: .55
|
|
5567
|
+
},
|
|
5568
|
+
blurs: {
|
|
5569
|
+
...zeroBlurs,
|
|
5570
|
+
bg: 16,
|
|
5571
|
+
sidebar: 12,
|
|
5572
|
+
card: 10,
|
|
5573
|
+
settings: 18,
|
|
5574
|
+
trajectory: 6,
|
|
5575
|
+
input: 20
|
|
5576
|
+
},
|
|
5577
|
+
settingsOpacity: .8,
|
|
5578
|
+
wallpaperOpacity: 1,
|
|
5579
|
+
blur: 0,
|
|
5580
|
+
chatTextOpacity: 0,
|
|
5581
|
+
trajectoryOpacity: .78
|
|
5582
|
+
}
|
|
5583
|
+
},
|
|
5584
|
+
{
|
|
5585
|
+
key: "warm",
|
|
5586
|
+
appearance: {
|
|
5587
|
+
color: [
|
|
5588
|
+
28,
|
|
5589
|
+
.6,
|
|
5590
|
+
.72
|
|
5591
|
+
],
|
|
5592
|
+
opacities: {
|
|
5593
|
+
bg: .92,
|
|
5594
|
+
sidebar: .95,
|
|
5595
|
+
card: 1,
|
|
5596
|
+
input: 1
|
|
5597
|
+
},
|
|
5598
|
+
blurs: { ...zeroBlurs },
|
|
5599
|
+
settingsOpacity: 1,
|
|
5600
|
+
wallpaperOpacity: 1,
|
|
5601
|
+
blur: 0,
|
|
5602
|
+
chatTextOpacity: 0,
|
|
5603
|
+
trajectoryOpacity: 1
|
|
5604
|
+
}
|
|
5605
|
+
}
|
|
5606
|
+
];
|
|
5607
|
+
//#endregion
|
|
4767
5608
|
//#region src/client/components/pages/ProfilePage.tsx
|
|
5609
|
+
const hexOf = (color) => color === null ? "" : "#" + hslToRgb(color[0], color[1], color[2]).map((v) => Math.round(v).toString(16).padStart(2, "0")).join("");
|
|
5610
|
+
function ProfileDot({ entry }) {
|
|
5611
|
+
const hex = hexOf(entry.config.color);
|
|
5612
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5613
|
+
className: "dab-profile-dot",
|
|
5614
|
+
style: hex ? { background: hex } : void 0,
|
|
5615
|
+
title: hex ? hex.toUpperCase() : void 0,
|
|
5616
|
+
children: hex ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, { size: 11 })
|
|
5617
|
+
});
|
|
5618
|
+
}
|
|
4768
5619
|
function ProfilePage({ p, notify }) {
|
|
4769
|
-
const { t, exportTheme, importTheme } = p;
|
|
5620
|
+
const { t, exportTheme, importTheme, saveProfile, applyProfile, deleteProfile, applyPreset, setSchedule, useStore } = p;
|
|
5621
|
+
const store = useStore((s) => s);
|
|
5622
|
+
const profiles = store.profiles;
|
|
5623
|
+
const schedule = store.schedule;
|
|
5624
|
+
const activeProfile = store.activeProfile;
|
|
4770
5625
|
const importRef = (0, react.useRef)(null);
|
|
5626
|
+
const [nameOpen, setNameOpen] = (0, react.useState)(false);
|
|
5627
|
+
const [nameVal, setNameVal] = (0, react.useState)("");
|
|
5628
|
+
const [confirmId, setConfirmId] = (0, react.useState)(null);
|
|
4771
5629
|
const onImport = async (file) => {
|
|
4772
5630
|
try {
|
|
4773
5631
|
const ok = await importTheme(file);
|
|
@@ -4776,6 +5634,39 @@ window.__ModuleLoader__.load({
|
|
|
4776
5634
|
notify(t("importFail"), false);
|
|
4777
5635
|
}
|
|
4778
5636
|
};
|
|
5637
|
+
const onSave = () => {
|
|
5638
|
+
const name = nameVal.trim();
|
|
5639
|
+
if (!name) return;
|
|
5640
|
+
if (saveProfile(name)) {
|
|
5641
|
+
notify(t("profileSaved"), true);
|
|
5642
|
+
setNameVal("");
|
|
5643
|
+
setNameOpen(false);
|
|
5644
|
+
} else notify(t("profileNameRequired"), false);
|
|
5645
|
+
};
|
|
5646
|
+
const onApply = (entry) => {
|
|
5647
|
+
if (applyProfile(entry.id)) notify(t("profileApplied"), true);
|
|
5648
|
+
};
|
|
5649
|
+
const onDelete = (entry) => {
|
|
5650
|
+
if (confirmId !== entry.id) {
|
|
5651
|
+
setConfirmId(entry.id);
|
|
5652
|
+
return;
|
|
5653
|
+
}
|
|
5654
|
+
setConfirmId(null);
|
|
5655
|
+
deleteProfile(entry.id);
|
|
5656
|
+
};
|
|
5657
|
+
const profileOptions = (value, onChange) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
5658
|
+
className: "dab-select",
|
|
5659
|
+
value: value ?? "",
|
|
5660
|
+
onChange: (e) => onChange(e.target.value || null),
|
|
5661
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
5662
|
+
value: "",
|
|
5663
|
+
children: t("profileNone")
|
|
5664
|
+
}), profiles.map((pr) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
5665
|
+
value: pr.id,
|
|
5666
|
+
children: pr.name
|
|
5667
|
+
}, pr.id))]
|
|
5668
|
+
});
|
|
5669
|
+
const patchSchedule = (patch) => setSchedule(patch);
|
|
4779
5670
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4780
5671
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
4781
5672
|
className: "dab-head dab-rise",
|
|
@@ -4795,11 +5686,258 @@ window.__ModuleLoader__.load({
|
|
|
4795
5686
|
})
|
|
4796
5687
|
]
|
|
4797
5688
|
}),
|
|
5689
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5690
|
+
className: "dab-card dab-rise",
|
|
5691
|
+
style: { "--d": 1 },
|
|
5692
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5693
|
+
className: "dab-swatch-title",
|
|
5694
|
+
children: t("presetGalleryTitle")
|
|
5695
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5696
|
+
className: "dab-preset-grid",
|
|
5697
|
+
children: BUILTIN_PRESETS.map((ps) => {
|
|
5698
|
+
const hex = hexOf(ps.appearance.color);
|
|
5699
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5700
|
+
type: "button",
|
|
5701
|
+
className: "dab-preset",
|
|
5702
|
+
onClick: () => {
|
|
5703
|
+
applyPreset(ps.appearance);
|
|
5704
|
+
notify(t("presetApplied"));
|
|
5705
|
+
},
|
|
5706
|
+
children: [
|
|
5707
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5708
|
+
className: "dab-preset-dot",
|
|
5709
|
+
style: hex ? { background: hex } : void 0,
|
|
5710
|
+
children: hex ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, { size: 12 })
|
|
5711
|
+
}),
|
|
5712
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5713
|
+
className: "dab-preset-name",
|
|
5714
|
+
children: t(`presetName_${ps.key}`)
|
|
5715
|
+
}),
|
|
5716
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5717
|
+
className: "dab-preset-desc",
|
|
5718
|
+
children: t(`presetDesc_${ps.key}`)
|
|
5719
|
+
})
|
|
5720
|
+
]
|
|
5721
|
+
}, ps.key);
|
|
5722
|
+
})
|
|
5723
|
+
})]
|
|
5724
|
+
}),
|
|
5725
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5726
|
+
className: "dab-card dab-rise",
|
|
5727
|
+
style: { "--d": 2 },
|
|
5728
|
+
children: [
|
|
5729
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5730
|
+
className: "dab-row-head",
|
|
5731
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5732
|
+
className: "dab-swatch-title",
|
|
5733
|
+
style: { marginBottom: 0 },
|
|
5734
|
+
children: t("profilesTitle")
|
|
5735
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5736
|
+
type: "button",
|
|
5737
|
+
className: "dab-btn dab-btn-primary",
|
|
5738
|
+
onClick: () => setNameOpen((o) => !o),
|
|
5739
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlusIcon, { size: 14 }), t("profileSave")]
|
|
5740
|
+
})]
|
|
5741
|
+
}),
|
|
5742
|
+
nameOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5743
|
+
className: "dab-urlrow",
|
|
5744
|
+
style: {
|
|
5745
|
+
marginTop: 12,
|
|
5746
|
+
display: "flex",
|
|
5747
|
+
flexWrap: "wrap",
|
|
5748
|
+
gap: 8,
|
|
5749
|
+
alignItems: "center"
|
|
5750
|
+
},
|
|
5751
|
+
children: [
|
|
5752
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5753
|
+
type: "text",
|
|
5754
|
+
className: "dab-urlinput",
|
|
5755
|
+
value: nameVal,
|
|
5756
|
+
placeholder: t("profileNamePlaceholder"),
|
|
5757
|
+
maxLength: 60,
|
|
5758
|
+
onChange: (e) => setNameVal(e.target.value),
|
|
5759
|
+
onKeyDown: (e) => {
|
|
5760
|
+
if (e.key === "Enter") {
|
|
5761
|
+
e.preventDefault();
|
|
5762
|
+
onSave();
|
|
5763
|
+
}
|
|
5764
|
+
},
|
|
5765
|
+
autoFocus: true
|
|
5766
|
+
}),
|
|
5767
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5768
|
+
type: "button",
|
|
5769
|
+
className: "dab-btn dab-btn-primary",
|
|
5770
|
+
disabled: !nameVal.trim(),
|
|
5771
|
+
onClick: onSave,
|
|
5772
|
+
children: t("editorCommit")
|
|
5773
|
+
}),
|
|
5774
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5775
|
+
type: "button",
|
|
5776
|
+
className: "dab-btn",
|
|
5777
|
+
onClick: () => {
|
|
5778
|
+
setNameOpen(false);
|
|
5779
|
+
setNameVal("");
|
|
5780
|
+
},
|
|
5781
|
+
children: t("bgUrlCancel")
|
|
5782
|
+
})
|
|
5783
|
+
]
|
|
5784
|
+
}) : null,
|
|
5785
|
+
profiles.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5786
|
+
className: "dab-hint",
|
|
5787
|
+
style: { marginTop: 10 },
|
|
5788
|
+
children: t("profilesEmpty")
|
|
5789
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5790
|
+
className: "dab-profile-list",
|
|
5791
|
+
children: profiles.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5792
|
+
className: `dab-profile-row${activeProfile === entry.id ? " is-active" : ""}`,
|
|
5793
|
+
children: [
|
|
5794
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProfileDot, { entry }),
|
|
5795
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5796
|
+
className: "dab-profile-meta",
|
|
5797
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5798
|
+
className: "dab-profile-name",
|
|
5799
|
+
children: entry.name
|
|
5800
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5801
|
+
className: "dab-profile-sub",
|
|
5802
|
+
children: new Date(entry.createdAt).toLocaleDateString()
|
|
5803
|
+
})]
|
|
5804
|
+
}),
|
|
5805
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5806
|
+
type: "button",
|
|
5807
|
+
className: "dab-btn",
|
|
5808
|
+
onClick: () => onApply(entry),
|
|
5809
|
+
children: t("profileApply")
|
|
5810
|
+
}),
|
|
5811
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5812
|
+
type: "button",
|
|
5813
|
+
className: `dab-btn dab-btn-ghost dab-btn-danger${confirmId === entry.id ? " dab-btn-confirm" : ""}`,
|
|
5814
|
+
title: t("profileDelete"),
|
|
5815
|
+
onClick: () => onDelete(entry),
|
|
5816
|
+
children: confirmId === entry.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, { size: 13 }), t("profileDeleteConfirm")] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrashIcon, { size: 13 })
|
|
5817
|
+
})
|
|
5818
|
+
]
|
|
5819
|
+
}, entry.id))
|
|
5820
|
+
})
|
|
5821
|
+
]
|
|
5822
|
+
}),
|
|
5823
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5824
|
+
className: "dab-card dab-rise",
|
|
5825
|
+
style: { "--d": 3 },
|
|
5826
|
+
children: [
|
|
5827
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5828
|
+
className: "dab-row-head",
|
|
5829
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5830
|
+
className: "dab-swatch-title",
|
|
5831
|
+
style: { marginBottom: 0 },
|
|
5832
|
+
children: t("scheduleTitle")
|
|
5833
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5834
|
+
type: "button",
|
|
5835
|
+
className: `dab-toggle${schedule.enabled ? " is-on" : ""}`,
|
|
5836
|
+
role: "switch",
|
|
5837
|
+
"aria-checked": schedule.enabled,
|
|
5838
|
+
onClick: () => patchSchedule({ enabled: !schedule.enabled }),
|
|
5839
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dab-toggle-knob" })
|
|
5840
|
+
})]
|
|
5841
|
+
}),
|
|
5842
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5843
|
+
className: "dab-hint",
|
|
5844
|
+
style: { marginTop: 8 },
|
|
5845
|
+
children: t("scheduleHint")
|
|
5846
|
+
}),
|
|
5847
|
+
schedule.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5848
|
+
className: "dab-schedule-grid",
|
|
5849
|
+
children: [
|
|
5850
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5851
|
+
className: "dab-schedule-cell",
|
|
5852
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5853
|
+
className: "dab-swatch-title",
|
|
5854
|
+
children: t("scheduleMode")
|
|
5855
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5856
|
+
className: "dab-chip-row",
|
|
5857
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5858
|
+
type: "button",
|
|
5859
|
+
className: `dab-chip${schedule.mode === "time" ? " is-active" : ""}`,
|
|
5860
|
+
onClick: () => patchSchedule({ mode: "time" }),
|
|
5861
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClockIcon, { size: 13 }), t("scheduleModeTime")]
|
|
5862
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5863
|
+
type: "button",
|
|
5864
|
+
className: `dab-chip${schedule.mode === "system" ? " is-active" : ""}`,
|
|
5865
|
+
onClick: () => patchSchedule({ mode: "system" }),
|
|
5866
|
+
children: t("scheduleModeSystem")
|
|
5867
|
+
})]
|
|
5868
|
+
})]
|
|
5869
|
+
}),
|
|
5870
|
+
schedule.mode === "time" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5871
|
+
className: "dab-schedule-cell",
|
|
5872
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5873
|
+
className: "dab-swatch-title",
|
|
5874
|
+
children: t("scheduleTimes")
|
|
5875
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5876
|
+
className: "dab-time-row",
|
|
5877
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5878
|
+
className: "dab-time-label",
|
|
5879
|
+
children: [
|
|
5880
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SunIcon, { size: 13 }),
|
|
5881
|
+
t("scheduleDayStart"),
|
|
5882
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5883
|
+
type: "time",
|
|
5884
|
+
className: "dab-timeinput",
|
|
5885
|
+
value: schedule.dayStart,
|
|
5886
|
+
onChange: (e) => {
|
|
5887
|
+
if (/^([01]\d|2[0-3]):[0-5]\d$/.test(e.target.value)) patchSchedule({ dayStart: e.target.value });
|
|
5888
|
+
}
|
|
5889
|
+
})
|
|
5890
|
+
]
|
|
5891
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5892
|
+
className: "dab-time-label",
|
|
5893
|
+
children: [
|
|
5894
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoonIcon, { size: 13 }),
|
|
5895
|
+
t("scheduleNightStart"),
|
|
5896
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5897
|
+
type: "time",
|
|
5898
|
+
className: "dab-timeinput",
|
|
5899
|
+
value: schedule.nightStart,
|
|
5900
|
+
onChange: (e) => {
|
|
5901
|
+
if (/^([01]\d|2[0-3]):[0-5]\d$/.test(e.target.value)) patchSchedule({ nightStart: e.target.value });
|
|
5902
|
+
}
|
|
5903
|
+
})
|
|
5904
|
+
]
|
|
5905
|
+
})]
|
|
5906
|
+
})]
|
|
5907
|
+
}) : null,
|
|
5908
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5909
|
+
className: "dab-schedule-cell",
|
|
5910
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5911
|
+
className: "dab-swatch-title",
|
|
5912
|
+
children: t("scheduleProfiles")
|
|
5913
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5914
|
+
className: "dab-time-row",
|
|
5915
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5916
|
+
className: "dab-time-label",
|
|
5917
|
+
children: [
|
|
5918
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SunIcon, { size: 13 }),
|
|
5919
|
+
t("scheduleDayProfile"),
|
|
5920
|
+
profileOptions(schedule.dayProfile, (id) => patchSchedule({ dayProfile: id }))
|
|
5921
|
+
]
|
|
5922
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5923
|
+
className: "dab-time-label",
|
|
5924
|
+
children: [
|
|
5925
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoonIcon, { size: 13 }),
|
|
5926
|
+
t("scheduleNightProfile"),
|
|
5927
|
+
profileOptions(schedule.nightProfile, (id) => patchSchedule({ nightProfile: id }))
|
|
5928
|
+
]
|
|
5929
|
+
})]
|
|
5930
|
+
})]
|
|
5931
|
+
})
|
|
5932
|
+
]
|
|
5933
|
+
}) : null
|
|
5934
|
+
]
|
|
5935
|
+
}),
|
|
4798
5936
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4799
5937
|
className: "dab-profile-grid",
|
|
4800
5938
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4801
5939
|
className: "dab-card dab-card-hover dab-rise",
|
|
4802
|
-
style: { "--d":
|
|
5940
|
+
style: { "--d": 4 },
|
|
4803
5941
|
children: [
|
|
4804
5942
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4805
5943
|
className: "dab-profile-ico",
|
|
@@ -4825,7 +5963,7 @@ window.__ModuleLoader__.load({
|
|
|
4825
5963
|
]
|
|
4826
5964
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4827
5965
|
className: "dab-card dab-card-hover dab-rise",
|
|
4828
|
-
style: { "--d":
|
|
5966
|
+
style: { "--d": 5 },
|
|
4829
5967
|
children: [
|
|
4830
5968
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4831
5969
|
className: "dab-profile-ico",
|
|
@@ -4862,7 +6000,7 @@ window.__ModuleLoader__.load({
|
|
|
4862
6000
|
}),
|
|
4863
6001
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
4864
6002
|
className: "dab-footer dab-rise",
|
|
4865
|
-
style: { "--d":
|
|
6003
|
+
style: { "--d": 6 },
|
|
4866
6004
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4867
6005
|
className: "dab-footer-mono",
|
|
4868
6006
|
children: "dsh-any-background"
|
|
@@ -4996,7 +6134,30 @@ window.__ModuleLoader__.load({
|
|
|
4996
6134
|
const registerCustom = (h, s, l) => {
|
|
4997
6135
|
customDispose?.();
|
|
4998
6136
|
try {
|
|
4999
|
-
|
|
6137
|
+
let colorScheme;
|
|
6138
|
+
let tokens;
|
|
6139
|
+
if (rHasColor()) ({colorScheme, tokens} = genTokens(h ?? rColor()[0], s ?? rColor()[1], l ?? rColor()[2], rColorScheme()));
|
|
6140
|
+
else if (rSchemeOverride() !== "auto") {
|
|
6141
|
+
const dark = rScheme() === "dark";
|
|
6142
|
+
({colorScheme, tokens} = genTokens(220, .04, dark ? .14 : .92, dark ? "dark" : "light"));
|
|
6143
|
+
} else {
|
|
6144
|
+
const verdict = rBgDark();
|
|
6145
|
+
if (verdict === null) {
|
|
6146
|
+
customDispose = null;
|
|
6147
|
+
return false;
|
|
6148
|
+
}
|
|
6149
|
+
const snap = ctx.theme.getTheme();
|
|
6150
|
+
const wantScheme = verdict ? "dark" : "light";
|
|
6151
|
+
const source = snap.themes.find((t) => t.id !== CUSTOM_ID && t.colorScheme === wantScheme) ?? snap.themes.find((t) => t.id !== CUSTOM_ID);
|
|
6152
|
+
if (source === void 0) {
|
|
6153
|
+
customDispose = null;
|
|
6154
|
+
return false;
|
|
6155
|
+
}
|
|
6156
|
+
colorScheme = wantScheme;
|
|
6157
|
+
tokens = { ...source.tokens };
|
|
6158
|
+
const font = verdict ? "#fff" : "#000";
|
|
6159
|
+
for (const name of LABEL_TOKENS) tokens[name] = font;
|
|
6160
|
+
}
|
|
5000
6161
|
customDispose = ctx.theme.register({
|
|
5001
6162
|
id: CUSTOM_ID,
|
|
5002
6163
|
colorScheme,
|
|
@@ -5005,9 +6166,20 @@ window.__ModuleLoader__.load({
|
|
|
5005
6166
|
} catch {
|
|
5006
6167
|
customDispose = null;
|
|
5007
6168
|
}
|
|
5008
|
-
|
|
6169
|
+
const present = ctx.theme.getTheme().themes.some((t) => t.id === CUSTOM_ID);
|
|
6170
|
+
if (present) ctx.theme.setTheme(CUSTOM_ID);
|
|
6171
|
+
return present;
|
|
5009
6172
|
};
|
|
5010
6173
|
if (rHasColor()) registerCustom(initH, initS, initL);
|
|
6174
|
+
onVerdictApplied(() => {
|
|
6175
|
+
if (!rHasColor()) registerCustom();
|
|
6176
|
+
});
|
|
6177
|
+
onColorAdopted((hsl) => {
|
|
6178
|
+
registerCustom(hsl[0], hsl[1], hsl[2]);
|
|
6179
|
+
saveConfig();
|
|
6180
|
+
colorRev++;
|
|
6181
|
+
bound?.syncColor(hslToHsv(hsl[0], hsl[1], hsl[2]), colorRev);
|
|
6182
|
+
});
|
|
5011
6183
|
ctx.effect(() => () => {
|
|
5012
6184
|
customDispose?.();
|
|
5013
6185
|
if (colorTimerRef.current !== null) window.clearTimeout(colorTimerRef.current);
|
|
@@ -5034,7 +6206,16 @@ window.__ModuleLoader__.load({
|
|
|
5034
6206
|
backgroundType: cfg.backgroundType,
|
|
5035
6207
|
generatedBg: cfg.generatedBg,
|
|
5036
6208
|
bgRev: -1,
|
|
5037
|
-
regenerateOnReload: cfg.regenerateOnReload
|
|
6209
|
+
regenerateOnReload: cfg.regenerateOnReload,
|
|
6210
|
+
profiles: [],
|
|
6211
|
+
rotation: {
|
|
6212
|
+
...DEFAULT_CONFIG.rotation,
|
|
6213
|
+
items: []
|
|
6214
|
+
},
|
|
6215
|
+
schedule: { ...DEFAULT_CONFIG.schedule },
|
|
6216
|
+
schemeOverride: "auto",
|
|
6217
|
+
activeProfile: null,
|
|
6218
|
+
metaRev: -1
|
|
5038
6219
|
}),
|
|
5039
6220
|
actions: {
|
|
5040
6221
|
syncBg: (d, url, r, bgType, genBg, bgr, reload) => {
|
|
@@ -5054,6 +6235,16 @@ window.__ModuleLoader__.load({
|
|
|
5054
6235
|
d.color = hsv;
|
|
5055
6236
|
d.colorRev = r;
|
|
5056
6237
|
}
|
|
6238
|
+
},
|
|
6239
|
+
syncMeta: (d, profiles, rotation, schedule, schemeOverride, activeProfile, r) => {
|
|
6240
|
+
if (r > d.metaRev) {
|
|
6241
|
+
d.profiles = profiles;
|
|
6242
|
+
d.rotation = rotation;
|
|
6243
|
+
d.schedule = schedule;
|
|
6244
|
+
d.schemeOverride = schemeOverride;
|
|
6245
|
+
d.activeProfile = activeProfile;
|
|
6246
|
+
d.metaRev = r;
|
|
6247
|
+
}
|
|
5057
6248
|
}
|
|
5058
6249
|
}
|
|
5059
6250
|
});
|
|
@@ -5064,14 +6255,142 @@ window.__ModuleLoader__.load({
|
|
|
5064
6255
|
bound?.syncBg(rWp(), rev, cfg.backgroundType, cfg.generatedBg, bgRev, cfg.regenerateOnReload);
|
|
5065
6256
|
};
|
|
5066
6257
|
onGeneratedSnapshot(syncBg);
|
|
6258
|
+
let metaRev = 0;
|
|
6259
|
+
const syncMetaNow = () => {
|
|
6260
|
+
metaRev++;
|
|
6261
|
+
bound?.syncMeta(rProfiles(), rRotation(), rSchedule(), rSchemeOverride(), cfg.activeProfile, metaRev);
|
|
6262
|
+
};
|
|
6263
|
+
/** Apply an appearance snapshot (profile or built-in preset) to the whole
|
|
6264
|
+
* interface: re-register the skin, re-emit tokens, persist. */
|
|
6265
|
+
const applyAppearanceLive = (ap) => {
|
|
6266
|
+
applyAppearance(ap);
|
|
6267
|
+
if (rHasColor()) {
|
|
6268
|
+
const [h, s, l] = rColor();
|
|
6269
|
+
registerCustom(h, s, l);
|
|
6270
|
+
} else if (!registerCustom()) {
|
|
6271
|
+
customDispose?.();
|
|
6272
|
+
customDispose = null;
|
|
6273
|
+
clearThemeTokens();
|
|
6274
|
+
const snap = ctx.theme.getTheme();
|
|
6275
|
+
const fallback = snap.themes.find((t) => t.id !== CUSTOM_ID);
|
|
6276
|
+
if (fallback !== void 0 && snap.preference === CUSTOM_ID) ctx.theme.setTheme(fallback.id);
|
|
6277
|
+
}
|
|
6278
|
+
applyWp();
|
|
6279
|
+
};
|
|
6280
|
+
const applyProfileById = (id) => {
|
|
6281
|
+
const entry = rProfiles().find((p) => p.id === id);
|
|
6282
|
+
if (entry === void 0) return false;
|
|
6283
|
+
applyAppearanceLive(entry.config);
|
|
6284
|
+
cfg.activeProfile = id;
|
|
6285
|
+
applyWp();
|
|
6286
|
+
persistConfig();
|
|
6287
|
+
syncMetaNow();
|
|
6288
|
+
return true;
|
|
6289
|
+
};
|
|
6290
|
+
/** Extract the theme color once from a freshly activated wallpaper and run
|
|
6291
|
+
* the full adaptation (host skin + editor wheel sync); the caller persists. */
|
|
6292
|
+
const adoptWallpaperColor = async (dataUrl) => {
|
|
6293
|
+
const hsl = await extractWallpaperColor(dataUrl, rBgState());
|
|
6294
|
+
if (!hsl) return;
|
|
6295
|
+
cfg.color = hsl;
|
|
6296
|
+
registerCustom(hsl[0], hsl[1], hsl[2]);
|
|
6297
|
+
colorRev++;
|
|
6298
|
+
bound?.syncColor(hslToHsv(hsl[0], hsl[1], hsl[2]), colorRev);
|
|
6299
|
+
};
|
|
6300
|
+
/** Activate a rotation item: the server copies its bytes into the wallpaper
|
|
6301
|
+
* slot; the client applies the returned data URL through the normal image
|
|
6302
|
+
* path, then re-extracts the theme color from the new picture so the
|
|
6303
|
+
* palette follows the rotation. */
|
|
6304
|
+
const applyRotationIndex = async (idx, auto) => {
|
|
6305
|
+
const rot = rRotation();
|
|
6306
|
+
if (idx < 0 || idx >= rot.items.length) return false;
|
|
6307
|
+
const r = await rotationActivate(idx);
|
|
6308
|
+
if (!r.ok || !r.dataUrl) {
|
|
6309
|
+
console.warn("dsh-any-background: rotation activate failed", r.error);
|
|
6310
|
+
return false;
|
|
6311
|
+
}
|
|
6312
|
+
setWpImageUrl(r.dataUrl);
|
|
6313
|
+
if (cfg.backgroundType === "image") setWpUrl(r.dataUrl);
|
|
6314
|
+
cfg.rotation = {
|
|
6315
|
+
...rot,
|
|
6316
|
+
current: idx,
|
|
6317
|
+
lastRotate: auto || rot.lastRotate === null ? (/* @__PURE__ */ new Date()).toISOString() : rot.lastRotate
|
|
6318
|
+
};
|
|
6319
|
+
if (cfg.backgroundType === "image") await adoptWallpaperColor(r.dataUrl);
|
|
6320
|
+
applyThemeColor();
|
|
6321
|
+
syncBg();
|
|
6322
|
+
saveConfig();
|
|
6323
|
+
syncMetaNow();
|
|
6324
|
+
return true;
|
|
6325
|
+
};
|
|
6326
|
+
const isoWeekKey = (d) => {
|
|
6327
|
+
const t = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
|
|
6328
|
+
const day = t.getUTCDay() || 7;
|
|
6329
|
+
t.setUTCDate(t.getUTCDate() + 4 - day);
|
|
6330
|
+
const yearStart = new Date(Date.UTC(t.getUTCFullYear(), 0, 1));
|
|
6331
|
+
const week = Math.ceil(((t.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
6332
|
+
return `${t.getUTCFullYear()}-W${week}`;
|
|
6333
|
+
};
|
|
6334
|
+
const rotationDue = () => {
|
|
6335
|
+
const rot = rRotation();
|
|
6336
|
+
if (!rot.enabled || rot.items.length === 0) return false;
|
|
6337
|
+
if (rot.interval === "reload") return true;
|
|
6338
|
+
const last = rot.lastRotate !== null ? new Date(rot.lastRotate) : null;
|
|
6339
|
+
if (last === null || isNaN(last.getTime())) return true;
|
|
6340
|
+
const now = /* @__PURE__ */ new Date();
|
|
6341
|
+
if (rot.interval === "daily") return last.toDateString() !== now.toDateString();
|
|
6342
|
+
return isoWeekKey(last) !== isoWeekKey(now);
|
|
6343
|
+
};
|
|
6344
|
+
const pickNextRotationIndex = () => {
|
|
6345
|
+
const rot = rRotation();
|
|
6346
|
+
const n = rot.items.length;
|
|
6347
|
+
if (n === 0) return -1;
|
|
6348
|
+
if (rot.mode === "shuffle" && n > 1) {
|
|
6349
|
+
let idx = rot.current;
|
|
6350
|
+
while (idx === rot.current) idx = Math.floor(Math.random() * n);
|
|
6351
|
+
return idx;
|
|
6352
|
+
}
|
|
6353
|
+
return (rot.current + 1) % n;
|
|
6354
|
+
};
|
|
6355
|
+
const maybeRotate = async () => {
|
|
6356
|
+
if (!rotationDue()) return;
|
|
6357
|
+
await applyRotationIndex(pickNextRotationIndex(), true);
|
|
6358
|
+
};
|
|
6359
|
+
const parseHHMM = (s) => {
|
|
6360
|
+
const m = /^([01]\d|2[0-3]):([0-5]\d)$/.exec(s);
|
|
6361
|
+
return m === null ? -1 : Number(m[1]) * 60 + Number(m[2]);
|
|
6362
|
+
};
|
|
6363
|
+
const isNightNow = (sc) => {
|
|
6364
|
+
const now = /* @__PURE__ */ new Date();
|
|
6365
|
+
const cur = now.getHours() * 60 + now.getMinutes();
|
|
6366
|
+
const day = parseHHMM(sc.dayStart);
|
|
6367
|
+
const night = parseHHMM(sc.nightStart);
|
|
6368
|
+
if (day < 0 || night < 0) return false;
|
|
6369
|
+
if (day <= night) return cur >= night || cur < day;
|
|
6370
|
+
return cur >= night && cur < day;
|
|
6371
|
+
};
|
|
6372
|
+
const scheduleTick = () => {
|
|
6373
|
+
const sc = rSchedule();
|
|
6374
|
+
if (!sc.enabled) return;
|
|
6375
|
+
const want = (sc.mode === "system" ? window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false : isNightNow(sc)) ? sc.nightProfile : sc.dayProfile;
|
|
6376
|
+
if (!want || want === cfg.activeProfile) return;
|
|
6377
|
+
applyProfileById(want);
|
|
6378
|
+
};
|
|
5067
6379
|
applyWp();
|
|
5068
6380
|
syncBg();
|
|
5069
6381
|
watchParts();
|
|
5070
|
-
loadPersisted().then(() => {
|
|
6382
|
+
loadPersisted().then(async (serverRotated) => {
|
|
6383
|
+
syncMetaNow();
|
|
5071
6384
|
if (rHasColor()) {
|
|
5072
6385
|
const [h, s, l] = rColor();
|
|
5073
6386
|
registerCustom(h, s, l);
|
|
5074
6387
|
}
|
|
6388
|
+
if (serverRotated) {
|
|
6389
|
+
if (cfg.backgroundType === "image" && rWp()) {
|
|
6390
|
+
await adoptWallpaperColor(rWp());
|
|
6391
|
+
saveConfig();
|
|
6392
|
+
}
|
|
6393
|
+
} else await maybeRotate();
|
|
5075
6394
|
if (cfg.backgroundType === "video") {
|
|
5076
6395
|
const v = rWpVideo();
|
|
5077
6396
|
if (v) {
|
|
@@ -5093,16 +6412,24 @@ window.__ModuleLoader__.load({
|
|
|
5093
6412
|
persistConfig();
|
|
5094
6413
|
} else applyThemeColor();
|
|
5095
6414
|
syncBg();
|
|
6415
|
+
scheduleTick();
|
|
5096
6416
|
if (rHasColor()) {
|
|
5097
6417
|
colorRev++;
|
|
5098
6418
|
bound?.syncColor(hslToHsv(...rColor()), colorRev);
|
|
5099
6419
|
}
|
|
5100
6420
|
});
|
|
6421
|
+
const schemeMq = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
6422
|
+
const scheduleTimer = window.setInterval(scheduleTick, 3e4);
|
|
6423
|
+
schemeMq?.addEventListener?.("change", scheduleTick);
|
|
6424
|
+
ctx.effect(() => () => {
|
|
6425
|
+
window.clearInterval(scheduleTimer);
|
|
6426
|
+
schemeMq?.removeEventListener?.("change", scheduleTick);
|
|
6427
|
+
}, "dsh-any-background: schedule timer");
|
|
5101
6428
|
ctx.effect(() => () => {
|
|
5102
6429
|
teardownWp();
|
|
5103
6430
|
}, "dsh-any-background: wp cleanup");
|
|
5104
6431
|
ctx.effect(() => ctx.on("theme/change", () => {
|
|
5105
|
-
if (rHasColor()) {
|
|
6432
|
+
if (rHasColor() || rBgDark() !== null || rSchemeOverride() !== "auto") {
|
|
5106
6433
|
const snapshot = ctx.theme.getTheme();
|
|
5107
6434
|
if (snapshot.preference !== CUSTOM_ID && snapshot.themes.some((t) => t.id === CUSTOM_ID)) ctx.theme.setTheme(CUSTOM_ID);
|
|
5108
6435
|
}
|
|
@@ -5135,6 +6462,7 @@ window.__ModuleLoader__.load({
|
|
|
5135
6462
|
const sectionInject = (actions) => {
|
|
5136
6463
|
bound = actions;
|
|
5137
6464
|
syncBg();
|
|
6465
|
+
syncMetaNow();
|
|
5138
6466
|
const playVideoFromBlob = (blob, mime) => {
|
|
5139
6467
|
const localUrl = URL.createObjectURL(blob);
|
|
5140
6468
|
cfg.backgroundType = "video";
|
|
@@ -5388,6 +6716,111 @@ window.__ModuleLoader__.load({
|
|
|
5388
6716
|
} catch {
|
|
5389
6717
|
return false;
|
|
5390
6718
|
}
|
|
6719
|
+
},
|
|
6720
|
+
saveProfile: (name) => {
|
|
6721
|
+
const trimmed = name.trim();
|
|
6722
|
+
if (!trimmed) return false;
|
|
6723
|
+
const profiles = [...rProfiles()];
|
|
6724
|
+
if (profiles.length >= 20) profiles.shift();
|
|
6725
|
+
const id = `p-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
6726
|
+
profiles.push({
|
|
6727
|
+
id,
|
|
6728
|
+
name: trimmed.slice(0, 60),
|
|
6729
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6730
|
+
config: currentAppearance()
|
|
6731
|
+
});
|
|
6732
|
+
cfg.profiles = profiles;
|
|
6733
|
+
cfg.activeProfile = id;
|
|
6734
|
+
persistConfig();
|
|
6735
|
+
syncMetaNow();
|
|
6736
|
+
return true;
|
|
6737
|
+
},
|
|
6738
|
+
applyProfile: (id) => applyProfileById(id),
|
|
6739
|
+
deleteProfile: (id) => {
|
|
6740
|
+
const before = rProfiles();
|
|
6741
|
+
if (!before.some((p) => p.id === id)) return false;
|
|
6742
|
+
cfg.profiles = before.filter((p) => p.id !== id);
|
|
6743
|
+
if (cfg.activeProfile === id) cfg.activeProfile = null;
|
|
6744
|
+
const sc = rSchedule();
|
|
6745
|
+
if (sc.dayProfile === id || sc.nightProfile === id) cfg.schedule = {
|
|
6746
|
+
...sc,
|
|
6747
|
+
dayProfile: sc.dayProfile === id ? null : sc.dayProfile,
|
|
6748
|
+
nightProfile: sc.nightProfile === id ? null : sc.nightProfile
|
|
6749
|
+
};
|
|
6750
|
+
persistConfig();
|
|
6751
|
+
syncMetaNow();
|
|
6752
|
+
return true;
|
|
6753
|
+
},
|
|
6754
|
+
applyPreset: (appearance) => {
|
|
6755
|
+
applyAppearanceLive(appearance);
|
|
6756
|
+
cfg.activeProfile = null;
|
|
6757
|
+
applyWp();
|
|
6758
|
+
persistConfig();
|
|
6759
|
+
syncMetaNow();
|
|
6760
|
+
},
|
|
6761
|
+
setSchemeOverride: (v) => {
|
|
6762
|
+
cfg.schemeOverride = v;
|
|
6763
|
+
registerCustom();
|
|
6764
|
+
applyWp();
|
|
6765
|
+
saveConfig();
|
|
6766
|
+
syncMetaNow();
|
|
6767
|
+
},
|
|
6768
|
+
setSchedule: (patch) => {
|
|
6769
|
+
cfg.schedule = {
|
|
6770
|
+
...rSchedule(),
|
|
6771
|
+
...patch
|
|
6772
|
+
};
|
|
6773
|
+
if (patch.enabled === true) scheduleTick();
|
|
6774
|
+
persistConfig();
|
|
6775
|
+
syncMetaNow();
|
|
6776
|
+
},
|
|
6777
|
+
setRotation: (patch) => {
|
|
6778
|
+
cfg.rotation = {
|
|
6779
|
+
...rRotation(),
|
|
6780
|
+
...patch
|
|
6781
|
+
};
|
|
6782
|
+
if (patch.enabled === true) maybeRotate();
|
|
6783
|
+
persistConfig();
|
|
6784
|
+
syncMetaNow();
|
|
6785
|
+
},
|
|
6786
|
+
addRotationItems: async (files) => {
|
|
6787
|
+
let added = false;
|
|
6788
|
+
for (const f of files) {
|
|
6789
|
+
if (!f.type.startsWith("image/")) continue;
|
|
6790
|
+
const dataUrl = await readImgAsync(f);
|
|
6791
|
+
if (!dataUrl) continue;
|
|
6792
|
+
const r = await rotationAdd(dataUrl, await makeThumb(dataUrl) ?? "");
|
|
6793
|
+
if (r.ok && r.items !== void 0) {
|
|
6794
|
+
cfg.rotation = {
|
|
6795
|
+
...rRotation(),
|
|
6796
|
+
items: r.items
|
|
6797
|
+
};
|
|
6798
|
+
added = true;
|
|
6799
|
+
}
|
|
6800
|
+
}
|
|
6801
|
+
if (added) {
|
|
6802
|
+
persistConfig();
|
|
6803
|
+
syncMetaNow();
|
|
6804
|
+
syncBg();
|
|
6805
|
+
}
|
|
6806
|
+
return added;
|
|
6807
|
+
},
|
|
6808
|
+
removeRotationItem: async (index) => {
|
|
6809
|
+
const r = await rotationRemove(index);
|
|
6810
|
+
if (!r.ok || r.items === void 0) return false;
|
|
6811
|
+
const rot = rRotation();
|
|
6812
|
+
cfg.rotation = {
|
|
6813
|
+
...rot,
|
|
6814
|
+
items: r.items,
|
|
6815
|
+
current: Math.max(0, Math.min(rot.current >= index ? rot.current - 1 : rot.current, Math.max(0, r.items.length - 1)))
|
|
6816
|
+
};
|
|
6817
|
+
persistConfig();
|
|
6818
|
+
syncMetaNow();
|
|
6819
|
+
return true;
|
|
6820
|
+
},
|
|
6821
|
+
rotateNow: async () => {
|
|
6822
|
+
if (rRotation().items.length === 0) return false;
|
|
6823
|
+
return applyRotationIndex(pickNextRotationIndex(), true);
|
|
5391
6824
|
}
|
|
5392
6825
|
};
|
|
5393
6826
|
};
|
|
@@ -5463,12 +6896,11 @@ window.__ModuleLoader__.load({
|
|
|
5463
6896
|
restoreTimers.forEach((id) => window.clearTimeout(id));
|
|
5464
6897
|
}, "dsh-any-background: boot restore");
|
|
5465
6898
|
const watchdogId = window.setInterval(() => {
|
|
5466
|
-
if (!rHasColor()) return;
|
|
6899
|
+
if (!rHasColor() && rBgDark() === null && rSchemeOverride() === "auto") return;
|
|
5467
6900
|
const snapshot = ctx.theme.getTheme();
|
|
5468
6901
|
let changed = false;
|
|
5469
6902
|
if (!snapshot.themes.some((t) => t.id === CUSTOM_ID)) {
|
|
5470
|
-
|
|
5471
|
-
registerCustom(h, s, l);
|
|
6903
|
+
registerCustom();
|
|
5472
6904
|
changed = true;
|
|
5473
6905
|
} else if (snapshot.preference !== CUSTOM_ID) {
|
|
5474
6906
|
ctx.theme.setTheme(CUSTOM_ID);
|