dsh-folia 2.5.1 → 2.5.7
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 +302 -437
- package/lib/index.js +11 -9
- package/lib/routes/boot.js +325 -0
- package/lib/routes/inventory.js +204 -49
- package/lib/routes/modules.js +186 -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,205 @@ 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
|
+
|
|
16995
|
+
const load = React.useCallback(async (force) => {
|
|
16996
|
+
try {
|
|
16997
|
+
const resp = await apiJson("/boot/list" + (force ? "?refresh=1" : ""), { parse: "always" });
|
|
16998
|
+
const r = (resp && resp.data) || null;
|
|
16999
|
+
setState(r || { ok: false, clips: [], error: "network" });
|
|
17000
|
+
} catch (e) {
|
|
17001
|
+
setState({ ok: false, clips: [], error: "network" });
|
|
17002
|
+
}
|
|
17003
|
+
}, []);
|
|
17004
|
+
|
|
17005
|
+
React.useEffect(() => { load(false); }, [load]);
|
|
17006
|
+
|
|
17007
|
+
async function act(kind, payload, label) {
|
|
17008
|
+
if (busy) return;
|
|
17009
|
+
setBusy(payload && payload.id ? payload.id : kind);
|
|
17010
|
+
setNote(null);
|
|
17011
|
+
try {
|
|
17012
|
+
const resp = await apiPostJson("/boot/" + kind, payload || {}, { parse: "always" });
|
|
17013
|
+
const r = (resp && resp.data) || null;
|
|
17014
|
+
if (resp && resp.ok && r && r.ok) {
|
|
17015
|
+
if (kind === "download") setNote({ ok: true, text: weT("已下载:") + label });
|
|
17016
|
+
if (kind === "select") setNote({ ok: true, text: weT("已选中「") + label + weT("」,下次启动生效") });
|
|
17017
|
+
if (kind === "remove") setNote({ ok: true, text: weT("已删除:") + label });
|
|
17018
|
+
if (kind === "toggle") setNote({ ok: true, text: payload.enabled ? weT("开机动画已开启") : weT("开机动画已关闭") });
|
|
17019
|
+
if (kind === "settings") setNote({ ok: true, text: weT("已保存") });
|
|
17020
|
+
await load(true);
|
|
17021
|
+
} else {
|
|
17022
|
+
const code = (r && r.error) || "failed";
|
|
17023
|
+
setNote({ ok: false, text: weT("操作失败:") + code + (r && r.detail ? "(" + r.detail + ")" : "") });
|
|
17024
|
+
}
|
|
17025
|
+
} catch (e) {
|
|
17026
|
+
setNote({ ok: false, text: weT("请求失败:") + (e && e.message ? e.message : weT("网络错误")) });
|
|
17027
|
+
} finally {
|
|
17028
|
+
setBusy("");
|
|
17029
|
+
}
|
|
17030
|
+
}
|
|
17031
|
+
|
|
17032
|
+
const clips = (state && state.clips) || [];
|
|
17033
|
+
const enabled = Boolean(state && state.enabled);
|
|
17034
|
+
const activeId = (state && state.clipId) || "";
|
|
17035
|
+
const installedCount = clips.filter((c) => c.installed).length;
|
|
17036
|
+
|
|
17037
|
+
const children = [];
|
|
17038
|
+
|
|
17039
|
+
children.push(React.createElement("div", { className: "we-picker__section-head", key: "head" },
|
|
17040
|
+
React.createElement("span", { className: "we-picker__section-label" }, weT("🎬 开机动画") + (clips.length ? " · " + clips.length : "")),
|
|
17041
|
+
React.createElement("button", {
|
|
17042
|
+
type: "button", className: "we-picker__btn", style: { marginLeft: "auto" },
|
|
17043
|
+
disabled: Boolean(busy), onClick: () => load(true),
|
|
17044
|
+
}, weT("刷新")),
|
|
17045
|
+
));
|
|
17046
|
+
|
|
17047
|
+
// 开关 + 当前选择
|
|
17048
|
+
children.push(React.createElement("div", {
|
|
17049
|
+
key: "switch", style: { ...BOOT_STYLE.row, margin: "6px 0 2px", flexWrap: "wrap" },
|
|
17050
|
+
},
|
|
17051
|
+
React.createElement("label", { style: { display: "flex", gap: "6px", alignItems: "center", fontSize: "12px" } },
|
|
17052
|
+
React.createElement("input", {
|
|
17053
|
+
type: "checkbox", checked: enabled, disabled: Boolean(busy) || !activeId,
|
|
17054
|
+
onChange: (e) => act("toggle", { enabled: e.target.checked }, ""),
|
|
17055
|
+
}),
|
|
17056
|
+
weT("启用(下次启动播放)"),
|
|
17057
|
+
),
|
|
17058
|
+
React.createElement("span", { className: "we-picker__hint" },
|
|
17059
|
+
activeId ? weT("当前:") + ((clips.find((c) => c.id === activeId) || {}).title || activeId) : weT("未选择")),
|
|
17060
|
+
));
|
|
17061
|
+
|
|
17062
|
+
children.push(React.createElement("div", { key: "hint", className: "we-picker__hint", style: { margin: "2px 0 6px", opacity: 0.75 } },
|
|
17063
|
+
weT("下载一条并「使用」后,每次启动都会先播它;默认播完才淡出进入界面(想更快就切「界面就绪就进」)。点击或按 Esc 可立即跳过。")));
|
|
17064
|
+
|
|
17065
|
+
if (state && state.error && !clips.length) {
|
|
17066
|
+
children.push(React.createElement("div", { key: "err", className: "we-picker__hint", style: { color: "#ff9a9a" } },
|
|
17067
|
+
weT("候选清单读不到:") + state.error));
|
|
17068
|
+
}
|
|
17069
|
+
|
|
17070
|
+
if (note) {
|
|
17071
|
+
children.push(React.createElement("div", { key: "note", className: "we-picker__hint", style: { color: note.ok ? "#8ee6a8" : "#ff9a9a", margin: "4px 0 8px" } }, note.text));
|
|
17072
|
+
}
|
|
17073
|
+
|
|
17074
|
+
// 淡出 / 停留
|
|
17075
|
+
if (state) {
|
|
17076
|
+
const opt = (label, key, list) => React.createElement("label", { style: { display: "flex", gap: "4px", alignItems: "center", fontSize: "12px" } },
|
|
17077
|
+
label,
|
|
17078
|
+
React.createElement("select", {
|
|
17079
|
+
className: "we-picker__playlist-select",
|
|
17080
|
+
value: String(state[key]),
|
|
17081
|
+
disabled: Boolean(busy),
|
|
17082
|
+
onChange: (e) => act("settings", { [key]: Number(e.target.value) }, ""),
|
|
17083
|
+
}, list.map(([v, t]) => React.createElement("option", { key: v, value: String(v) }, t))),
|
|
17084
|
+
);
|
|
17085
|
+
children.push(React.createElement("div", { key: "opts", style: { ...BOOT_STYLE.row, gap: "14px", flexWrap: "wrap", margin: "2px 0 8px" } },
|
|
17086
|
+
// 进入方式:默认「播完再进」——之前只有「就绪即进」,15 秒的片子 1.2 秒就被掐掉。
|
|
17087
|
+
React.createElement("label", { style: { display: "flex", gap: "4px", alignItems: "center", fontSize: "12px" } },
|
|
17088
|
+
weT("进入方式"),
|
|
17089
|
+
React.createElement("select", {
|
|
17090
|
+
className: "we-picker__playlist-select",
|
|
17091
|
+
value: String(state.enterMode || "tail"),
|
|
17092
|
+
disabled: Boolean(busy),
|
|
17093
|
+
onChange: (e) => act("settings", { enterMode: e.target.value }, ""),
|
|
17094
|
+
},
|
|
17095
|
+
React.createElement("option", { value: "tail" }, weT("播完再进(默认)")),
|
|
17096
|
+
React.createElement("option", { value: "ready" }, weT("界面就绪就进")),
|
|
17097
|
+
React.createElement("option", { value: "click" }, weT("点一下才进")),
|
|
17098
|
+
),
|
|
17099
|
+
),
|
|
17100
|
+
opt(weT("淡出时长"), "fadeMs", [[400, "0.4 秒"], [600, "0.6 秒"], [900, "0.9 秒"], [1200, "1.2 秒"], [1600, "1.6 秒"]]),
|
|
17101
|
+
opt(weT("最短显示"), "minShowMs", [[0, "不限制"], [600, "0.6 秒"], [1200, "1.2 秒"], [2000, "2 秒"]]),
|
|
17102
|
+
opt(weT("最长停留"), "maxHoldMs", [[10000, "10 秒"], [20000, "20 秒(默认)"], [30000, "30 秒"], [60000, "60 秒"]]),
|
|
17103
|
+
));
|
|
17104
|
+
}
|
|
17105
|
+
|
|
17106
|
+
children.push(React.createElement("div", { key: "count", className: "we-picker__hint", style: { margin: "2px 0 4px" } },
|
|
17107
|
+
weT("已下载 ") + installedCount + " / " + clips.length));
|
|
17108
|
+
|
|
17109
|
+
if (clips.length) {
|
|
17110
|
+
children.push(React.createElement("div", { key: "grid", style: BOOT_STYLE.grid },
|
|
17111
|
+
clips.map((c) => {
|
|
17112
|
+
const isBusy = busy === c.id;
|
|
17113
|
+
const isActive = c.id === activeId;
|
|
17114
|
+
const btn = (label, onClick, disabled, primary) => React.createElement("button", {
|
|
17115
|
+
type: "button",
|
|
17116
|
+
className: "we-picker__btn" + (primary ? " we-picker__btn--primary" : ""),
|
|
17117
|
+
style: { flex: "1 1 0", fontSize: "11.5px", padding: "4px 6px" },
|
|
17118
|
+
disabled: Boolean(disabled) || Boolean(busy),
|
|
17119
|
+
onClick,
|
|
17120
|
+
}, label);
|
|
17121
|
+
const actions = [];
|
|
17122
|
+
if (!c.installed) {
|
|
17123
|
+
actions.push(btn(isBusy ? weT("下载中…") : weT("下载"), () => act("download", { id: c.id }, c.title), false, true));
|
|
17124
|
+
} else if (!isActive) {
|
|
17125
|
+
actions.push(btn(weT("使用"), () => act("select", { id: c.id }, c.title), false, true));
|
|
17126
|
+
actions.push(btn(weT("删除"), () => act("remove", { id: c.id }, c.title), isBusy, false));
|
|
17127
|
+
} else {
|
|
17128
|
+
actions.push(btn(weT("使用中"), () => {}, true, false));
|
|
17129
|
+
}
|
|
17130
|
+
return React.createElement("div", { key: c.id, style: BOOT_STYLE.card },
|
|
17131
|
+
React.createElement("div", { style: BOOT_STYLE.title }, c.title || c.id),
|
|
17132
|
+
React.createElement("div", { style: BOOT_STYLE.meta },
|
|
17133
|
+
[c.durationSec ? Number(c.durationSec).toFixed(1) + "s" : null,
|
|
17134
|
+
bootFmtBytes(c.localBytes || c.bytes)].filter(Boolean).join(" · ")),
|
|
17135
|
+
c.faststart === false
|
|
17136
|
+
? React.createElement("div", { style: { ...BOOT_STYLE.meta, color: "#ffd08a" } }, weT("未做 faststart,播放可能更慢"))
|
|
17137
|
+
: null,
|
|
17138
|
+
React.createElement("div", { style: { ...BOOT_STYLE.row, marginTop: "4px" } }, actions),
|
|
17139
|
+
);
|
|
17140
|
+
})));
|
|
17141
|
+
} else if (state) {
|
|
17142
|
+
children.push(React.createElement("div", { key: "empty", className: "we-picker__empty" },
|
|
17143
|
+
React.createElement("span", { className: "we-picker__empty-title" }, weT("没有可用的开机动画")),
|
|
17144
|
+
React.createElement("span", { className: "we-picker__hint" }, weT("素材来自站点;站点不可达时这里为空,但不影响启动"))));
|
|
17145
|
+
}
|
|
17146
|
+
|
|
17147
|
+
return React.createElement(React.Fragment, null, children);
|
|
17148
|
+
}
|
|
17149
|
+
|
|
17150
|
+
function renderBootTab() {
|
|
17151
|
+
return React.createElement(BootTabPanel, null);
|
|
17152
|
+
}
|
|
17153
|
+
|
|
17311
17154
|
// ── 内联模块:src/quick-panel.js —— 快捷播放面板(官方侧栏 tab 与低版本抽屉共用同一份内容):当前壁纸 + 轮播 + 列表快切 + 声音 + 设置入口。列表虚拟滚动(spacer + 可视窗口)(构建期注入;勿手改本段)──
|
|
17312
17155
|
/**
|
|
17313
17156
|
* quick-panel.js — 快捷播放面板(侧边栏的**唯一**内容):当前壁纸 + 轮播 + 三档页签
|
|
@@ -17478,10 +17321,9 @@ window.__ModuleLoader__.load({
|
|
|
17478
17321
|
// 对照很常见)。与设置页的页签表(PICKER_TABS)是两件事 —— 这里只镜像用户点名要的
|
|
17479
17322
|
// 两页,外加壁纸页。
|
|
17480
17323
|
const QP_TABS = [
|
|
17324
|
+
// 开机动画放在「壁纸」左边:它和壁纸是并列的两种"开机时看到什么",不是壁纸的子功能。
|
|
17325
|
+
{ id: "boot", get label() { return weT("开机动画"); } },
|
|
17481
17326
|
{ id: "wallpaper", get label() { return weT("壁纸"); } },
|
|
17482
|
-
// 「皮肤包」与设置页那个页签是**同一个组件**(SkinsTabPanel):首页这里只是把它挂进抽屉,
|
|
17483
|
-
// 不另写一份列表逻辑 —— 两份实现迟早会漂移。
|
|
17484
|
-
{ id: "skins", get label() { return weT("皮肤包"); } },
|
|
17485
17327
|
{ id: "appearance", get label() { return weT("外观"); } },
|
|
17486
17328
|
{ id: "playback", get label() { return weT("播放"); } },
|
|
17487
17329
|
];
|
|
@@ -17846,7 +17688,17 @@ window.__ModuleLoader__.load({
|
|
|
17846
17688
|
);
|
|
17847
17689
|
}
|
|
17848
17690
|
|
|
17849
|
-
|
|
17691
|
+
// 来源档(本机 Wallpaper Engine 库 / 我们站点):与设置页同一个键 `sourceFilter`,
|
|
17692
|
+
// 同样"两处同步"——这里切,设置页跟着变,反之亦然。只筛列表,不拦正在播的壁纸。
|
|
17693
|
+
function qpSources() {
|
|
17694
|
+
return [
|
|
17695
|
+
{ id: "all", label: weT("全部来源") },
|
|
17696
|
+
{ id: "local", label: weT("本机") },
|
|
17697
|
+
{ id: "site", label: weT("站点") },
|
|
17698
|
+
];
|
|
17699
|
+
}
|
|
17700
|
+
|
|
17701
|
+
function qpRenderViewBar(sel, view, typeFilter, ratingFilter, switchView, sourceFilter) {
|
|
17850
17702
|
return React.createElement("div", { className: "we-qp__viewbar" },
|
|
17851
17703
|
React.createElement("input", {
|
|
17852
17704
|
className: "we-picker__text we-qp__search", type: "text",
|
|
@@ -17880,6 +17732,16 @@ window.__ModuleLoader__.load({
|
|
|
17880
17732
|
},
|
|
17881
17733
|
...qpTypes().map((t) => React.createElement("option", { key: t.id, value: t.id }, t.label)),
|
|
17882
17734
|
),
|
|
17735
|
+
// 来源筛选:本机 Wallpaper Engine 库 vs 我们站点(与设置页同一个键)。
|
|
17736
|
+
React.createElement("select", {
|
|
17737
|
+
className: "we-picker__select we-qp__source",
|
|
17738
|
+
value: sourceFilter,
|
|
17739
|
+
onChange: (e) => { setSetting("sourceFilter", e.target.value); emit(); },
|
|
17740
|
+
"aria-label": weT("来源筛选"),
|
|
17741
|
+
title: weT("按来源筛选:本机 Wallpaper Engine 库,还是从我们站点拉下来的壁纸"),
|
|
17742
|
+
},
|
|
17743
|
+
...qpSources().map((s) => React.createElement("option", { key: s.id, value: s.id }, s.label)),
|
|
17744
|
+
),
|
|
17883
17745
|
// 列表 / 卡片:标签式切换(滑动胶囊做激活指示、无分段底 —— 样式见
|
|
17884
17746
|
// .we-qp__viewtabs),偏好记 localStorage(QP_VIEW_KEY),两个壳共用一份。
|
|
17885
17747
|
// 语义仍是开关(role=group + aria-pressed),只是外观走标签形态。
|
|
@@ -17945,7 +17807,7 @@ window.__ModuleLoader__.load({
|
|
|
17945
17807
|
);
|
|
17946
17808
|
}
|
|
17947
17809
|
|
|
17948
|
-
function qpRenderLibrarySection(sel, view, typeFilter, ratingFilter, listRef, rows, cardsView, vTopH, vBtmH, q, switchView) {
|
|
17810
|
+
function qpRenderLibrarySection(sel, view, typeFilter, ratingFilter, listRef, rows, cardsView, vTopH, vBtmH, q, switchView, sourceFilter) {
|
|
17949
17811
|
return React.createElement("div", { className: "we-qp__section we-qp__library" },
|
|
17950
17812
|
!sel.loaded
|
|
17951
17813
|
? React.createElement("span", { className: "we-picker__hint" }, weT("扫描 Wallpaper Engine…"))
|
|
@@ -17959,7 +17821,7 @@ window.__ModuleLoader__.load({
|
|
|
17959
17821
|
}, weT(sel.loading ? "刷新中…" : "重试")),
|
|
17960
17822
|
)
|
|
17961
17823
|
: React.createElement(React.Fragment, null,
|
|
17962
|
-
qpRenderViewBar(sel, view, typeFilter, ratingFilter, switchView),
|
|
17824
|
+
qpRenderViewBar(sel, view, typeFilter, ratingFilter, switchView, sourceFilter),
|
|
17963
17825
|
qpRenderList(listRef, rows, view, cardsView, vTopH, vBtmH, q, ratingFilter, typeFilter, sel),
|
|
17964
17826
|
),
|
|
17965
17827
|
);
|
|
@@ -18079,8 +17941,11 @@ window.__ModuleLoader__.load({
|
|
|
18079
17941
|
const ratingFilter = qpRatings().some((r) => r.id === sel.contentRatingFilter)
|
|
18080
17942
|
? sel.contentRatingFilter : "all";
|
|
18081
17943
|
const playable = playableInventory();
|
|
17944
|
+
const sourceFilter = qpSources().some((s) => s.id === sel.sourceFilter) ? sel.sourceFilter : "all";
|
|
18082
17945
|
const filtered = playable.filter((w) => {
|
|
18083
17946
|
if (typeFilter !== "all" && w.type !== typeFilter) return false;
|
|
17947
|
+
// 站点条目带 `source: "folia"`;其余(本机工坊 + 上传)一律算本机。
|
|
17948
|
+
if (sourceFilter !== "all" && ((w.source === "folia") ? "site" : "local") !== sourceFilter) return false;
|
|
18084
17949
|
if (q && String(w.title || "").toLowerCase().indexOf(q) === -1) return false;
|
|
18085
17950
|
return true;
|
|
18086
17951
|
});
|
|
@@ -18115,7 +17980,13 @@ window.__ModuleLoader__.load({
|
|
|
18115
17980
|
// 设置页同名页签 —— 玻璃详细调节仍只在侧栏(本页,折叠块),设置页那边留玻璃预设、
|
|
18116
17981
|
// 全量配置与**字体节**(三次修订后字体节两档都画);播放页的出图来源 /
|
|
18117
17982
|
// 实时帧仍住设置页。
|
|
18118
|
-
const foot = qpTab === "
|
|
17983
|
+
const foot = qpTab === "boot"
|
|
17984
|
+
? {
|
|
17985
|
+
label: weT("打开设置 ›"),
|
|
17986
|
+
target: "",
|
|
17987
|
+
title: weT("开机动画的全部参数都在侧栏「壁纸引擎」设置里"),
|
|
17988
|
+
}
|
|
17989
|
+
: qpTab === "appearance"
|
|
18119
17990
|
? {
|
|
18120
17991
|
label: weT("更多外观设置 ›"),
|
|
18121
17992
|
target: "appearance",
|
|
@@ -18127,17 +17998,11 @@ window.__ModuleLoader__.load({
|
|
|
18127
17998
|
target: "playback",
|
|
18128
17999
|
title: weT("在设置页打开「播放」页签 —— 出图来源 / 实时帧 / 帧率上限在那里"),
|
|
18129
18000
|
}
|
|
18130
|
-
:
|
|
18131
|
-
|
|
18132
|
-
|
|
18133
|
-
|
|
18134
|
-
|
|
18135
|
-
}
|
|
18136
|
-
: {
|
|
18137
|
-
label: weT("壁纸引擎设置 ›"),
|
|
18138
|
-
target: "",
|
|
18139
|
-
title: weT("打开设置对话框的「壁纸引擎」分区(外观 / 播放 / 系统与全部配置)"),
|
|
18140
|
-
};
|
|
18001
|
+
: {
|
|
18002
|
+
label: weT("壁纸引擎设置 ›"),
|
|
18003
|
+
target: "",
|
|
18004
|
+
title: weT("打开设置对话框的「壁纸引擎」分区(外观 / 播放 / 系统与全部配置)"),
|
|
18005
|
+
};
|
|
18141
18006
|
|
|
18142
18007
|
return React.createElement("div", { className: "we-qp we-qp--" + dock },
|
|
18143
18008
|
qpRenderCurrent(sel, current, playbackLive),
|
|
@@ -18153,7 +18018,9 @@ window.__ModuleLoader__.load({
|
|
|
18153
18018
|
React.createElement("div", { className: tabBodyClass, ref: qpPaneRef },
|
|
18154
18019
|
qpTab === "wallpaper" && userPropsPanelOpen() && propsAvailable
|
|
18155
18020
|
&& React.createElement("div", { className: "we-qp__propsview we-qp__propsview--drill" }, renderUserPropsPanel()),
|
|
18156
|
-
|
|
18021
|
+
// 开机动画档:整区就是那个面板(它自己管加载/下载/开关)。
|
|
18022
|
+
qpTab === "boot" && React.createElement(BootTabPanel, null),
|
|
18023
|
+
!(qpTab === "wallpaper" && userPropsPanelOpen() && propsAvailable) && qpTab !== "boot" && (qpTab === "appearance"
|
|
18157
18024
|
? qpRenderAppearancePane(sel, {
|
|
18158
18025
|
fontOpen, onToggleFontSection: toggleFontSection,
|
|
18159
18026
|
uiColorOpen, onToggleUiColorSection: toggleUiColorSection,
|
|
@@ -18167,15 +18034,12 @@ window.__ModuleLoader__.load({
|
|
|
18167
18034
|
})
|
|
18168
18035
|
: qpTab === "playback"
|
|
18169
18036
|
? qpRenderPlaybackPane(sel, switchQpTab)
|
|
18170
|
-
|
|
18171
|
-
: qpTab === "skins"
|
|
18172
|
-
? React.createElement(SkinsTabPanel, null)
|
|
18173
|
-
: null),
|
|
18037
|
+
: null),
|
|
18174
18038
|
!(qpTab === "wallpaper" && userPropsPanelOpen() && propsAvailable) && qpTab === "wallpaper" && React.createElement(React.Fragment, null,
|
|
18175
18039
|
// ── 壁纸列表(搜索 + 视图切换 + 快切)──
|
|
18176
18040
|
// 库还没回来 / 扫描失败:**只有这一页**给一句话 —— 外观 / 播放两页不依赖库,
|
|
18177
18041
|
// 照常可用(不能整块替换掉,那样连外观都进不去)。
|
|
18178
|
-
qpRenderLibrarySection(sel, view, typeFilter, ratingFilter, listRef, rows, cardsView, vTopH, vBtmH, q, switchView),
|
|
18042
|
+
qpRenderLibrarySection(sel, view, typeFilter, ratingFilter, listRef, rows, cardsView, vTopH, vBtmH, q, switchView, sourceFilter),
|
|
18179
18043
|
// 声音(与播放页那份同源:同一份渲染器、同一状态)。包一层 .we-qp__section
|
|
18180
18044
|
// 只为保留这一节的分隔线与间距(渲染器自己那份 section 是给设置页排版的)。
|
|
18181
18045
|
React.createElement("div", { className: "we-qp__section" },
|
|
@@ -27166,9 +27030,6 @@ window.__ModuleLoader__.load({
|
|
|
27166
27030
|
const PICKER_TAB_KEY = "dsh-folia:picker-tab";
|
|
27167
27031
|
const PICKER_TABS = [
|
|
27168
27032
|
{ id: "library", get label() { return weT("壁纸库"); } },
|
|
27169
|
-
// 「皮肤包」挨着「壁纸库」:两者都是"给背景换东西",用户心智在同一处。
|
|
27170
|
-
// 它列的是**自建站**上的皮肤包(宿主端 /skins 族),不是皮肤中心的内置列表。
|
|
27171
|
-
{ id: "skins", get label() { return weT("皮肤包"); } },
|
|
27172
27033
|
{ id: "appearance", get label() { return weT("外观"); } },
|
|
27173
27034
|
{ id: "playback", get label() { return weT("播放"); } },
|
|
27174
27035
|
{ id: "system", get label() { return weT("系统"); } },
|
|
@@ -29170,6 +29031,12 @@ window.__ModuleLoader__.load({
|
|
|
29170
29031
|
setSetting("typeFilter", e.target.value);
|
|
29171
29032
|
revalidateSelection();
|
|
29172
29033
|
};
|
|
29034
|
+
// 来源筛选(本机 Wallpaper Engine 库 / 我们站点):只筛列表与轮播候选,
|
|
29035
|
+
// 与类型档同一个效力 —— 不写进播放闸门,切换不会把正在播的壁纸掐掉。
|
|
29036
|
+
const onSourceFilterChange = (e) => {
|
|
29037
|
+
setSetting("sourceFilter", e.target.value);
|
|
29038
|
+
revalidateSelection();
|
|
29039
|
+
};
|
|
29173
29040
|
// Card style: classic (CD-rack) vs fixed (overlap-proof).
|
|
29174
29041
|
const onLayoutChange = (value) => {
|
|
29175
29042
|
setSetting("pickerLayout", value);
|
|
@@ -29714,7 +29581,7 @@ window.__ModuleLoader__.load({
|
|
|
29714
29581
|
// 标题子串匹配(空查询 = 不过滤),每页 24 张,页号越界自动 clamp。这里只把状态喂
|
|
29715
29582
|
// 进去、把结果取出来 —— 组件体不再就地算这些(分级/类型/隐藏的判定也在那边)。
|
|
29716
29583
|
const {
|
|
29717
|
-
query, playableList, basePlayable, ratingCounts, typeCounts, hiddenList,
|
|
29584
|
+
query, playableList, basePlayable, ratingCounts, typeCounts, sourceCounts, hiddenList,
|
|
29718
29585
|
editorPageView,
|
|
29719
29586
|
} = pickerModel({
|
|
29720
29587
|
wallpapers: list,
|
|
@@ -29722,6 +29589,7 @@ window.__ModuleLoader__.load({
|
|
|
29722
29589
|
search: sel.search,
|
|
29723
29590
|
ratingFilter: sel.contentRatingFilter,
|
|
29724
29591
|
typeFilter: sel.typeFilter,
|
|
29592
|
+
sourceFilter: sel.sourceFilter,
|
|
29725
29593
|
editorPage: sel.editorPage,
|
|
29726
29594
|
});
|
|
29727
29595
|
// CD-rack mode: compact one-page grid (no pagination, no virtualization —
|
|
@@ -29785,9 +29653,6 @@ window.__ModuleLoader__.load({
|
|
|
29785
29653
|
onReopenNotice: () => { noticeReopenRequest = true; emit(); },
|
|
29786
29654
|
});
|
|
29787
29655
|
if (activeTab === "extensions") return renderExtensionsTab(extensionCtx());
|
|
29788
|
-
// 「皮肤包」自带异步状态(列表 / 下载中 / 错误),所以它是一个**真组件**而不是
|
|
29789
|
-
// 纯渲染函数 —— 薄包装只负责挂上去(理由见 src/panel-tabs-skins.js 文件头)。
|
|
29790
|
-
if (activeTab === "skins") return renderSkinsTab();
|
|
29791
29656
|
if (activeTab === "appearance") return renderAppearanceTab({
|
|
29792
29657
|
setSetting, setTransient,
|
|
29793
29658
|
fontSet: fontSetCtx(),
|
|
@@ -29884,10 +29749,10 @@ window.__ModuleLoader__.load({
|
|
|
29884
29749
|
// 壁纸网格,ESC / 顶部「返回」退出,切页签也会退出(见 switchTab)。
|
|
29885
29750
|
sel.pickerOpen ? renderPickerModal({
|
|
29886
29751
|
sel, closePicker, current, playbackLive, playableList, hiddenList,
|
|
29887
|
-
cdMode, query, basePlayable, ratingCounts, typeCounts,
|
|
29752
|
+
cdMode, query, basePlayable, ratingCounts, typeCounts, sourceCounts,
|
|
29888
29753
|
gridRef: pickerGridRef, vwin: pickerVwin,
|
|
29889
29754
|
armedConfirm: sel.armedConfirm, onArmConfirm: armConfirm, onDisarmConfirm: disarmConfirm,
|
|
29890
|
-
onClear, onRatingFilterChange, onTypeFilterChange,
|
|
29755
|
+
onClear, onRatingFilterChange, onTypeFilterChange, onSourceFilterChange,
|
|
29891
29756
|
onShowNormalView, onShowHiddenView,
|
|
29892
29757
|
onToggleBatchMode, onArmBatchHide, onBatchHide, onBatchCancel, onSearchInput, onPickCard,
|
|
29893
29758
|
}) : React.createElement(React.Fragment, null,
|