@linxin666/dsh-remote-web-ui 0.2.0 → 0.2.2
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 +17 -8
- package/README.zh.md +5 -5
- package/lib/client.js +259 -43
- package/lib/client.js.map +1 -1
- package/lib/index.js +26 -7
- package/lib/mobile.js +209 -39
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts +22 -0
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +142 -3
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +11 -0
- package/lib/types/client/RemoteSettingsCard.d.ts +2 -2
- package/lib/types/client/UpdateEntry.d.ts.map +1 -1
- package/lib/types/client/UpdateEntry.js +8 -5
- package/lib/types/client/UpdatePanel.d.ts +3 -1
- package/lib/types/client/UpdatePanel.d.ts.map +1 -1
- package/lib/types/client/UpdatePanel.js +3 -3
- 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 +8 -4
- package/lib/types/client/pair-api.d.ts +1 -6
- package/lib/types/client/pair-api.d.ts.map +1 -1
- package/lib/types/client/pair-api.js +1 -10
- package/lib/types/gate.d.ts.map +1 -1
- package/lib/types/gate.js +2 -0
- package/lib/types/mobile/api.d.ts +10 -0
- package/lib/types/mobile/api.d.ts.map +1 -1
- package/lib/types/mobile/api.js +4 -0
- package/lib/types/mobile/messages.d.ts +24 -0
- package/lib/types/mobile/messages.d.ts.map +1 -1
- package/lib/types/mobile/messages.js +60 -23
- package/lib/types/mobile/messages.test.js +49 -1
- package/lib/types/mobile/mobile-styles.d.ts +1 -1
- package/lib/types/mobile/mobile-styles.d.ts.map +1 -1
- package/lib/types/mobile/mobile-styles.js +38 -0
- package/lib/types/mobile/views/ChatView.d.ts.map +1 -1
- package/lib/types/mobile/views/ChatView.js +34 -11
- package/lib/types/mobile/views/SessionListView.d.ts.map +1 -1
- package/lib/types/mobile/views/SessionListView.js +30 -4
- package/lib/types/mobile/views/SessionListView.test.js +58 -1
- package/lib/types/mobile/views/WorkspaceView.d.ts.map +1 -1
- package/lib/types/mobile/views/WorkspaceView.js +4 -2
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +10 -1
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +22 -2
- package/lib/types/tunnel.d.ts +1 -0
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/tunnel.js +7 -2
- package/lib/types/update.d.ts +1 -1
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +1 -1
- package/package.json +1 -2
- package/src/client/PluginSettingsCard.tsx +219 -18
- package/src/client/RemoteEntry.tsx +10 -0
- package/src/client/RemoteSettingsCard.tsx +2 -2
- package/src/client/UpdateEntry.tsx +9 -5
- package/src/client/UpdatePanel.tsx +12 -1
- package/src/client/locales.ts +8 -4
- package/src/client/pair-api.ts +1 -10
- package/src/client/settings-card.module.css +90 -1
- package/src/gate.ts +2 -0
- package/src/mobile/api.ts +14 -1
- package/src/mobile/messages.test.ts +52 -1
- package/src/mobile/messages.ts +65 -22
- package/src/mobile/mobile-styles.ts +38 -0
- package/src/mobile/mobile-theme.test.ts +1 -1
- package/src/mobile/views/ChatView.tsx +33 -10
- package/src/mobile/views/SessionListView.test.tsx +69 -1
- package/src/mobile/views/SessionListView.tsx +58 -5
- package/src/mobile/views/WorkspaceView.tsx +4 -2
- package/src/mobile-api.ts +9 -1
- package/src/routes.ts +22 -2
- package/src/tunnel.ts +7 -2
- package/src/update.ts +2 -2
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: f93e37eae9e0b0293d9f78b590dff7a237b72ad6
|
|
6
|
+
README.zh.md: cfea9d0a66185db3af570255a349d01f82fd5efb
|
package/README.md
CHANGED
|
@@ -45,7 +45,8 @@ actions, and the update panel that probes and runs the update.
|
|
|
45
45
|
`@linxin666/dsh-*` family releases. Without the aggregate package, checks and
|
|
46
46
|
updates cover every registry-managed direct `@linxin666/*` dependency in the
|
|
47
47
|
profile; local link/file development dependencies are skipped. When a newer
|
|
48
|
-
release exists, the panel
|
|
48
|
+
release exists, the panel shows the version comparison and waits for
|
|
49
|
+
confirmation — clicking "Update now" runs the update (`pnpm update --latest` inside the
|
|
49
50
|
owning dsh profile; when pnpm is missing it falls back to `corepack pnpm`
|
|
50
51
|
and then `npx --yes pnpm`, and on Windows the command runs through
|
|
51
52
|
`cmd.exe` so npm-installed `.cmd` shims resolve; the loopback-only
|
|
@@ -103,7 +104,7 @@ Install the family aggregate package `@linxin666/dsh-web-ui-all` (all plugins an
|
|
|
103
104
|
|
|
104
105
|
```sh
|
|
105
106
|
# Recommended: install directly from npm
|
|
106
|
-
dsh plugin --profile web add @linxin666/dsh-remote-web-ui
|
|
107
|
+
dsh plugin --profile web add @linxin666/dsh-remote-web-ui@latest
|
|
107
108
|
|
|
108
109
|
# Or from the repository (development loop)
|
|
109
110
|
git clone https://github.com/zhu1090093659/dsh-web-ui.git
|
|
@@ -130,9 +131,11 @@ mounts both halves.
|
|
|
130
131
|
3. Scan with the phone (or open the copied link): the phone binds and
|
|
131
132
|
lands on the **standalone mobile surface at `/m/`** — no desktop UI on a
|
|
132
133
|
small screen. The surface is deliberately thin:
|
|
133
|
-
- workspaces straight away (
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
- workspaces straight away (each workspace's session list offers an Agent
|
|
135
|
+
mode picker before 新建会话: it selects the usable default preset, or the
|
|
136
|
+
first usable preset, and passes that roster id with the workspace id to
|
|
137
|
+
the host's `session.create`; an empty or unavailable roster keeps the
|
|
138
|
+
host-default creation flow),
|
|
136
139
|
- one workspace's sessions load **incrementally** (20 rows per page,
|
|
137
140
|
"加载更多会话" continues; never the whole list at once),
|
|
138
141
|
- opening a session fetches its chat content **on demand** (history
|
|
@@ -172,9 +175,10 @@ pages `session.list` itself), so the tunneled Host never has to enter the
|
|
|
172
175
|
connection plugin's trust fence. The phone is gated by its paired-device
|
|
173
176
|
cookie and an explicit method allowlist (settings/credentials/host-action
|
|
174
177
|
domains are never reachable from the phone; model reads/writes are limited
|
|
175
|
-
to the advisory `session.models` / `session.selectModel` pair,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
+
to the advisory `session.models` / `session.selectModel` pair, agent preset
|
|
179
|
+
access to read-only `agentPreset.list`, creation to `session.create`
|
|
180
|
+
(workspace id plus an optional id from that roster — the phone never names a
|
|
181
|
+
working directory of its own), and the permission picker only ever sends the
|
|
178
182
|
mode-agnostic `/permission` command
|
|
179
183
|
through the already-allowlisted `session.prompt`); the live stream arrives
|
|
180
184
|
over Server-Sent Events on `/m/api/events.mux`. The canonical `/m/` page owns a same-scope manifest and Service Worker; its cache is limited to the static shell and an offline page, never mobile API responses, session data, or commands.
|
|
@@ -242,6 +246,11 @@ pairing fence trusts the tunneled host. Two knobs are involved:
|
|
|
242
246
|
card): the public origin, e.g. `https://foo.trycloudflare.com`. The QR
|
|
243
247
|
link is built from it, and `accept`/`heartbeat`/`status` accept its host.
|
|
244
248
|
Malformed values are ignored with a warning (LAN-only behavior kept).
|
|
249
|
+
Unpaired callers of `status` see only the pairing-relevant fields (phase /
|
|
250
|
+
LAN bases); token expiry, device roster, and the tunnel URL require a live
|
|
251
|
+
device cookie. The accept rate limit partitions its buckets by the
|
|
252
|
+
client-visible `X-Forwarded-For` hop so one internet client cannot
|
|
253
|
+
exhaust the shared bucket behind the tunnel.
|
|
245
254
|
- **`--trusted-host <authority>`** (dsh web flag): the transport-level
|
|
246
255
|
`/api` fence of the connection plugin must accept the public host too —
|
|
247
256
|
without it every `/api` request through the tunnel 403s *before* the
|
package/README.zh.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
- **手机侧**:扫码将手机与一次性、限时令牌绑定,并落地到 **`/m/` 独立移动端界面**——一款专为小屏设计的轻客户端(见[截图](#截图)),而不是把桌面 UI 塞进手机。该页面可安装为 PWA;每个已安装应用使用自己的已配对设备 cookie,存储隔离的移动端 Web App 需要在该上下文打开新的二维码链接,或粘贴桌面端新复制的链接完成配对。链接携带 `workspace` 参数,手机落地到桌面正在查看的同一工作区。
|
|
14
14
|
- **安全**:一个有效的一次性令牌(刷新会使旧链接失效;已接受的令牌不可复用;令牌会过期)。停止会撤销每一台已配对设备与当前令牌——已配对设备在下一次请求时被切断。当插件 `requirePairingForLan` 门开启(默认)时,每个非 loopback 的 `/api` 请求必须携带有效的已配对设备 cookie,因此二维码是进入暴露在局域网上的 dsh web 的唯一途径。
|
|
15
15
|
- **实时状态**:桌面面板经 SSE 流实时镜像配对状态(等待 → 已连接 → 已断开)。
|
|
16
|
-
- **远程更新**:侧边栏加载后,底部的下载触发按钮(手机图标左侧)会静默探测 npm registry 上已安装的 `@linxin666/dsh-*` 全家桶版本;发现可自动更新的新版本时,按钮显示圆点并提供“发现新版本,检查更新”提示。点击按钮打开更新面板;未安装聚合包时,检查和更新覆盖 profile 中 registry 管理的全部 `@linxin666/*` 直接依赖,本地 link / file
|
|
16
|
+
- **远程更新**:侧边栏加载后,底部的下载触发按钮(手机图标左侧)会静默探测 npm registry 上已安装的 `@linxin666/dsh-*` 全家桶版本;发现可自动更新的新版本时,按钮显示圆点并提供“发现新版本,检查更新”提示。点击按钮打开更新面板;未安装聚合包时,检查和更新覆盖 profile 中 registry 管理的全部 `@linxin666/*` 直接依赖,本地 link / file 开发依赖会被跳过。当存在较新版本时,面板展示版本对比并等待确认——点击「开始更新」后执行更新(在所属 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
|
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
```sh
|
|
39
39
|
### 从 npm 安装(推荐)
|
|
40
|
-
dsh plugin --profile web add @linxin666/dsh-remote-web-ui
|
|
40
|
+
dsh plugin --profile web add @linxin666/dsh-remote-web-ui@latest
|
|
41
41
|
|
|
42
42
|
### 从仓库安装(开发调试)
|
|
43
43
|
git clone https://github.com/zhu1090093659/dsh-web-ui.git
|
|
@@ -56,7 +56,7 @@ dsh plugin --profile web add link:$(pwd)/packages/dsh-remote-web-ui
|
|
|
56
56
|
1. `dsh web --host 0.0.0.0`(打印的局域网 URL 确认可达性)。
|
|
57
57
|
2. 点击手机图标 → 面板铸一枚新的二维码。
|
|
58
58
|
3. 用手机扫码(或打开复制的链接):手机绑定并落到 **`/m/` 独立移动端界面**——不在小屏显示桌面 UI。该界面刻意精简:
|
|
59
|
-
-
|
|
59
|
+
- 直接进入工作区(每个工作区的会话列表在 新建会话 前提供 Agent 模式选择器:默认选择可用的默认预设,否则选择第一个可用预设,并把该清单 id 与工作区 id 一起传给 host 的 `session.create`;清单为空或不可用时保留 host 默认创建流程),
|
|
60
60
|
- 一个工作区的会话**增量**加载(每页 20 行,"加载更多会话"继续;绝不同时加载整份列表),
|
|
61
61
|
- 打开会话**按需**抓取聊天内容(历史分页,"加载更早的消息"继续往回翻),
|
|
62
62
|
- 实时流随消息到达展示新消息,带发送自己消息的输入框(默认 **Enter 发送、Shift+Enter 换行**;设 `mobileEnterToSend: false` 后 Enter 改为换行,发送仅走「发送」按钮),
|
|
@@ -67,7 +67,7 @@ dsh plugin --profile web add link:$(pwd)/packages/dsh-remote-web-ui
|
|
|
67
67
|
5. 桌面徽标实时翻到 已连接;手机离开时回落到离线/断开。
|
|
68
68
|
6. 刷新二维码 使旧链接失效并铸一枚新的。停止 撤销移动端访问:已配对设备下一次请求 403,包括其实时流。
|
|
69
69
|
|
|
70
|
-
该移动端界面完全自包含在本插件内:`/m/` 页面及其数据通道(`/m/api`)由插件自己的路由伺服,**无需任何 harness 源码改动**——手机的 RPC 调用走插件的 `/m/api` 代理(它委托给 host 的 ApiProxy 服务并自己分页 `session.list`),因此被隧道化的 Host 永远不必进入连接插件的信任围栏。手机受其已配对设备 cookie 与显式方法白名单门控(settings/credentials/host-action 域手机永远不可达;模型读写限制于建议性的 `session.models` / `session.selectModel`
|
|
70
|
+
该移动端界面完全自包含在本插件内:`/m/` 页面及其数据通道(`/m/api`)由插件自己的路由伺服,**无需任何 harness 源码改动**——手机的 RPC 调用走插件的 `/m/api` 代理(它委托给 host 的 ApiProxy 服务并自己分页 `session.list`),因此被隧道化的 Host 永远不必进入连接插件的信任围栏。手机受其已配对设备 cookie 与显式方法白名单门控(settings/credentials/host-action 域手机永远不可达;模型读写限制于建议性的 `session.models` / `session.selectModel` 对,Agent 预设访问限制于只读 `agentPreset.list`,创建限制于 `session.create`(工作区 id 加清单中的可选 id——手机绝不自命名工作目录),权限选择器只通过已放行的 `session.prompt` 发送模式无关的 `/permission` 命令);实时流在 `/m/api/events.mux` 上经 Server-Sent Events 送达。规范的 `/m/` 页面拥有同 scope 的 manifest 与 Service Worker;其缓存仅包含静态壳和离线页,绝不包含移动端 API 响应、会话数据或命令。
|
|
71
71
|
|
|
72
72
|
### 行为说明
|
|
73
73
|
|
|
@@ -92,7 +92,7 @@ dsh plugin --profile web add link:$(pwd)/packages/dsh-remote-web-ui
|
|
|
92
92
|
|
|
93
93
|
二维码链接通常是局域网 URL,所以家外的手机无法使用。把隧道指向 dsh web 端口,并告知插件其公网地址——二维码随后由隧道 URL 构建,面向手机的配对围栏信任隧道化的主机。涉及两个钮:
|
|
94
94
|
|
|
95
|
-
- **`publicBaseUrl`**(插件配置,在 profile patch 或设置卡片里):公网 origin,如 `https://foo.trycloudflare.com`。二维码链接由它构建,`accept`/`heartbeat`/`status`
|
|
95
|
+
- **`publicBaseUrl`**(插件配置,在 profile patch 或设置卡片里):公网 origin,如 `https://foo.trycloudflare.com`。二维码链接由它构建,`accept`/`heartbeat`/`status` 接受它的主机。畸形值被忽略并告警(保持仅局域网行为)。未配对调用方的 `status` 只看到配对相关字段(phase / 局域网地址);token 过期时间、设备列表与隧道 URL 需要有效设备 cookie。accept 限速按客户端可见的 `X-Forwarded-For` 跳点分桶,避免隧道背后的单个来源耗尽共享桶。
|
|
96
96
|
- **`--trusted-host <authority>`**(dsh web flag):连接插件传输层 `/api` 围栏也必须接受公网主机——否则经隧道的每个 `/api` 请求在**配对层之前**就 403(插件自己的围栏只覆盖 `/api/pair` 路由)。请像隧道转发那样精确传入公网主机(或 `host:port`)。
|
|
97
97
|
|
|
98
98
|
### Cloudflare 隧道(quick tunnel——无账号、无域名)
|
package/lib/client.js
CHANGED
|
@@ -1312,6 +1312,7 @@ window.__ModuleLoader__.load({
|
|
|
1312
1312
|
...workspace !== null && workspace !== "" ? { workspace } : {}
|
|
1313
1313
|
};
|
|
1314
1314
|
}
|
|
1315
|
+
/** Human-readable expiry clock, e.g. "10:35". */
|
|
1315
1316
|
function formatClock(epochMs) {
|
|
1316
1317
|
const date = new Date(epochMs);
|
|
1317
1318
|
return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
@@ -1703,7 +1704,7 @@ window.__ModuleLoader__.load({
|
|
|
1703
1704
|
* @param props - copy, view state, and actions.
|
|
1704
1705
|
* @returns the panel element tree.
|
|
1705
1706
|
*/
|
|
1706
|
-
function UpdatePanel({ t, view, onClose, onRecheck }) {
|
|
1707
|
+
function UpdatePanel({ t, view, onClose, onRecheck, onStartUpdate }) {
|
|
1707
1708
|
const status = view.kind === "result" || view.kind === "updating" ? view.status : void 0;
|
|
1708
1709
|
const title = view.kind === "done" && view.result.ok ? t("update.done") : t("update.title");
|
|
1709
1710
|
const subtitle = subtitleOf(t, view);
|
|
@@ -1740,13 +1741,29 @@ window.__ModuleLoader__.load({
|
|
|
1740
1741
|
t,
|
|
1741
1742
|
status
|
|
1742
1743
|
}),
|
|
1743
|
-
view.kind === "
|
|
1744
|
-
className: remote_module_css_default.
|
|
1745
|
-
children:
|
|
1746
|
-
|
|
1747
|
-
|
|
1744
|
+
view.kind === "result" && status !== void 0 && status.mode === "npm" && status.outdated && status.error === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1745
|
+
className: remote_module_css_default.updateActions,
|
|
1746
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1747
|
+
type: "button",
|
|
1748
|
+
className: remote_module_css_default.updateRetry,
|
|
1749
|
+
onClick: () => onStartUpdate(status),
|
|
1750
|
+
children: t("update.start")
|
|
1748
1751
|
})
|
|
1749
|
-
}),
|
|
1752
|
+
}),
|
|
1753
|
+
view.kind === "updating" && status !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1754
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1755
|
+
className: remote_module_css_default.updateStatus,
|
|
1756
|
+
children: t("update.updating", {
|
|
1757
|
+
name: anchorName(status) ?? "",
|
|
1758
|
+
version: anchorLatest(status) ?? ""
|
|
1759
|
+
})
|
|
1760
|
+
}),
|
|
1761
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1762
|
+
className: remote_module_css_default.updateDetail,
|
|
1763
|
+
children: t("update.cooldownNotice")
|
|
1764
|
+
}),
|
|
1765
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageList, { status })
|
|
1766
|
+
] }),
|
|
1750
1767
|
view.kind === "done" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoneBody, {
|
|
1751
1768
|
t,
|
|
1752
1769
|
result: view.result
|
|
@@ -1809,16 +1826,23 @@ window.__ModuleLoader__.load({
|
|
|
1809
1826
|
className: remote_module_css_default.updateDetail,
|
|
1810
1827
|
children: t("update.offlineDetail")
|
|
1811
1828
|
})] });
|
|
1812
|
-
if (status.outdated) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1829
|
+
if (status.outdated) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1830
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1831
|
+
className: remote_module_css_default.updateStatus,
|
|
1832
|
+
children: t("update.found")
|
|
1833
|
+
}),
|
|
1834
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1835
|
+
className: remote_module_css_default.updateDetail,
|
|
1836
|
+
children: anchor !== void 0 ? t("update.foundDetail", {
|
|
1837
|
+
name: anchor,
|
|
1838
|
+
version: latest ?? ""
|
|
1839
|
+
}) : ""
|
|
1840
|
+
}),
|
|
1841
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1842
|
+
className: remote_module_css_default.updateDetail,
|
|
1843
|
+
children: t("update.cooldownNotice")
|
|
1844
|
+
})
|
|
1845
|
+
] });
|
|
1822
1846
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1823
1847
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1824
1848
|
className: remote_module_css_default.updateStatus,
|
|
@@ -1887,8 +1911,9 @@ window.__ModuleLoader__.load({
|
|
|
1887
1911
|
/**
|
|
1888
1912
|
* The sidebar update seat: the download trigger beside the remote-control
|
|
1889
1913
|
* trigger plus the update panel modal. Owns the flow — probe the registry
|
|
1890
|
-
* on open,
|
|
1891
|
-
* outcome (restart hint on success,
|
|
1914
|
+
* on open, show the result, and let the user start the update from the
|
|
1915
|
+
* result view (#507), then report the outcome (restart hint on success,
|
|
1916
|
+
* translated failure on error).
|
|
1892
1917
|
* Component-local state per the client stack rules.
|
|
1893
1918
|
*/
|
|
1894
1919
|
/**
|
|
@@ -1947,6 +1972,8 @@ window.__ModuleLoader__.load({
|
|
|
1947
1972
|
kind: "result",
|
|
1948
1973
|
status
|
|
1949
1974
|
});
|
|
1975
|
+
}, [t]);
|
|
1976
|
+
const startUpdate = (0, react.useCallback)(async (status) => {
|
|
1950
1977
|
if (status.mode !== "npm" || !status.outdated) return;
|
|
1951
1978
|
setView({
|
|
1952
1979
|
kind: "updating",
|
|
@@ -2010,6 +2037,9 @@ window.__ModuleLoader__.load({
|
|
|
2010
2037
|
onClose: closePanel,
|
|
2011
2038
|
onRecheck: () => {
|
|
2012
2039
|
check();
|
|
2040
|
+
},
|
|
2041
|
+
onStartUpdate: (status) => {
|
|
2042
|
+
startUpdate(status);
|
|
2013
2043
|
}
|
|
2014
2044
|
})]
|
|
2015
2045
|
}), document.body)] });
|
|
@@ -2058,6 +2088,7 @@ window.__ModuleLoader__.load({
|
|
|
2058
2088
|
}, [state]);
|
|
2059
2089
|
const [copied, setCopied] = (0, react.useState)(false);
|
|
2060
2090
|
const eventSource = (0, react.useRef)(void 0);
|
|
2091
|
+
const openSeq = (0, react.useRef)(0);
|
|
2061
2092
|
const workspaceId = useWorkspaces((s) => s.recentWorkspaceId);
|
|
2062
2093
|
const closeEventSource = (0, react.useCallback)(() => {
|
|
2063
2094
|
eventSource.current?.close();
|
|
@@ -2091,8 +2122,10 @@ window.__ModuleLoader__.load({
|
|
|
2091
2122
|
};
|
|
2092
2123
|
}, [workspaceId]);
|
|
2093
2124
|
const openPanel = (0, react.useCallback)(async () => {
|
|
2125
|
+
const seq = ++openSeq.current;
|
|
2094
2126
|
setOpen(true);
|
|
2095
2127
|
const next = await mint();
|
|
2128
|
+
if (seq !== openSeq.current) return;
|
|
2096
2129
|
setState(next);
|
|
2097
2130
|
if (next.kind !== "ready" && next.kind !== "lan-required") return;
|
|
2098
2131
|
const source = new EventSource("/api/pair/events");
|
|
@@ -2111,6 +2144,7 @@ window.__ModuleLoader__.load({
|
|
|
2111
2144
|
};
|
|
2112
2145
|
}, [mint]);
|
|
2113
2146
|
const closePanel = (0, react.useCallback)(() => {
|
|
2147
|
+
openSeq.current += 1;
|
|
2114
2148
|
closeEventSource();
|
|
2115
2149
|
setOpen(false);
|
|
2116
2150
|
}, [closeEventSource]);
|
|
@@ -2260,7 +2294,7 @@ window.__ModuleLoader__.load({
|
|
|
2260
2294
|
}
|
|
2261
2295
|
//#endregion
|
|
2262
2296
|
//#region \0dsh-css:packages/dsh-remote-web-ui/src/client/settings-card.module.css.mjs
|
|
2263
|
-
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_headerStatic{border-radius:12px;align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}.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}}";
|
|
2297
|
+
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_headerStatic{border-radius:12px;align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}.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_selectWrap{position:relative}.Kwoi6G_selectButton{appearance:none;text-align:left;cursor:pointer;justify-content:space-between;align-items:center;gap:8px;width:100%;display:flex}.Kwoi6G_selectLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.Kwoi6G_selectChevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Kwoi6G_selectChevronOpen{transform:rotate(180deg)}.Kwoi6G_selectPopup{z-index:40;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);max-height:240px;box-shadow:0 8px 24px var(--dsw-alias-bg-mask-2);opacity:0;border-radius:8px;flex-direction:column;padding:4px;transition:opacity .1s,transform .1s;display:flex;position:absolute;top:calc(100% + 4px);left:0;right:0;overflow-y:auto;transform:translateY(-4px)}.Kwoi6G_selectPopupOpen{opacity:1;transform:none}.Kwoi6G_selectPopupClose{opacity:0;pointer-events:none;transform:translateY(-4px)}.Kwoi6G_selectOption{color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;text-overflow:ellipsis;border-radius:6px;padding:6px 10px;font-size:13px;line-height:1.5;overflow:hidden}.Kwoi6G_selectOption:hover,.Kwoi6G_selectOptionActive{background:var(--dsw-alias-interactive-bg-hover)}.Kwoi6G_selectOptionSelected{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 10%, transparent);font-weight:500}.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,.Kwoi6G_selectChevron,.Kwoi6G_selectChevronOpen,.Kwoi6G_selectPopup{transition:none}}";
|
|
2264
2298
|
const tagId = "@linxin666/dsh-remote-web-ui/settings-card.module.css";
|
|
2265
2299
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
2266
2300
|
const tag = document.createElement("style");
|
|
@@ -2297,7 +2331,18 @@ window.__ModuleLoader__.load({
|
|
|
2297
2331
|
"readOnly": "Kwoi6G_readOnly",
|
|
2298
2332
|
"reset": "Kwoi6G_reset",
|
|
2299
2333
|
"save": "Kwoi6G_save",
|
|
2300
|
-
"select": "Kwoi6G_select"
|
|
2334
|
+
"select": "Kwoi6G_select",
|
|
2335
|
+
"selectButton": "Kwoi6G_selectButton",
|
|
2336
|
+
"selectChevron": "Kwoi6G_selectChevron",
|
|
2337
|
+
"selectChevronOpen": "Kwoi6G_selectChevronOpen",
|
|
2338
|
+
"selectLabel": "Kwoi6G_selectLabel",
|
|
2339
|
+
"selectOption": "Kwoi6G_selectOption",
|
|
2340
|
+
"selectOptionActive": "Kwoi6G_selectOptionActive",
|
|
2341
|
+
"selectOptionSelected": "Kwoi6G_selectOptionSelected",
|
|
2342
|
+
"selectPopup": "Kwoi6G_selectPopup",
|
|
2343
|
+
"selectPopupClose": "Kwoi6G_selectPopupClose",
|
|
2344
|
+
"selectPopupOpen": "Kwoi6G_selectPopupOpen",
|
|
2345
|
+
"selectWrap": "Kwoi6G_selectWrap"
|
|
2301
2346
|
};
|
|
2302
2347
|
//#endregion
|
|
2303
2348
|
//#region src/client/PluginSettingsCard.tsx
|
|
@@ -2476,6 +2521,175 @@ window.__ModuleLoader__.load({
|
|
|
2476
2521
|
]
|
|
2477
2522
|
});
|
|
2478
2523
|
}
|
|
2524
|
+
const NON_SKIN_BODY_MARKERS = /* @__PURE__ */ new Set(["dshSkinCenter", "dshSidebarCollapsed"]);
|
|
2525
|
+
function isSkinActive() {
|
|
2526
|
+
return Object.keys(document.body.dataset).some((key) => key.startsWith("dsh") && !NON_SKIN_BODY_MARKERS.has(key));
|
|
2527
|
+
}
|
|
2528
|
+
const SELECT_CLOSE_MS = 100;
|
|
2529
|
+
/**
|
|
2530
|
+
* The shared dual-mode select control. While an appearance skin is active it
|
|
2531
|
+
* renders the legacy native `<select>` untouched, so element-level skin
|
|
2532
|
+
* selectors keep working; under the default appearance it renders a
|
|
2533
|
+
* self-drawn `role="listbox"` popup whose open/close is transition-animated.
|
|
2534
|
+
* Staged cards reach it through BooleanField/ChoiceField; immediate-apply
|
|
2535
|
+
* editors (the side-card prefs) bind it directly through onEdit.
|
|
2536
|
+
* 双模式下拉框:皮肤激活时用原生 select,默认外观用自绘动画弹层。
|
|
2537
|
+
*/
|
|
2538
|
+
function SelectField(props) {
|
|
2539
|
+
const { id, options, value } = props;
|
|
2540
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
2541
|
+
const [closing, setClosing] = (0, react.useState)(false);
|
|
2542
|
+
const [phase, setPhase] = (0, react.useState)("initial");
|
|
2543
|
+
const [activeIndex, setActiveIndex] = (0, react.useState)(0);
|
|
2544
|
+
const closeTimer = (0, react.useRef)(void 0);
|
|
2545
|
+
const wrapRef = (0, react.useRef)(null);
|
|
2546
|
+
const popupRef = (0, react.useRef)(null);
|
|
2547
|
+
const currentIndex = () => {
|
|
2548
|
+
const index = options.findIndex((option) => option.value === value);
|
|
2549
|
+
return index >= 0 ? index : 0;
|
|
2550
|
+
};
|
|
2551
|
+
const close = (0, react.useCallback)(() => {
|
|
2552
|
+
if (closeTimer.current !== void 0) clearTimeout(closeTimer.current);
|
|
2553
|
+
setClosing(true);
|
|
2554
|
+
closeTimer.current = setTimeout(() => {
|
|
2555
|
+
setClosing(false);
|
|
2556
|
+
setOpen(false);
|
|
2557
|
+
}, SELECT_CLOSE_MS);
|
|
2558
|
+
}, []);
|
|
2559
|
+
const openPopup = () => {
|
|
2560
|
+
if (closeTimer.current !== void 0) clearTimeout(closeTimer.current);
|
|
2561
|
+
setActiveIndex(currentIndex());
|
|
2562
|
+
setPhase("initial");
|
|
2563
|
+
setClosing(false);
|
|
2564
|
+
setOpen(true);
|
|
2565
|
+
};
|
|
2566
|
+
const commit = (index) => {
|
|
2567
|
+
const option = options[index];
|
|
2568
|
+
if (option) props.onEdit(option.value);
|
|
2569
|
+
close();
|
|
2570
|
+
};
|
|
2571
|
+
const onTriggerClick = () => {
|
|
2572
|
+
if (props.disabled) return;
|
|
2573
|
+
if (open && !closing) close();
|
|
2574
|
+
else openPopup();
|
|
2575
|
+
};
|
|
2576
|
+
const onKeyDown = (event) => {
|
|
2577
|
+
if (props.disabled) return;
|
|
2578
|
+
const count = options.length;
|
|
2579
|
+
switch (event.key) {
|
|
2580
|
+
case "ArrowDown":
|
|
2581
|
+
case "ArrowUp":
|
|
2582
|
+
case "Enter":
|
|
2583
|
+
case " ":
|
|
2584
|
+
event.preventDefault();
|
|
2585
|
+
if (!open) openPopup();
|
|
2586
|
+
else if (!closing) if (event.key === "ArrowDown") setActiveIndex((index) => (index + 1) % count);
|
|
2587
|
+
else if (event.key === "ArrowUp") setActiveIndex((index) => (index - 1 + count) % count);
|
|
2588
|
+
else commit(activeIndex);
|
|
2589
|
+
break;
|
|
2590
|
+
case "Escape":
|
|
2591
|
+
if (open) {
|
|
2592
|
+
event.preventDefault();
|
|
2593
|
+
event.stopPropagation();
|
|
2594
|
+
close();
|
|
2595
|
+
}
|
|
2596
|
+
break;
|
|
2597
|
+
case "Tab":
|
|
2598
|
+
if (open) close();
|
|
2599
|
+
break;
|
|
2600
|
+
}
|
|
2601
|
+
};
|
|
2602
|
+
(0, react.useEffect)(() => () => {
|
|
2603
|
+
if (closeTimer.current !== void 0) clearTimeout(closeTimer.current);
|
|
2604
|
+
}, []);
|
|
2605
|
+
(0, react.useLayoutEffect)(() => {
|
|
2606
|
+
if (open && !closing && phase === "initial") {
|
|
2607
|
+
popupRef.current?.offsetHeight;
|
|
2608
|
+
setPhase("open");
|
|
2609
|
+
}
|
|
2610
|
+
}, [
|
|
2611
|
+
open,
|
|
2612
|
+
closing,
|
|
2613
|
+
phase
|
|
2614
|
+
]);
|
|
2615
|
+
(0, react.useEffect)(() => {
|
|
2616
|
+
if (!open) return;
|
|
2617
|
+
const onPointerDown = (event) => {
|
|
2618
|
+
const target = event.target;
|
|
2619
|
+
if (target instanceof Node && !wrapRef.current?.contains(target)) close();
|
|
2620
|
+
};
|
|
2621
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
2622
|
+
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
2623
|
+
}, [open, close]);
|
|
2624
|
+
(0, react.useEffect)(() => {
|
|
2625
|
+
if (props.disabled && open) close();
|
|
2626
|
+
}, [
|
|
2627
|
+
props.disabled,
|
|
2628
|
+
open,
|
|
2629
|
+
close
|
|
2630
|
+
]);
|
|
2631
|
+
if (isSkinActive()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
2632
|
+
id,
|
|
2633
|
+
className: settings_card_module_css_default.select,
|
|
2634
|
+
value,
|
|
2635
|
+
disabled: props.disabled,
|
|
2636
|
+
onChange: (event) => {
|
|
2637
|
+
props.onEdit(event.target.value);
|
|
2638
|
+
},
|
|
2639
|
+
children: options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2640
|
+
value: option.value,
|
|
2641
|
+
children: option.label
|
|
2642
|
+
}, option.value))
|
|
2643
|
+
});
|
|
2644
|
+
const label = options.find((option) => option.value === value)?.label ?? "";
|
|
2645
|
+
const popupClass = closing ? `${settings_card_module_css_default.selectPopup} ${settings_card_module_css_default.selectPopupClose}` : phase === "open" ? `${settings_card_module_css_default.selectPopup} ${settings_card_module_css_default.selectPopupOpen}` : settings_card_module_css_default.selectPopup;
|
|
2646
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2647
|
+
className: settings_card_module_css_default.selectWrap,
|
|
2648
|
+
ref: wrapRef,
|
|
2649
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2650
|
+
type: "button",
|
|
2651
|
+
id,
|
|
2652
|
+
className: `${settings_card_module_css_default.select} ${settings_card_module_css_default.selectButton}`,
|
|
2653
|
+
disabled: props.disabled,
|
|
2654
|
+
"aria-haspopup": "listbox",
|
|
2655
|
+
"aria-expanded": open,
|
|
2656
|
+
"aria-activedescendant": open ? `${id}-o${activeIndex}` : void 0,
|
|
2657
|
+
"aria-invalid": props.invalid || void 0,
|
|
2658
|
+
onClick: onTriggerClick,
|
|
2659
|
+
onKeyDown,
|
|
2660
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2661
|
+
className: settings_card_module_css_default.selectLabel,
|
|
2662
|
+
children: label
|
|
2663
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2664
|
+
width: "14",
|
|
2665
|
+
height: "14",
|
|
2666
|
+
viewBox: "0 0 14 14",
|
|
2667
|
+
fill: "none",
|
|
2668
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2669
|
+
className: open ? `${settings_card_module_css_default.selectChevron} ${settings_card_module_css_default.selectChevronOpen}` : settings_card_module_css_default.selectChevron,
|
|
2670
|
+
"aria-hidden": "true",
|
|
2671
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
2672
|
+
d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
|
|
2673
|
+
fill: "currentColor"
|
|
2674
|
+
})
|
|
2675
|
+
})]
|
|
2676
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2677
|
+
className: popupClass,
|
|
2678
|
+
role: "listbox",
|
|
2679
|
+
ref: popupRef,
|
|
2680
|
+
children: options.map((option, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2681
|
+
id: `${id}-o${index}`,
|
|
2682
|
+
role: "option",
|
|
2683
|
+
"aria-selected": option.value === value,
|
|
2684
|
+
className: `${settings_card_module_css_default.selectOption}${option.value === value ? ` ${settings_card_module_css_default.selectOptionSelected}` : ""}${index === activeIndex && !closing ? ` ${settings_card_module_css_default.selectOptionActive}` : ""}`,
|
|
2685
|
+
onClick: () => {
|
|
2686
|
+
commit(index);
|
|
2687
|
+
},
|
|
2688
|
+
children: option.label
|
|
2689
|
+
}, option.value))
|
|
2690
|
+
}) : null]
|
|
2691
|
+
});
|
|
2692
|
+
}
|
|
2479
2693
|
/** A staged boolean field: 继承 / 开 / 关. */
|
|
2480
2694
|
function BooleanField(props) {
|
|
2481
2695
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2501,28 +2715,26 @@ window.__ModuleLoader__.load({
|
|
|
2501
2715
|
})]
|
|
2502
2716
|
}) : null]
|
|
2503
2717
|
}),
|
|
2504
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2718
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectField, {
|
|
2505
2719
|
id: props.id,
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
disabled: props.disabled,
|
|
2509
|
-
onChange: (event) => {
|
|
2510
|
-
props.onEdit(event.target.value);
|
|
2511
|
-
},
|
|
2512
|
-
children: [
|
|
2513
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2720
|
+
options: [
|
|
2721
|
+
{
|
|
2514
2722
|
value: "",
|
|
2515
|
-
|
|
2516
|
-
}
|
|
2517
|
-
|
|
2723
|
+
label: props.inheritLabel
|
|
2724
|
+
},
|
|
2725
|
+
{
|
|
2518
2726
|
value: "true",
|
|
2519
|
-
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2727
|
+
label: props.onLabel
|
|
2728
|
+
},
|
|
2729
|
+
{
|
|
2522
2730
|
value: "false",
|
|
2523
|
-
|
|
2524
|
-
}
|
|
2525
|
-
]
|
|
2731
|
+
label: props.offLabel
|
|
2732
|
+
}
|
|
2733
|
+
],
|
|
2734
|
+
value: props.text,
|
|
2735
|
+
disabled: props.disabled,
|
|
2736
|
+
invalid: props.invalid,
|
|
2737
|
+
onEdit: props.onEdit
|
|
2526
2738
|
}),
|
|
2527
2739
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2528
2740
|
className: settings_card_module_css_default.hint,
|
|
@@ -3114,9 +3326,11 @@ window.__ModuleLoader__.load({
|
|
|
3114
3326
|
"update.upToDate": "当前已是最新版本",
|
|
3115
3327
|
"update.upToDateDetail": "{name} 已是最新版本 v{version}。",
|
|
3116
3328
|
"update.found": "发现新版本",
|
|
3117
|
-
"update.foundDetail": "{name} 有新版本 v{version}
|
|
3329
|
+
"update.foundDetail": "{name} 有新版本 v{version}。确认后开始更新。",
|
|
3330
|
+
"update.start": "开始更新",
|
|
3118
3331
|
"update.updating": "正在更新 {name} 至 v{version},可能需要 1-2 分钟…",
|
|
3119
|
-
"update.
|
|
3332
|
+
"update.cooldownNotice": "本次更新由你主动触发,将跳过 pnpm 的 24 小时发布冷却期(供应链保护)直接安装所选版本。",
|
|
3333
|
+
"update.updatingTitle": "正在更新…",
|
|
3120
3334
|
"update.done": "更新完成",
|
|
3121
3335
|
"update.doneDetail": "所有组件已更新到最新版本。",
|
|
3122
3336
|
"update.restartHint": "请重启 dsh web 使新版本生效:在终端按 Ctrl+C 停止后重新运行 dsh web。",
|
|
@@ -3219,9 +3433,11 @@ window.__ModuleLoader__.load({
|
|
|
3219
3433
|
"update.upToDate": "Everything is up to date",
|
|
3220
3434
|
"update.upToDateDetail": "{name} is at the latest version v{version}.",
|
|
3221
3435
|
"update.found": "A new version is available",
|
|
3222
|
-
"update.foundDetail": "{name} has a newer release v{version}
|
|
3436
|
+
"update.foundDetail": "{name} has a newer release v{version}. Confirm to start the update.",
|
|
3437
|
+
"update.start": "Update now",
|
|
3223
3438
|
"update.updating": "Updating {name} to v{version}; this may take 1-2 minutes…",
|
|
3224
|
-
"update.
|
|
3439
|
+
"update.cooldownNotice": "This user-initiated update bypasses pnpm's 24-hour release cooldown (supply-chain protection) and installs the selected version immediately.",
|
|
3440
|
+
"update.updatingTitle": "Updating…",
|
|
3225
3441
|
"update.done": "Update complete",
|
|
3226
3442
|
"update.doneDetail": "All components are up to date.",
|
|
3227
3443
|
"update.restartHint": "Restart dsh web for the new version to take effect: stop it with Ctrl+C in the terminal, then run dsh web again.",
|