dsh-any-background 0.2.9 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -27
- package/README.zh.md +48 -26
- package/lib/client.js +1294 -271
- package/lib/client.js.map +1 -1
- package/lib/index.js +200 -42
- package/package.json +23 -11
package/lib/client.js
CHANGED
|
@@ -12,9 +12,15 @@ window.__ModuleLoader__.load({
|
|
|
12
12
|
try {
|
|
13
13
|
return require("@deepseek-ai/dsh-client-store");
|
|
14
14
|
} catch {}
|
|
15
|
-
|
|
15
|
+
try {
|
|
16
|
+
return require("@deepseek-ai/dsh-client-runtime/client");
|
|
17
|
+
} catch {
|
|
18
|
+
console.warn("dsh-any-background: no client store module resolved on this host; the settings panel will be disabled");
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
16
21
|
}
|
|
17
|
-
|
|
22
|
+
/** null when neither store package resolves on this host (see resolveStore). */
|
|
23
|
+
const defineStore = resolveStore()?.defineStore ?? null;
|
|
18
24
|
//#endregion
|
|
19
25
|
//#region src/client/i18n.ts
|
|
20
26
|
const NS = "settings.anyBg";
|
|
@@ -46,8 +52,11 @@ window.__ModuleLoader__.load({
|
|
|
46
52
|
uiSop: "设置界面透明度",
|
|
47
53
|
uiChatRegion: "对话文本框",
|
|
48
54
|
uiTrajectory: "轨迹页",
|
|
49
|
-
uiPanelRegion: "
|
|
55
|
+
uiPanelRegion: "bettersidebar",
|
|
56
|
+
uiPanelNative: "右方侧边栏",
|
|
57
|
+
guideDesc: "管理主题色、壁纸与界面透明度",
|
|
50
58
|
uiProduced: "产出物/高亮内容",
|
|
59
|
+
uiHeader: "顶栏选项",
|
|
51
60
|
fontPageTitle: "字体与描边",
|
|
52
61
|
fontTitle: "自定义字体",
|
|
53
62
|
fontHint: "支持 ttf/otf/woff/woff2 字体文件(上限 100MB),将应用到整个界面的文字;代码块的等宽字体保持不变。中文字体通常有几十 MB,首次上传与刷新加载需要片刻",
|
|
@@ -57,6 +66,9 @@ window.__ModuleLoader__.load({
|
|
|
57
66
|
fontEnabled: "启用字体",
|
|
58
67
|
fontNone: "未设置,使用宿主默认字体",
|
|
59
68
|
fontFail: "字体上传失败,请确认是有效的 ttf/otf/woff/woff2 文件",
|
|
69
|
+
uploadTooLarge: "文件过大,上限 {limit}",
|
|
70
|
+
bgUploadFail: "壁纸上传失败",
|
|
71
|
+
videoUploadFail: "视频上传失败,本次播放不会被保存",
|
|
60
72
|
strokeTitle: "文字描边",
|
|
61
73
|
strokeHint: "按区域为文字添加描边:粗细为 0 表示关闭,「自动」会生成与文字颜色相反的颜色,「主题色」跟随当前主题色。代码块与图标已自动豁免,多色语法不会糊成一团",
|
|
62
74
|
strokeWidth: "描边粗细",
|
|
@@ -232,8 +244,11 @@ window.__ModuleLoader__.load({
|
|
|
232
244
|
uiSop: "Settings interface opacity",
|
|
233
245
|
uiChatRegion: "Conversation text frame",
|
|
234
246
|
uiTrajectory: "Trajectory view",
|
|
235
|
-
uiPanelRegion: "
|
|
247
|
+
uiPanelRegion: "bettersidebar",
|
|
248
|
+
uiPanelNative: "Right sidebar",
|
|
249
|
+
guideDesc: "Theme color, wallpaper and interface opacity in the right sidebar",
|
|
236
250
|
uiProduced: "Produced / highlights",
|
|
251
|
+
uiHeader: "Header popovers",
|
|
237
252
|
fontPageTitle: "Font & outline",
|
|
238
253
|
fontTitle: "Custom font",
|
|
239
254
|
fontHint: "Accepts ttf/otf/woff/woff2 files (up to 100MB) and applies them to the whole interface; code blocks keep their monospace stack. CJK fonts routinely span tens of MB, so the first upload and each reload take a moment",
|
|
@@ -243,6 +258,9 @@ window.__ModuleLoader__.load({
|
|
|
243
258
|
fontEnabled: "Font enabled",
|
|
244
259
|
fontNone: "None — host default",
|
|
245
260
|
fontFail: "Upload failed — make sure it is a valid ttf/otf/woff/woff2 file",
|
|
261
|
+
uploadTooLarge: "File is too large (limit {limit})",
|
|
262
|
+
bgUploadFail: "Wallpaper upload failed",
|
|
263
|
+
videoUploadFail: "Video upload failed — this playback will not be saved",
|
|
246
264
|
strokeTitle: "Text outline",
|
|
247
265
|
strokeHint: "Outline text per region: width 0 turns it off, \"auto\" picks the inverse of the text color, and \"theme\" follows the current accent. Code blocks and icons are exempted automatically, so multi-color syntax never smears",
|
|
248
266
|
strokeWidth: "Outline width",
|
|
@@ -409,7 +427,8 @@ window.__ModuleLoader__.load({
|
|
|
409
427
|
trajectory: 30,
|
|
410
428
|
input: 30,
|
|
411
429
|
panel: 30,
|
|
412
|
-
produced: 30
|
|
430
|
+
produced: 30,
|
|
431
|
+
header: 30
|
|
413
432
|
},
|
|
414
433
|
strokes: {
|
|
415
434
|
bg: {
|
|
@@ -456,6 +475,11 @@ window.__ModuleLoader__.load({
|
|
|
456
475
|
width: 0,
|
|
457
476
|
color: "auto",
|
|
458
477
|
customColor: "#808080"
|
|
478
|
+
},
|
|
479
|
+
header: {
|
|
480
|
+
width: 0,
|
|
481
|
+
color: "auto",
|
|
482
|
+
customColor: "#808080"
|
|
459
483
|
}
|
|
460
484
|
},
|
|
461
485
|
settingsOpacity: .5,
|
|
@@ -486,6 +510,7 @@ window.__ModuleLoader__.load({
|
|
|
486
510
|
trajectoryOpacity: .5,
|
|
487
511
|
panelOpacity: .5,
|
|
488
512
|
producedOpacity: .5,
|
|
513
|
+
headerOpacity: .5,
|
|
489
514
|
profiles: [],
|
|
490
515
|
rotation: {
|
|
491
516
|
enabled: false,
|
|
@@ -625,7 +650,8 @@ window.__ModuleLoader__.load({
|
|
|
625
650
|
"trajectory",
|
|
626
651
|
"input",
|
|
627
652
|
"panel",
|
|
628
|
-
"produced"
|
|
653
|
+
"produced",
|
|
654
|
+
"header"
|
|
629
655
|
]) {
|
|
630
656
|
const v = b[k];
|
|
631
657
|
out[k] = typeof v === "number" ? Math.min(60, Math.max(0, v)) : DEFAULT_CONFIG.blurs[k];
|
|
@@ -641,7 +667,8 @@ window.__ModuleLoader__.load({
|
|
|
641
667
|
"trajectory",
|
|
642
668
|
"input",
|
|
643
669
|
"panel",
|
|
644
|
-
"produced"
|
|
670
|
+
"produced",
|
|
671
|
+
"header"
|
|
645
672
|
];
|
|
646
673
|
const STROKE_COLOR_KEYS = [
|
|
647
674
|
"auto",
|
|
@@ -690,6 +717,9 @@ window.__ModuleLoader__.load({
|
|
|
690
717
|
function rProducedOpacity() {
|
|
691
718
|
return clamp01(cfg.producedOpacity, DEFAULT_CONFIG.producedOpacity);
|
|
692
719
|
}
|
|
720
|
+
function rHeaderOpacity() {
|
|
721
|
+
return clamp01(cfg.headerOpacity, DEFAULT_CONFIG.headerOpacity);
|
|
722
|
+
}
|
|
693
723
|
function rBgState() {
|
|
694
724
|
return cfg.bgState;
|
|
695
725
|
}
|
|
@@ -753,7 +783,8 @@ window.__ModuleLoader__.load({
|
|
|
753
783
|
chatTextOpacity: rChatTextOpacity(),
|
|
754
784
|
trajectoryOpacity: rTrajectoryOpacity(),
|
|
755
785
|
panelOpacity: rPanelOpacity(),
|
|
756
|
-
producedOpacity: rProducedOpacity()
|
|
786
|
+
producedOpacity: rProducedOpacity(),
|
|
787
|
+
headerOpacity: rHeaderOpacity()
|
|
757
788
|
};
|
|
758
789
|
}
|
|
759
790
|
/** Apply an appearance snapshot onto cfg (meta fields untouched). */
|
|
@@ -775,6 +806,7 @@ window.__ModuleLoader__.load({
|
|
|
775
806
|
cfg.trajectoryOpacity = clamp01(ap.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity);
|
|
776
807
|
cfg.panelOpacity = clamp01(ap.panelOpacity, DEFAULT_CONFIG.panelOpacity);
|
|
777
808
|
cfg.producedOpacity = clamp01(ap.producedOpacity, DEFAULT_CONFIG.producedOpacity);
|
|
809
|
+
cfg.headerOpacity = clamp01(ap.headerOpacity, DEFAULT_CONFIG.headerOpacity);
|
|
778
810
|
}
|
|
779
811
|
const num = (n, def) => typeof n === "number" ? n : def;
|
|
780
812
|
const cl = (n, lo, hi, def) => typeof n === "number" ? Math.min(hi, Math.max(lo, n)) : def;
|
|
@@ -808,7 +840,8 @@ window.__ModuleLoader__.load({
|
|
|
808
840
|
chatTextOpacity: clamp01(ac.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity),
|
|
809
841
|
trajectoryOpacity: clamp01(ac.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity),
|
|
810
842
|
panelOpacity: clamp01(ac.panelOpacity, DEFAULT_CONFIG.panelOpacity),
|
|
811
|
-
producedOpacity: clamp01(ac.producedOpacity, DEFAULT_CONFIG.producedOpacity)
|
|
843
|
+
producedOpacity: clamp01(ac.producedOpacity, DEFAULT_CONFIG.producedOpacity),
|
|
844
|
+
headerOpacity: clamp01(ac.headerOpacity, DEFAULT_CONFIG.headerOpacity)
|
|
812
845
|
}
|
|
813
846
|
});
|
|
814
847
|
}
|
|
@@ -873,7 +906,8 @@ window.__ModuleLoader__.load({
|
|
|
873
906
|
"trajectory",
|
|
874
907
|
"input",
|
|
875
908
|
"panel",
|
|
876
|
-
"produced"
|
|
909
|
+
"produced",
|
|
910
|
+
"header"
|
|
877
911
|
]) blurs[k] = num(bl[k], DEFAULT_CONFIG.blurs[k]);
|
|
878
912
|
const bgType = [
|
|
879
913
|
"video",
|
|
@@ -916,6 +950,7 @@ window.__ModuleLoader__.load({
|
|
|
916
950
|
trajectoryOpacity: clamp01(c.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity),
|
|
917
951
|
panelOpacity: clamp01(c.panelOpacity, DEFAULT_CONFIG.panelOpacity),
|
|
918
952
|
producedOpacity: clamp01(c.producedOpacity, DEFAULT_CONFIG.producedOpacity),
|
|
953
|
+
headerOpacity: clamp01(c.headerOpacity, DEFAULT_CONFIG.headerOpacity),
|
|
919
954
|
profiles: adoptProfiles(c.profiles),
|
|
920
955
|
rotation: adoptRotation(c.rotation),
|
|
921
956
|
schedule: adoptSchedule(c.schedule),
|
|
@@ -959,6 +994,95 @@ window.__ModuleLoader__.load({
|
|
|
959
994
|
};
|
|
960
995
|
}
|
|
961
996
|
//#endregion
|
|
997
|
+
//#region src/client/host.ts
|
|
998
|
+
/**
|
|
999
|
+
* Host release detection + feature classification.
|
|
1000
|
+
*
|
|
1001
|
+
* WHY THIS EXISTS
|
|
1002
|
+
* The client context exposes no host version: there is no `hostVersion` /
|
|
1003
|
+
* `dshVersion` field on `ctx`, and `window.__DSH_BOOT__.version` is the
|
|
1004
|
+
* module-table format tag (`'client'`), not a release. Capability probing alone
|
|
1005
|
+
* is also not sufficient, because the obvious probes do not separate the
|
|
1006
|
+
* generations — `ctx.sidebarRightTabs` and the native `[data-sidebar-right-panel]`
|
|
1007
|
+
* marker both exist on 0.1.5-rc.2 already, so using either as a "0.1.6+" test
|
|
1008
|
+
* mis-classifies 0.1.5 as newer.
|
|
1009
|
+
*
|
|
1010
|
+
* So the release is resolved on the Node half (where the launcher's on-disk
|
|
1011
|
+
* layout is visible) and handed to this half through the `read` RPC payload.
|
|
1012
|
+
* This module stores that verdict and turns it into the feature flags the rest
|
|
1013
|
+
* of the plugin gates on. When the release cannot be determined the flags fall
|
|
1014
|
+
* back to capability probing, never to a guess.
|
|
1015
|
+
*
|
|
1016
|
+
* @module
|
|
1017
|
+
*/
|
|
1018
|
+
let current = {
|
|
1019
|
+
version: null,
|
|
1020
|
+
generation: "unknown"
|
|
1021
|
+
};
|
|
1022
|
+
/** False until the server verdict lands; feature gates must not treat "not
|
|
1023
|
+
* asked yet" as "old host". */
|
|
1024
|
+
let resolved = false;
|
|
1025
|
+
const subs$1 = /* @__PURE__ */ new Set();
|
|
1026
|
+
/** Adopt the verdict delivered by the server's `read` payload. */
|
|
1027
|
+
function adoptHostInfo(raw) {
|
|
1028
|
+
if (raw === null || typeof raw !== "object") return;
|
|
1029
|
+
const h = raw;
|
|
1030
|
+
const version = typeof h.version === "string" && h.version.trim() !== "" ? h.version.trim() : null;
|
|
1031
|
+
const generation = h.generation === "0.1.5" || h.generation === "0.1.6" || h.generation === "0.1.7" ? h.generation : "unknown";
|
|
1032
|
+
const changed = !resolved || current.version !== version || current.generation !== generation;
|
|
1033
|
+
current = {
|
|
1034
|
+
version,
|
|
1035
|
+
generation
|
|
1036
|
+
};
|
|
1037
|
+
resolved = true;
|
|
1038
|
+
if (changed) subs$1.forEach((cb) => cb());
|
|
1039
|
+
}
|
|
1040
|
+
/** Subscribe to a verdict landing (or changing). */
|
|
1041
|
+
function subscribeHostInfo(cb) {
|
|
1042
|
+
subs$1.add(cb);
|
|
1043
|
+
return () => {
|
|
1044
|
+
subs$1.delete(cb);
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
/** Ordered generation rank, for "at least this generation" tests. `unknown`
|
|
1048
|
+
* has no rank and must be handled by the caller's fallback. */
|
|
1049
|
+
const RANK = {
|
|
1050
|
+
"0.1.5": 5,
|
|
1051
|
+
"0.1.6": 6,
|
|
1052
|
+
"0.1.7": 7
|
|
1053
|
+
};
|
|
1054
|
+
/**
|
|
1055
|
+
* Whether the host is at least `gen`. An unknown generation answers `false`:
|
|
1056
|
+
* callers treat that as "do not assume the newer behaviour" and use whatever
|
|
1057
|
+
* capability probe they own, which is the safe direction for every gate here.
|
|
1058
|
+
*/
|
|
1059
|
+
function hostAtLeast(gen) {
|
|
1060
|
+
const rank = RANK[current.generation];
|
|
1061
|
+
return rank !== void 0 && rank >= RANK[gen];
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Whether this host's own right Sidebar already carries the appearance page,
|
|
1065
|
+
* which makes the plugin's dsh-better-sidebar page a duplicate entry on the same
|
|
1066
|
+
* guide surface.
|
|
1067
|
+
*
|
|
1068
|
+
* `true` — 0.1.6 or newer: the better-sidebar page stands down.
|
|
1069
|
+
* `false` — 0.1.5 or an undetermined-but-answered generation: keep it.
|
|
1070
|
+
* `null` — the verdict has not landed yet (the probe is a Node round-trip).
|
|
1071
|
+
*
|
|
1072
|
+
* Callers must treat `null` as "not yet known": register nothing eagerly on a
|
|
1073
|
+
* `true`, and arm a withdrawal for a later `true` rather than blocking on it.
|
|
1074
|
+
*
|
|
1075
|
+
* NOTE: 0.1.5-rc.2 ALSO ships the native right Sidebar (verified: the
|
|
1076
|
+
* `ui-sidebar-right` entry is in that release's `dsh-web-app/cordis.patch.yml`),
|
|
1077
|
+
* so this gate deliberately keys off the release rather than off the presence of
|
|
1078
|
+
* the native sidebar service — the two are not the same fact.
|
|
1079
|
+
*/
|
|
1080
|
+
function suppressesBetterSidebar() {
|
|
1081
|
+
if (!resolved) return null;
|
|
1082
|
+
if (current.generation === "unknown") return false;
|
|
1083
|
+
return hostAtLeast("0.1.6");
|
|
1084
|
+
}
|
|
1085
|
+
//#endregion
|
|
962
1086
|
//#region src/client/rpc.ts
|
|
963
1087
|
const RPC_CHANNEL = "/dsh-any-background";
|
|
964
1088
|
/** Same-origin serve URL of the persisted video (enough for <video src>/fetch). */
|
|
@@ -1023,6 +1147,7 @@ window.__ModuleLoader__.load({
|
|
|
1023
1147
|
const data = await rpcCall("read", {});
|
|
1024
1148
|
if (data && typeof data === "object") {
|
|
1025
1149
|
const d = data;
|
|
1150
|
+
adoptHostInfo(d.host);
|
|
1026
1151
|
if (d.config) adoptConfig(d.config);
|
|
1027
1152
|
if (typeof d.wallpaperUrl === "string") setWpImageUrl(d.wallpaperUrl);
|
|
1028
1153
|
else if (d.wallpaperUrl === null) setWpImageUrl(null);
|
|
@@ -1117,36 +1242,73 @@ window.__ModuleLoader__.load({
|
|
|
1117
1242
|
error: v?.error ?? "failed"
|
|
1118
1243
|
};
|
|
1119
1244
|
}
|
|
1245
|
+
/** Map a refused upload response onto an outcome. The body has to be read: the
|
|
1246
|
+
* node half names the reason there, and an oversized transfer answers
|
|
1247
|
+
* `413 { ok:false, error:'too large', limit:'100 MB' }`. Checking `res.ok`
|
|
1248
|
+
* alone threw that away, so an oversized upload surfaced as a bare failure —
|
|
1249
|
+
* or, on the wallpaper path, as nothing at all. */
|
|
1250
|
+
async function refusedUpload(res) {
|
|
1251
|
+
const body = await res.json().catch(() => null);
|
|
1252
|
+
if (body?.error === "too large") return {
|
|
1253
|
+
ok: false,
|
|
1254
|
+
refusal: {
|
|
1255
|
+
kind: "too-large",
|
|
1256
|
+
status: res.status,
|
|
1257
|
+
limit: typeof body.limit === "string" ? body.limit : void 0
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
return {
|
|
1261
|
+
ok: false,
|
|
1262
|
+
refusal: {
|
|
1263
|
+
kind: "http",
|
|
1264
|
+
status: res.status
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
/** Localized description of a refusal for the panel's toast. `fallbackKey` is
|
|
1269
|
+
* the caller's own "…upload failed" string, used for anything that is not a
|
|
1270
|
+
* size refusal. */
|
|
1271
|
+
function uploadRefusalText(o, t, fallbackKey) {
|
|
1272
|
+
if (o.refusal?.kind === "too-large") {
|
|
1273
|
+
const limit = o.refusal.limit ?? "";
|
|
1274
|
+
return t("uploadTooLarge").split("{limit}").join(limit);
|
|
1275
|
+
}
|
|
1276
|
+
if (o.refusal !== void 0) return `${t(fallbackKey)} (http ${o.refusal.status})`;
|
|
1277
|
+
return t(fallbackKey);
|
|
1278
|
+
}
|
|
1120
1279
|
/** Upload a video's raw bytes over HTTP (MIME in Content-Type, body untouched
|
|
1121
1280
|
* — no base64 inflation that would blow the RPC body limit on large clips). */
|
|
1122
1281
|
async function uploadVideo(blob, mime) {
|
|
1123
1282
|
try {
|
|
1124
|
-
|
|
1283
|
+
const res = await fetch(VIDEO_UPLOAD_URL, {
|
|
1125
1284
|
method: "POST",
|
|
1126
1285
|
headers: { "Content-Type": mime || "application/octet-stream" },
|
|
1127
1286
|
body: blob
|
|
1128
|
-
})
|
|
1287
|
+
});
|
|
1288
|
+
return res.ok ? { ok: true } : await refusedUpload(res);
|
|
1129
1289
|
} catch (e) {
|
|
1130
1290
|
console.warn("dsh-any-background: video upload failed", e);
|
|
1131
|
-
return false;
|
|
1291
|
+
return { ok: false };
|
|
1132
1292
|
}
|
|
1133
1293
|
}
|
|
1134
1294
|
/** Upload a wallpaper's raw bytes over HTTP — same streaming model as videos,
|
|
1135
1295
|
* original pixels preserved, zero base64 round-trips. */
|
|
1136
1296
|
async function uploadWallpaper(blob) {
|
|
1137
1297
|
try {
|
|
1138
|
-
|
|
1298
|
+
const res = await fetch(WALLPAPER_UPLOAD_URL, {
|
|
1139
1299
|
method: "POST",
|
|
1140
1300
|
headers: { "Content-Type": blob.type || "image/jpeg" },
|
|
1141
1301
|
body: blob
|
|
1142
|
-
})
|
|
1302
|
+
});
|
|
1303
|
+
return res.ok ? { ok: true } : await refusedUpload(res);
|
|
1143
1304
|
} catch (e) {
|
|
1144
1305
|
console.warn("dsh-any-background: wallpaper upload failed", e);
|
|
1145
|
-
return false;
|
|
1306
|
+
return { ok: false };
|
|
1146
1307
|
}
|
|
1147
1308
|
}
|
|
1148
1309
|
/** Upload a custom font's raw bytes over HTTP; resolves the sniffed MIME on
|
|
1149
|
-
* success
|
|
1310
|
+
* success, or a refusal the caller can report (an oversized font used to be
|
|
1311
|
+
* indistinguishable from a corrupt one). */
|
|
1150
1312
|
async function uploadFont(blob) {
|
|
1151
1313
|
try {
|
|
1152
1314
|
const res = await fetch(FONT_UPLOAD_URL, {
|
|
@@ -1154,12 +1316,15 @@ window.__ModuleLoader__.load({
|
|
|
1154
1316
|
headers: { "Content-Type": blob.type || "application/octet-stream" },
|
|
1155
1317
|
body: blob
|
|
1156
1318
|
});
|
|
1157
|
-
if (!res.ok) return
|
|
1319
|
+
if (!res.ok) return await refusedUpload(res);
|
|
1158
1320
|
const data = await res.json().catch(() => null);
|
|
1159
|
-
return
|
|
1321
|
+
return {
|
|
1322
|
+
ok: true,
|
|
1323
|
+
mime: typeof data?.mime === "string" ? data.mime : "font/ttf"
|
|
1324
|
+
};
|
|
1160
1325
|
} catch (e) {
|
|
1161
1326
|
console.warn("dsh-any-background: font upload failed", e);
|
|
1162
|
-
return
|
|
1327
|
+
return { ok: false };
|
|
1163
1328
|
}
|
|
1164
1329
|
}
|
|
1165
1330
|
/** Remove the stored custom font (server deletes every variant + clears the
|
|
@@ -1340,6 +1505,12 @@ window.__ModuleLoader__.load({
|
|
|
1340
1505
|
v
|
|
1341
1506
|
];
|
|
1342
1507
|
}
|
|
1508
|
+
/** Format an RGB triple as a lowercase hex string. Channels are rounded: some
|
|
1509
|
+
* callers feed float channels and a fractional byte would produce invalid
|
|
1510
|
+
* hex. */
|
|
1511
|
+
function rgbToHex(rgb) {
|
|
1512
|
+
return "#" + rgb.map((v) => Math.round(v).toString(16).padStart(2, "0")).join("");
|
|
1513
|
+
}
|
|
1343
1514
|
let tokensCacheKey = "";
|
|
1344
1515
|
let tokensCache = null;
|
|
1345
1516
|
/**
|
|
@@ -2483,9 +2654,15 @@ window.__ModuleLoader__.load({
|
|
|
2483
2654
|
wpController = null;
|
|
2484
2655
|
}
|
|
2485
2656
|
/** Register a callback fired once a generated snapshot is ready (so the caller
|
|
2486
|
-
* can re-sync the settings preview / store).
|
|
2657
|
+
* can re-sync the settings preview / store). Returns an unsubscribe: HMR
|
|
2658
|
+
* re-runs apply and would otherwise leave the previous apply's closure as the
|
|
2659
|
+
* live listener (a stale this-session callback invoked by a lingering
|
|
2660
|
+
* controller). */
|
|
2487
2661
|
function onGeneratedSnapshot(cb) {
|
|
2488
2662
|
snapshotListener = cb;
|
|
2663
|
+
return () => {
|
|
2664
|
+
if (snapshotListener === cb) snapshotListener = null;
|
|
2665
|
+
};
|
|
2489
2666
|
}
|
|
2490
2667
|
function ensureTokenStyle() {
|
|
2491
2668
|
if (tokenStyleEl?.isConnected) return tokenStyleEl;
|
|
@@ -2647,6 +2824,7 @@ window.__ModuleLoader__.load({
|
|
|
2647
2824
|
lastBgKey = bgKey;
|
|
2648
2825
|
applyPartOpacities(ops);
|
|
2649
2826
|
}
|
|
2827
|
+
applyExemptDefaults();
|
|
2650
2828
|
} catch {}
|
|
2651
2829
|
}
|
|
2652
2830
|
const SETTINGS_PANEL_SEL = "[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]";
|
|
@@ -2665,17 +2843,98 @@ window.__ModuleLoader__.load({
|
|
|
2665
2843
|
if (px > 0) document.documentElement.style.setProperty("--dsh-any-part-blur-global", `blur(${px}px)`);
|
|
2666
2844
|
else document.documentElement.style.removeProperty("--dsh-any-part-blur-global");
|
|
2667
2845
|
}
|
|
2668
|
-
const PANEL_TOKEN_RULE = "[data-dsh-bottom-panel],[data-sidebar-right-panel]{--dsw-alias-bg-base:var(--dsh-any-panel-bg-base);--dsw-alias-bg-layer-1:var(--dsh-any-panel-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-panel-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-panel-layer-3)
|
|
2669
|
-
const
|
|
2670
|
-
const
|
|
2846
|
+
const PANEL_TOKEN_RULE = "[data-dsh-bottom-panel],[data-sidebar-right-panel]{--dsw-alias-bg-base:var(--dsh-any-panel-bg-base);--dsw-alias-bg-layer-1:var(--dsh-any-panel-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-panel-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-panel-layer-3)}";
|
|
2847
|
+
const NO_DOCKKIT = ":not(:has([data-dockkit-host],[data-dockkit-empty]))";
|
|
2848
|
+
const PANEL_BLUR_RULE = `[data-sidebar-right-panel] [data-dockkit-host="dock"],[data-sidebar-right-panel] [data-dockkit-empty]{-webkit-backdrop-filter:var(--dsh-any-blur-panel,none);backdrop-filter:var(--dsh-any-blur-panel,none)}[data-sidebar-right-panel]${NO_DOCKKIT}{-webkit-backdrop-filter:var(--dsh-any-blur-panel,none);backdrop-filter:var(--dsh-any-blur-panel,none)}[data-dsh-bottom-panel]{-webkit-backdrop-filter:var(--dsh-any-blur-panel,none);backdrop-filter:var(--dsh-any-blur-panel,none)}`;
|
|
2849
|
+
const PANEL_PROMOTION_RULE = `[data-sidebar-right-panel]${NO_DOCKKIT}{position:fixed!important;z-index:26!important}[data-sidebar-right-panel="fullscreen"]${NO_DOCKKIT}{z-index:40!important}`;
|
|
2850
|
+
const HEADER_POPOVER_RULE = "[data-dsh-any-header-popover][role],ul[class*=\"_menu\"],div[role=\"dialog\"][class*=\"_panel\"]:not([aria-modal=\"true\"]),[role=\"menu\"][class*=\"_denseList\"]:not([class*=\"_portal\"]),[role=\"tree\"][class*=\"_menu\"]{-webkit-backdrop-filter:var(--dsh-any-blur-header,none);backdrop-filter:var(--dsh-any-blur-header,none)}";
|
|
2851
|
+
const EXEMPT_DEFAULT_RULE = "[role=\"dialog\"][aria-modal=\"true\"]:not([aria-labelledby]){backdrop-filter:none;-webkit-backdrop-filter:none}[role=\"menu\"]:has([class*=\"_danger\"]){backdrop-filter:none;-webkit-backdrop-filter:none;-webkit-text-stroke-width:0}";
|
|
2852
|
+
/** Selector the dynamic header token rule targets — kept in sync with the
|
|
2853
|
+
* static HEADER_POPOVER_RULE selector list (specificity equal or higher than
|
|
2854
|
+
* the card group's `[role="menu"]:not(...)` so it wins). */
|
|
2855
|
+
const HEADER_TOKEN_SELECTOR = "[data-dsh-any-header-popover][role],ul[class*=\"_menu\"],div[role=\"dialog\"][class*=\"_panel\"]:not([aria-modal=\"true\"]),[role=\"menu\"][class*=\"_denseList\"]:not([class*=\"_portal\"]),[role=\"tree\"][class*=\"_menu\"]";
|
|
2856
|
+
/** Header-popover surfaces: retint the menu token and (re)write the blur.
|
|
2857
|
+
* Mirrors applyProduced: the static rule above never changes, so a drag only
|
|
2858
|
+
* rewrites the blur variable and this one dynamic rule. */
|
|
2859
|
+
function applyHeaderPopovers() {
|
|
2860
|
+
const root = document.documentElement;
|
|
2861
|
+
const px = rBlurs().header;
|
|
2862
|
+
if (px > 0) root.style.setProperty("--dsh-any-blur-header", `blur(${px}px)`);
|
|
2863
|
+
else root.style.removeProperty("--dsh-any-blur-header");
|
|
2864
|
+
let opacity = rHeaderOpacity();
|
|
2865
|
+
if (opacity < 0) opacity = 0;
|
|
2866
|
+
if (opacity > 1) opacity = 1;
|
|
2867
|
+
const menu = (paletteTokens() ?? readHostOpacityTokens())?.["--dsw-specific-menu"];
|
|
2868
|
+
const el = ensureHeaderStyle();
|
|
2869
|
+
const css = menu === void 0 ? "" : `${HEADER_TOKEN_SELECTOR}{--dsw-specific-menu:${toRgba(menu, opacity)}}`;
|
|
2870
|
+
if (el.textContent !== css) el.textContent = css;
|
|
2871
|
+
}
|
|
2872
|
+
let headerStyleEl = null;
|
|
2873
|
+
function ensureHeaderStyle() {
|
|
2874
|
+
if (headerStyleEl?.isConnected) return headerStyleEl;
|
|
2875
|
+
headerStyleEl = document.createElement("style");
|
|
2876
|
+
headerStyleEl.dataset.plugin = "dsh-any-background-header";
|
|
2877
|
+
document.head.appendChild(headerStyleEl);
|
|
2878
|
+
return headerStyleEl;
|
|
2879
|
+
}
|
|
2880
|
+
/** Pin the exempt groups (confirm dialogs and the session-row menu) so no
|
|
2881
|
+
* appearance slider moves them — while KEEPING the active theme color.
|
|
2882
|
+
*
|
|
2883
|
+
* "Default" here means "not faded, not blurred", NOT "host-palette original":
|
|
2884
|
+
* an earlier version restored the :root host colors, which also stripped the
|
|
2885
|
+
* picked theme color and made the menu look un-themed. The correct source is
|
|
2886
|
+
* the same one every other slider uses (the plugin palette when there is one,
|
|
2887
|
+
* else the host's resolved tokens) with alpha pinned to 1 — so the surface
|
|
2888
|
+
* keeps whatever color the theme currently dictates and only loses the slider's
|
|
2889
|
+
* translucency and blur.
|
|
2890
|
+
*
|
|
2891
|
+
* Emitted as REAL literals into a dedicated stylesheet, never as
|
|
2892
|
+
* `var(--x, var(--x))`: a self-referencing fallback is a CSS cycle that
|
|
2893
|
+
* computes to the guaranteed-invalid value and turns every consuming
|
|
2894
|
+
* `background:var(--dsw-alias-bg-layer-2)` fully transparent. Literals also
|
|
2895
|
+
* mean that when no color can be resolved we emit NO declaration at all, so
|
|
2896
|
+
* the surface keeps the re-scoped value rather than going transparent. */
|
|
2897
|
+
function applyExemptDefaults() {
|
|
2898
|
+
if (typeof document === "undefined") return;
|
|
2899
|
+
const source = paletteTokens() ?? readHostOpacityTokens();
|
|
2900
|
+
const el = ensureExemptStyle();
|
|
2901
|
+
const decls = [];
|
|
2902
|
+
const add = (token, value) => {
|
|
2903
|
+
if (value === void 0 || value === "") return;
|
|
2904
|
+
decls.push(`${token}:${toRgba(value, 1)}`);
|
|
2905
|
+
};
|
|
2906
|
+
add("--dsw-alias-bg-layer-1", source?.["--dsw-alias-bg-layer-1"]);
|
|
2907
|
+
add("--dsw-alias-bg-layer-2", source?.["--dsw-alias-bg-layer-2"]);
|
|
2908
|
+
add("--dsw-alias-bg-layer-3", source?.["--dsw-alias-bg-layer-3"]);
|
|
2909
|
+
add("--dsw-specific-menu", source?.["--dsw-specific-menu"]);
|
|
2910
|
+
if (decls.length === 0) {
|
|
2911
|
+
if (el.textContent !== "") el.textContent = "";
|
|
2912
|
+
return;
|
|
2913
|
+
}
|
|
2914
|
+
const dialog = "[role=\"dialog\"][aria-modal=\"true\"]:not([aria-labelledby])";
|
|
2915
|
+
const body = decls.join(";");
|
|
2916
|
+
const css = `${dialog},${dialog} *{${body}}[role="menu"]:has([class*="_danger"]){${body}}`;
|
|
2917
|
+
if (el.textContent !== css) el.textContent = css;
|
|
2918
|
+
}
|
|
2919
|
+
let exemptStyleEl = null;
|
|
2920
|
+
function ensureExemptStyle() {
|
|
2921
|
+
if (exemptStyleEl?.isConnected) return exemptStyleEl;
|
|
2922
|
+
exemptStyleEl = document.createElement("style");
|
|
2923
|
+
exemptStyleEl.dataset.plugin = "dsh-any-background-exempt";
|
|
2924
|
+
document.head.appendChild(exemptStyleEl);
|
|
2925
|
+
return exemptStyleEl;
|
|
2926
|
+
}
|
|
2927
|
+
const PRODUCED_RULE = "[data-code-block-content] pre,[data-code-block-banner],[data-composer-chip],[data-changed-files]>button:first-child,[data-terminal],[data-read],[data-context-injection-body],[data-search],[data-web],[class*=\"_ioCard\"]{-webkit-backdrop-filter:var(--dsh-any-blur-prod,none);backdrop-filter:var(--dsh-any-blur-prod,none)}@supports (background:color-mix(in srgb,red 50%,transparent)){.md-code-block{background:transparent!important}.md-code-block>div{background-color:transparent!important}.md-code-block [data-code-block-content] pre{background-color:color-mix(in srgb,var(--dsl-code-block-background,var(--dsw-alias-markdown-code-block,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}.md-code-block [data-code-block-banner]{background-color:color-mix(in srgb,var(--dsl-code-block-banner-background-color,var(--dsw-alias-markdown-code-block-banner,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-code-block-content]{--shiki-background:color-mix(in srgb,var(--dsw-alias-markdown-code-block,var(--dsl-code-block-background,transparent)) var(--dsh-any-prod-pct,100%),transparent)}:not(pre)>code{background-color:color-mix(in srgb,var(--dsw-alias-markdown-inline-code,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-composer-chip]>*{background-color:color-mix(in srgb,var(--dsw-alias-interactive-bg-hover,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-terminal]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-terminal] button{background-color:transparent!important}[data-read]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-read]>div:first-child{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block-banner,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-context-injection-body]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-search]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-web]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[class*=\"_ioCard\"]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}}";
|
|
2671
2928
|
const STROKE_RULE = [
|
|
2672
2929
|
`${SETTINGS_PANEL_SEL}{-webkit-text-stroke:var(--dsh-any-stroke-settings-w,0px) var(--dsh-any-stroke-settings-c,transparent);paint-order:stroke fill}`,
|
|
2673
2930
|
"[role=\"menu\"]:not([data-dockkit-tab-menu]),[role=\"listbox\"],body>[role=\"tree\"],body>[role=\"dialog\"]:not([aria-modal=\"true\"]){-webkit-text-stroke:var(--dsh-any-stroke-card-w,0px) var(--dsh-any-stroke-card-c,transparent);paint-order:stroke fill}",
|
|
2674
2931
|
"[data-composer-card],[data-cordis-panel],[data-composer-card] textarea,[data-composer-card] input,[data-composer-card] [contenteditable],[data-cordis-panel] input,[data-cordis-panel] textarea{-webkit-text-stroke:var(--dsh-any-stroke-input-w,0px) var(--dsh-any-stroke-input-c,transparent);paint-order:stroke fill}",
|
|
2675
2932
|
`[data-chat-flow]{-webkit-text-stroke:var(--dsh-any-stroke-chat-w,0px) var(--dsh-any-stroke-chat-c,transparent);paint-order:stroke fill}`,
|
|
2676
2933
|
`[data-conversation-composer-overlay]{-webkit-text-stroke:var(--dsh-any-stroke-trajectory-w,0px) var(--dsh-any-stroke-trajectory-c,transparent);paint-order:stroke fill}`,
|
|
2677
|
-
"[data-code-block-content] pre,[data-code-block-content],[data-code-block-banner],[data-composer-chip],:not(pre)>code{-webkit-text-stroke:var(--dsh-any-stroke-produced-w,0px) var(--dsh-any-stroke-produced-c,transparent);paint-order:stroke fill}",
|
|
2934
|
+
"[data-code-block-content] pre,[data-code-block-content],[data-code-block-banner],[data-composer-chip],:not(pre)>code,[data-changed-files],[data-terminal],[data-read],[data-context-injection-body],[data-search],[data-web],[class*=\"_ioCard\"]{-webkit-text-stroke:var(--dsh-any-stroke-produced-w,0px) var(--dsh-any-stroke-produced-c,transparent);paint-order:stroke fill}",
|
|
2678
2935
|
`[data-dsh-bottom-panel],[data-sidebar-right-panel]{-webkit-text-stroke:var(--dsh-any-stroke-panel-w,0px) var(--dsh-any-stroke-panel-c,transparent);paint-order:stroke fill}`,
|
|
2936
|
+
"[data-dsh-any-header-popover],ul[class*=\"_menu\"],div[role=\"dialog\"][class*=\"_panel\"]:not([aria-modal=\"true\"]),[role=\"menu\"][class*=\"_denseList\"]:not([class*=\"_portal\"]),[role=\"tree\"][class*=\"_menu\"]{-webkit-text-stroke:var(--dsh-any-stroke-header-w,0px) var(--dsh-any-stroke-header-c,transparent);paint-order:stroke fill}",
|
|
2937
|
+
"[data-chat-flow] [role=\"status\"],[data-chat-flow] [data-turn-process],[data-chat-flow] [data-text-shimmer],[data-chat-flow] [style*=\"--dsh-text-shimmer-spread\"]{-webkit-text-stroke-width:0!important;paint-order:normal!important}",
|
|
2679
2938
|
`svg{-webkit-text-stroke-width:0!important;paint-order:normal!important}`,
|
|
2680
2939
|
`::placeholder{-webkit-text-stroke-width:0!important}`
|
|
2681
2940
|
].join("");
|
|
@@ -2689,7 +2948,8 @@ window.__ModuleLoader__.load({
|
|
|
2689
2948
|
"trajectory",
|
|
2690
2949
|
"input",
|
|
2691
2950
|
"panel",
|
|
2692
|
-
"produced"
|
|
2951
|
+
"produced",
|
|
2952
|
+
"header"
|
|
2693
2953
|
];
|
|
2694
2954
|
/** Resolve one group's stroke color key into a concrete CSS color.
|
|
2695
2955
|
* 'auto' contrasts the FONT direction (white fonts → black stroke and vice
|
|
@@ -2724,6 +2984,18 @@ window.__ModuleLoader__.load({
|
|
|
2724
2984
|
}
|
|
2725
2985
|
/** Write every group's stroke width/color variables + the two column strokes.
|
|
2726
2986
|
* Called from applyWp so palette/verdict changes re-derive 'auto'/'theme'. */
|
|
2987
|
+
/** Write (or clear) one column's inline text stroke. The stroke rides inline
|
|
2988
|
+
* styles and is fully removed at width 0 so nothing lingers after the slider
|
|
2989
|
+
* resets. */
|
|
2990
|
+
function applyInlineStroke(el, s) {
|
|
2991
|
+
if (s.width > 0) {
|
|
2992
|
+
el.style.setProperty("-webkit-text-stroke", `${s.width}px ${strokeColor(s)}`);
|
|
2993
|
+
el.style.setProperty("paint-order", "stroke fill");
|
|
2994
|
+
} else {
|
|
2995
|
+
el.style.removeProperty("-webkit-text-stroke");
|
|
2996
|
+
el.style.removeProperty("paint-order");
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2727
2999
|
function applyStrokes() {
|
|
2728
3000
|
const strokes = rStrokes();
|
|
2729
3001
|
const root = document.documentElement;
|
|
@@ -2735,15 +3007,7 @@ window.__ModuleLoader__.load({
|
|
|
2735
3007
|
discoverParts();
|
|
2736
3008
|
for (const g of STROKE_INLINE_GROUPS) {
|
|
2737
3009
|
const el = g === "bg" ? centerEl : sidebarEl;
|
|
2738
|
-
if (el
|
|
2739
|
-
const s = strokes[g];
|
|
2740
|
-
if (s.width > 0) {
|
|
2741
|
-
el.style.setProperty("-webkit-text-stroke", `${s.width}px ${strokeColor(s)}`);
|
|
2742
|
-
el.style.setProperty("paint-order", "stroke fill");
|
|
2743
|
-
} else {
|
|
2744
|
-
el.style.removeProperty("-webkit-text-stroke");
|
|
2745
|
-
el.style.removeProperty("paint-order");
|
|
2746
|
-
}
|
|
3010
|
+
if (el !== null) applyInlineStroke(el, strokes[g]);
|
|
2747
3011
|
}
|
|
2748
3012
|
}
|
|
2749
3013
|
/** Live per-group stroke update during slider drag (no full re-apply). */
|
|
@@ -2756,13 +3020,7 @@ window.__ModuleLoader__.load({
|
|
|
2756
3020
|
discoverParts();
|
|
2757
3021
|
const el = part === "bg" ? centerEl : sidebarEl;
|
|
2758
3022
|
if (el === null) return;
|
|
2759
|
-
|
|
2760
|
-
el.style.setProperty("-webkit-text-stroke", `${s.width}px ${strokeColor(s)}`);
|
|
2761
|
-
el.style.setProperty("paint-order", "stroke fill");
|
|
2762
|
-
} else {
|
|
2763
|
-
el.style.removeProperty("-webkit-text-stroke");
|
|
2764
|
-
el.style.removeProperty("paint-order");
|
|
2765
|
-
}
|
|
3023
|
+
applyInlineStroke(el, s);
|
|
2766
3024
|
}
|
|
2767
3025
|
/** Teardown only: drop every stroke variable and column inline stroke. */
|
|
2768
3026
|
function removeStrokes() {
|
|
@@ -2867,27 +3125,35 @@ window.__ModuleLoader__.load({
|
|
|
2867
3125
|
if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-panel", `blur(${px}px)`);
|
|
2868
3126
|
else document.documentElement.style.removeProperty("--dsh-any-blur-panel");
|
|
2869
3127
|
}
|
|
2870
|
-
|
|
3128
|
+
/** Re-scope one surface group's layer tokens onto plugin-owned variables so a
|
|
3129
|
+
* dedicated slider owns its alpha (the host has no per-surface opacity). The
|
|
3130
|
+
* settings group additionally retints its dialog surface variable. Always
|
|
3131
|
+
* written explicitly — including at 100% — so removing a slider's effect means
|
|
3132
|
+
* writing 1, not deleting the variable (the style rules below have no fallback
|
|
3133
|
+
* and would otherwise resolve the body tokens that applyCustomTokens rewrote
|
|
3134
|
+
* with the homepage card alpha). */
|
|
3135
|
+
function applyLayerOverrides(prefix, op, surface) {
|
|
2871
3136
|
const [h, s, l] = rColor();
|
|
2872
3137
|
const tokens = genTokens(h, s, l, rColorScheme()).tokens;
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
3138
|
+
if (surface) {
|
|
3139
|
+
const surfaceColor = tokens["--dsw-alias-bg-layer-2"];
|
|
3140
|
+
if (surfaceColor !== void 0) document.documentElement.style.setProperty(`${prefix}-surface`, toRgba(surfaceColor, op));
|
|
3141
|
+
}
|
|
3142
|
+
for (const layer of [
|
|
3143
|
+
1,
|
|
3144
|
+
2,
|
|
3145
|
+
3
|
|
3146
|
+
]) {
|
|
3147
|
+
const c = tokens[`--dsw-alias-bg-layer-${layer}`];
|
|
3148
|
+
if (c !== void 0) document.documentElement.style.setProperty(`${prefix}-layer-${layer}`, toRgba(c, op));
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
function applySettingsOverrides(op) {
|
|
3152
|
+
applyLayerOverrides("--dsh-any-bg-settings", op, true);
|
|
2880
3153
|
}
|
|
2881
3154
|
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)}";
|
|
2882
3155
|
function applyTrajectoryOverrides(op) {
|
|
2883
|
-
|
|
2884
|
-
const tokens = genTokens(h, s, l, rColorScheme()).tokens;
|
|
2885
|
-
const layer1 = tokens["--dsw-alias-bg-layer-1"];
|
|
2886
|
-
const layer2 = tokens["--dsw-alias-bg-layer-2"];
|
|
2887
|
-
const layer3 = tokens["--dsw-alias-bg-layer-3"];
|
|
2888
|
-
if (layer1 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-1", toRgba(layer1, op));
|
|
2889
|
-
if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-2", toRgba(layer2, op));
|
|
2890
|
-
if (layer3 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-3", toRgba(layer3, op));
|
|
3156
|
+
applyLayerOverrides("--dsh-any-traj", op, false);
|
|
2891
3157
|
}
|
|
2892
3158
|
/** Register a callback fired when a wallpaper-extracted theme color is
|
|
2893
3159
|
* adopted by the auto-adaptation path, so the section can re-register the
|
|
@@ -2896,6 +3162,9 @@ window.__ModuleLoader__.load({
|
|
|
2896
3162
|
let colorAdoptedListener = null;
|
|
2897
3163
|
function onColorAdopted(cb) {
|
|
2898
3164
|
colorAdoptedListener = cb;
|
|
3165
|
+
return () => {
|
|
3166
|
+
if (colorAdoptedListener === cb) colorAdoptedListener = null;
|
|
3167
|
+
};
|
|
2899
3168
|
}
|
|
2900
3169
|
/** Apply the theme color: use the saved pick directly, or fall back to
|
|
2901
3170
|
* extracting a dominant color from the current wallpaper. */
|
|
@@ -2911,8 +3180,8 @@ window.__ModuleLoader__.load({
|
|
|
2911
3180
|
if (hsl && rWp() === url) {
|
|
2912
3181
|
cfg.color = hsl;
|
|
2913
3182
|
colorAdoptedListener?.(hsl);
|
|
3183
|
+
applyWp();
|
|
2914
3184
|
}
|
|
2915
|
-
applyWp();
|
|
2916
3185
|
});
|
|
2917
3186
|
} else applyWp();
|
|
2918
3187
|
}
|
|
@@ -2944,7 +3213,7 @@ window.__ModuleLoader__.load({
|
|
|
2944
3213
|
* preserving the user's scale/intensity/speed/density/preset choices. */
|
|
2945
3214
|
function regenerateGeneratedBg() {
|
|
2946
3215
|
const params = cfg.generatedBg;
|
|
2947
|
-
if (!params || cfg.backgroundType === "image") return;
|
|
3216
|
+
if (!params || cfg.backgroundType === "image" || cfg.backgroundType === "video") return;
|
|
2948
3217
|
cfg.generatedBg = {
|
|
2949
3218
|
...params,
|
|
2950
3219
|
seed: randomSeed()
|
|
@@ -2967,9 +3236,10 @@ window.__ModuleLoader__.load({
|
|
|
2967
3236
|
wpEl.appendChild(wpController.canvas);
|
|
2968
3237
|
}
|
|
2969
3238
|
requestAnimationFrame(() => {
|
|
2970
|
-
if (!wpController) return;
|
|
2971
3239
|
const controller = wpController;
|
|
3240
|
+
if (controller === null) return;
|
|
2972
3241
|
const frame = controller.snapshot();
|
|
3242
|
+
if (wpController !== controller) return;
|
|
2973
3243
|
setWpUrl(frame);
|
|
2974
3244
|
applyWp();
|
|
2975
3245
|
snapshotListener?.();
|
|
@@ -2996,35 +3266,36 @@ window.__ModuleLoader__.load({
|
|
|
2996
3266
|
partBlurStyleEl.textContent = PART_BLUR_RULE;
|
|
2997
3267
|
document.head.appendChild(partBlurStyleEl);
|
|
2998
3268
|
}
|
|
2999
|
-
/** Find one direct child of `frame` whose `[data-rightbar-col]` marker matches.
|
|
3000
|
-
* The rightbar is its own grid track, NOT a "details" twin of the center
|
|
3001
|
-
* column, so it must never receive the main-bg tint or blur underlay. */
|
|
3002
|
-
function findRightbarInFrame(frame) {
|
|
3003
|
-
for (let i = 0; i < frame.children.length; i++) {
|
|
3004
|
-
const child = frame.children[i];
|
|
3005
|
-
if (child instanceof HTMLElement && child.dataset.rightbarCol !== void 0) return child;
|
|
3006
|
-
}
|
|
3007
|
-
return null;
|
|
3008
|
-
}
|
|
3009
3269
|
function discoverParts() {
|
|
3010
3270
|
const overlay = document.querySelector("[data-shell-overlay]");
|
|
3011
3271
|
if (overlay === null) return;
|
|
3012
3272
|
const frame = overlay.parentElement;
|
|
3013
3273
|
if (frame === null) return;
|
|
3014
3274
|
frameEl = frame;
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3275
|
+
let overlayIdx = -1;
|
|
3276
|
+
let rightIdx = -1;
|
|
3277
|
+
let right = null;
|
|
3278
|
+
const children = frame.children;
|
|
3279
|
+
for (let i = 0; i < children.length; i++) {
|
|
3280
|
+
const child = children[i];
|
|
3281
|
+
if (child === overlay) overlayIdx = i;
|
|
3282
|
+
else if (child instanceof HTMLElement && child.dataset.rightbarCol !== void 0) {
|
|
3283
|
+
right = child;
|
|
3284
|
+
rightIdx = i;
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
rightEl = right;
|
|
3288
|
+
if (right !== null) {
|
|
3289
|
+
sidebarEl = children[rightIdx - 2] ?? null;
|
|
3290
|
+
centerEl = children[rightIdx - 1] ?? null;
|
|
3021
3291
|
} else {
|
|
3022
|
-
sidebarEl =
|
|
3023
|
-
centerEl =
|
|
3292
|
+
sidebarEl = children[overlayIdx - 3] ?? null;
|
|
3293
|
+
centerEl = children[overlayIdx - 2] ?? null;
|
|
3024
3294
|
}
|
|
3025
3295
|
}
|
|
3026
3296
|
function setBlur(el, px) {
|
|
3027
3297
|
if (el === null) return;
|
|
3298
|
+
if (px === 0 && !el.classList.contains(PART_BLUR_CLASS) && el.getAttribute("data-dab-pos-patched") !== "1") return;
|
|
3028
3299
|
const underlay = el.querySelector(`:scope > .${PART_UNDERLAY_CLASS}`);
|
|
3029
3300
|
if (px > 0) {
|
|
3030
3301
|
ensurePartBlurStyle();
|
|
@@ -3092,6 +3363,7 @@ window.__ModuleLoader__.load({
|
|
|
3092
3363
|
applyInputBlur(blurs.input);
|
|
3093
3364
|
applyPanelBlur(blurs.panel);
|
|
3094
3365
|
applyProduced();
|
|
3366
|
+
applyHeaderPopovers();
|
|
3095
3367
|
applyViewCards();
|
|
3096
3368
|
}
|
|
3097
3369
|
/** Produced/artifact surfaces (conversation code blocks + their banner +
|
|
@@ -3132,6 +3404,10 @@ window.__ModuleLoader__.load({
|
|
|
3132
3404
|
applyProduced();
|
|
3133
3405
|
return;
|
|
3134
3406
|
}
|
|
3407
|
+
if (part === "header") {
|
|
3408
|
+
applyHeaderPopovers();
|
|
3409
|
+
return;
|
|
3410
|
+
}
|
|
3135
3411
|
if (part === "chat" || part === "trajectory") {
|
|
3136
3412
|
applyViewCards();
|
|
3137
3413
|
return;
|
|
@@ -3158,6 +3434,34 @@ window.__ModuleLoader__.load({
|
|
|
3158
3434
|
plain: true
|
|
3159
3435
|
}];
|
|
3160
3436
|
const viewTargets = VIEW_CARDS.map(() => null);
|
|
3437
|
+
/** View specs whose real host marker has been seen at least once. Until a
|
|
3438
|
+
* marker is seen, its absence is ambiguous — "that view is not mounted" or
|
|
3439
|
+
* "this build has no such marker, keep running the generic fallback" — and the
|
|
3440
|
+
* pass has to keep probing. Once seen, absence can only mean the former, so the
|
|
3441
|
+
* pass may be skipped.
|
|
3442
|
+
*
|
|
3443
|
+
* This matters because the two specs are two different MAIN VIEWS and only one
|
|
3444
|
+
* of them is mounted at a time (the host renders the main slot for the active
|
|
3445
|
+
* panel id, keyed by `entryKey` — see AppFrame's MainPanel). So "every surface
|
|
3446
|
+
* present" is never true during a normal session, and an all-or-nothing gate on
|
|
3447
|
+
* it silently never engages: that is exactly how the previous version of this
|
|
3448
|
+
* observer ended up running the full pass on every animation frame while
|
|
3449
|
+
* tokens streamed. */
|
|
3450
|
+
const markerSeen = /* @__PURE__ */ new Set();
|
|
3451
|
+
/** Resolve just the stable host markers for the view specs — one querySelector
|
|
3452
|
+
* each, no fallback heuristics. Cheap enough to run on every coalesced burst,
|
|
3453
|
+
* and enough to notice a view mounting/unmounting. */
|
|
3454
|
+
function probeViewMarkers() {
|
|
3455
|
+
const center = centerEl;
|
|
3456
|
+
if (center === null || !document.body.contains(center)) return VIEW_CARDS.map(() => null);
|
|
3457
|
+
return VIEW_CARDS.map((spec) => center.querySelector(spec.sel));
|
|
3458
|
+
}
|
|
3459
|
+
/** Whether the pass must run regardless of the value/identity signature. */
|
|
3460
|
+
function needsPartsPass(markers) {
|
|
3461
|
+
if (!columnsPresent()) return true;
|
|
3462
|
+
if (markers.some((el, i) => el !== null && el !== viewTargets[i])) return true;
|
|
3463
|
+
return VIEW_CARDS.some((spec, i) => spec.fallback === true && markers[i] === null && !markerSeen.has(i));
|
|
3464
|
+
}
|
|
3161
3465
|
function isScrollableY(el) {
|
|
3162
3466
|
const oy = getComputedStyle(el).overflowY;
|
|
3163
3467
|
return oy === "auto" || oy === "scroll" || oy === "overlay";
|
|
@@ -3202,6 +3506,7 @@ window.__ModuleLoader__.load({
|
|
|
3202
3506
|
const marked = centerEl.querySelector(spec.sel);
|
|
3203
3507
|
const cached = viewTargets[idx];
|
|
3204
3508
|
if (marked !== null) {
|
|
3509
|
+
markerSeen.add(idx);
|
|
3205
3510
|
if (cached !== null && cached !== marked) {
|
|
3206
3511
|
setBlur(cached, 0);
|
|
3207
3512
|
restoreCardHost(cached, spec.mark, spec.prev, spec.plain === true);
|
|
@@ -3337,15 +3642,48 @@ window.__ModuleLoader__.load({
|
|
|
3337
3642
|
syncTableFix();
|
|
3338
3643
|
}
|
|
3339
3644
|
let partsObserver = null;
|
|
3645
|
+
let partsApplyRaf = 0;
|
|
3646
|
+
/** Signature of the last blur+opacity pass we actually wrote to the DOM. */
|
|
3647
|
+
let appliedPartsKey = "";
|
|
3648
|
+
/** The surfaces the last pass styled, compared BY IDENTITY: the host can swap
|
|
3649
|
+
* a view element for a fresh one while the slider values stay identical, and
|
|
3650
|
+
* the value signature alone would then skip the pass and leave the new view
|
|
3651
|
+
* unstyled until a slider moves. */
|
|
3652
|
+
let appliedTargets = [];
|
|
3653
|
+
/** The four frame columns are mounted and still attached. These are the frame's
|
|
3654
|
+
* own children (see the host's AppFrame: sidebar / center / rightbar carrying
|
|
3655
|
+
* `data-rightbar-col` / the overlay), so they exist as soon as the shell does —
|
|
3656
|
+
* unlike the per-view cards below, which come and go with the active view. */
|
|
3657
|
+
function columnsPresent() {
|
|
3658
|
+
return frameEl !== null && sidebarEl !== null && centerEl !== null && rightEl !== null && document.body.contains(frameEl) && document.body.contains(centerEl);
|
|
3659
|
+
}
|
|
3660
|
+
/** Snapshot of every surface the pass would style, for the identity check. */
|
|
3661
|
+
function targetsNow() {
|
|
3662
|
+
return [
|
|
3663
|
+
frameEl,
|
|
3664
|
+
sidebarEl,
|
|
3665
|
+
centerEl,
|
|
3666
|
+
rightEl,
|
|
3667
|
+
...viewTargets
|
|
3668
|
+
];
|
|
3669
|
+
}
|
|
3340
3670
|
/** Watch for the AppFrame mounting so persisted blurs land even when the shell
|
|
3341
|
-
* renders after this plugin's apply.
|
|
3342
|
-
* callback returns. */
|
|
3671
|
+
* renders after this plugin's apply. */
|
|
3343
3672
|
function watchParts() {
|
|
3344
3673
|
if (partsObserver !== null || typeof MutationObserver === "undefined") return;
|
|
3345
3674
|
partsObserver = new MutationObserver(() => {
|
|
3346
|
-
if (
|
|
3347
|
-
|
|
3348
|
-
|
|
3675
|
+
if (partsApplyRaf !== 0) return;
|
|
3676
|
+
partsApplyRaf = requestAnimationFrame(() => {
|
|
3677
|
+
partsApplyRaf = 0;
|
|
3678
|
+
const blurs = rBlurs();
|
|
3679
|
+
const ops = rOps();
|
|
3680
|
+
const key = JSON.stringify([blurs, ops]);
|
|
3681
|
+
if (!needsPartsPass(probeViewMarkers()) && key === appliedPartsKey && !targetsNow().some((el, i) => el !== appliedTargets[i])) return;
|
|
3682
|
+
applyPartBlurs(blurs);
|
|
3683
|
+
applyPartOpacities(ops);
|
|
3684
|
+
appliedPartsKey = key;
|
|
3685
|
+
appliedTargets = targetsNow();
|
|
3686
|
+
});
|
|
3349
3687
|
});
|
|
3350
3688
|
partsObserver.observe(document.body, {
|
|
3351
3689
|
childList: true,
|
|
@@ -3355,6 +3693,13 @@ window.__ModuleLoader__.load({
|
|
|
3355
3693
|
function stopWatchingParts() {
|
|
3356
3694
|
partsObserver?.disconnect();
|
|
3357
3695
|
partsObserver = null;
|
|
3696
|
+
if (partsApplyRaf !== 0) {
|
|
3697
|
+
cancelAnimationFrame(partsApplyRaf);
|
|
3698
|
+
partsApplyRaf = 0;
|
|
3699
|
+
}
|
|
3700
|
+
appliedPartsKey = "";
|
|
3701
|
+
appliedTargets = [];
|
|
3702
|
+
markerSeen.clear();
|
|
3358
3703
|
}
|
|
3359
3704
|
let themeObserver = null;
|
|
3360
3705
|
let themeRaf = 0;
|
|
@@ -3507,6 +3852,9 @@ window.__ModuleLoader__.load({
|
|
|
3507
3852
|
* be re-registered through the host theme service. */
|
|
3508
3853
|
function onVerdictApplied(cb) {
|
|
3509
3854
|
verdictListener = cb;
|
|
3855
|
+
return () => {
|
|
3856
|
+
if (verdictListener === cb) verdictListener = null;
|
|
3857
|
+
};
|
|
3510
3858
|
}
|
|
3511
3859
|
function applyVerdict(dark) {
|
|
3512
3860
|
if (rBgDark() === dark) return;
|
|
@@ -3634,6 +3982,8 @@ window.__ModuleLoader__.load({
|
|
|
3634
3982
|
clearVideoEl();
|
|
3635
3983
|
wpEl?.remove();
|
|
3636
3984
|
wpEl = null;
|
|
3985
|
+
wpVerdict = null;
|
|
3986
|
+
setBgDark(null);
|
|
3637
3987
|
}
|
|
3638
3988
|
} else if (cfg.backgroundType !== "image" && cfg.generatedBg) {
|
|
3639
3989
|
clearVideoEl();
|
|
@@ -3659,6 +4009,9 @@ window.__ModuleLoader__.load({
|
|
|
3659
4009
|
applyTrajectoryOverrides(rTrajectoryOpacity());
|
|
3660
4010
|
}
|
|
3661
4011
|
applyPanelOverrides(rPanelOpacity());
|
|
4012
|
+
applyProduced();
|
|
4013
|
+
applyHeaderPopovers();
|
|
4014
|
+
applyExemptDefaults();
|
|
3662
4015
|
applyPartBlurs(rBlurs());
|
|
3663
4016
|
applyStrokes();
|
|
3664
4017
|
}
|
|
@@ -3695,6 +4048,11 @@ window.__ModuleLoader__.load({
|
|
|
3695
4048
|
document.documentElement.style.removeProperty("--dsh-any-blur-panel");
|
|
3696
4049
|
document.documentElement.style.removeProperty("--dsh-any-blur-prod");
|
|
3697
4050
|
document.documentElement.style.removeProperty("--dsh-any-prod-pct");
|
|
4051
|
+
document.documentElement.style.removeProperty("--dsh-any-blur-header");
|
|
4052
|
+
headerStyleEl?.remove();
|
|
4053
|
+
headerStyleEl = null;
|
|
4054
|
+
exemptStyleEl?.remove();
|
|
4055
|
+
exemptStyleEl = null;
|
|
3698
4056
|
for (const v of Object.values(OPACITY_VARS)) document.documentElement.style.removeProperty(v);
|
|
3699
4057
|
baseTokenKey = "";
|
|
3700
4058
|
lastBgKey = "";
|
|
@@ -3705,8 +4063,14 @@ window.__ModuleLoader__.load({
|
|
|
3705
4063
|
pendingOps = null;
|
|
3706
4064
|
tableFixStyleEl?.remove();
|
|
3707
4065
|
tableFixStyleEl = null;
|
|
4066
|
+
partBlurStyleEl?.remove();
|
|
4067
|
+
partBlurStyleEl = null;
|
|
3708
4068
|
removeStrokes();
|
|
3709
4069
|
removeFontFace();
|
|
4070
|
+
imgNat = null;
|
|
4071
|
+
lowResUrl = null;
|
|
4072
|
+
lowResFor = "";
|
|
4073
|
+
dragLow = false;
|
|
3710
4074
|
setBlur(frameEl, 0);
|
|
3711
4075
|
setBlur(sidebarEl, 0);
|
|
3712
4076
|
setBlur(centerEl, 0);
|
|
@@ -3842,7 +4206,10 @@ window.__ModuleLoader__.load({
|
|
|
3842
4206
|
/* ── wheel card ──────────────────────────────────────────────────────────── */
|
|
3843
4207
|
.dab-wheel-card{position:relative;display:flex;align-items:center;justify-content:center;gap:28px;flex-wrap:wrap;padding:24px 18px}
|
|
3844
4208
|
.dab-wheel-glow{position:absolute;width:230px;height:230px;border-radius:50%;filter:blur(48px);opacity:.2;background:var(--c,#888);pointer-events:none;transition:background .4s ease}
|
|
3845
|
-
|
|
4209
|
+
/* flex:none is load bearing, not cosmetic: the pointer maths reads client
|
|
4210
|
+
* coordinates straight off this element's box, so any shrink below the 220px
|
|
4211
|
+
* drawing surface would desynchronise the ring from the cursor. */
|
|
4212
|
+
.dab-wheel{position:relative;flex:none;cursor:crosshair;border-radius:50%;box-shadow:0 12px 32px -14px rgba(0,0,0,.4)}
|
|
3846
4213
|
.dab-hint{font-size:11.5px;line-height:1.55;color:var(--dsw-alias-label-tertiary);padding:0 4px}
|
|
3847
4214
|
|
|
3848
4215
|
/* ── precise color inputs ────────────────────────────────────────────────── */
|
|
@@ -3896,7 +4263,7 @@ window.__ModuleLoader__.load({
|
|
|
3896
4263
|
.dab-slider::-moz-range-thumb{width:13px;height:13px;border-radius:50%;background:var(--dsw-alias-button-elevated-fill,#fff);border:2px solid var(--dsw-alias-brand-primary)}
|
|
3897
4264
|
|
|
3898
4265
|
/* ── interface part cards ────────────────────────────────────────────────── */
|
|
3899
|
-
.dab-grid-parts{display:grid;grid-template-columns:repeat(auto-fill,minmax(
|
|
4266
|
+
.dab-grid-parts{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(240px,100%),1fr));gap:13px}
|
|
3900
4267
|
.dab-part-head{display:flex;align-items:center;gap:11px;margin-bottom:14px}
|
|
3901
4268
|
.dab-part-ico{width:32px;height:32px;flex:none;border-radius:10px;display:grid;place-items:center;color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-bg-layer-2);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 12%,transparent)}
|
|
3902
4269
|
.dab-part-name{font-size:13.5px;font-weight:600}
|
|
@@ -3973,7 +4340,7 @@ window.__ModuleLoader__.load({
|
|
|
3973
4340
|
.dab-spin{animation:dab-rotate .7s cubic-bezier(.3,.7,.3,1) 1}
|
|
3974
4341
|
|
|
3975
4342
|
/* ── profile page ────────────────────────────────────────────────────────── */
|
|
3976
|
-
.dab-profile-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(
|
|
4343
|
+
.dab-profile-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(220px,100%),1fr));gap:13px}
|
|
3977
4344
|
.dab-profile-ico{width:38px;height:38px;border-radius:11px;display:grid;place-items:center;margin-bottom:13px;color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-bg-layer-2);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 12%,transparent)}
|
|
3978
4345
|
.dab-profile-title{font-size:14px;font-weight:650}
|
|
3979
4346
|
.dab-profile-desc{font-size:12px;color:var(--dsw-alias-label-tertiary);line-height:1.55;margin:5px 0 15px}
|
|
@@ -3982,7 +4349,7 @@ window.__ModuleLoader__.load({
|
|
|
3982
4349
|
|
|
3983
4350
|
/* ── presets / profiles / schedule / rotation ────────────────────────────── */
|
|
3984
4351
|
.dab-row-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
|
|
3985
|
-
.dab-preset-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px}
|
|
4352
|
+
.dab-preset-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(150px,100%),1fr));gap:10px}
|
|
3986
4353
|
.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;animation:dab-row-in .34s cubic-bezier(.22,1,.36,1) both;animation-delay:calc(var(--i,0) * 45ms)}
|
|
3987
4354
|
.dab-preset:hover{transform:translateY(-2px);border-color:var(--dsw-alias-brand-primary);box-shadow:0 8px 22px -12px rgba(0,0,0,.3)}
|
|
3988
4355
|
.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)}
|
|
@@ -4009,7 +4376,7 @@ window.__ModuleLoader__.load({
|
|
|
4009
4376
|
.dab-schedule-wrap.is-open{grid-template-rows:1fr;visibility:visible;transition:grid-template-rows .42s cubic-bezier(.22,1,.36,1)}
|
|
4010
4377
|
.dab-schedule-clip{overflow:hidden;min-height:0;opacity:0;transition:opacity .28s ease}
|
|
4011
4378
|
.dab-schedule-wrap.is-open .dab-schedule-clip{opacity:1;transition:opacity .32s ease .1s}
|
|
4012
|
-
.dab-schedule-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;padding-top:12px}
|
|
4379
|
+
.dab-schedule-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(220px,100%),1fr));gap:16px;padding-top:12px}
|
|
4013
4380
|
.dab-schedule-cell{min-width:0;animation:dab-row-in .34s cubic-bezier(.22,1,.36,1) both;animation-delay:calc(var(--i,0) * 60ms)}
|
|
4014
4381
|
.dab-time-row{display:flex;flex-direction:column;gap:9px}
|
|
4015
4382
|
.dab-time-label{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary))}
|
|
@@ -4045,6 +4412,9 @@ window.__ModuleLoader__.load({
|
|
|
4045
4412
|
.dab-crash{display:flex;flex-direction:column;gap:10px;align-items:center;padding:28px 18px;border:1px solid var(--dsw-alias-border-l2);border-radius:16px}
|
|
4046
4413
|
.dab-crash-title{font-size:15px;font-weight:650}
|
|
4047
4414
|
.dab-crash-desc{font-size:12px;color:var(--dsw-alias-label-tertiary);text-align:center;line-height:1.5}
|
|
4415
|
+
/* The crash cause: readable, scrollable, and clipped to a few lines so a long
|
|
4416
|
+
* stack cannot push the reset button off the panel. */
|
|
4417
|
+
.dab-crash-detail{margin:0;max-width:min(520px,80vw);max-height:132px;overflow:auto;padding:8px 10px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);font-family:var(--dab-mono);font-size:10.5px;line-height:1.5;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary));text-align:left;white-space:pre-wrap;word-break:break-word}
|
|
4048
4418
|
|
|
4049
4419
|
/* ── keyframes ───────────────────────────────────────────────────────────── */
|
|
4050
4420
|
@keyframes dab-fade-in{from{opacity:0}to{opacity:1}}
|
|
@@ -4060,6 +4430,72 @@ window.__ModuleLoader__.load({
|
|
|
4060
4430
|
@keyframes dab-zoom-in{from{opacity:0;transform:scale(.94) translateY(8px)}to{opacity:1;transform:none}}
|
|
4061
4431
|
@keyframes dab-flow{to{background-position:300% 50%}}
|
|
4062
4432
|
|
|
4433
|
+
/* ── sidebar surface (a dsh-better-sidebar page) ─────────────────────────────
|
|
4434
|
+
* Same shell, hosted in a panel that is handed a full-height column (the host's
|
|
4435
|
+
* native tab body is height:100%) and whose width the user drags freely. So only
|
|
4436
|
+
* THREE things are fixed here, all of them structural:
|
|
4437
|
+
* 1. the shell fills the height and the page body owns the scroll
|
|
4438
|
+
* (flex:1 + min-height:0), so the panel stays one scrollable column instead
|
|
4439
|
+
* of pushing the whole tab into a scrollbar;
|
|
4440
|
+
* 2. the nav is a rail ABOVE the page rather than beside it — a side rail would
|
|
4441
|
+
* spend 150px of a panel that is narrow by nature, and this way the page
|
|
4442
|
+
* keeps the full width for its own grids;
|
|
4443
|
+
* 3. long tokens (paths, hex, URLs) may wrap instead of widening the panel.
|
|
4444
|
+
*
|
|
4445
|
+
* Everything else — grid columns, paddings, type scale, whether the cards lift on
|
|
4446
|
+
* hover — is width-driven through the container queries below, which measure this
|
|
4447
|
+
* shell (the .dab-root container, which declares container-type:inline-size) and
|
|
4448
|
+
* not the viewport. A narrow sidebar compacts; a wide one falls back to the
|
|
4449
|
+
* dialog's own spacing and multi-column grids, because at that point it *is* a
|
|
4450
|
+
* dialog-sized surface. */
|
|
4451
|
+
[data-dab-surface="sidebar"].dab-root{height:100%;min-height:0;padding:12px 12px 0}
|
|
4452
|
+
[data-dab-surface="sidebar"] .dab-shell{display:flex;flex-direction:column;align-items:stretch;gap:11px;height:100%;min-height:0;max-width:none;padding-bottom:0}
|
|
4453
|
+
[data-dab-surface="sidebar"] .dab-nav{gap:0}
|
|
4454
|
+
[data-dab-surface="sidebar"] .dab-nav-list{flex-direction:row;gap:2px;overflow-x:auto;overflow-y:hidden;padding-bottom:1px}
|
|
4455
|
+
[data-dab-surface="sidebar"] .dab-nav-list::-webkit-scrollbar{display:none}
|
|
4456
|
+
[data-dab-surface="sidebar"] .dab-nav-ind{display:none}
|
|
4457
|
+
[data-dab-surface="sidebar"] .dab-nav-item{flex:none;height:32px;gap:7px;padding:0 10px;font-size:12px}
|
|
4458
|
+
[data-dab-surface="sidebar"] .dab-nav-item.is-active{background:var(--dsw-alias-bg-layer-2)}
|
|
4459
|
+
[data-dab-surface="sidebar"] .dab-page{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding-bottom:16px;scrollbar-width:thin}
|
|
4460
|
+
/* Last-resort safety for text that cannot wrap (paths, hex, URLs). */
|
|
4461
|
+
[data-dab-surface="sidebar"] .dab-hint,[data-dab-surface="sidebar"] .dab-part-name{overflow-wrap:anywhere}
|
|
4462
|
+
/* The panel's width is whatever the user dragged it to, so let the part cards
|
|
4463
|
+
* fill as many columns as actually fit instead of hard-coding one. 170px is the
|
|
4464
|
+
* narrowest a part card stays readable once its header can wrap (see the head
|
|
4465
|
+
* rules below), which puts the second column at roughly a 380px panel — the
|
|
4466
|
+
* default right-sidebar width. */
|
|
4467
|
+
[data-dab-surface="sidebar"] .dab-grid-parts{grid-template-columns:repeat(auto-fill,minmax(min(170px,100%),1fr));gap:12px}
|
|
4468
|
+
[data-dab-surface="sidebar"] .dab-types{grid-template-columns:repeat(auto-fit,minmax(min(150px,100%),1fr))}
|
|
4469
|
+
[data-dab-surface="sidebar"] .dab-card{padding:12px}
|
|
4470
|
+
[data-dab-surface="sidebar"] .dab-part-head{gap:8px;margin-bottom:10px}
|
|
4471
|
+
[data-dab-surface="sidebar"] .dab-part-ico{width:26px;height:26px;border-radius:8px}
|
|
4472
|
+
[data-dab-surface="sidebar"] .dab-part-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12.5px}
|
|
4473
|
+
[data-dab-surface="sidebar"] .dab-part-badge{padding:2px 7px;font-size:10px}
|
|
4474
|
+
|
|
4475
|
+
/* Narrow panel: tighten the chrome so the content still has room. */
|
|
4476
|
+
@container (max-width:539px){
|
|
4477
|
+
[data-dab-surface="sidebar"] .dab-page{gap:11px}
|
|
4478
|
+
[data-dab-surface="sidebar"] .dab-h1{font-size:17px}
|
|
4479
|
+
[data-dab-surface="sidebar"] .dab-desc{font-size:12px}
|
|
4480
|
+
[data-dab-surface="sidebar"] .dab-orb-wrap{width:96px;height:96px}
|
|
4481
|
+
[data-dab-surface="sidebar"] .dab-wheel-card{padding:14px 10px;gap:16px}
|
|
4482
|
+
[data-dab-surface="sidebar"] .dab-wheel-glow{width:170px;height:170px}
|
|
4483
|
+
/* Lift-on-hover is a dialog affordance; in a cramped scrolling panel it only
|
|
4484
|
+
* adds jitter under the cursor. */
|
|
4485
|
+
[data-dab-surface="sidebar"] .dab-card-hover:hover{transform:none;box-shadow:none}
|
|
4486
|
+
}
|
|
4487
|
+
/* Roomy panel: behave like the dialog again — its spacing, its type scale, and
|
|
4488
|
+
* multi-column grids (a smaller track minimum than the dialog, because the page
|
|
4489
|
+
* here is a few hundred px narrower than a dialog's content column). */
|
|
4490
|
+
@container (min-width:540px){
|
|
4491
|
+
[data-dab-surface="sidebar"] .dab-shell{gap:16px}
|
|
4492
|
+
[data-dab-surface="sidebar"] .dab-page{gap:13px}
|
|
4493
|
+
[data-dab-surface="sidebar"] .dab-nav-item{height:34px;font-size:12.5px;padding:0 12px}
|
|
4494
|
+
/* The shared ≤620px rule flattens the type cards to one column, which is right
|
|
4495
|
+
* for a narrow phone-width dialog but wasteful in a roomy panel. */
|
|
4496
|
+
[data-dab-surface="sidebar"] .dab-types{grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}
|
|
4497
|
+
}
|
|
4498
|
+
|
|
4063
4499
|
/* ── responsive & motion preferences ─────────────────────────────────────── */
|
|
4064
4500
|
@container (max-width:620px){
|
|
4065
4501
|
.dab-shell{grid-template-columns:1fr;gap:14px}
|
|
@@ -4074,6 +4510,30 @@ window.__ModuleLoader__.load({
|
|
|
4074
4510
|
@container (min-width:621px){
|
|
4075
4511
|
.dab-shell{grid-template-columns:158px minmax(0,1fr);gap:26px}
|
|
4076
4512
|
}
|
|
4513
|
+
/* Phone: on a handset the settings dialog's content column shrinks to roughly
|
|
4514
|
+
* the viewport, and the dialog-sized chrome (18px cards, 21px headings, a 118px
|
|
4515
|
+
* orb, 24px paddings) stops fitting — cards end up wider than the column or the
|
|
4516
|
+
* color row wraps into three cramped pieces. These are the same tightenings the
|
|
4517
|
+
* sidebar surface applies at its own narrow widths, so a phone settings dialog
|
|
4518
|
+
* and a narrow sidebar panel end up looking the same. */
|
|
4519
|
+
@container (max-width:480px){
|
|
4520
|
+
.dab-shell{gap:10px}
|
|
4521
|
+
.dab-page{gap:10px}
|
|
4522
|
+
.dab-head{margin:0 0 2px}
|
|
4523
|
+
.dab-h1{font-size:18px}
|
|
4524
|
+
.dab-desc{font-size:12px}
|
|
4525
|
+
.dab-card{padding:14px;border-radius:14px}
|
|
4526
|
+
.dab-hero-accent{gap:16px}
|
|
4527
|
+
.dab-orb-wrap{width:92px;height:92px}
|
|
4528
|
+
.dab-wheel-card{padding:14px 10px;gap:16px}
|
|
4529
|
+
.dab-wheel-glow{width:180px;height:180px}
|
|
4530
|
+
/* Three fixed type-card columns need ~400px; two adaptive ones fit 320px. */
|
|
4531
|
+
.dab-types{grid-template-columns:repeat(auto-fit,minmax(min(150px,100%),1fr))}
|
|
4532
|
+
.dab-grid-parts{gap:11px}
|
|
4533
|
+
.dab-part-head{margin-bottom:10px}
|
|
4534
|
+
.dab-slider-block + .dab-slider-block{margin-top:10px}
|
|
4535
|
+
.dab-footer{padding-top:10px}
|
|
4536
|
+
}
|
|
4077
4537
|
@media (prefers-reduced-motion:reduce){
|
|
4078
4538
|
.dab-root *,.dab-root *::before,.dab-root *::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}
|
|
4079
4539
|
}
|
|
@@ -4404,25 +4864,32 @@ window.__ModuleLoader__.load({
|
|
|
4404
4864
|
this.props.onReset?.();
|
|
4405
4865
|
};
|
|
4406
4866
|
render() {
|
|
4407
|
-
if (this.state.error)
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4867
|
+
if (this.state.error) {
|
|
4868
|
+
const detail = this.state.error.stack ?? `${this.state.error.name}: ${this.state.error.message}`;
|
|
4869
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4870
|
+
className: "dab-crash",
|
|
4871
|
+
children: [
|
|
4872
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4873
|
+
className: "dab-crash-title",
|
|
4874
|
+
children: this.props.t("crashTitle")
|
|
4875
|
+
}),
|
|
4876
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4877
|
+
className: "dab-crash-desc",
|
|
4878
|
+
children: this.props.t("crashDesc")
|
|
4879
|
+
}),
|
|
4880
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
4881
|
+
className: "dab-crash-detail",
|
|
4882
|
+
children: detail
|
|
4883
|
+
}),
|
|
4884
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4885
|
+
type: "button",
|
|
4886
|
+
className: "dab-btn",
|
|
4887
|
+
onClick: this.reset,
|
|
4888
|
+
children: this.props.t("crashReset")
|
|
4889
|
+
})
|
|
4890
|
+
]
|
|
4891
|
+
});
|
|
4892
|
+
}
|
|
4426
4893
|
return this.props.children;
|
|
4427
4894
|
}
|
|
4428
4895
|
};
|
|
@@ -4571,6 +5038,10 @@ window.__ModuleLoader__.load({
|
|
|
4571
5038
|
}, [col]);
|
|
4572
5039
|
const pendingRef = (0, react.useRef)(null);
|
|
4573
5040
|
const rafRef = (0, react.useRef)(null);
|
|
5041
|
+
/** Unmount-time cleanup for a drag that never saw its mouseup (panel closed
|
|
5042
|
+
* mid-drag): without it the document listeners stay attached forever and the
|
|
5043
|
+
* pending rAF keeps calling setState on a dead component. */
|
|
5044
|
+
const dragCleanupRef = (0, react.useRef)(null);
|
|
4574
5045
|
const flushPending = (0, react.useCallback)(() => {
|
|
4575
5046
|
rafRef.current = null;
|
|
4576
5047
|
const p = pendingRef.current;
|
|
@@ -4612,13 +5083,25 @@ window.__ModuleLoader__.load({
|
|
|
4612
5083
|
schedule(colRef.current.hue, s, l);
|
|
4613
5084
|
}
|
|
4614
5085
|
};
|
|
4615
|
-
const
|
|
5086
|
+
const stopDrag = () => {
|
|
4616
5087
|
document.removeEventListener("mousemove", onMove);
|
|
4617
|
-
document.removeEventListener("mouseup",
|
|
5088
|
+
document.removeEventListener("mouseup", stopDrag);
|
|
5089
|
+
dragCleanupRef.current = null;
|
|
4618
5090
|
};
|
|
5091
|
+
dragCleanupRef.current = stopDrag;
|
|
4619
5092
|
document.addEventListener("mousemove", onMove);
|
|
4620
|
-
document.addEventListener("mouseup",
|
|
5093
|
+
document.addEventListener("mouseup", stopDrag);
|
|
4621
5094
|
}, [schedule]);
|
|
5095
|
+
(0, react.useEffect)(() => {
|
|
5096
|
+
return () => {
|
|
5097
|
+
if (rafRef.current !== null) {
|
|
5098
|
+
cancelAnimationFrame(rafRef.current);
|
|
5099
|
+
rafRef.current = null;
|
|
5100
|
+
}
|
|
5101
|
+
pendingRef.current = null;
|
|
5102
|
+
dragCleanupRef.current?.();
|
|
5103
|
+
};
|
|
5104
|
+
}, []);
|
|
4622
5105
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("canvas", {
|
|
4623
5106
|
ref: cvsRef,
|
|
4624
5107
|
width: WHEEL_SIZE,
|
|
@@ -4667,6 +5150,7 @@ window.__ModuleLoader__.load({
|
|
|
4667
5150
|
},
|
|
4668
5151
|
onChange: (e) => {
|
|
4669
5152
|
setText(e.target.value);
|
|
5153
|
+
if (e.target.value === "") return;
|
|
4670
5154
|
const v = Number(e.target.value);
|
|
4671
5155
|
if (Number.isFinite(v)) onChange(clamp(v, min, max));
|
|
4672
5156
|
}
|
|
@@ -4783,9 +5267,6 @@ window.__ModuleLoader__.load({
|
|
|
4783
5267
|
//#region src/client/components/ColorPicker.tsx
|
|
4784
5268
|
const MAG_SIZE = 96;
|
|
4785
5269
|
const MAG_ZOOM = 8;
|
|
4786
|
-
function toHex$1(rgb) {
|
|
4787
|
-
return "#" + rgb.map((v) => v.toString(16).padStart(2, "0")).join("");
|
|
4788
|
-
}
|
|
4789
5270
|
/**
|
|
4790
5271
|
* Eyedropper modal: shows the wallpaper full-bleed (no drag/zoom) and lets the
|
|
4791
5272
|
* user click any pixel to adopt it as the theme color. A magnifier circle
|
|
@@ -4815,7 +5296,7 @@ window.__ModuleLoader__.load({
|
|
|
4815
5296
|
canvas.height = imgRef.current.naturalHeight;
|
|
4816
5297
|
const ctx = canvas.getContext("2d");
|
|
4817
5298
|
if (ctx) ctx.drawImage(imgRef.current, 0, 0);
|
|
4818
|
-
}, [ready]);
|
|
5299
|
+
}, [ready, url]);
|
|
4819
5300
|
const sampleAt = (0, react.useCallback)((clientX, clientY) => {
|
|
4820
5301
|
const canvas = canvasRef.current;
|
|
4821
5302
|
if (!canvas) return null;
|
|
@@ -4930,14 +5411,14 @@ window.__ModuleLoader__.load({
|
|
|
4930
5411
|
height: 28,
|
|
4931
5412
|
borderRadius: 8,
|
|
4932
5413
|
border: "1px solid rgba(255,255,255,0.4)",
|
|
4933
|
-
background: hover ?
|
|
5414
|
+
background: hover ? rgbToHex(hover.rgb) : "transparent"
|
|
4934
5415
|
} }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4935
5416
|
style: {
|
|
4936
5417
|
color: "#fff",
|
|
4937
5418
|
fontSize: 13,
|
|
4938
5419
|
fontFamily: "var(--dab-mono, monospace)"
|
|
4939
5420
|
},
|
|
4940
|
-
children: hover ? `${
|
|
5421
|
+
children: hover ? `${rgbToHex(hover.rgb)} · rgb(${hover.rgb.join(", ")})` : "—"
|
|
4941
5422
|
})]
|
|
4942
5423
|
}),
|
|
4943
5424
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -5020,9 +5501,6 @@ window.__ModuleLoader__.load({
|
|
|
5020
5501
|
.58
|
|
5021
5502
|
]
|
|
5022
5503
|
];
|
|
5023
|
-
function toHex(rgb) {
|
|
5024
|
-
return "#" + rgb.map((v) => Math.round(v).toString(16).padStart(2, "0")).join("");
|
|
5025
|
-
}
|
|
5026
5504
|
function ColorPage({ p, notify }) {
|
|
5027
5505
|
const { t, hue, sat, lit, setColor, extractColor, setSchemeOverride, useStore } = p;
|
|
5028
5506
|
const storeUrl = useStore((s) => s.url);
|
|
@@ -5037,7 +5515,7 @@ window.__ModuleLoader__.load({
|
|
|
5037
5515
|
];
|
|
5038
5516
|
const [h, s, l] = hsvToHsl(wheel[0], wheel[1], wheel[2]);
|
|
5039
5517
|
const [r, g, b] = hslToRgb(h, s, l);
|
|
5040
|
-
const hex =
|
|
5518
|
+
const hex = rgbToHex([
|
|
5041
5519
|
r,
|
|
5042
5520
|
g,
|
|
5043
5521
|
b
|
|
@@ -5167,7 +5645,7 @@ window.__ModuleLoader__.load({
|
|
|
5167
5645
|
type: "button",
|
|
5168
5646
|
className: `dab-swatch${on ? " is-on" : ""}`,
|
|
5169
5647
|
style: { background: `hsl(${sh} ${Math.round(ss * 100)}% ${Math.round(sl * 100)}%)` },
|
|
5170
|
-
title:
|
|
5648
|
+
title: rgbToHex(hslToRgb(sh, ss, sl)).toUpperCase(),
|
|
5171
5649
|
onClick: () => setColor(...hslToHsv(sh, ss, sl))
|
|
5172
5650
|
}, i);
|
|
5173
5651
|
})
|
|
@@ -5242,15 +5720,16 @@ window.__ModuleLoader__.load({
|
|
|
5242
5720
|
const paint = (el, v) => {
|
|
5243
5721
|
el.style.setProperty("--pct", String((v - min) / (max - min) * 100));
|
|
5244
5722
|
};
|
|
5723
|
+
const fmtRef = (0, react.useRef)(fmt);
|
|
5724
|
+
fmtRef.current = fmt;
|
|
5245
5725
|
(0, react.useEffect)(() => {
|
|
5246
5726
|
if (inputRef.current) {
|
|
5247
5727
|
inputRef.current.value = String(def);
|
|
5248
5728
|
paint(inputRef.current, def);
|
|
5249
5729
|
}
|
|
5250
|
-
if (valRef.current) valRef.current.textContent =
|
|
5730
|
+
if (valRef.current) valRef.current.textContent = fmtRef.current(def);
|
|
5251
5731
|
}, [
|
|
5252
5732
|
def,
|
|
5253
|
-
fmt,
|
|
5254
5733
|
min,
|
|
5255
5734
|
max
|
|
5256
5735
|
]);
|
|
@@ -5319,12 +5798,16 @@ window.__ModuleLoader__.load({
|
|
|
5319
5798
|
* signal and the body childList observer fires exactly when it appears. The
|
|
5320
5799
|
* panel surfaces we actually style are listed only as reinforcement: they are
|
|
5321
5800
|
* conditionally rendered (collapsed panels drop them), so they must never be
|
|
5322
|
-
* the sole signal.
|
|
5801
|
+
* the sole signal.
|
|
5802
|
+
*
|
|
5803
|
+
* `[data-sidebar-right-panel]` is deliberately ABSENT: on every host generation
|
|
5804
|
+
* it marks the host's own right Sidebar (always present once a session opens),
|
|
5805
|
+
* so counting it would make the better-sidebar verdict permanently true and the
|
|
5806
|
+
* panel slider could never drop back to its native "右方侧边栏" identity. */
|
|
5323
5807
|
const BETTER_SIDEBAR_MARKERS = [
|
|
5324
5808
|
"[data-dsh-better-sidebar]",
|
|
5325
5809
|
"[data-dsh-panel-host]",
|
|
5326
|
-
"[data-dsh-bottom-panel]"
|
|
5327
|
-
"[data-sidebar-right-panel]"
|
|
5810
|
+
"[data-dsh-bottom-panel]"
|
|
5328
5811
|
];
|
|
5329
5812
|
let betterSidebar = false;
|
|
5330
5813
|
const subs = /* @__PURE__ */ new Set();
|
|
@@ -5360,6 +5843,15 @@ window.__ModuleLoader__.load({
|
|
|
5360
5843
|
observer.observe(document.body, { childList: true });
|
|
5361
5844
|
return () => observer.disconnect();
|
|
5362
5845
|
}
|
|
5846
|
+
let nativeTabs = false;
|
|
5847
|
+
const nativeSubs = /* @__PURE__ */ new Set();
|
|
5848
|
+
/** Mark the native right-Sidebar service as confirmed. Sticky — the service
|
|
5849
|
+
* never goes away within a session. */
|
|
5850
|
+
function markNativeSidebarTabs() {
|
|
5851
|
+
if (nativeTabs) return;
|
|
5852
|
+
nativeTabs = true;
|
|
5853
|
+
nativeSubs.forEach((cb) => cb());
|
|
5854
|
+
}
|
|
5363
5855
|
//#endregion
|
|
5364
5856
|
//#region src/client/components/pages/InterfacePage.tsx
|
|
5365
5857
|
const PARTS$1 = [
|
|
@@ -5403,6 +5895,11 @@ window.__ModuleLoader__.load({
|
|
|
5403
5895
|
labelKey: "uiProduced",
|
|
5404
5896
|
Icon: TextIcon
|
|
5405
5897
|
},
|
|
5898
|
+
{
|
|
5899
|
+
isHeader: true,
|
|
5900
|
+
labelKey: "uiHeader",
|
|
5901
|
+
Icon: PanelIcon
|
|
5902
|
+
},
|
|
5406
5903
|
{
|
|
5407
5904
|
isPanel: true,
|
|
5408
5905
|
labelKey: "uiPanelRegion",
|
|
@@ -5410,9 +5907,9 @@ window.__ModuleLoader__.load({
|
|
|
5410
5907
|
}
|
|
5411
5908
|
];
|
|
5412
5909
|
function InterfacePage({ p }) {
|
|
5413
|
-
const { t, setOps, setBlurs, setSop, setPanelOp } = p;
|
|
5910
|
+
const { t, setOps, setBlurs, setSop, setPanelOp, useStore } = p;
|
|
5911
|
+
useStore((s) => s.metaRev);
|
|
5414
5912
|
const hasBetterSidebar = useBetterSidebar();
|
|
5415
|
-
const parts = PARTS$1.filter((part) => !(part.isPanel && !hasBetterSidebar));
|
|
5416
5913
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
5417
5914
|
className: "dab-head dab-rise",
|
|
5418
5915
|
style: { "--d": 0 },
|
|
@@ -5432,11 +5929,12 @@ window.__ModuleLoader__.load({
|
|
|
5432
5929
|
]
|
|
5433
5930
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5434
5931
|
className: "dab-grid-parts",
|
|
5435
|
-
children:
|
|
5436
|
-
const { labelKey, Icon, isSettings, isChat, isTrajectory, isPanel, isProduced } = part;
|
|
5932
|
+
children: PARTS$1.map((part, i) => {
|
|
5933
|
+
const { labelKey, Icon, isSettings, isChat, isTrajectory, isPanel, isProduced, isHeader } = part;
|
|
5437
5934
|
const opKey = part.opKey;
|
|
5438
|
-
const
|
|
5439
|
-
const
|
|
5935
|
+
const label = isPanel ? t(hasBetterSidebar ? "uiPanelRegion" : "uiPanelNative") : t(labelKey);
|
|
5936
|
+
const blurKey = isChat ? "chat" : isTrajectory ? "trajectory" : isSettings ? "settings" : isPanel ? "panel" : isProduced ? "produced" : isHeader ? "header" : opKey;
|
|
5937
|
+
const opacity = isChat ? rChatTextOpacity() : isTrajectory ? rTrajectoryOpacity() : isSettings ? rSop() : isPanel ? rPanelOpacity() : isProduced ? rProducedOpacity() : isHeader ? rHeaderOpacity() : rOps()[opKey];
|
|
5440
5938
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5441
5939
|
className: "dab-card dab-card-hover dab-rise",
|
|
5442
5940
|
style: { "--d": i + 1 },
|
|
@@ -5450,7 +5948,7 @@ window.__ModuleLoader__.load({
|
|
|
5450
5948
|
}),
|
|
5451
5949
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5452
5950
|
className: "dab-part-name",
|
|
5453
|
-
children:
|
|
5951
|
+
children: label
|
|
5454
5952
|
}),
|
|
5455
5953
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5456
5954
|
className: "dab-part-badge",
|
|
@@ -5482,6 +5980,9 @@ window.__ModuleLoader__.load({
|
|
|
5482
5980
|
} else if (isProduced) {
|
|
5483
5981
|
cfg.producedOpacity = op;
|
|
5484
5982
|
applyProduced();
|
|
5983
|
+
} else if (isHeader) {
|
|
5984
|
+
cfg.headerOpacity = op;
|
|
5985
|
+
applyHeaderPopovers();
|
|
5485
5986
|
} else {
|
|
5486
5987
|
const ops = { ...rOps() };
|
|
5487
5988
|
ops[opKey] = op;
|
|
@@ -5506,6 +6007,10 @@ window.__ModuleLoader__.load({
|
|
|
5506
6007
|
cfg.producedOpacity = op;
|
|
5507
6008
|
applyProduced();
|
|
5508
6009
|
saveConfig();
|
|
6010
|
+
} else if (isHeader) {
|
|
6011
|
+
cfg.headerOpacity = op;
|
|
6012
|
+
applyHeaderPopovers();
|
|
6013
|
+
saveConfig();
|
|
5509
6014
|
} else {
|
|
5510
6015
|
const ops = { ...rOps() };
|
|
5511
6016
|
ops[opKey] = op;
|
|
@@ -5581,6 +6086,11 @@ window.__ModuleLoader__.load({
|
|
|
5581
6086
|
labelKey: "uiProduced",
|
|
5582
6087
|
Icon: TextIcon
|
|
5583
6088
|
},
|
|
6089
|
+
{
|
|
6090
|
+
key: "header",
|
|
6091
|
+
labelKey: "uiHeader",
|
|
6092
|
+
Icon: PanelIcon
|
|
6093
|
+
},
|
|
5584
6094
|
{
|
|
5585
6095
|
key: "panel",
|
|
5586
6096
|
labelKey: "uiPanelRegion",
|
|
@@ -5617,7 +6127,7 @@ window.__ModuleLoader__.load({
|
|
|
5617
6127
|
}
|
|
5618
6128
|
}
|
|
5619
6129
|
function FontPage({ p, notify }) {
|
|
5620
|
-
const { t, setFont, removeFont, setFontEnabled, setStrokes } = p;
|
|
6130
|
+
const { t, setFont, removeFont, setFontEnabled, setStrokes, useStore } = p;
|
|
5621
6131
|
const hasBetterSidebar = useBetterSidebar();
|
|
5622
6132
|
const fileRef = (0, react.useRef)(null);
|
|
5623
6133
|
const [strokes, setStrokesState] = (0, react.useState)(() => rStrokes());
|
|
@@ -5627,6 +6137,15 @@ window.__ModuleLoader__.load({
|
|
|
5627
6137
|
name: null
|
|
5628
6138
|
}));
|
|
5629
6139
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
6140
|
+
const metaRev = useStore((s) => s.metaRev);
|
|
6141
|
+
(0, react.useEffect)(() => {
|
|
6142
|
+
setStrokesState(rStrokes());
|
|
6143
|
+
setFontInfo((prev) => prev.mime === cfg.fontMime && prev.enabled === cfg.fontEnabled ? prev : {
|
|
6144
|
+
mime: cfg.fontMime,
|
|
6145
|
+
enabled: cfg.fontEnabled,
|
|
6146
|
+
name: null
|
|
6147
|
+
});
|
|
6148
|
+
}, [metaRev]);
|
|
5630
6149
|
const hasFont = fontInfo.mime !== null;
|
|
5631
6150
|
const fontLabel = fontInfo.name ?? (fontInfo.mime !== null ? fontInfo.mime.replace("font/", "").toUpperCase() : t("fontNone"));
|
|
5632
6151
|
/** Write one group's stroke: cfg + live DOM now, disk on commit. `live`
|
|
@@ -5651,9 +6170,9 @@ window.__ModuleLoader__.load({
|
|
|
5651
6170
|
...fontInfo,
|
|
5652
6171
|
name: file.name
|
|
5653
6172
|
});
|
|
5654
|
-
const
|
|
6173
|
+
const outcome = await setFont(file);
|
|
5655
6174
|
setBusy(false);
|
|
5656
|
-
if (ok) setFontInfo({
|
|
6175
|
+
if (outcome.ok) setFontInfo({
|
|
5657
6176
|
mime: cfg.fontMime,
|
|
5658
6177
|
enabled: cfg.fontEnabled,
|
|
5659
6178
|
name: file.name
|
|
@@ -5664,7 +6183,7 @@ window.__ModuleLoader__.load({
|
|
|
5664
6183
|
enabled: cfg.fontEnabled,
|
|
5665
6184
|
name: null
|
|
5666
6185
|
});
|
|
5667
|
-
notify(t
|
|
6186
|
+
notify(uploadRefusalText(outcome, t, "fontFail"), false);
|
|
5668
6187
|
}
|
|
5669
6188
|
};
|
|
5670
6189
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
@@ -5878,6 +6397,19 @@ window.__ModuleLoader__.load({
|
|
|
5878
6397
|
});
|
|
5879
6398
|
const containerRef = (0, react.useRef)(null);
|
|
5880
6399
|
const imgRef = (0, react.useRef)(null);
|
|
6400
|
+
/**
|
|
6401
|
+
* The overlay node is held in STATE rather than a plain ref, so the listener
|
|
6402
|
+
* effect below re-runs the moment it actually exists.
|
|
6403
|
+
*
|
|
6404
|
+
* This is not a style choice. Everything here renders through <Portal>, which
|
|
6405
|
+
* returns null on its first render — its own effect has to create the host
|
|
6406
|
+
* node under <html> first. A plain ref is therefore still null when a
|
|
6407
|
+
* mount-only effect runs, and an effect whose deps never change again attaches
|
|
6408
|
+
* nothing, ever. That is exactly how the touch handlers below ended up dead on
|
|
6409
|
+
* real phones: the wheel handler only survived because its deps churn on every
|
|
6410
|
+
* zoom/pan, so the wallpaper's img.onload re-ran it once and wired it up.
|
|
6411
|
+
*/
|
|
6412
|
+
const [overlayEl, setOverlayEl] = (0, react.useState)(null);
|
|
5881
6413
|
const dragRef = (0, react.useRef)({
|
|
5882
6414
|
active: false,
|
|
5883
6415
|
sx: 0,
|
|
@@ -5885,6 +6417,16 @@ window.__ModuleLoader__.load({
|
|
|
5885
6417
|
spx: 0,
|
|
5886
6418
|
spy: 0
|
|
5887
6419
|
});
|
|
6420
|
+
/** Unmount-time cleanup for a drag that never saw its mouseup (editor closed
|
|
6421
|
+
* mid-drag): without it the document listeners stay attached forever and
|
|
6422
|
+
* keep calling setPos on a dead component. */
|
|
6423
|
+
const dragStopRef = (0, react.useRef)(null);
|
|
6424
|
+
/** Set once a gesture actually moved, so the click that follows a drag is not
|
|
6425
|
+
* mistaken for a tap on the backdrop (mousedown and mouseup in different
|
|
6426
|
+
* subtrees make the click land on the overlay, i.e. "outside"). Reset at the
|
|
6427
|
+
* start of every gesture — touchstart / mousedown / wheel — so it can only
|
|
6428
|
+
* ever describe the gesture that just ended, never an earlier one. */
|
|
6429
|
+
const movedRef = (0, react.useRef)(false);
|
|
5888
6430
|
const viewRef = (0, react.useRef)({
|
|
5889
6431
|
zoom,
|
|
5890
6432
|
pos
|
|
@@ -5930,6 +6472,7 @@ window.__ModuleLoader__.load({
|
|
|
5930
6472
|
]);
|
|
5931
6473
|
const onDown = (0, react.useCallback)((e) => {
|
|
5932
6474
|
e.preventDefault();
|
|
6475
|
+
movedRef.current = false;
|
|
5933
6476
|
dragRef.current = {
|
|
5934
6477
|
active: true,
|
|
5935
6478
|
sx: e.clientX,
|
|
@@ -5939,58 +6482,50 @@ window.__ModuleLoader__.load({
|
|
|
5939
6482
|
};
|
|
5940
6483
|
const onMove = (ev) => {
|
|
5941
6484
|
if (!dragRef.current.active) return;
|
|
6485
|
+
if (Math.abs(ev.clientX - dragRef.current.sx) > 4 || Math.abs(ev.clientY - dragRef.current.sy) > 4) movedRef.current = true;
|
|
5942
6486
|
setPos({
|
|
5943
6487
|
x: dragRef.current.spx + ev.clientX - dragRef.current.sx,
|
|
5944
6488
|
y: dragRef.current.spy + ev.clientY - dragRef.current.sy
|
|
5945
6489
|
});
|
|
5946
6490
|
};
|
|
5947
|
-
const
|
|
6491
|
+
const stopDrag = () => {
|
|
5948
6492
|
dragRef.current.active = false;
|
|
5949
6493
|
document.removeEventListener("mousemove", onMove);
|
|
5950
|
-
document.removeEventListener("mouseup",
|
|
6494
|
+
document.removeEventListener("mouseup", stopDrag);
|
|
6495
|
+
dragStopRef.current = null;
|
|
5951
6496
|
};
|
|
6497
|
+
dragStopRef.current = stopDrag;
|
|
5952
6498
|
document.addEventListener("mousemove", onMove);
|
|
5953
|
-
document.addEventListener("mouseup",
|
|
6499
|
+
document.addEventListener("mouseup", stopDrag);
|
|
5954
6500
|
}, [pos]);
|
|
6501
|
+
(0, react.useEffect)(() => () => {
|
|
6502
|
+
dragStopRef.current?.();
|
|
6503
|
+
}, []);
|
|
5955
6504
|
const onWheelCb = (0, react.useCallback)((e) => {
|
|
5956
6505
|
e.preventDefault();
|
|
6506
|
+
movedRef.current = false;
|
|
5957
6507
|
const el = containerRef.current;
|
|
5958
6508
|
if (!el) return;
|
|
5959
6509
|
const rect = el.getBoundingClientRect();
|
|
5960
6510
|
const mx = rect.width / 2, my = rect.height / 2;
|
|
5961
6511
|
const factor = e.deltaY > 0 ? .97 : 1.03;
|
|
5962
|
-
const
|
|
5963
|
-
const
|
|
5964
|
-
const
|
|
6512
|
+
const { zoom: z, pos: p } = viewRef.current;
|
|
6513
|
+
const nz = clampZoom(z * factor);
|
|
6514
|
+
const nx = mx - (mx - p.x) * (nz / z);
|
|
6515
|
+
const ny = my - (my - p.y) * (nz / z);
|
|
5965
6516
|
setZoom(nz);
|
|
5966
6517
|
setPos({
|
|
5967
6518
|
x: nx,
|
|
5968
6519
|
y: ny
|
|
5969
6520
|
});
|
|
5970
|
-
}, [
|
|
5971
|
-
(0, react.useEffect)(() => {
|
|
5972
|
-
const el = containerRef.current;
|
|
5973
|
-
if (!el) return;
|
|
5974
|
-
el.addEventListener("wheel", onWheelCb, { passive: false });
|
|
5975
|
-
return () => el.removeEventListener("wheel", onWheelCb);
|
|
5976
|
-
}, [onWheelCb]);
|
|
6521
|
+
}, []);
|
|
5977
6522
|
const onTouchStartCb = (0, react.useCallback)((e) => {
|
|
5978
|
-
const
|
|
5979
|
-
if (!
|
|
5980
|
-
|
|
5981
|
-
pinchRef.current = null;
|
|
5982
|
-
const tp = e.touches[0];
|
|
5983
|
-
touchDragRef.current = {
|
|
5984
|
-
sx: tp.clientX,
|
|
5985
|
-
sy: tp.clientY,
|
|
5986
|
-
spx: viewRef.current.pos.x,
|
|
5987
|
-
spy: viewRef.current.pos.y
|
|
5988
|
-
};
|
|
5989
|
-
return;
|
|
5990
|
-
}
|
|
6523
|
+
const card = containerRef.current;
|
|
6524
|
+
if (!card) return;
|
|
6525
|
+
movedRef.current = false;
|
|
5991
6526
|
if (e.touches.length >= 2) {
|
|
5992
6527
|
touchDragRef.current = null;
|
|
5993
|
-
const rect =
|
|
6528
|
+
const rect = card.getBoundingClientRect();
|
|
5994
6529
|
const a = e.touches[0], b = e.touches[1];
|
|
5995
6530
|
pinchRef.current = {
|
|
5996
6531
|
dist: Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY),
|
|
@@ -6000,16 +6535,32 @@ window.__ModuleLoader__.load({
|
|
|
6000
6535
|
px: viewRef.current.pos.x,
|
|
6001
6536
|
py: viewRef.current.pos.y
|
|
6002
6537
|
};
|
|
6538
|
+
return;
|
|
6539
|
+
}
|
|
6540
|
+
if (e.touches.length === 1) {
|
|
6541
|
+
pinchRef.current = null;
|
|
6542
|
+
if (!card.contains(e.target)) {
|
|
6543
|
+
touchDragRef.current = null;
|
|
6544
|
+
return;
|
|
6545
|
+
}
|
|
6546
|
+
const tp = e.touches[0];
|
|
6547
|
+
touchDragRef.current = {
|
|
6548
|
+
sx: tp.clientX,
|
|
6549
|
+
sy: tp.clientY,
|
|
6550
|
+
spx: viewRef.current.pos.x,
|
|
6551
|
+
spy: viewRef.current.pos.y
|
|
6552
|
+
};
|
|
6003
6553
|
}
|
|
6004
6554
|
}, []);
|
|
6005
6555
|
const onTouchMoveCb = (0, react.useCallback)((e) => {
|
|
6006
|
-
const
|
|
6007
|
-
if (!
|
|
6556
|
+
const card = containerRef.current;
|
|
6557
|
+
if (!card) return;
|
|
6008
6558
|
if (e.touches.length >= 2 && pinchRef.current !== null) {
|
|
6009
6559
|
e.preventDefault();
|
|
6560
|
+
movedRef.current = true;
|
|
6010
6561
|
const p = pinchRef.current;
|
|
6011
6562
|
if (p.dist <= 0) return;
|
|
6012
|
-
const rect =
|
|
6563
|
+
const rect = card.getBoundingClientRect();
|
|
6013
6564
|
const a = e.touches[0], b = e.touches[1];
|
|
6014
6565
|
const dist = Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY);
|
|
6015
6566
|
const cx = (a.clientX + b.clientX) / 2 - rect.left;
|
|
@@ -6026,6 +6577,7 @@ window.__ModuleLoader__.load({
|
|
|
6026
6577
|
e.preventDefault();
|
|
6027
6578
|
const d = touchDragRef.current;
|
|
6028
6579
|
const tp = e.touches[0];
|
|
6580
|
+
if (Math.abs(tp.clientX - d.sx) > 4 || Math.abs(tp.clientY - d.sy) > 4) movedRef.current = true;
|
|
6029
6581
|
setPos({
|
|
6030
6582
|
x: d.spx + tp.clientX - d.sx,
|
|
6031
6583
|
y: d.spy + tp.clientY - d.sy
|
|
@@ -6048,19 +6600,29 @@ window.__ModuleLoader__.load({
|
|
|
6048
6600
|
}
|
|
6049
6601
|
}, []);
|
|
6050
6602
|
(0, react.useEffect)(() => {
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6603
|
+
if (!overlayEl) return;
|
|
6604
|
+
const noPageGesture = (e) => e.preventDefault();
|
|
6605
|
+
overlayEl.addEventListener("wheel", onWheelCb, { passive: false });
|
|
6606
|
+
overlayEl.addEventListener("touchstart", onTouchStartCb, { passive: false });
|
|
6607
|
+
overlayEl.addEventListener("touchmove", onTouchMoveCb, { passive: false });
|
|
6608
|
+
overlayEl.addEventListener("touchend", onTouchEndCb, { passive: false });
|
|
6609
|
+
overlayEl.addEventListener("touchcancel", onTouchEndCb, { passive: false });
|
|
6610
|
+
overlayEl.addEventListener("gesturestart", noPageGesture, { passive: false });
|
|
6611
|
+
overlayEl.addEventListener("gesturechange", noPageGesture, { passive: false });
|
|
6612
|
+
overlayEl.addEventListener("gestureend", noPageGesture, { passive: false });
|
|
6057
6613
|
return () => {
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6614
|
+
overlayEl.removeEventListener("wheel", onWheelCb);
|
|
6615
|
+
overlayEl.removeEventListener("touchstart", onTouchStartCb);
|
|
6616
|
+
overlayEl.removeEventListener("touchmove", onTouchMoveCb);
|
|
6617
|
+
overlayEl.removeEventListener("touchend", onTouchEndCb);
|
|
6618
|
+
overlayEl.removeEventListener("touchcancel", onTouchEndCb);
|
|
6619
|
+
overlayEl.removeEventListener("gesturestart", noPageGesture);
|
|
6620
|
+
overlayEl.removeEventListener("gesturechange", noPageGesture);
|
|
6621
|
+
overlayEl.removeEventListener("gestureend", noPageGesture);
|
|
6062
6622
|
};
|
|
6063
6623
|
}, [
|
|
6624
|
+
overlayEl,
|
|
6625
|
+
onWheelCb,
|
|
6064
6626
|
onTouchStartCb,
|
|
6065
6627
|
onTouchMoveCb,
|
|
6066
6628
|
onTouchEndCb
|
|
@@ -6078,10 +6640,22 @@ window.__ModuleLoader__.load({
|
|
|
6078
6640
|
imgSize
|
|
6079
6641
|
]);
|
|
6080
6642
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Portal, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6643
|
+
ref: setOverlayEl,
|
|
6081
6644
|
className: "dab-overlay",
|
|
6645
|
+
style: {
|
|
6646
|
+
touchAction: "none",
|
|
6647
|
+
overscrollBehavior: "contain"
|
|
6648
|
+
},
|
|
6082
6649
|
onClick: (e) => {
|
|
6650
|
+
if (movedRef.current) {
|
|
6651
|
+
movedRef.current = false;
|
|
6652
|
+
return;
|
|
6653
|
+
}
|
|
6083
6654
|
if (e.target === e.currentTarget) onClose();
|
|
6084
6655
|
},
|
|
6656
|
+
onMouseDown: () => {
|
|
6657
|
+
movedRef.current = false;
|
|
6658
|
+
},
|
|
6085
6659
|
children: [
|
|
6086
6660
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6087
6661
|
className: "dab-overlay-title",
|
|
@@ -6177,7 +6751,7 @@ window.__ModuleLoader__.load({
|
|
|
6177
6751
|
labelKey: "bgModeCenter"
|
|
6178
6752
|
}
|
|
6179
6753
|
];
|
|
6180
|
-
function BackgroundPage({ p }) {
|
|
6754
|
+
function BackgroundPage({ p, notify }) {
|
|
6181
6755
|
const { t, setWpFromServer, setVideo, setWop, setBl, setBgType, setGeneratedBg, regenerateBg, setRegenerateOnReload, setRotation, addRotationItems, removeRotationItem, rotateNow, setVideoFromUrl, useStore } = p;
|
|
6182
6756
|
const storeUrl = useStore((s) => s.url);
|
|
6183
6757
|
const backgroundType = useStore((s) => s.backgroundType);
|
|
@@ -6194,7 +6768,11 @@ window.__ModuleLoader__.load({
|
|
|
6194
6768
|
const [urlVal, setUrlVal] = (0, react.useState)("");
|
|
6195
6769
|
const [urlBusy, setUrlBusy] = (0, react.useState)(false);
|
|
6196
6770
|
const [urlErr, setUrlErr] = (0, react.useState)(null);
|
|
6771
|
+
const bgRev = useStore((s) => s.bgRev);
|
|
6197
6772
|
const [mode, setModeState] = (0, react.useState)(rBgMode());
|
|
6773
|
+
(0, react.useEffect)(() => {
|
|
6774
|
+
setModeState(rBgMode());
|
|
6775
|
+
}, [bgRev]);
|
|
6198
6776
|
const isVideo = backgroundType === "video";
|
|
6199
6777
|
const isStatic = backgroundType === "image" || isVideo;
|
|
6200
6778
|
const isGenerated = !isStatic;
|
|
@@ -6208,14 +6786,15 @@ window.__ModuleLoader__.load({
|
|
|
6208
6786
|
genPreset,
|
|
6209
6787
|
generatedBg?.seed
|
|
6210
6788
|
]);
|
|
6211
|
-
const onFileSelect = (f) => {
|
|
6789
|
+
const onFileSelect = async (f) => {
|
|
6212
6790
|
if (f.type.startsWith("video/")) {
|
|
6213
|
-
setVideo(f, f.type);
|
|
6791
|
+
const outcome = await setVideo(f, f.type);
|
|
6792
|
+
if (!outcome.ok) notify(uploadRefusalText(outcome, t, "videoUploadFail"), false);
|
|
6214
6793
|
return;
|
|
6215
6794
|
}
|
|
6216
|
-
uploadWallpaper(f)
|
|
6217
|
-
|
|
6218
|
-
|
|
6795
|
+
const outcome = await uploadWallpaper(f);
|
|
6796
|
+
if (outcome.ok) setWpFromServer(WALLPAPER_SERVE_URL);
|
|
6797
|
+
else notify(uploadRefusalText(outcome, t, "bgUploadFail"), false);
|
|
6219
6798
|
};
|
|
6220
6799
|
const onDrop = (e) => {
|
|
6221
6800
|
e.preventDefault();
|
|
@@ -6884,7 +7463,8 @@ window.__ModuleLoader__.load({
|
|
|
6884
7463
|
trajectory: 0,
|
|
6885
7464
|
input: 0,
|
|
6886
7465
|
panel: 0,
|
|
6887
|
-
produced: 0
|
|
7466
|
+
produced: 0,
|
|
7467
|
+
header: 0
|
|
6888
7468
|
};
|
|
6889
7469
|
/** Presets ship with strokes off — text outlines are an opt-in look. Fresh
|
|
6890
7470
|
* objects per call so a profile restore can never alias another preset's
|
|
@@ -6904,7 +7484,8 @@ window.__ModuleLoader__.load({
|
|
|
6904
7484
|
trajectory: off(),
|
|
6905
7485
|
input: off(),
|
|
6906
7486
|
panel: off(),
|
|
6907
|
-
produced: off()
|
|
7487
|
+
produced: off(),
|
|
7488
|
+
header: off()
|
|
6908
7489
|
};
|
|
6909
7490
|
};
|
|
6910
7491
|
const BUILTIN_PRESETS = [
|
|
@@ -6926,7 +7507,8 @@ window.__ModuleLoader__.load({
|
|
|
6926
7507
|
chatTextOpacity: 0,
|
|
6927
7508
|
trajectoryOpacity: 1,
|
|
6928
7509
|
panelOpacity: 1,
|
|
6929
|
-
producedOpacity: 1
|
|
7510
|
+
producedOpacity: 1,
|
|
7511
|
+
headerOpacity: 1
|
|
6930
7512
|
}
|
|
6931
7513
|
},
|
|
6932
7514
|
{
|
|
@@ -6960,7 +7542,8 @@ window.__ModuleLoader__.load({
|
|
|
6960
7542
|
chatTextOpacity: 0,
|
|
6961
7543
|
trajectoryOpacity: .85,
|
|
6962
7544
|
panelOpacity: .85,
|
|
6963
|
-
producedOpacity: 1
|
|
7545
|
+
producedOpacity: 1,
|
|
7546
|
+
headerOpacity: 1
|
|
6964
7547
|
}
|
|
6965
7548
|
},
|
|
6966
7549
|
{
|
|
@@ -6981,7 +7564,8 @@ window.__ModuleLoader__.load({
|
|
|
6981
7564
|
chatTextOpacity: 0,
|
|
6982
7565
|
trajectoryOpacity: 1,
|
|
6983
7566
|
panelOpacity: 1,
|
|
6984
|
-
producedOpacity: 1
|
|
7567
|
+
producedOpacity: 1,
|
|
7568
|
+
headerOpacity: 1
|
|
6985
7569
|
}
|
|
6986
7570
|
},
|
|
6987
7571
|
{
|
|
@@ -7015,7 +7599,8 @@ window.__ModuleLoader__.load({
|
|
|
7015
7599
|
chatTextOpacity: 0,
|
|
7016
7600
|
trajectoryOpacity: .8,
|
|
7017
7601
|
panelOpacity: .8,
|
|
7018
|
-
producedOpacity: 1
|
|
7602
|
+
producedOpacity: 1,
|
|
7603
|
+
headerOpacity: 1
|
|
7019
7604
|
}
|
|
7020
7605
|
},
|
|
7021
7606
|
{
|
|
@@ -7049,7 +7634,8 @@ window.__ModuleLoader__.load({
|
|
|
7049
7634
|
chatTextOpacity: 0,
|
|
7050
7635
|
trajectoryOpacity: .78,
|
|
7051
7636
|
panelOpacity: .75,
|
|
7052
|
-
producedOpacity: 1
|
|
7637
|
+
producedOpacity: 1,
|
|
7638
|
+
headerOpacity: 1
|
|
7053
7639
|
}
|
|
7054
7640
|
},
|
|
7055
7641
|
{
|
|
@@ -7074,7 +7660,8 @@ window.__ModuleLoader__.load({
|
|
|
7074
7660
|
chatTextOpacity: 0,
|
|
7075
7661
|
trajectoryOpacity: 1,
|
|
7076
7662
|
panelOpacity: 1,
|
|
7077
|
-
producedOpacity: 1
|
|
7663
|
+
producedOpacity: 1,
|
|
7664
|
+
headerOpacity: 1
|
|
7078
7665
|
}
|
|
7079
7666
|
}
|
|
7080
7667
|
];
|
|
@@ -7489,31 +8076,37 @@ window.__ModuleLoader__.load({
|
|
|
7489
8076
|
//#endregion
|
|
7490
8077
|
//#region src/client/components/ThemeSection.tsx
|
|
7491
8078
|
/**
|
|
7492
|
-
* dsh-any-background —
|
|
8079
|
+
* dsh-any-background — appearance shell for the five pages.
|
|
7493
8080
|
*
|
|
7494
|
-
*
|
|
7495
|
-
*
|
|
7496
|
-
*
|
|
7497
|
-
*
|
|
7498
|
-
*
|
|
7499
|
-
*
|
|
8081
|
+
* Two surfaces render this shell, and only the chrome differs:
|
|
8082
|
+
* · `settings` — injected into the host settings dialog's content column,
|
|
8083
|
+
* which supplies the modal frame (nav rail on the left, brand block, page
|
|
8084
|
+
* max-width, the dialog's own scrolling);
|
|
8085
|
+
* · `sidebar` — a page inside dsh-better-sidebar, where the panel is much
|
|
8086
|
+
* narrower, is handed a full-height column, and has to own its own scroll
|
|
8087
|
+
* region. The brand block is dropped (the sidebar's tab bar already names
|
|
8088
|
+
* the page) and the rail turns into a compact row.
|
|
8089
|
+
* The five pages themselves never branch on the surface: width-driven
|
|
8090
|
+
* adaptation lives in the stylesheet's container queries, which measure the
|
|
8091
|
+
* shell itself rather than assuming either surface's width.
|
|
7500
8092
|
*/
|
|
7501
8093
|
function ThemeSection(props) {
|
|
7502
8094
|
ensureUiCss();
|
|
7503
8095
|
const { t } = props;
|
|
8096
|
+
const surface = props.surface ?? "settings";
|
|
7504
8097
|
const [page, setPage] = (0, react.useState)(0);
|
|
7505
8098
|
const [toast, setToast] = (0, react.useState)(null);
|
|
7506
8099
|
const toastTimer = (0, react.useRef)(void 0);
|
|
7507
8100
|
(0, react.useEffect)(() => () => window.clearTimeout(toastTimer.current), []);
|
|
7508
|
-
const notify = (msg, ok = true) => {
|
|
8101
|
+
const notify = (0, react.useCallback)((msg, ok = true) => {
|
|
7509
8102
|
setToast({
|
|
7510
8103
|
msg,
|
|
7511
8104
|
ok
|
|
7512
8105
|
});
|
|
7513
8106
|
window.clearTimeout(toastTimer.current);
|
|
7514
8107
|
toastTimer.current = window.setTimeout(() => setToast(null), 2600);
|
|
7515
|
-
};
|
|
7516
|
-
const pages = [
|
|
8108
|
+
}, []);
|
|
8109
|
+
const pages = (0, react.useMemo)(() => [
|
|
7517
8110
|
{
|
|
7518
8111
|
label: t("pageColor"),
|
|
7519
8112
|
Icon: DropletIcon,
|
|
@@ -7538,7 +8131,10 @@ window.__ModuleLoader__.load({
|
|
|
7538
8131
|
{
|
|
7539
8132
|
label: t("pageBackground"),
|
|
7540
8133
|
Icon: PhotoIcon,
|
|
7541
|
-
node: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BackgroundPage, {
|
|
8134
|
+
node: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BackgroundPage, {
|
|
8135
|
+
p: props,
|
|
8136
|
+
notify
|
|
8137
|
+
})
|
|
7542
8138
|
},
|
|
7543
8139
|
{
|
|
7544
8140
|
label: t("pageProfile"),
|
|
@@ -7548,16 +8144,21 @@ window.__ModuleLoader__.load({
|
|
|
7548
8144
|
notify
|
|
7549
8145
|
})
|
|
7550
8146
|
}
|
|
7551
|
-
]
|
|
8147
|
+
], [
|
|
8148
|
+
t,
|
|
8149
|
+
props,
|
|
8150
|
+
notify
|
|
8151
|
+
]);
|
|
7552
8152
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ErrorBoundary, {
|
|
7553
8153
|
t,
|
|
7554
8154
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7555
8155
|
className: "dab-root",
|
|
8156
|
+
"data-dab-surface": surface,
|
|
7556
8157
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7557
8158
|
className: "dab-shell",
|
|
7558
8159
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", {
|
|
7559
8160
|
className: "dab-nav",
|
|
7560
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8161
|
+
children: [surface === "sidebar" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7561
8162
|
className: "dab-brand",
|
|
7562
8163
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7563
8164
|
className: "dab-brand-tile",
|
|
@@ -7597,6 +8198,377 @@ window.__ModuleLoader__.load({
|
|
|
7597
8198
|
});
|
|
7598
8199
|
}
|
|
7599
8200
|
//#endregion
|
|
8201
|
+
//#region src/client/sidebar/store-hook.ts
|
|
8202
|
+
/**
|
|
8203
|
+
* dsh-any-background — selector hook over a host store instance.
|
|
8204
|
+
*
|
|
8205
|
+
* The settings slot kit hands the pages a `useStore(selector)` hook that is
|
|
8206
|
+
* bound to this plugin's store instance by the host renderer. A better-sidebar
|
|
8207
|
+
* page gets no such kit — its tab props carry `ctx`/`store`/`scope` for
|
|
8208
|
+
* better-sidebar's own store — so the same contract is rebuilt here from the
|
|
8209
|
+
* store instance itself, which already IS an observable source
|
|
8210
|
+
* (`getSnapshot`/`subscribe`, see `defineStore` in the host's client store).
|
|
8211
|
+
*
|
|
8212
|
+
* Semantics match the host binding on purpose: reduce the published snapshot
|
|
8213
|
+
* through the selector and compare with `Object.is`, so a page subscribing to
|
|
8214
|
+
* one field does not re-render on unrelated writes.
|
|
8215
|
+
*/
|
|
8216
|
+
/**
|
|
8217
|
+
* Bind a store instance to a `useStore(selector)` hook. Call once per store and
|
|
8218
|
+
* share the result: the returned hook keeps one subscription identity for its
|
|
8219
|
+
* whole lifetime.
|
|
8220
|
+
* @param store - observable store instance (`defineStore` handle).
|
|
8221
|
+
* @returns a selector hook reading that instance.
|
|
8222
|
+
*/
|
|
8223
|
+
function createStoreHook(store) {
|
|
8224
|
+
const subscribe = (onChange) => store.subscribe(onChange);
|
|
8225
|
+
const { getSnapshot } = store;
|
|
8226
|
+
return function useStoreSelector(selector) {
|
|
8227
|
+
const cache = (0, react.useRef)(null);
|
|
8228
|
+
const read = (0, react.useCallback)(() => {
|
|
8229
|
+
const snapshot = getSnapshot();
|
|
8230
|
+
const hit = cache.current;
|
|
8231
|
+
if (hit !== null && hit.snapshot === snapshot) return hit.value;
|
|
8232
|
+
const value = selector(snapshot);
|
|
8233
|
+
cache.current = {
|
|
8234
|
+
snapshot,
|
|
8235
|
+
value
|
|
8236
|
+
};
|
|
8237
|
+
return value;
|
|
8238
|
+
}, [selector]);
|
|
8239
|
+
return (0, react.useSyncExternalStore)(subscribe, read, read);
|
|
8240
|
+
};
|
|
8241
|
+
}
|
|
8242
|
+
//#endregion
|
|
8243
|
+
//#region src/client/sidebar/tab.tsx
|
|
8244
|
+
/**
|
|
8245
|
+
* dsh-any-background — dsh-better-sidebar integration.
|
|
8246
|
+
*
|
|
8247
|
+
* Publishes the plugin's appearance controls as a sidebar page ("主题") in
|
|
8248
|
+
* better-sidebar's tab registry. The page renders the SAME five-page tree as
|
|
8249
|
+
* the settings section — `ThemeSection` takes a `surface` variant — so there is
|
|
8250
|
+
* one UI to maintain, not two forks of it.
|
|
8251
|
+
*
|
|
8252
|
+
* The dependency is optional at every level, which is why nothing here imports
|
|
8253
|
+
* the package:
|
|
8254
|
+
* · the service is read off the client context at runtime, so a DSH install
|
|
8255
|
+
* without better-sidebar silently registers nothing, and a better-sidebar
|
|
8256
|
+
* that mounts *after* this plugin still gets the page (`ctx.inject` fires
|
|
8257
|
+
* whenever the service shows up, never when it never does);
|
|
8258
|
+
* · the plugin's own types are restated in the minimal local contract below
|
|
8259
|
+
* instead of imported — a value import would be rejected by the host's
|
|
8260
|
+
* client-bundle purity gate, and even a type-only import would make the
|
|
8261
|
+
* package a build-time dependency for everyone who does not run it.
|
|
8262
|
+
*/
|
|
8263
|
+
/**
|
|
8264
|
+
* The active locale id, used as a re-render signal: page labels are resolved by
|
|
8265
|
+
* calling `t(...)` at render time, so the props object (and with it the memoized
|
|
8266
|
+
* page tree) has to be rebuilt when the language flips — otherwise the page
|
|
8267
|
+
* would keep painting the previous language until some unrelated store write
|
|
8268
|
+
* happened to re-render it.
|
|
8269
|
+
* @param locale - host locale service.
|
|
8270
|
+
* @returns the active locale id (a stable string, safe for `Object.is`).
|
|
8271
|
+
*/
|
|
8272
|
+
function useActiveLocale(locale) {
|
|
8273
|
+
const subscribe = (0, react.useCallback)((onChange) => locale.subscribe(onChange), [locale]);
|
|
8274
|
+
const read = (0, react.useCallback)(() => locale.getSnapshot().active, [locale]);
|
|
8275
|
+
return (0, react.useSyncExternalStore)(subscribe, read, read);
|
|
8276
|
+
}
|
|
8277
|
+
/** Rendered inside a sidebar tab: the five pages, `surface: 'sidebar'` shell.
|
|
8278
|
+
* Shared by the better-sidebar page and the native right-Sidebar tab — both
|
|
8279
|
+
* hand a full-height column that owns its own scroll. */
|
|
8280
|
+
function ThemeTab({ face, useStore, locale }) {
|
|
8281
|
+
const active = useActiveLocale(locale);
|
|
8282
|
+
const props = (0, react.useMemo)(() => ({
|
|
8283
|
+
...face,
|
|
8284
|
+
useStore
|
|
8285
|
+
}), [
|
|
8286
|
+
face,
|
|
8287
|
+
useStore,
|
|
8288
|
+
active
|
|
8289
|
+
]);
|
|
8290
|
+
return (0, react.createElement)(ThemeSection, {
|
|
8291
|
+
...props,
|
|
8292
|
+
surface: "sidebar"
|
|
8293
|
+
});
|
|
8294
|
+
}
|
|
8295
|
+
/**
|
|
8296
|
+
* Register the appearance page with better-sidebar, if it is installed.
|
|
8297
|
+
*
|
|
8298
|
+
* Call from the client `apply`. The registration is owned by the injected
|
|
8299
|
+
* scope's effect, so unloading the plugin (or HMR) withdraws it — without that,
|
|
8300
|
+
* the next activation would throw on the duplicate id.
|
|
8301
|
+
* @param ctx - client context.
|
|
8302
|
+
* @param opts.face - lazy accessor for the shared business face.
|
|
8303
|
+
* @param opts.store - the plugin's store instance, or null on a host where the
|
|
8304
|
+
* store module never resolved (in that case there is nothing to bind and no
|
|
8305
|
+
* page is registered).
|
|
8306
|
+
*/
|
|
8307
|
+
function registerThemeSidebarTab(ctx, opts) {
|
|
8308
|
+
const { store } = opts;
|
|
8309
|
+
if (store === null || typeof store.getSnapshot !== "function" || typeof store.subscribe !== "function") {
|
|
8310
|
+
console.warn("dsh-any-background: no observable store instance on this host; the sidebar page stays unregistered");
|
|
8311
|
+
return;
|
|
8312
|
+
}
|
|
8313
|
+
const locale = ctx.locale;
|
|
8314
|
+
const t = locale.bind(NS);
|
|
8315
|
+
const useStore = createStoreHook(store);
|
|
8316
|
+
const ORDER = 55;
|
|
8317
|
+
ctx.inject?.(["betterSidebar"], (scope) => {
|
|
8318
|
+
const service = scope.betterSidebar;
|
|
8319
|
+
if (service === void 0) return;
|
|
8320
|
+
const suppress = suppressesBetterSidebar();
|
|
8321
|
+
if (suppress === true) return;
|
|
8322
|
+
let disposeTab = null;
|
|
8323
|
+
const unsubscribeHost = suppress === null ? subscribeHostInfo(() => {
|
|
8324
|
+
if (suppressesBetterSidebar() === true) {
|
|
8325
|
+
disposeTab?.();
|
|
8326
|
+
disposeTab = null;
|
|
8327
|
+
unsubscribeHost();
|
|
8328
|
+
}
|
|
8329
|
+
}) : () => {};
|
|
8330
|
+
const face = opts.face();
|
|
8331
|
+
scope.effect(() => {
|
|
8332
|
+
disposeTab = service.registerTab({
|
|
8333
|
+
id: "dsh-any-background:theme",
|
|
8334
|
+
title: () => t("nav"),
|
|
8335
|
+
icon: (size) => (0, react.createElement)(SunIcon, { size }),
|
|
8336
|
+
order: ORDER,
|
|
8337
|
+
single: true,
|
|
8338
|
+
component: () => (0, react.createElement)(ThemeTab, {
|
|
8339
|
+
face,
|
|
8340
|
+
useStore,
|
|
8341
|
+
locale
|
|
8342
|
+
})
|
|
8343
|
+
}) ?? null;
|
|
8344
|
+
return () => {
|
|
8345
|
+
unsubscribeHost();
|
|
8346
|
+
disposeTab?.();
|
|
8347
|
+
disposeTab = null;
|
|
8348
|
+
};
|
|
8349
|
+
});
|
|
8350
|
+
});
|
|
8351
|
+
}
|
|
8352
|
+
//#endregion
|
|
8353
|
+
//#region src/client/sidebar/native-tab.tsx
|
|
8354
|
+
/**
|
|
8355
|
+
* dsh-any-background — native right-Sidebar integration (DSH 0.1.6+).
|
|
8356
|
+
*
|
|
8357
|
+
* DSH 0.1.6 ships an official right Sidebar (`dsh-client-ui-sidebar-right`)
|
|
8358
|
+
* with a public two-stage extension path: a static tab type into
|
|
8359
|
+
* `ctx.sidebarRightTabs` — whose `guide` entry draws a card on the Sidebar's
|
|
8360
|
+
* guide page — and a body under the keyed `sidebar.right.pane.tab` seat. This
|
|
8361
|
+
* module registers the plugin's appearance pages through that path, so the
|
|
8362
|
+
* official Sidebar gains a "主题" card that opens the very same five pages the
|
|
8363
|
+
* settings panel shows (one implementation, one state store; editing in either
|
|
8364
|
+
* place shows up in the other).
|
|
8365
|
+
*
|
|
8366
|
+
* The dependency is optional at every level, mirroring `sidebar/tab.tsx`:
|
|
8367
|
+
* · the registry is waited on with `ctx.inject`, so a host without the
|
|
8368
|
+
* right Sidebar (or an older DSH) silently registers nothing, and a
|
|
8369
|
+
* Sidebar that initializes after this plugin still gets the card;
|
|
8370
|
+
* · the service shape is restated as a minimal local contract instead of
|
|
8371
|
+
* imported — a value import would be rejected by the host's client-bundle
|
|
8372
|
+
* purity gate, and even a type-only import would make the package a
|
|
8373
|
+
* build-time dependency for everyone who does not run it.
|
|
8374
|
+
*
|
|
8375
|
+
* When dsh-better-sidebar is installed it brings its own "主题" page (registered
|
|
8376
|
+
* by `sidebar/tab.tsx`), which would duplicate this card on the same guide
|
|
8377
|
+
* page. The native card therefore yields: it is not registered while
|
|
8378
|
+
* better-sidebar is detected, and it withdraws itself if better-sidebar mounts
|
|
8379
|
+
* later (the presence verdict is sticky-true, so no re-registration path is
|
|
8380
|
+
* needed). The Interface page's panel slider follows the same split:
|
|
8381
|
+
* "右方侧边栏" natively, "bettersidebar" when that plugin is present.
|
|
8382
|
+
*/
|
|
8383
|
+
/** The tab implementation's identity, and the key the body registers under. */
|
|
8384
|
+
const NATIVE_TAB_ID = "dsh-any-background:theme";
|
|
8385
|
+
/** The page kind: what `openTab` names and the guide entry opens. */
|
|
8386
|
+
const NATIVE_TAB_KIND = "danybg-theme";
|
|
8387
|
+
/**
|
|
8388
|
+
* Register the appearance pages with the native right Sidebar, if one exists.
|
|
8389
|
+
*
|
|
8390
|
+
* Call from the client `apply`. Both registrations sit inside the injected
|
|
8391
|
+
* scope's effects, so unloading the plugin (or HMR) withdraws them — without
|
|
8392
|
+
* that, the next activation would throw on the duplicate id.
|
|
8393
|
+
* @param ctx - client context.
|
|
8394
|
+
* @param opts.face - lazy accessor for the shared business face.
|
|
8395
|
+
* @param opts.store - the plugin's store instance, or null on a host where the
|
|
8396
|
+
* store module never resolved (in that case there is nothing to bind and no
|
|
8397
|
+
* page is registered).
|
|
8398
|
+
*/
|
|
8399
|
+
function registerNativeSidebarTab(ctx, opts) {
|
|
8400
|
+
const { store } = opts;
|
|
8401
|
+
if (store === null || typeof store.getSnapshot !== "function" || typeof store.subscribe !== "function") {
|
|
8402
|
+
console.warn("dsh-any-background: no observable store instance on this host; the native sidebar page stays unregistered");
|
|
8403
|
+
return;
|
|
8404
|
+
}
|
|
8405
|
+
const locale = ctx.locale;
|
|
8406
|
+
const t = locale.bind(NS);
|
|
8407
|
+
const useStore = createStoreHook(store);
|
|
8408
|
+
ctx.inject?.(["sidebarRightTabs"], (scope) => {
|
|
8409
|
+
const tabs = scope.sidebarRightTabs;
|
|
8410
|
+
if (tabs === void 0 || typeof tabs.register !== "function") return;
|
|
8411
|
+
markNativeSidebarTabs();
|
|
8412
|
+
const face = opts.face();
|
|
8413
|
+
const ThemeBody = () => (0, react.createElement)(ThemeTab, {
|
|
8414
|
+
face,
|
|
8415
|
+
useStore,
|
|
8416
|
+
locale
|
|
8417
|
+
});
|
|
8418
|
+
scope.effect(() => {
|
|
8419
|
+
const yieldTo = () => suppressesBetterSidebar() === false && rBetterSidebar();
|
|
8420
|
+
if (yieldTo()) return;
|
|
8421
|
+
let disposeTab = null;
|
|
8422
|
+
const withdraw = () => {
|
|
8423
|
+
disposeTab?.();
|
|
8424
|
+
disposeTab = null;
|
|
8425
|
+
};
|
|
8426
|
+
const unsubscribeBetter = subscribe(() => {
|
|
8427
|
+
if (yieldTo()) withdraw();
|
|
8428
|
+
});
|
|
8429
|
+
const unsubscribeHost = subscribeHostInfo(() => {
|
|
8430
|
+
if (yieldTo()) withdraw();
|
|
8431
|
+
});
|
|
8432
|
+
disposeTab = tabs.register({
|
|
8433
|
+
id: NATIVE_TAB_ID,
|
|
8434
|
+
kind: NATIVE_TAB_KIND,
|
|
8435
|
+
title: () => t("nav"),
|
|
8436
|
+
guide: [{
|
|
8437
|
+
id: "theme",
|
|
8438
|
+
order: 55,
|
|
8439
|
+
title: () => t("nav"),
|
|
8440
|
+
description: () => t("guideDesc"),
|
|
8441
|
+
icon: SunIcon
|
|
8442
|
+
}]
|
|
8443
|
+
});
|
|
8444
|
+
return () => {
|
|
8445
|
+
unsubscribeBetter();
|
|
8446
|
+
unsubscribeHost();
|
|
8447
|
+
withdraw();
|
|
8448
|
+
};
|
|
8449
|
+
}, "dsh-any-background: native sidebar theme tab");
|
|
8450
|
+
scope.effect(() => ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register({
|
|
8451
|
+
name: "sidebar.right.pane.tab",
|
|
8452
|
+
key: NATIVE_TAB_ID
|
|
8453
|
+
}, ThemeBody)), "dsh-any-background: native sidebar theme body");
|
|
8454
|
+
});
|
|
8455
|
+
}
|
|
8456
|
+
//#endregion
|
|
8457
|
+
//#region src/client/header-tag.ts
|
|
8458
|
+
/**
|
|
8459
|
+
* Header-surface tagging.
|
|
8460
|
+
*
|
|
8461
|
+
* WHY A RUNTIME TAG IS NEEDED
|
|
8462
|
+
* The session-header dropdowns (open-in-app, session-log, Agent Team, the job
|
|
8463
|
+
* list, the subagent lineage tree) render their popover through the Menu/tree
|
|
8464
|
+
* primitives, which PORTAL the list to `document.body`. A portaled list is not a
|
|
8465
|
+
* DOM descendant of the header — no ancestor selector can reach it — and the
|
|
8466
|
+
* primitive emits no `id`/`aria-controls` back-link to its anchor either. Pure
|
|
8467
|
+
* CSS class matching was the remaining option, but 0.1.7 broke the shapes this
|
|
8468
|
+
* plugin relied on: open-in-app moved from in-place to `portal` (so the old
|
|
8469
|
+
* `:not(_portal)` discriminator no longer selected it), and the session row menu
|
|
8470
|
+
* became a dynamic `sidebar.workspaces.session.menu.item` slot whose rows are no
|
|
8471
|
+
* longer guaranteed to include a destructive one.
|
|
8472
|
+
*
|
|
8473
|
+
* THE SIGNAL
|
|
8474
|
+
* The slot renderer stamps every registered slot with `data-slot="<key>"`
|
|
8475
|
+
* (ui-renderer `scoped-slots.tsx`), and the session header's own slots are
|
|
8476
|
+
* `conversation.session.header{,.actions,.utilities,.corner,.lineage}` — present
|
|
8477
|
+
* on 0.1.5 through 0.1.7 alike. Those anchors live in the header subtree, so the
|
|
8478
|
+
* plugin can detect when a header trigger is expanded.
|
|
8479
|
+
*
|
|
8480
|
+
* WHY NOT GEOMETRY
|
|
8481
|
+
* A tempting link is the inline position the primitive writes on a portaled list
|
|
8482
|
+
* (`style="left: …; top: …"`). It was tried and REJECTED: an `align="end"` list
|
|
8483
|
+
* is flush to the trigger's right edge while the trigger itself sits at the far
|
|
8484
|
+
* right of the header, so the two boxes often do not overlap horizontally (in
|
|
8485
|
+
* the reported DOM the list is at `left:1012` with the trigger near x=1400). Any
|
|
8486
|
+
* purely geometric proximity test therefore misses real header menus.
|
|
8487
|
+
*
|
|
8488
|
+
* THE ACTUAL RULE (structural, no geometry, no timing)
|
|
8489
|
+
* A header dropdown can only be open while its trigger reports
|
|
8490
|
+
* `aria-expanded="true"`, and opening one is what mounts the popover. So a
|
|
8491
|
+
* popover tag is maintained as: "there is at least one expanded header trigger,
|
|
8492
|
+
* AND this popover is not owned by another group". Popovers are only ever
|
|
8493
|
+
* tagged/released on a change of that combined state, which makes the tag
|
|
8494
|
+
* self-consistent and idempotent.
|
|
8495
|
+
*
|
|
8496
|
+
* The one hazard is a popover that is open for an unrelated reason (a composer
|
|
8497
|
+
* menu, a hover card) *while* a header trigger stays expanded. Such a popover
|
|
8498
|
+
* is not a candidate: it is excluded by `ownsForeignSurface`, which skips any
|
|
8499
|
+
* popover carrying markers of a different slider group. In practice header
|
|
8500
|
+
* dropdowns are transient (opening one closes the others), so this is a guard
|
|
8501
|
+
* rather than the common path.
|
|
8502
|
+
*
|
|
8503
|
+
* @module
|
|
8504
|
+
*/
|
|
8505
|
+
/** Marker attribute the stylesheet keys on. */
|
|
8506
|
+
const HEADER_POPOVER_ATTR = "data-dsh-any-header-popover";
|
|
8507
|
+
/** Slot anchors that identify the session header's own surfaces. */
|
|
8508
|
+
const HEADER_SLOT_SELECTOR = [
|
|
8509
|
+
"[data-slot=\"conversation.session.header\"]",
|
|
8510
|
+
"[data-slot=\"conversation.session.header.actions\"]",
|
|
8511
|
+
"[data-slot=\"conversation.session.header.utilities\"]",
|
|
8512
|
+
"[data-slot=\"conversation.session.header.corner\"]",
|
|
8513
|
+
"[data-slot=\"conversation.session.header.lineage\"]"
|
|
8514
|
+
].join(",");
|
|
8515
|
+
/** Portaled popovers a header trigger can own. */
|
|
8516
|
+
const POPOVER_SELECTOR = "[role=\"menu\"], [role=\"tree\"], [role=\"dialog\"]";
|
|
8517
|
+
/** Whether any header slot anchor has an expanded trigger. */
|
|
8518
|
+
function headerTriggerOpen() {
|
|
8519
|
+
for (const anchor of document.querySelectorAll(HEADER_SLOT_SELECTOR)) if (anchor.querySelector("[aria-expanded=\"true\"]") !== null) return true;
|
|
8520
|
+
return false;
|
|
8521
|
+
}
|
|
8522
|
+
/** Surfaces that belong to a different slider group and must never be claimed.
|
|
8523
|
+
* `_denseList` without `_portal` is the in-place card-side dense menu, an
|
|
8524
|
+
* `_ioCard`/`_menu`-classed body-level list belongs elsewhere, and a modal is
|
|
8525
|
+
* its own surface. */
|
|
8526
|
+
function ownsForeignSurface(el) {
|
|
8527
|
+
if (el.getAttribute("aria-modal") === "true") return true;
|
|
8528
|
+
return el.parentElement !== document.body;
|
|
8529
|
+
}
|
|
8530
|
+
let observer = null;
|
|
8531
|
+
let raf = null;
|
|
8532
|
+
/** Recompute tags from the current open state. */
|
|
8533
|
+
function refreshHeaderPopovers() {
|
|
8534
|
+
const open = headerTriggerOpen();
|
|
8535
|
+
const candidates = [...document.querySelectorAll(POPOVER_SELECTOR)].filter((el) => !ownsForeignSurface(el));
|
|
8536
|
+
if (!open) {
|
|
8537
|
+
for (const el of document.querySelectorAll(`[${HEADER_POPOVER_ATTR}]`)) el.removeAttribute(HEADER_POPOVER_ATTR);
|
|
8538
|
+
return;
|
|
8539
|
+
}
|
|
8540
|
+
for (const el of candidates) el.setAttribute(HEADER_POPOVER_ATTR, "");
|
|
8541
|
+
for (const el of document.querySelectorAll(`[${HEADER_POPOVER_ATTR}]`)) if (!candidates.includes(el)) el.removeAttribute(HEADER_POPOVER_ATTR);
|
|
8542
|
+
}
|
|
8543
|
+
function schedule() {
|
|
8544
|
+
if (raf !== null) return;
|
|
8545
|
+
raf = window.requestAnimationFrame(() => {
|
|
8546
|
+
raf = null;
|
|
8547
|
+
refreshHeaderPopovers();
|
|
8548
|
+
});
|
|
8549
|
+
}
|
|
8550
|
+
/** Start tagging. Returns a teardown that stops observing and clears markings. */
|
|
8551
|
+
function startHeaderPopoverTagging() {
|
|
8552
|
+
if (typeof MutationObserver === "undefined" || typeof document === "undefined") return () => {};
|
|
8553
|
+
refreshHeaderPopovers();
|
|
8554
|
+
observer = new MutationObserver(schedule);
|
|
8555
|
+
observer.observe(document.body, {
|
|
8556
|
+
childList: true,
|
|
8557
|
+
subtree: true,
|
|
8558
|
+
attributes: true,
|
|
8559
|
+
attributeFilter: ["aria-expanded"]
|
|
8560
|
+
});
|
|
8561
|
+
return () => {
|
|
8562
|
+
observer?.disconnect();
|
|
8563
|
+
observer = null;
|
|
8564
|
+
if (raf !== null) {
|
|
8565
|
+
window.cancelAnimationFrame(raf);
|
|
8566
|
+
raf = null;
|
|
8567
|
+
}
|
|
8568
|
+
for (const el of document.querySelectorAll(`[${HEADER_POPOVER_ATTR}]`)) el.removeAttribute(HEADER_POPOVER_ATTR);
|
|
8569
|
+
};
|
|
8570
|
+
}
|
|
8571
|
+
//#endregion
|
|
7600
8572
|
//#region src/client/index.tsx
|
|
7601
8573
|
/**
|
|
7602
8574
|
* dsh-any-background — browser half entry.
|
|
@@ -7617,6 +8589,7 @@ window.__ModuleLoader__.load({
|
|
|
7617
8589
|
initRpc((endpoint, payload) => ctx.connection.rpc.call(RPC_CHANNEL, endpoint, payload).then((res) => res));
|
|
7618
8590
|
const [initH, initS, initL] = rColor();
|
|
7619
8591
|
let customDispose = null;
|
|
8592
|
+
let registerFailed = false;
|
|
7620
8593
|
const registerCustom = (h, s, l) => {
|
|
7621
8594
|
customDispose?.();
|
|
7622
8595
|
try {
|
|
@@ -7649,30 +8622,54 @@ window.__ModuleLoader__.load({
|
|
|
7649
8622
|
colorScheme,
|
|
7650
8623
|
tokens
|
|
7651
8624
|
});
|
|
7652
|
-
} catch {
|
|
8625
|
+
} catch (e) {
|
|
8626
|
+
if (!(() => {
|
|
8627
|
+
try {
|
|
8628
|
+
return ctx.theme.getTheme().themes.some((t) => t.id === CUSTOM_ID);
|
|
8629
|
+
} catch {
|
|
8630
|
+
return false;
|
|
8631
|
+
}
|
|
8632
|
+
})()) {
|
|
8633
|
+
if (!registerFailed) {
|
|
8634
|
+
registerFailed = true;
|
|
8635
|
+
console.error("dsh-any-background: host theme register failed; the custom skin will stay inactive", e);
|
|
8636
|
+
}
|
|
8637
|
+
customDispose = null;
|
|
8638
|
+
return false;
|
|
8639
|
+
}
|
|
7653
8640
|
customDispose = null;
|
|
7654
8641
|
}
|
|
7655
|
-
const present =
|
|
8642
|
+
const present = (() => {
|
|
8643
|
+
try {
|
|
8644
|
+
return ctx.theme.getTheme().themes.some((t) => t.id === CUSTOM_ID);
|
|
8645
|
+
} catch {
|
|
8646
|
+
return false;
|
|
8647
|
+
}
|
|
8648
|
+
})();
|
|
7656
8649
|
if (present) ctx.theme.setTheme(CUSTOM_ID);
|
|
7657
8650
|
return present;
|
|
7658
8651
|
};
|
|
7659
8652
|
if (rHasColor()) registerCustom(initH, initS, initL);
|
|
7660
|
-
onVerdictApplied(() => {
|
|
8653
|
+
const disposeVerdict = onVerdictApplied(() => {
|
|
7661
8654
|
if (!rHasColor()) registerCustom();
|
|
7662
8655
|
});
|
|
7663
|
-
onColorAdopted((hsl) => {
|
|
8656
|
+
const disposeColorAdopted = onColorAdopted((hsl) => {
|
|
7664
8657
|
registerCustom(hsl[0], hsl[1], hsl[2]);
|
|
7665
8658
|
saveConfig();
|
|
7666
8659
|
colorRev++;
|
|
7667
8660
|
bound?.syncColor(hslToHsv(hsl[0], hsl[1], hsl[2]), colorRev);
|
|
7668
8661
|
});
|
|
8662
|
+
ctx.effect(() => () => {
|
|
8663
|
+
disposeVerdict();
|
|
8664
|
+
disposeColorAdopted();
|
|
8665
|
+
}, "dsh-any-background: verdict/color listeners");
|
|
7669
8666
|
ctx.effect(() => () => {
|
|
7670
8667
|
customDispose?.();
|
|
7671
8668
|
if (colorTimerRef.current !== null) window.clearTimeout(colorTimerRef.current);
|
|
7672
8669
|
}, "dsh-any-background: skin dispose");
|
|
7673
8670
|
const styleEl = document.createElement("style");
|
|
7674
8671
|
styleEl.dataset.plugin = "dsh-any-background";
|
|
7675
|
-
styleEl.textContent = `body[data-ds-dark-theme="dsh-any-background"]::before{content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;background:radial-gradient(ellipse 80% 60% at 50% 0%,rgba(255,255,255,0.03) 0%,transparent 60%)}${SETTINGS_STYLE_RULE}${POPOVER_BLUR_RULE}${TRAJECTORY_STYLE_RULE}${INPUT_BLUR_RULE}${MOBILE_HEADER_RULE}${PANEL_TOKEN_RULE}${PANEL_BLUR_RULE}${PRODUCED_RULE}${STROKE_RULE}[data-composer-card] textarea::placeholder,[data-composer-card] input::placeholder,[data-composer-card] [contenteditable]::placeholder,[data-cordis-panel] input::placeholder,[data-cordis-panel] textarea::placeholder,.dab-input::placeholder,.dab-input textarea::placeholder,.dab-input input::placeholder{color:var(--dsh-any-placeholder,var(--dsw-alias-label-caption,#8a8f98))!important;font-style:italic;opacity:.85}`;
|
|
8672
|
+
styleEl.textContent = `body[data-ds-dark-theme="dsh-any-background"]::before{content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;background:radial-gradient(ellipse 80% 60% at 50% 0%,rgba(255,255,255,0.03) 0%,transparent 60%)}${SETTINGS_STYLE_RULE}${POPOVER_BLUR_RULE}${TRAJECTORY_STYLE_RULE}${INPUT_BLUR_RULE}${MOBILE_HEADER_RULE}${PANEL_TOKEN_RULE}${PANEL_PROMOTION_RULE}${PANEL_BLUR_RULE}${PRODUCED_RULE}${STROKE_RULE}${HEADER_POPOVER_RULE}${EXEMPT_DEFAULT_RULE}[data-composer-card] textarea::placeholder,[data-composer-card] input::placeholder,[data-composer-card] [contenteditable]::placeholder,[data-cordis-panel] input::placeholder,[data-cordis-panel] textarea::placeholder,.dab-input::placeholder,.dab-input textarea::placeholder,.dab-input input::placeholder{color:var(--dsh-any-placeholder,var(--dsw-alias-label-caption,#8a8f98))!important;font-style:italic;opacity:.85}`;
|
|
7676
8673
|
document.head.appendChild(styleEl);
|
|
7677
8674
|
ctx.effect(() => () => {
|
|
7678
8675
|
styleEl?.parentNode?.removeChild(styleEl);
|
|
@@ -7683,7 +8680,7 @@ window.__ModuleLoader__.load({
|
|
|
7683
8680
|
let colorRev = 0;
|
|
7684
8681
|
let bgRev = 0;
|
|
7685
8682
|
const colorTimerRef = { current: null };
|
|
7686
|
-
const
|
|
8683
|
+
const storeSpec = defineStore === null ? null : defineStore({
|
|
7687
8684
|
init: () => ({
|
|
7688
8685
|
url: null,
|
|
7689
8686
|
rev: -1,
|
|
@@ -7734,13 +8731,44 @@ window.__ModuleLoader__.load({
|
|
|
7734
8731
|
}
|
|
7735
8732
|
}
|
|
7736
8733
|
});
|
|
8734
|
+
/** One shared instance, whatever the host build handed back.
|
|
8735
|
+
*
|
|
8736
|
+
* The plugin publishes a single appearance state that BOTH surfaces read —
|
|
8737
|
+
* the settings section (through the renderer's `useStore`) and the
|
|
8738
|
+
* better-sidebar page (through the hook in `sidebar/store-hook`). Handing the
|
|
8739
|
+
* host a declaration whose `create` always returns this very instance is what
|
|
8740
|
+
* keeps them from drifting: letting the renderer mint its own would give the
|
|
8741
|
+
* sidebar a second, permanently empty copy of the state. */
|
|
8742
|
+
const storeInstance = storeSpec === null ? null : typeof storeSpec.getSnapshot === "function" ? storeSpec : storeSpec.create();
|
|
8743
|
+
/** What gets registered: the instance on newer builds, a single-instance
|
|
8744
|
+
* declaration on the ones that expect a declaration. */
|
|
8745
|
+
const store = storeSpec === null || storeInstance === storeSpec ? storeSpec : {
|
|
8746
|
+
spec: storeSpec.spec ?? {},
|
|
8747
|
+
create: () => storeInstance
|
|
8748
|
+
};
|
|
7737
8749
|
let bound = null;
|
|
8750
|
+
if (storeInstance !== null) bound = storeInstance.actions;
|
|
7738
8751
|
const syncBg = () => {
|
|
7739
8752
|
rev++;
|
|
7740
8753
|
bgRev++;
|
|
7741
8754
|
bound?.syncBg(rWp(), rev, cfg.backgroundType, cfg.generatedBg, bgRev, cfg.regenerateOnReload);
|
|
7742
8755
|
};
|
|
7743
|
-
onGeneratedSnapshot(syncBg);
|
|
8756
|
+
const disposeSnapshot = onGeneratedSnapshot(syncBg);
|
|
8757
|
+
ctx.effect(() => () => disposeSnapshot(), "dsh-any-background: snapshot listener");
|
|
8758
|
+
/** Capture the first frame of the video now applied and finish its
|
|
8759
|
+
* adaptation: the frame stands in for every still-image API (previews,
|
|
8760
|
+
* color extraction) and the brightness verdict follows it. The stale guard
|
|
8761
|
+
* skips a capture whose video was swapped out from under it (another
|
|
8762
|
+
* upload or a rotation switch landing mid-decode). */
|
|
8763
|
+
const captureAndApplyVideo = (url) => {
|
|
8764
|
+
if (url === null) return;
|
|
8765
|
+
captureVideoSnapshot(url).then((snap) => {
|
|
8766
|
+
if (rWpVideo() !== url) return;
|
|
8767
|
+
setWpVideoSnapshot(snap);
|
|
8768
|
+
applyThemeColor();
|
|
8769
|
+
syncBg();
|
|
8770
|
+
});
|
|
8771
|
+
};
|
|
7744
8772
|
let metaRev = 0;
|
|
7745
8773
|
const syncMetaNow = () => {
|
|
7746
8774
|
metaRev++;
|
|
@@ -7861,7 +8889,7 @@ window.__ModuleLoader__.load({
|
|
|
7861
8889
|
const scheduleTick = () => {
|
|
7862
8890
|
const sc = rSchedule();
|
|
7863
8891
|
if (!sc.enabled) return;
|
|
7864
|
-
const want = (sc.mode === "system" ? window.matchMedia?.("(prefers-color-scheme: dark)")
|
|
8892
|
+
const want = (sc.mode === "system" ? window.matchMedia?.("(prefers-color-scheme: dark)")?.matches ?? false : isNightNow(sc)) ? sc.nightProfile : sc.dayProfile;
|
|
7865
8893
|
if (!want || want === cfg.activeProfile) return;
|
|
7866
8894
|
applyProfileById(want);
|
|
7867
8895
|
};
|
|
@@ -7889,12 +8917,7 @@ window.__ModuleLoader__.load({
|
|
|
7889
8917
|
if (cfg.backgroundType === "video") {
|
|
7890
8918
|
const v = rWpVideo();
|
|
7891
8919
|
if (v) {
|
|
7892
|
-
|
|
7893
|
-
if (rWpVideo() !== v) return;
|
|
7894
|
-
setWpVideoSnapshot(snap);
|
|
7895
|
-
applyThemeColor();
|
|
7896
|
-
syncBg();
|
|
7897
|
-
});
|
|
8920
|
+
captureAndApplyVideo(v);
|
|
7898
8921
|
applyWp();
|
|
7899
8922
|
} else {
|
|
7900
8923
|
cfg.backgroundType = "image";
|
|
@@ -7924,6 +8947,7 @@ window.__ModuleLoader__.load({
|
|
|
7924
8947
|
teardownWp();
|
|
7925
8948
|
}, "dsh-any-background: wp cleanup");
|
|
7926
8949
|
ctx.effect(() => startBetterSidebarWatch(), "dsh-any-background: better-sidebar watch");
|
|
8950
|
+
ctx.effect(() => startHeaderPopoverTagging(), "dsh-any-background: header popover tagging");
|
|
7927
8951
|
ctx.effect(() => ctx.on("theme/change", () => {
|
|
7928
8952
|
if (rHasColor() || rBgDark() !== null || rSchemeOverride() !== "auto") {
|
|
7929
8953
|
const snapshot = ctx.theme.getTheme();
|
|
@@ -7944,19 +8968,25 @@ window.__ModuleLoader__.load({
|
|
|
7944
8968
|
document.body.append(sentinel);
|
|
7945
8969
|
const viewportObserver = new ResizeObserver(applySoon);
|
|
7946
8970
|
viewportObserver.observe(sentinel);
|
|
7947
|
-
const dprQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
|
|
7948
|
-
dprQuery
|
|
8971
|
+
const dprQuery = window.matchMedia?.(`(resolution: ${window.devicePixelRatio}dppx)`);
|
|
8972
|
+
dprQuery?.addEventListener?.("change", applySoon);
|
|
7949
8973
|
ctx.effect(() => () => {
|
|
7950
8974
|
viewportObserver.disconnect();
|
|
7951
|
-
dprQuery
|
|
8975
|
+
dprQuery?.removeEventListener?.("change", applySoon);
|
|
7952
8976
|
sentinel.remove();
|
|
7953
8977
|
}, "dsh-any-background: viewport watch");
|
|
7954
8978
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
7955
8979
|
zh,
|
|
7956
8980
|
en
|
|
7957
8981
|
}), "dsh-any-background: i18n");
|
|
7958
|
-
|
|
7959
|
-
|
|
8982
|
+
/** The business face, built once on first use and shared by both surfaces
|
|
8983
|
+
* (the settings section and the better-sidebar page). Memoizing is not just
|
|
8984
|
+
* about cost: the face closes over per-surface state — the font preview blob
|
|
8985
|
+
* URL behind releaseFontPreview — so a second build would give the sidebar
|
|
8986
|
+
* its own preview slot and leak whichever copy never gets released. */
|
|
8987
|
+
let themeFace = null;
|
|
8988
|
+
const buildFace = () => {
|
|
8989
|
+
if (themeFace !== null) return themeFace;
|
|
7960
8990
|
syncBg();
|
|
7961
8991
|
syncMetaNow();
|
|
7962
8992
|
const playVideoFromBlob = (blob, mime) => {
|
|
@@ -7967,15 +8997,10 @@ window.__ModuleLoader__.load({
|
|
|
7967
8997
|
setWpVideoUrl(localUrl, mime ?? blob.type ?? "video/mp4");
|
|
7968
8998
|
applyWp();
|
|
7969
8999
|
syncBg();
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
if (
|
|
7973
|
-
|
|
7974
|
-
applyThemeColor();
|
|
7975
|
-
syncBg();
|
|
7976
|
-
});
|
|
7977
|
-
uploadVideo(blob, mime ?? blob.type ?? "video/mp4").then((ok) => {
|
|
7978
|
-
if (ok) persistConfig();
|
|
9000
|
+
captureAndApplyVideo(rWpVideo());
|
|
9001
|
+
return uploadVideo(blob, mime ?? blob.type ?? "video/mp4").then((outcome) => {
|
|
9002
|
+
if (outcome.ok) persistConfig();
|
|
9003
|
+
return outcome;
|
|
7979
9004
|
});
|
|
7980
9005
|
};
|
|
7981
9006
|
const FONT_EXT_MIME = {
|
|
@@ -8002,7 +9027,7 @@ window.__ModuleLoader__.load({
|
|
|
8002
9027
|
};
|
|
8003
9028
|
const [wh, ws, wl] = rColor();
|
|
8004
9029
|
const [dh, ds, dv] = hslToHsv(wh, ws, wl);
|
|
8005
|
-
|
|
9030
|
+
const built = {
|
|
8006
9031
|
t: ctx.locale.bind(NS),
|
|
8007
9032
|
hue: dh,
|
|
8008
9033
|
sat: ds,
|
|
@@ -8052,13 +9077,11 @@ window.__ModuleLoader__.load({
|
|
|
8052
9077
|
applyThemeColor();
|
|
8053
9078
|
syncBg();
|
|
8054
9079
|
saveConfig();
|
|
8055
|
-
return;
|
|
8056
|
-
}
|
|
8057
|
-
if (typeof u !== "string") {
|
|
8058
|
-
playVideoFromBlob(u, mime);
|
|
8059
|
-
return;
|
|
9080
|
+
return { ok: true };
|
|
8060
9081
|
}
|
|
8061
|
-
|
|
9082
|
+
if (typeof u !== "string") return playVideoFromBlob(u, mime);
|
|
9083
|
+
const ok = await persistVideo(u);
|
|
9084
|
+
const live = ok ? VIDEO_SERVE_URL : u;
|
|
8062
9085
|
cfg.backgroundType = "video";
|
|
8063
9086
|
setWpUrl(null);
|
|
8064
9087
|
cfg.videoBgState = { ...DEFAULT_CONFIG.bgState };
|
|
@@ -8066,13 +9089,8 @@ window.__ModuleLoader__.load({
|
|
|
8066
9089
|
applyWp();
|
|
8067
9090
|
saveConfig();
|
|
8068
9091
|
syncBg();
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
if (rWpVideo() !== applied) return;
|
|
8072
|
-
setWpVideoSnapshot(snap);
|
|
8073
|
-
applyThemeColor();
|
|
8074
|
-
syncBg();
|
|
8075
|
-
});
|
|
9092
|
+
captureAndApplyVideo(rWpVideo());
|
|
9093
|
+
return { ok };
|
|
8076
9094
|
},
|
|
8077
9095
|
setBgType: (type) => {
|
|
8078
9096
|
setBackgroundType(type);
|
|
@@ -8116,18 +9134,21 @@ window.__ModuleLoader__.load({
|
|
|
8116
9134
|
const localUrl = URL.createObjectURL(file);
|
|
8117
9135
|
fontPreviewUrl = localUrl;
|
|
8118
9136
|
applyFontFace(localUrl, true, fontMimeFromName(file.name));
|
|
8119
|
-
const
|
|
8120
|
-
if (mime ===
|
|
9137
|
+
const outcome = await uploadFont(file);
|
|
9138
|
+
if (!outcome.ok || outcome.mime === void 0) {
|
|
8121
9139
|
applyStoredFont();
|
|
8122
9140
|
releaseFontPreview(4e3);
|
|
8123
|
-
return
|
|
9141
|
+
return {
|
|
9142
|
+
ok: false,
|
|
9143
|
+
refusal: outcome.refusal
|
|
9144
|
+
};
|
|
8124
9145
|
}
|
|
8125
|
-
cfg.fontMime = mime;
|
|
9146
|
+
cfg.fontMime = outcome.mime;
|
|
8126
9147
|
cfg.fontEnabled = true;
|
|
8127
|
-
applyFontFace(FONT_SERVE_URL, true, mime);
|
|
9148
|
+
applyFontFace(FONT_SERVE_URL, true, outcome.mime);
|
|
8128
9149
|
releaseFontPreview(4e3);
|
|
8129
9150
|
persistConfig();
|
|
8130
|
-
return true;
|
|
9151
|
+
return { ok: true };
|
|
8131
9152
|
},
|
|
8132
9153
|
removeFont: () => {
|
|
8133
9154
|
releaseFontPreview(0);
|
|
@@ -8213,7 +9234,7 @@ window.__ModuleLoader__.load({
|
|
|
8213
9234
|
a.href = url;
|
|
8214
9235
|
a.download = "dsh-any-theme.json";
|
|
8215
9236
|
a.click();
|
|
8216
|
-
URL.revokeObjectURL(url);
|
|
9237
|
+
window.setTimeout(() => URL.revokeObjectURL(url), 4e3);
|
|
8217
9238
|
},
|
|
8218
9239
|
importTheme: async (file) => {
|
|
8219
9240
|
try {
|
|
@@ -8233,16 +9254,9 @@ window.__ModuleLoader__.load({
|
|
|
8233
9254
|
}
|
|
8234
9255
|
if (blob !== null) playVideoFromBlob(blob, cfg.videoMime);
|
|
8235
9256
|
else {
|
|
8236
|
-
|
|
8237
|
-
setWpVideoUrl(live, cfg.videoMime);
|
|
9257
|
+
setWpVideoUrl(await persistVideo(video) ? VIDEO_SERVE_URL : video, cfg.videoMime);
|
|
8238
9258
|
applyWp();
|
|
8239
|
-
|
|
8240
|
-
captureVideoSnapshot(live).then((snap) => {
|
|
8241
|
-
if (rWpVideo() !== applied) return;
|
|
8242
|
-
setWpVideoSnapshot(snap);
|
|
8243
|
-
applyThemeColor();
|
|
8244
|
-
syncBg();
|
|
8245
|
-
});
|
|
9259
|
+
captureAndApplyVideo(rWpVideo());
|
|
8246
9260
|
}
|
|
8247
9261
|
} else {
|
|
8248
9262
|
setWpVideoUrl(null, null);
|
|
@@ -8401,18 +9415,19 @@ window.__ModuleLoader__.load({
|
|
|
8401
9415
|
applyWp();
|
|
8402
9416
|
saveConfig();
|
|
8403
9417
|
syncBg();
|
|
8404
|
-
|
|
8405
|
-
captureVideoSnapshot(applied).then((snap) => {
|
|
8406
|
-
if (rWpVideo() !== applied) return;
|
|
8407
|
-
setWpVideoSnapshot(snap);
|
|
8408
|
-
applyThemeColor();
|
|
8409
|
-
syncBg();
|
|
8410
|
-
});
|
|
9418
|
+
captureAndApplyVideo(rWpVideo());
|
|
8411
9419
|
return true;
|
|
8412
9420
|
}
|
|
8413
9421
|
};
|
|
9422
|
+
themeFace = built;
|
|
9423
|
+
return built;
|
|
8414
9424
|
};
|
|
8415
|
-
|
|
9425
|
+
const sectionInject = (actions) => {
|
|
9426
|
+
bound = actions;
|
|
9427
|
+
return buildFace();
|
|
9428
|
+
};
|
|
9429
|
+
if (store === null) console.error("dsh-any-background: settings panel disabled on this host (no store module)");
|
|
9430
|
+
else ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
8416
9431
|
name: "settings.section",
|
|
8417
9432
|
id: "dsh-any-background",
|
|
8418
9433
|
order: 35,
|
|
@@ -8421,6 +9436,14 @@ window.__ModuleLoader__.load({
|
|
|
8421
9436
|
store,
|
|
8422
9437
|
inject: sectionInject
|
|
8423
9438
|
}, ThemeSection));
|
|
9439
|
+
registerThemeSidebarTab(ctx, {
|
|
9440
|
+
face: buildFace,
|
|
9441
|
+
store: storeInstance
|
|
9442
|
+
});
|
|
9443
|
+
registerNativeSidebarTab(ctx, {
|
|
9444
|
+
face: buildFace,
|
|
9445
|
+
store: storeInstance
|
|
9446
|
+
});
|
|
8424
9447
|
const navLabel = () => ctx.locale.bind(NS)("nav");
|
|
8425
9448
|
const applyNavIcon = () => {
|
|
8426
9449
|
const nav = document.querySelector("[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]")?.querySelector("nav");
|