dsh-any-background 0.3.10 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -12
- package/README.zh.md +13 -12
- package/lib/client.js +313 -92
- package/lib/client.js.map +1 -1
- package/lib/index.js +325 -73
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -27,7 +27,6 @@ window.__ModuleLoader__.load({
|
|
|
27
27
|
const zh = {
|
|
28
28
|
nav: "主题",
|
|
29
29
|
brandTag: "外观定制",
|
|
30
|
-
close: "关闭",
|
|
31
30
|
pageColor: "色彩",
|
|
32
31
|
pageInterface: "界面",
|
|
33
32
|
pageFont: "字体",
|
|
@@ -78,7 +77,6 @@ window.__ModuleLoader__.load({
|
|
|
78
77
|
strokeTitle: "文字描边",
|
|
79
78
|
strokeHint: "按区域为文字添加描边:粗细为 0 表示关闭,「自动」会生成与文字颜色相反的颜色,「主题色」跟随当前主题色。代码块与图标已自动豁免,多色语法不会糊成一团",
|
|
80
79
|
strokeWidth: "描边粗细",
|
|
81
|
-
strokeColor: "描边颜色",
|
|
82
80
|
strokeAuto: "自动反色",
|
|
83
81
|
strokeGray: "灰色",
|
|
84
82
|
strokeBlack: "黑色",
|
|
@@ -118,7 +116,6 @@ window.__ModuleLoader__.load({
|
|
|
118
116
|
editorReset: "重置",
|
|
119
117
|
extractColor: "从背景提取主题色",
|
|
120
118
|
extracting: "取色中…",
|
|
121
|
-
extractNoWp: "请先设置背景图片",
|
|
122
119
|
extractDone: "已应用背景主色调",
|
|
123
120
|
extractFail: "未在背景中找到鲜明的颜色,请换一张背景",
|
|
124
121
|
crashTitle: "界面渲染出错",
|
|
@@ -138,7 +135,6 @@ window.__ModuleLoader__.load({
|
|
|
138
135
|
pickerTitle: "从背景取色",
|
|
139
136
|
pickerHint: "移动鼠标预览颜色,点击背景选取为主题色",
|
|
140
137
|
pickerClose: "关闭",
|
|
141
|
-
bgTypeImage: "图片",
|
|
142
138
|
bgTypeMesh: "网格渐变",
|
|
143
139
|
bgTypeShader: "Shader",
|
|
144
140
|
bgTypePattern: "几何图案",
|
|
@@ -194,8 +190,12 @@ window.__ModuleLoader__.load({
|
|
|
194
190
|
profileNamePlaceholder: "输入配置名称…",
|
|
195
191
|
profileNameRequired: "请先输入配置名称",
|
|
196
192
|
profileSaved: "配置已保存",
|
|
193
|
+
profileRenamed: "配置已重命名",
|
|
194
|
+
profileDuplicated: "配置已复制",
|
|
197
195
|
profileApplied: "配置已应用",
|
|
198
196
|
profileApply: "应用",
|
|
197
|
+
profileRename: "重命名",
|
|
198
|
+
profileDuplicate: "复制配置",
|
|
199
199
|
profileDelete: "删除配置",
|
|
200
200
|
profileDeleteConfirm: "确认删除",
|
|
201
201
|
profileNone: "不切换",
|
|
@@ -218,6 +218,7 @@ window.__ModuleLoader__.load({
|
|
|
218
218
|
rotShuffle: "随机",
|
|
219
219
|
rotOrder: "顺序",
|
|
220
220
|
rotReload: "每次刷新",
|
|
221
|
+
rotHourly: "每小时",
|
|
221
222
|
rotDaily: "每天",
|
|
222
223
|
rotWeekly: "每周",
|
|
223
224
|
rotNow: "立即切换"
|
|
@@ -225,7 +226,6 @@ window.__ModuleLoader__.load({
|
|
|
225
226
|
const en = {
|
|
226
227
|
nav: "Theme",
|
|
227
228
|
brandTag: "Appearance",
|
|
228
|
-
close: "Close",
|
|
229
229
|
pageColor: "Color",
|
|
230
230
|
pageInterface: "Interface",
|
|
231
231
|
pageFont: "Font",
|
|
@@ -256,7 +256,7 @@ window.__ModuleLoader__.load({
|
|
|
256
256
|
uiHeader: "Header popovers",
|
|
257
257
|
uiHeaderBar: "Header bar",
|
|
258
258
|
captionInkLabel: "Caption strip text",
|
|
259
|
-
captionInkHint: "Who paints the text on the desktop caption strip — the native
|
|
259
|
+
captionInkHint: "Who paints the text on the desktop caption strip — the native \"App / Edit\" menu bar and the sidebar toggle. The strip is the sidebar material over the wallpaper, so it can end up on the opposite side of the theme.",
|
|
260
260
|
captionInkAuto: "Auto",
|
|
261
261
|
captionInkTheme: "Follow theme",
|
|
262
262
|
captionInkWhite: "Always white",
|
|
@@ -276,7 +276,6 @@ window.__ModuleLoader__.load({
|
|
|
276
276
|
strokeTitle: "Text outline",
|
|
277
277
|
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",
|
|
278
278
|
strokeWidth: "Outline width",
|
|
279
|
-
strokeColor: "Outline color",
|
|
280
279
|
strokeAuto: "Auto contrast",
|
|
281
280
|
strokeGray: "Gray",
|
|
282
281
|
strokeBlack: "Black",
|
|
@@ -316,7 +315,6 @@ window.__ModuleLoader__.load({
|
|
|
316
315
|
editorReset: "Reset",
|
|
317
316
|
extractColor: "Extract from background",
|
|
318
317
|
extracting: "Extracting…",
|
|
319
|
-
extractNoWp: "Set a background first",
|
|
320
318
|
extractDone: "Background color applied",
|
|
321
319
|
extractFail: "No vivid color found in this background, try another",
|
|
322
320
|
crashTitle: "Section crashed",
|
|
@@ -336,7 +334,6 @@ window.__ModuleLoader__.load({
|
|
|
336
334
|
pickerTitle: "Pick from background",
|
|
337
335
|
pickerHint: "Hover to preview, click to pick as theme color",
|
|
338
336
|
pickerClose: "Close",
|
|
339
|
-
bgTypeImage: "Image",
|
|
340
337
|
bgTypeMesh: "Mesh gradient",
|
|
341
338
|
bgTypeShader: "Shader",
|
|
342
339
|
bgTypePattern: "Pattern",
|
|
@@ -392,8 +389,12 @@ window.__ModuleLoader__.load({
|
|
|
392
389
|
profileNamePlaceholder: "Profile name…",
|
|
393
390
|
profileNameRequired: "Enter a name first",
|
|
394
391
|
profileSaved: "Profile saved",
|
|
392
|
+
profileRenamed: "Profile renamed",
|
|
393
|
+
profileDuplicated: "Profile duplicated",
|
|
395
394
|
profileApplied: "Profile applied",
|
|
396
395
|
profileApply: "Apply",
|
|
396
|
+
profileRename: "Rename",
|
|
397
|
+
profileDuplicate: "Duplicate",
|
|
397
398
|
profileDelete: "Delete profile",
|
|
398
399
|
profileDeleteConfirm: "Sure?",
|
|
399
400
|
profileNone: "None",
|
|
@@ -416,6 +417,7 @@ window.__ModuleLoader__.load({
|
|
|
416
417
|
rotShuffle: "Shuffle",
|
|
417
418
|
rotOrder: "In order",
|
|
418
419
|
rotReload: "Every refresh",
|
|
420
|
+
rotHourly: "Hourly",
|
|
419
421
|
rotDaily: "Daily",
|
|
420
422
|
rotWeekly: "Weekly",
|
|
421
423
|
rotNow: "Switch now"
|
|
@@ -902,7 +904,7 @@ window.__ModuleLoader__.load({
|
|
|
902
904
|
return {
|
|
903
905
|
enabled: r.enabled === true,
|
|
904
906
|
mode: r.mode === "order" ? "order" : "shuffle",
|
|
905
|
-
interval: r.interval === "reload" || r.interval === "weekly" ? r.interval : "daily",
|
|
907
|
+
interval: r.interval === "reload" || r.interval === "hourly" || r.interval === "weekly" ? r.interval : "daily",
|
|
906
908
|
current: num(r.current, 0),
|
|
907
909
|
items,
|
|
908
910
|
lastRotate: typeof r.lastRotate === "string" ? r.lastRotate : null
|
|
@@ -1879,22 +1881,44 @@ window.__ModuleLoader__.load({
|
|
|
1879
1881
|
}
|
|
1880
1882
|
const SAVE_DEBOUNCE_MS = 250;
|
|
1881
1883
|
let saveTimer;
|
|
1884
|
+
/** Write the config and SURFACE a failed persist. The server answers
|
|
1885
|
+
* `ok:true` with `value:false` when the DISK write failed — the transport
|
|
1886
|
+
* succeeded, so `rpcCall` returns the boolean without a word and every caller
|
|
1887
|
+
* used to assume the save had landed. Retry once after a beat (the common
|
|
1888
|
+
* Windows cause is a transient EBUSY from an antivirus scan holding the
|
|
1889
|
+
* file), then say it in the log so a "settings reverted" report has a trail. */
|
|
1890
|
+
let writeRetryQueued = false;
|
|
1891
|
+
async function writeNow() {
|
|
1892
|
+
if (await rpcCall("writeConfig", { config: cfg }) === true) {
|
|
1893
|
+
writeRetryQueued = false;
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
if (!writeRetryQueued) {
|
|
1897
|
+
writeRetryQueued = true;
|
|
1898
|
+
window.setTimeout(() => {
|
|
1899
|
+
writeNow();
|
|
1900
|
+
}, 1e3);
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
writeRetryQueued = false;
|
|
1904
|
+
console.warn("dsh-any-background: the host could not persist the config — recent settings may be lost on reload (see the host log for the write error)");
|
|
1905
|
+
}
|
|
1882
1906
|
function saveConfig() {
|
|
1883
1907
|
if (saveTimer !== void 0) window.clearTimeout(saveTimer);
|
|
1884
1908
|
saveTimer = window.setTimeout(() => {
|
|
1885
1909
|
saveTimer = void 0;
|
|
1886
|
-
|
|
1910
|
+
writeNow();
|
|
1887
1911
|
}, SAVE_DEBOUNCE_MS);
|
|
1888
1912
|
}
|
|
1889
1913
|
function flushSave() {
|
|
1890
1914
|
if (saveTimer === void 0) return;
|
|
1891
1915
|
window.clearTimeout(saveTimer);
|
|
1892
1916
|
saveTimer = void 0;
|
|
1893
|
-
|
|
1917
|
+
writeNow();
|
|
1894
1918
|
}
|
|
1895
1919
|
/** Persist the current config immediately (import path — no debounce). */
|
|
1896
1920
|
function persistConfig() {
|
|
1897
|
-
|
|
1921
|
+
writeNow();
|
|
1898
1922
|
}
|
|
1899
1923
|
/** Load the persisted theme (config + wallpaper URL + video URL) from the node
|
|
1900
1924
|
* half. Image and video both travel as serve URLs — never bytes — so this RPC
|
|
@@ -2092,6 +2116,10 @@ window.__ModuleLoader__.load({
|
|
|
2092
2116
|
async function removeFont() {
|
|
2093
2117
|
return await rpcCall("removeFont", {}) === true;
|
|
2094
2118
|
}
|
|
2119
|
+
/** Serve URL of one rotation-pool image (the export path fetches its bytes). */
|
|
2120
|
+
function rotationFileUrl(file) {
|
|
2121
|
+
return `/dsh-any-background/rotation/file?f=${encodeURIComponent(file)}`;
|
|
2122
|
+
}
|
|
2095
2123
|
/** Add an image (data URL + small thumbnail) to the server-side rotation pool. */
|
|
2096
2124
|
async function rotationAdd(dataUrl, thumb) {
|
|
2097
2125
|
if (!rpcCallFn) return {
|
|
@@ -2165,6 +2193,32 @@ window.__ModuleLoader__.load({
|
|
|
2165
2193
|
};
|
|
2166
2194
|
}
|
|
2167
2195
|
}
|
|
2196
|
+
/** Replace the whole server-side rotation pool (theme-import path): the bytes
|
|
2197
|
+
* land under their exported file names, so the imported config's pool
|
|
2198
|
+
* metadata is live on this machine instead of pointing at files it does not
|
|
2199
|
+
* have. */
|
|
2200
|
+
async function rotationRestore(items) {
|
|
2201
|
+
if (!rpcCallFn) return {
|
|
2202
|
+
ok: false,
|
|
2203
|
+
error: "rpc not ready"
|
|
2204
|
+
};
|
|
2205
|
+
try {
|
|
2206
|
+
const res = await rpcCallFn(rpcEndpoint("rotationRestore"), { items });
|
|
2207
|
+
if (res && res.ok === true) return res.value ?? {
|
|
2208
|
+
ok: false,
|
|
2209
|
+
error: "no value"
|
|
2210
|
+
};
|
|
2211
|
+
return {
|
|
2212
|
+
ok: false,
|
|
2213
|
+
error: res?.error?.message ?? "request failed"
|
|
2214
|
+
};
|
|
2215
|
+
} catch (e) {
|
|
2216
|
+
return {
|
|
2217
|
+
ok: false,
|
|
2218
|
+
error: e instanceof Error ? e.message : String(e)
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2168
2222
|
//#endregion
|
|
2169
2223
|
//#region src/client/host-compat/capabilities.ts
|
|
2170
2224
|
/**
|
|
@@ -2188,6 +2242,32 @@ window.__ModuleLoader__.load({
|
|
|
2188
2242
|
return rHostAdapter();
|
|
2189
2243
|
}
|
|
2190
2244
|
//#endregion
|
|
2245
|
+
//#region src/client/dom-watch.ts
|
|
2246
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2247
|
+
let observer = null;
|
|
2248
|
+
/** Subscribe to body-subtree mutations. Returns an unsubscribe. */
|
|
2249
|
+
function watchDom(cb) {
|
|
2250
|
+
listeners.add(cb);
|
|
2251
|
+
if (observer === null && typeof MutationObserver !== "undefined" && typeof document !== "undefined") {
|
|
2252
|
+
observer = new MutationObserver((records) => {
|
|
2253
|
+
for (const l of listeners) l(records);
|
|
2254
|
+
});
|
|
2255
|
+
observer.observe(document.body, {
|
|
2256
|
+
childList: true,
|
|
2257
|
+
subtree: true,
|
|
2258
|
+
attributes: true,
|
|
2259
|
+
attributeFilter: ["aria-expanded"]
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
return () => {
|
|
2263
|
+
listeners.delete(cb);
|
|
2264
|
+
if (listeners.size === 0) {
|
|
2265
|
+
observer?.disconnect();
|
|
2266
|
+
observer = null;
|
|
2267
|
+
}
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
//#endregion
|
|
2191
2271
|
//#region src/client/header-tag.ts
|
|
2192
2272
|
/**
|
|
2193
2273
|
* Header-surface tagging.
|
|
@@ -2292,7 +2372,6 @@ window.__ModuleLoader__.load({
|
|
|
2292
2372
|
if (el.getAttribute("aria-modal") === "true") return false;
|
|
2293
2373
|
return el.parentElement === document.body || el.closest(headerSlotAnchors()) !== null;
|
|
2294
2374
|
}
|
|
2295
|
-
let observer = null;
|
|
2296
2375
|
let pending = null;
|
|
2297
2376
|
/** False once the effect is torn down, so a pass already queued on the microtask queue
|
|
2298
2377
|
* cannot re-mark a popover after the teardown cleared it. */
|
|
@@ -2333,17 +2412,10 @@ window.__ModuleLoader__.load({
|
|
|
2333
2412
|
if (typeof MutationObserver === "undefined" || typeof document === "undefined") return () => {};
|
|
2334
2413
|
active = true;
|
|
2335
2414
|
refreshHeaderPopovers();
|
|
2336
|
-
|
|
2337
|
-
observer.observe(document.body, {
|
|
2338
|
-
childList: true,
|
|
2339
|
-
subtree: true,
|
|
2340
|
-
attributes: true,
|
|
2341
|
-
attributeFilter: ["aria-expanded"]
|
|
2342
|
-
});
|
|
2415
|
+
const dispose = watchDom(() => schedule());
|
|
2343
2416
|
return () => {
|
|
2344
2417
|
active = false;
|
|
2345
|
-
|
|
2346
|
-
observer = null;
|
|
2418
|
+
dispose();
|
|
2347
2419
|
for (const el of document.querySelectorAll(`[${HEADER_POPOVER_ATTR}]`)) el.removeAttribute(HEADER_POPOVER_ATTR);
|
|
2348
2420
|
};
|
|
2349
2421
|
}
|
|
@@ -5176,8 +5248,11 @@ window.__ModuleLoader__.load({
|
|
|
5176
5248
|
});
|
|
5177
5249
|
syncTableFix();
|
|
5178
5250
|
}
|
|
5179
|
-
let
|
|
5180
|
-
let
|
|
5251
|
+
let partsDispose = null;
|
|
5252
|
+
let partsPending = null;
|
|
5253
|
+
/** False once stopped, so a pass already queued on the microtask queue cannot
|
|
5254
|
+
* re-style anything after the teardown. */
|
|
5255
|
+
let partsActive = false;
|
|
5181
5256
|
/** Signature of the last blur+opacity pass we actually wrote to the DOM. */
|
|
5182
5257
|
let appliedPartsKey = "";
|
|
5183
5258
|
/** The surfaces the last pass styled, compared BY IDENTITY: the host can swap
|
|
@@ -5217,11 +5292,13 @@ window.__ModuleLoader__.load({
|
|
|
5217
5292
|
/** Watch for the AppFrame mounting so persisted blurs land even when the shell
|
|
5218
5293
|
* renders after this plugin's apply. */
|
|
5219
5294
|
function watchParts() {
|
|
5220
|
-
if (
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5295
|
+
if (partsDispose !== null || typeof MutationObserver === "undefined") return;
|
|
5296
|
+
partsActive = true;
|
|
5297
|
+
partsDispose = watchDom(() => {
|
|
5298
|
+
if (partsPending !== null || !partsActive) return;
|
|
5299
|
+
partsPending = Promise.resolve().then(() => {
|
|
5300
|
+
partsPending = null;
|
|
5301
|
+
if (!partsActive) return;
|
|
5225
5302
|
const blurs = rBlurs();
|
|
5226
5303
|
const ops = rOps();
|
|
5227
5304
|
const key = JSON.stringify([blurs, ops]);
|
|
@@ -5232,18 +5309,12 @@ window.__ModuleLoader__.load({
|
|
|
5232
5309
|
appliedTargets = targetsNow();
|
|
5233
5310
|
});
|
|
5234
5311
|
});
|
|
5235
|
-
partsObserver.observe(document.body, {
|
|
5236
|
-
childList: true,
|
|
5237
|
-
subtree: true
|
|
5238
|
-
});
|
|
5239
5312
|
}
|
|
5240
5313
|
function stopWatchingParts() {
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
partsApplyRaf = 0;
|
|
5246
|
-
}
|
|
5314
|
+
partsDispose?.();
|
|
5315
|
+
partsDispose = null;
|
|
5316
|
+
partsActive = false;
|
|
5317
|
+
partsPending = null;
|
|
5247
5318
|
appliedPartsKey = "";
|
|
5248
5319
|
appliedTargets = [];
|
|
5249
5320
|
markerSeen.clear();
|
|
@@ -5556,8 +5627,6 @@ window.__ModuleLoader__.load({
|
|
|
5556
5627
|
applyTrajectoryOverrides(rTrajectoryOpacity());
|
|
5557
5628
|
}
|
|
5558
5629
|
applyPanelOverrides(rPanelOpacity());
|
|
5559
|
-
applyProduced();
|
|
5560
|
-
applyHeaderPopovers();
|
|
5561
5630
|
applyExemptDefaults();
|
|
5562
5631
|
applyPartBlurs(rBlurs());
|
|
5563
5632
|
applyStrokes();
|
|
@@ -8793,6 +8862,12 @@ window.__ModuleLoader__.load({
|
|
|
8793
8862
|
onClick: () => setRotation({ interval: "reload" }),
|
|
8794
8863
|
children: t("rotReload")
|
|
8795
8864
|
}),
|
|
8865
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8866
|
+
type: "button",
|
|
8867
|
+
className: `dab-chip${rotation.interval === "hourly" ? " is-active" : ""}`,
|
|
8868
|
+
onClick: () => setRotation({ interval: "hourly" }),
|
|
8869
|
+
children: t("rotHourly")
|
|
8870
|
+
}),
|
|
8796
8871
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8797
8872
|
type: "button",
|
|
8798
8873
|
className: `dab-chip${rotation.interval === "daily" ? " is-active" : ""}`,
|
|
@@ -9322,7 +9397,7 @@ window.__ModuleLoader__.load({
|
|
|
9322
9397
|
});
|
|
9323
9398
|
}
|
|
9324
9399
|
function ProfilePage({ p, notify }) {
|
|
9325
|
-
const { t, exportTheme, importTheme, saveProfile, applyProfile, deleteProfile, applyPreset, setSchedule, useStore } = p;
|
|
9400
|
+
const { t, exportTheme, importTheme, saveProfile, applyProfile, deleteProfile, renameProfile, duplicateProfile, applyPreset, setSchedule, useStore } = p;
|
|
9326
9401
|
const profiles = useStore((s) => s.profiles);
|
|
9327
9402
|
const schedule = useStore((s) => s.schedule);
|
|
9328
9403
|
const activeProfile = useStore((s) => s.activeProfile);
|
|
@@ -9330,6 +9405,8 @@ window.__ModuleLoader__.load({
|
|
|
9330
9405
|
const [nameOpen, setNameOpen] = (0, react.useState)(false);
|
|
9331
9406
|
const [nameVal, setNameVal] = (0, react.useState)("");
|
|
9332
9407
|
const [confirmId, setConfirmId] = (0, react.useState)(null);
|
|
9408
|
+
const [editId, setEditId] = (0, react.useState)(null);
|
|
9409
|
+
const [editVal, setEditVal] = (0, react.useState)("");
|
|
9333
9410
|
const onImport = async (file) => {
|
|
9334
9411
|
try {
|
|
9335
9412
|
const ok = await importTheme(file);
|
|
@@ -9358,6 +9435,14 @@ window.__ModuleLoader__.load({
|
|
|
9358
9435
|
setConfirmId(null);
|
|
9359
9436
|
deleteProfile(entry.id);
|
|
9360
9437
|
};
|
|
9438
|
+
const onRenameCommit = (entry) => {
|
|
9439
|
+
const name = editVal.trim();
|
|
9440
|
+
setEditId(null);
|
|
9441
|
+
if (name && name !== entry.name && renameProfile(entry.id, name)) notify(t("profileRenamed"), true);
|
|
9442
|
+
};
|
|
9443
|
+
const onDuplicate = (entry) => {
|
|
9444
|
+
if (duplicateProfile(entry.id)) notify(t("profileDuplicated"), true);
|
|
9445
|
+
};
|
|
9361
9446
|
const profileOptions = (value, onChange) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
9362
9447
|
className: "dab-select",
|
|
9363
9448
|
value: value ?? "",
|
|
@@ -9489,8 +9574,42 @@ window.__ModuleLoader__.load({
|
|
|
9489
9574
|
children: profiles.map((entry, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9490
9575
|
className: `dab-profile-row${activeProfile === entry.id ? " is-active" : ""}`,
|
|
9491
9576
|
style: { "--i": i },
|
|
9492
|
-
children: [
|
|
9493
|
-
|
|
9577
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProfileDot, { entry }), editId === entry.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9578
|
+
className: "dab-urlrow",
|
|
9579
|
+
style: {
|
|
9580
|
+
flex: 1,
|
|
9581
|
+
minWidth: 0
|
|
9582
|
+
},
|
|
9583
|
+
children: [
|
|
9584
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
9585
|
+
type: "text",
|
|
9586
|
+
className: "dab-urlinput",
|
|
9587
|
+
value: editVal,
|
|
9588
|
+
maxLength: 60,
|
|
9589
|
+
onChange: (e) => setEditVal(e.target.value),
|
|
9590
|
+
onKeyDown: (e) => {
|
|
9591
|
+
if (e.key === "Enter") {
|
|
9592
|
+
e.preventDefault();
|
|
9593
|
+
onRenameCommit(entry);
|
|
9594
|
+
}
|
|
9595
|
+
},
|
|
9596
|
+
autoFocus: true
|
|
9597
|
+
}),
|
|
9598
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9599
|
+
type: "button",
|
|
9600
|
+
className: "dab-btn dab-btn-primary",
|
|
9601
|
+
disabled: !editVal.trim(),
|
|
9602
|
+
onClick: () => onRenameCommit(entry),
|
|
9603
|
+
children: t("editorCommit")
|
|
9604
|
+
}),
|
|
9605
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9606
|
+
type: "button",
|
|
9607
|
+
className: "dab-btn",
|
|
9608
|
+
onClick: () => setEditId(null),
|
|
9609
|
+
children: t("bgUrlCancel")
|
|
9610
|
+
})
|
|
9611
|
+
]
|
|
9612
|
+
}) }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
9494
9613
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9495
9614
|
className: "dab-profile-meta",
|
|
9496
9615
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -9507,6 +9626,23 @@ window.__ModuleLoader__.load({
|
|
|
9507
9626
|
onClick: () => onApply(entry),
|
|
9508
9627
|
children: t("profileApply")
|
|
9509
9628
|
}),
|
|
9629
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9630
|
+
type: "button",
|
|
9631
|
+
className: "dab-btn",
|
|
9632
|
+
title: t("profileRename"),
|
|
9633
|
+
onClick: () => {
|
|
9634
|
+
setEditId(entry.id);
|
|
9635
|
+
setEditVal(entry.name);
|
|
9636
|
+
},
|
|
9637
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditIcon, { size: 13 })
|
|
9638
|
+
}),
|
|
9639
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9640
|
+
type: "button",
|
|
9641
|
+
className: "dab-btn",
|
|
9642
|
+
title: t("profileDuplicate"),
|
|
9643
|
+
onClick: () => onDuplicate(entry),
|
|
9644
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlusIcon, { size: 13 })
|
|
9645
|
+
}),
|
|
9510
9646
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9511
9647
|
type: "button",
|
|
9512
9648
|
className: `dab-btn dab-btn-danger-solid${confirmId === entry.id ? " dab-btn-confirm" : ""}`,
|
|
@@ -9514,7 +9650,7 @@ window.__ModuleLoader__.load({
|
|
|
9514
9650
|
onClick: () => onDelete(entry),
|
|
9515
9651
|
children: confirmId === entry.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, { size: 13 }), t("profileDeleteConfirm")] }) : t("profileDelete")
|
|
9516
9652
|
})
|
|
9517
|
-
]
|
|
9653
|
+
] })]
|
|
9518
9654
|
}, entry.id))
|
|
9519
9655
|
})
|
|
9520
9656
|
]
|
|
@@ -10048,9 +10184,9 @@ window.__ModuleLoader__.load({
|
|
|
10048
10184
|
* unsubscribe for plugin teardown. */
|
|
10049
10185
|
function startBetterSidebarWatch() {
|
|
10050
10186
|
sync();
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10187
|
+
return watchDom((records) => {
|
|
10188
|
+
if (records.some((r) => r.target === document.body && r.type === "childList")) sync();
|
|
10189
|
+
});
|
|
10054
10190
|
}
|
|
10055
10191
|
let nativeTabs = false;
|
|
10056
10192
|
const nativeSubs = /* @__PURE__ */ new Set();
|
|
@@ -10171,6 +10307,50 @@ window.__ModuleLoader__.load({
|
|
|
10171
10307
|
});
|
|
10172
10308
|
}
|
|
10173
10309
|
//#endregion
|
|
10310
|
+
//#region src/shared/rotation.ts
|
|
10311
|
+
/** ISO week key (Monday-first). */
|
|
10312
|
+
function isoWeekKey(d) {
|
|
10313
|
+
const t = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
|
|
10314
|
+
const day = t.getUTCDay() || 7;
|
|
10315
|
+
t.setUTCDate(t.getUTCDate() + 4 - day);
|
|
10316
|
+
const yearStart = new Date(Date.UTC(t.getUTCFullYear(), 0, 1));
|
|
10317
|
+
const week = Math.ceil(((t.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
10318
|
+
return `${t.getUTCFullYear()}-W${week}`;
|
|
10319
|
+
}
|
|
10320
|
+
/** Whether the pool should advance right now. */
|
|
10321
|
+
function rotationDue(rot, now = /* @__PURE__ */ new Date()) {
|
|
10322
|
+
if (!rot.enabled || rot.items.length === 0) return false;
|
|
10323
|
+
if (rot.interval === "reload") return true;
|
|
10324
|
+
const last = rot.lastRotate !== null ? new Date(rot.lastRotate) : null;
|
|
10325
|
+
if (last === null || isNaN(last.getTime())) return true;
|
|
10326
|
+
if (rot.interval === "hourly") return hourKey(last) !== hourKey(now);
|
|
10327
|
+
if (rot.interval === "daily") return last.toDateString() !== now.toDateString();
|
|
10328
|
+
return isoWeekKey(last) !== isoWeekKey(now);
|
|
10329
|
+
}
|
|
10330
|
+
/** Epoch-hour bucket: hourly cadence advances on each full hour boundary
|
|
10331
|
+
* (in whole-hour timezones — UTC+8 among them — that is exactly the local
|
|
10332
|
+
* on-the-hour tick). */
|
|
10333
|
+
const hourKey = (d) => Math.floor(d.getTime() / 36e5);
|
|
10334
|
+
/** Whether an OPEN page should keep advancing this cadence on its timer.
|
|
10335
|
+
* Only the time-based cadences tick live; 'reload' is a once-per-READ pick
|
|
10336
|
+
* (the server advances it inside the `read` itself), so a live tick driving
|
|
10337
|
+
* it would flip the wallpaper every 30 s — exactly the v0.4.0 regression
|
|
10338
|
+
* (rotationDue is unconditionally true for 'reload'). */
|
|
10339
|
+
function rotationAdvancesLive(rot) {
|
|
10340
|
+
return rot.interval !== "reload";
|
|
10341
|
+
}
|
|
10342
|
+
/** Pick the next pool index under the configured mode. */
|
|
10343
|
+
function pickNextRotationIndex(rot) {
|
|
10344
|
+
const n = rot.items.length;
|
|
10345
|
+
if (n === 0) return -1;
|
|
10346
|
+
if (rot.mode === "shuffle" && n > 1) {
|
|
10347
|
+
let idx = rot.current;
|
|
10348
|
+
while (idx === rot.current) idx = Math.floor(Math.random() * n);
|
|
10349
|
+
return idx;
|
|
10350
|
+
}
|
|
10351
|
+
return (rot.current + 1) % n;
|
|
10352
|
+
}
|
|
10353
|
+
//#endregion
|
|
10174
10354
|
//#region src/client/index.tsx
|
|
10175
10355
|
/**
|
|
10176
10356
|
* dsh-any-background — browser half entry.
|
|
@@ -10436,38 +10616,11 @@ window.__ModuleLoader__.load({
|
|
|
10436
10616
|
syncMetaNow();
|
|
10437
10617
|
return true;
|
|
10438
10618
|
};
|
|
10439
|
-
const
|
|
10440
|
-
|
|
10441
|
-
const day = t.getUTCDay() || 7;
|
|
10442
|
-
t.setUTCDate(t.getUTCDate() + 4 - day);
|
|
10443
|
-
const yearStart = new Date(Date.UTC(t.getUTCFullYear(), 0, 1));
|
|
10444
|
-
const week = Math.ceil(((t.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
10445
|
-
return `${t.getUTCFullYear()}-W${week}`;
|
|
10446
|
-
};
|
|
10447
|
-
const rotationDue = () => {
|
|
10448
|
-
const rot = rRotation();
|
|
10449
|
-
if (!rot.enabled || rot.items.length === 0) return false;
|
|
10450
|
-
if (rot.interval === "reload") return true;
|
|
10451
|
-
const last = rot.lastRotate !== null ? new Date(rot.lastRotate) : null;
|
|
10452
|
-
if (last === null || isNaN(last.getTime())) return true;
|
|
10453
|
-
const now = /* @__PURE__ */ new Date();
|
|
10454
|
-
if (rot.interval === "daily") return last.toDateString() !== now.toDateString();
|
|
10455
|
-
return isoWeekKey(last) !== isoWeekKey(now);
|
|
10456
|
-
};
|
|
10457
|
-
const pickNextRotationIndex = () => {
|
|
10458
|
-
const rot = rRotation();
|
|
10459
|
-
const n = rot.items.length;
|
|
10460
|
-
if (n === 0) return -1;
|
|
10461
|
-
if (rot.mode === "shuffle" && n > 1) {
|
|
10462
|
-
let idx = rot.current;
|
|
10463
|
-
while (idx === rot.current) idx = Math.floor(Math.random() * n);
|
|
10464
|
-
return idx;
|
|
10465
|
-
}
|
|
10466
|
-
return (rot.current + 1) % n;
|
|
10467
|
-
};
|
|
10619
|
+
const rotationDue$1 = () => rotationDue(rRotation());
|
|
10620
|
+
const pickNextRotationIndex$1 = () => pickNextRotationIndex(rRotation());
|
|
10468
10621
|
const maybeRotate = async () => {
|
|
10469
|
-
if (!rotationDue()) return;
|
|
10470
|
-
await applyRotationIndex(pickNextRotationIndex(), true);
|
|
10622
|
+
if (!rotationDue$1()) return;
|
|
10623
|
+
await applyRotationIndex(pickNextRotationIndex$1(), true);
|
|
10471
10624
|
};
|
|
10472
10625
|
const parseHHMM = (s) => {
|
|
10473
10626
|
const m = /^([01]\d|2[0-3]):([0-5]\d)$/.exec(s);
|
|
@@ -10483,6 +10636,7 @@ window.__ModuleLoader__.load({
|
|
|
10483
10636
|
return cur >= night && cur < day;
|
|
10484
10637
|
};
|
|
10485
10638
|
const scheduleTick = () => {
|
|
10639
|
+
if (rotationAdvancesLive(rRotation())) maybeRotate();
|
|
10486
10640
|
const sc = rSchedule();
|
|
10487
10641
|
if (!sc.enabled) return;
|
|
10488
10642
|
const want = (sc.mode === "system" ? window.matchMedia?.("(prefers-color-scheme: dark)")?.matches ?? false : isNightNow(sc)) ? sc.nightProfile : sc.dayProfile;
|
|
@@ -10817,12 +10971,32 @@ window.__ModuleLoader__.load({
|
|
|
10817
10971
|
}
|
|
10818
10972
|
}
|
|
10819
10973
|
}
|
|
10974
|
+
let pool;
|
|
10975
|
+
const rotItems = rRotation().items;
|
|
10976
|
+
if (rotItems.length > 0) {
|
|
10977
|
+
pool = [];
|
|
10978
|
+
for (const it of rotItems) try {
|
|
10979
|
+
const blob = await fetch(rotationFileUrl(it.file)).then((r) => {
|
|
10980
|
+
if (!r.ok) throw new Error(`http ${r.status}`);
|
|
10981
|
+
return r.blob();
|
|
10982
|
+
});
|
|
10983
|
+
pool.push({
|
|
10984
|
+
file: it.file,
|
|
10985
|
+
thumb: it.thumb,
|
|
10986
|
+
dataUrl: await blobToDataUrl(blob)
|
|
10987
|
+
});
|
|
10988
|
+
} catch {
|
|
10989
|
+
console.warn(`dsh-any-background: export skipped a rotation image (${it.file})`);
|
|
10990
|
+
}
|
|
10991
|
+
if (pool.length === 0) pool = void 0;
|
|
10992
|
+
}
|
|
10820
10993
|
const payload = {
|
|
10821
|
-
version:
|
|
10994
|
+
version: 3,
|
|
10822
10995
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10823
10996
|
config: cfg,
|
|
10824
10997
|
wallpaper: wallpaperPayload,
|
|
10825
|
-
video: videoPayload
|
|
10998
|
+
video: videoPayload,
|
|
10999
|
+
pool
|
|
10826
11000
|
};
|
|
10827
11001
|
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
|
|
10828
11002
|
const url = URL.createObjectURL(blob);
|
|
@@ -10876,6 +11050,26 @@ window.__ModuleLoader__.load({
|
|
|
10876
11050
|
if (cfg.regenerateOnReload) regenerateGeneratedBg();
|
|
10877
11051
|
else if (cfg.generatedBg) updateGeneratedBg(cfg.generatedBg);
|
|
10878
11052
|
}
|
|
11053
|
+
const poolRaw = d.pool;
|
|
11054
|
+
if (Array.isArray(poolRaw)) {
|
|
11055
|
+
if (poolRaw.length > 0) {
|
|
11056
|
+
const r = await rotationRestore(poolRaw);
|
|
11057
|
+
if (r.ok && r.items) cfg.rotation = {
|
|
11058
|
+
...cfg.rotation,
|
|
11059
|
+
items: r.items,
|
|
11060
|
+
current: Math.min(cfg.rotation.current, Math.max(0, r.items.length - 1))
|
|
11061
|
+
};
|
|
11062
|
+
else console.warn("dsh-any-background: rotation pool restore failed", r.error);
|
|
11063
|
+
} else if (cfg.rotation.items.length > 0) cfg.rotation = {
|
|
11064
|
+
...cfg.rotation,
|
|
11065
|
+
items: [],
|
|
11066
|
+
current: 0
|
|
11067
|
+
};
|
|
11068
|
+
} else if ((typeof d.version !== "number" || d.version < 3) && cfg.rotation.items.length > 0) cfg.rotation = {
|
|
11069
|
+
...cfg.rotation,
|
|
11070
|
+
items: [],
|
|
11071
|
+
current: 0
|
|
11072
|
+
};
|
|
10879
11073
|
persistConfig();
|
|
10880
11074
|
syncMetaNow();
|
|
10881
11075
|
if (rHasColor()) {
|
|
@@ -10911,6 +11105,37 @@ window.__ModuleLoader__.load({
|
|
|
10911
11105
|
return true;
|
|
10912
11106
|
},
|
|
10913
11107
|
applyProfile: (id) => applyProfileById(id),
|
|
11108
|
+
renameProfile: (id, name) => {
|
|
11109
|
+
const trimmed = name.trim();
|
|
11110
|
+
if (!trimmed) return false;
|
|
11111
|
+
const before = rProfiles();
|
|
11112
|
+
if (!before.some((p) => p.id === id)) return false;
|
|
11113
|
+
cfg.profiles = before.map((p) => p.id === id ? {
|
|
11114
|
+
...p,
|
|
11115
|
+
name: trimmed.slice(0, 60)
|
|
11116
|
+
} : p);
|
|
11117
|
+
persistConfig();
|
|
11118
|
+
syncMetaNow();
|
|
11119
|
+
return true;
|
|
11120
|
+
},
|
|
11121
|
+
duplicateProfile: (id) => {
|
|
11122
|
+
const before = rProfiles();
|
|
11123
|
+
const src = before.find((p) => p.id === id);
|
|
11124
|
+
if (!src) return false;
|
|
11125
|
+
const profiles = [...before];
|
|
11126
|
+
if (profiles.length >= 20) profiles.shift();
|
|
11127
|
+
const nid = `p-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
11128
|
+
profiles.push({
|
|
11129
|
+
...src,
|
|
11130
|
+
id: nid,
|
|
11131
|
+
name: `${src.name} (2)`.slice(0, 60),
|
|
11132
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11133
|
+
});
|
|
11134
|
+
cfg.profiles = profiles;
|
|
11135
|
+
persistConfig();
|
|
11136
|
+
syncMetaNow();
|
|
11137
|
+
return true;
|
|
11138
|
+
},
|
|
10914
11139
|
deleteProfile: (id) => {
|
|
10915
11140
|
const before = rProfiles();
|
|
10916
11141
|
if (!before.some((p) => p.id === id)) return false;
|
|
@@ -10995,7 +11220,7 @@ window.__ModuleLoader__.load({
|
|
|
10995
11220
|
},
|
|
10996
11221
|
rotateNow: async () => {
|
|
10997
11222
|
if (rRotation().items.length === 0) return false;
|
|
10998
|
-
return applyRotationIndex(pickNextRotationIndex(), true);
|
|
11223
|
+
return applyRotationIndex(pickNextRotationIndex$1(), true);
|
|
10999
11224
|
},
|
|
11000
11225
|
setVideoFromUrl: async (url) => {
|
|
11001
11226
|
const r = await setVideoFromUrl(url);
|
|
@@ -11064,10 +11289,10 @@ window.__ModuleLoader__.load({
|
|
|
11064
11289
|
}
|
|
11065
11290
|
}
|
|
11066
11291
|
};
|
|
11067
|
-
let
|
|
11292
|
+
let disposeNavWatch = null;
|
|
11068
11293
|
const watchNavIcon = () => {
|
|
11069
|
-
if (
|
|
11070
|
-
|
|
11294
|
+
if (disposeNavWatch !== null || typeof MutationObserver === "undefined") return;
|
|
11295
|
+
disposeNavWatch = watchDom((records) => {
|
|
11071
11296
|
if (records.some((r) => {
|
|
11072
11297
|
for (const n of r.addedNodes) {
|
|
11073
11298
|
if (n.nodeType !== 1) continue;
|
|
@@ -11077,16 +11302,12 @@ window.__ModuleLoader__.load({
|
|
|
11077
11302
|
return false;
|
|
11078
11303
|
})) applyNavIcon();
|
|
11079
11304
|
});
|
|
11080
|
-
navIconObserver.observe(document.body, {
|
|
11081
|
-
childList: true,
|
|
11082
|
-
subtree: true
|
|
11083
|
-
});
|
|
11084
11305
|
applyNavIcon();
|
|
11085
11306
|
};
|
|
11086
11307
|
watchNavIcon();
|
|
11087
11308
|
ctx.effect(() => () => {
|
|
11088
|
-
|
|
11089
|
-
|
|
11309
|
+
disposeNavWatch?.();
|
|
11310
|
+
disposeNavWatch = null;
|
|
11090
11311
|
}, "dsh-any-background: nav icon watch");
|
|
11091
11312
|
const restoreSaved = () => {
|
|
11092
11313
|
if (rHasColor()) {
|