dsh-life-pack 0.3.4 → 0.3.6
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/dist/client.js +132 -37
- package/dist/client.js.map +1 -1
- package/dist/directory-browser-api.d.ts +8 -6
- package/dist/directory-browser-api.d.ts.map +1 -1
- package/dist/directory-browser-api.js +6 -5
- package/dist/directory-browser-api.js.map +1 -1
- package/dist/directory-browser-contract.d.ts +13 -4
- package/dist/directory-browser-contract.d.ts.map +1 -1
- package/dist/directory-browser-contract.js +0 -10
- package/dist/directory-browser-contract.js.map +1 -1
- package/dist/directory-browser-parts.d.ts +65 -0
- package/dist/directory-browser-parts.d.ts.map +1 -0
- package/dist/directory-browser-parts.js +189 -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 +9 -1
- package/dist/directory-browser-state.d.ts.map +1 -1
- package/dist/directory-browser-state.js +23 -0
- package/dist/directory-browser-state.js.map +1 -1
- package/dist/directory-browser-ui.d.ts +4 -22
- package/dist/directory-browser-ui.d.ts.map +1 -1
- package/dist/directory-browser-ui.js +8 -150
- 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 +1 -1
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
|
*
|
|
@@ -1841,15 +1848,6 @@ window.__ModuleLoader__.load({
|
|
|
1841
1848
|
if (needle === "") return entries;
|
|
1842
1849
|
return entries.filter((entry) => entry.name.toLowerCase().includes(needle));
|
|
1843
1850
|
}
|
|
1844
|
-
/** 给界面用的一句话位置说明:优先显示相对 home 的说法,省得整条绝对路径占满一行。 */
|
|
1845
|
-
function locationLabel(listing) {
|
|
1846
|
-
const home = listing.home.replace(/[\\/]+$/, "");
|
|
1847
|
-
if (home !== "" && listing.path !== home && listing.path.startsWith(home)) {
|
|
1848
|
-
const tail = listing.path.slice(home.length).replace(/^[\\/]+/, "");
|
|
1849
|
-
if (tail !== "") return "~" + (listing.path.includes("\\") ? "\\" : "/") + tail;
|
|
1850
|
-
}
|
|
1851
|
-
return listing.path;
|
|
1852
|
-
}
|
|
1853
1851
|
/** 新建文件夹的名字合不合法(单个路径段:不许分隔符、不许空、不许 `.`/`..`)。
|
|
1854
1852
|
*
|
|
1855
1853
|
* 与宿主 browse 后端的校验同口径——这里先拦一道,是为了在图上给得出人话,不是替代宿主校验。 */
|
|
@@ -1884,7 +1882,8 @@ window.__ModuleLoader__.load({
|
|
|
1884
1882
|
draft: deps.initialPath,
|
|
1885
1883
|
filter: "",
|
|
1886
1884
|
creating: null,
|
|
1887
|
-
notice: null
|
|
1885
|
+
notice: null,
|
|
1886
|
+
roots: []
|
|
1888
1887
|
};
|
|
1889
1888
|
/** 每次列举带一个序号:晚回来的旧回执直接丢掉,不许覆盖新一层。 */
|
|
1890
1889
|
let generation = 0;
|
|
@@ -1895,6 +1894,18 @@ window.__ModuleLoader__.load({
|
|
|
1895
1894
|
};
|
|
1896
1895
|
for (const listener of [...listeners]) listener(state);
|
|
1897
1896
|
};
|
|
1897
|
+
/** 根清单只问一次:取不到就是空(不报错、不重试——它只是图上的一行入口)。
|
|
1898
|
+
*
|
|
1899
|
+
* 「问一次」钉在这里而不是图里:视图那半每次重画都会重新求值,问在这里才谈得上一次。 */
|
|
1900
|
+
let rootsAsked = false;
|
|
1901
|
+
const loadRoots = async () => {
|
|
1902
|
+
if (rootsAsked || deps.rootsSource === void 0) return;
|
|
1903
|
+
rootsAsked = true;
|
|
1904
|
+
try {
|
|
1905
|
+
const roots = await deps.rootsSource();
|
|
1906
|
+
if (roots.length > 0) emit({ roots });
|
|
1907
|
+
} catch {}
|
|
1908
|
+
};
|
|
1898
1909
|
const humanize = (cause) => {
|
|
1899
1910
|
const raw = typeof cause === "object" && cause !== null ? cause : {};
|
|
1900
1911
|
const message = typeof raw.message === "string" && raw.message.trim() !== "" ? raw.message.trim() : String(cause);
|
|
@@ -1952,6 +1963,7 @@ window.__ModuleLoader__.load({
|
|
|
1952
1963
|
return () => listeners.delete(listener);
|
|
1953
1964
|
},
|
|
1954
1965
|
async open() {
|
|
1966
|
+
loadRoots();
|
|
1955
1967
|
const wanted = deps.initialPath.trim();
|
|
1956
1968
|
if (wanted !== "") {
|
|
1957
1969
|
await load(wanted);
|
|
@@ -2064,7 +2076,8 @@ window.__ModuleLoader__.load({
|
|
|
2064
2076
|
initialPath: deps.initialPath,
|
|
2065
2077
|
onPicked: deps.onPicked,
|
|
2066
2078
|
onClose: () => deps.onClosed?.(),
|
|
2067
|
-
...deps.failureLabel === void 0 ? {} : { failureLabel: deps.failureLabel }
|
|
2079
|
+
...deps.failureLabel === void 0 ? {} : { failureLabel: deps.failureLabel },
|
|
2080
|
+
...deps.rootsSource === void 0 ? {} : { rootsSource: deps.rootsSource }
|
|
2068
2081
|
});
|
|
2069
2082
|
return {
|
|
2070
2083
|
open: () => controller.open(),
|
|
@@ -2086,18 +2099,22 @@ window.__ModuleLoader__.load({
|
|
|
2086
2099
|
};
|
|
2087
2100
|
}
|
|
2088
2101
|
//#endregion
|
|
2089
|
-
//#region src/directory-browser-
|
|
2090
|
-
/**
|
|
2102
|
+
//#region src/directory-browser-parts.ts
|
|
2103
|
+
/** 文件浏览器视图半的**零件**:样式表与四行控件(面包屑/根/一行子目录/新建行)。
|
|
2091
2104
|
*
|
|
2092
|
-
*
|
|
2093
|
-
*
|
|
2105
|
+
* 为什么拆出来:整张对话框的组版与这些零件的变化频率不同——零件跟着「图上多一行/少一行」动,
|
|
2106
|
+
* 组版跟着「对话框长什么样」动。拆开之后两边都在 350 行告警线内,而对外面不变
|
|
2107
|
+
* (`directory-browser-ui.ts` 照旧转出组件与两个接线函数)。
|
|
2108
|
+
*
|
|
2109
|
+
* 本件与视图半同样**不认识宿主**:只认状态与文案。
|
|
2094
2110
|
*/
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2111
|
+
/** 根的类型说明(悬停可见):**本件自己的词汇**,与取数方无关。 */
|
|
2112
|
+
const ROOT_KIND_TEXT = {
|
|
2113
|
+
fixed: "固定磁盘",
|
|
2114
|
+
network: "网络驱动器",
|
|
2115
|
+
removable: "可移动磁盘",
|
|
2116
|
+
optical: "光驱",
|
|
2117
|
+
other: "磁盘"
|
|
2101
2118
|
};
|
|
2102
2119
|
const S$1 = {
|
|
2103
2120
|
scrim: {
|
|
@@ -2134,14 +2151,6 @@ window.__ModuleLoader__.load({
|
|
|
2134
2151
|
fontSize: 14,
|
|
2135
2152
|
fontWeight: 700
|
|
2136
2153
|
},
|
|
2137
|
-
headTail: {
|
|
2138
|
-
marginLeft: "auto",
|
|
2139
|
-
fontSize: 12,
|
|
2140
|
-
opacity: .72,
|
|
2141
|
-
overflow: "hidden",
|
|
2142
|
-
textOverflow: "ellipsis",
|
|
2143
|
-
whiteSpace: "nowrap"
|
|
2144
|
-
},
|
|
2145
2154
|
crumbs: {
|
|
2146
2155
|
display: "flex",
|
|
2147
2156
|
flexWrap: "wrap",
|
|
@@ -2163,6 +2172,20 @@ window.__ModuleLoader__.load({
|
|
|
2163
2172
|
opacity: .45,
|
|
2164
2173
|
fontSize: 12
|
|
2165
2174
|
},
|
|
2175
|
+
roots: {
|
|
2176
|
+
display: "flex",
|
|
2177
|
+
flexWrap: "wrap",
|
|
2178
|
+
alignItems: "center",
|
|
2179
|
+
gap: 6,
|
|
2180
|
+
padding: "0 12px 8px",
|
|
2181
|
+
fontSize: 12
|
|
2182
|
+
},
|
|
2183
|
+
rootButton: {
|
|
2184
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35))",
|
|
2185
|
+
borderRadius: 6,
|
|
2186
|
+
padding: "3px 8px",
|
|
2187
|
+
opacity: 1
|
|
2188
|
+
},
|
|
2166
2189
|
pathRow: {
|
|
2167
2190
|
display: "flex",
|
|
2168
2191
|
gap: 6,
|
|
@@ -2241,6 +2264,7 @@ window.__ModuleLoader__.load({
|
|
|
2241
2264
|
},
|
|
2242
2265
|
error: { color: "var(--dsw-alias-label-error, #e66)" }
|
|
2243
2266
|
};
|
|
2267
|
+
/** 面包屑:从文件系统根到当前层,每一格都是跳转目标(`listing.crumbs` 由取数方给)。 */
|
|
2244
2268
|
function crumbRow(state, onEnter) {
|
|
2245
2269
|
const listing = state.listing;
|
|
2246
2270
|
if (listing === null || listing.crumbs.length === 0) return null;
|
|
@@ -2264,6 +2288,23 @@ window.__ModuleLoader__.load({
|
|
|
2264
2288
|
});
|
|
2265
2289
|
return react.createElement("div", { style: S$1.crumbs }, parts);
|
|
2266
2290
|
}
|
|
2291
|
+
/** 可跳转的根那一行(Windows 上的盘符等):**就是本机全部磁盘**,故不加行首说明。
|
|
2292
|
+
* **少于两个根就不画**:一个根没有「跳到别处」可言。 */
|
|
2293
|
+
function rootsRow(state, onEnter) {
|
|
2294
|
+
if (state.roots.length < 2) return null;
|
|
2295
|
+
const parts = state.roots.map((root) => react.createElement("button", {
|
|
2296
|
+
key: root.path,
|
|
2297
|
+
type: "button",
|
|
2298
|
+
style: {
|
|
2299
|
+
...S$1.chromeButton,
|
|
2300
|
+
...S$1.rootButton
|
|
2301
|
+
},
|
|
2302
|
+
title: ROOT_KIND_TEXT[root.kind] ?? ROOT_KIND_TEXT.other,
|
|
2303
|
+
onClick: () => onEnter(root.path)
|
|
2304
|
+
}, root.path));
|
|
2305
|
+
return react.createElement("div", { style: S$1.roots }, parts);
|
|
2306
|
+
}
|
|
2307
|
+
/** 一行子目录:点名字进它,点「选」把它当目标(「选」再点一次取消)。 */
|
|
2267
2308
|
function entryRow(entry, selected, labels, onToggleSelect, onEnter) {
|
|
2268
2309
|
return react.createElement("div", {
|
|
2269
2310
|
key: entry.path,
|
|
@@ -2292,6 +2333,7 @@ window.__ModuleLoader__.load({
|
|
|
2292
2333
|
onClick: () => onToggleSelect(entry.path)
|
|
2293
2334
|
}, selected ? "✓" : labels.select));
|
|
2294
2335
|
}
|
|
2336
|
+
/** 「新建文件夹」那一行(`creatingName === null` 时收起)。 */
|
|
2295
2337
|
function createRow(labels, onName, onCancel, onCreate, creatingName) {
|
|
2296
2338
|
if (creatingName === null) return null;
|
|
2297
2339
|
return react.createElement("div", { style: {
|
|
@@ -2317,16 +2359,31 @@ window.__ModuleLoader__.load({
|
|
|
2317
2359
|
onClick: onCancel
|
|
2318
2360
|
}, labels.createCancel));
|
|
2319
2361
|
}
|
|
2362
|
+
//#endregion
|
|
2363
|
+
//#region src/directory-browser-ui.ts
|
|
2364
|
+
/** 文件浏览器的视图半:把 `BrowseState` 画成一张对话框。**不认识宿主**,也不自己做 IO。
|
|
2365
|
+
*
|
|
2366
|
+
* 手写 `React.createElement`(本包 client 束禁 JSX,见 `tsdown.config.ts` 的既成口径)。
|
|
2367
|
+
* 只读状态、只回调;进哪一层、选到什么由操作半(`directory-browser-state.ts`)算。
|
|
2368
|
+
* 样式与四行控件在 `directory-browser-parts.ts`(那里跟着「多一行/少一行」动,本件只管组版)。
|
|
2369
|
+
*/
|
|
2370
|
+
const EMPTY_LISTING = {
|
|
2371
|
+
path: "",
|
|
2372
|
+
home: "",
|
|
2373
|
+
crumbs: [],
|
|
2374
|
+
entries: [],
|
|
2375
|
+
truncated: false
|
|
2376
|
+
};
|
|
2320
2377
|
/** 画一张文件浏览器对话框。`open=false` 时返回 null(本件不挂 portal,由调用方决定挂在哪)。 */
|
|
2321
2378
|
function DirectoryBrowser(props) {
|
|
2322
2379
|
const { open, state, labels } = props;
|
|
2323
2380
|
if (!open) return null;
|
|
2324
2381
|
const rows = rowsOf(state);
|
|
2325
2382
|
const target = targetOf(state);
|
|
2326
|
-
const
|
|
2327
|
-
const hidden = hiddenCount(listing);
|
|
2383
|
+
const hidden = hiddenCount(state.listing ?? EMPTY_LISTING);
|
|
2328
2384
|
const body = [];
|
|
2329
2385
|
body.push(crumbRow(state, props.onEnter));
|
|
2386
|
+
body.push(rootsRow(state, props.onEnter));
|
|
2330
2387
|
body.push(react.createElement("div", { style: S$1.pathRow }, react.createElement("input", {
|
|
2331
2388
|
style: S$1.input,
|
|
2332
2389
|
value: state.draft,
|
|
@@ -2393,12 +2450,12 @@ window.__ModuleLoader__.load({
|
|
|
2393
2450
|
role: "dialog",
|
|
2394
2451
|
"aria-modal": true,
|
|
2395
2452
|
"aria-label": labels.title
|
|
2396
|
-
}, react.createElement("div", { style: S$1.head }, react.createElement("span", { style: S$1.title }, labels.title), react.createElement("
|
|
2397
|
-
style: S$1.headTail,
|
|
2398
|
-
title: listing.path
|
|
2399
|
-
}, state.listing === null ? "" : locationLabel(listing)), react.createElement("button", {
|
|
2453
|
+
}, react.createElement("div", { style: S$1.head }, react.createElement("span", { style: S$1.title }, labels.title), react.createElement("button", {
|
|
2400
2454
|
type: "button",
|
|
2401
|
-
style:
|
|
2455
|
+
style: {
|
|
2456
|
+
...S$1.button,
|
|
2457
|
+
marginLeft: "auto"
|
|
2458
|
+
},
|
|
2402
2459
|
"aria-label": labels.close,
|
|
2403
2460
|
title: labels.close,
|
|
2404
2461
|
onClick: props.onClose
|
|
@@ -2421,6 +2478,42 @@ window.__ModuleLoader__.load({
|
|
|
2421
2478
|
});
|
|
2422
2479
|
}
|
|
2423
2480
|
//#endregion
|
|
2481
|
+
//#region src/directory-browser-roots.ts
|
|
2482
|
+
/** 认一认某个值像不像一个根(认不出的行丢掉:图的干净比多画一行重要)。 */
|
|
2483
|
+
function isRootRow(raw) {
|
|
2484
|
+
if (typeof raw !== "object" || raw === null) return false;
|
|
2485
|
+
const row = raw;
|
|
2486
|
+
return typeof row.path === "string" && row.path !== "" && typeof row.kind === "string";
|
|
2487
|
+
}
|
|
2488
|
+
/** 信封 → 根清单(纯函数,**永不抛**):`{ok:true, value:{roots:[…]}}` 之外的形状一律当空。 */
|
|
2489
|
+
function readRootsAnswer(raw) {
|
|
2490
|
+
const answer = typeof raw === "object" && raw !== null ? raw : {};
|
|
2491
|
+
if (answer.ok !== true) return [];
|
|
2492
|
+
const value = typeof answer.value === "object" && answer.value !== null ? answer.value : {};
|
|
2493
|
+
if (!Array.isArray(value.roots)) return [];
|
|
2494
|
+
return value.roots.filter(isRootRow).map((row) => ({
|
|
2495
|
+
path: row.path,
|
|
2496
|
+
kind: row.kind
|
|
2497
|
+
}));
|
|
2498
|
+
}
|
|
2499
|
+
/** 建一个「根清单取数器」:给调用口与三个名字,回一个 `() => Promise<RootRow[]>`。
|
|
2500
|
+
*
|
|
2501
|
+
* 界面按需调它(开图一次),不在渲染里调,也不缓存到这里——缓存是宿主半的事。 */
|
|
2502
|
+
function createRootsSource(input) {
|
|
2503
|
+
return async () => {
|
|
2504
|
+
const call = input.getCall();
|
|
2505
|
+
if (typeof call !== "function") return [];
|
|
2506
|
+
try {
|
|
2507
|
+
return readRootsAnswer(await call(input.base, input.endpoint, {
|
|
2508
|
+
method: input.method,
|
|
2509
|
+
payload: {}
|
|
2510
|
+
}));
|
|
2511
|
+
} catch {
|
|
2512
|
+
return [];
|
|
2513
|
+
}
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
//#endregion
|
|
2424
2517
|
//#region src/client.ts
|
|
2425
2518
|
/** dsh-life-pack client 适配器(六边形:browser 侧;爱生活单卡方案)。
|
|
2426
2519
|
*
|
|
@@ -2811,10 +2904,12 @@ window.__ModuleLoader__.load({
|
|
|
2811
2904
|
exports.canGoUp = canGoUp;
|
|
2812
2905
|
exports.createBrowseController = createBrowseController;
|
|
2813
2906
|
exports.createDirectoryRowBrowser = createDirectoryRowBrowser;
|
|
2907
|
+
exports.createRootsSource = createRootsSource;
|
|
2814
2908
|
exports.entryPath = entryPath;
|
|
2815
2909
|
exports.inject = inject;
|
|
2816
2910
|
exports.pickerModeOf = pickerModeOf;
|
|
2817
2911
|
exports.readPickAnswer = readPickAnswer;
|
|
2912
|
+
exports.readRootsAnswer = readRootsAnswer;
|
|
2818
2913
|
exports.rowsOf = rowsOf;
|
|
2819
2914
|
exports.targetOf = targetOf;
|
|
2820
2915
|
return module.exports;
|