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.
Files changed (42) hide show
  1. package/dist/client.js +132 -37
  2. package/dist/client.js.map +1 -1
  3. package/dist/directory-browser-api.d.ts +8 -6
  4. package/dist/directory-browser-api.d.ts.map +1 -1
  5. package/dist/directory-browser-api.js +6 -5
  6. package/dist/directory-browser-api.js.map +1 -1
  7. package/dist/directory-browser-contract.d.ts +13 -4
  8. package/dist/directory-browser-contract.d.ts.map +1 -1
  9. package/dist/directory-browser-contract.js +0 -10
  10. package/dist/directory-browser-contract.js.map +1 -1
  11. package/dist/directory-browser-parts.d.ts +65 -0
  12. package/dist/directory-browser-parts.d.ts.map +1 -0
  13. package/dist/directory-browser-parts.js +189 -0
  14. package/dist/directory-browser-parts.js.map +1 -0
  15. package/dist/directory-browser-roots.d.ts +21 -0
  16. package/dist/directory-browser-roots.d.ts.map +1 -0
  17. package/dist/directory-browser-roots.js +40 -0
  18. package/dist/directory-browser-roots.js.map +1 -0
  19. package/dist/directory-browser-state.d.ts +9 -1
  20. package/dist/directory-browser-state.d.ts.map +1 -1
  21. package/dist/directory-browser-state.js +23 -0
  22. package/dist/directory-browser-state.js.map +1 -1
  23. package/dist/directory-browser-ui.d.ts +4 -22
  24. package/dist/directory-browser-ui.d.ts.map +1 -1
  25. package/dist/directory-browser-ui.js +8 -150
  26. package/dist/directory-browser-ui.js.map +1 -1
  27. package/dist/index.d.ts +3 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +2 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/roots.d.ts +34 -0
  32. package/dist/roots.d.ts.map +1 -0
  33. package/dist/roots.js +95 -0
  34. package/dist/roots.js.map +1 -0
  35. package/dist/update-contract.d.ts +7 -0
  36. package/dist/update-contract.d.ts.map +1 -1
  37. package/dist/update-contract.js +7 -0
  38. package/dist/update-contract.js.map +1 -1
  39. package/dist/update-host.d.ts.map +1 -1
  40. package/dist/update-host.js +5 -1
  41. package/dist/update-host.js.map +1 -1
  42. 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-ui.ts
2090
- /** 文件浏览器的视图半:把 `BrowseState` 画成一张对话框。**不认识宿主**,也不自己做 IO。
2102
+ //#region src/directory-browser-parts.ts
2103
+ /** 文件浏览器视图半的**零件**:样式表与四行控件(面包屑/根/一行子目录/新建行)。
2091
2104
  *
2092
- * 手写 `React.createElement`(本包 client 束禁 JSX,见 `tsdown.config.ts` 的既成口径)。
2093
- * 只读状态、只回调;进哪一层、选到什么由操作半(`directory-browser-state.ts`)算。
2105
+ * 为什么拆出来:整张对话框的组版与这些零件的变化频率不同——零件跟着「图上多一行/少一行」动,
2106
+ * 组版跟着「对话框长什么样」动。拆开之后两边都在 350 行告警线内,而对外面不变
2107
+ * (`directory-browser-ui.ts` 照旧转出组件与两个接线函数)。
2108
+ *
2109
+ * 本件与视图半同样**不认识宿主**:只认状态与文案。
2094
2110
  */
2095
- const EMPTY_LISTING = {
2096
- path: "",
2097
- home: "",
2098
- crumbs: [],
2099
- entries: [],
2100
- truncated: false
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 listing = state.listing ?? EMPTY_LISTING;
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("span", {
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: S$1.button,
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;