@linxin666/dsh-remote-web-ui 0.1.14 → 0.1.16
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.i18n.yaml +2 -2
- package/README.md +9 -5
- package/README.zh.md +1 -1
- package/lib/client.js +84 -23
- package/lib/client.js.map +1 -1
- package/lib/index.js +173 -18
- package/lib/mobile.js +44 -25
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts +26 -11
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +30 -8
- package/lib/types/client/UpdatePanel.js +2 -0
- package/lib/types/client/locales.d.ts +4 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +4 -0
- package/lib/types/client/settings-form.d.ts +76 -5
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +97 -20
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +36 -10
- package/lib/types/mobile/mux.d.ts +13 -4
- package/lib/types/mobile/mux.d.ts.map +1 -1
- package/lib/types/mobile/mux.js +50 -30
- package/lib/types/mobile/mux.test.js +75 -6
- package/lib/types/mobile-api.d.ts +2 -0
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +4 -1
- package/lib/types/routes.d.ts +17 -0
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +49 -10
- package/lib/types/update.d.ts +44 -4
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +118 -8
- package/package.json +1 -1
- package/src/client/PluginSettingsCard.tsx +90 -18
- package/src/client/UpdatePanel.tsx +2 -0
- package/src/client/locales.ts +4 -0
- package/src/client/settings-card.module.css +9 -0
- package/src/client/settings-form.ts +153 -19
- package/src/index.ts +34 -10
- package/src/mobile/mux.test.ts +84 -6
- package/src/mobile/mux.ts +46 -25
- package/src/mobile-api.ts +6 -1
- package/src/routes.ts +51 -10
- package/src/update.ts +128 -9
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# node scripts/verify-docs.mjs --write <dir>
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: c1e5ea0deaca90a5a188342db0d38256f5917369
|
|
6
|
+
README.zh.md: 9be2fbeb481ea23f05aa06f732234b2310c3d16a
|
package/README.md
CHANGED
|
@@ -38,12 +38,16 @@ actions, and the update panel that probes and runs the update.
|
|
|
38
38
|
- **Remote update**: the download trigger in the sidebar foot (left of the
|
|
39
39
|
phone icon) opens the update panel, which probes the npm registry for the
|
|
40
40
|
installed `@linxin666/dsh-*` family releases. When a newer release exists
|
|
41
|
-
the panel runs the update automatically (`pnpm update` inside the
|
|
42
|
-
dsh profile; when pnpm is missing it falls back to `corepack pnpm`
|
|
43
|
-
then `npx --yes pnpm`, and on Windows the command runs through
|
|
44
|
-
so npm-installed `.cmd` shims resolve; the loopback-only
|
|
41
|
+
the panel runs the update automatically (`pnpm update --latest` inside the
|
|
42
|
+
owning dsh profile; when pnpm is missing it falls back to `corepack pnpm`
|
|
43
|
+
and then `npx --yes pnpm`, and on Windows the command runs through
|
|
44
|
+
`cmd.exe` so npm-installed `.cmd` shims resolve; the loopback-only
|
|
45
45
|
`/api/update/status` + `/api/update/run` endpoints drive it) and asks for
|
|
46
|
-
a dsh web restart to pick it up.
|
|
46
|
+
a dsh web restart to pick it up. After a green pnpm exit the installed
|
|
47
|
+
versions are re-checked against the registry: a green exit that left every
|
|
48
|
+
version in place (e.g. the pnpm `minimumReleaseAge` gate silently skipping
|
|
49
|
+
same-day releases) is reported as a stale update with configuration
|
|
50
|
+
guidance instead of a false success. Local
|
|
47
51
|
link installs (development mode) are detected and report the npm state
|
|
48
52
|
without updating.
|
|
49
53
|
|
package/README.zh.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
- **手机侧**:扫码将手机与一次性、限时令牌绑定,并落地到 **`/m` 独立移动端界面**——一款专为小屏设计的轻客户端(见[截图](#截图)),而不是把桌面 UI 塞进手机。链接携带 `workspace` 参数,手机落地到桌面正在查看的同一工作区。
|
|
14
14
|
- **安全**:一个有效的一次性令牌(刷新会使旧链接失效;已接受的令牌不可复用;令牌会过期)。停止会撤销每一台已配对设备与当前令牌——已配对设备在下一次请求时被切断。当插件 `requirePairingForLan` 门开启(默认)时,每个非 loopback 的 `/api` 请求必须携带有效的已配对设备 cookie,因此二维码是进入暴露在局域网上的 dsh web 的唯一途径。
|
|
15
15
|
- **实时状态**:桌面面板经 SSE 流实时镜像配对状态(等待 → 已连接 → 已断开)。
|
|
16
|
-
- **远程更新**:侧边栏底部的下载触发按钮(手机图标左侧)打开更新面板,它探测 npm registry 上已安装的 `@linxin666/dsh-*` 全家桶版本。当存在较新版本时,面板自动执行更新(在所属 dsh profile 内 `pnpm update`;pnpm 缺失时依次回退 `corepack pnpm`、`npx --yes pnpm`,Windows 上经 `cmd.exe` 执行以解析 npm 安装的 `.cmd` shim;由仅 loopback 的 `/api/update/status` + `/api/update/run` 端点驱动)并请求重启 dsh web
|
|
16
|
+
- **远程更新**:侧边栏底部的下载触发按钮(手机图标左侧)打开更新面板,它探测 npm registry 上已安装的 `@linxin666/dsh-*` 全家桶版本。当存在较新版本时,面板自动执行更新(在所属 dsh profile 内 `pnpm update --latest`;pnpm 缺失时依次回退 `corepack pnpm`、`npx --yes pnpm`,Windows 上经 `cmd.exe` 执行以解析 npm 安装的 `.cmd` shim;由仅 loopback 的 `/api/update/status` + `/api/update/run` 端点驱动)并请求重启 dsh web 以生效。pnpm 绿色退出后还会对照 registry 复核已装版本:绿色退出但版本纹丝不动(例如 pnpm 的 `minimumReleaseAge` 门禁静默跳过同日发布的新版本)会报告为「未更新成功」并附配置指引,而不是误报成功。本地 link 安装(开发模式)会被探测到,只报告 npm 状态而不更新。
|
|
17
17
|
|
|
18
18
|
## 截图
|
|
19
19
|
|
package/lib/client.js
CHANGED
|
@@ -1877,6 +1877,8 @@ window.__ModuleLoader__.load({
|
|
|
1877
1877
|
case "not-found": return t("update.error.notFound");
|
|
1878
1878
|
case "link": return t("update.error.link");
|
|
1879
1879
|
case "pnpm-failed": return t("update.error.pnpmFailed", { code: String(result.exitCode ?? "?") });
|
|
1880
|
+
case "stale": return t("update.error.stale");
|
|
1881
|
+
case "verify-failed": return t("update.error.verifyFailed");
|
|
1880
1882
|
default: return result.error ?? t("update.error.unknown");
|
|
1881
1883
|
}
|
|
1882
1884
|
}
|
|
@@ -2217,7 +2219,7 @@ window.__ModuleLoader__.load({
|
|
|
2217
2219
|
}
|
|
2218
2220
|
//#endregion
|
|
2219
2221
|
//#region \0dsh-css:packages/dsh-remote-web-ui/src/client/settings-card.module.css.mjs
|
|
2220
|
-
const css = ".Kwoi6G_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Kwoi6G_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Kwoi6G_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Kwoi6G_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Kwoi6G_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Kwoi6G_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.Kwoi6G_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Kwoi6G_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Kwoi6G_chevronOpen{transform:rotate(180deg)}.Kwoi6G_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.Kwoi6G_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.Kwoi6G_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.Kwoi6G_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.Kwoi6G_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.Kwoi6G_discard,.Kwoi6G_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Kwoi6G_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Kwoi6G_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Kwoi6G_discard:disabled,.Kwoi6G_save:disabled{opacity:.4;cursor:default}.Kwoi6G_discard:focus-visible,.Kwoi6G_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Kwoi6G_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.Kwoi6G_field+.Kwoi6G_field{border-top:1px solid var(--dsw-alias-border-l2)}.Kwoi6G_head{align-items:center;gap:8px;display:flex}.Kwoi6G_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.Kwoi6G_badges{align-items:center;gap:8px;display:inline-flex}.Kwoi6G_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Kwoi6G_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.Kwoi6G_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.Kwoi6G_reset:disabled{cursor:default}.Kwoi6G_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.Kwoi6G_input,.Kwoi6G_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Kwoi6G_input:focus-visible,.Kwoi6G_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.Kwoi6G_input:disabled,.Kwoi6G_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.Kwoi6G_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Kwoi6G_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.Kwoi6G_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.Kwoi6G_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.Kwoi6G_card,.Kwoi6G_header,.Kwoi6G_chevron,.Kwoi6G_chevronOpen,.Kwoi6G_discard,.Kwoi6G_save{transition:none}}";
|
|
2222
|
+
const css = ".Kwoi6G_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Kwoi6G_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Kwoi6G_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Kwoi6G_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Kwoi6G_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Kwoi6G_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.Kwoi6G_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Kwoi6G_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Kwoi6G_chevronOpen{transform:rotate(180deg)}.Kwoi6G_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.Kwoi6G_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.Kwoi6G_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.Kwoi6G_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.Kwoi6G_failed{min-width:0;color:var(--dsw-alias-label-error);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:12px;line-height:1.5;overflow:hidden}.Kwoi6G_discard,.Kwoi6G_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Kwoi6G_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Kwoi6G_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Kwoi6G_discard:disabled,.Kwoi6G_save:disabled{opacity:.4;cursor:default}.Kwoi6G_discard:focus-visible,.Kwoi6G_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Kwoi6G_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.Kwoi6G_field+.Kwoi6G_field{border-top:1px solid var(--dsw-alias-border-l2)}.Kwoi6G_head{align-items:center;gap:8px;display:flex}.Kwoi6G_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.Kwoi6G_badges{align-items:center;gap:8px;display:inline-flex}.Kwoi6G_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Kwoi6G_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.Kwoi6G_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.Kwoi6G_reset:disabled{cursor:default}.Kwoi6G_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.Kwoi6G_input,.Kwoi6G_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Kwoi6G_input:focus-visible,.Kwoi6G_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.Kwoi6G_input:disabled,.Kwoi6G_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.Kwoi6G_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Kwoi6G_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.Kwoi6G_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.Kwoi6G_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.Kwoi6G_card,.Kwoi6G_header,.Kwoi6G_chevron,.Kwoi6G_chevronOpen,.Kwoi6G_discard,.Kwoi6G_save{transition:none}}";
|
|
2221
2223
|
const tagId = "@linxin666/dsh-remote-web-ui/settings-card.module.css";
|
|
2222
2224
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
2223
2225
|
const tag = document.createElement("style");
|
|
@@ -2258,12 +2260,12 @@ window.__ModuleLoader__.load({
|
|
|
2258
2260
|
//#endregion
|
|
2259
2261
|
//#region src/client/PluginSettingsCard.tsx
|
|
2260
2262
|
/**
|
|
2261
|
-
*
|
|
2262
|
-
* plugin and what its settings govern, the controls inside, and the save
|
|
2263
|
-
* writes them. Renders nothing while the namespace is unavailable — a
|
|
2263
|
+
* Family-shared chrome for plugin settings cards: a disclosure header naming
|
|
2264
|
+
* the plugin and what its settings govern, the controls inside, and the save
|
|
2265
|
+
* that writes them. Renders nothing while the namespace is unavailable — a
|
|
2264
2266
|
* deployment that does not compose the owning plugin should show no trace of
|
|
2265
|
-
* it.
|
|
2266
|
-
*
|
|
2267
|
+
* it. Inlined into each consumer's client bundle; mirrors the official
|
|
2268
|
+
* ui-plugin-config PluginCard in a self-contained slice.
|
|
2267
2269
|
*/
|
|
2268
2270
|
/**
|
|
2269
2271
|
* Render one plugin settings card.
|
|
@@ -2275,6 +2277,7 @@ window.__ModuleLoader__.load({
|
|
|
2275
2277
|
const { state } = props;
|
|
2276
2278
|
if (!state.available) return null;
|
|
2277
2279
|
const title = props.t(props.titleKey);
|
|
2280
|
+
const description = props.t(props.descriptionKey);
|
|
2278
2281
|
const blocked = !state.dirty || state.invalid || state.saving;
|
|
2279
2282
|
const cardClass = open ? `${settings_card_module_css_default.cardOpen} ${settings_card_module_css_default.card}` : settings_card_module_css_default.card;
|
|
2280
2283
|
if (!state.exposed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
@@ -2291,10 +2294,12 @@ window.__ModuleLoader__.load({
|
|
|
2291
2294
|
className: settings_card_module_css_default.headText,
|
|
2292
2295
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2293
2296
|
className: settings_card_module_css_default.name,
|
|
2297
|
+
title,
|
|
2294
2298
|
children: title
|
|
2295
2299
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2296
2300
|
className: settings_card_module_css_default.description,
|
|
2297
|
-
|
|
2301
|
+
title: description,
|
|
2302
|
+
children: description
|
|
2298
2303
|
})]
|
|
2299
2304
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2300
2305
|
width: "14",
|
|
@@ -2332,14 +2337,17 @@ window.__ModuleLoader__.load({
|
|
|
2332
2337
|
className: settings_card_module_css_default.headText,
|
|
2333
2338
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2334
2339
|
className: settings_card_module_css_default.name,
|
|
2340
|
+
title,
|
|
2335
2341
|
children: title
|
|
2336
2342
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2337
2343
|
className: settings_card_module_css_default.description,
|
|
2338
|
-
|
|
2344
|
+
title: description,
|
|
2345
|
+
children: description
|
|
2339
2346
|
})]
|
|
2340
2347
|
}),
|
|
2341
2348
|
state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2342
2349
|
className: settings_card_module_css_default.pending,
|
|
2350
|
+
title: props.t("settings.unsaved"),
|
|
2343
2351
|
children: props.t("settings.unsaved")
|
|
2344
2352
|
}) : null,
|
|
2345
2353
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
@@ -2367,10 +2375,10 @@ window.__ModuleLoader__.load({
|
|
|
2367
2375
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2368
2376
|
className: settings_card_module_css_default.footer,
|
|
2369
2377
|
children: [
|
|
2370
|
-
state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
2378
|
+
state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
2371
2379
|
className: settings_card_module_css_default.failed,
|
|
2372
2380
|
role: "status",
|
|
2373
|
-
children: props.t("settings.saveFailed")
|
|
2381
|
+
children: [props.t("settings.saveFailed"), state.failedReason ? " - " + state.failedReason : ""]
|
|
2374
2382
|
}) : null,
|
|
2375
2383
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2376
2384
|
type: "button",
|
|
@@ -2494,8 +2502,9 @@ window.__ModuleLoader__.load({
|
|
|
2494
2502
|
}
|
|
2495
2503
|
//#endregion
|
|
2496
2504
|
//#region src/client/settings-form.ts
|
|
2497
|
-
/** A whole-number field. An empty draft clears the field; any other draft that is not a finite number blocks the save. */
|
|
2498
|
-
function numberField(field) {
|
|
2505
|
+
/** A whole- or decimal-number field. An empty draft clears the field; any other draft that is not a finite number within the constraints blocks the save. */
|
|
2506
|
+
function numberField(field, constraints = {}) {
|
|
2507
|
+
const { integer = false, min } = constraints;
|
|
2499
2508
|
return {
|
|
2500
2509
|
field,
|
|
2501
2510
|
format: (value) => typeof value === "number" ? String(value) : "",
|
|
@@ -2503,10 +2512,13 @@ window.__ModuleLoader__.load({
|
|
|
2503
2512
|
const trimmed = text.trim();
|
|
2504
2513
|
if (trimmed === "") return { kind: "clear" };
|
|
2505
2514
|
const parsed = Number(trimmed);
|
|
2506
|
-
|
|
2515
|
+
if (!Number.isFinite(parsed)) return void 0;
|
|
2516
|
+
if (integer && !Number.isInteger(parsed)) return void 0;
|
|
2517
|
+
if (min !== void 0 && parsed < min) return void 0;
|
|
2518
|
+
return {
|
|
2507
2519
|
kind: "set",
|
|
2508
2520
|
value: parsed
|
|
2509
|
-
}
|
|
2521
|
+
};
|
|
2510
2522
|
}
|
|
2511
2523
|
};
|
|
2512
2524
|
}
|
|
@@ -2530,11 +2542,13 @@ window.__ModuleLoader__.load({
|
|
|
2530
2542
|
field,
|
|
2531
2543
|
format: (value) => typeof value === "boolean" ? String(value) : "",
|
|
2532
2544
|
parse: (text) => {
|
|
2533
|
-
|
|
2545
|
+
const trimmed = text.trim();
|
|
2546
|
+
if (trimmed === "") return { kind: "clear" };
|
|
2547
|
+
if (trimmed === "true") return {
|
|
2534
2548
|
kind: "set",
|
|
2535
2549
|
value: true
|
|
2536
2550
|
};
|
|
2537
|
-
if (
|
|
2551
|
+
if (trimmed === "false") return {
|
|
2538
2552
|
kind: "set",
|
|
2539
2553
|
value: false
|
|
2540
2554
|
};
|
|
@@ -2556,6 +2570,7 @@ window.__ModuleLoader__.load({
|
|
|
2556
2570
|
listeners = /* @__PURE__ */ new Set();
|
|
2557
2571
|
saving = false;
|
|
2558
2572
|
failed = false;
|
|
2573
|
+
failedReason;
|
|
2559
2574
|
/** @param scope - the bound settings scope for this card's namespace. */
|
|
2560
2575
|
constructor(scope, specs) {
|
|
2561
2576
|
this.scope = scope;
|
|
@@ -2583,7 +2598,8 @@ window.__ModuleLoader__.load({
|
|
|
2583
2598
|
dirty: plan.length > 0,
|
|
2584
2599
|
invalid: plan.some((item) => item.run === void 0),
|
|
2585
2600
|
saving: this.saving,
|
|
2586
|
-
failed: this.failed
|
|
2601
|
+
failed: this.failed,
|
|
2602
|
+
...this.failedReason === void 0 ? {} : { failedReason: this.failedReason }
|
|
2587
2603
|
};
|
|
2588
2604
|
}
|
|
2589
2605
|
/** Read one field's state from the effective section and its staged draft. */
|
|
@@ -2624,28 +2640,50 @@ window.__ModuleLoader__.load({
|
|
|
2624
2640
|
if (this.staged.size === 0 && !this.failed) return;
|
|
2625
2641
|
this.staged.clear();
|
|
2626
2642
|
this.failed = false;
|
|
2643
|
+
this.failedReason = void 0;
|
|
2627
2644
|
this.publish();
|
|
2628
2645
|
}
|
|
2629
2646
|
};
|
|
2630
2647
|
}
|
|
2631
2648
|
/**
|
|
2632
2649
|
* Write every staged edit, then re-seed from what the Host accepted.
|
|
2650
|
+
*
|
|
2651
|
+
* When the scope carries the optional batch surface (the dsh-web-ui
|
|
2652
|
+
* bridge scope), every planned write rides one mutation so cross-field
|
|
2653
|
+
* validate hooks (baseURL+model) judge the batch as a unit instead of
|
|
2654
|
+
* deadlocking on per-field writes. Otherwise the per-field loop runs.
|
|
2655
|
+
* A field lands only when the Host reports it held the staged value; a
|
|
2656
|
+
* landed field's draft is dropped, a failed one stays staged for the user.
|
|
2633
2657
|
* @returns settlement after every write and the read-back.
|
|
2634
2658
|
*/
|
|
2635
2659
|
async save() {
|
|
2636
2660
|
const plan = this.plan();
|
|
2637
|
-
const
|
|
2638
|
-
if (plan.length === 0 || this.saving ||
|
|
2661
|
+
const valid = plan.filter((item) => item.run !== void 0);
|
|
2662
|
+
if (plan.length === 0 || this.saving || valid.length !== plan.length) return;
|
|
2663
|
+
const plannedWrites = valid.map((item) => item.op);
|
|
2664
|
+
const fields = new Set(plan.map((item) => item.field));
|
|
2639
2665
|
this.saving = true;
|
|
2640
2666
|
this.failed = false;
|
|
2667
|
+
this.failedReason = void 0;
|
|
2641
2668
|
this.publish();
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
if (
|
|
2669
|
+
const landed = /* @__PURE__ */ new Set();
|
|
2670
|
+
const batch = this.batchedScope();
|
|
2671
|
+
if (batch !== void 0) {
|
|
2672
|
+
const result = await batch.mutate(plannedWrites);
|
|
2673
|
+
if (result.ok) {
|
|
2674
|
+
for (const field of result.fields) if (field.landed) landed.add(field.field);
|
|
2675
|
+
} else this.failedReason = result.message;
|
|
2676
|
+
} else for (const item of valid) if (await item.run()) landed.add(item.field);
|
|
2677
|
+
for (const field of fields) if (landed.has(field)) this.staged.delete(field);
|
|
2645
2678
|
this.saving = false;
|
|
2646
|
-
this.failed =
|
|
2679
|
+
this.failed = landed.size !== fields.size;
|
|
2647
2680
|
this.publish();
|
|
2648
2681
|
}
|
|
2682
|
+
/** The scope's batch surface when it supports one; undefined conservatively otherwise. */
|
|
2683
|
+
batchedScope() {
|
|
2684
|
+
const candidate = this.scope;
|
|
2685
|
+
return typeof candidate?.mutate === "function" ? candidate : void 0;
|
|
2686
|
+
}
|
|
2649
2687
|
/**
|
|
2650
2688
|
* Every staged edit a save would write. An entry whose draft is not a value
|
|
2651
2689
|
* its field accepts carries no write: the form is still dirty, and the save
|
|
@@ -2660,6 +2698,10 @@ window.__ModuleLoader__.load({
|
|
|
2660
2698
|
if (staged.clear) {
|
|
2661
2699
|
if (this.stored(field)) plan.push({
|
|
2662
2700
|
field,
|
|
2701
|
+
op: {
|
|
2702
|
+
field,
|
|
2703
|
+
op: "unset"
|
|
2704
|
+
},
|
|
2663
2705
|
run: () => this.clear(field)
|
|
2664
2706
|
});
|
|
2665
2707
|
continue;
|
|
@@ -2668,14 +2710,27 @@ window.__ModuleLoader__.load({
|
|
|
2668
2710
|
const write = spec.parse(staged.text);
|
|
2669
2711
|
if (write === void 0) plan.push({
|
|
2670
2712
|
field,
|
|
2713
|
+
op: {
|
|
2714
|
+
field,
|
|
2715
|
+
op: "unset"
|
|
2716
|
+
},
|
|
2671
2717
|
run: void 0
|
|
2672
2718
|
});
|
|
2673
2719
|
else if (write.kind === "clear") plan.push({
|
|
2674
2720
|
field,
|
|
2721
|
+
op: {
|
|
2722
|
+
field,
|
|
2723
|
+
op: "unset"
|
|
2724
|
+
},
|
|
2675
2725
|
run: () => this.clear(field)
|
|
2676
2726
|
});
|
|
2677
2727
|
else plan.push({
|
|
2678
2728
|
field,
|
|
2729
|
+
op: {
|
|
2730
|
+
field,
|
|
2731
|
+
op: "set",
|
|
2732
|
+
value: write.value
|
|
2733
|
+
},
|
|
2679
2734
|
run: () => this.store(field, write.value)
|
|
2680
2735
|
});
|
|
2681
2736
|
}
|
|
@@ -2687,11 +2742,13 @@ window.__ModuleLoader__.load({
|
|
|
2687
2742
|
}
|
|
2688
2743
|
async store(field, value) {
|
|
2689
2744
|
await this.scope.set(field, value);
|
|
2745
|
+
if (this.specOf(field).secret) return true;
|
|
2690
2746
|
return this.userLayer()?.[field] === value;
|
|
2691
2747
|
}
|
|
2692
2748
|
stage(field, edit) {
|
|
2693
2749
|
this.staged.set(field, edit);
|
|
2694
2750
|
this.failed = false;
|
|
2751
|
+
this.failedReason = void 0;
|
|
2695
2752
|
this.publish();
|
|
2696
2753
|
}
|
|
2697
2754
|
specOf(field) {
|
|
@@ -3027,6 +3084,8 @@ window.__ModuleLoader__.load({
|
|
|
3027
3084
|
"update.error.notFound": "未找到 dsh-web-ui 聚合包安装,无法更新。",
|
|
3028
3085
|
"update.error.link": "当前为本地开发模式(链接安装),无法自动更新。",
|
|
3029
3086
|
"update.error.pnpmFailed": "更新执行失败(pnpm 退出码 {code}),详见下方输出。",
|
|
3087
|
+
"update.error.stale": "更新命令已执行,但安装版本未变化。常见原因:pnpm 11 的 minimumReleaseAge 门禁会静默跳过发布不足 24 小时的新版本。请在 profile 目录(~/.dsh/profiles/<profile>)的 pnpm-workspace.yaml 中配置 minimumReleaseAgeExclude(例如 \"@linxin666/*\")或 minimumReleaseAge: 0 后重新点击更新。",
|
|
3088
|
+
"update.error.verifyFailed": "更新命令已执行,但无法核对安装后的版本(可能是 registry 探测失败或安装路径变化)。请查看下方输出,或稍后重试。",
|
|
3030
3089
|
"update.error.unknown": "更新失败,请重试。"
|
|
3031
3090
|
};
|
|
3032
3091
|
/** English dictionary, checked complete against the zh key set. */
|
|
@@ -3129,6 +3188,8 @@ window.__ModuleLoader__.load({
|
|
|
3129
3188
|
"update.error.notFound": "The dsh-web-ui aggregate package is not installed; nothing to update.",
|
|
3130
3189
|
"update.error.link": "Local development mode (link install) — automatic update is unavailable.",
|
|
3131
3190
|
"update.error.pnpmFailed": "The update failed (pnpm exited with code {code}); see the output below.",
|
|
3191
|
+
"update.error.stale": "The update command ran but the installed versions did not change. Likely cause: the pnpm 11 minimumReleaseAge gate silently skips releases published less than 24 hours ago. Add minimumReleaseAgeExclude (e.g. \"@linxin666/*\") or set minimumReleaseAge: 0 in pnpm-workspace.yaml under the profile directory (~/.dsh/profiles/<profile>), then run the update again.",
|
|
3192
|
+
"update.error.verifyFailed": "The update command ran but the installed versions could not be verified afterwards (registry probe failed or the install path changed). Inspect the output below, or retry later.",
|
|
3132
3193
|
"update.error.unknown": "Update failed; try again."
|
|
3133
3194
|
};
|
|
3134
3195
|
//#endregion
|