@linxin666/dsh-remote-web-ui 0.2.0 → 0.2.1
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 +15 -7
- package/README.zh.md +4 -4
- package/lib/client.js +235 -37
- 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/UpdatePanel.d.ts.map +1 -1
- package/lib/types/client/UpdatePanel.js +2 -2
- package/lib/types/client/locales.d.ts +2 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +2 -0
- 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 -1
- package/src/client/PluginSettingsCard.tsx +219 -18
- package/src/client/RemoteEntry.tsx +10 -0
- package/src/client/RemoteSettingsCard.tsx +2 -2
- package/src/client/UpdatePanel.tsx +2 -0
- package/src/client/locales.ts +2 -0
- 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/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: 30f19949c347321da330c22bee0f61ba42b70bcf
|
|
6
|
+
README.zh.md: 5221d455f62613b62795a1340e64bbdd7ed63937
|
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ Install the family aggregate package `@linxin666/dsh-web-ui-all` (all plugins an
|
|
|
103
103
|
|
|
104
104
|
```sh
|
|
105
105
|
# Recommended: install directly from npm
|
|
106
|
-
dsh plugin --profile web add @linxin666/dsh-remote-web-ui
|
|
106
|
+
dsh plugin --profile web add @linxin666/dsh-remote-web-ui@latest
|
|
107
107
|
|
|
108
108
|
# Or from the repository (development loop)
|
|
109
109
|
git clone https://github.com/zhu1090093659/dsh-web-ui.git
|
|
@@ -130,9 +130,11 @@ mounts both halves.
|
|
|
130
130
|
3. Scan with the phone (or open the copied link): the phone binds and
|
|
131
131
|
lands on the **standalone mobile surface at `/m/`** — no desktop UI on a
|
|
132
132
|
small screen. The surface is deliberately thin:
|
|
133
|
-
- workspaces straight away (
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
- workspaces straight away (each workspace's session list offers an Agent
|
|
134
|
+
mode picker before 新建会话: it selects the usable default preset, or the
|
|
135
|
+
first usable preset, and passes that roster id with the workspace id to
|
|
136
|
+
the host's `session.create`; an empty or unavailable roster keeps the
|
|
137
|
+
host-default creation flow),
|
|
136
138
|
- one workspace's sessions load **incrementally** (20 rows per page,
|
|
137
139
|
"加载更多会话" continues; never the whole list at once),
|
|
138
140
|
- opening a session fetches its chat content **on demand** (history
|
|
@@ -172,9 +174,10 @@ pages `session.list` itself), so the tunneled Host never has to enter the
|
|
|
172
174
|
connection plugin's trust fence. The phone is gated by its paired-device
|
|
173
175
|
cookie and an explicit method allowlist (settings/credentials/host-action
|
|
174
176
|
domains are never reachable from the phone; model reads/writes are limited
|
|
175
|
-
to the advisory `session.models` / `session.selectModel` pair,
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
to the advisory `session.models` / `session.selectModel` pair, agent preset
|
|
178
|
+
access to read-only `agentPreset.list`, creation to `session.create`
|
|
179
|
+
(workspace id plus an optional id from that roster — the phone never names a
|
|
180
|
+
working directory of its own), and the permission picker only ever sends the
|
|
178
181
|
mode-agnostic `/permission` command
|
|
179
182
|
through the already-allowlisted `session.prompt`); the live stream arrives
|
|
180
183
|
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 +245,11 @@ pairing fence trusts the tunneled host. Two knobs are involved:
|
|
|
242
245
|
card): the public origin, e.g. `https://foo.trycloudflare.com`. The QR
|
|
243
246
|
link is built from it, and `accept`/`heartbeat`/`status` accept its host.
|
|
244
247
|
Malformed values are ignored with a warning (LAN-only behavior kept).
|
|
248
|
+
Unpaired callers of `status` see only the pairing-relevant fields (phase /
|
|
249
|
+
LAN bases); token expiry, device roster, and the tunnel URL require a live
|
|
250
|
+
device cookie. The accept rate limit partitions its buckets by the
|
|
251
|
+
client-visible `X-Forwarded-For` hop so one internet client cannot
|
|
252
|
+
exhaust the shared bucket behind the tunnel.
|
|
245
253
|
- **`--trusted-host <authority>`** (dsh web flag): the transport-level
|
|
246
254
|
`/api` fence of the connection plugin must accept the public host too —
|
|
247
255
|
without it every `/api` request through the tunnel 403s *before* the
|
package/README.zh.md
CHANGED
|
@@ -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
|
@@ -1740,13 +1740,20 @@ window.__ModuleLoader__.load({
|
|
|
1740
1740
|
t,
|
|
1741
1741
|
status
|
|
1742
1742
|
}),
|
|
1743
|
-
view.kind === "updating" && status !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1743
|
+
view.kind === "updating" && status !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1744
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1745
|
+
className: remote_module_css_default.updateStatus,
|
|
1746
|
+
children: t("update.updating", {
|
|
1747
|
+
name: anchorName(status) ?? "",
|
|
1748
|
+
version: anchorLatest(status) ?? ""
|
|
1749
|
+
})
|
|
1750
|
+
}),
|
|
1751
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1752
|
+
className: remote_module_css_default.updateDetail,
|
|
1753
|
+
children: t("update.cooldownNotice")
|
|
1754
|
+
}),
|
|
1755
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageList, { status })
|
|
1756
|
+
] }),
|
|
1750
1757
|
view.kind === "done" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoneBody, {
|
|
1751
1758
|
t,
|
|
1752
1759
|
result: view.result
|
|
@@ -1809,16 +1816,23 @@ window.__ModuleLoader__.load({
|
|
|
1809
1816
|
className: remote_module_css_default.updateDetail,
|
|
1810
1817
|
children: t("update.offlineDetail")
|
|
1811
1818
|
})] });
|
|
1812
|
-
if (status.outdated) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1819
|
+
if (status.outdated) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1820
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1821
|
+
className: remote_module_css_default.updateStatus,
|
|
1822
|
+
children: t("update.found")
|
|
1823
|
+
}),
|
|
1824
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1825
|
+
className: remote_module_css_default.updateDetail,
|
|
1826
|
+
children: anchor !== void 0 ? t("update.foundDetail", {
|
|
1827
|
+
name: anchor,
|
|
1828
|
+
version: latest ?? ""
|
|
1829
|
+
}) : ""
|
|
1830
|
+
}),
|
|
1831
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1832
|
+
className: remote_module_css_default.updateDetail,
|
|
1833
|
+
children: t("update.cooldownNotice")
|
|
1834
|
+
})
|
|
1835
|
+
] });
|
|
1822
1836
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1823
1837
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1824
1838
|
className: remote_module_css_default.updateStatus,
|
|
@@ -2058,6 +2072,7 @@ window.__ModuleLoader__.load({
|
|
|
2058
2072
|
}, [state]);
|
|
2059
2073
|
const [copied, setCopied] = (0, react.useState)(false);
|
|
2060
2074
|
const eventSource = (0, react.useRef)(void 0);
|
|
2075
|
+
const openSeq = (0, react.useRef)(0);
|
|
2061
2076
|
const workspaceId = useWorkspaces((s) => s.recentWorkspaceId);
|
|
2062
2077
|
const closeEventSource = (0, react.useCallback)(() => {
|
|
2063
2078
|
eventSource.current?.close();
|
|
@@ -2091,8 +2106,10 @@ window.__ModuleLoader__.load({
|
|
|
2091
2106
|
};
|
|
2092
2107
|
}, [workspaceId]);
|
|
2093
2108
|
const openPanel = (0, react.useCallback)(async () => {
|
|
2109
|
+
const seq = ++openSeq.current;
|
|
2094
2110
|
setOpen(true);
|
|
2095
2111
|
const next = await mint();
|
|
2112
|
+
if (seq !== openSeq.current) return;
|
|
2096
2113
|
setState(next);
|
|
2097
2114
|
if (next.kind !== "ready" && next.kind !== "lan-required") return;
|
|
2098
2115
|
const source = new EventSource("/api/pair/events");
|
|
@@ -2111,6 +2128,7 @@ window.__ModuleLoader__.load({
|
|
|
2111
2128
|
};
|
|
2112
2129
|
}, [mint]);
|
|
2113
2130
|
const closePanel = (0, react.useCallback)(() => {
|
|
2131
|
+
openSeq.current += 1;
|
|
2114
2132
|
closeEventSource();
|
|
2115
2133
|
setOpen(false);
|
|
2116
2134
|
}, [closeEventSource]);
|
|
@@ -2260,7 +2278,7 @@ window.__ModuleLoader__.load({
|
|
|
2260
2278
|
}
|
|
2261
2279
|
//#endregion
|
|
2262
2280
|
//#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}}";
|
|
2281
|
+
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
2282
|
const tagId = "@linxin666/dsh-remote-web-ui/settings-card.module.css";
|
|
2265
2283
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
2266
2284
|
const tag = document.createElement("style");
|
|
@@ -2297,7 +2315,18 @@ window.__ModuleLoader__.load({
|
|
|
2297
2315
|
"readOnly": "Kwoi6G_readOnly",
|
|
2298
2316
|
"reset": "Kwoi6G_reset",
|
|
2299
2317
|
"save": "Kwoi6G_save",
|
|
2300
|
-
"select": "Kwoi6G_select"
|
|
2318
|
+
"select": "Kwoi6G_select",
|
|
2319
|
+
"selectButton": "Kwoi6G_selectButton",
|
|
2320
|
+
"selectChevron": "Kwoi6G_selectChevron",
|
|
2321
|
+
"selectChevronOpen": "Kwoi6G_selectChevronOpen",
|
|
2322
|
+
"selectLabel": "Kwoi6G_selectLabel",
|
|
2323
|
+
"selectOption": "Kwoi6G_selectOption",
|
|
2324
|
+
"selectOptionActive": "Kwoi6G_selectOptionActive",
|
|
2325
|
+
"selectOptionSelected": "Kwoi6G_selectOptionSelected",
|
|
2326
|
+
"selectPopup": "Kwoi6G_selectPopup",
|
|
2327
|
+
"selectPopupClose": "Kwoi6G_selectPopupClose",
|
|
2328
|
+
"selectPopupOpen": "Kwoi6G_selectPopupOpen",
|
|
2329
|
+
"selectWrap": "Kwoi6G_selectWrap"
|
|
2301
2330
|
};
|
|
2302
2331
|
//#endregion
|
|
2303
2332
|
//#region src/client/PluginSettingsCard.tsx
|
|
@@ -2476,6 +2505,175 @@ window.__ModuleLoader__.load({
|
|
|
2476
2505
|
]
|
|
2477
2506
|
});
|
|
2478
2507
|
}
|
|
2508
|
+
const NON_SKIN_BODY_MARKERS = /* @__PURE__ */ new Set(["dshSkinCenter", "dshSidebarCollapsed"]);
|
|
2509
|
+
function isSkinActive() {
|
|
2510
|
+
return Object.keys(document.body.dataset).some((key) => key.startsWith("dsh") && !NON_SKIN_BODY_MARKERS.has(key));
|
|
2511
|
+
}
|
|
2512
|
+
const SELECT_CLOSE_MS = 100;
|
|
2513
|
+
/**
|
|
2514
|
+
* The shared dual-mode select control. While an appearance skin is active it
|
|
2515
|
+
* renders the legacy native `<select>` untouched, so element-level skin
|
|
2516
|
+
* selectors keep working; under the default appearance it renders a
|
|
2517
|
+
* self-drawn `role="listbox"` popup whose open/close is transition-animated.
|
|
2518
|
+
* Staged cards reach it through BooleanField/ChoiceField; immediate-apply
|
|
2519
|
+
* editors (the side-card prefs) bind it directly through onEdit.
|
|
2520
|
+
* 双模式下拉框:皮肤激活时用原生 select,默认外观用自绘动画弹层。
|
|
2521
|
+
*/
|
|
2522
|
+
function SelectField(props) {
|
|
2523
|
+
const { id, options, value } = props;
|
|
2524
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
2525
|
+
const [closing, setClosing] = (0, react.useState)(false);
|
|
2526
|
+
const [phase, setPhase] = (0, react.useState)("initial");
|
|
2527
|
+
const [activeIndex, setActiveIndex] = (0, react.useState)(0);
|
|
2528
|
+
const closeTimer = (0, react.useRef)(void 0);
|
|
2529
|
+
const wrapRef = (0, react.useRef)(null);
|
|
2530
|
+
const popupRef = (0, react.useRef)(null);
|
|
2531
|
+
const currentIndex = () => {
|
|
2532
|
+
const index = options.findIndex((option) => option.value === value);
|
|
2533
|
+
return index >= 0 ? index : 0;
|
|
2534
|
+
};
|
|
2535
|
+
const close = (0, react.useCallback)(() => {
|
|
2536
|
+
if (closeTimer.current !== void 0) clearTimeout(closeTimer.current);
|
|
2537
|
+
setClosing(true);
|
|
2538
|
+
closeTimer.current = setTimeout(() => {
|
|
2539
|
+
setClosing(false);
|
|
2540
|
+
setOpen(false);
|
|
2541
|
+
}, SELECT_CLOSE_MS);
|
|
2542
|
+
}, []);
|
|
2543
|
+
const openPopup = () => {
|
|
2544
|
+
if (closeTimer.current !== void 0) clearTimeout(closeTimer.current);
|
|
2545
|
+
setActiveIndex(currentIndex());
|
|
2546
|
+
setPhase("initial");
|
|
2547
|
+
setClosing(false);
|
|
2548
|
+
setOpen(true);
|
|
2549
|
+
};
|
|
2550
|
+
const commit = (index) => {
|
|
2551
|
+
const option = options[index];
|
|
2552
|
+
if (option) props.onEdit(option.value);
|
|
2553
|
+
close();
|
|
2554
|
+
};
|
|
2555
|
+
const onTriggerClick = () => {
|
|
2556
|
+
if (props.disabled) return;
|
|
2557
|
+
if (open && !closing) close();
|
|
2558
|
+
else openPopup();
|
|
2559
|
+
};
|
|
2560
|
+
const onKeyDown = (event) => {
|
|
2561
|
+
if (props.disabled) return;
|
|
2562
|
+
const count = options.length;
|
|
2563
|
+
switch (event.key) {
|
|
2564
|
+
case "ArrowDown":
|
|
2565
|
+
case "ArrowUp":
|
|
2566
|
+
case "Enter":
|
|
2567
|
+
case " ":
|
|
2568
|
+
event.preventDefault();
|
|
2569
|
+
if (!open) openPopup();
|
|
2570
|
+
else if (!closing) if (event.key === "ArrowDown") setActiveIndex((index) => (index + 1) % count);
|
|
2571
|
+
else if (event.key === "ArrowUp") setActiveIndex((index) => (index - 1 + count) % count);
|
|
2572
|
+
else commit(activeIndex);
|
|
2573
|
+
break;
|
|
2574
|
+
case "Escape":
|
|
2575
|
+
if (open) {
|
|
2576
|
+
event.preventDefault();
|
|
2577
|
+
event.stopPropagation();
|
|
2578
|
+
close();
|
|
2579
|
+
}
|
|
2580
|
+
break;
|
|
2581
|
+
case "Tab":
|
|
2582
|
+
if (open) close();
|
|
2583
|
+
break;
|
|
2584
|
+
}
|
|
2585
|
+
};
|
|
2586
|
+
(0, react.useEffect)(() => () => {
|
|
2587
|
+
if (closeTimer.current !== void 0) clearTimeout(closeTimer.current);
|
|
2588
|
+
}, []);
|
|
2589
|
+
(0, react.useLayoutEffect)(() => {
|
|
2590
|
+
if (open && !closing && phase === "initial") {
|
|
2591
|
+
popupRef.current?.offsetHeight;
|
|
2592
|
+
setPhase("open");
|
|
2593
|
+
}
|
|
2594
|
+
}, [
|
|
2595
|
+
open,
|
|
2596
|
+
closing,
|
|
2597
|
+
phase
|
|
2598
|
+
]);
|
|
2599
|
+
(0, react.useEffect)(() => {
|
|
2600
|
+
if (!open) return;
|
|
2601
|
+
const onPointerDown = (event) => {
|
|
2602
|
+
const target = event.target;
|
|
2603
|
+
if (target instanceof Node && !wrapRef.current?.contains(target)) close();
|
|
2604
|
+
};
|
|
2605
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
2606
|
+
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
2607
|
+
}, [open, close]);
|
|
2608
|
+
(0, react.useEffect)(() => {
|
|
2609
|
+
if (props.disabled && open) close();
|
|
2610
|
+
}, [
|
|
2611
|
+
props.disabled,
|
|
2612
|
+
open,
|
|
2613
|
+
close
|
|
2614
|
+
]);
|
|
2615
|
+
if (isSkinActive()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
2616
|
+
id,
|
|
2617
|
+
className: settings_card_module_css_default.select,
|
|
2618
|
+
value,
|
|
2619
|
+
disabled: props.disabled,
|
|
2620
|
+
onChange: (event) => {
|
|
2621
|
+
props.onEdit(event.target.value);
|
|
2622
|
+
},
|
|
2623
|
+
children: options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2624
|
+
value: option.value,
|
|
2625
|
+
children: option.label
|
|
2626
|
+
}, option.value))
|
|
2627
|
+
});
|
|
2628
|
+
const label = options.find((option) => option.value === value)?.label ?? "";
|
|
2629
|
+
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;
|
|
2630
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2631
|
+
className: settings_card_module_css_default.selectWrap,
|
|
2632
|
+
ref: wrapRef,
|
|
2633
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2634
|
+
type: "button",
|
|
2635
|
+
id,
|
|
2636
|
+
className: `${settings_card_module_css_default.select} ${settings_card_module_css_default.selectButton}`,
|
|
2637
|
+
disabled: props.disabled,
|
|
2638
|
+
"aria-haspopup": "listbox",
|
|
2639
|
+
"aria-expanded": open,
|
|
2640
|
+
"aria-activedescendant": open ? `${id}-o${activeIndex}` : void 0,
|
|
2641
|
+
"aria-invalid": props.invalid || void 0,
|
|
2642
|
+
onClick: onTriggerClick,
|
|
2643
|
+
onKeyDown,
|
|
2644
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2645
|
+
className: settings_card_module_css_default.selectLabel,
|
|
2646
|
+
children: label
|
|
2647
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2648
|
+
width: "14",
|
|
2649
|
+
height: "14",
|
|
2650
|
+
viewBox: "0 0 14 14",
|
|
2651
|
+
fill: "none",
|
|
2652
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2653
|
+
className: open ? `${settings_card_module_css_default.selectChevron} ${settings_card_module_css_default.selectChevronOpen}` : settings_card_module_css_default.selectChevron,
|
|
2654
|
+
"aria-hidden": "true",
|
|
2655
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
2656
|
+
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",
|
|
2657
|
+
fill: "currentColor"
|
|
2658
|
+
})
|
|
2659
|
+
})]
|
|
2660
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2661
|
+
className: popupClass,
|
|
2662
|
+
role: "listbox",
|
|
2663
|
+
ref: popupRef,
|
|
2664
|
+
children: options.map((option, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2665
|
+
id: `${id}-o${index}`,
|
|
2666
|
+
role: "option",
|
|
2667
|
+
"aria-selected": option.value === value,
|
|
2668
|
+
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}` : ""}`,
|
|
2669
|
+
onClick: () => {
|
|
2670
|
+
commit(index);
|
|
2671
|
+
},
|
|
2672
|
+
children: option.label
|
|
2673
|
+
}, option.value))
|
|
2674
|
+
}) : null]
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2479
2677
|
/** A staged boolean field: 继承 / 开 / 关. */
|
|
2480
2678
|
function BooleanField(props) {
|
|
2481
2679
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2501,28 +2699,26 @@ window.__ModuleLoader__.load({
|
|
|
2501
2699
|
})]
|
|
2502
2700
|
}) : null]
|
|
2503
2701
|
}),
|
|
2504
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2702
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectField, {
|
|
2505
2703
|
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", {
|
|
2704
|
+
options: [
|
|
2705
|
+
{
|
|
2514
2706
|
value: "",
|
|
2515
|
-
|
|
2516
|
-
}
|
|
2517
|
-
|
|
2707
|
+
label: props.inheritLabel
|
|
2708
|
+
},
|
|
2709
|
+
{
|
|
2518
2710
|
value: "true",
|
|
2519
|
-
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2711
|
+
label: props.onLabel
|
|
2712
|
+
},
|
|
2713
|
+
{
|
|
2522
2714
|
value: "false",
|
|
2523
|
-
|
|
2524
|
-
}
|
|
2525
|
-
]
|
|
2715
|
+
label: props.offLabel
|
|
2716
|
+
}
|
|
2717
|
+
],
|
|
2718
|
+
value: props.text,
|
|
2719
|
+
disabled: props.disabled,
|
|
2720
|
+
invalid: props.invalid,
|
|
2721
|
+
onEdit: props.onEdit
|
|
2526
2722
|
}),
|
|
2527
2723
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2528
2724
|
className: settings_card_module_css_default.hint,
|
|
@@ -3116,6 +3312,7 @@ window.__ModuleLoader__.load({
|
|
|
3116
3312
|
"update.found": "发现新版本",
|
|
3117
3313
|
"update.foundDetail": "{name} 有新版本 v{version},正在自动更新…",
|
|
3118
3314
|
"update.updating": "正在更新 {name} 至 v{version},可能需要 1-2 分钟…",
|
|
3315
|
+
"update.cooldownNotice": "本次更新由你主动触发,将跳过 pnpm 的 24 小时发布冷却期(供应链保护)直接安装所选版本。",
|
|
3119
3316
|
"update.updatingTitle": "正在自动更新…",
|
|
3120
3317
|
"update.done": "更新完成",
|
|
3121
3318
|
"update.doneDetail": "所有组件已更新到最新版本。",
|
|
@@ -3221,6 +3418,7 @@ window.__ModuleLoader__.load({
|
|
|
3221
3418
|
"update.found": "A new version is available",
|
|
3222
3419
|
"update.foundDetail": "{name} has a newer release v{version}; updating automatically…",
|
|
3223
3420
|
"update.updating": "Updating {name} to v{version}; this may take 1-2 minutes…",
|
|
3421
|
+
"update.cooldownNotice": "This user-initiated update bypasses pnpm's 24-hour release cooldown (supply-chain protection) and installs the selected version immediately.",
|
|
3224
3422
|
"update.updatingTitle": "Updating automatically…",
|
|
3225
3423
|
"update.done": "Update complete",
|
|
3226
3424
|
"update.doneDetail": "All components are up to date.",
|