dsh-folia 2.5.1 → 2.5.8
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/lib/boot/index.js +220 -0
- package/lib/client.js +314 -439
- package/lib/index.js +15 -9
- package/lib/routes/boot.js +325 -0
- package/lib/routes/inventory.js +207 -49
- package/lib/routes/modules.js +186 -0
- package/lib/routes/site-thumb.js +109 -0
- package/lib/settings-schema.js +5 -2
- package/package.json +5 -2
- package/lib/routes/skins.js +0 -653
package/lib/client.js
CHANGED
|
@@ -7217,6 +7217,8 @@ window.__ModuleLoader__.load({
|
|
|
7217
7217
|
contentRatingFilter: "everyone",
|
|
7218
7218
|
// Wallpaper-type filter (all / video / web / image / scene). "all" disables it.
|
|
7219
7219
|
typeFilter: "all",
|
|
7220
|
+
// 壁纸来源:all / local(本机 Wallpaper Engine 库 + 上传)/ site(我们站点)
|
|
7221
|
+
sourceFilter: "all",
|
|
7220
7222
|
// Thumbnail-card style: "classic" (WE's original aspect-ratio 16/9 cards —
|
|
7221
7223
|
// the CD-like look the author liked; can overlap in older browsers) or
|
|
7222
7224
|
// "fixed" (fixed-height cards that never overlap). The vinyl
|
|
@@ -7661,6 +7663,7 @@ window.__ModuleLoader__.load({
|
|
|
7661
7663
|
objectFit: { kind: 'enum', values: OBJECT_FIT_VALUES },
|
|
7662
7664
|
contentRatingFilter: { kind: 'enum', values: RATING_VALUES },
|
|
7663
7665
|
typeFilter: { kind: 'enum', values: TYPE_VALUES },
|
|
7666
|
+
sourceFilter: { kind: 'enum', values: ['all', 'local', 'site'] },
|
|
7664
7667
|
pickerLayout: { kind: 'enum', values: PICKER_LAYOUT_VALUES },
|
|
7665
7668
|
edgeCompat: { kind: 'boolTrue' },
|
|
7666
7669
|
accent: { kind: 'hex' },
|
|
@@ -7912,7 +7915,7 @@ window.__ModuleLoader__.load({
|
|
|
7912
7915
|
library: [
|
|
7913
7916
|
'switchTransition', 'switchTransitionDir', 'switchTransitionSpeed',
|
|
7914
7917
|
'rotationEnabled', 'rotationGroupId', 'rotationGroups', 'rotationSeeded',
|
|
7915
|
-
'contentRatingFilter', 'typeFilter', 'pickerLayout',
|
|
7918
|
+
'contentRatingFilter', 'typeFilter', 'sourceFilter', 'pickerLayout',
|
|
7916
7919
|
],
|
|
7917
7920
|
// 外观:玻璃 / 侧栏与四角 / 标题栏 / 思考块 / 浮层 / 主题预设 / 字体 / UI 配色。
|
|
7918
7921
|
appearance: [
|
|
@@ -8547,7 +8550,7 @@ window.__ModuleLoader__.load({
|
|
|
8547
8550
|
'id', 'hiddenIds', 'rotationEnabled', 'rotationGroupId', 'rotationGroups', 'rotationSeeded',
|
|
8548
8551
|
'playbackRate', 'videoVolume', 'videoAudioEnabled', 'fpsCap', 'sceneLive', 'sceneLiveFps',
|
|
8549
8552
|
'liveBootDelay', 'sceneLiveFailures', 'pauseOnHidden', 'pauseOnBlur', 'pauseOnBattery',
|
|
8550
|
-
'flip', 'objectFit', 'contentRatingFilter', 'typeFilter', 'pickerLayout', 'edgeCompat',
|
|
8553
|
+
'flip', 'objectFit', 'contentRatingFilter', 'typeFilter', 'sourceFilter', 'pickerLayout', 'edgeCompat',
|
|
8551
8554
|
'userProps', 'noticeSeen', 'layerPositionX', 'layerPositionY', 'layerScale',
|
|
8552
8555
|
],
|
|
8553
8556
|
// 玻璃 / 材质 / 侧栏 / 过渡(`accent`、`glassMode` 与皮肤让路之外的整套玻璃登记表都在这里)
|
|
@@ -16168,401 +16171,6 @@ window.__ModuleLoader__.load({
|
|
|
16168
16171
|
);
|
|
16169
16172
|
}
|
|
16170
16173
|
|
|
16171
|
-
// ── 内联模块:src/panel-tabs-skins.js —— 「皮肤包」页签:从自建站列皮肤包 → 下载 → 装到 harness/skins → 激活(宿主端 /skins 族)(构建期注入;勿手改本段)──
|
|
16172
|
-
/**
|
|
16173
|
-
* panel-tabs-skins.js — 「皮肤包」页签:从**自建站**列皮肤包 → 下载 → 装到本地 → 激活。
|
|
16174
|
-
*
|
|
16175
|
-
* 为什么是一个**真正的 React 组件**(而不是像别的页签那样是纯渲染函数):
|
|
16176
|
-
* 这个页签有自己的异步状态(列表 / 加载中 / 下载中 / 错误),而页签渲染器是在
|
|
16177
|
-
* `renderActiveTab` 里被**条件调用**的普通函数 —— 在里面放 hooks 会违反 hooks 顺序。
|
|
16178
|
-
* 所以这里导出一个组件 `SkinsTabPanel`,由薄包装 `renderSkinsTab(ctx)` 挂上去:
|
|
16179
|
-
* 组件实例化后 hooks 是稳定的,父级重渲染也不会错位。
|
|
16180
|
-
*
|
|
16181
|
-
* 契约与别的 `panel-tabs-*.js` 相同:构建期被内联进 bundle 的工厂作用域,
|
|
16182
|
-
* 因此 `React` / `apiUrl` / `apiJson` / `apiPostJson` / `weT` 都是同作用域的既有名字,
|
|
16183
|
-
* 本文件**不 import 任何东西**。
|
|
16184
|
-
*
|
|
16185
|
-
* 数据流(全部走宿主端,浏览器不直连站点):
|
|
16186
|
-
* apiJson("/skins") → 索引 + 已装/在用标记
|
|
16187
|
-
* apiPostJson("/skins/install", {id}) → 下载 / 校验 / 解压
|
|
16188
|
-
* apiPostJson("/skins/activate", {id}) → 写 skin-center-active.json
|
|
16189
|
-
* apiPostJson("/skins/remove", {id}) → 删目录
|
|
16190
|
-
* apiUrl("/skins/preview?p=…") → 预览图代理
|
|
16191
|
-
*/
|
|
16192
|
-
|
|
16193
|
-
/** 卡片网格与卡片本体的样式:内联,避免为一个页签去动全局样式表。 */
|
|
16194
|
-
const SKINS_STYLE = {
|
|
16195
|
-
grid: {
|
|
16196
|
-
display: "grid",
|
|
16197
|
-
gridTemplateColumns: "repeat(auto-fill, minmax(168px, 1fr))",
|
|
16198
|
-
gap: "10px",
|
|
16199
|
-
marginTop: "10px",
|
|
16200
|
-
},
|
|
16201
|
-
card: {
|
|
16202
|
-
display: "flex", flexDirection: "column", overflow: "hidden",
|
|
16203
|
-
border: "1px solid rgba(255,255,255,0.12)", borderRadius: "10px",
|
|
16204
|
-
background: "rgba(255,255,255,0.04)",
|
|
16205
|
-
},
|
|
16206
|
-
thumb: { width: "100%", aspectRatio: "4 / 3", objectFit: "cover", display: "block", background: "rgba(0,0,0,0.35)" },
|
|
16207
|
-
body: { padding: "8px 9px 9px", display: "flex", flexDirection: "column", gap: "4px", flex: "1 1 auto" },
|
|
16208
|
-
title: { fontSize: "12.5px", fontWeight: 600, lineHeight: 1.3 },
|
|
16209
|
-
meta: { fontSize: "11px", opacity: 0.62, lineHeight: 1.3 },
|
|
16210
|
-
tags: { display: "flex", flexWrap: "wrap", gap: "3px", marginTop: "2px" },
|
|
16211
|
-
tag: { fontSize: "10px", padding: "1px 5px", borderRadius: "999px", background: "rgba(255,255,255,0.09)", opacity: 0.75 },
|
|
16212
|
-
actions: { display: "flex", gap: "6px", marginTop: "6px" },
|
|
16213
|
-
};
|
|
16214
|
-
|
|
16215
|
-
function skinsFmtBytes(n) {
|
|
16216
|
-
const v = Number(n) || 0;
|
|
16217
|
-
if (v >= 1024 * 1024) return (v / 1024 / 1024).toFixed(1) + " MB";
|
|
16218
|
-
if (v >= 1024) return Math.round(v / 1024) + " KB";
|
|
16219
|
-
return v + " B";
|
|
16220
|
-
}
|
|
16221
|
-
|
|
16222
|
-
function skinsErrText(code) {
|
|
16223
|
-
if (!code) return "";
|
|
16224
|
-
if (code === "disabled") return weT("站点数据源已关闭(DSH_FOLIA_SKINS_SITE=off)");
|
|
16225
|
-
if (code === "timeout") return weT("站点超时,点「刷新」重试");
|
|
16226
|
-
if (code === "tls") return weT("站点证书校验失败");
|
|
16227
|
-
if (code === "network") return weT("连不上站点(网络或域名问题)");
|
|
16228
|
-
if (/^http-/.test(code)) return weT("站点返回错误:") + code.slice(5);
|
|
16229
|
-
if (code === "json") return weT("站点索引格式不对");
|
|
16230
|
-
return code;
|
|
16231
|
-
}
|
|
16232
|
-
|
|
16233
|
-
/**
|
|
16234
|
-
* 把一套皮肤的样式**当场**挂进文档。
|
|
16235
|
-
*
|
|
16236
|
-
* 为什么是 `<style>` 而不是 `<link>`:
|
|
16237
|
-
* · `<link rel=stylesheet>` 是**异步**的,加载完成的时机不定,容易输给皮肤中心随后
|
|
16238
|
-
* 重新注入的那份;
|
|
16239
|
-
* · `<style>` 一赋值就参与样式计算,且追加到 `<body>` **末尾** ⇒ 文档顺序最后,
|
|
16240
|
-
* 同等特异性下后写的赢。这是"不刷新换肤"能真正看见的关键。
|
|
16241
|
-
*
|
|
16242
|
-
* 仍然不跑 `hooks.mjs`(粒子等动效),那部分只有整页重载才生效。
|
|
16243
|
-
*
|
|
16244
|
-
* @param {string} id 皮肤 id
|
|
16245
|
-
* @returns {Promise<{ bytes: number, rules: number }>}
|
|
16246
|
-
*/
|
|
16247
|
-
async function applySkinCss(id) {
|
|
16248
|
-
const resp = await apiFetch("/skins/css?id=" + encodeURIComponent(id) + "&t=" + Date.now(), {
|
|
16249
|
-
parse: false,
|
|
16250
|
-
cache: "no-store",
|
|
16251
|
-
});
|
|
16252
|
-
if (!resp || !resp.ok || !resp.response) throw new Error("css-http-" + ((resp && resp.status) || 0));
|
|
16253
|
-
const css = await resp.response.text();
|
|
16254
|
-
if (!css || !css.trim()) throw new Error("css-empty");
|
|
16255
|
-
let el = document.getElementById("folia-skin-hot");
|
|
16256
|
-
if (!el) {
|
|
16257
|
-
el = document.createElement("style");
|
|
16258
|
-
el.id = "folia-skin-hot";
|
|
16259
|
-
// 挂 body 末尾(不是 head):文档顺序最后 ⇒ 同等特异性下覆盖先前的规则。
|
|
16260
|
-
(document.body || document.head).appendChild(el);
|
|
16261
|
-
}
|
|
16262
|
-
el.textContent = css;
|
|
16263
|
-
let rules = 0;
|
|
16264
|
-
try { rules = (el.sheet && el.sheet.cssRules && el.sheet.cssRules.length) || 0; } catch { /* 读不到就报 0 */ }
|
|
16265
|
-
return { bytes: css.length, rules };
|
|
16266
|
-
}
|
|
16267
|
-
|
|
16268
|
-
function SkinsTabPanel() {
|
|
16269
|
-
const [list, setList] = React.useState([]);
|
|
16270
|
-
const [active, setActive] = React.useState(null);
|
|
16271
|
-
const [loading, setLoading] = React.useState(true);
|
|
16272
|
-
const [error, setError] = React.useState(null);
|
|
16273
|
-
const [harnessFound, setHarnessFound] = React.useState(true);
|
|
16274
|
-
const [busy, setBusy] = React.useState(""); // 正在安装 / 激活的那个 id
|
|
16275
|
-
const [note, setNote] = React.useState(null); // 一次性提示(成功 / 失败原因)
|
|
16276
|
-
const [q, setQ] = React.useState("");
|
|
16277
|
-
|
|
16278
|
-
const load = React.useCallback(async (force) => {
|
|
16279
|
-
setLoading(true);
|
|
16280
|
-
setError(null);
|
|
16281
|
-
try {
|
|
16282
|
-
// ⚠️ `apiJson` 返回的是 **`{ ok, status, data, error }` 包装对象**,不是响应体本身
|
|
16283
|
-
//(见 src/api-client.js 的 apiFetch)。最初按裸体取值 ⇒ `list=[] 且 error=null`,
|
|
16284
|
-
// 界面显示"没有匹配的皮肤包"却没有任何错误提示 —— 静默空列表比报错难查得多。
|
|
16285
|
-
// `parse: "always"` 让非 2xx 的 `{error}` 也能被读到。
|
|
16286
|
-
const resp = await apiJson("/skins" + (force ? "?refresh=1" : ""), { parse: "always" });
|
|
16287
|
-
const r = (resp && resp.data) || null;
|
|
16288
|
-
setList((r && r.skins) || []);
|
|
16289
|
-
setActive((r && r.active) || null);
|
|
16290
|
-
setError((r && r.error) || (resp && resp.error) || null);
|
|
16291
|
-
setHarnessFound(!r || r.harnessFound !== false);
|
|
16292
|
-
} catch (e) {
|
|
16293
|
-
setError("network");
|
|
16294
|
-
setList([]);
|
|
16295
|
-
} finally {
|
|
16296
|
-
setLoading(false);
|
|
16297
|
-
}
|
|
16298
|
-
}, []);
|
|
16299
|
-
|
|
16300
|
-
React.useEffect(() => { load(false); }, [load]);
|
|
16301
|
-
|
|
16302
|
-
/**
|
|
16303
|
-
* 执行一个动作(install / activate / remove)。
|
|
16304
|
-
*
|
|
16305
|
-
* ⚠️ **「使用」必须立刻有反馈**:切换皮肤只写 `skin-center-active.json`,host 端 6 ms 就应,
|
|
16306
|
-
* 而皮肤要重启才生效 —— 如果等一次 `?refresh=1`(约 600 ms)再改标记,用户看到的就是
|
|
16307
|
-
* "点了一下,半天没反应"。所以「使用」走**乐观更新**:先本地翻转 active 并把提示打出来,
|
|
16308
|
-
* 请求失败再回滚重拉。装 / 删仍然要重拉(已装标记与体积都变了)。
|
|
16309
|
-
*/
|
|
16310
|
-
async function act(kind, id, label) {
|
|
16311
|
-
if (busy) return;
|
|
16312
|
-
setBusy(id);
|
|
16313
|
-
setNote(null);
|
|
16314
|
-
const prevActive = active;
|
|
16315
|
-
if (kind === "activate") {
|
|
16316
|
-
// 乐观:先给反馈,再等网络
|
|
16317
|
-
setActive(id);
|
|
16318
|
-
setList((prev) => prev.map((x) => ({ ...x, active: x.id === id })));
|
|
16319
|
-
setNote({ kind: "ok", text: weT("正在切换到「") + label + weT("」…") });
|
|
16320
|
-
}
|
|
16321
|
-
try {
|
|
16322
|
-
// 同样是包装对象:`ok` 是 HTTP 2xx,业务结论在 `data.ok`(宿主回 `{ok:false,error}`)。
|
|
16323
|
-
const resp = await apiPostJson("/skins/" + kind, { id }, { parse: "always" });
|
|
16324
|
-
const r = (resp && resp.data) || null;
|
|
16325
|
-
if (resp && resp.ok && r && r.ok) {
|
|
16326
|
-
if (kind === "install") {
|
|
16327
|
-
setNote({ kind: "ok", text: weT("已安装:") + label });
|
|
16328
|
-
await load(true);
|
|
16329
|
-
} else if (kind === "activate") {
|
|
16330
|
-
// **当场换肤**:把皮肤样式同步注入文档,不刷新、不重启。
|
|
16331
|
-
// 注入结果直接写进提示里(字节数 / 规则条数):万一样式还是没盖住皮肤中心,
|
|
16332
|
-
// 这一行就是判断"是没注入"还是"注入了但被压住"的依据,不用再猜。
|
|
16333
|
-
let info = null;
|
|
16334
|
-
let applyErr = "";
|
|
16335
|
-
try {
|
|
16336
|
-
info = await applySkinCss(id);
|
|
16337
|
-
} catch (e) {
|
|
16338
|
-
applyErr = String((e && e.message) || e);
|
|
16339
|
-
}
|
|
16340
|
-
setNote({
|
|
16341
|
-
kind: applyErr ? "err" : "ok",
|
|
16342
|
-
text: applyErr
|
|
16343
|
-
? weT("已切换为「") + label + weT("」,但样式注入失败:") + applyErr
|
|
16344
|
-
: weT("已切换到「") + label + weT("」—— 已注入 ")
|
|
16345
|
-
+ Math.round(info.bytes / 1024) + " KB / " + info.rules + weT(" 条样式规则"),
|
|
16346
|
-
// hooks(粒子等动效)只有整页重载才会跑:给一个可选入口,不强制。
|
|
16347
|
-
reload: true,
|
|
16348
|
-
});
|
|
16349
|
-
// 不再整表重拉:本地标记已经对了,省掉那 600 ms 的"没反应"窗口。
|
|
16350
|
-
} else if (kind === "remove") {
|
|
16351
|
-
setNote({ kind: "ok", text: weT("已删除:") + label });
|
|
16352
|
-
await load(true);
|
|
16353
|
-
}
|
|
16354
|
-
} else {
|
|
16355
|
-
const code = (r && r.error) || "failed";
|
|
16356
|
-
const detail = (r && r.detail) ? "(" + r.detail + ")" : "";
|
|
16357
|
-
setNote({ kind: "err", text: weT("操作失败:") + code + detail });
|
|
16358
|
-
if (kind === "activate") {
|
|
16359
|
-
// 回滚乐观更新
|
|
16360
|
-
setActive(prevActive);
|
|
16361
|
-
setList((prev) => prev.map((x) => ({ ...x, active: x.id === prevActive })));
|
|
16362
|
-
}
|
|
16363
|
-
}
|
|
16364
|
-
} catch (e) {
|
|
16365
|
-
setNote({ kind: "err", text: weT("请求失败:") + (e && e.message ? e.message : weT("网络错误")) });
|
|
16366
|
-
if (kind === "activate") {
|
|
16367
|
-
setActive(prevActive);
|
|
16368
|
-
setList((prev) => prev.map((x) => ({ ...x, active: x.id === prevActive })));
|
|
16369
|
-
}
|
|
16370
|
-
} finally {
|
|
16371
|
-
setBusy("");
|
|
16372
|
-
}
|
|
16373
|
-
}
|
|
16374
|
-
|
|
16375
|
-
const needle = q.trim().toLowerCase();
|
|
16376
|
-
const shown = needle
|
|
16377
|
-
? list.filter((s) => (s.name + " " + s.nameEn + " " + s.id + " " + (s.tags || []).join(" "))
|
|
16378
|
-
.toLowerCase().indexOf(needle) !== -1)
|
|
16379
|
-
: list;
|
|
16380
|
-
|
|
16381
|
-
const installedCount = list.filter((s) => s.installed).length;
|
|
16382
|
-
|
|
16383
|
-
const header = React.createElement("div", { className: "we-picker__section-head" },
|
|
16384
|
-
React.createElement("span", { className: "we-picker__section-label" },
|
|
16385
|
-
weT("🎨 皮肤包") + (list.length ? " · " + list.length : "")),
|
|
16386
|
-
React.createElement("button", {
|
|
16387
|
-
type: "button",
|
|
16388
|
-
className: "we-picker__btn",
|
|
16389
|
-
style: { marginLeft: "auto" },
|
|
16390
|
-
disabled: loading,
|
|
16391
|
-
onClick: () => load(true),
|
|
16392
|
-
title: weT("重新从站点拉取列表"),
|
|
16393
|
-
}, loading ? weT("加载中…") : weT("刷新")),
|
|
16394
|
-
);
|
|
16395
|
-
|
|
16396
|
-
if (!harnessFound) {
|
|
16397
|
-
return React.createElement(React.Fragment, null,
|
|
16398
|
-
React.createElement("div", { className: "we-picker__section" }, header,
|
|
16399
|
-
React.createElement("div", { className: "we-picker__empty" },
|
|
16400
|
-
React.createElement("span", { className: "we-picker__empty-title" }, weT("找不到 DSH 的 skins 目录")),
|
|
16401
|
-
React.createElement("span", { className: "we-picker__hint" },
|
|
16402
|
-
weT("皮肤包要装到 DSH 的 harness/skins 下。可用环境变量 DSH_FOLIA_HARNESS 指定该目录")))));
|
|
16403
|
-
}
|
|
16404
|
-
|
|
16405
|
-
const children = [React.createElement("div", { className: "we-picker__section", key: "sec" }, header)];
|
|
16406
|
-
// 常驻一行「当前皮肤」:切换只写文件、皮肤要重启才生效,不给这一行用户就无从判断
|
|
16407
|
-
// 刚才那次点击到底成没成(这是"点了没反应"的一半原因)。
|
|
16408
|
-
if (active) {
|
|
16409
|
-
const activeName = (list.find((x) => x.id === active) || {}).name || active;
|
|
16410
|
-
children.push(React.createElement("div", {
|
|
16411
|
-
key: "active",
|
|
16412
|
-
className: "we-picker__hint",
|
|
16413
|
-
style: { margin: "4px 0 8px", display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" },
|
|
16414
|
-
},
|
|
16415
|
-
React.createElement("span", null, weT("当前皮肤:") + activeName),
|
|
16416
|
-
React.createElement("span", {
|
|
16417
|
-
style: { fontSize: "11px", padding: "1px 6px", borderRadius: "999px", background: "rgba(142,230,168,0.16)", color: "#8ee6a8" },
|
|
16418
|
-
}, weT("已应用")),
|
|
16419
|
-
));
|
|
16420
|
-
}
|
|
16421
|
-
|
|
16422
|
-
// ⚠️ **壁纸在台时皮肤不会生效** —— 这是皮肤中心写死的行为,不是我们的 bug:
|
|
16423
|
-
// · host 侧:peer(= 本插件)持久化的壁纸选择非空时,它从文档里**扣住**皮肤;
|
|
16424
|
-
// · client 侧:它盯 `body[data-we-wallpaper]`,只要那个标记在,就把皮肤让位
|
|
16425
|
-
// (源码注释:`an active WE wallpaper suppresses skin media`)。
|
|
16426
|
-
// 于是"点了使用但界面没变"最可能的原因就是**壁纸还开着**。这里直接读那个标记,
|
|
16427
|
-
// 命中就把话说清楚,并告诉用户该先做什么。
|
|
16428
|
-
let wallpaperOn = false;
|
|
16429
|
-
try {
|
|
16430
|
-
wallpaperOn = Boolean(document.body && document.body.hasAttribute("data-we-wallpaper"));
|
|
16431
|
-
} catch { /* 拿不到 DOM 就当没有 */ }
|
|
16432
|
-
if (wallpaperOn) {
|
|
16433
|
-
children.push(React.createElement("div", {
|
|
16434
|
-
key: "wpwarning",
|
|
16435
|
-
className: "we-picker__hint",
|
|
16436
|
-
style: {
|
|
16437
|
-
margin: "4px 0 10px",
|
|
16438
|
-
padding: "7px 10px",
|
|
16439
|
-
borderRadius: "8px",
|
|
16440
|
-
background: "rgba(255,196,120,0.12)",
|
|
16441
|
-
color: "#ffd08a",
|
|
16442
|
-
lineHeight: 1.5,
|
|
16443
|
-
},
|
|
16444
|
-
}, weT("⚠️ 当前有壁纸在台:皮肤会被壁纸让位,切换不会生效。")
|
|
16445
|
-
+ weT(" 先到「壁纸」页签点「清除」,再回来切换皮肤。")));
|
|
16446
|
-
}
|
|
16447
|
-
|
|
16448
|
-
if (error) {
|
|
16449
|
-
children.push(React.createElement("div", {
|
|
16450
|
-
key: "err",
|
|
16451
|
-
className: "we-picker__hint",
|
|
16452
|
-
style: { color: "#ff9a9a", margin: "6px 0 10px" },
|
|
16453
|
-
}, skinsErrText(error)));
|
|
16454
|
-
}
|
|
16455
|
-
if (note) {
|
|
16456
|
-
children.push(React.createElement("div", {
|
|
16457
|
-
key: "note",
|
|
16458
|
-
className: "we-picker__hint",
|
|
16459
|
-
style: {
|
|
16460
|
-
color: note.kind === "err" ? "#ff9a9a" : "#8ee6a8",
|
|
16461
|
-
margin: "6px 0 10px",
|
|
16462
|
-
display: "flex",
|
|
16463
|
-
gap: "10px",
|
|
16464
|
-
alignItems: "center",
|
|
16465
|
-
flexWrap: "wrap",
|
|
16466
|
-
},
|
|
16467
|
-
},
|
|
16468
|
-
React.createElement("span", null, note.text),
|
|
16469
|
-
note.reload
|
|
16470
|
-
? React.createElement("button", {
|
|
16471
|
-
type: "button",
|
|
16472
|
-
className: "we-picker__btn we-picker__btn--primary",
|
|
16473
|
-
style: { fontSize: "11.5px", padding: "3px 10px" },
|
|
16474
|
-
onClick: () => {
|
|
16475
|
-
// 与自动那条路一致:优先硬跳转,退而 reload。只重载前端页面 ——
|
|
16476
|
-
// 对话与设置都在宿主侧,不会丢。
|
|
16477
|
-
try { window.location.replace(window.location.href); } catch {
|
|
16478
|
-
try { window.location.reload(); } catch { /* 极端环境刷不动就让用户手动重启 */ }
|
|
16479
|
-
}
|
|
16480
|
-
},
|
|
16481
|
-
title: weT("整页重载 —— 皮肤动效(粒子等)需要重载才跑;纯配色已即时生效"),
|
|
16482
|
-
}, weT("重载以播放动效"))
|
|
16483
|
-
: null,
|
|
16484
|
-
));
|
|
16485
|
-
}
|
|
16486
|
-
children.push(React.createElement("div", {
|
|
16487
|
-
key: "search",
|
|
16488
|
-
style: { display: "flex", gap: "8px", alignItems: "center", margin: "2px 0 4px" },
|
|
16489
|
-
},
|
|
16490
|
-
React.createElement("input", {
|
|
16491
|
-
type: "text",
|
|
16492
|
-
value: q,
|
|
16493
|
-
placeholder: weT("搜索名称 / 作者 / 标签"),
|
|
16494
|
-
className: "we-picker__search",
|
|
16495
|
-
onChange: (e) => setQ(e.target.value),
|
|
16496
|
-
style: { flex: "1 1 auto" },
|
|
16497
|
-
}),
|
|
16498
|
-
React.createElement("span", { className: "we-picker__hint" },
|
|
16499
|
-
weT("已装 ") + installedCount + " / " + list.length),
|
|
16500
|
-
));
|
|
16501
|
-
|
|
16502
|
-
if (loading && list.length === 0) {
|
|
16503
|
-
children.push(React.createElement("div", { key: "loading", className: "we-picker__empty" },
|
|
16504
|
-
React.createElement("span", { className: "we-picker__empty-title" }, weT("正在从站点拉取皮肤包…"))));
|
|
16505
|
-
} else if (!error && shown.length === 0) {
|
|
16506
|
-
children.push(React.createElement("div", { key: "empty", className: "we-picker__empty" },
|
|
16507
|
-
React.createElement("span", { className: "we-picker__empty-title" }, weT("没有匹配的皮肤包")),
|
|
16508
|
-
React.createElement("span", { className: "we-picker__hint" }, weT("换个关键词,或点「刷新」重新拉取"))));
|
|
16509
|
-
}
|
|
16510
|
-
|
|
16511
|
-
if (shown.length > 0) {
|
|
16512
|
-
children.push(React.createElement("div", { key: "grid", style: SKINS_STYLE.grid },
|
|
16513
|
-
shown.map((s) => {
|
|
16514
|
-
const previewRel = s.previewLight || s.previewDark || "";
|
|
16515
|
-
const previewSrc = previewRel
|
|
16516
|
-
? apiUrl("/skins/preview?p=" + encodeURIComponent(previewRel))
|
|
16517
|
-
: "";
|
|
16518
|
-
const isBusy = busy === s.id;
|
|
16519
|
-
const btn = (label, onClick, disabled, primary) => React.createElement("button", {
|
|
16520
|
-
type: "button",
|
|
16521
|
-
className: "we-picker__btn" + (primary ? " we-picker__btn--primary" : ""),
|
|
16522
|
-
style: { flex: "1 1 0", fontSize: "11.5px", padding: "4px 6px" },
|
|
16523
|
-
disabled: Boolean(disabled) || Boolean(busy),
|
|
16524
|
-
onClick,
|
|
16525
|
-
}, label);
|
|
16526
|
-
|
|
16527
|
-
const actions = [];
|
|
16528
|
-
if (!s.installed) {
|
|
16529
|
-
actions.push(btn(isBusy ? weT("安装中…") : weT("下载安装"), () => act("install", s.id, s.name), false, true));
|
|
16530
|
-
} else {
|
|
16531
|
-
if (!s.active) {
|
|
16532
|
-
actions.push(btn(isBusy ? weT("切换中…") : weT("使用"), () => act("activate", s.id, s.name), false, true));
|
|
16533
|
-
actions.push(btn(weT("删除"), () => act("remove", s.id, s.name), isBusy, false));
|
|
16534
|
-
} else {
|
|
16535
|
-
actions.push(btn(weT("使用中"), () => {}, true, false));
|
|
16536
|
-
}
|
|
16537
|
-
}
|
|
16538
|
-
|
|
16539
|
-
return React.createElement("div", { key: s.id, style: SKINS_STYLE.card },
|
|
16540
|
-
previewSrc
|
|
16541
|
-
? React.createElement("img", {
|
|
16542
|
-
src: previewSrc, alt: s.name, loading: "lazy", draggable: false, style: SKINS_STYLE.thumb,
|
|
16543
|
-
})
|
|
16544
|
-
: React.createElement("div", { style: SKINS_STYLE.thumb }),
|
|
16545
|
-
React.createElement("div", { style: SKINS_STYLE.body },
|
|
16546
|
-
React.createElement("div", { style: SKINS_STYLE.title }, s.name || s.id),
|
|
16547
|
-
s.tagline ? React.createElement("div", { style: SKINS_STYLE.meta }, s.tagline) : null,
|
|
16548
|
-
(s.tags || []).length
|
|
16549
|
-
? React.createElement("div", { style: SKINS_STYLE.tags },
|
|
16550
|
-
s.tags.slice(0, 4).map((t) => React.createElement("span", { key: t, style: SKINS_STYLE.tag }, t)))
|
|
16551
|
-
: null,
|
|
16552
|
-
React.createElement("div", { style: SKINS_STYLE.actions }, actions),
|
|
16553
|
-
),
|
|
16554
|
-
);
|
|
16555
|
-
})));
|
|
16556
|
-
}
|
|
16557
|
-
|
|
16558
|
-
return React.createElement(React.Fragment, null, children);
|
|
16559
|
-
}
|
|
16560
|
-
|
|
16561
|
-
/** 页签薄包装:`renderActiveTab` 只认这个签名(与别的 `render*Tab(ctx)` 一致)。 */
|
|
16562
|
-
function renderSkinsTab(ctx) {
|
|
16563
|
-
return React.createElement(SkinsTabPanel, null);
|
|
16564
|
-
}
|
|
16565
|
-
|
|
16566
16174
|
// ── 内联模块:src/picker-model.js —— 选择器模型层:过滤判定 + 派生数据 + 分页切片(纯函数 / 显式入参 / 零 DOM)(构建期注入;勿手改本段)──
|
|
16567
16175
|
/**
|
|
16568
16176
|
* picker-model.js — 壁纸选择器的**模型层**:过滤判定 + 派生数据 + 分页切片。
|
|
@@ -16667,8 +16275,25 @@ window.__ModuleLoader__.load({
|
|
|
16667
16275
|
return w.type === "scene" && Boolean(w.frameUrl);
|
|
16668
16276
|
}
|
|
16669
16277
|
|
|
16670
|
-
|
|
16671
|
-
|
|
16278
|
+
/**
|
|
16279
|
+
* 壁纸来源:`site` = 从我们站点拉下来的(inventory 里带 `source: "folia"`),
|
|
16280
|
+
* 其余一律算 `local`(本机 Wallpaper Engine 库 / 用户上传 —— 它们都没有那个标记)。
|
|
16281
|
+
*
|
|
16282
|
+
* 为什么不给本机条目补一个 `source`:那要动宿主侧的 inventory 产出,而客户端只靠
|
|
16283
|
+
* “有没有 folia 标记”就能判对,改面更小。
|
|
16284
|
+
*/
|
|
16285
|
+
function sourceOf(w) {
|
|
16286
|
+
return w && w.source === "folia" ? "site" : "local";
|
|
16287
|
+
}
|
|
16288
|
+
|
|
16289
|
+
function matchesSourceFilter(w, sourceFilter) {
|
|
16290
|
+
if (!sourceFilter || sourceFilter === "all") return true;
|
|
16291
|
+
return sourceOf(w) === sourceFilter;
|
|
16292
|
+
}
|
|
16293
|
+
|
|
16294
|
+
function isRotatableWallpaper(w, ratingFilter, typeFilter, sourceFilter) {
|
|
16295
|
+
return isPlayableType(w) && matchesRatingFilter(w, ratingFilter)
|
|
16296
|
+
&& matchesTypeFilter(w, typeFilter) && matchesSourceFilter(w, sourceFilter);
|
|
16672
16297
|
}
|
|
16673
16298
|
|
|
16674
16299
|
// 「正在应用的壁纸可以继续播」判据:可播放 ∧ 分级闸门,**不过滤类型**。过滤档分两种
|
|
@@ -16692,9 +16317,9 @@ window.__ModuleLoader__.load({
|
|
|
16692
16317
|
}
|
|
16693
16318
|
|
|
16694
16319
|
/** 可轮换列表:可播放类型 ∧ 命中两级过滤 ∧ 未隐藏(网格与轮换编辑器共用的那份)。 */
|
|
16695
|
-
function playableWallpapers(wallpapers, ratingFilter, typeFilter, hiddenIds) {
|
|
16320
|
+
function playableWallpapers(wallpapers, ratingFilter, typeFilter, hiddenIds, sourceFilter) {
|
|
16696
16321
|
return wallpapers.filter(
|
|
16697
|
-
(w) => isRotatableWallpaper(w, ratingFilter, typeFilter) && !isHiddenWallpaper(w.id, hiddenIds),
|
|
16322
|
+
(w) => isRotatableWallpaper(w, ratingFilter, typeFilter, sourceFilter) && !isHiddenWallpaper(w.id, hiddenIds),
|
|
16698
16323
|
);
|
|
16699
16324
|
}
|
|
16700
16325
|
|
|
@@ -16708,15 +16333,19 @@ window.__ModuleLoader__.load({
|
|
|
16708
16333
|
const hiddenIds = input.hiddenIds || [];
|
|
16709
16334
|
const ratingFilter = input.ratingFilter;
|
|
16710
16335
|
const typeFilter = input.typeFilter;
|
|
16336
|
+
const sourceFilter = input.sourceFilter;
|
|
16711
16337
|
// 标题搜索(选择器模态框):在两级过滤之上再收窄可播放网格。大小写不敏感的**子串**匹配。
|
|
16712
16338
|
const query = (input.search || "").trim().toLowerCase();
|
|
16713
16339
|
// 只有可播放的 Video/Web/Image 才进网格 —— Scene/Application 不能嵌进 web UI,
|
|
16714
16340
|
// 隐掉它们网格才有用。已隐藏(软删除)的壁纸离开这里去「已隐藏」区;两级过滤再收窄。
|
|
16715
16341
|
const playableList = list.filter((w) =>
|
|
16716
|
-
isRotatableWallpaper(w, ratingFilter, typeFilter) && !isHiddenWallpaper(w.id, hiddenIds)
|
|
16342
|
+
isRotatableWallpaper(w, ratingFilter, typeFilter, sourceFilter) && !isHiddenWallpaper(w.id, hiddenIds)
|
|
16717
16343
|
&& (!query || String(w.title || "").toLowerCase().indexOf(query) !== -1));
|
|
16718
16344
|
// 两个过滤下拉的分档计数(可播放、未隐藏):反映实际可用量,与当前生效的过滤无关。
|
|
16719
16345
|
const basePlayable = list.filter((w) => isPlayableType(w) && !isHiddenWallpaper(w.id, hiddenIds));
|
|
16346
|
+
// 来源分档计数(可播放、未隐藏、**不受来源过滤影响** —— 它要能反映“切过去有多少张”)。
|
|
16347
|
+
const sourceCounts = { local: 0, site: 0 };
|
|
16348
|
+
for (const w of basePlayable) sourceCounts[sourceOf(w)] += 1;
|
|
16720
16349
|
// 单遍聚合 —— 分档计数各刷一遍 basePlayable 是 10 次 O(n)(5 个分级档 + 5 个类型档)。
|
|
16721
16350
|
const ratingCounts = { everyone: 0, pg13: 0, mature: 0, unrated: 0 };
|
|
16722
16351
|
const typeCounts = { video: 0, web: 0, image: 0, scene: 0 };
|
|
@@ -16725,7 +16354,7 @@ window.__ModuleLoader__.load({
|
|
|
16725
16354
|
ratingCounts[r] = (ratingCounts[r] || 0) + 1;
|
|
16726
16355
|
typeCounts[w.type] = (typeCounts[w.type] || 0) + 1;
|
|
16727
16356
|
}
|
|
16728
|
-
const editorList = playableWallpapers(list, ratingFilter, typeFilter, hiddenIds);
|
|
16357
|
+
const editorList = playableWallpapers(list, ratingFilter, typeFilter, hiddenIds, sourceFilter);
|
|
16729
16358
|
const hiddenList = hiddenWallpapers(list, hiddenIds);
|
|
16730
16359
|
return {
|
|
16731
16360
|
query,
|
|
@@ -16735,6 +16364,7 @@ window.__ModuleLoader__.load({
|
|
|
16735
16364
|
typeCounts,
|
|
16736
16365
|
editorList,
|
|
16737
16366
|
hiddenList,
|
|
16367
|
+
sourceCounts,
|
|
16738
16368
|
normalPage: pageSlice(playableList, input.page),
|
|
16739
16369
|
hiddenPageView: pageSlice(hiddenList, input.hiddenPage),
|
|
16740
16370
|
editorPageView: pageSlice(editorList, input.editorPage),
|
|
@@ -17107,7 +16737,7 @@ window.__ModuleLoader__.load({
|
|
|
17107
16737
|
}
|
|
17108
16738
|
|
|
17109
16739
|
function renderPickerFilterRow(ctx) {
|
|
17110
|
-
const { sel, basePlayable, ratingCounts, typeCounts, onSearchInput, onRatingFilterChange, onTypeFilterChange } = ctx;
|
|
16740
|
+
const { sel, basePlayable, ratingCounts, typeCounts, sourceCounts, onSearchInput, onRatingFilterChange, onTypeFilterChange, onSourceFilterChange } = ctx;
|
|
17111
16741
|
return React.createElement("div", { className: "we-picker__row we-picker__filter-row" },
|
|
17112
16742
|
// 标题搜索:几百上千张壁纸时最快的定位方式。输入即过滤,与分级/类型过滤叠加。
|
|
17113
16743
|
React.createElement("input", {
|
|
@@ -17117,6 +16747,20 @@ window.__ModuleLoader__.load({
|
|
|
17117
16747
|
"aria-label": weT("搜索壁纸标题"),
|
|
17118
16748
|
onInput: onSearchInput,
|
|
17119
16749
|
}),
|
|
16750
|
+
// 来源:本机 Wallpaper Engine 库 vs 我们站点。放在最前面 —— 它是最粗的一刀,
|
|
16751
|
+
// 用户常常只想看其中一边(本机几十张 vs 站点几条,混在一起没法挑)。
|
|
16752
|
+
React.createElement("span", { className: "we-picker__hint we-picker__label" }, weT("来源")),
|
|
16753
|
+
React.createElement("select", {
|
|
16754
|
+
className: "we-picker__playlist-select",
|
|
16755
|
+
value: sel.sourceFilter || "all",
|
|
16756
|
+
onChange: onSourceFilterChange,
|
|
16757
|
+
"aria-label": weT("来源"),
|
|
16758
|
+
title: weT("按来源过滤:本机 Wallpaper Engine 库,还是从我们站点拉下来的壁纸"),
|
|
16759
|
+
},
|
|
16760
|
+
React.createElement("option", { value: "all" }, weT("全部({n})", { n: basePlayable.length })),
|
|
16761
|
+
React.createElement("option", { value: "local" }, weT("本机 Wallpaper Engine({n})", { n: (sourceCounts && sourceCounts.local) || 0 })),
|
|
16762
|
+
React.createElement("option", { value: "site" }, weT("站点壁纸({n})", { n: (sourceCounts && sourceCounts.site) || 0 })),
|
|
16763
|
+
),
|
|
17120
16764
|
React.createElement("span", { className: "we-picker__hint we-picker__label" }, weT("内容分级")),
|
|
17121
16765
|
React.createElement("select", {
|
|
17122
16766
|
className: "we-picker__playlist-select",
|
|
@@ -17308,6 +16952,215 @@ window.__ModuleLoader__.load({
|
|
|
17308
16952
|
return React.createElement("div", { key: p.name, className: "we-picker__props-row" }, label, control);
|
|
17309
16953
|
}
|
|
17310
16954
|
|
|
16955
|
+
// ── 内联模块:src/panel-tabs-boot.js —— 「开机动画」页签:候选/下载/选择/开关。播放不在前端 —— 启动那一刻应用还没挂载,由宿主注入的脚本负责(构建期注入;勿手改本段)──
|
|
16956
|
+
/**
|
|
16957
|
+
* panel-tabs-boot.js — 「开机动画」页签(放在「壁纸」左侧)。
|
|
16958
|
+
*
|
|
16959
|
+
* 它只做四件事:看候选、下载、选一个、开关。**播放不在这里** —— 播放发生在启动那一刻,
|
|
16960
|
+
* 由宿主注入的脚本完成(`lib/boot/index.js` 的 BOOT_SCREEN_SOURCE),因为那时候应用
|
|
16961
|
+
* 还没挂载,任何 React 组件都还不存在。
|
|
16962
|
+
*
|
|
16963
|
+
* 数据全部来自宿主(`/folia/boot/*`),浏览器不直连站点:
|
|
16964
|
+
* GET /folia/boot/list 候选 + 本地状态
|
|
16965
|
+
* POST /folia/boot/download 下一条到本地
|
|
16966
|
+
* POST /folia/boot/select 选中并启用
|
|
16967
|
+
* POST /folia/boot/toggle 开/关
|
|
16968
|
+
* POST /folia/boot/settings 淡出/最短显示/最长停留
|
|
16969
|
+
* POST /folia/boot/remove 删本地
|
|
16970
|
+
*/
|
|
16971
|
+
|
|
16972
|
+
function bootFmtBytes(n) {
|
|
16973
|
+
const v = Number(n) || 0;
|
|
16974
|
+
if (v >= 1024 * 1024) return (v / 1024 / 1024).toFixed(1) + " MB";
|
|
16975
|
+
if (v >= 1024) return Math.round(v / 1024) + " KB";
|
|
16976
|
+
return v + " B";
|
|
16977
|
+
}
|
|
16978
|
+
|
|
16979
|
+
const BOOT_STYLE = {
|
|
16980
|
+
grid: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))", gap: "10px", marginTop: "8px" },
|
|
16981
|
+
card: {
|
|
16982
|
+
display: "flex", flexDirection: "column", gap: "6px", padding: "9px 10px",
|
|
16983
|
+
border: "1px solid rgba(255,255,255,0.12)", borderRadius: "10px", background: "rgba(255,255,255,0.04)",
|
|
16984
|
+
},
|
|
16985
|
+
title: { fontSize: "12.5px", fontWeight: 600 },
|
|
16986
|
+
meta: { fontSize: "11px", opacity: 0.62 },
|
|
16987
|
+
row: { display: "flex", gap: "6px", alignItems: "center" },
|
|
16988
|
+
};
|
|
16989
|
+
|
|
16990
|
+
function BootTabPanel() {
|
|
16991
|
+
const [state, setState] = React.useState(null);
|
|
16992
|
+
const [busy, setBusy] = React.useState("");
|
|
16993
|
+
const [note, setNote] = React.useState(null);
|
|
16994
|
+
// 下载没有进度通道(宿主一次性 fetch 完才返回),但等待必须"看得见":
|
|
16995
|
+
// 每秒 +1 的秒表足以让用户知道它没卡死。
|
|
16996
|
+
const [tick, setTick] = React.useState(0);
|
|
16997
|
+
|
|
16998
|
+
const load = React.useCallback(async (force) => {
|
|
16999
|
+
try {
|
|
17000
|
+
const resp = await apiJson("/boot/list" + (force ? "?refresh=1" : ""), { parse: "always" });
|
|
17001
|
+
const r = (resp && resp.data) || null;
|
|
17002
|
+
setState(r || { ok: false, clips: [], error: "network" });
|
|
17003
|
+
} catch (e) {
|
|
17004
|
+
setState({ ok: false, clips: [], error: "network" });
|
|
17005
|
+
}
|
|
17006
|
+
}, []);
|
|
17007
|
+
|
|
17008
|
+
React.useEffect(() => { load(false); }, [load]);
|
|
17009
|
+
|
|
17010
|
+
React.useEffect(() => {
|
|
17011
|
+
if (!busy) return undefined;
|
|
17012
|
+
const t = setInterval(() => setTick((n) => n + 1), 1000);
|
|
17013
|
+
return () => clearInterval(t);
|
|
17014
|
+
}, [busy]);
|
|
17015
|
+
|
|
17016
|
+
async function act(kind, payload, label) {
|
|
17017
|
+
if (busy) return;
|
|
17018
|
+
setBusy(payload && payload.id ? payload.id : kind);
|
|
17019
|
+
setTick(0);
|
|
17020
|
+
setNote(null);
|
|
17021
|
+
try {
|
|
17022
|
+
const resp = await apiPostJson("/boot/" + kind, payload || {}, { parse: "always" });
|
|
17023
|
+
const r = (resp && resp.data) || null;
|
|
17024
|
+
if (resp && resp.ok && r && r.ok) {
|
|
17025
|
+
if (kind === "download") setNote({ ok: true, text: weT("已下载:") + label });
|
|
17026
|
+
if (kind === "select") setNote({ ok: true, text: weT("已选中「") + label + weT("」,下次启动生效") });
|
|
17027
|
+
if (kind === "remove") setNote({ ok: true, text: weT("已删除:") + label });
|
|
17028
|
+
if (kind === "toggle") setNote({ ok: true, text: payload.enabled ? weT("开机动画已开启") : weT("开机动画已关闭") });
|
|
17029
|
+
if (kind === "settings") setNote({ ok: true, text: weT("已保存") });
|
|
17030
|
+
await load(true);
|
|
17031
|
+
} else {
|
|
17032
|
+
const code = (r && r.error) || "failed";
|
|
17033
|
+
setNote({ ok: false, text: weT("操作失败:") + code + (r && r.detail ? "(" + r.detail + ")" : "") });
|
|
17034
|
+
}
|
|
17035
|
+
} catch (e) {
|
|
17036
|
+
setNote({ ok: false, text: weT("请求失败:") + (e && e.message ? e.message : weT("网络错误")) });
|
|
17037
|
+
} finally {
|
|
17038
|
+
setBusy("");
|
|
17039
|
+
}
|
|
17040
|
+
}
|
|
17041
|
+
|
|
17042
|
+
const clips = (state && state.clips) || [];
|
|
17043
|
+
const enabled = Boolean(state && state.enabled);
|
|
17044
|
+
const activeId = (state && state.clipId) || "";
|
|
17045
|
+
const installedCount = clips.filter((c) => c.installed).length;
|
|
17046
|
+
|
|
17047
|
+
const children = [];
|
|
17048
|
+
|
|
17049
|
+
children.push(React.createElement("div", { className: "we-picker__section-head", key: "head" },
|
|
17050
|
+
React.createElement("span", { className: "we-picker__section-label" }, weT("🎬 开机动画") + (clips.length ? " · " + clips.length : "")),
|
|
17051
|
+
React.createElement("button", {
|
|
17052
|
+
type: "button", className: "we-picker__btn", style: { marginLeft: "auto" },
|
|
17053
|
+
disabled: Boolean(busy), onClick: () => load(true),
|
|
17054
|
+
}, weT("刷新")),
|
|
17055
|
+
));
|
|
17056
|
+
|
|
17057
|
+
// 开关 + 当前选择
|
|
17058
|
+
children.push(React.createElement("div", {
|
|
17059
|
+
key: "switch", style: { ...BOOT_STYLE.row, margin: "6px 0 2px", flexWrap: "wrap" },
|
|
17060
|
+
},
|
|
17061
|
+
React.createElement("label", { style: { display: "flex", gap: "6px", alignItems: "center", fontSize: "12px" } },
|
|
17062
|
+
React.createElement("input", {
|
|
17063
|
+
type: "checkbox", checked: enabled, disabled: Boolean(busy) || !activeId,
|
|
17064
|
+
onChange: (e) => act("toggle", { enabled: e.target.checked }, ""),
|
|
17065
|
+
}),
|
|
17066
|
+
weT("启用(下次启动播放)"),
|
|
17067
|
+
),
|
|
17068
|
+
React.createElement("span", { className: "we-picker__hint" },
|
|
17069
|
+
activeId ? weT("当前:") + ((clips.find((c) => c.id === activeId) || {}).title || activeId) : weT("未选择")),
|
|
17070
|
+
));
|
|
17071
|
+
|
|
17072
|
+
children.push(React.createElement("div", { key: "hint", className: "we-picker__hint", style: { margin: "2px 0 6px", opacity: 0.75 } },
|
|
17073
|
+
weT("下载一条并「使用」后,每次启动都会先播它;默认播完才淡出进入界面(想更快就切「界面就绪就进」)。点击或按 Esc 可立即跳过。")));
|
|
17074
|
+
|
|
17075
|
+
if (state && state.error && !clips.length) {
|
|
17076
|
+
children.push(React.createElement("div", { key: "err", className: "we-picker__hint", style: { color: "#ff9a9a" } },
|
|
17077
|
+
weT("候选清单读不到:") + state.error));
|
|
17078
|
+
}
|
|
17079
|
+
|
|
17080
|
+
if (note) {
|
|
17081
|
+
children.push(React.createElement("div", { key: "note", className: "we-picker__hint", style: { color: note.ok ? "#8ee6a8" : "#ff9a9a", margin: "4px 0 8px" } }, note.text));
|
|
17082
|
+
}
|
|
17083
|
+
|
|
17084
|
+
// 淡出 / 停留
|
|
17085
|
+
if (state) {
|
|
17086
|
+
const opt = (label, key, list) => React.createElement("label", { style: { display: "flex", gap: "4px", alignItems: "center", fontSize: "12px" } },
|
|
17087
|
+
label,
|
|
17088
|
+
React.createElement("select", {
|
|
17089
|
+
className: "we-picker__playlist-select",
|
|
17090
|
+
value: String(state[key]),
|
|
17091
|
+
disabled: Boolean(busy),
|
|
17092
|
+
onChange: (e) => act("settings", { [key]: Number(e.target.value) }, ""),
|
|
17093
|
+
}, list.map(([v, t]) => React.createElement("option", { key: v, value: String(v) }, t))),
|
|
17094
|
+
);
|
|
17095
|
+
children.push(React.createElement("div", { key: "opts", style: { ...BOOT_STYLE.row, gap: "14px", flexWrap: "wrap", margin: "2px 0 8px" } },
|
|
17096
|
+
// 进入方式:默认「播完再进」——之前只有「就绪即进」,15 秒的片子 1.2 秒就被掐掉。
|
|
17097
|
+
React.createElement("label", { style: { display: "flex", gap: "4px", alignItems: "center", fontSize: "12px" } },
|
|
17098
|
+
weT("进入方式"),
|
|
17099
|
+
React.createElement("select", {
|
|
17100
|
+
className: "we-picker__playlist-select",
|
|
17101
|
+
value: String(state.enterMode || "tail"),
|
|
17102
|
+
disabled: Boolean(busy),
|
|
17103
|
+
onChange: (e) => act("settings", { enterMode: e.target.value }, ""),
|
|
17104
|
+
},
|
|
17105
|
+
React.createElement("option", { value: "tail" }, weT("播完再进(默认)")),
|
|
17106
|
+
React.createElement("option", { value: "ready" }, weT("界面就绪就进")),
|
|
17107
|
+
React.createElement("option", { value: "click" }, weT("点一下才进")),
|
|
17108
|
+
),
|
|
17109
|
+
),
|
|
17110
|
+
opt(weT("淡出时长"), "fadeMs", [[400, "0.4 秒"], [600, "0.6 秒"], [900, "0.9 秒"], [1200, "1.2 秒"], [1600, "1.6 秒"]]),
|
|
17111
|
+
opt(weT("最短显示"), "minShowMs", [[0, "不限制"], [600, "0.6 秒"], [1200, "1.2 秒"], [2000, "2 秒"]]),
|
|
17112
|
+
opt(weT("最长停留"), "maxHoldMs", [[10000, "10 秒"], [20000, "20 秒(默认)"], [30000, "30 秒"], [60000, "60 秒"]]),
|
|
17113
|
+
));
|
|
17114
|
+
}
|
|
17115
|
+
|
|
17116
|
+
children.push(React.createElement("div", { key: "count", className: "we-picker__hint", style: { margin: "2px 0 4px" } },
|
|
17117
|
+
weT("已下载 ") + installedCount + " / " + clips.length));
|
|
17118
|
+
|
|
17119
|
+
if (clips.length) {
|
|
17120
|
+
children.push(React.createElement("div", { key: "grid", style: BOOT_STYLE.grid },
|
|
17121
|
+
clips.map((c) => {
|
|
17122
|
+
const isBusy = busy === c.id;
|
|
17123
|
+
const isActive = c.id === activeId;
|
|
17124
|
+
const btn = (label, onClick, disabled, primary) => React.createElement("button", {
|
|
17125
|
+
type: "button",
|
|
17126
|
+
className: "we-picker__btn" + (primary ? " we-picker__btn--primary" : ""),
|
|
17127
|
+
style: { flex: "1 1 0", fontSize: "11.5px", padding: "4px 6px" },
|
|
17128
|
+
disabled: Boolean(disabled) || Boolean(busy),
|
|
17129
|
+
onClick,
|
|
17130
|
+
}, label);
|
|
17131
|
+
const actions = [];
|
|
17132
|
+
if (!c.installed) {
|
|
17133
|
+
actions.push(btn(isBusy ? (weT("下载中… ") + tick + "s") : weT("下载"), () => act("download", { id: c.id }, c.title), false, true));
|
|
17134
|
+
} else if (!isActive) {
|
|
17135
|
+
actions.push(btn(weT("使用"), () => act("select", { id: c.id }, c.title), false, true));
|
|
17136
|
+
actions.push(btn(weT("删除"), () => act("remove", { id: c.id }, c.title), isBusy, false));
|
|
17137
|
+
} else {
|
|
17138
|
+
actions.push(btn(weT("使用中"), () => {}, true, false));
|
|
17139
|
+
}
|
|
17140
|
+
return React.createElement("div", { key: c.id, style: BOOT_STYLE.card },
|
|
17141
|
+
React.createElement("div", { style: BOOT_STYLE.title }, c.title || c.id),
|
|
17142
|
+
React.createElement("div", { style: BOOT_STYLE.meta },
|
|
17143
|
+
[c.durationSec ? Number(c.durationSec).toFixed(1) + "s" : null,
|
|
17144
|
+
bootFmtBytes(c.localBytes || c.bytes)].filter(Boolean).join(" · ")),
|
|
17145
|
+
c.faststart === false
|
|
17146
|
+
? React.createElement("div", { style: { ...BOOT_STYLE.meta, color: "#ffd08a" } }, weT("未做 faststart,播放可能更慢"))
|
|
17147
|
+
: null,
|
|
17148
|
+
React.createElement("div", { style: { ...BOOT_STYLE.row, marginTop: "4px" } }, actions),
|
|
17149
|
+
);
|
|
17150
|
+
})));
|
|
17151
|
+
} else if (state) {
|
|
17152
|
+
children.push(React.createElement("div", { key: "empty", className: "we-picker__empty" },
|
|
17153
|
+
React.createElement("span", { className: "we-picker__empty-title" }, weT("没有可用的开机动画")),
|
|
17154
|
+
React.createElement("span", { className: "we-picker__hint" }, weT("素材来自站点;站点不可达时这里为空,但不影响启动"))));
|
|
17155
|
+
}
|
|
17156
|
+
|
|
17157
|
+
return React.createElement(React.Fragment, null, children);
|
|
17158
|
+
}
|
|
17159
|
+
|
|
17160
|
+
function renderBootTab() {
|
|
17161
|
+
return React.createElement(BootTabPanel, null);
|
|
17162
|
+
}
|
|
17163
|
+
|
|
17311
17164
|
// ── 内联模块:src/quick-panel.js —— 快捷播放面板(官方侧栏 tab 与低版本抽屉共用同一份内容):当前壁纸 + 轮播 + 列表快切 + 声音 + 设置入口。列表虚拟滚动(spacer + 可视窗口)(构建期注入;勿手改本段)──
|
|
17312
17165
|
/**
|
|
17313
17166
|
* quick-panel.js — 快捷播放面板(侧边栏的**唯一**内容):当前壁纸 + 轮播 + 三档页签
|
|
@@ -17478,10 +17331,9 @@ window.__ModuleLoader__.load({
|
|
|
17478
17331
|
// 对照很常见)。与设置页的页签表(PICKER_TABS)是两件事 —— 这里只镜像用户点名要的
|
|
17479
17332
|
// 两页,外加壁纸页。
|
|
17480
17333
|
const QP_TABS = [
|
|
17334
|
+
// 开机动画放在「壁纸」左边:它和壁纸是并列的两种"开机时看到什么",不是壁纸的子功能。
|
|
17335
|
+
{ id: "boot", get label() { return weT("开机动画"); } },
|
|
17481
17336
|
{ id: "wallpaper", get label() { return weT("壁纸"); } },
|
|
17482
|
-
// 「皮肤包」与设置页那个页签是**同一个组件**(SkinsTabPanel):首页这里只是把它挂进抽屉,
|
|
17483
|
-
// 不另写一份列表逻辑 —— 两份实现迟早会漂移。
|
|
17484
|
-
{ id: "skins", get label() { return weT("皮肤包"); } },
|
|
17485
17337
|
{ id: "appearance", get label() { return weT("外观"); } },
|
|
17486
17338
|
{ id: "playback", get label() { return weT("播放"); } },
|
|
17487
17339
|
];
|
|
@@ -17609,7 +17461,7 @@ window.__ModuleLoader__.load({
|
|
|
17609
17461
|
React.createElement("span", { className: "we-qp__item-thumb" },
|
|
17610
17462
|
w.preview
|
|
17611
17463
|
? React.createElement("img", {
|
|
17612
|
-
src: w.preview, alt: "", loading: "lazy",
|
|
17464
|
+
src: w.preview, alt: "", loading: "lazy", decoding: "async",
|
|
17613
17465
|
onError: (e) => { e.target.style.display = "none"; },
|
|
17614
17466
|
onLoad: (e) => { e.target.style.opacity = "1"; },
|
|
17615
17467
|
})
|
|
@@ -17637,7 +17489,7 @@ window.__ModuleLoader__.load({
|
|
|
17637
17489
|
},
|
|
17638
17490
|
w.preview
|
|
17639
17491
|
? React.createElement("img", {
|
|
17640
|
-
src: w.preview, alt: "", loading: "lazy",
|
|
17492
|
+
src: w.preview, alt: "", loading: "lazy", decoding: "async",
|
|
17641
17493
|
onError: (e) => { e.target.style.display = "none"; },
|
|
17642
17494
|
onLoad: (e) => { e.target.style.opacity = "1"; },
|
|
17643
17495
|
})
|
|
@@ -17846,7 +17698,17 @@ window.__ModuleLoader__.load({
|
|
|
17846
17698
|
);
|
|
17847
17699
|
}
|
|
17848
17700
|
|
|
17849
|
-
|
|
17701
|
+
// 来源档(本机 Wallpaper Engine 库 / 我们站点):与设置页同一个键 `sourceFilter`,
|
|
17702
|
+
// 同样"两处同步"——这里切,设置页跟着变,反之亦然。只筛列表,不拦正在播的壁纸。
|
|
17703
|
+
function qpSources() {
|
|
17704
|
+
return [
|
|
17705
|
+
{ id: "all", label: weT("全部来源") },
|
|
17706
|
+
{ id: "local", label: weT("本机") },
|
|
17707
|
+
{ id: "site", label: weT("站点") },
|
|
17708
|
+
];
|
|
17709
|
+
}
|
|
17710
|
+
|
|
17711
|
+
function qpRenderViewBar(sel, view, typeFilter, ratingFilter, switchView, sourceFilter) {
|
|
17850
17712
|
return React.createElement("div", { className: "we-qp__viewbar" },
|
|
17851
17713
|
React.createElement("input", {
|
|
17852
17714
|
className: "we-picker__text we-qp__search", type: "text",
|
|
@@ -17880,6 +17742,16 @@ window.__ModuleLoader__.load({
|
|
|
17880
17742
|
},
|
|
17881
17743
|
...qpTypes().map((t) => React.createElement("option", { key: t.id, value: t.id }, t.label)),
|
|
17882
17744
|
),
|
|
17745
|
+
// 来源筛选:本机 Wallpaper Engine 库 vs 我们站点(与设置页同一个键)。
|
|
17746
|
+
React.createElement("select", {
|
|
17747
|
+
className: "we-picker__select we-qp__source",
|
|
17748
|
+
value: sourceFilter,
|
|
17749
|
+
onChange: (e) => { setSetting("sourceFilter", e.target.value); emit(); },
|
|
17750
|
+
"aria-label": weT("来源筛选"),
|
|
17751
|
+
title: weT("按来源筛选:本机 Wallpaper Engine 库,还是从我们站点拉下来的壁纸"),
|
|
17752
|
+
},
|
|
17753
|
+
...qpSources().map((s) => React.createElement("option", { key: s.id, value: s.id }, s.label)),
|
|
17754
|
+
),
|
|
17883
17755
|
// 列表 / 卡片:标签式切换(滑动胶囊做激活指示、无分段底 —— 样式见
|
|
17884
17756
|
// .we-qp__viewtabs),偏好记 localStorage(QP_VIEW_KEY),两个壳共用一份。
|
|
17885
17757
|
// 语义仍是开关(role=group + aria-pressed),只是外观走标签形态。
|
|
@@ -17945,7 +17817,7 @@ window.__ModuleLoader__.load({
|
|
|
17945
17817
|
);
|
|
17946
17818
|
}
|
|
17947
17819
|
|
|
17948
|
-
function qpRenderLibrarySection(sel, view, typeFilter, ratingFilter, listRef, rows, cardsView, vTopH, vBtmH, q, switchView) {
|
|
17820
|
+
function qpRenderLibrarySection(sel, view, typeFilter, ratingFilter, listRef, rows, cardsView, vTopH, vBtmH, q, switchView, sourceFilter) {
|
|
17949
17821
|
return React.createElement("div", { className: "we-qp__section we-qp__library" },
|
|
17950
17822
|
!sel.loaded
|
|
17951
17823
|
? React.createElement("span", { className: "we-picker__hint" }, weT("扫描 Wallpaper Engine…"))
|
|
@@ -17959,7 +17831,7 @@ window.__ModuleLoader__.load({
|
|
|
17959
17831
|
}, weT(sel.loading ? "刷新中…" : "重试")),
|
|
17960
17832
|
)
|
|
17961
17833
|
: React.createElement(React.Fragment, null,
|
|
17962
|
-
qpRenderViewBar(sel, view, typeFilter, ratingFilter, switchView),
|
|
17834
|
+
qpRenderViewBar(sel, view, typeFilter, ratingFilter, switchView, sourceFilter),
|
|
17963
17835
|
qpRenderList(listRef, rows, view, cardsView, vTopH, vBtmH, q, ratingFilter, typeFilter, sel),
|
|
17964
17836
|
),
|
|
17965
17837
|
);
|
|
@@ -18079,8 +17951,11 @@ window.__ModuleLoader__.load({
|
|
|
18079
17951
|
const ratingFilter = qpRatings().some((r) => r.id === sel.contentRatingFilter)
|
|
18080
17952
|
? sel.contentRatingFilter : "all";
|
|
18081
17953
|
const playable = playableInventory();
|
|
17954
|
+
const sourceFilter = qpSources().some((s) => s.id === sel.sourceFilter) ? sel.sourceFilter : "all";
|
|
18082
17955
|
const filtered = playable.filter((w) => {
|
|
18083
17956
|
if (typeFilter !== "all" && w.type !== typeFilter) return false;
|
|
17957
|
+
// 站点条目带 `source: "folia"`;其余(本机工坊 + 上传)一律算本机。
|
|
17958
|
+
if (sourceFilter !== "all" && ((w.source === "folia") ? "site" : "local") !== sourceFilter) return false;
|
|
18084
17959
|
if (q && String(w.title || "").toLowerCase().indexOf(q) === -1) return false;
|
|
18085
17960
|
return true;
|
|
18086
17961
|
});
|
|
@@ -18115,7 +17990,13 @@ window.__ModuleLoader__.load({
|
|
|
18115
17990
|
// 设置页同名页签 —— 玻璃详细调节仍只在侧栏(本页,折叠块),设置页那边留玻璃预设、
|
|
18116
17991
|
// 全量配置与**字体节**(三次修订后字体节两档都画);播放页的出图来源 /
|
|
18117
17992
|
// 实时帧仍住设置页。
|
|
18118
|
-
const foot = qpTab === "
|
|
17993
|
+
const foot = qpTab === "boot"
|
|
17994
|
+
? {
|
|
17995
|
+
label: weT("打开设置 ›"),
|
|
17996
|
+
target: "",
|
|
17997
|
+
title: weT("开机动画的全部参数都在侧栏「壁纸引擎」设置里"),
|
|
17998
|
+
}
|
|
17999
|
+
: qpTab === "appearance"
|
|
18119
18000
|
? {
|
|
18120
18001
|
label: weT("更多外观设置 ›"),
|
|
18121
18002
|
target: "appearance",
|
|
@@ -18127,17 +18008,11 @@ window.__ModuleLoader__.load({
|
|
|
18127
18008
|
target: "playback",
|
|
18128
18009
|
title: weT("在设置页打开「播放」页签 —— 出图来源 / 实时帧 / 帧率上限在那里"),
|
|
18129
18010
|
}
|
|
18130
|
-
:
|
|
18131
|
-
|
|
18132
|
-
|
|
18133
|
-
|
|
18134
|
-
|
|
18135
|
-
}
|
|
18136
|
-
: {
|
|
18137
|
-
label: weT("壁纸引擎设置 ›"),
|
|
18138
|
-
target: "",
|
|
18139
|
-
title: weT("打开设置对话框的「壁纸引擎」分区(外观 / 播放 / 系统与全部配置)"),
|
|
18140
|
-
};
|
|
18011
|
+
: {
|
|
18012
|
+
label: weT("壁纸引擎设置 ›"),
|
|
18013
|
+
target: "",
|
|
18014
|
+
title: weT("打开设置对话框的「壁纸引擎」分区(外观 / 播放 / 系统与全部配置)"),
|
|
18015
|
+
};
|
|
18141
18016
|
|
|
18142
18017
|
return React.createElement("div", { className: "we-qp we-qp--" + dock },
|
|
18143
18018
|
qpRenderCurrent(sel, current, playbackLive),
|
|
@@ -18153,7 +18028,9 @@ window.__ModuleLoader__.load({
|
|
|
18153
18028
|
React.createElement("div", { className: tabBodyClass, ref: qpPaneRef },
|
|
18154
18029
|
qpTab === "wallpaper" && userPropsPanelOpen() && propsAvailable
|
|
18155
18030
|
&& React.createElement("div", { className: "we-qp__propsview we-qp__propsview--drill" }, renderUserPropsPanel()),
|
|
18156
|
-
|
|
18031
|
+
// 开机动画档:整区就是那个面板(它自己管加载/下载/开关)。
|
|
18032
|
+
qpTab === "boot" && React.createElement(BootTabPanel, null),
|
|
18033
|
+
!(qpTab === "wallpaper" && userPropsPanelOpen() && propsAvailable) && qpTab !== "boot" && (qpTab === "appearance"
|
|
18157
18034
|
? qpRenderAppearancePane(sel, {
|
|
18158
18035
|
fontOpen, onToggleFontSection: toggleFontSection,
|
|
18159
18036
|
uiColorOpen, onToggleUiColorSection: toggleUiColorSection,
|
|
@@ -18167,15 +18044,12 @@ window.__ModuleLoader__.load({
|
|
|
18167
18044
|
})
|
|
18168
18045
|
: qpTab === "playback"
|
|
18169
18046
|
? qpRenderPlaybackPane(sel, switchQpTab)
|
|
18170
|
-
|
|
18171
|
-
: qpTab === "skins"
|
|
18172
|
-
? React.createElement(SkinsTabPanel, null)
|
|
18173
|
-
: null),
|
|
18047
|
+
: null),
|
|
18174
18048
|
!(qpTab === "wallpaper" && userPropsPanelOpen() && propsAvailable) && qpTab === "wallpaper" && React.createElement(React.Fragment, null,
|
|
18175
18049
|
// ── 壁纸列表(搜索 + 视图切换 + 快切)──
|
|
18176
18050
|
// 库还没回来 / 扫描失败:**只有这一页**给一句话 —— 外观 / 播放两页不依赖库,
|
|
18177
18051
|
// 照常可用(不能整块替换掉,那样连外观都进不去)。
|
|
18178
|
-
qpRenderLibrarySection(sel, view, typeFilter, ratingFilter, listRef, rows, cardsView, vTopH, vBtmH, q, switchView),
|
|
18052
|
+
qpRenderLibrarySection(sel, view, typeFilter, ratingFilter, listRef, rows, cardsView, vTopH, vBtmH, q, switchView, sourceFilter),
|
|
18179
18053
|
// 声音(与播放页那份同源:同一份渲染器、同一状态)。包一层 .we-qp__section
|
|
18180
18054
|
// 只为保留这一节的分隔线与间距(渲染器自己那份 section 是给设置页排版的)。
|
|
18181
18055
|
React.createElement("div", { className: "we-qp__section" },
|
|
@@ -27166,9 +27040,6 @@ window.__ModuleLoader__.load({
|
|
|
27166
27040
|
const PICKER_TAB_KEY = "dsh-folia:picker-tab";
|
|
27167
27041
|
const PICKER_TABS = [
|
|
27168
27042
|
{ id: "library", get label() { return weT("壁纸库"); } },
|
|
27169
|
-
// 「皮肤包」挨着「壁纸库」:两者都是"给背景换东西",用户心智在同一处。
|
|
27170
|
-
// 它列的是**自建站**上的皮肤包(宿主端 /skins 族),不是皮肤中心的内置列表。
|
|
27171
|
-
{ id: "skins", get label() { return weT("皮肤包"); } },
|
|
27172
27043
|
{ id: "appearance", get label() { return weT("外观"); } },
|
|
27173
27044
|
{ id: "playback", get label() { return weT("播放"); } },
|
|
27174
27045
|
{ id: "system", get label() { return weT("系统"); } },
|
|
@@ -29170,6 +29041,12 @@ window.__ModuleLoader__.load({
|
|
|
29170
29041
|
setSetting("typeFilter", e.target.value);
|
|
29171
29042
|
revalidateSelection();
|
|
29172
29043
|
};
|
|
29044
|
+
// 来源筛选(本机 Wallpaper Engine 库 / 我们站点):只筛列表与轮播候选,
|
|
29045
|
+
// 与类型档同一个效力 —— 不写进播放闸门,切换不会把正在播的壁纸掐掉。
|
|
29046
|
+
const onSourceFilterChange = (e) => {
|
|
29047
|
+
setSetting("sourceFilter", e.target.value);
|
|
29048
|
+
revalidateSelection();
|
|
29049
|
+
};
|
|
29173
29050
|
// Card style: classic (CD-rack) vs fixed (overlap-proof).
|
|
29174
29051
|
const onLayoutChange = (value) => {
|
|
29175
29052
|
setSetting("pickerLayout", value);
|
|
@@ -29714,7 +29591,7 @@ window.__ModuleLoader__.load({
|
|
|
29714
29591
|
// 标题子串匹配(空查询 = 不过滤),每页 24 张,页号越界自动 clamp。这里只把状态喂
|
|
29715
29592
|
// 进去、把结果取出来 —— 组件体不再就地算这些(分级/类型/隐藏的判定也在那边)。
|
|
29716
29593
|
const {
|
|
29717
|
-
query, playableList, basePlayable, ratingCounts, typeCounts, hiddenList,
|
|
29594
|
+
query, playableList, basePlayable, ratingCounts, typeCounts, sourceCounts, hiddenList,
|
|
29718
29595
|
editorPageView,
|
|
29719
29596
|
} = pickerModel({
|
|
29720
29597
|
wallpapers: list,
|
|
@@ -29722,6 +29599,7 @@ window.__ModuleLoader__.load({
|
|
|
29722
29599
|
search: sel.search,
|
|
29723
29600
|
ratingFilter: sel.contentRatingFilter,
|
|
29724
29601
|
typeFilter: sel.typeFilter,
|
|
29602
|
+
sourceFilter: sel.sourceFilter,
|
|
29725
29603
|
editorPage: sel.editorPage,
|
|
29726
29604
|
});
|
|
29727
29605
|
// CD-rack mode: compact one-page grid (no pagination, no virtualization —
|
|
@@ -29785,9 +29663,6 @@ window.__ModuleLoader__.load({
|
|
|
29785
29663
|
onReopenNotice: () => { noticeReopenRequest = true; emit(); },
|
|
29786
29664
|
});
|
|
29787
29665
|
if (activeTab === "extensions") return renderExtensionsTab(extensionCtx());
|
|
29788
|
-
// 「皮肤包」自带异步状态(列表 / 下载中 / 错误),所以它是一个**真组件**而不是
|
|
29789
|
-
// 纯渲染函数 —— 薄包装只负责挂上去(理由见 src/panel-tabs-skins.js 文件头)。
|
|
29790
|
-
if (activeTab === "skins") return renderSkinsTab();
|
|
29791
29666
|
if (activeTab === "appearance") return renderAppearanceTab({
|
|
29792
29667
|
setSetting, setTransient,
|
|
29793
29668
|
fontSet: fontSetCtx(),
|
|
@@ -29884,10 +29759,10 @@ window.__ModuleLoader__.load({
|
|
|
29884
29759
|
// 壁纸网格,ESC / 顶部「返回」退出,切页签也会退出(见 switchTab)。
|
|
29885
29760
|
sel.pickerOpen ? renderPickerModal({
|
|
29886
29761
|
sel, closePicker, current, playbackLive, playableList, hiddenList,
|
|
29887
|
-
cdMode, query, basePlayable, ratingCounts, typeCounts,
|
|
29762
|
+
cdMode, query, basePlayable, ratingCounts, typeCounts, sourceCounts,
|
|
29888
29763
|
gridRef: pickerGridRef, vwin: pickerVwin,
|
|
29889
29764
|
armedConfirm: sel.armedConfirm, onArmConfirm: armConfirm, onDisarmConfirm: disarmConfirm,
|
|
29890
|
-
onClear, onRatingFilterChange, onTypeFilterChange,
|
|
29765
|
+
onClear, onRatingFilterChange, onTypeFilterChange, onSourceFilterChange,
|
|
29891
29766
|
onShowNormalView, onShowHiddenView,
|
|
29892
29767
|
onToggleBatchMode, onArmBatchHide, onBatchHide, onBatchCancel, onSearchInput, onPickCard,
|
|
29893
29768
|
}) : React.createElement(React.Fragment, null,
|