dsh-plugin 1.1.2 → 1.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/client/client.js +4469 -1445
- package/client/client.js.map +1 -1
- package/lib/http/routes.js +438 -22
- package/lib/index.js +1 -1
- package/lib/services/install/install.js +4 -0
- package/lib/services/install/npm-check.js +55 -0
- package/lib/services/{npm-resolve.js → install/npm-resolve.js} +7 -5
- package/lib/services/{preflight.js → install/preflight.js} +15 -4
- package/lib/services/{task-queue.js → install/task-queue.js} +155 -66
- package/lib/services/loader.js +56 -1
- package/lib/services/log.js +138 -0
- package/lib/services/probe.js +157 -0
- package/lib/services/{profile.js → profile/profile.js} +29 -1
- package/lib/services/settings.js +60 -0
- package/lib/types/http/routes.d.ts +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/services/{install-types.d.ts → install/install-types.d.ts} +3 -0
- package/lib/types/services/{install.d.ts → install/install.d.ts} +5 -5
- package/lib/types/services/install/npm-check.d.ts +12 -0
- package/lib/types/services/{npm-resolve.d.ts → install/npm-resolve.d.ts} +2 -1
- package/lib/types/services/{task-queue.d.ts → install/task-queue.d.ts} +5 -1
- package/lib/types/services/loader.d.ts +14 -8
- package/lib/types/services/log.d.ts +49 -0
- package/lib/types/services/probe.d.ts +30 -0
- package/lib/types/services/{pending-restart.d.ts → profile/pending-restart.d.ts} +1 -1
- package/lib/types/services/{profile.d.ts → profile/profile.d.ts} +2 -0
- package/lib/types/services/settings.d.ts +24 -0
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +341 -120
- package/src/client/components/{CatalogList.tsx → catalog/CatalogList.tsx} +11 -7
- package/src/client/components/{PluginCard.tsx → catalog/PluginCard.tsx} +5 -4
- package/src/client/components/layout/CatalogControls.tsx +80 -0
- package/src/client/components/{CatalogHeader.tsx → layout/CatalogHeader.tsx} +4 -4
- package/src/client/components/{CategoryTabs.tsx → layout/CategoryTabs.tsx} +3 -3
- package/src/client/components/layout/SectionTabs.tsx +60 -0
- package/src/client/components/{AboutModal.tsx → modals/AboutModal.tsx} +7 -7
- package/src/client/components/{HubUpdateModal.tsx → modals/HubUpdateModal.tsx} +5 -5
- package/src/client/components/modals/InstalledDetailModal.tsx +193 -0
- package/src/client/components/modals/LogsModal.tsx +301 -0
- package/src/client/components/modals/LogsPathModal.tsx +161 -0
- package/src/client/components/{NotificationsModal.tsx → modals/NotificationsModal.tsx} +47 -20
- package/src/client/components/{ProgressView.tsx → modals/ProgressView.tsx} +2 -2
- package/src/client/components/{modals.tsx → modals/modals.tsx} +117 -40
- package/src/client/components/{Dropdown.tsx → ui/Dropdown.tsx} +14 -6
- package/src/client/components/ui/Toggle.tsx +28 -0
- package/src/client/components/ui/icons.tsx +343 -0
- package/src/client/components/views/CustomInstallView.tsx +250 -0
- package/src/client/components/views/DiagnosticsView.tsx +172 -0
- package/src/client/components/views/InstalledView.tsx +328 -0
- package/src/client/components/views/LogsView.tsx +150 -0
- package/src/client/components/views/MarketView.tsx +66 -0
- package/src/client/components/views/SettingsView.tsx +273 -0
- package/src/client/data/catalog.ts +38 -0
- package/src/client/data/host.ts +107 -0
- package/src/client/data/hub.ts +41 -0
- package/src/client/hooks/useCatalog.ts +71 -115
- package/src/client/hooks/useIncrementalList.ts +46 -0
- package/src/client/hooks/useLanguage.ts +40 -0
- package/src/client/hooks/useSettings.ts +90 -0
- package/src/client/hooks/useTaskQueue.ts +156 -39
- package/src/client/locales.ts +358 -38
- package/src/client/logic/constants.ts +87 -0
- package/src/client/{lib → logic}/failures.ts +95 -74
- package/src/client/logic/install-command.ts +56 -0
- package/src/client/logic/installed.ts +207 -0
- package/src/client/logic/normalize.ts +52 -0
- package/src/client/logic/urls.ts +125 -0
- package/src/client/styles/CustomInstallView.module.css +191 -0
- package/src/client/styles/DiagnosticsView.module.css +231 -0
- package/src/client/styles/Dropdown.module.css +42 -41
- package/src/client/styles/Header.module.css +100 -132
- package/src/client/styles/InstalledView.module.css +447 -0
- package/src/client/styles/List.module.css +6 -0
- package/src/client/styles/LogsView.module.css +503 -0
- package/src/client/styles/Modal.module.css +352 -70
- package/src/client/styles/SectionTabs.module.css +142 -0
- package/src/client/styles/SettingsView.module.css +232 -0
- package/src/client/styles/Toggle.module.css +60 -0
- package/src/client/styles/tokens.module.css +1 -0
- package/src/client/types.ts +23 -3
- package/src/server/http/routes.ts +414 -22
- package/src/server/index.ts +1 -1
- package/src/server/services/{install-types.ts → install/install-types.ts} +3 -1
- package/src/server/services/{install.ts → install/install.ts} +5 -5
- package/src/server/services/install/npm-check.ts +53 -0
- package/src/server/services/{npm-resolve.ts → install/npm-resolve.ts} +7 -5
- package/src/server/services/{preflight.ts → install/preflight.ts} +16 -4
- package/src/server/services/{task-queue.ts → install/task-queue.ts} +157 -63
- package/src/server/services/loader.ts +55 -1
- package/src/server/services/log.ts +167 -0
- package/src/server/services/probe.ts +157 -0
- package/src/server/services/{pending-restart.ts → profile/pending-restart.ts} +1 -1
- package/src/server/services/{profile.ts → profile/profile.ts} +31 -1
- package/src/server/services/settings.ts +79 -0
- package/lib/services/install.js +0 -4
- package/src/client/components/CatalogControls.tsx +0 -108
- package/src/client/components/icons.tsx +0 -129
- package/src/client/lib/catalog.ts +0 -293
- package/src/client/lib/env.ts +0 -20
- /package/lib/services/{install-types.js → install/install-types.js} +0 -0
- /package/lib/services/{installed-versions.js → profile/installed-versions.js} +0 -0
- /package/lib/services/{pending-restart.js → profile/pending-restart.js} +0 -0
- /package/lib/services/{progress.js → profile/progress.js} +0 -0
- /package/lib/types/services/{preflight.d.ts → install/preflight.d.ts} +0 -0
- /package/lib/types/services/{installed-versions.d.ts → profile/installed-versions.d.ts} +0 -0
- /package/lib/types/services/{progress.d.ts → profile/progress.d.ts} +0 -0
- /package/src/client/{lib → logic}/format.ts +0 -0
- /package/src/client/{lib → logic}/renderMarkdown.ts +0 -0
- /package/src/server/services/{installed-versions.ts → profile/installed-versions.ts} +0 -0
- /package/src/server/services/{progress.ts → profile/progress.ts} +0 -0
|
@@ -10,12 +10,13 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { createElement as h } from 'react'
|
|
12
12
|
import type { MouseEvent } from 'react'
|
|
13
|
-
import styles from '
|
|
14
|
-
import type { EnvInfo, HubPlugin, TaskState, ToastState, Translate } from '
|
|
15
|
-
import { CloseIcon, CopyIcon, LinkIcon } from '
|
|
13
|
+
import styles from '../../styles/Modal.module.css'
|
|
14
|
+
import type { EnvInfo, HubPlugin, TaskState, ToastState, Translate } from '../../types.ts'
|
|
15
|
+
import { CloseIcon, CopyIcon, LinkIcon } from '../ui/icons.tsx'
|
|
16
16
|
import { ProgressView } from './ProgressView.tsx'
|
|
17
|
-
import { installCommandOf
|
|
18
|
-
import {
|
|
17
|
+
import { installCommandOf } from '../../logic/install-command.ts'
|
|
18
|
+
import { pluginDetailUrl, pluginIssueUrl, pluginSiteUrl } from '../../logic/urls.ts'
|
|
19
|
+
import { classifyFailure, npmTooLowVersion } from '../../logic/failures.ts'
|
|
19
20
|
|
|
20
21
|
/** 完成结果视图:绿色对勾 + 标题/描述;
|
|
21
22
|
* needsRestart=true(插件需重启才生效)→ 「稍后重启 / 立即重启」按钮对,点稍后重启后
|
|
@@ -56,7 +57,7 @@ function ResultView({
|
|
|
56
57
|
h('div', { className: styles.resultRestarting }, restarting ? t('restarting') : t('restartHint')),
|
|
57
58
|
h('div', { className: styles.modalActions },
|
|
58
59
|
h('button', { className: styles.restartLater, onClick: onClose, disabled: restarting }, t('restartLater')),
|
|
59
|
-
h('button', { className: styles.
|
|
60
|
+
h('button', { className: styles.restartNowWarning, onClick: onRestart, disabled: restarting },
|
|
60
61
|
restarting ? t('restarting') : t('restartNow')),
|
|
61
62
|
),
|
|
62
63
|
])
|
|
@@ -66,7 +67,13 @@ function ResultView({
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
export interface InstallModalProps {
|
|
69
|
-
|
|
70
|
+
/** 目录插件(有完整元数据);命令行安装(custom)传 null 走 customTarget 模式 */
|
|
71
|
+
plugin: HubPlugin | null
|
|
72
|
+
/** 命令行安装目标(无目录元数据):提供时按裸目标展示确认/进度/结果,命令条显示 pnpm add <target> */
|
|
73
|
+
customTarget?: string
|
|
74
|
+
/** 全局 npm 安装(官方 README 的 `npm install -g <pkgs>`):提供时按包列表展示确认/进度/结果,
|
|
75
|
+
* 命令条显示 npm install -g <pkgs> —— 装的是系统级 CLI 工具,不进插件列表、无需重启 */
|
|
76
|
+
globalNpm?: string[]
|
|
70
77
|
done: boolean
|
|
71
78
|
task: TaskState | null
|
|
72
79
|
t: Translate
|
|
@@ -89,12 +96,14 @@ export interface InstallModalProps {
|
|
|
89
96
|
/**
|
|
90
97
|
* 信任确认弹窗:安装进入后台队列后弹窗仍可关闭(任务继续),
|
|
91
98
|
* 只在本任务执行中展示实时进度;完成后切换为结果视图,与卸载一致。
|
|
99
|
+
* 目录插件与命令行安装(customTarget 模式)共用同一套确认/进度/结果流程。
|
|
92
100
|
*/
|
|
93
101
|
export function InstallModal(props: InstallModalProps) {
|
|
94
|
-
const { plugin, done, task, t, langPath, restarting, submitting, update, cliOnly, needsRestart, onClose, onCopy, onInstall, onRestart } = props
|
|
102
|
+
const { plugin, customTarget, globalNpm, done, task, t, langPath, restarting, submitting, update, cliOnly, needsRestart, onClose, onCopy, onInstall, onRestart } = props
|
|
95
103
|
const busy = submitting || (task !== null && (task.status === 'pending' || task.status === 'running'))
|
|
96
|
-
// 进行中标题带上插件名(中文「XX
|
|
97
|
-
|
|
104
|
+
// 进行中标题带上插件名(中文「XX 插件安装中」;英文状态词在前更自然),并用状态色区分;
|
|
105
|
+
// 命令行安装无插件元数据 → 直接用输入的目标展示;全局 npm 安装 → 包列表空格拼接
|
|
106
|
+
const name = customTarget ?? (globalNpm !== undefined && globalNpm.length > 0 ? globalNpm.join(' ') : undefined) ?? plugin?.displayName ?? plugin?.slug ?? ''
|
|
98
107
|
const busyTitle = (label: string) => langPath === 'zh/' ? `${name} 插件${label}` : `${label} ${name}`
|
|
99
108
|
const title = busy
|
|
100
109
|
? task && task.status === 'pending'
|
|
@@ -138,10 +147,9 @@ export function InstallModal(props: InstallModalProps) {
|
|
|
138
147
|
h('div', { className: styles.trustHint }, t('confirmDesc')),
|
|
139
148
|
h('div', { className: styles.modalRow },
|
|
140
149
|
h('span', { className: styles.modalLabel }, t('confirmPlugin')),
|
|
141
|
-
h('span', { className: styles.modalValue, title:
|
|
142
|
-
plugin.displayName ?? plugin.slug),
|
|
150
|
+
h('span', { className: styles.modalValue, title: name }, name),
|
|
143
151
|
),
|
|
144
|
-
plugin
|
|
152
|
+
plugin?.source?.repo ? h('div', { className: styles.modalRow },
|
|
145
153
|
h('span', { className: styles.modalLabel }, t('confirmSource')),
|
|
146
154
|
// 来源仓库:可点击跳转官网收录详情页(新窗口打开)
|
|
147
155
|
h('a', {
|
|
@@ -152,7 +160,8 @@ export function InstallModal(props: InstallModalProps) {
|
|
|
152
160
|
title: plugin.source.repo,
|
|
153
161
|
}, h(LinkIcon), plugin.source.repo),
|
|
154
162
|
) : null,
|
|
155
|
-
//
|
|
163
|
+
// 手动命令条:整条点击即复制,右侧再放一个显式复制按钮;
|
|
164
|
+
// 命令行安装(无目录数据)直接展示 pnpm add <target>;全局 npm 安装展示 npm install -g <pkgs>
|
|
156
165
|
h('div', {
|
|
157
166
|
className: styles.modalCmd,
|
|
158
167
|
onClick: onCopy,
|
|
@@ -160,9 +169,16 @@ export function InstallModal(props: InstallModalProps) {
|
|
|
160
169
|
role: 'button',
|
|
161
170
|
tabIndex: 0,
|
|
162
171
|
},
|
|
163
|
-
h('span', { className: styles.modalCmdText },
|
|
172
|
+
h('span', { className: styles.modalCmdText },
|
|
173
|
+
globalNpm !== undefined && globalNpm.length > 0
|
|
174
|
+
? `npm install -g ${globalNpm.join(' ')}`
|
|
175
|
+
: customTarget ? `pnpm add ${customTarget}` : installCommandOf(plugin!)),
|
|
164
176
|
h('span', { className: styles.modalCmdCopy }, h(CopyIcon), t('copyCmdLabel')),
|
|
165
177
|
),
|
|
178
|
+
// 全局 npm 安装说明:装的是系统级 CLI 工具,不进插件列表、无需重启宿主
|
|
179
|
+
globalNpm !== undefined && globalNpm.length > 0
|
|
180
|
+
? h('div', { className: styles.cliOnlyHint }, t('globalNpmHint'))
|
|
181
|
+
: null,
|
|
166
182
|
// AI 识别可能需要命令行辅助的插件(webInstallable=false):提示但不拦截,仍允许尝试一键安装
|
|
167
183
|
cliOnly ? h('div', { className: styles.cliOnlyHint }, t('cliOnlyHint')) : null,
|
|
168
184
|
task && task.status === 'pending' ? h('div', { className: styles.queuedHint }, t('queuedHint')) : null,
|
|
@@ -272,21 +288,22 @@ export function UninstallModal(props: UninstallModalProps) {
|
|
|
272
288
|
task && task.status === 'pending' ? h('div', { className: styles.queuedHint }, t('queuedHint')) : null,
|
|
273
289
|
task ? h(ProgressView, { task }) : null,
|
|
274
290
|
h('div', { className: styles.modalActions },
|
|
275
|
-
h('button', {
|
|
276
|
-
className: styles.modalCancel,
|
|
277
|
-
onClick: onCancel,
|
|
278
|
-
}, t('confirmCancel')),
|
|
279
291
|
// 复制卸载命令:万一直接卸载失败,可去终端手动执行
|
|
280
292
|
h('button', {
|
|
281
293
|
className: styles.modalCopy,
|
|
282
294
|
disabled: busy,
|
|
283
295
|
onClick: onCopyCommand,
|
|
284
296
|
}, t('copyUninstallCommand')),
|
|
297
|
+
// 确认框语义:取消动作需明确指向「卸载」,避免歧义
|
|
298
|
+
h('button', {
|
|
299
|
+
className: styles.modalCancel,
|
|
300
|
+
onClick: onCancel,
|
|
301
|
+
}, t('cancelUninstall')),
|
|
285
302
|
h('button', {
|
|
286
303
|
className: styles.uninstallConfirm,
|
|
287
304
|
disabled: busy,
|
|
288
305
|
onClick: onConfirm,
|
|
289
|
-
}, busy ? (task && task.status === 'pending' ? t('queuedUninstallTitle') : t('uninstalling')) : t('
|
|
306
|
+
}, busy ? (task && task.status === 'pending' ? t('queuedUninstallTitle') : t('uninstalling')) : t('uninstallConfirm')),
|
|
290
307
|
),
|
|
291
308
|
),
|
|
292
309
|
),
|
|
@@ -309,6 +326,16 @@ export function ErrorModal({ message, repo, kind, command, attempts, t, env, onC
|
|
|
309
326
|
}) {
|
|
310
327
|
// 失败归类:当前安装通道(npm/git)装不上 = 插件分发/依赖的问题,一律引导提 Issue
|
|
311
328
|
const failureKind = classifyFailure(message)
|
|
329
|
+
// 服务端 [npm-too-low] 标记里带的本机 npm 版本(无标记为 null,提示文案降级为「怀疑」)
|
|
330
|
+
const npmVersion = npmTooLowVersion(message)
|
|
331
|
+
// 复制完整错误:实际执行/尝试过的安装命令(如有)+ 完整错误正文。
|
|
332
|
+
// 只复制 message 会丢上下文 —— 命令行安装输入的目标、执行过的命令都在 command/attempts 里,
|
|
333
|
+
// 光拷一句「unsupported install target」出去没人看得懂
|
|
334
|
+
const copyText = [
|
|
335
|
+
...(command ? [command] : []),
|
|
336
|
+
...(attempts ?? []),
|
|
337
|
+
message,
|
|
338
|
+
].join('\n')
|
|
312
339
|
return h('div', {
|
|
313
340
|
className: styles.overlay,
|
|
314
341
|
onClick: (e: MouseEvent<HTMLDivElement>) => {
|
|
@@ -341,33 +368,37 @@ export function ErrorModal({ message, repo, kind, command, attempts, t, env, onC
|
|
|
341
368
|
title: repo,
|
|
342
369
|
}, repo) : null,
|
|
343
370
|
// 复制按钮放最上面、浅灰隐蔽;报错正文不允许鼠标选择复制,只能点这里
|
|
344
|
-
h('button', { className: styles.errorCopySoft, onClick: () => onCopy(
|
|
371
|
+
h('button', { className: styles.errorCopySoft, onClick: () => onCopy(copyText) }, t('errorCopy')),
|
|
345
372
|
),
|
|
346
373
|
// 报错信息完整展示(可滚动),比失败记录的预览看得更多
|
|
347
374
|
h('pre', { className: styles.errorBox }, message),
|
|
348
|
-
//
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
375
|
+
// npm 内部崩溃(edgesOut)+ 本机 npm 版本过低:npm 自身已知缺陷,不是插件问题 ——
|
|
376
|
+
// 直接给升级指引,不引导提 Issue(提了也是 npm 的问题,插件作者无法修复)
|
|
377
|
+
failureKind === 'npmTooOld'
|
|
378
|
+
? h('div', { className: styles.failPrepareHint },
|
|
379
|
+
t(npmVersion ? 'failNpmTooLowV' : 'failNpmTooLow', npmVersion ? { v: npmVersion } : undefined))
|
|
380
|
+
: failureKind === 'pluginPrepare' || failureKind === 'pnpmIgnoredBuild'
|
|
381
|
+
? h('div', null, [
|
|
382
|
+
// [packaging](预检/装后校验拦截:git 分发缺产物)与
|
|
383
|
+
// prepare 构建脚本实际执行失败 / 原生依赖构建被 pnpm 拦截:都是插件打包分发问题 —— 先说明原因,按钮在下方提交
|
|
384
|
+
h('div', { className: styles.failPrepareHint }, failureKind === 'pnpmIgnoredBuild'
|
|
385
|
+
? t('failIgnoredBuild')
|
|
386
|
+
: /\[packaging\]/i.test(message) ? t('failPackagingHint') : t('failPrepareHint')),
|
|
387
|
+
repo ? h('a', {
|
|
388
|
+
className: styles.failBigIssue,
|
|
389
|
+
href: pluginIssueUrl(repo, message, env, command, attempts),
|
|
390
|
+
target: '_blank',
|
|
391
|
+
rel: 'noopener noreferrer',
|
|
392
|
+
title: t('failIssueHint'),
|
|
393
|
+
}, t('failIssueBig')) : null,
|
|
394
|
+
])
|
|
395
|
+
: repo ? h('a', {
|
|
357
396
|
className: styles.failBigIssue,
|
|
358
397
|
href: pluginIssueUrl(repo, message, env, command, attempts),
|
|
359
398
|
target: '_blank',
|
|
360
399
|
rel: 'noopener noreferrer',
|
|
361
400
|
title: t('failIssueHint'),
|
|
362
401
|
}, t('failIssueBig')) : null,
|
|
363
|
-
])
|
|
364
|
-
: repo ? h('a', {
|
|
365
|
-
className: styles.failBigIssue,
|
|
366
|
-
href: pluginIssueUrl(repo, message, env, command, attempts),
|
|
367
|
-
target: '_blank',
|
|
368
|
-
rel: 'noopener noreferrer',
|
|
369
|
-
title: t('failIssueHint'),
|
|
370
|
-
}, t('failIssueBig')) : null,
|
|
371
402
|
),
|
|
372
403
|
h('div', { className: styles.modalActions },
|
|
373
404
|
h('button', { className: styles.restartLater, onClick: onClose }, t('errorClose')),
|
|
@@ -384,10 +415,56 @@ export function Toast({ toast, t }: { toast: ToastState; t: Translate }) {
|
|
|
384
415
|
: toast.kind === 'done' ? t('installDone')
|
|
385
416
|
: toast.kind === 'fail' ? t('installFail')
|
|
386
417
|
: toast.kind === 'removed' ? t('uninstallDone')
|
|
387
|
-
: t('
|
|
388
|
-
|
|
418
|
+
: toast.kind === 'revealFail' ? t('openFolderFail')
|
|
419
|
+
: toast.kind === 'updates' ? t('updatesFound', { n: toast.n ?? 0 })
|
|
420
|
+
: t('uninstallFail')
|
|
421
|
+
const fail = toast.kind === 'fail' || toast.kind === 'removeFail' || toast.kind === 'revealFail'
|
|
389
422
|
return h('div', {
|
|
390
423
|
key: toast.id,
|
|
391
424
|
className: fail ? `${styles.toast} ${styles.toastFail}` : styles.toast,
|
|
392
425
|
}, text)
|
|
393
426
|
}
|
|
427
|
+
|
|
428
|
+
/** 待重启确认弹窗:已安装列表行内「重启」按钮点击后弹出,
|
|
429
|
+
* 与通知中心待重启条目同一交互(说明 + 稍后重启 / 立即重启),
|
|
430
|
+
* 避免行内按钮误触直接触发宿主重启。 */
|
|
431
|
+
export function RestartConfirmModal({ t, restarting, onClose, onRestartNow }: {
|
|
432
|
+
t: Translate
|
|
433
|
+
restarting: boolean
|
|
434
|
+
onClose: () => void
|
|
435
|
+
onRestartNow: () => void
|
|
436
|
+
}) {
|
|
437
|
+
return h('div', {
|
|
438
|
+
className: styles.overlay,
|
|
439
|
+
onClick: (e: MouseEvent<HTMLDivElement>) => { if (e.target === e.currentTarget) onClose() },
|
|
440
|
+
},
|
|
441
|
+
h('div', { className: styles.modal, role: 'dialog', 'aria-modal': 'true' },
|
|
442
|
+
h('div', { className: styles.modalHead },
|
|
443
|
+
h('div', { className: styles.modalTitle }, t('sectionPendingRestart')),
|
|
444
|
+
h('button', {
|
|
445
|
+
className: styles.modalClose,
|
|
446
|
+
type: 'button',
|
|
447
|
+
'aria-label': t('errorClose'),
|
|
448
|
+
onClick: onClose,
|
|
449
|
+
}, h(CloseIcon)),
|
|
450
|
+
),
|
|
451
|
+
h('div', { className: styles.modalBody },
|
|
452
|
+
h('div', { className: styles.failPrepareHint }, t('restartHint')),
|
|
453
|
+
h('div', { className: styles.modalActions },
|
|
454
|
+
h('button', {
|
|
455
|
+
className: styles.restartLater,
|
|
456
|
+
type: 'button',
|
|
457
|
+
disabled: restarting,
|
|
458
|
+
onClick: onClose,
|
|
459
|
+
}, t('restartLater')),
|
|
460
|
+
h('button', {
|
|
461
|
+
className: styles.restartNowWarning,
|
|
462
|
+
type: 'button',
|
|
463
|
+
disabled: restarting,
|
|
464
|
+
onClick: onRestartNow,
|
|
465
|
+
}, restarting ? t('restarting') : t('restartNow')),
|
|
466
|
+
),
|
|
467
|
+
),
|
|
468
|
+
),
|
|
469
|
+
)
|
|
470
|
+
}
|
|
@@ -3,14 +3,17 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Button-style dropdown (native <select> replacement).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* The trigger is a compact toolbar button — hairline border + subtle grey
|
|
9
|
+
* fill + current value + chevron — so it sits naturally next to the other
|
|
10
|
+
* toolbar buttons and takes a single slot of horizontal space. Clicking
|
|
11
|
+
* outside or pressing Esc closes the panel; options get a hover fill and
|
|
12
|
+
* the active one a brand highlight, all driven by the --hub-* tokens.
|
|
10
13
|
*/
|
|
11
14
|
import { createElement as h, useEffect, useRef, useState } from 'react'
|
|
12
15
|
import type { JSX } from 'react'
|
|
13
|
-
import styles from '
|
|
16
|
+
import styles from '../../styles/Dropdown.module.css'
|
|
14
17
|
import { ChevronDownIcon } from './icons.tsx'
|
|
15
18
|
|
|
16
19
|
export interface DropdownOption<T extends string> {
|
|
@@ -25,9 +28,11 @@ interface DropdownProps<T extends string> {
|
|
|
25
28
|
options: DropdownOption<T>[]
|
|
26
29
|
onChange: (value: T) => void
|
|
27
30
|
title?: string
|
|
31
|
+
/** 额外挂到根节点上的类名(如 margin 微调) */
|
|
32
|
+
className?: string
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
export function Dropdown<T extends string>({ value, options, onChange, title }: DropdownProps<T>): JSX.Element {
|
|
35
|
+
export function Dropdown<T extends string>({ value, options, onChange, title, className }: DropdownProps<T>): JSX.Element {
|
|
31
36
|
const [open, setOpen] = useState(false)
|
|
32
37
|
const rootRef = useRef<HTMLDivElement | null>(null)
|
|
33
38
|
|
|
@@ -50,7 +55,10 @@ export function Dropdown<T extends string>({ value, options, onChange, title }:
|
|
|
50
55
|
|
|
51
56
|
const current = options.find((o) => o.value === value)
|
|
52
57
|
|
|
53
|
-
return h('div', {
|
|
58
|
+
return h('div', {
|
|
59
|
+
className: className ? `${styles.dropdown} ${className}` : styles.dropdown,
|
|
60
|
+
ref: rootRef,
|
|
61
|
+
},
|
|
54
62
|
h('button', {
|
|
55
63
|
className: styles.dropdownBtn,
|
|
56
64
|
type: 'button',
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
6
|
+
* Compact switch used by the settings rows. Native-button based so it is
|
|
7
|
+
* keyboard focusable and announces state via aria-checked; styling driven
|
|
8
|
+
* by the --hub-* tokens (on = brand fill, off = neutral track).
|
|
9
|
+
*/
|
|
10
|
+
import { createElement as h } from 'react'
|
|
11
|
+
import styles from '../../styles/Toggle.module.css'
|
|
12
|
+
|
|
13
|
+
export function Toggle({ checked, onChange, title, disabled }: {
|
|
14
|
+
checked: boolean
|
|
15
|
+
onChange: (next: boolean) => void
|
|
16
|
+
title?: string
|
|
17
|
+
disabled?: boolean
|
|
18
|
+
}) {
|
|
19
|
+
return h('button', {
|
|
20
|
+
type: 'button',
|
|
21
|
+
role: 'switch',
|
|
22
|
+
'aria-checked': checked,
|
|
23
|
+
disabled,
|
|
24
|
+
title,
|
|
25
|
+
className: checked ? styles.toggleOn : styles.toggle,
|
|
26
|
+
onClick: () => onChange(!checked),
|
|
27
|
+
}, h('span', { className: styles.knob }))
|
|
28
|
+
}
|