dsh-folia 4.16.2 → 4.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +102 -6
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -18314,6 +18314,13 @@ window.__ModuleLoader__.load({
|
|
|
18314
18314
|
const busyRef = React.useRef("");
|
|
18315
18315
|
const [note, setNote] = React.useState(null);
|
|
18316
18316
|
|
|
18317
|
+
// ⚠️ 这两面旗必须**在函数体最前面**算出来:下面「刷新」按钮的文案就要用它。
|
|
18318
|
+
// 曾把它们写在列表渲染处(更靠后)⇒ 首次渲染读到 TDZ 里的 const
|
|
18319
|
+
// ⇒ `Cannot access 'busyIsStructural' before initialization` ⇒ 整页被守卫接住变红。
|
|
18320
|
+
// 教训:渲染函数里任何"靠后声明的 const"都可能被靠前的 JSX 读到,声明一律前置。
|
|
18321
|
+
const busyIsStructural = /^(install|remove|repair):/.test(String(busy || ""));
|
|
18322
|
+
const busyIsActivating = /^activate:/.test(String(busy || ""));
|
|
18323
|
+
|
|
18317
18324
|
const load = React.useCallback(async (force) => {
|
|
18318
18325
|
try {
|
|
18319
18326
|
const resp = await apiJson("/skins/list" + (force ? "?refresh=1" : ""), { parse: "always" });
|
|
@@ -18573,8 +18580,7 @@ window.__ModuleLoader__.load({
|
|
|
18573
18580
|
// 所以把 busy 拆成两条互不相干的通道:
|
|
18574
18581
|
// · 结构操作(install / remove / repair):改动磁盘目录,串行,期间只锁**结构按钮**
|
|
18575
18582
|
// · 切换(activate):只写 active,幂等且"最后动作优先",期间只锁**该卡片的「应用」**
|
|
18576
|
-
|
|
18577
|
-
const busyIsActivating = /^activate:/.test(String(busy || ""));
|
|
18583
|
+
// (这两面旗已上移到函数开头声明 —— 上面的「刷新」按钮文案要用到它们,写在这里会触发 TDZ。)
|
|
18578
18584
|
|
|
18579
18585
|
const installed = (state && state.installed) || [];
|
|
18580
18586
|
const activeId = (state && state.active) || "";
|
|
@@ -18955,10 +18961,40 @@ window.__ModuleLoader__.load({
|
|
|
18955
18961
|
* 整区面板:这个页签的内容**整个**由功能自己的组件渲染(而不是 quick-panel 内的通用列表)。
|
|
18956
18962
|
* 没登记的页签走 quick-panel 自己的通用布局(壁纸列表 / 外观节 / 播放节)。
|
|
18957
18963
|
*/
|
|
18964
|
+
/**
|
|
18965
|
+
* 整区面板的**守卫包装**:任何一个页签在渲染期抛错,只让这一页显示错误信息,
|
|
18966
|
+
* 不许把整个面板带走。
|
|
18967
|
+
*
|
|
18968
|
+
* 实测故障:站点数据里出现一条字段形状异常的条目,渲染期抛错 ⇒ React 丢弃整棵树 ⇒
|
|
18969
|
+
* 整个「壁纸」窗口一片空白,用户只看到"界面消失了",换版本也一样(故障在数据)。
|
|
18970
|
+
* 单卡错离在 quick-panel 里做;这里补"整页错离",两层缺一就会重演那种空白。
|
|
18971
|
+
*/
|
|
18972
|
+
function guardTabPanel(name, Panel) {
|
|
18973
|
+
if (typeof Panel !== "function") return Panel;
|
|
18974
|
+
function GuardedTabPanel(props) {
|
|
18975
|
+
try {
|
|
18976
|
+
return Panel(props);
|
|
18977
|
+
} catch (e) {
|
|
18978
|
+
const why = (e && (e.message || String(e))) || "unknown";
|
|
18979
|
+
try { console.warn("[folia] 页签渲染失败:" + name, why); } catch { /* ignore */ }
|
|
18980
|
+
return React.createElement("div", {
|
|
18981
|
+
className: "we-picker",
|
|
18982
|
+
style: { padding: "12px", fontSize: "13px", lineHeight: 1.6, color: "#ff9a9a" },
|
|
18983
|
+
},
|
|
18984
|
+
React.createElement("div", null, weT("这一页加载失败:{name}", { name })),
|
|
18985
|
+
React.createElement("div", { style: { opacity: 0.8, marginTop: "4px", fontSize: "12px" } }, String(why).slice(0, 240)),
|
|
18986
|
+
React.createElement("div", { style: { opacity: 0.6, marginTop: "6px", fontSize: "12px" } },
|
|
18987
|
+
weT("其它页签仍可用;把这行原因发给作者即可定位。")));
|
|
18988
|
+
}
|
|
18989
|
+
}
|
|
18990
|
+
GuardedTabPanel.displayName = "Guarded(" + name + ")";
|
|
18991
|
+
return GuardedTabPanel;
|
|
18992
|
+
}
|
|
18993
|
+
|
|
18958
18994
|
const WE_FEATURE_PANELS = {
|
|
18959
|
-
boot: BootTabPanel,
|
|
18960
|
-
pets: PetsTabPanel,
|
|
18961
|
-
skins: SkinsTabPanel,
|
|
18995
|
+
boot: guardTabPanel("boot", BootTabPanel),
|
|
18996
|
+
pets: guardTabPanel("pets", PetsTabPanel),
|
|
18997
|
+
skins: guardTabPanel("skins", SkinsTabPanel),
|
|
18962
18998
|
};
|
|
18963
18999
|
|
|
18964
19000
|
/**
|
|
@@ -19316,6 +19352,63 @@ window.__ModuleLoader__.load({
|
|
|
19316
19352
|
return shortSide > 0 && shortSide < 1280;
|
|
19317
19353
|
}
|
|
19318
19354
|
|
|
19355
|
+
/**
|
|
19356
|
+
* 把一条清单数据**归一**成渲染层可以无条件相信的形状。
|
|
19357
|
+
*
|
|
19358
|
+
* 为什么在渲染前统一做,而不是每处判空:站点清单来自外部(154+ 条、每天都在变),
|
|
19359
|
+
* 字段形状出现一次意外(`tags` 是字符串、`preview` 是对象、`bytes` 是 "1.2 MB")就会让
|
|
19360
|
+
* 卡片渲染期抛错。判空散落在十几个位置必然漏,归一只有一处。
|
|
19361
|
+
*/
|
|
19362
|
+
function qpNormalizeItem(raw) {
|
|
19363
|
+
const w = (raw && typeof raw === "object") ? raw : {};
|
|
19364
|
+
const str = (v) => (typeof v === "string" && v ? v : null);
|
|
19365
|
+
const num = (v) => {
|
|
19366
|
+
const n = Number(v);
|
|
19367
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
19368
|
+
};
|
|
19369
|
+
let tags = w.tags;
|
|
19370
|
+
if (typeof tags === "string") tags = tags.split(/[,,\s]+/).filter(Boolean);
|
|
19371
|
+
if (!Array.isArray(tags)) tags = [];
|
|
19372
|
+
tags = tags.filter((t) => typeof t === "string");
|
|
19373
|
+
const type = str(w.type);
|
|
19374
|
+
return {
|
|
19375
|
+
...w,
|
|
19376
|
+
id: (typeof w.id === "string" || typeof w.id === "number") ? w.id : String(w.id || ""),
|
|
19377
|
+
title: str(w.title) || String(w.id || ""),
|
|
19378
|
+
type: (type === "video" || type === "image" || type === "scene" || type === "package") ? type : "image",
|
|
19379
|
+
preview: str(w.preview),
|
|
19380
|
+
thumb: str(w.thumb),
|
|
19381
|
+
media: str(w.media),
|
|
19382
|
+
mediaProxy: str(w.mediaProxy),
|
|
19383
|
+
bytes: num(w.bytes),
|
|
19384
|
+
width: num(w.width),
|
|
19385
|
+
height: num(w.height),
|
|
19386
|
+
duration: num(w.duration),
|
|
19387
|
+
tags,
|
|
19388
|
+
rating: str(w.rating) || "unknown",
|
|
19389
|
+
};
|
|
19390
|
+
}
|
|
19391
|
+
|
|
19392
|
+
/**
|
|
19393
|
+
* 渲染一张卡,**任何异常都只影响这一张**。
|
|
19394
|
+
* 失败时渲染一张"不可用"占位卡(带原因),而不是抛出把整棵树带走。
|
|
19395
|
+
*/
|
|
19396
|
+
function qpRenderCardGuarded(raw, sel, cardsView) {
|
|
19397
|
+
const w = qpNormalizeItem(raw);
|
|
19398
|
+
try {
|
|
19399
|
+
return cardsView ? qpRenderCard(w, sel) : qpRenderRow(w, sel);
|
|
19400
|
+
} catch (e) {
|
|
19401
|
+
const why = (e && (e.message || String(e))) || "unknown";
|
|
19402
|
+
try { console.warn("[folia] 卡片渲染失败,已跳过:", w && w.id, why); } catch { /* ignore */ }
|
|
19403
|
+
return React.createElement("div", {
|
|
19404
|
+
key: "bad-" + String((w && w.id) || Math.random()),
|
|
19405
|
+
className: "we-qp__card we-qp__card--broken",
|
|
19406
|
+
title: why,
|
|
19407
|
+
style: { opacity: 0.5, padding: "8px", fontSize: "12px", lineHeight: 1.4 },
|
|
19408
|
+
}, weT("这条无法显示(数据异常)"), React.createElement("br"), String((w && w.id) || ""), " · ", why.slice(0, 80));
|
|
19409
|
+
}
|
|
19410
|
+
}
|
|
19411
|
+
|
|
19319
19412
|
function qpRenderCard(w, sel) {
|
|
19320
19413
|
// ── 卡:缩略图网格形态(视图切换的「卡片」档;点击语义与列表行一致)──
|
|
19321
19414
|
return React.createElement("div", {
|
|
@@ -19642,7 +19735,10 @@ window.__ModuleLoader__.load({
|
|
|
19642
19735
|
rows.length
|
|
19643
19736
|
? React.createElement(React.Fragment, null,
|
|
19644
19737
|
vTopH > 0 ? qpRenderSpacer("we-qp-vtop", vTopH) : null,
|
|
19645
|
-
|
|
19738
|
+
// **单卡错离**:一条数据坏掉只丢那一张卡,不许带走整片列表。
|
|
19739
|
+
// 实测故障:站点清单里出现字段形状异常(tags 非数组 / preview 非字符串 / media 缺失)后,
|
|
19740
|
+
// 卡片渲染期抛错 ⇒ React 丢弃整棵树 ⇒ 整个面板一片空白且没有任何提示。
|
|
19741
|
+
rows.map((w) => qpRenderCardGuarded(w, sel, cardsView)),
|
|
19646
19742
|
vBtmH > 0 ? qpRenderSpacer("we-qp-vbtm", vBtmH) : null,
|
|
19647
19743
|
)
|
|
19648
19744
|
: React.createElement(React.Fragment, null,
|