dsh-life-pack 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/client.js +166 -22
- package/dist/client.js.map +1 -1
- package/dist/directory-browser-api.d.ts +10 -7
- package/dist/directory-browser-api.d.ts.map +1 -1
- package/dist/directory-browser-api.js +8 -6
- package/dist/directory-browser-api.js.map +1 -1
- package/dist/directory-browser-contract.d.ts +51 -3
- package/dist/directory-browser-contract.d.ts.map +1 -1
- package/dist/directory-browser-contract.js +56 -5
- package/dist/directory-browser-contract.js.map +1 -1
- package/dist/directory-browser-parts.d.ts +68 -0
- package/dist/directory-browser-parts.d.ts.map +1 -0
- package/dist/directory-browser-parts.js +195 -0
- package/dist/directory-browser-parts.js.map +1 -0
- package/dist/directory-browser-roots.d.ts +21 -0
- package/dist/directory-browser-roots.d.ts.map +1 -0
- package/dist/directory-browser-roots.js +40 -0
- package/dist/directory-browser-roots.js.map +1 -0
- package/dist/directory-browser-state.d.ts +38 -5
- package/dist/directory-browser-state.d.ts.map +1 -1
- package/dist/directory-browser-state.js +69 -2
- package/dist/directory-browser-state.js.map +1 -1
- package/dist/directory-browser-ui.d.ts +8 -23
- package/dist/directory-browser-ui.d.ts.map +1 -1
- package/dist/directory-browser-ui.js +10 -148
- package/dist/directory-browser-ui.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/roots.d.ts +34 -0
- package/dist/roots.d.ts.map +1 -0
- package/dist/roots.js +95 -0
- package/dist/roots.js.map +1 -0
- package/dist/update-contract.d.ts +7 -0
- package/dist/update-contract.d.ts.map +1 -1
- package/dist/update-contract.js +7 -0
- package/dist/update-contract.js.map +1 -1
- package/dist/update-host.d.ts.map +1 -1
- package/dist/update-host.js +5 -1
- package/dist/update-host.js.map +1 -1
- package/package.json +6 -1
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# 爱生活(dsh-life-pack)
|
|
2
|
+
|
|
3
|
+
DSH 设置弹窗里的「爱生活」卡:把六个技能(备忘录、卡路里、私家大厨、居家管家、作息管家、饼干记账)的**设置页**收进一排页签,另带**配置体检**与**更新区**。
|
|
4
|
+
|
|
5
|
+
One settings card for the iLife home-life suite: memo, calorie, chef, home, schedule and bill in a tab bar, with a read-only config health check and an update panel.
|
|
6
|
+
|
|
7
|
+
## 装
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
dsh plugin add dsh-life-pack # 只要这张卡
|
|
11
|
+
dsh plugin add dsh-life-pack dsh-calorie # 卡 + 某一个技能(一条命令两个包)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
某个技能没装时,那一个页签的位置会变成**补装提示**,带上该条可直接复制的命令——不需要先装齐六个。
|
|
15
|
+
|
|
16
|
+
## 卡里有什么
|
|
17
|
+
|
|
18
|
+
| 区域 | 做什么 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| 页签条 | 六个技能的设置页各自注册进「爱生活页签槽」;点谁切谁 |
|
|
21
|
+
| 配置体检 | 点一次,按家取数:顶部一行总账,每家一张表(每条:灯/标题/档/一句话/去哪修)。**只读**——不建目录、不改配置、不自动重置 |
|
|
22
|
+
| 更新区 | 七个包(它自己 + 六家)检查更新、逐个安装;装完按事实给下一步(重启 DSH 生效/重试/重新检查) |
|
|
23
|
+
| 面板入口 | 右上角:检查更新、去仓库点 Star、反馈问题;底部一句「作者其他插件」 |
|
|
24
|
+
|
|
25
|
+
## 它不做什么
|
|
26
|
+
|
|
27
|
+
不 import 任何一个单品包,也不依赖它们——依赖方向是反的(单品硬依赖 `dsh-life-pack`)。总管只做导航与聚合:六家的页面各自注册进槽位,缺席就渲染补装提示,不轮询、不代管。
|
|
28
|
+
|
|
29
|
+
## 配置与数据
|
|
30
|
+
|
|
31
|
+
- 配置:`~/.ilife/<技能>.yaml`(六个技能各一份,在各自的设置页里改)
|
|
32
|
+
- 数据目录:默认 `~/.ilife/data`(可在各技能设置页里改;留空=用默认)
|
|
33
|
+
|
|
34
|
+
## 链接
|
|
35
|
+
|
|
36
|
+
- 仓库与整套插件(六个单品同仓):<https://github.com/FeatherHunter/ilife>
|
|
37
|
+
- 问题与建议:<https://github.com/FeatherHunter/ilife/issues>
|
|
38
|
+
- 许可:MIT
|
package/dist/client.js
CHANGED
|
@@ -179,7 +179,14 @@ window.__ModuleLoader__.load({
|
|
|
179
179
|
* 宿主读**自己这份已安装包**的 `package.json`(`manager-version.ts`),面板只渲染读到的值。
|
|
180
180
|
* 为什么走电话:面板是浏览器产物,禁 node 内建(`test/client-bundle-48.test.mjs` 看门),
|
|
181
181
|
* 读盘只许在宿主半;与卡路里 #130 同一条路(host 读 → RPC → client 纯渲染)。 */
|
|
182
|
-
version: "ilife-manager.version"
|
|
182
|
+
version: "ilife-manager.version",
|
|
183
|
+
/** 本机「根」清单(票 #744):入参 `{}`,回包 `{roots: [{path, kind}]}`。
|
|
184
|
+
*
|
|
185
|
+
* 为什么这条能力住在总管:浏览器里没有卷清单接口,而宿主那条目录选择接缝只有
|
|
186
|
+
* `list`/`createDirectory` 两格——「这台机器上有哪些盘」只能由宿主去问操作系统。
|
|
187
|
+
* 六家的目录浏览器都取这一通电话,故名字与实现都只有这一处(清单本身见 `roots.ts`)。
|
|
188
|
+
* 回包**恒成功**:列不出来是空清单,不是错误码(界面据此只是不画那一行)。 */
|
|
189
|
+
roots: "ilife-manager.roots"
|
|
183
190
|
};
|
|
184
191
|
/** 版本读不到时两侧共用的降级字面量(面板照原样显示,不假装知道版本)。
|
|
185
192
|
*
|
|
@@ -1747,10 +1754,15 @@ window.__ModuleLoader__.load({
|
|
|
1747
1754
|
if (typeof raw !== "object" || raw === null) return false;
|
|
1748
1755
|
return typeof raw.pick === "function";
|
|
1749
1756
|
}
|
|
1750
|
-
/**
|
|
1757
|
+
/** 三态判定:**先看两格浏览原语**(都在=应用内浏览器这条路在),再看单独的 `pick`,都没有=`none`。
|
|
1758
|
+
*
|
|
1759
|
+
* 为什么浏览优先:客户端命名空间上三条动词一定都在(由 `TYPERT_REMOTE.descriptors` 生成,
|
|
1760
|
+
* 与组合里服务哪种能力无关),所以两格原语在=这台宿主**可能**给浏览;真调一次才知道给不给,
|
|
1761
|
+
* 不给({@link CAPABILITY_REFUSED})再由调用方换系统对话框。反过来先认 `pick` 就会在
|
|
1762
|
+
* Desktop 这种「只服务 browse」的宿主上每次都去唤一次系统对话框、被拒一次(#744 实测)。 */
|
|
1751
1763
|
function pickerModeOf(raw) {
|
|
1752
|
-
if (
|
|
1753
|
-
return
|
|
1764
|
+
if (isBrowseFace(raw)) return "browse";
|
|
1765
|
+
return hasPickFn(raw) ? "native" : "none";
|
|
1754
1766
|
}
|
|
1755
1767
|
/** 平台回执 → 三态(**永不抛**):成功回的是信封里的 `value` 不是路径;`ok:false` 是「供不了」不是「取消」。
|
|
1756
1768
|
*
|
|
@@ -1771,8 +1783,10 @@ window.__ModuleLoader__.load({
|
|
|
1771
1783
|
} : { kind: "cancelled" };
|
|
1772
1784
|
}
|
|
1773
1785
|
const detail = answer.error?.message?.trim() ?? "";
|
|
1786
|
+
const code = answer.error?.code;
|
|
1774
1787
|
return {
|
|
1775
1788
|
kind: "unavailable",
|
|
1789
|
+
...typeof code === "string" && code !== "" ? { code } : {},
|
|
1776
1790
|
message: "打不开系统文件夹对话框" + (detail === "" ? "" : "(" + detail + ")") + ":请直接在框里填绝对路径。"
|
|
1777
1791
|
};
|
|
1778
1792
|
}
|
|
@@ -1877,7 +1891,8 @@ window.__ModuleLoader__.load({
|
|
|
1877
1891
|
draft: deps.initialPath,
|
|
1878
1892
|
filter: "",
|
|
1879
1893
|
creating: null,
|
|
1880
|
-
notice: null
|
|
1894
|
+
notice: null,
|
|
1895
|
+
roots: []
|
|
1881
1896
|
};
|
|
1882
1897
|
/** 每次列举带一个序号:晚回来的旧回执直接丢掉,不许覆盖新一层。 */
|
|
1883
1898
|
let generation = 0;
|
|
@@ -1888,6 +1903,18 @@ window.__ModuleLoader__.load({
|
|
|
1888
1903
|
};
|
|
1889
1904
|
for (const listener of [...listeners]) listener(state);
|
|
1890
1905
|
};
|
|
1906
|
+
/** 根清单只问一次:取不到就是空(不报错、不重试——它只是图上的一行入口)。
|
|
1907
|
+
*
|
|
1908
|
+
* 「问一次」钉在这里而不是图里:视图那半每次重画都会重新求值,问在这里才谈得上一次。 */
|
|
1909
|
+
let rootsAsked = false;
|
|
1910
|
+
const loadRoots = async () => {
|
|
1911
|
+
if (rootsAsked || deps.rootsSource === void 0) return;
|
|
1912
|
+
rootsAsked = true;
|
|
1913
|
+
try {
|
|
1914
|
+
const roots = await deps.rootsSource();
|
|
1915
|
+
if (roots.length > 0) emit({ roots });
|
|
1916
|
+
} catch {}
|
|
1917
|
+
};
|
|
1891
1918
|
const humanize = (cause) => {
|
|
1892
1919
|
const raw = typeof cause === "object" && cause !== null ? cause : {};
|
|
1893
1920
|
const message = typeof raw.message === "string" && raw.message.trim() !== "" ? raw.message.trim() : String(cause);
|
|
@@ -1944,7 +1971,16 @@ window.__ModuleLoader__.load({
|
|
|
1944
1971
|
listeners.add(listener);
|
|
1945
1972
|
return () => listeners.delete(listener);
|
|
1946
1973
|
},
|
|
1947
|
-
open
|
|
1974
|
+
async open() {
|
|
1975
|
+
loadRoots();
|
|
1976
|
+
const wanted = deps.initialPath.trim();
|
|
1977
|
+
if (wanted !== "") {
|
|
1978
|
+
await load(wanted);
|
|
1979
|
+
if (state.phase === "ready") return state;
|
|
1980
|
+
}
|
|
1981
|
+
await load(null);
|
|
1982
|
+
return state;
|
|
1983
|
+
},
|
|
1948
1984
|
enter: (path) => load(path),
|
|
1949
1985
|
async up() {
|
|
1950
1986
|
const listing = state.listing;
|
|
@@ -2049,10 +2085,12 @@ window.__ModuleLoader__.load({
|
|
|
2049
2085
|
initialPath: deps.initialPath,
|
|
2050
2086
|
onPicked: deps.onPicked,
|
|
2051
2087
|
onClose: () => deps.onClosed?.(),
|
|
2052
|
-
...deps.failureLabel === void 0 ? {} : { failureLabel: deps.failureLabel }
|
|
2088
|
+
...deps.failureLabel === void 0 ? {} : { failureLabel: deps.failureLabel },
|
|
2089
|
+
...deps.rootsSource === void 0 ? {} : { rootsSource: deps.rootsSource }
|
|
2053
2090
|
});
|
|
2054
2091
|
return {
|
|
2055
2092
|
open: () => controller.open(),
|
|
2093
|
+
subscribe: (listener) => controller.subscribe(listener),
|
|
2056
2094
|
state: () => controller.getState(),
|
|
2057
2095
|
summary: () => describe(controller.getState()),
|
|
2058
2096
|
actions: {
|
|
@@ -2070,18 +2108,22 @@ window.__ModuleLoader__.load({
|
|
|
2070
2108
|
};
|
|
2071
2109
|
}
|
|
2072
2110
|
//#endregion
|
|
2073
|
-
//#region src/directory-browser-
|
|
2074
|
-
/**
|
|
2111
|
+
//#region src/directory-browser-parts.ts
|
|
2112
|
+
/** 文件浏览器视图半的**零件**:样式表与四行控件(面包屑/根/一行子目录/新建行)。
|
|
2075
2113
|
*
|
|
2076
|
-
*
|
|
2077
|
-
*
|
|
2114
|
+
* 为什么拆出来:整张对话框的组版与这些零件的变化频率不同——零件跟着「图上多一行/少一行」动,
|
|
2115
|
+
* 组版跟着「对话框长什么样」动。拆开之后两边都在 350 行告警线内,而对外面不变
|
|
2116
|
+
* (`directory-browser-ui.ts` 照旧转出组件与两个接线函数)。
|
|
2117
|
+
*
|
|
2118
|
+
* 本件与视图半同样**不认识宿主**:只认状态与文案。
|
|
2078
2119
|
*/
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2120
|
+
/** 根的类型说明(悬停可见):**本件自己的词汇**,与取数方无关。 */
|
|
2121
|
+
const ROOT_KIND_TEXT = {
|
|
2122
|
+
fixed: "固定磁盘",
|
|
2123
|
+
network: "网络驱动器",
|
|
2124
|
+
removable: "可移动磁盘",
|
|
2125
|
+
optical: "光驱",
|
|
2126
|
+
other: "磁盘"
|
|
2085
2127
|
};
|
|
2086
2128
|
const S$1 = {
|
|
2087
2129
|
scrim: {
|
|
@@ -2147,6 +2189,24 @@ window.__ModuleLoader__.load({
|
|
|
2147
2189
|
opacity: .45,
|
|
2148
2190
|
fontSize: 12
|
|
2149
2191
|
},
|
|
2192
|
+
roots: {
|
|
2193
|
+
display: "flex",
|
|
2194
|
+
flexWrap: "wrap",
|
|
2195
|
+
alignItems: "center",
|
|
2196
|
+
gap: 6,
|
|
2197
|
+
padding: "0 12px 8px",
|
|
2198
|
+
fontSize: 12
|
|
2199
|
+
},
|
|
2200
|
+
rootsLabel: {
|
|
2201
|
+
opacity: .72,
|
|
2202
|
+
marginRight: 2
|
|
2203
|
+
},
|
|
2204
|
+
rootButton: {
|
|
2205
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35))",
|
|
2206
|
+
borderRadius: 6,
|
|
2207
|
+
padding: "3px 8px",
|
|
2208
|
+
opacity: 1
|
|
2209
|
+
},
|
|
2150
2210
|
pathRow: {
|
|
2151
2211
|
display: "flex",
|
|
2152
2212
|
gap: 6,
|
|
@@ -2225,7 +2285,8 @@ window.__ModuleLoader__.load({
|
|
|
2225
2285
|
},
|
|
2226
2286
|
error: { color: "var(--dsw-alias-label-error, #e66)" }
|
|
2227
2287
|
};
|
|
2228
|
-
|
|
2288
|
+
/** 面包屑:从文件系统根到当前层,每一格都是跳转目标(`listing.crumbs` 由取数方给)。 */
|
|
2289
|
+
function crumbRow(state, onEnter) {
|
|
2229
2290
|
const listing = state.listing;
|
|
2230
2291
|
if (listing === null || listing.crumbs.length === 0) return null;
|
|
2231
2292
|
const parts = [];
|
|
@@ -2244,10 +2305,34 @@ window.__ModuleLoader__.load({
|
|
|
2244
2305
|
},
|
|
2245
2306
|
title: crumb.path,
|
|
2246
2307
|
onClick: () => onEnter(crumb.path)
|
|
2247
|
-
},
|
|
2308
|
+
}, crumb.name));
|
|
2248
2309
|
});
|
|
2249
2310
|
return react.createElement("div", { style: S$1.crumbs }, parts);
|
|
2250
2311
|
}
|
|
2312
|
+
/** 可跳转的根那一行(Windows 上的盘符等)。**少于两个根就不画**:一个根没有「跳到别处」可言。 */
|
|
2313
|
+
function rootsRow(state, labels, onEnter) {
|
|
2314
|
+
if (state.roots.length < 2) return null;
|
|
2315
|
+
const parts = [react.createElement("span", {
|
|
2316
|
+
key: "roots-label",
|
|
2317
|
+
style: S$1.rootsLabel
|
|
2318
|
+
}, labels.roots)];
|
|
2319
|
+
for (const root of state.roots) parts.push(react.createElement("button", {
|
|
2320
|
+
key: root.path,
|
|
2321
|
+
type: "button",
|
|
2322
|
+
style: {
|
|
2323
|
+
...S$1.chromeButton,
|
|
2324
|
+
...S$1.rootButton
|
|
2325
|
+
},
|
|
2326
|
+
title: ROOT_KIND_TEXT[root.kind] ?? ROOT_KIND_TEXT.other,
|
|
2327
|
+
onClick: () => onEnter(root.path)
|
|
2328
|
+
}, root.path));
|
|
2329
|
+
return react.createElement("div", {
|
|
2330
|
+
style: S$1.roots,
|
|
2331
|
+
role: "group",
|
|
2332
|
+
"aria-label": labels.roots
|
|
2333
|
+
}, parts);
|
|
2334
|
+
}
|
|
2335
|
+
/** 一行子目录:点名字进它,点「选」把它当目标(「选」再点一次取消)。 */
|
|
2251
2336
|
function entryRow(entry, selected, labels, onToggleSelect, onEnter) {
|
|
2252
2337
|
return react.createElement("div", {
|
|
2253
2338
|
key: entry.path,
|
|
@@ -2276,6 +2361,7 @@ window.__ModuleLoader__.load({
|
|
|
2276
2361
|
onClick: () => onToggleSelect(entry.path)
|
|
2277
2362
|
}, selected ? "✓" : labels.select));
|
|
2278
2363
|
}
|
|
2364
|
+
/** 「新建文件夹」那一行(`creatingName === null` 时收起)。 */
|
|
2279
2365
|
function createRow(labels, onName, onCancel, onCreate, creatingName) {
|
|
2280
2366
|
if (creatingName === null) return null;
|
|
2281
2367
|
return react.createElement("div", { style: {
|
|
@@ -2301,6 +2387,21 @@ window.__ModuleLoader__.load({
|
|
|
2301
2387
|
onClick: onCancel
|
|
2302
2388
|
}, labels.createCancel));
|
|
2303
2389
|
}
|
|
2390
|
+
//#endregion
|
|
2391
|
+
//#region src/directory-browser-ui.ts
|
|
2392
|
+
/** 文件浏览器的视图半:把 `BrowseState` 画成一张对话框。**不认识宿主**,也不自己做 IO。
|
|
2393
|
+
*
|
|
2394
|
+
* 手写 `React.createElement`(本包 client 束禁 JSX,见 `tsdown.config.ts` 的既成口径)。
|
|
2395
|
+
* 只读状态、只回调;进哪一层、选到什么由操作半(`directory-browser-state.ts`)算。
|
|
2396
|
+
* 样式与四行控件在 `directory-browser-parts.ts`(那里跟着「多一行/少一行」动,本件只管组版)。
|
|
2397
|
+
*/
|
|
2398
|
+
const EMPTY_LISTING = {
|
|
2399
|
+
path: "",
|
|
2400
|
+
home: "",
|
|
2401
|
+
crumbs: [],
|
|
2402
|
+
entries: [],
|
|
2403
|
+
truncated: false
|
|
2404
|
+
};
|
|
2304
2405
|
/** 画一张文件浏览器对话框。`open=false` 时返回 null(本件不挂 portal,由调用方决定挂在哪)。 */
|
|
2305
2406
|
function DirectoryBrowser(props) {
|
|
2306
2407
|
const { open, state, labels } = props;
|
|
@@ -2310,7 +2411,8 @@ window.__ModuleLoader__.load({
|
|
|
2310
2411
|
const listing = state.listing ?? EMPTY_LISTING;
|
|
2311
2412
|
const hidden = hiddenCount(listing);
|
|
2312
2413
|
const body = [];
|
|
2313
|
-
body.push(crumbRow(state,
|
|
2414
|
+
body.push(crumbRow(state, props.onEnter));
|
|
2415
|
+
body.push(rootsRow(state, labels, props.onEnter));
|
|
2314
2416
|
body.push(react.createElement("div", { style: S$1.pathRow }, react.createElement("input", {
|
|
2315
2417
|
style: S$1.input,
|
|
2316
2418
|
value: state.draft,
|
|
@@ -2391,16 +2493,56 @@ window.__ModuleLoader__.load({
|
|
|
2391
2493
|
/** 目录行接线的 React 半边:把操作半那组动作直接喂给组件。
|
|
2392
2494
|
*
|
|
2393
2495
|
* 操作半(`directory-browser-state.ts` 的 `createDirectoryRowBrowser`)不碰 React,
|
|
2394
|
-
* 这里只把它的 `actions` 摊进 props —— 于是「进哪一层」那套在 Node 里可测,这里只做接线。
|
|
2496
|
+
* 这里只把它的 `actions` 摊进 props —— 于是「进哪一层」那套在 Node 里可测,这里只做接线。
|
|
2497
|
+
*
|
|
2498
|
+
* `useSyncExternalStore` 是**必需**的:列举是异步落定的,不订阅的话这一帧画完就再没有重画,
|
|
2499
|
+
* 图上会永远停在「正在读取…」(#744 实测)。 */
|
|
2395
2500
|
function DirectoryBrowserFromRow(props) {
|
|
2501
|
+
const state = react.useSyncExternalStore(props.row.subscribe, props.row.state);
|
|
2396
2502
|
return DirectoryBrowser({
|
|
2397
2503
|
open: props.open,
|
|
2398
|
-
state
|
|
2504
|
+
state,
|
|
2399
2505
|
labels: props.labels,
|
|
2400
2506
|
...props.row.actions
|
|
2401
2507
|
});
|
|
2402
2508
|
}
|
|
2403
2509
|
//#endregion
|
|
2510
|
+
//#region src/directory-browser-roots.ts
|
|
2511
|
+
/** 认一认某个值像不像一个根(认不出的行丢掉:图的干净比多画一行重要)。 */
|
|
2512
|
+
function isRootRow(raw) {
|
|
2513
|
+
if (typeof raw !== "object" || raw === null) return false;
|
|
2514
|
+
const row = raw;
|
|
2515
|
+
return typeof row.path === "string" && row.path !== "" && typeof row.kind === "string";
|
|
2516
|
+
}
|
|
2517
|
+
/** 信封 → 根清单(纯函数,**永不抛**):`{ok:true, value:{roots:[…]}}` 之外的形状一律当空。 */
|
|
2518
|
+
function readRootsAnswer(raw) {
|
|
2519
|
+
const answer = typeof raw === "object" && raw !== null ? raw : {};
|
|
2520
|
+
if (answer.ok !== true) return [];
|
|
2521
|
+
const value = typeof answer.value === "object" && answer.value !== null ? answer.value : {};
|
|
2522
|
+
if (!Array.isArray(value.roots)) return [];
|
|
2523
|
+
return value.roots.filter(isRootRow).map((row) => ({
|
|
2524
|
+
path: row.path,
|
|
2525
|
+
kind: row.kind
|
|
2526
|
+
}));
|
|
2527
|
+
}
|
|
2528
|
+
/** 建一个「根清单取数器」:给调用口与三个名字,回一个 `() => Promise<RootRow[]>`。
|
|
2529
|
+
*
|
|
2530
|
+
* 界面按需调它(开图一次),不在渲染里调,也不缓存到这里——缓存是宿主半的事。 */
|
|
2531
|
+
function createRootsSource(input) {
|
|
2532
|
+
return async () => {
|
|
2533
|
+
const call = input.getCall();
|
|
2534
|
+
if (typeof call !== "function") return [];
|
|
2535
|
+
try {
|
|
2536
|
+
return readRootsAnswer(await call(input.base, input.endpoint, {
|
|
2537
|
+
method: input.method,
|
|
2538
|
+
payload: {}
|
|
2539
|
+
}));
|
|
2540
|
+
} catch {
|
|
2541
|
+
return [];
|
|
2542
|
+
}
|
|
2543
|
+
};
|
|
2544
|
+
}
|
|
2545
|
+
//#endregion
|
|
2404
2546
|
//#region src/client.ts
|
|
2405
2547
|
/** dsh-life-pack client 适配器(六边形:browser 侧;爱生活单卡方案)。
|
|
2406
2548
|
*
|
|
@@ -2791,10 +2933,12 @@ window.__ModuleLoader__.load({
|
|
|
2791
2933
|
exports.canGoUp = canGoUp;
|
|
2792
2934
|
exports.createBrowseController = createBrowseController;
|
|
2793
2935
|
exports.createDirectoryRowBrowser = createDirectoryRowBrowser;
|
|
2936
|
+
exports.createRootsSource = createRootsSource;
|
|
2794
2937
|
exports.entryPath = entryPath;
|
|
2795
2938
|
exports.inject = inject;
|
|
2796
2939
|
exports.pickerModeOf = pickerModeOf;
|
|
2797
2940
|
exports.readPickAnswer = readPickAnswer;
|
|
2941
|
+
exports.readRootsAnswer = readRootsAnswer;
|
|
2798
2942
|
exports.rowsOf = rowsOf;
|
|
2799
2943
|
exports.targetOf = targetOf;
|
|
2800
2944
|
return module.exports;
|