dsh-plugin-standalone-session 0.1.0 → 0.2.0
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.md +74 -169
- package/lib/client.js +461 -44
- package/lib/types/client/actions.d.ts +19 -0
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/client/selection.d.ts +29 -0
- package/lib/types/client/styles.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,160 +1,81 @@
|
|
|
1
1
|
# dsh-plugin-standalone-session
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/dsh-plugin-standalone-session)
|
|
4
|
+
[](https://github.com/LRainner/dsh-plugin-standalone-session/releases)
|
|
5
|
+
|
|
6
|
+
DeepSeek Harness 的独立会话:**不隶属于任何工作区**,但完整复用原生输入区。
|
|
4
7
|
|
|
5
8
|
- 插件 ID:`standalone-session`
|
|
6
|
-
-
|
|
7
|
-
- 中文名:`独立会话`
|
|
9
|
+
- 中文名:`独立会话` / `Standalone Session`
|
|
8
10
|
- 兼容 DSH:`>=0.2.1-rc.2 <0.3.0-0`
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
## 它解决什么
|
|
11
|
+
- License:MIT
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## 功能
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
15
|
+
- 侧边栏顶部点击 **新建独立会话**,直接创建并打开会话。
|
|
16
|
+
- 上一次由该入口创建的空白会话会复用,避免连点产生大量空会话;管理面板空状态可强制新建。
|
|
17
|
+
- 侧边栏底部 **管理独立会话**:点击行打开,`⋯` 菜单重命名/删除,支持多选批量删除。
|
|
18
|
+
- 原生输入区可用:模型选择、附件、`/` 指令、发送。
|
|
19
|
+
- 重启 DSH 后仍保持独立状态。
|
|
20
|
+
- 不替换原生新建会话、工作区浏览器、会话菜单或聊天 UI。
|
|
20
21
|
|
|
21
22
|
## 安装
|
|
22
23
|
|
|
23
|
-
###
|
|
24
|
+
### npm(推荐)
|
|
24
25
|
|
|
25
|
-
**Web
|
|
26
|
+
**Web**
|
|
26
27
|
|
|
27
28
|
```bash
|
|
28
29
|
dsh plugin --profile web add dsh-plugin-standalone-session
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
重启 `dsh web
|
|
32
|
-
|
|
33
|
-
**Desktop 配置:**
|
|
34
|
-
|
|
35
|
-
DSH 0.2.1-rc.2 的 `dsh` CLI 会拒绝直接管理 desktop 配置:
|
|
36
|
-
|
|
37
|
-
```text
|
|
38
|
-
error: profile "desktop" is managed exclusively by the Electron application
|
|
39
|
-
```
|
|
32
|
+
重启 `dsh web`,硬刷新页面。
|
|
40
33
|
|
|
41
|
-
|
|
34
|
+
**Desktop**
|
|
42
35
|
|
|
43
|
-
|
|
44
|
-
2. 侧边栏 → **插件** → 安装/添加插件。
|
|
45
|
-
3. 输入 npm 包名 `dsh-plugin-standalone-session`(Plugin Manager 也接受 tarball 路径和绝对本地目录)。
|
|
46
|
-
4. 安装完成后,完全退出并重新启动 DeepSeek Harness;宿主端插件只在进程启动时加载。
|
|
47
|
-
5. 如果右侧/左侧 UI 没有立即变化,执行一次窗口重载(`Cmd+R`)。
|
|
36
|
+
打开 **插件 / Plugins** → 安装 → 输入 `dsh-plugin-standalone-session`,然后完全退出并重启 DSH。
|
|
48
37
|
|
|
49
|
-
|
|
38
|
+
桌面端的 profile 由 Electron 管理,不能用 `dsh plugin --profile desktop` 安装。
|
|
50
39
|
|
|
51
|
-
###
|
|
40
|
+
### 本地 tarball
|
|
52
41
|
|
|
53
42
|
```bash
|
|
54
|
-
|
|
55
|
-
npm install
|
|
43
|
+
npm ci
|
|
56
44
|
npm run build
|
|
57
45
|
npm pack
|
|
58
|
-
# 产物:/absolute/path/to/dsh-plugin-standalone-session-0.1.0.tgz
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
**Web 配置:**
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
46
|
dsh plugin --profile web add /absolute/path/to/dsh-plugin-standalone-session-0.1.0.tgz
|
|
65
47
|
```
|
|
66
48
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
**Desktop 配置:** 使用应用内 **插件 / Plugins** 页面,输入 tarball 的绝对路径。
|
|
70
|
-
|
|
71
|
-
> **不要直接安装 GitHub 源码 tarball。** 仓库不包含构建后的 `lib/`,pnpm 会要求执行构建脚本,默认被供应链策略拦截并报 `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`。请使用 npm 包、本地 tarball,或按 pnpm 提示在 profile 的 `pnpm-workspace.yaml` 中显式允许对应 commit 的构建。
|
|
72
|
-
|
|
73
|
-
### 卸载
|
|
74
|
-
|
|
75
|
-
- **Web**:`dsh plugin --profile web remove dsh-plugin-standalone-session`,然后重启 `dsh web`。
|
|
76
|
-
- **Desktop**:在桌面应用的 **插件 / Plugins** 页面卸载,然后完全重启应用。
|
|
77
|
-
|
|
78
|
-
**卸载不会删除任何会话**,也不会自动删除独立会话的工作目录。卸载后:
|
|
79
|
-
|
|
80
|
-
- 已经产生过消息的独立会话仍可作为普通“未分组”会话继续使用(输入区在活动会话中本来就是可用的)。
|
|
81
|
-
- **空白**独立会话缺少本插件的输入区解锁逻辑,输入区会回到 DSH 原生的“选择工作区”状态;可以给它挂一个真实工作区,或重新安装本插件。
|
|
82
|
-
- 如果确认不再需要这些独立会话,可手动清理其工作目录(见“清理”)。
|
|
49
|
+
> 不要直接安装 GitHub 源码 tarball。仓库不包含构建后的 `lib/`,pnpm 会因构建脚本策略报 `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`;请用 npm 包或本地 tarball。
|
|
83
50
|
|
|
84
51
|
## 使用
|
|
85
52
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
- 点击这一行会**立即创建并进入**一个新的独立会话,不再先显示管理面板。
|
|
91
|
-
- 如果上一次由这个入口创建的会话仍然是空白且仍然存在,会直接复用它,避免误点/连点产生大量空白会话。
|
|
92
|
-
- 创建失败会停留在轻量的 launcher 页,提供“重试”和“管理已有会话”。
|
|
93
|
-
|
|
94
|
-
其他入口:
|
|
95
|
-
|
|
96
|
-
1. **管理面板为空时**:空状态里的 `新建独立会话` 总是创建全新的空白会话。
|
|
97
|
-
2. **命令菜单**:在任意会话的输入框输入 `/`,选择 `独立会话:新建`(英文 `Standalone Session: New`)。
|
|
98
|
-
|
|
99
|
-
创建后会直接在**主视图**打开原生会话页。输入框、模型选择、附件按钮立即可用。
|
|
100
|
-
|
|
101
|
-
### 管理
|
|
53
|
+
- **新建**:点击侧边栏顶部 `新建独立会话`。如果上次创建的会话仍为空白,会直接复用它。
|
|
54
|
+
- **管理**:点击底部 `管理独立会话(n)`,或命令菜单里的 `独立会话:管理`。
|
|
55
|
+
- **命令**:输入 `/`,选择 `独立会话:新建` / `独立会话:管理`。
|
|
56
|
+
- **删除**:使用 DSH 原生归档,不是物理删除;已删除会话可在归档视图中恢复。
|
|
102
57
|
|
|
103
|
-
|
|
58
|
+
管理面板点击整行打开会话;行尾 `⋯` 提供重命名和删除。点击右上角 `选择` 进入批量模式,可全选/半选后一次删除;部分失败会保留失败项。删除最后一条后会停留在空状态,不会跳回新会话页。
|
|
104
59
|
|
|
105
|
-
|
|
106
|
-
- 点击会话行:切回原生会话页;行尾 `⋯` 菜单提供重命名和删除。
|
|
107
|
-
- `重命名`:调用原生 Session Controller 的重命名能力,标题投影同步更新。
|
|
108
|
-
- `删除`:调用原生归档(DSH 没有物理删除会话的公开 API)。
|
|
109
|
-
|
|
110
|
-
### 删除语义
|
|
111
|
-
|
|
112
|
-
DSH 把“删除”实现为**归档**:会话从普通列表消失,但仍可在原生归档视图中恢复。本插件遵守这一点:
|
|
113
|
-
|
|
114
|
-
- 从未产生过用户消息、且目录为空的独立会话:归档后,宿主端会删除它的专用工作目录。
|
|
115
|
-
- 从未产生过用户消息、但目录非空(例如 agent 已经写入文件):保留目录,避免把“没有记录到消息”误判为“可以删除内容”。
|
|
116
|
-
- 已经产生过消息的独立会话:归档后**保留**工作目录,以便日后从归档恢复时仍能继续在原目录工作;即使目录为空也不删除。
|
|
117
|
-
- 清理失败:宿主端记录 `[standalone-session]` 前缀的日志,并在 `registry.json` 中保留 `cleanupFailed` / `cleanupError`,下次扫描重试;这不会阻塞归档主流程。
|
|
118
|
-
|
|
119
|
-
## 与普通工作区会话的区别
|
|
60
|
+
## 与普通会话的区别
|
|
120
61
|
|
|
121
62
|
| | 普通工作区会话 | 独立会话 |
|
|
122
63
|
| --- | --- | --- |
|
|
123
64
|
| 工作区归属 | 属于某个工作区 | 不属于任何工作区 |
|
|
124
|
-
| 工作区注册表 |
|
|
125
|
-
|
|
|
126
|
-
| badge | 无 | `独` / `独立会话 · 无工作区` |
|
|
65
|
+
| 工作区注册表 | 有记录 | 没有记录 |
|
|
66
|
+
| 侧边栏 | 工作区分组 | 顶层“未分组” + 独立面板 |
|
|
127
67
|
| 工作目录 | 工作区目录 | `~/.dsh/dsh-standalone-sessions/<sessionId>` |
|
|
128
|
-
|
|
|
129
|
-
| 重启恢复 | 是 | 是(靠会话自身 cwd 标记 + 宿主 registry) |
|
|
130
|
-
|
|
131
|
-
## 实现方案(没有使用临时工作区)
|
|
132
|
-
|
|
133
|
-
调研结论:原生输入区的 `inert` 条件来自 Conversation 的 `hero && chipTitle === undefined`,而 `chipTitle` 通常来自“当前会话是否属于某个工作区”。常见做法是**临时创建一个工作区**并挂载会话,让输入区解锁,首条消息后再删除该工作区。
|
|
68
|
+
| 输入区 | 原生启用 | 插件解锁同一个原生 InputBar |
|
|
134
69
|
|
|
135
|
-
|
|
70
|
+
## 实现
|
|
136
71
|
|
|
137
|
-
|
|
138
|
-
2. 若在首条消息前崩溃或用户切走,还可能留下“孤儿工作区”,与“临时工作区不得出现在用户工作区列表”的要求冲突。
|
|
72
|
+
没有创建临时工作区,也没有 patch 原生 workspace 逻辑:
|
|
139
73
|
|
|
140
|
-
|
|
74
|
+
- 会话自身 `cwd` 带有独立标记;宿主 `registry.json` 记录生命周期。
|
|
75
|
+
- 客户端只包装 `conversation.composer.bar`:当 cwd 是独立标记且原生输入区为 disabled 时解锁。
|
|
76
|
+
- 不替换原生聊天、工作区选择器、会话菜单或新建按钮。
|
|
141
77
|
|
|
142
|
-
|
|
143
|
-
- 原生 InputBar 仍然负责编辑器、模型选择、附件、斜杠指令、提交、停止和错误展示;插件没有重写聊天 UI,也没有修改会话数据模型。
|
|
144
|
-
- 适配器在插件卸载时恢复原组件;如果 DSH 未来改变该 slot 的形状,适配器会安全地不生效(输入区回到原生状态),管理面板仍然可用。
|
|
145
|
-
|
|
146
|
-
其他部分:
|
|
147
|
-
|
|
148
|
-
- **持久化标记**:会话的 `cwd` 是 `~/.dsh/dsh-standalone-sessions/<sessionId>`;`<sessionId>` 与目录名一致。这个标记写入 DSH 的会话 header,重启后仍然存在。宿主端另有 `~/.dsh/dsh-standalone-sessions/registry.json` 记录 `standalone: true`、`hadMessage`、清理状态等生命周期信息;浏览器端只依赖 cwd 标记,因此 registry 损坏不会导致会话“消失”。
|
|
149
|
-
- **点击即新建**:`sidebar.panellist` 的行点击被 DSH 固定为 `selectPanel(id)`,插件无法替换整行动作。因此顶栏行指向一个专用的 launcher main panel:它挂载时创建(或复用空白)会话并 `openSession`,用户感知为“点一下就进入新对话”。launcher 还会记录一次真实行点击作为“arm”信号,布局/历史恢复该 panel 时只显示 CTA,不会静默创建会话。
|
|
150
|
-
- **宿主端**:监听 `api-session/added` / `api-session/activity`,维护 registry;每 2s 读取 `workspaceRegistry.archivedSessionIds`,对已归档的独立会话执行清理;启动时回收失败的空白目录。
|
|
151
|
-
- **并发创建**:每次创建由客户端铸造 `session-<uuid>`,目录唯一;registry 写入串行化并使用临时文件 + `rename` 原子替换。
|
|
152
|
-
- **失败回滚**:创建失败时不会调用 `openSession`;宿主扫描会回收 60s 以上、没有 live session、且为空目录的残留。删除清理失败会记录并重试,不影响归档。
|
|
153
|
-
- **不破坏原生行为**:没有 patch `uiWorkspace.startSession`,没有替换 `sidebar.workspaces`,没有改工作区选择器或会话菜单。
|
|
154
|
-
|
|
155
|
-
## 数据与清理
|
|
156
|
-
|
|
157
|
-
默认工作目录:
|
|
78
|
+
数据目录:
|
|
158
79
|
|
|
159
80
|
```text
|
|
160
81
|
~/.dsh/dsh-standalone-sessions/
|
|
@@ -162,78 +83,62 @@ DSH 把“删除”实现为**归档**:会话从普通列表消失,但仍可
|
|
|
162
83
|
└── session-<uuid>/
|
|
163
84
|
```
|
|
164
85
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
-
|
|
86
|
+
删除语义:
|
|
87
|
+
|
|
88
|
+
- 空白且目录为空:归档后删除工作目录。
|
|
89
|
+
- 目录非空或产生过消息:保留目录,便于恢复。
|
|
90
|
+
- 清理失败会记录日志并重试,不阻塞归档。
|
|
168
91
|
|
|
169
|
-
|
|
92
|
+
## 兼容与限制
|
|
170
93
|
|
|
171
|
-
|
|
94
|
+
- 只在 DSH `>=0.2.1-rc.2 <0.3.0-0` 验证。
|
|
95
|
+
- 独立会话仍会出现在原生“未分组”区域;插件没有替换 WorkspaceBrowser。
|
|
96
|
+
- 原生 hero 的 `选择工作区` chip 仍在;插件只在右侧显示 `独立会话 · 无工作区`。
|
|
97
|
+
- 卸载后,空白独立会话的输入区会回到原生 inert 状态;有消息的会话不受影响。
|
|
98
|
+
- DSH 没有物理删除会话 API,删除等于归档。
|
|
99
|
+
- 自定义 `DSH_HOME` 时,独立会话目录仍固定在 `~/.dsh/dsh-standalone-sessions`。
|
|
100
|
+
|
|
101
|
+
## 开发
|
|
172
102
|
|
|
173
103
|
```bash
|
|
174
|
-
|
|
175
|
-
|
|
104
|
+
npm ci
|
|
105
|
+
npm run check # typecheck + build + 59 个单元测试
|
|
106
|
+
npm test
|
|
176
107
|
```
|
|
177
108
|
|
|
178
|
-
|
|
109
|
+
产物:
|
|
179
110
|
|
|
180
|
-
|
|
111
|
+
- `lib/index.js`:宿主插件
|
|
112
|
+
- `lib/client.js`:客户端 Module Loader bundle
|
|
113
|
+
- `lib/types/**`:类型声明
|
|
181
114
|
|
|
182
|
-
|
|
183
|
-
- 本插件**不依赖** npm 上的 `@deepseek-ai/dsh-tools`、`@deepseek-ai/dsh-client-ui-*` 等子包。当前 npm 上这些子包仍是过旧的 `0.0.1-rc.1`(`dsh-tools` 为 `0.0.1-rc.1`),与 `0.2.1-rc.2` 宿主不兼容;插件在运行时使用宿主提供的模块图(React、slot、Session/Workspace controllers),构建期只依赖 TypeScript、esbuild 和 Vitest。
|
|
184
|
-
- 自定义 `DSH_HOME`:本插件的工作目录固定为 `~/.dsh/dsh-standalone-sessions`,因为浏览器端只能拿到 `$host.home`。如果使用自定义 `DSH_HOME`,DSH 的 profile/session 数据会在自定义位置,但独立会话目录仍在 `~/.dsh` 下;会话功能不受影响,只是目录不跟随 `DSH_HOME`。
|
|
185
|
-
- 如果未来 DSH 修改 `conversation.composer.bar` slot 或原生 InputBar props,输入区解锁可能失效;插件会安全降级,不损坏会话或工作区数据。
|
|
115
|
+
## 发布
|
|
186
116
|
|
|
187
|
-
|
|
117
|
+
发布使用 [Release Please](https://github.com/googleapis/release-please-action):合并 `feat:` / `fix:` 后自动创建 release PR;合并 release PR 后自动创建 tag、GitHub Release,并通过 npm Trusted Publishing 发布和附加 tarball。
|
|
188
118
|
|
|
189
|
-
|
|
190
|
-
- **删除 = 归档**:DSH 没有公开的物理删除会话 API;已删除会话可从原生归档视图恢复。空白会话的目录会被清理;已使用会话的目录会保留。
|
|
191
|
-
- **空白会话依赖本插件解锁输入区**:卸载后空白独立会话的输入区会回到原生 inert 状态。
|
|
192
|
-
- **没有全局快捷键**:只注册了命令菜单项和侧边栏入口,避免与原生快捷键冲突。
|
|
193
|
-
- **顶栏行本质是导航键**:DSH 的 `sidebar.panellist` 点击固定执行 `selectPanel`,插件通过 launcher main panel 实现“点击即创建”;如果未来 DSH 改变 panel/行语义,需要重新适配。
|
|
194
|
-
- **没有自动模型回复测试**:本地验证使用了隔离的 DSH profile 和真实浏览器;首次消息成功写入并保持独立状态,但测试环境的 API Key 无效,模型返回 `API 密钥无效`。这不影响会话、输入区和独立标记的验证。
|
|
119
|
+
首次使用需要在 npm 包设置中配置 Trusted Publisher:
|
|
195
120
|
|
|
196
|
-
|
|
121
|
+
- Repository:`LRainner/dsh-plugin-standalone-session`
|
|
122
|
+
- Workflow:`release.yml`
|
|
197
123
|
|
|
198
|
-
|
|
199
|
-
npm install
|
|
200
|
-
npm run typecheck # tsc --noEmit
|
|
201
|
-
npm run build # 宿主 ESM + 客户端 Module Loader bundle + .d.ts
|
|
202
|
-
npm test # Vitest:51 个单元测试
|
|
203
|
-
npm run check # typecheck + build + test
|
|
204
|
-
```
|
|
124
|
+
同时确保仓库 Actions 设置允许创建 pull request。
|
|
205
125
|
|
|
206
|
-
|
|
126
|
+
## 卸载
|
|
207
127
|
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
src/host/registry.ts 原子写入的 registry、目录清理策略
|
|
211
|
-
src/shared/standalone.ts 纯函数:路径标记、id、registry 归一化、清理策略
|
|
212
|
-
src/client/index.ts 客户端入口(slot 注册、命令、主题样式)
|
|
213
|
-
src/client/adapters.ts 原生输入条适配器(仅解锁独立会话)
|
|
214
|
-
src/client/components.tsx 独立会话面板、launcher、侧边栏入口、行 badge、toast
|
|
215
|
-
src/client/launcher.ts launcher 的创建去重、空白复用、点击 arm 策略
|
|
216
|
-
src/client/panels.ts main panel id(launcher / 管理)
|
|
217
|
-
src/client/format.ts 相对时间本地化
|
|
218
|
-
src/client/create.ts 创建策略(可注入测试)
|
|
219
|
-
src/client/hooks.ts Session/Workspace 可观察源的 React 绑定
|
|
220
|
-
test/standalone.test.ts 创建策略、标记、回滚、composer 策略
|
|
221
|
-
test/registry.test.ts registry 持久化、并发、清理、失败重试、孤儿回收
|
|
222
|
-
test/launcher.test.ts launcher 创建去重、空白复用、arm 窗口
|
|
223
|
-
test/actions.test.ts 管理入口与显式创建/打开
|
|
224
|
-
test/format.test.ts 相对时间本地化
|
|
128
|
+
```bash
|
|
129
|
+
dsh plugin --profile web remove dsh-plugin-standalone-session
|
|
225
130
|
```
|
|
226
131
|
|
|
227
|
-
|
|
132
|
+
Desktop 在 **插件 / Plugins** 页面卸载,然后完全重启。
|
|
228
133
|
|
|
229
|
-
|
|
230
|
-
- `lib/client.js`:`window.__ModuleLoader__.load({ id, factory })` 形式的客户端 bundle。
|
|
231
|
-
- `lib/types/**`:TypeScript 声明。
|
|
134
|
+
卸载不会删除会话。确需清理独立会话目录时:
|
|
232
135
|
|
|
233
|
-
|
|
136
|
+
```bash
|
|
137
|
+
rm -rf ~/.dsh/dsh-standalone-sessions
|
|
138
|
+
```
|
|
234
139
|
|
|
235
|
-
|
|
140
|
+
这会删除工作目录,可能包含 agent 写入的文件;会话历史仍由 DSH 管理。
|
|
236
141
|
|
|
237
142
|
## License
|
|
238
143
|
|
|
239
|
-
MIT
|
|
144
|
+
MIT
|
package/lib/client.js
CHANGED
|
@@ -311,6 +311,20 @@ async function renameStandalone(ctx, sessionId, title) {
|
|
|
311
311
|
async function archiveStandalone(ctx, sessionId) {
|
|
312
312
|
await ctx.workspaces.archiveSession(sessionId, { stopActivity: true });
|
|
313
313
|
}
|
|
314
|
+
async function archiveStandalones(ctx, sessionIds, onProgress) {
|
|
315
|
+
const archived = [];
|
|
316
|
+
const failed = [];
|
|
317
|
+
for (const [index, sessionId] of sessionIds.entries()) {
|
|
318
|
+
try {
|
|
319
|
+
await archiveStandalone(ctx, sessionId);
|
|
320
|
+
archived.push(sessionId);
|
|
321
|
+
} catch (reason) {
|
|
322
|
+
failed.push({ sessionId, message: messageOf(reason) });
|
|
323
|
+
}
|
|
324
|
+
onProgress?.(index + 1, sessionIds.length);
|
|
325
|
+
}
|
|
326
|
+
return { archived, failed };
|
|
327
|
+
}
|
|
314
328
|
function messageOf(error) {
|
|
315
329
|
return error instanceof Error ? error.message : String(error);
|
|
316
330
|
}
|
|
@@ -462,6 +476,19 @@ var zh = {
|
|
|
462
476
|
"panel.deleteTitle": "\u5220\u9664\u72EC\u7ACB\u4F1A\u8BDD\uFF1F",
|
|
463
477
|
"panel.deleteBody": "\u4F1A\u8BDD\u5C06\u4ECE\u5217\u8868\u548C\u539F\u751F\u4F1A\u8BDD\u89C6\u56FE\u4E2D\u79FB\u9664\uFF08DSH \u4EE5\u5F52\u6863\u5B9E\u73B0\u5220\u9664\uFF09\uFF0C\u4F46\u4ECD\u53EF\u5728\u5F52\u6863\u89C6\u56FE\u4E2D\u6062\u590D\u3002\u7A7A\u767D\u4F1A\u8BDD\u7684\u4E34\u65F6\u76EE\u5F55\u4F1A\u88AB\u6E05\u7406\uFF1B\u5DF2\u7ECF\u4EA7\u751F\u8FC7\u6D88\u606F\u7684\u4F1A\u8BDD\u4F1A\u4FDD\u7559\u5176\u76EE\u5F55\u3002",
|
|
464
478
|
"panel.deleteConfirm": "\u5220\u9664",
|
|
479
|
+
"panel.select": "\u9009\u62E9",
|
|
480
|
+
"panel.selectToolbar": "\u6279\u91CF\u9009\u62E9",
|
|
481
|
+
"panel.selectAll": "\u5168\u9009",
|
|
482
|
+
"panel.selectAllLabel": "\u5168\u9009\u72EC\u7ACB\u4F1A\u8BDD",
|
|
483
|
+
"panel.selectedCount": "\u5DF2\u9009 {n} / {total}",
|
|
484
|
+
"panel.selectRow": "\u9009\u62E9\u201C{title}\u201D",
|
|
485
|
+
"panel.batchDelete": "\u5220\u9664",
|
|
486
|
+
"panel.batchDeleteTitle": "\u5220\u9664 {n} \u4E2A\u72EC\u7ACB\u4F1A\u8BDD\uFF1F",
|
|
487
|
+
"panel.batchDeleteBody": "\u5C06\u5F52\u6863\u8FD9 {n} \u4E2A\u4F1A\u8BDD\uFF0C\u53EF\u5728\u5F52\u6863\u89C6\u56FE\u4E2D\u6062\u590D\u3002",
|
|
488
|
+
"panel.batchDeleteRunning": "\u5176\u4E2D {n} \u4E2A\u6B63\u5728\u8FD0\u884C\uFF0C\u5F52\u6863\u4F1A\u505C\u6B62\u5B83\u4EEC\u3002",
|
|
489
|
+
"panel.batchDeleteDirs": "\u7A7A\u767D\u4E14\u76EE\u5F55\u4E3A\u7A7A\u7684\u4F1A\u8BDD\u4F1A\u6E05\u7406\u5DE5\u4F5C\u76EE\u5F55\uFF1B\u5DF2\u6709\u6D88\u606F\u6216\u76EE\u5F55\u975E\u7A7A\u7684\u4F1A\u8BDD\u4F1A\u4FDD\u7559\u76EE\u5F55\u3002",
|
|
490
|
+
"panel.batchDeleting": "\u5F52\u6863\u4E2D {done}/{total}\u2026",
|
|
491
|
+
"panel.batchPartial": "{n} \u4E2A\u4F1A\u8BDD\u5F52\u6863\u5931\u8D25\uFF0C\u5DF2\u4FDD\u7559\u9009\u4E2D\u72B6\u6001\u3002",
|
|
465
492
|
"panel.deleteKeepDir": "\u8BE5\u4F1A\u8BDD\u5DF2\u6709\u6D88\u606F\uFF0C\u76EE\u5F55\u4F1A\u88AB\u4FDD\u7559\u3002",
|
|
466
493
|
"panel.cancel": "\u53D6\u6D88",
|
|
467
494
|
"panel.renameTitle": "\u91CD\u547D\u540D\u72EC\u7ACB\u4F1A\u8BDD",
|
|
@@ -524,6 +551,19 @@ var en = {
|
|
|
524
551
|
"panel.deleteTitle": "Delete standalone session?",
|
|
525
552
|
"panel.deleteBody": "The session leaves the list and the native session view (DSH implements deletion as archive), and remains recoverable from the archived view. The directory of an unused session is removed; a session that already carried messages keeps its directory.",
|
|
526
553
|
"panel.deleteConfirm": "Delete",
|
|
554
|
+
"panel.select": "Select",
|
|
555
|
+
"panel.selectToolbar": "Bulk selection",
|
|
556
|
+
"panel.selectAll": "Select all",
|
|
557
|
+
"panel.selectAllLabel": "Select all standalone sessions",
|
|
558
|
+
"panel.selectedCount": "{n} / {total} selected",
|
|
559
|
+
"panel.selectRow": 'Select "{title}"',
|
|
560
|
+
"panel.batchDelete": "Delete",
|
|
561
|
+
"panel.batchDeleteTitle": "Delete {n} standalone session(s)?",
|
|
562
|
+
"panel.batchDeleteBody": "The {n} selected session(s) will be archived and can be restored from the archived view.",
|
|
563
|
+
"panel.batchDeleteRunning": "{n} session(s) are running; archiving will stop them.",
|
|
564
|
+
"panel.batchDeleteDirs": "Blank sessions with empty directories lose their working directory; sessions with messages or non-empty directories keep theirs.",
|
|
565
|
+
"panel.batchDeleting": "Archiving {done}/{total}\u2026",
|
|
566
|
+
"panel.batchPartial": "Failed to archive {n} session(s); selection was kept.",
|
|
527
567
|
"panel.deleteKeepDir": "This session already carried messages; its directory is retained.",
|
|
528
568
|
"panel.cancel": "Cancel",
|
|
529
569
|
"panel.renameTitle": "Rename standalone session",
|
|
@@ -568,6 +608,39 @@ var en = {
|
|
|
568
608
|
"error.noSession": "No session is available"
|
|
569
609
|
};
|
|
570
610
|
|
|
611
|
+
// src/client/selection.ts
|
|
612
|
+
function toggleSelection(selected, sessionId) {
|
|
613
|
+
const next = new Set(selected);
|
|
614
|
+
if (next.has(sessionId)) next.delete(sessionId);
|
|
615
|
+
else next.add(sessionId);
|
|
616
|
+
return next;
|
|
617
|
+
}
|
|
618
|
+
function selectAllRows(rows) {
|
|
619
|
+
return new Set(rows.map((row) => row.id));
|
|
620
|
+
}
|
|
621
|
+
function selectedRows(rows, selected) {
|
|
622
|
+
return rows.filter((row) => selected.has(row.id));
|
|
623
|
+
}
|
|
624
|
+
function pruneSelection(rows, selected) {
|
|
625
|
+
if (selected.size === 0) return selected;
|
|
626
|
+
const live = new Set(rows.map((row) => row.id));
|
|
627
|
+
let changed = false;
|
|
628
|
+
const next = /* @__PURE__ */ new Set();
|
|
629
|
+
for (const id of selected) {
|
|
630
|
+
if (live.has(id)) next.add(id);
|
|
631
|
+
else changed = true;
|
|
632
|
+
}
|
|
633
|
+
return changed ? next : selected;
|
|
634
|
+
}
|
|
635
|
+
function selectionState(rows, selected) {
|
|
636
|
+
const count = selectedRows(rows, selected).length;
|
|
637
|
+
return {
|
|
638
|
+
count,
|
|
639
|
+
all: rows.length > 0 && count === rows.length,
|
|
640
|
+
some: count > 0 && count < rows.length
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
571
644
|
// src/client/components.tsx
|
|
572
645
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
573
646
|
function StandaloneIcon({
|
|
@@ -1031,6 +1104,72 @@ function DeleteDialog({
|
|
|
1031
1104
|
}
|
|
1032
1105
|
);
|
|
1033
1106
|
}
|
|
1107
|
+
function BatchDeleteDialog({
|
|
1108
|
+
targets,
|
|
1109
|
+
t,
|
|
1110
|
+
progress,
|
|
1111
|
+
onCancel,
|
|
1112
|
+
onConfirm
|
|
1113
|
+
}) {
|
|
1114
|
+
const [submitting, setSubmitting] = (0, import_react3.useState)(false);
|
|
1115
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
1116
|
+
const busy = submitting || progress !== null;
|
|
1117
|
+
const running = targets.filter((row) => row.running).length;
|
|
1118
|
+
const hasBlank = targets.some((row) => row.blank);
|
|
1119
|
+
const confirm = async () => {
|
|
1120
|
+
if (busy) return;
|
|
1121
|
+
setSubmitting(true);
|
|
1122
|
+
setError(null);
|
|
1123
|
+
try {
|
|
1124
|
+
await onConfirm();
|
|
1125
|
+
} catch (reason) {
|
|
1126
|
+
setError(messageOf(reason));
|
|
1127
|
+
setSubmitting(false);
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1131
|
+
DialogFrame,
|
|
1132
|
+
{
|
|
1133
|
+
title: t("panel.batchDeleteTitle", { n: targets.length }),
|
|
1134
|
+
busy,
|
|
1135
|
+
onCancel,
|
|
1136
|
+
footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1137
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1138
|
+
"button",
|
|
1139
|
+
{
|
|
1140
|
+
type: "button",
|
|
1141
|
+
className: "ss-button ss-buttonQuiet",
|
|
1142
|
+
disabled: busy,
|
|
1143
|
+
onClick: onCancel,
|
|
1144
|
+
children: t("panel.cancel")
|
|
1145
|
+
}
|
|
1146
|
+
),
|
|
1147
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1148
|
+
"button",
|
|
1149
|
+
{
|
|
1150
|
+
type: "button",
|
|
1151
|
+
className: "ss-button ss-buttonDanger",
|
|
1152
|
+
disabled: busy,
|
|
1153
|
+
"aria-busy": progress !== null,
|
|
1154
|
+
onClick: () => {
|
|
1155
|
+
void confirm();
|
|
1156
|
+
},
|
|
1157
|
+
children: progress === null ? t("panel.batchDelete") : t("panel.batchDeleting", {
|
|
1158
|
+
done: progress.done,
|
|
1159
|
+
total: progress.total
|
|
1160
|
+
})
|
|
1161
|
+
}
|
|
1162
|
+
)
|
|
1163
|
+
] }),
|
|
1164
|
+
children: [
|
|
1165
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-dialogBody", children: t("panel.batchDeleteBody", { n: targets.length }) }),
|
|
1166
|
+
running > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-batchWarning", children: t("panel.batchDeleteRunning", { n: running }) }) : null,
|
|
1167
|
+
hasBlank ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-batchDirs", children: t("panel.batchDeleteDirs") }) : null,
|
|
1168
|
+
error !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-error", children: error }) : null
|
|
1169
|
+
]
|
|
1170
|
+
}
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1034
1173
|
function createStandalonePanel(ctx) {
|
|
1035
1174
|
const fallbackT = ctx.locale.bind(NS);
|
|
1036
1175
|
return function StandalonePanel(props) {
|
|
@@ -1041,8 +1180,30 @@ function createStandalonePanel(ctx) {
|
|
|
1041
1180
|
const [renameTarget, setRenameTarget] = (0, import_react3.useState)(null);
|
|
1042
1181
|
const [deleteTarget, setDeleteTarget] = (0, import_react3.useState)(null);
|
|
1043
1182
|
const [menuTarget, setMenuTarget] = (0, import_react3.useState)(null);
|
|
1183
|
+
const [selectionMode, setSelectionMode] = (0, import_react3.useState)(false);
|
|
1184
|
+
const [selectedIds, setSelectedIds] = (0, import_react3.useState)(() => /* @__PURE__ */ new Set());
|
|
1185
|
+
const [batchTargets, setBatchTargets] = (0, import_react3.useState)(null);
|
|
1186
|
+
const [batchProgress, setBatchProgress] = (0, import_react3.useState)(null);
|
|
1044
1187
|
const menuRef = (0, import_react3.useRef)(null);
|
|
1045
1188
|
const menuButtonRef = (0, import_react3.useRef)(null);
|
|
1189
|
+
const selectAllRef = (0, import_react3.useRef)(null);
|
|
1190
|
+
const selectButtonRef = (0, import_react3.useRef)(null);
|
|
1191
|
+
const selected = (0, import_react3.useMemo)(() => selectedRows(rows, selectedIds), [rows, selectedIds]);
|
|
1192
|
+
const state = (0, import_react3.useMemo)(() => selectionState(rows, selectedIds), [rows, selectedIds]);
|
|
1193
|
+
const locked = busy !== null || batchProgress !== null;
|
|
1194
|
+
const exitSelection = () => {
|
|
1195
|
+
setSelectionMode(false);
|
|
1196
|
+
setSelectedIds(/* @__PURE__ */ new Set());
|
|
1197
|
+
setMenuTarget(null);
|
|
1198
|
+
setError(null);
|
|
1199
|
+
selectButtonRef.current?.focus();
|
|
1200
|
+
};
|
|
1201
|
+
const enterSelection = () => {
|
|
1202
|
+
if (locked) return;
|
|
1203
|
+
setMenuTarget(null);
|
|
1204
|
+
setSelectedIds(/* @__PURE__ */ new Set());
|
|
1205
|
+
setSelectionMode(true);
|
|
1206
|
+
};
|
|
1046
1207
|
(0, import_react3.useEffect)(() => {
|
|
1047
1208
|
if (menuTarget === null) return;
|
|
1048
1209
|
menuRef.current?.querySelector("button")?.focus();
|
|
@@ -1063,8 +1224,36 @@ function createStandalonePanel(ctx) {
|
|
|
1063
1224
|
document.removeEventListener("keydown", onKeyDown);
|
|
1064
1225
|
};
|
|
1065
1226
|
}, [menuTarget]);
|
|
1227
|
+
(0, import_react3.useEffect)(() => {
|
|
1228
|
+
const pruned = pruneSelection(rows, selectedIds);
|
|
1229
|
+
if (pruned !== selectedIds) setSelectedIds(pruned);
|
|
1230
|
+
if (rows.length === 0 && selectionMode) {
|
|
1231
|
+
setSelectionMode(false);
|
|
1232
|
+
setSelectedIds(/* @__PURE__ */ new Set());
|
|
1233
|
+
}
|
|
1234
|
+
}, [rows, selectedIds, selectionMode]);
|
|
1235
|
+
(0, import_react3.useEffect)(() => {
|
|
1236
|
+
if (selectionMode) selectAllRef.current?.focus();
|
|
1237
|
+
}, [selectionMode]);
|
|
1238
|
+
(0, import_react3.useEffect)(() => {
|
|
1239
|
+
if (selectAllRef.current !== null) selectAllRef.current.indeterminate = state.some;
|
|
1240
|
+
}, [state.some, selectionMode]);
|
|
1241
|
+
(0, import_react3.useEffect)(() => {
|
|
1242
|
+
if (!selectionMode) return;
|
|
1243
|
+
const onKey = (event) => {
|
|
1244
|
+
if (batchTargets !== null || renameTarget !== null || deleteTarget !== null || menuTarget !== null) {
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
if (event.key === "Escape") {
|
|
1248
|
+
event.preventDefault();
|
|
1249
|
+
exitSelection();
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
document.addEventListener("keydown", onKey);
|
|
1253
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
1254
|
+
}, [selectionMode, batchTargets, renameTarget, deleteTarget, menuTarget]);
|
|
1066
1255
|
const create = async () => {
|
|
1067
|
-
if (
|
|
1256
|
+
if (locked) return;
|
|
1068
1257
|
setBusy("new");
|
|
1069
1258
|
setError(null);
|
|
1070
1259
|
try {
|
|
@@ -1076,7 +1265,7 @@ function createStandalonePanel(ctx) {
|
|
|
1076
1265
|
}
|
|
1077
1266
|
};
|
|
1078
1267
|
const open = (row) => {
|
|
1079
|
-
if (
|
|
1268
|
+
if (locked) return;
|
|
1080
1269
|
setMenuTarget(null);
|
|
1081
1270
|
try {
|
|
1082
1271
|
ctx.uiWorkspace.openSession(row.id);
|
|
@@ -1084,6 +1273,14 @@ function createStandalonePanel(ctx) {
|
|
|
1084
1273
|
setError(t("panel.actionFailed", { message: messageOf(reason) }));
|
|
1085
1274
|
}
|
|
1086
1275
|
};
|
|
1276
|
+
const toggleRow = (sessionId) => {
|
|
1277
|
+
if (locked) return;
|
|
1278
|
+
setSelectedIds((current2) => toggleSelection(current2, sessionId));
|
|
1279
|
+
};
|
|
1280
|
+
const toggleAll = () => {
|
|
1281
|
+
if (locked) return;
|
|
1282
|
+
setSelectedIds(state.all ? /* @__PURE__ */ new Set() : selectAllRows(rows));
|
|
1283
|
+
};
|
|
1087
1284
|
const rename = async (row, title) => {
|
|
1088
1285
|
setBusy(row.id);
|
|
1089
1286
|
setError(null);
|
|
@@ -1109,16 +1306,109 @@ function createStandalonePanel(ctx) {
|
|
|
1109
1306
|
setBusy(null);
|
|
1110
1307
|
}
|
|
1111
1308
|
};
|
|
1309
|
+
const beginBatch = () => {
|
|
1310
|
+
if (locked || selected.length === 0) return;
|
|
1311
|
+
setMenuTarget(null);
|
|
1312
|
+
setError(null);
|
|
1313
|
+
setBatchProgress(null);
|
|
1314
|
+
setBatchTargets([...selected]);
|
|
1315
|
+
};
|
|
1316
|
+
const runBatch = async (targets) => {
|
|
1317
|
+
setBatchProgress({ done: 0, total: targets.length });
|
|
1318
|
+
setError(null);
|
|
1319
|
+
try {
|
|
1320
|
+
const result = await archiveStandalones(
|
|
1321
|
+
ctx,
|
|
1322
|
+
targets.map((row) => row.id),
|
|
1323
|
+
(done, total) => setBatchProgress({ done, total })
|
|
1324
|
+
);
|
|
1325
|
+
setBatchTargets(null);
|
|
1326
|
+
setSelectedIds(new Set(result.failed.map((failure) => failure.sessionId)));
|
|
1327
|
+
if (result.failed.length === 0) {
|
|
1328
|
+
setSelectionMode(false);
|
|
1329
|
+
} else {
|
|
1330
|
+
const message = t("panel.batchPartial", { n: result.failed.length });
|
|
1331
|
+
setError(message);
|
|
1332
|
+
showNotice("error", message);
|
|
1333
|
+
}
|
|
1334
|
+
openManagementPanel(ctx, t);
|
|
1335
|
+
} catch (reason) {
|
|
1336
|
+
setBatchTargets(null);
|
|
1337
|
+
const message = t("panel.actionFailed", { message: messageOf(reason) });
|
|
1338
|
+
setError(message);
|
|
1339
|
+
showNotice("error", message);
|
|
1340
|
+
openManagementPanel(ctx, t);
|
|
1341
|
+
} finally {
|
|
1342
|
+
setBatchProgress(null);
|
|
1343
|
+
}
|
|
1344
|
+
};
|
|
1112
1345
|
const count = t("panel.count", { n: rows.length });
|
|
1113
1346
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "ss-panel", "aria-label": t("panel.title"), children: [
|
|
1114
1347
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-shell", children: [
|
|
1115
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1348
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1349
|
+
"header",
|
|
1350
|
+
{
|
|
1351
|
+
className: `ss-header${selectionMode ? " ss-headerSelection" : ""}`,
|
|
1352
|
+
role: selectionMode ? "toolbar" : void 0,
|
|
1353
|
+
"aria-label": selectionMode ? t("panel.selectToolbar") : void 0,
|
|
1354
|
+
children: [
|
|
1355
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-headerMain", children: [
|
|
1356
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-titleRow", children: [
|
|
1357
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: "ss-title", children: t("panel.title") }),
|
|
1358
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ss-count ss-countInline", children: selectionMode ? t("panel.selectedCount", { n: state.count, total: rows.length }) : count })
|
|
1359
|
+
] }),
|
|
1360
|
+
!selectionMode ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-description", children: t("panel.description") }) : null
|
|
1361
|
+
] }),
|
|
1362
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-headerActions", children: selectionMode ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1363
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "ss-selectAll", children: [
|
|
1364
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1365
|
+
"input",
|
|
1366
|
+
{
|
|
1367
|
+
ref: selectAllRef,
|
|
1368
|
+
type: "checkbox",
|
|
1369
|
+
className: "ss-checkbox",
|
|
1370
|
+
checked: state.all,
|
|
1371
|
+
disabled: locked,
|
|
1372
|
+
"aria-label": t("panel.selectAllLabel"),
|
|
1373
|
+
onChange: toggleAll
|
|
1374
|
+
}
|
|
1375
|
+
),
|
|
1376
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("panel.selectAll") })
|
|
1377
|
+
] }),
|
|
1378
|
+
state.count > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1379
|
+
"button",
|
|
1380
|
+
{
|
|
1381
|
+
type: "button",
|
|
1382
|
+
className: "ss-button ss-buttonDanger",
|
|
1383
|
+
disabled: locked,
|
|
1384
|
+
onClick: beginBatch,
|
|
1385
|
+
children: t("panel.batchDelete")
|
|
1386
|
+
}
|
|
1387
|
+
) : null,
|
|
1388
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1389
|
+
"button",
|
|
1390
|
+
{
|
|
1391
|
+
type: "button",
|
|
1392
|
+
className: "ss-selectButton",
|
|
1393
|
+
disabled: locked,
|
|
1394
|
+
onClick: exitSelection,
|
|
1395
|
+
children: t("panel.cancel")
|
|
1396
|
+
}
|
|
1397
|
+
)
|
|
1398
|
+
] }) : rows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1399
|
+
"button",
|
|
1400
|
+
{
|
|
1401
|
+
ref: selectButtonRef,
|
|
1402
|
+
type: "button",
|
|
1403
|
+
className: "ss-selectButton",
|
|
1404
|
+
disabled: locked,
|
|
1405
|
+
onClick: enterSelection,
|
|
1406
|
+
children: t("panel.select")
|
|
1407
|
+
}
|
|
1408
|
+
) : null })
|
|
1409
|
+
]
|
|
1410
|
+
}
|
|
1411
|
+
),
|
|
1122
1412
|
error !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-error", role: "alert", children: error }) : null,
|
|
1123
1413
|
loading && rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-empty", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-emptyHint", children: t("panel.loading") }) }) : rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-empty", children: [
|
|
1124
1414
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-emptyTitle", children: t("panel.empty") }),
|
|
@@ -1128,7 +1418,7 @@ function createStandalonePanel(ctx) {
|
|
|
1128
1418
|
{
|
|
1129
1419
|
type: "button",
|
|
1130
1420
|
className: "ss-button ss-buttonPrimary",
|
|
1131
|
-
disabled:
|
|
1421
|
+
disabled: locked,
|
|
1132
1422
|
onClick: () => {
|
|
1133
1423
|
void create();
|
|
1134
1424
|
},
|
|
@@ -1142,6 +1432,7 @@ function createStandalonePanel(ctx) {
|
|
|
1142
1432
|
const title = row.title?.trim() || t("panel.untitled");
|
|
1143
1433
|
const updated = relativeTimeLabel(t, row.updatedAt);
|
|
1144
1434
|
const openMenu = menuTarget === row.id;
|
|
1435
|
+
const checked = selectedIds.has(row.id);
|
|
1145
1436
|
const metaItems = [];
|
|
1146
1437
|
if (updated !== "") metaItems.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: updated }, "time"));
|
|
1147
1438
|
if (row.running) {
|
|
@@ -1160,30 +1451,46 @@ function createStandalonePanel(ctx) {
|
|
|
1160
1451
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1161
1452
|
"li",
|
|
1162
1453
|
{
|
|
1163
|
-
className: `ss-row${openMenu ? " ss-rowMenuOpen" : ""}`,
|
|
1164
|
-
role: "button",
|
|
1165
|
-
tabIndex: 0,
|
|
1166
|
-
"aria-disabled": busy !== null,
|
|
1167
|
-
onClick: () => open(row),
|
|
1168
|
-
onKeyDown: (event) => {
|
|
1169
|
-
if (event.target !== event.currentTarget) return;
|
|
1170
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
1171
|
-
event.preventDefault();
|
|
1172
|
-
open(row);
|
|
1173
|
-
} else if (event.key === "ArrowDown") {
|
|
1174
|
-
event.preventDefault();
|
|
1175
|
-
setMenuTarget(row.id);
|
|
1176
|
-
}
|
|
1177
|
-
},
|
|
1454
|
+
className: `ss-row${openMenu ? " ss-rowMenuOpen" : ""}${selectionMode && checked ? " ss-rowSelected" : ""}`,
|
|
1178
1455
|
children: [
|
|
1179
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1456
|
+
selectionMode ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1457
|
+
"input",
|
|
1458
|
+
{
|
|
1459
|
+
type: "checkbox",
|
|
1460
|
+
className: "ss-checkbox",
|
|
1461
|
+
checked,
|
|
1462
|
+
disabled: locked,
|
|
1463
|
+
"aria-label": t("panel.selectRow", { title }),
|
|
1464
|
+
onChange: () => toggleRow(row.id)
|
|
1465
|
+
}
|
|
1466
|
+
) : null,
|
|
1467
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1468
|
+
"button",
|
|
1469
|
+
{
|
|
1470
|
+
type: "button",
|
|
1471
|
+
className: "ss-rowMain",
|
|
1472
|
+
disabled: locked,
|
|
1473
|
+
"aria-pressed": selectionMode ? checked : void 0,
|
|
1474
|
+
onClick: () => {
|
|
1475
|
+
if (selectionMode) toggleRow(row.id);
|
|
1476
|
+
else open(row);
|
|
1477
|
+
},
|
|
1478
|
+
onKeyDown: (event) => {
|
|
1479
|
+
if (!selectionMode && event.key === "ArrowDown") {
|
|
1480
|
+
event.preventDefault();
|
|
1481
|
+
setMenuTarget(row.id);
|
|
1482
|
+
}
|
|
1483
|
+
},
|
|
1484
|
+
children: [
|
|
1485
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-rowTitleRow", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ss-rowTitle", title, children: title }) }),
|
|
1486
|
+
metaItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-rowMeta", children: metaItems.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react3.Fragment, { children: [
|
|
1487
|
+
index > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ss-dotSep", children: "\xB7" }) : null,
|
|
1488
|
+
item
|
|
1489
|
+
] }, index)) }) : null
|
|
1490
|
+
]
|
|
1491
|
+
}
|
|
1492
|
+
),
|
|
1493
|
+
!selectionMode ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ss-rowActions", children: [
|
|
1187
1494
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1188
1495
|
"button",
|
|
1189
1496
|
{
|
|
@@ -1192,7 +1499,7 @@ function createStandalonePanel(ctx) {
|
|
|
1192
1499
|
"aria-label": t("panel.more"),
|
|
1193
1500
|
"aria-haspopup": "menu",
|
|
1194
1501
|
"aria-expanded": openMenu,
|
|
1195
|
-
disabled:
|
|
1502
|
+
disabled: locked,
|
|
1196
1503
|
ref: openMenu ? menuButtonRef : void 0,
|
|
1197
1504
|
onClick: (event) => {
|
|
1198
1505
|
event.stopPropagation();
|
|
@@ -1215,7 +1522,7 @@ function createStandalonePanel(ctx) {
|
|
|
1215
1522
|
{
|
|
1216
1523
|
type: "button",
|
|
1217
1524
|
role: "menuitem",
|
|
1218
|
-
disabled:
|
|
1525
|
+
disabled: locked,
|
|
1219
1526
|
onClick: () => {
|
|
1220
1527
|
setMenuTarget(null);
|
|
1221
1528
|
setRenameTarget(row);
|
|
@@ -1229,7 +1536,7 @@ function createStandalonePanel(ctx) {
|
|
|
1229
1536
|
type: "button",
|
|
1230
1537
|
role: "menuitem",
|
|
1231
1538
|
className: "ss-rowMenuDanger",
|
|
1232
|
-
disabled:
|
|
1539
|
+
disabled: locked,
|
|
1233
1540
|
onClick: () => {
|
|
1234
1541
|
setMenuTarget(null);
|
|
1235
1542
|
setDeleteTarget(row);
|
|
@@ -1240,14 +1547,26 @@ function createStandalonePanel(ctx) {
|
|
|
1240
1547
|
]
|
|
1241
1548
|
}
|
|
1242
1549
|
) : null
|
|
1243
|
-
] })
|
|
1550
|
+
] }) : null
|
|
1244
1551
|
]
|
|
1245
1552
|
},
|
|
1246
1553
|
row.id
|
|
1247
1554
|
);
|
|
1248
1555
|
}) }),
|
|
1249
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-panelHint", children: t("panel.archivedHint") })
|
|
1556
|
+
!selectionMode ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ss-panelHint", children: t("panel.archivedHint") }) : null
|
|
1250
1557
|
] }),
|
|
1558
|
+
batchTargets !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1559
|
+
BatchDeleteDialog,
|
|
1560
|
+
{
|
|
1561
|
+
targets: batchTargets,
|
|
1562
|
+
t,
|
|
1563
|
+
progress: batchProgress,
|
|
1564
|
+
onCancel: () => {
|
|
1565
|
+
if (batchProgress === null) setBatchTargets(null);
|
|
1566
|
+
},
|
|
1567
|
+
onConfirm: () => runBatch(batchTargets)
|
|
1568
|
+
}
|
|
1569
|
+
) : null,
|
|
1251
1570
|
renameTarget !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1252
1571
|
RenameDialog,
|
|
1253
1572
|
{
|
|
@@ -1323,6 +1642,46 @@ var CSS = `
|
|
|
1323
1642
|
margin-top: 0;
|
|
1324
1643
|
align-self: center;
|
|
1325
1644
|
}
|
|
1645
|
+
.ss-headerActions {
|
|
1646
|
+
display: flex;
|
|
1647
|
+
align-items: center;
|
|
1648
|
+
justify-content: flex-end;
|
|
1649
|
+
flex-wrap: wrap;
|
|
1650
|
+
gap: 8px;
|
|
1651
|
+
}
|
|
1652
|
+
.ss-headerSelection {
|
|
1653
|
+
position: sticky;
|
|
1654
|
+
top: 0;
|
|
1655
|
+
z-index: 6;
|
|
1656
|
+
align-items: center;
|
|
1657
|
+
padding: 8px 0;
|
|
1658
|
+
background: var(--dsw-specific-main-fill, var(--dsw-alias-bg-elevated, #fff));
|
|
1659
|
+
}
|
|
1660
|
+
.ss-selectButton {
|
|
1661
|
+
min-height: 30px;
|
|
1662
|
+
padding: 3px 8px;
|
|
1663
|
+
border: 0;
|
|
1664
|
+
border-radius: var(--dsw-radius-md, 10px);
|
|
1665
|
+
background: transparent;
|
|
1666
|
+
color: var(--dsw-alias-label-secondary, #59636e);
|
|
1667
|
+
font: inherit;
|
|
1668
|
+
font-size: 13px;
|
|
1669
|
+
line-height: 20px;
|
|
1670
|
+
cursor: pointer;
|
|
1671
|
+
}
|
|
1672
|
+
.ss-selectButton:hover:not(:disabled) {
|
|
1673
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
1674
|
+
background: var(--dsw-alias-interactive-bg-hover, #e9ebee);
|
|
1675
|
+
}
|
|
1676
|
+
.ss-selectButton:focus-visible {
|
|
1677
|
+
outline: var(--dsw-focus-ring-width, 2px) solid
|
|
1678
|
+
var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary, #3b82f6));
|
|
1679
|
+
outline-offset: 1px;
|
|
1680
|
+
}
|
|
1681
|
+
.ss-selectButton:disabled {
|
|
1682
|
+
cursor: default;
|
|
1683
|
+
opacity: 0.55;
|
|
1684
|
+
}
|
|
1326
1685
|
.ss-button {
|
|
1327
1686
|
font: inherit;
|
|
1328
1687
|
display: inline-flex;
|
|
@@ -1384,6 +1743,28 @@ var CSS = `
|
|
|
1384
1743
|
background: var(--dsw-alias-state-business-tertiary, rgba(59, 130, 246, 0.12));
|
|
1385
1744
|
border: 0.5px solid var(--dsw-alias-interactive-bg-hover, rgba(59, 130, 246, 0.18));
|
|
1386
1745
|
}
|
|
1746
|
+
.ss-selectAll {
|
|
1747
|
+
display: inline-flex;
|
|
1748
|
+
align-items: center;
|
|
1749
|
+
gap: 7px;
|
|
1750
|
+
color: var(--dsw-alias-label-secondary, #59636e);
|
|
1751
|
+
font-size: 13px;
|
|
1752
|
+
line-height: 20px;
|
|
1753
|
+
cursor: pointer;
|
|
1754
|
+
user-select: none;
|
|
1755
|
+
}
|
|
1756
|
+
.ss-checkbox {
|
|
1757
|
+
width: 18px;
|
|
1758
|
+
height: 18px;
|
|
1759
|
+
margin: 0;
|
|
1760
|
+
flex: none;
|
|
1761
|
+
accent-color: var(--dsw-alias-state-business-primary, #2563eb);
|
|
1762
|
+
cursor: pointer;
|
|
1763
|
+
}
|
|
1764
|
+
.ss-checkbox:disabled {
|
|
1765
|
+
cursor: default;
|
|
1766
|
+
opacity: 0.55;
|
|
1767
|
+
}
|
|
1387
1768
|
.ss-list {
|
|
1388
1769
|
display: flex;
|
|
1389
1770
|
flex-direction: column;
|
|
@@ -1410,10 +1791,10 @@ var CSS = `
|
|
|
1410
1791
|
border-color: var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));
|
|
1411
1792
|
background: var(--dsw-alias-interactive-bg-hover, #e9ebee);
|
|
1412
1793
|
}
|
|
1413
|
-
.ss-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1794
|
+
.ss-rowSelected,
|
|
1795
|
+
.ss-rowSelected:hover,
|
|
1796
|
+
.ss-rowSelected:focus-within {
|
|
1797
|
+
border-color: var(--dsw-alias-state-business-primary, #3b82f6);
|
|
1417
1798
|
}
|
|
1418
1799
|
.ss-row[aria-disabled="true"] {
|
|
1419
1800
|
cursor: default;
|
|
@@ -1425,6 +1806,23 @@ var CSS = `
|
|
|
1425
1806
|
display: flex;
|
|
1426
1807
|
flex-direction: column;
|
|
1427
1808
|
gap: 3px;
|
|
1809
|
+
padding: 0;
|
|
1810
|
+
border: 0;
|
|
1811
|
+
background: transparent;
|
|
1812
|
+
color: inherit;
|
|
1813
|
+
font: inherit;
|
|
1814
|
+
text-align: left;
|
|
1815
|
+
cursor: pointer;
|
|
1816
|
+
}
|
|
1817
|
+
.ss-rowMain:disabled {
|
|
1818
|
+
cursor: default;
|
|
1819
|
+
opacity: 0.7;
|
|
1820
|
+
}
|
|
1821
|
+
.ss-rowMain:focus-visible {
|
|
1822
|
+
outline: var(--dsw-focus-ring-width, 2px) solid
|
|
1823
|
+
var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary, #3b82f6));
|
|
1824
|
+
outline-offset: 2px;
|
|
1825
|
+
border-radius: 6px;
|
|
1428
1826
|
}
|
|
1429
1827
|
.ss-rowTitleRow {
|
|
1430
1828
|
display: flex;
|
|
@@ -1613,6 +2011,8 @@ var CSS = `
|
|
|
1613
2011
|
.ss-dialog {
|
|
1614
2012
|
width: 100%;
|
|
1615
2013
|
max-width: 440px;
|
|
2014
|
+
max-height: calc(100vh - 48px);
|
|
2015
|
+
overflow: auto;
|
|
1616
2016
|
box-sizing: border-box;
|
|
1617
2017
|
border-radius: var(--dsw-radius-lg, 14px);
|
|
1618
2018
|
border: 0.5px solid var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));
|
|
@@ -1649,6 +2049,19 @@ var CSS = `
|
|
|
1649
2049
|
gap: 8px;
|
|
1650
2050
|
margin-top: 18px;
|
|
1651
2051
|
}
|
|
2052
|
+
.ss-batchWarning {
|
|
2053
|
+
margin: 0 0 10px;
|
|
2054
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
2055
|
+
font-size: 12px;
|
|
2056
|
+
line-height: 18px;
|
|
2057
|
+
font-weight: 600;
|
|
2058
|
+
}
|
|
2059
|
+
.ss-batchDirs {
|
|
2060
|
+
margin: 0 0 10px;
|
|
2061
|
+
color: var(--dsw-alias-label-tertiary, #818b98);
|
|
2062
|
+
font-size: 12px;
|
|
2063
|
+
line-height: 18px;
|
|
2064
|
+
}
|
|
1652
2065
|
.ss-footerAction {
|
|
1653
2066
|
box-sizing: border-box;
|
|
1654
2067
|
width: 100%;
|
|
@@ -1804,8 +2217,12 @@ var CSS = `
|
|
|
1804
2217
|
}
|
|
1805
2218
|
@media (max-width: 640px) {
|
|
1806
2219
|
.ss-panel { padding: 20px 14px 40px; }
|
|
1807
|
-
.ss-row { flex-wrap:
|
|
1808
|
-
.ss-
|
|
2220
|
+
.ss-row { flex-wrap: nowrap; gap: 10px; }
|
|
2221
|
+
.ss-headerSelection { align-items: flex-start; }
|
|
2222
|
+
.ss-headerSelection .ss-headerActions {
|
|
2223
|
+
width: 100%;
|
|
2224
|
+
justify-content: flex-end;
|
|
2225
|
+
}
|
|
1809
2226
|
}
|
|
1810
2227
|
`;
|
|
1811
2228
|
function insertStyles() {
|
|
@@ -36,5 +36,24 @@ export declare function renameStandalone(ctx: ClientContext, sessionId: string,
|
|
|
36
36
|
* @param sessionId - target session.
|
|
37
37
|
*/
|
|
38
38
|
export declare function archiveStandalone(ctx: ClientContext, sessionId: string): Promise<void>;
|
|
39
|
+
/** One failed session in a batch archive operation. */
|
|
40
|
+
export interface BatchArchiveFailure {
|
|
41
|
+
readonly sessionId: string;
|
|
42
|
+
readonly message: string;
|
|
43
|
+
}
|
|
44
|
+
/** Result of archiving a snapshot of selected sessions. */
|
|
45
|
+
export interface BatchArchiveResult {
|
|
46
|
+
readonly archived: readonly string[];
|
|
47
|
+
readonly failed: readonly BatchArchiveFailure[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Archive sessions one at a time. A failure is recorded and the remaining
|
|
51
|
+
* sessions continue, so one stale row cannot strand the rest of the batch.
|
|
52
|
+
* @param ctx - client root context.
|
|
53
|
+
* @param sessionIds - frozen snapshot of the selected session ids.
|
|
54
|
+
* @param onProgress - called after every attempt with 1-based progress.
|
|
55
|
+
* @returns successfully archived ids and per-session failures.
|
|
56
|
+
*/
|
|
57
|
+
export declare function archiveStandalones(ctx: ClientContext, sessionIds: readonly string[], onProgress?: (done: number, total: number) => void): Promise<BatchArchiveResult>;
|
|
39
58
|
/** Human-readable error text. */
|
|
40
59
|
export declare function messageOf(error: unknown): string;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* keeps `en` complete, and both shipped languages are registered together.
|
|
4
4
|
*/
|
|
5
5
|
export declare const NS = "standaloneSession";
|
|
6
|
-
export type StandaloneKey = 'panel.label' | 'panel.title' | 'panel.description' | 'panel.new' | 'panel.empty' | 'panel.emptyHint' | 'panel.badge' | 'panel.open' | 'panel.rename' | 'panel.delete' | 'panel.untitled' | 'panel.blank' | 'panel.running' | 'panel.cwd' | 'panel.count' | 'panel.deleteTitle' | 'panel.deleteBody' | 'panel.deleteConfirm' | 'panel.deleteKeepDir' | 'panel.cancel' | 'panel.renameTitle' | 'panel.renameLabel' | 'panel.more' | 'panel.save' | 'panel.titleRequired' | 'panel.actionFailed' | 'panel.retry' | 'panel.archivedHint' | 'panel.loading' | 'entry.label' | 'launcher.creating' | 'launcher.failedTitle' | 'launcher.retry' | 'launcher.manage' | 'launcher.hint' | 'command.label' | 'command.description' | 'command.manageLabel' | 'command.manageDescription' | 'footer.manage' | 'footer.manageCount' | 'footer.manageTooltip' | 'time.now' | 'time.minutes' | 'time.hours' | 'time.days' | 'toast.manageFailed' | 'hero.badge' | 'hero.hint' | 'row.mark' | 'row.hover' | 'composer.placeholder' | 'toast.created' | 'toast.createFailed' | 'toast.deleteFailed' | 'toast.deleted' | 'toast.renamed' | 'toast.renameFailed' | 'error.noHome' | 'error.noSession';
|
|
6
|
+
export type StandaloneKey = 'panel.label' | 'panel.title' | 'panel.description' | 'panel.new' | 'panel.empty' | 'panel.emptyHint' | 'panel.badge' | 'panel.open' | 'panel.rename' | 'panel.delete' | 'panel.untitled' | 'panel.blank' | 'panel.running' | 'panel.cwd' | 'panel.count' | 'panel.deleteTitle' | 'panel.deleteBody' | 'panel.deleteConfirm' | 'panel.select' | 'panel.selectToolbar' | 'panel.selectAll' | 'panel.selectAllLabel' | 'panel.selectedCount' | 'panel.selectRow' | 'panel.batchDelete' | 'panel.batchDeleteTitle' | 'panel.batchDeleteBody' | 'panel.batchDeleteRunning' | 'panel.batchDeleteDirs' | 'panel.batchDeleting' | 'panel.batchPartial' | 'panel.deleteKeepDir' | 'panel.cancel' | 'panel.renameTitle' | 'panel.renameLabel' | 'panel.more' | 'panel.save' | 'panel.titleRequired' | 'panel.actionFailed' | 'panel.retry' | 'panel.archivedHint' | 'panel.loading' | 'entry.label' | 'launcher.creating' | 'launcher.failedTitle' | 'launcher.retry' | 'launcher.manage' | 'launcher.hint' | 'command.label' | 'command.description' | 'command.manageLabel' | 'command.manageDescription' | 'footer.manage' | 'footer.manageCount' | 'footer.manageTooltip' | 'time.now' | 'time.minutes' | 'time.hours' | 'time.days' | 'toast.manageFailed' | 'hero.badge' | 'hero.hint' | 'row.mark' | 'row.hover' | 'composer.placeholder' | 'toast.created' | 'toast.createFailed' | 'toast.deleteFailed' | 'toast.deleted' | 'toast.renamed' | 'toast.renameFailed' | 'error.noHome' | 'error.noSession';
|
|
7
7
|
export type StandaloneDict = Readonly<Record<StandaloneKey, string>>;
|
|
8
8
|
export declare const zh: StandaloneDict;
|
|
9
9
|
export declare const en: StandaloneDict;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure selection helpers for the standalone-session manager. Keeping these out
|
|
3
|
+
* of the panel makes bulk-selection edge cases (pruning, select-all, partial
|
|
4
|
+
* state) directly unit-testable.
|
|
5
|
+
*/
|
|
6
|
+
import type { SessionRow } from './dsh.js';
|
|
7
|
+
/** Toggle one session id without mutating the caller's set. */
|
|
8
|
+
export declare function toggleSelection(selected: ReadonlySet<string>, sessionId: string): ReadonlySet<string>;
|
|
9
|
+
/** Select every row in the current view. */
|
|
10
|
+
export declare function selectAllRows(rows: readonly SessionRow[]): ReadonlySet<string>;
|
|
11
|
+
/** Rows that are still present in the current view and selected. */
|
|
12
|
+
export declare function selectedRows(rows: readonly SessionRow[], selected: ReadonlySet<string>): SessionRow[];
|
|
13
|
+
/**
|
|
14
|
+
* Drop ids that disappeared from the view. Returns the original set when
|
|
15
|
+
* nothing changed so callers can avoid a redundant state update.
|
|
16
|
+
*/
|
|
17
|
+
export declare function pruneSelection(rows: readonly SessionRow[], selected: ReadonlySet<string>): ReadonlySet<string>;
|
|
18
|
+
/** Select-all checkbox state for the current view. */
|
|
19
|
+
export declare function selectionState(rows: readonly SessionRow[], selected: ReadonlySet<string>): {
|
|
20
|
+
readonly count: number;
|
|
21
|
+
readonly all: boolean;
|
|
22
|
+
readonly some: boolean;
|
|
23
|
+
};
|
|
24
|
+
/** Counts used by the batch confirmation copy. */
|
|
25
|
+
export declare function batchSummary(rows: readonly SessionRow[], selected: ReadonlySet<string>): {
|
|
26
|
+
readonly count: number;
|
|
27
|
+
readonly running: number;
|
|
28
|
+
readonly blank: number;
|
|
29
|
+
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* One namespaced stylesheet. DSH theme variables carry the palette, so the
|
|
3
3
|
* plugin follows light/dark themes without owning color tokens.
|
|
4
4
|
*/
|
|
5
|
-
export declare const CSS = "\n.ss-panel {\n box-sizing: border-box;\n height: 100%;\n min-height: 0;\n overflow: auto;\n padding: 32px clamp(20px, 5vw, 56px) 56px;\n color: var(--dsw-alias-label-primary, #1f2328);\n background: var(--dsw-specific-main-fill, transparent);\n}\n.ss-shell {\n width: 100%;\n max-width: 880px;\n margin: 0 auto;\n}\n.ss-header {\n display: flex;\n align-items: flex-start;\n gap: 16px;\n justify-content: space-between;\n flex-wrap: wrap;\n margin-bottom: 8px;\n}\n.ss-titleRow {\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: 0;\n}\n.ss-title {\n font-size: 24px;\n line-height: 32px;\n font-weight: 600;\n margin: 0;\n}\n.ss-description {\n color: var(--dsw-alias-label-secondary, #59636e);\n font-size: 13px;\n line-height: 20px;\n margin: 6px 0 0;\n max-width: 560px;\n}\n.ss-count {\n color: var(--dsw-alias-label-tertiary, #818b98);\n font-size: 12px;\n line-height: 20px;\n margin-top: 6px;\n}\n.ss-countInline {\n margin-top: 0;\n align-self: center;\n}\n.ss-button {\n font: inherit;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n box-sizing: border-box;\n min-height: 32px;\n padding: 5px 12px;\n border-radius: var(--dsw-radius-md, 10px);\n border: 0.5px solid transparent;\n cursor: pointer;\n color: var(--dsw-alias-label-primary, #1f2328);\n background: var(--dsw-alias-button-elevated-fill, #f3f4f6);\n transition: background 120ms ease, border-color 120ms ease;\n}\n.ss-button:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover, #e9ebee);\n}\n.ss-button:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid\n var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary, #3b82f6));\n outline-offset: 1px;\n}\n.ss-button:disabled {\n cursor: default;\n opacity: 0.55;\n}\n.ss-buttonPrimary {\n color: #fff;\n background: var(--dsw-alias-state-business-primary, #2563eb);\n border-color: var(--dsw-alias-state-business-primary, #2563eb);\n}\n.ss-buttonPrimary:hover:not(:disabled) {\n background: var(--dsw-alias-state-business-primary, #2563eb);\n filter: brightness(1.05);\n}\n.ss-buttonQuiet {\n background: transparent;\n border-color: var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.22));\n}\n.ss-buttonDanger {\n color: var(--dsw-alias-state-error-primary, #d1242f);\n background: transparent;\n border-color: var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.22));\n}\n.ss-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n flex: none;\n border-radius: 999px;\n padding: 1px 8px 2px;\n font-size: 11px;\n line-height: 16px;\n font-weight: 600;\n letter-spacing: 0.01em;\n color: var(--dsw-alias-label-primary-bluish, #1f4f8f);\n background: var(--dsw-alias-state-business-tertiary, rgba(59, 130, 246, 0.12));\n border: 0.5px solid var(--dsw-alias-interactive-bg-hover, rgba(59, 130, 246, 0.18));\n}\n.ss-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin: 20px 0 0;\n padding: 0;\n list-style: none;\n}\n.ss-row {\n position: relative;\n display: flex;\n align-items: center;\n gap: 14px;\n box-sizing: border-box;\n padding: 12px 14px;\n border-radius: var(--dsw-radius-lg, 14px);\n border: 0.5px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.2));\n background: var(--dsw-alias-bg-elevated, rgba(255, 255, 255, 0.02));\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n.ss-row:hover,\n.ss-row:focus-within {\n border-color: var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));\n background: var(--dsw-alias-interactive-bg-hover, #e9ebee);\n}\n.ss-row:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid\n var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary, #3b82f6));\n outline-offset: 1px;\n}\n.ss-row[aria-disabled=\"true\"] {\n cursor: default;\n opacity: 0.7;\n}\n.ss-rowMain {\n min-width: 0;\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n.ss-rowTitleRow {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n.ss-rowTitle {\n font-size: 14px;\n line-height: 20px;\n font-weight: 600;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ss-rowMeta {\n color: var(--dsw-alias-label-tertiary, #818b98);\n font-size: 12px;\n line-height: 18px;\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n flex-wrap: wrap;\n}\n.ss-dotSep {\n opacity: 0.5;\n}\n.ss-rowActions {\n position: relative;\n display: flex;\n align-items: center;\n gap: 6px;\n flex: none;\n}\n.ss-rowMenuButton {\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: 0;\n border-radius: var(--dsw-radius-md, 10px);\n background: transparent;\n color: var(--dsw-alias-label-tertiary, #818b98);\n font: inherit;\n font-size: 18px;\n line-height: 1;\n cursor: pointer;\n opacity: 0;\n transition: background 120ms ease, color 120ms ease, opacity 120ms ease;\n}\n.ss-row:hover .ss-rowMenuButton,\n.ss-row:focus-within .ss-rowMenuButton,\n.ss-rowMenuOpen .ss-rowMenuButton {\n opacity: 1;\n}\n.ss-rowMenuButton:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary, #1f2328);\n background: var(--dsw-alias-interactive-bg-hover, #e9ebee);\n}\n.ss-rowMenuButton:focus-visible {\n opacity: 1;\n outline: var(--dsw-focus-ring-width, 2px) solid\n var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary, #3b82f6));\n outline-offset: 1px;\n}\n.ss-rowMenuButton:disabled {\n cursor: default;\n opacity: 0.35;\n}\n.ss-rowMenu {\n position: absolute;\n top: calc(100% + 4px);\n right: 0;\n z-index: 20;\n min-width: 136px;\n padding: 4px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n border: 0.5px solid var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));\n border-radius: var(--dsw-radius-md, 10px);\n background: var(--dsw-alias-bg-elevated, #fff);\n box-shadow: 0 10px 28px rgba(0, 0, 0, 0.16);\n}\n.ss-rowMenu button {\n box-sizing: border-box;\n width: 100%;\n padding: 6px 9px;\n border: 0;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-primary, #1f2328);\n font: inherit;\n font-size: 13px;\n line-height: 20px;\n text-align: left;\n cursor: pointer;\n}\n.ss-rowMenu button:hover:not(:disabled),\n.ss-rowMenu button:focus-visible {\n background: var(--dsw-alias-interactive-bg-hover, #e9ebee);\n outline: none;\n}\n.ss-rowMenu button:disabled {\n cursor: default;\n opacity: 0.5;\n}\n.ss-rowMenuDanger {\n color: var(--dsw-alias-state-error-primary, #d1242f) !important;\n}\n.ss-status {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n}\n.ss-statusDot {\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: currentColor;\n opacity: 0.55;\n}\n.ss-stateRunning {\n color: var(--dsw-alias-state-business-primary, #2563eb);\n font-weight: 600;\n}\n.ss-empty {\n margin-top: 28px;\n padding: 44px 24px;\n text-align: center;\n border: 0.5px dashed var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.3));\n border-radius: var(--dsw-radius-lg, 14px);\n}\n.ss-emptyTitle {\n font-size: 15px;\n font-weight: 600;\n line-height: 22px;\n margin: 0 0 6px;\n}\n.ss-emptyHint {\n color: var(--dsw-alias-label-secondary, #59636e);\n font-size: 13px;\n line-height: 20px;\n margin: 0 auto;\n max-width: 460px;\n}\n.ss-emptyAction {\n margin-top: 18px;\n}\n.ss-panelHint {\n margin: 18px 0 0;\n color: var(--dsw-alias-label-tertiary, #818b98);\n font-size: 12px;\n line-height: 20px;\n}\n.ss-fieldLabel {\n display: block;\n margin: 12px 0 6px;\n color: var(--dsw-alias-label-secondary, #59636e);\n font-size: 12px;\n line-height: 18px;\n}\n.ss-error {\n margin-top: 16px;\n padding: 8px 10px;\n border-radius: var(--dsw-radius-sm, 8px);\n color: var(--dsw-alias-state-error-primary, #d1242f);\n background: var(--dsw-alias-state-error-tertiary, rgba(209, 36, 47, 0.08));\n font-size: 12px;\n line-height: 18px;\n}\n.ss-dialogBackdrop {\n position: fixed;\n inset: 0;\n z-index: 80;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.28);\n padding: 24px;\n}\n.ss-dialog {\n width: 100%;\n max-width: 440px;\n box-sizing: border-box;\n border-radius: var(--dsw-radius-lg, 14px);\n border: 0.5px solid var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));\n background: var(--dsw-alias-bg-elevated, #fff);\n padding: 20px;\n box-shadow: 0 18px 48px rgba(0, 0, 0, 0.22);\n}\n.ss-dialogTitle {\n margin: 0 0 6px;\n font-size: 16px;\n line-height: 24px;\n font-weight: 600;\n}\n.ss-dialogBody {\n margin: 0 0 14px;\n color: var(--dsw-alias-label-secondary, #59636e);\n font-size: 13px;\n line-height: 20px;\n}\n.ss-input {\n box-sizing: border-box;\n width: 100%;\n height: 36px;\n border-radius: var(--dsw-radius-sm, 8px);\n border: 0.5px solid var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));\n background: transparent;\n color: inherit;\n font: inherit;\n padding: 0 10px;\n}\n.ss-dialogFooter {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 18px;\n}\n.ss-footerAction {\n box-sizing: border-box;\n width: 100%;\n display: flex;\n align-items: center;\n gap: 8px;\n min-height: 30px;\n padding: 4px 8px;\n border: 0;\n border-radius: var(--dsw-radius-md, 10px);\n background: transparent;\n color: var(--dsw-alias-label-secondary, #59636e);\n font: inherit;\n cursor: pointer;\n text-align: left;\n}\n.ss-footerAction:hover {\n color: var(--dsw-alias-label-primary, #1f2328);\n background: var(--dsw-alias-interactive-bg-hover, #e9ebee);\n}\n.ss-footerAction:disabled {\n cursor: default;\n opacity: 0.55;\n}\n.ss-footerLabel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 13px;\n}\n.ss-footerRail {\n width: 36px;\n height: 36px;\n justify-content: center;\n padding: 0;\n}\n.ss-panelIcon {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n.ss-launcher {\n box-sizing: border-box;\n min-height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 32px;\n color: var(--dsw-alias-label-primary, #1f2328);\n}\n.ss-launcherCard {\n box-sizing: border-box;\n width: 100%;\n max-width: 430px;\n padding: 30px 28px;\n text-align: center;\n border: 0.5px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.22));\n border-radius: var(--dsw-radius-lg, 14px);\n background: var(--dsw-alias-bg-elevated, rgba(255, 255, 255, 0.02));\n}\n.ss-launcherSpinner {\n width: 22px;\n height: 22px;\n margin: 0 auto 14px;\n border-radius: 50%;\n border: 2px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));\n border-top-color: var(--dsw-alias-state-business-primary, #2563eb);\n animation: ss-spin 900ms linear infinite;\n}\n@keyframes ss-spin {\n to { transform: rotate(360deg); }\n}\n.ss-launcherTitle {\n margin: 0;\n font-size: 15px;\n line-height: 22px;\n font-weight: 600;\n}\n.ss-launcherHint {\n margin: 6px 0 0;\n color: var(--dsw-alias-label-secondary, #59636e);\n font-size: 13px;\n line-height: 20px;\n}\n.ss-launcherActions {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n gap: 8px;\n margin-top: 18px;\n}\n.ss-heroBadge {\n margin-right: 4px;\n}\n.ss-rowMark {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n border-radius: 4px;\n font-size: 10px;\n line-height: 1;\n font-weight: 700;\n color: var(--dsw-alias-label-primary-bluish, #1f4f8f);\n background: var(--dsw-alias-state-business-tertiary, rgba(59, 130, 246, 0.14));\n flex: none;\n}\n.ss-rowHover {\n display: flex;\n align-items: center;\n gap: 6px;\n color: #cfd3d6;\n font-size: 12px;\n line-height: 16px;\n}\n.ss-toast {\n position: fixed;\n left: 50%;\n bottom: 28px;\n transform: translateX(-50%);\n z-index: 90;\n max-width: min(520px, calc(100vw - 40px));\n box-sizing: border-box;\n padding: 9px 14px;\n border-radius: var(--dsw-radius-md, 10px);\n border: 0.5px solid var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));\n background: var(--dsw-alias-bg-elevated, #fff);\n color: var(--dsw-alias-label-primary, #1f2328);\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);\n font-size: 13px;\n line-height: 19px;\n pointer-events: auto;\n}\n.ss-toastError {\n color: var(--dsw-alias-state-error-primary, #d1242f);\n}\n.ss-visuallyHidden {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n@media (hover: none) {\n .ss-rowMenuButton { opacity: 1; }\n}\n@media (max-width: 640px) {\n .ss-panel { padding: 20px 14px 40px; }\n .ss-row { flex-wrap: wrap; }\n .ss-rowActions { width: 100%; justify-content: flex-end; }\n}\n";
|
|
5
|
+
export declare const CSS = "\n.ss-panel {\n box-sizing: border-box;\n height: 100%;\n min-height: 0;\n overflow: auto;\n padding: 32px clamp(20px, 5vw, 56px) 56px;\n color: var(--dsw-alias-label-primary, #1f2328);\n background: var(--dsw-specific-main-fill, transparent);\n}\n.ss-shell {\n width: 100%;\n max-width: 880px;\n margin: 0 auto;\n}\n.ss-header {\n display: flex;\n align-items: flex-start;\n gap: 16px;\n justify-content: space-between;\n flex-wrap: wrap;\n margin-bottom: 8px;\n}\n.ss-titleRow {\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: 0;\n}\n.ss-title {\n font-size: 24px;\n line-height: 32px;\n font-weight: 600;\n margin: 0;\n}\n.ss-description {\n color: var(--dsw-alias-label-secondary, #59636e);\n font-size: 13px;\n line-height: 20px;\n margin: 6px 0 0;\n max-width: 560px;\n}\n.ss-count {\n color: var(--dsw-alias-label-tertiary, #818b98);\n font-size: 12px;\n line-height: 20px;\n margin-top: 6px;\n}\n.ss-countInline {\n margin-top: 0;\n align-self: center;\n}\n.ss-headerActions {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n flex-wrap: wrap;\n gap: 8px;\n}\n.ss-headerSelection {\n position: sticky;\n top: 0;\n z-index: 6;\n align-items: center;\n padding: 8px 0;\n background: var(--dsw-specific-main-fill, var(--dsw-alias-bg-elevated, #fff));\n}\n.ss-selectButton {\n min-height: 30px;\n padding: 3px 8px;\n border: 0;\n border-radius: var(--dsw-radius-md, 10px);\n background: transparent;\n color: var(--dsw-alias-label-secondary, #59636e);\n font: inherit;\n font-size: 13px;\n line-height: 20px;\n cursor: pointer;\n}\n.ss-selectButton:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary, #1f2328);\n background: var(--dsw-alias-interactive-bg-hover, #e9ebee);\n}\n.ss-selectButton:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid\n var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary, #3b82f6));\n outline-offset: 1px;\n}\n.ss-selectButton:disabled {\n cursor: default;\n opacity: 0.55;\n}\n.ss-button {\n font: inherit;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n box-sizing: border-box;\n min-height: 32px;\n padding: 5px 12px;\n border-radius: var(--dsw-radius-md, 10px);\n border: 0.5px solid transparent;\n cursor: pointer;\n color: var(--dsw-alias-label-primary, #1f2328);\n background: var(--dsw-alias-button-elevated-fill, #f3f4f6);\n transition: background 120ms ease, border-color 120ms ease;\n}\n.ss-button:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover, #e9ebee);\n}\n.ss-button:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid\n var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary, #3b82f6));\n outline-offset: 1px;\n}\n.ss-button:disabled {\n cursor: default;\n opacity: 0.55;\n}\n.ss-buttonPrimary {\n color: #fff;\n background: var(--dsw-alias-state-business-primary, #2563eb);\n border-color: var(--dsw-alias-state-business-primary, #2563eb);\n}\n.ss-buttonPrimary:hover:not(:disabled) {\n background: var(--dsw-alias-state-business-primary, #2563eb);\n filter: brightness(1.05);\n}\n.ss-buttonQuiet {\n background: transparent;\n border-color: var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.22));\n}\n.ss-buttonDanger {\n color: var(--dsw-alias-state-error-primary, #d1242f);\n background: transparent;\n border-color: var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.22));\n}\n.ss-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n flex: none;\n border-radius: 999px;\n padding: 1px 8px 2px;\n font-size: 11px;\n line-height: 16px;\n font-weight: 600;\n letter-spacing: 0.01em;\n color: var(--dsw-alias-label-primary-bluish, #1f4f8f);\n background: var(--dsw-alias-state-business-tertiary, rgba(59, 130, 246, 0.12));\n border: 0.5px solid var(--dsw-alias-interactive-bg-hover, rgba(59, 130, 246, 0.18));\n}\n.ss-selectAll {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n color: var(--dsw-alias-label-secondary, #59636e);\n font-size: 13px;\n line-height: 20px;\n cursor: pointer;\n user-select: none;\n}\n.ss-checkbox {\n width: 18px;\n height: 18px;\n margin: 0;\n flex: none;\n accent-color: var(--dsw-alias-state-business-primary, #2563eb);\n cursor: pointer;\n}\n.ss-checkbox:disabled {\n cursor: default;\n opacity: 0.55;\n}\n.ss-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin: 20px 0 0;\n padding: 0;\n list-style: none;\n}\n.ss-row {\n position: relative;\n display: flex;\n align-items: center;\n gap: 14px;\n box-sizing: border-box;\n padding: 12px 14px;\n border-radius: var(--dsw-radius-lg, 14px);\n border: 0.5px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.2));\n background: var(--dsw-alias-bg-elevated, rgba(255, 255, 255, 0.02));\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n.ss-row:hover,\n.ss-row:focus-within {\n border-color: var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));\n background: var(--dsw-alias-interactive-bg-hover, #e9ebee);\n}\n.ss-rowSelected,\n.ss-rowSelected:hover,\n.ss-rowSelected:focus-within {\n border-color: var(--dsw-alias-state-business-primary, #3b82f6);\n}\n.ss-row[aria-disabled=\"true\"] {\n cursor: default;\n opacity: 0.7;\n}\n.ss-rowMain {\n min-width: 0;\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 3px;\n padding: 0;\n border: 0;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.ss-rowMain:disabled {\n cursor: default;\n opacity: 0.7;\n}\n.ss-rowMain:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid\n var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary, #3b82f6));\n outline-offset: 2px;\n border-radius: 6px;\n}\n.ss-rowTitleRow {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n.ss-rowTitle {\n font-size: 14px;\n line-height: 20px;\n font-weight: 600;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ss-rowMeta {\n color: var(--dsw-alias-label-tertiary, #818b98);\n font-size: 12px;\n line-height: 18px;\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n flex-wrap: wrap;\n}\n.ss-dotSep {\n opacity: 0.5;\n}\n.ss-rowActions {\n position: relative;\n display: flex;\n align-items: center;\n gap: 6px;\n flex: none;\n}\n.ss-rowMenuButton {\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: 0;\n border-radius: var(--dsw-radius-md, 10px);\n background: transparent;\n color: var(--dsw-alias-label-tertiary, #818b98);\n font: inherit;\n font-size: 18px;\n line-height: 1;\n cursor: pointer;\n opacity: 0;\n transition: background 120ms ease, color 120ms ease, opacity 120ms ease;\n}\n.ss-row:hover .ss-rowMenuButton,\n.ss-row:focus-within .ss-rowMenuButton,\n.ss-rowMenuOpen .ss-rowMenuButton {\n opacity: 1;\n}\n.ss-rowMenuButton:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary, #1f2328);\n background: var(--dsw-alias-interactive-bg-hover, #e9ebee);\n}\n.ss-rowMenuButton:focus-visible {\n opacity: 1;\n outline: var(--dsw-focus-ring-width, 2px) solid\n var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary, #3b82f6));\n outline-offset: 1px;\n}\n.ss-rowMenuButton:disabled {\n cursor: default;\n opacity: 0.35;\n}\n.ss-rowMenu {\n position: absolute;\n top: calc(100% + 4px);\n right: 0;\n z-index: 20;\n min-width: 136px;\n padding: 4px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n border: 0.5px solid var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));\n border-radius: var(--dsw-radius-md, 10px);\n background: var(--dsw-alias-bg-elevated, #fff);\n box-shadow: 0 10px 28px rgba(0, 0, 0, 0.16);\n}\n.ss-rowMenu button {\n box-sizing: border-box;\n width: 100%;\n padding: 6px 9px;\n border: 0;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-primary, #1f2328);\n font: inherit;\n font-size: 13px;\n line-height: 20px;\n text-align: left;\n cursor: pointer;\n}\n.ss-rowMenu button:hover:not(:disabled),\n.ss-rowMenu button:focus-visible {\n background: var(--dsw-alias-interactive-bg-hover, #e9ebee);\n outline: none;\n}\n.ss-rowMenu button:disabled {\n cursor: default;\n opacity: 0.5;\n}\n.ss-rowMenuDanger {\n color: var(--dsw-alias-state-error-primary, #d1242f) !important;\n}\n.ss-status {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n}\n.ss-statusDot {\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: currentColor;\n opacity: 0.55;\n}\n.ss-stateRunning {\n color: var(--dsw-alias-state-business-primary, #2563eb);\n font-weight: 600;\n}\n.ss-empty {\n margin-top: 28px;\n padding: 44px 24px;\n text-align: center;\n border: 0.5px dashed var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.3));\n border-radius: var(--dsw-radius-lg, 14px);\n}\n.ss-emptyTitle {\n font-size: 15px;\n font-weight: 600;\n line-height: 22px;\n margin: 0 0 6px;\n}\n.ss-emptyHint {\n color: var(--dsw-alias-label-secondary, #59636e);\n font-size: 13px;\n line-height: 20px;\n margin: 0 auto;\n max-width: 460px;\n}\n.ss-emptyAction {\n margin-top: 18px;\n}\n.ss-panelHint {\n margin: 18px 0 0;\n color: var(--dsw-alias-label-tertiary, #818b98);\n font-size: 12px;\n line-height: 20px;\n}\n.ss-fieldLabel {\n display: block;\n margin: 12px 0 6px;\n color: var(--dsw-alias-label-secondary, #59636e);\n font-size: 12px;\n line-height: 18px;\n}\n.ss-error {\n margin-top: 16px;\n padding: 8px 10px;\n border-radius: var(--dsw-radius-sm, 8px);\n color: var(--dsw-alias-state-error-primary, #d1242f);\n background: var(--dsw-alias-state-error-tertiary, rgba(209, 36, 47, 0.08));\n font-size: 12px;\n line-height: 18px;\n}\n.ss-dialogBackdrop {\n position: fixed;\n inset: 0;\n z-index: 80;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.28);\n padding: 24px;\n}\n.ss-dialog {\n width: 100%;\n max-width: 440px;\n max-height: calc(100vh - 48px);\n overflow: auto;\n box-sizing: border-box;\n border-radius: var(--dsw-radius-lg, 14px);\n border: 0.5px solid var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));\n background: var(--dsw-alias-bg-elevated, #fff);\n padding: 20px;\n box-shadow: 0 18px 48px rgba(0, 0, 0, 0.22);\n}\n.ss-dialogTitle {\n margin: 0 0 6px;\n font-size: 16px;\n line-height: 24px;\n font-weight: 600;\n}\n.ss-dialogBody {\n margin: 0 0 14px;\n color: var(--dsw-alias-label-secondary, #59636e);\n font-size: 13px;\n line-height: 20px;\n}\n.ss-input {\n box-sizing: border-box;\n width: 100%;\n height: 36px;\n border-radius: var(--dsw-radius-sm, 8px);\n border: 0.5px solid var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));\n background: transparent;\n color: inherit;\n font: inherit;\n padding: 0 10px;\n}\n.ss-dialogFooter {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 18px;\n}\n.ss-batchWarning {\n margin: 0 0 10px;\n color: var(--dsw-alias-label-primary, #1f2328);\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n}\n.ss-batchDirs {\n margin: 0 0 10px;\n color: var(--dsw-alias-label-tertiary, #818b98);\n font-size: 12px;\n line-height: 18px;\n}\n.ss-footerAction {\n box-sizing: border-box;\n width: 100%;\n display: flex;\n align-items: center;\n gap: 8px;\n min-height: 30px;\n padding: 4px 8px;\n border: 0;\n border-radius: var(--dsw-radius-md, 10px);\n background: transparent;\n color: var(--dsw-alias-label-secondary, #59636e);\n font: inherit;\n cursor: pointer;\n text-align: left;\n}\n.ss-footerAction:hover {\n color: var(--dsw-alias-label-primary, #1f2328);\n background: var(--dsw-alias-interactive-bg-hover, #e9ebee);\n}\n.ss-footerAction:disabled {\n cursor: default;\n opacity: 0.55;\n}\n.ss-footerLabel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 13px;\n}\n.ss-footerRail {\n width: 36px;\n height: 36px;\n justify-content: center;\n padding: 0;\n}\n.ss-panelIcon {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n.ss-launcher {\n box-sizing: border-box;\n min-height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 32px;\n color: var(--dsw-alias-label-primary, #1f2328);\n}\n.ss-launcherCard {\n box-sizing: border-box;\n width: 100%;\n max-width: 430px;\n padding: 30px 28px;\n text-align: center;\n border: 0.5px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.22));\n border-radius: var(--dsw-radius-lg, 14px);\n background: var(--dsw-alias-bg-elevated, rgba(255, 255, 255, 0.02));\n}\n.ss-launcherSpinner {\n width: 22px;\n height: 22px;\n margin: 0 auto 14px;\n border-radius: 50%;\n border: 2px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));\n border-top-color: var(--dsw-alias-state-business-primary, #2563eb);\n animation: ss-spin 900ms linear infinite;\n}\n@keyframes ss-spin {\n to { transform: rotate(360deg); }\n}\n.ss-launcherTitle {\n margin: 0;\n font-size: 15px;\n line-height: 22px;\n font-weight: 600;\n}\n.ss-launcherHint {\n margin: 6px 0 0;\n color: var(--dsw-alias-label-secondary, #59636e);\n font-size: 13px;\n line-height: 20px;\n}\n.ss-launcherActions {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n gap: 8px;\n margin-top: 18px;\n}\n.ss-heroBadge {\n margin-right: 4px;\n}\n.ss-rowMark {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n border-radius: 4px;\n font-size: 10px;\n line-height: 1;\n font-weight: 700;\n color: var(--dsw-alias-label-primary-bluish, #1f4f8f);\n background: var(--dsw-alias-state-business-tertiary, rgba(59, 130, 246, 0.14));\n flex: none;\n}\n.ss-rowHover {\n display: flex;\n align-items: center;\n gap: 6px;\n color: #cfd3d6;\n font-size: 12px;\n line-height: 16px;\n}\n.ss-toast {\n position: fixed;\n left: 50%;\n bottom: 28px;\n transform: translateX(-50%);\n z-index: 90;\n max-width: min(520px, calc(100vw - 40px));\n box-sizing: border-box;\n padding: 9px 14px;\n border-radius: var(--dsw-radius-md, 10px);\n border: 0.5px solid var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));\n background: var(--dsw-alias-bg-elevated, #fff);\n color: var(--dsw-alias-label-primary, #1f2328);\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);\n font-size: 13px;\n line-height: 19px;\n pointer-events: auto;\n}\n.ss-toastError {\n color: var(--dsw-alias-state-error-primary, #d1242f);\n}\n.ss-visuallyHidden {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n@media (hover: none) {\n .ss-rowMenuButton { opacity: 1; }\n}\n@media (max-width: 640px) {\n .ss-panel { padding: 20px 14px 40px; }\n .ss-row { flex-wrap: nowrap; gap: 10px; }\n .ss-headerSelection { align-items: flex-start; }\n .ss-headerSelection .ss-headerActions {\n width: 100%;\n justify-content: flex-end;\n }\n}\n";
|
|
6
6
|
/**
|
|
7
7
|
* Insert (or replace) the plugin's one style element.
|
|
8
8
|
* @returns a disposer that removes the element.
|
package/package.json
CHANGED