dsh-life-pack 0.3.4 → 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.
Files changed (41) hide show
  1. package/dist/client.js +137 -13
  2. package/dist/client.js.map +1 -1
  3. package/dist/directory-browser-api.d.ts +7 -5
  4. package/dist/directory-browser-api.d.ts.map +1 -1
  5. package/dist/directory-browser-api.js +5 -4
  6. package/dist/directory-browser-api.js.map +1 -1
  7. package/dist/directory-browser-contract.d.ts +13 -2
  8. package/dist/directory-browser-contract.d.ts.map +1 -1
  9. package/dist/directory-browser-contract.js.map +1 -1
  10. package/dist/directory-browser-parts.d.ts +68 -0
  11. package/dist/directory-browser-parts.d.ts.map +1 -0
  12. package/dist/directory-browser-parts.js +195 -0
  13. package/dist/directory-browser-parts.js.map +1 -0
  14. package/dist/directory-browser-roots.d.ts +21 -0
  15. package/dist/directory-browser-roots.d.ts.map +1 -0
  16. package/dist/directory-browser-roots.js +40 -0
  17. package/dist/directory-browser-roots.js.map +1 -0
  18. package/dist/directory-browser-state.d.ts +9 -1
  19. package/dist/directory-browser-state.d.ts.map +1 -1
  20. package/dist/directory-browser-state.js +23 -0
  21. package/dist/directory-browser-state.js.map +1 -1
  22. package/dist/directory-browser-ui.d.ts +4 -22
  23. package/dist/directory-browser-ui.d.ts.map +1 -1
  24. package/dist/directory-browser-ui.js +3 -148
  25. package/dist/directory-browser-ui.js.map +1 -1
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +2 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/roots.d.ts +34 -0
  31. package/dist/roots.d.ts.map +1 -0
  32. package/dist/roots.js +95 -0
  33. package/dist/roots.js.map +1 -0
  34. package/dist/update-contract.d.ts +7 -0
  35. package/dist/update-contract.d.ts.map +1 -1
  36. package/dist/update-contract.js +7 -0
  37. package/dist/update-contract.js.map +1 -1
  38. package/dist/update-host.d.ts.map +1 -1
  39. package/dist/update-host.js +5 -1
  40. package/dist/update-host.js.map +1 -1
  41. 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
  *
@@ -1884,7 +1891,8 @@ window.__ModuleLoader__.load({
1884
1891
  draft: deps.initialPath,
1885
1892
  filter: "",
1886
1893
  creating: null,
1887
- notice: null
1894
+ notice: null,
1895
+ roots: []
1888
1896
  };
1889
1897
  /** 每次列举带一个序号:晚回来的旧回执直接丢掉,不许覆盖新一层。 */
1890
1898
  let generation = 0;
@@ -1895,6 +1903,18 @@ window.__ModuleLoader__.load({
1895
1903
  };
1896
1904
  for (const listener of [...listeners]) listener(state);
1897
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
+ };
1898
1918
  const humanize = (cause) => {
1899
1919
  const raw = typeof cause === "object" && cause !== null ? cause : {};
1900
1920
  const message = typeof raw.message === "string" && raw.message.trim() !== "" ? raw.message.trim() : String(cause);
@@ -1952,6 +1972,7 @@ window.__ModuleLoader__.load({
1952
1972
  return () => listeners.delete(listener);
1953
1973
  },
1954
1974
  async open() {
1975
+ loadRoots();
1955
1976
  const wanted = deps.initialPath.trim();
1956
1977
  if (wanted !== "") {
1957
1978
  await load(wanted);
@@ -2064,7 +2085,8 @@ window.__ModuleLoader__.load({
2064
2085
  initialPath: deps.initialPath,
2065
2086
  onPicked: deps.onPicked,
2066
2087
  onClose: () => deps.onClosed?.(),
2067
- ...deps.failureLabel === void 0 ? {} : { failureLabel: deps.failureLabel }
2088
+ ...deps.failureLabel === void 0 ? {} : { failureLabel: deps.failureLabel },
2089
+ ...deps.rootsSource === void 0 ? {} : { rootsSource: deps.rootsSource }
2068
2090
  });
2069
2091
  return {
2070
2092
  open: () => controller.open(),
@@ -2086,18 +2108,22 @@ window.__ModuleLoader__.load({
2086
2108
  };
2087
2109
  }
2088
2110
  //#endregion
2089
- //#region src/directory-browser-ui.ts
2090
- /** 文件浏览器的视图半:把 `BrowseState` 画成一张对话框。**不认识宿主**,也不自己做 IO。
2111
+ //#region src/directory-browser-parts.ts
2112
+ /** 文件浏览器视图半的**零件**:样式表与四行控件(面包屑/根/一行子目录/新建行)。
2091
2113
  *
2092
- * 手写 `React.createElement`(本包 client 束禁 JSX,见 `tsdown.config.ts` 的既成口径)。
2093
- * 只读状态、只回调;进哪一层、选到什么由操作半(`directory-browser-state.ts`)算。
2114
+ * 为什么拆出来:整张对话框的组版与这些零件的变化频率不同——零件跟着「图上多一行/少一行」动,
2115
+ * 组版跟着「对话框长什么样」动。拆开之后两边都在 350 行告警线内,而对外面不变
2116
+ * (`directory-browser-ui.ts` 照旧转出组件与两个接线函数)。
2117
+ *
2118
+ * 本件与视图半同样**不认识宿主**:只认状态与文案。
2094
2119
  */
2095
- const EMPTY_LISTING = {
2096
- path: "",
2097
- home: "",
2098
- crumbs: [],
2099
- entries: [],
2100
- truncated: false
2120
+ /** 根的类型说明(悬停可见):**本件自己的词汇**,与取数方无关。 */
2121
+ const ROOT_KIND_TEXT = {
2122
+ fixed: "固定磁盘",
2123
+ network: "网络驱动器",
2124
+ removable: "可移动磁盘",
2125
+ optical: "光驱",
2126
+ other: "磁盘"
2101
2127
  };
2102
2128
  const S$1 = {
2103
2129
  scrim: {
@@ -2163,6 +2189,24 @@ window.__ModuleLoader__.load({
2163
2189
  opacity: .45,
2164
2190
  fontSize: 12
2165
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
+ },
2166
2210
  pathRow: {
2167
2211
  display: "flex",
2168
2212
  gap: 6,
@@ -2241,6 +2285,7 @@ window.__ModuleLoader__.load({
2241
2285
  },
2242
2286
  error: { color: "var(--dsw-alias-label-error, #e66)" }
2243
2287
  };
2288
+ /** 面包屑:从文件系统根到当前层,每一格都是跳转目标(`listing.crumbs` 由取数方给)。 */
2244
2289
  function crumbRow(state, onEnter) {
2245
2290
  const listing = state.listing;
2246
2291
  if (listing === null || listing.crumbs.length === 0) return null;
@@ -2264,6 +2309,30 @@ window.__ModuleLoader__.load({
2264
2309
  });
2265
2310
  return react.createElement("div", { style: S$1.crumbs }, parts);
2266
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
+ /** 一行子目录:点名字进它,点「选」把它当目标(「选」再点一次取消)。 */
2267
2336
  function entryRow(entry, selected, labels, onToggleSelect, onEnter) {
2268
2337
  return react.createElement("div", {
2269
2338
  key: entry.path,
@@ -2292,6 +2361,7 @@ window.__ModuleLoader__.load({
2292
2361
  onClick: () => onToggleSelect(entry.path)
2293
2362
  }, selected ? "✓" : labels.select));
2294
2363
  }
2364
+ /** 「新建文件夹」那一行(`creatingName === null` 时收起)。 */
2295
2365
  function createRow(labels, onName, onCancel, onCreate, creatingName) {
2296
2366
  if (creatingName === null) return null;
2297
2367
  return react.createElement("div", { style: {
@@ -2317,6 +2387,21 @@ window.__ModuleLoader__.load({
2317
2387
  onClick: onCancel
2318
2388
  }, labels.createCancel));
2319
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
+ };
2320
2405
  /** 画一张文件浏览器对话框。`open=false` 时返回 null(本件不挂 portal,由调用方决定挂在哪)。 */
2321
2406
  function DirectoryBrowser(props) {
2322
2407
  const { open, state, labels } = props;
@@ -2327,6 +2412,7 @@ window.__ModuleLoader__.load({
2327
2412
  const hidden = hiddenCount(listing);
2328
2413
  const body = [];
2329
2414
  body.push(crumbRow(state, props.onEnter));
2415
+ body.push(rootsRow(state, labels, props.onEnter));
2330
2416
  body.push(react.createElement("div", { style: S$1.pathRow }, react.createElement("input", {
2331
2417
  style: S$1.input,
2332
2418
  value: state.draft,
@@ -2421,6 +2507,42 @@ window.__ModuleLoader__.load({
2421
2507
  });
2422
2508
  }
2423
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
2424
2546
  //#region src/client.ts
2425
2547
  /** dsh-life-pack client 适配器(六边形:browser 侧;爱生活单卡方案)。
2426
2548
  *
@@ -2811,10 +2933,12 @@ window.__ModuleLoader__.load({
2811
2933
  exports.canGoUp = canGoUp;
2812
2934
  exports.createBrowseController = createBrowseController;
2813
2935
  exports.createDirectoryRowBrowser = createDirectoryRowBrowser;
2936
+ exports.createRootsSource = createRootsSource;
2814
2937
  exports.entryPath = entryPath;
2815
2938
  exports.inject = inject;
2816
2939
  exports.pickerModeOf = pickerModeOf;
2817
2940
  exports.readPickAnswer = readPickAnswer;
2941
+ exports.readRootsAnswer = readRootsAnswer;
2818
2942
  exports.rowsOf = rowsOf;
2819
2943
  exports.targetOf = targetOf;
2820
2944
  return module.exports;