dsh-auto-open-web 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +18 -0
- package/README.en.md +181 -0
- package/README.md +167 -0
- package/cordis.patch.yml +5 -0
- package/host-publish/DshAppWindow.deps.json +94 -0
- package/host-publish/DshAppWindow.dll +0 -0
- package/host-publish/DshAppWindow.exe +0 -0
- package/host-publish/DshAppWindow.pdb +0 -0
- package/host-publish/DshAppWindow.runtimeconfig.json +20 -0
- package/host-publish/Microsoft.Web.WebView2.Core.dll +0 -0
- package/host-publish/Microsoft.Web.WebView2.Core.xml +6662 -0
- package/host-publish/Microsoft.Web.WebView2.WinForms.dll +0 -0
- package/host-publish/Microsoft.Web.WebView2.WinForms.xml +504 -0
- package/host-publish/Microsoft.Web.WebView2.Wpf.dll +0 -0
- package/host-publish/Microsoft.Web.WebView2.Wpf.xml +1902 -0
- package/host-publish/runtimes/win-arm64/native/WebView2Loader.dll +0 -0
- package/host-publish/runtimes/win-x64/native/WebView2Loader.dll +0 -0
- package/host-publish/runtimes/win-x86/native/WebView2Loader.dll +0 -0
- package/lib/client.js +398 -0
- package/lib/index.js +553 -0
- package/lib/paths.js +27 -0
- package/lib/platform.js +27 -0
- package/lib/posix.js +42 -0
- package/lib/win32.js +347 -0
- package/lib/worker.cjs +174 -0
- package/package.json +47 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
// dsh-auto-open-web — Client bundle(设置卡片,官方控件 + 官方 webServer 数据通道)
|
|
2
|
+
// 格式:window.__ModuleLoader__.load({ id, factory });factory(require) 的
|
|
3
|
+
// require 解析浏览器端冻结模块表(react、@deepseek-ai/dsh-client-ui-primitives 等)。
|
|
4
|
+
//
|
|
5
|
+
// 数据通道:设置卡片经官方 ctx.webServer JSON API 读写(第三方 settings 命名空间
|
|
6
|
+
// 不在客户端白名单,故走自定义 API,与 proxy-router 卡片同一机制):
|
|
7
|
+
// GET /auto-open-web/config → { ok, appWindow, windowKind,
|
|
8
|
+
// browserPath, exitOnWindowClose, writable }
|
|
9
|
+
// POST /auto-open-web/config → { ok, ... } | { ok:false, error }
|
|
10
|
+
// POST /auto-open-web/pick-browser → 原生"浏览"对话框(宿主子进程 + koffi
|
|
11
|
+
// IFileOpenDialog,与官方工作区选择器同机制;不使用 PowerShell),返回
|
|
12
|
+
// { ok:true, path } | { ok:false, cancelled:true, error }
|
|
13
|
+
// 说明:网页 <input type=file> 拿不到本地完整路径,故浏览走宿主原生对话框。
|
|
14
|
+
//
|
|
15
|
+
// 注意:不做客户端 schema 校验——浏览器内置(frontend dist 冻结)的
|
|
16
|
+
// dsh-client-schema-form 副本对本插件 schema 执行 validateDraft 会抛
|
|
17
|
+
// "n is not a function"(与 npm 安装版行为不一致),导致保存按钮永远置灰;
|
|
18
|
+
// 保存由宿主权威校验(normalizeState + scope.update)并持久化到 settings。
|
|
19
|
+
//
|
|
20
|
+
// 官方 API 使用:
|
|
21
|
+
// - 控件:Input / Button(官方 primitives,与全站设计系统一致)
|
|
22
|
+
// - 草稿模型:draft 普通对象,setPath 不可变编辑
|
|
23
|
+
// 卡片外壳保持内置 PluginCard 同款外观(与 proxy-router 卡片同一份 CSS)。
|
|
24
|
+
window.__ModuleLoader__.load({
|
|
25
|
+
id: 'dsh-auto-open-web',
|
|
26
|
+
factory: (require) => {
|
|
27
|
+
var module = { exports: {} }
|
|
28
|
+
var exports = module.exports
|
|
29
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
30
|
+
let React = require('react')
|
|
31
|
+
|
|
32
|
+
// ---- 内置 PluginCard.module.css(默认卡片外观,原样注入) ----
|
|
33
|
+
var CARD_CSS = '.YyYd_a_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.YyYd_a_card:hover{border-color:var(--dsw-alias-label-dimmed,var(--dsw-alias-border-l2))}.YyYd_a_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed,var(--dsw-alias-border-l2))}.YyYd_a_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.YyYd_a_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.YyYd_a_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.YyYd_a_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.YyYd_a_description{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));font-size:13px;line-height:1.5}.YyYd_a_chevron{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));flex:none;transition:transform .16s}.YyYd_a_chevronOpen{transform:rotate(180deg)}.YyYd_a_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.YyYd_a_readOnly{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));margin:12px 0 0;font-size:12px;line-height:1.5}.YyYd_a_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.YyYd_a_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.YyYd_a_failed{min-width:0;color:var(--dsw-alias-label-error,var(--dsw-alias-state-error-primary));flex:1;margin:0;font-size:12px;line-height:1.5}.YyYd_a_discard,.YyYd_a_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.YyYd_a_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.YyYd_a_save{background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-inverse,#fff);border-color:var(--dsw-alias-brand-primary)}.YyYd_a_save:hover:not(:disabled){filter:brightness(.95)}.YyYd_a_discard:disabled,.YyYd_a_save:disabled{opacity:.4;cursor:default}'
|
|
34
|
+
var CARD_TAG = 'dsh-auto-open-web/PluginCard.module.css'
|
|
35
|
+
// ---- 内置 ValueField 字段布局样式(label/hint 排版,原样注入) ----
|
|
36
|
+
var FIELDS_CSS = '.At1oFq_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.At1oFq_field+.At1oFq_field{border-top:1px solid var(--dsw-alias-border-l2)}.At1oFq_head{align-items:center;gap:8px;display:flex}.At1oFq_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.At1oFq_badges{align-items:center;gap:8px;display:inline-flex}.At1oFq_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.At1oFq_badgeMuted{white-space:nowrap;color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px}.At1oFq_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.At1oFq_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.At1oFq_reset:disabled{cursor:default}.At1oFq_input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.At1oFq_input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.At1oFq_input:disabled{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));cursor:default}.At1oFq_inputInvalid{border-color:var(--dsw-alias-label-error,var(--dsw-alias-state-error-primary));}.At1oFq_invalid{color:var(--dsw-alias-label-error,var(--dsw-alias-state-error-primary));margin:0;font-size:12px;line-height:1.5}.At1oFq_hint{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));margin:0;font-size:12px;line-height:1.5}'
|
|
37
|
+
var FIELDS_TAG = 'dsh-auto-open-web/Fields.module.css'
|
|
38
|
+
// ---- 极少量自有布局 CSS(仅行排列/复选行,不改变观感) ----
|
|
39
|
+
var EXTRA_CSS =
|
|
40
|
+
'.aow-row{display:flex;gap:8px;align-items:center}' +
|
|
41
|
+
'.aow-grow{flex:1;min-width:0}' +
|
|
42
|
+
'.aow-check{display:flex;align-items:center;gap:8px;cursor:pointer}' +
|
|
43
|
+
'.aow-check input{accent-color:var(--dsw-alias-brand-primary);width:15px;height:15px;margin:0;flex:none}' +
|
|
44
|
+
'.aow-check input:disabled{cursor:default}' +
|
|
45
|
+
'.aow-test-ok{color:var(--dsw-alias-state-success-primary,#3a8a4a);margin:4px 0 0;font-size:12px;line-height:1.5}' +
|
|
46
|
+
'.aow-test-fail{color:var(--dsw-alias-label-error,var(--dsw-alias-state-error-primary));margin:4px 0 0;font-size:12px;line-height:1.5}'
|
|
47
|
+
var EXTRA_TAG = 'dsh-auto-open-web/Extra.module.css'
|
|
48
|
+
function injectCss(tagId, css) {
|
|
49
|
+
if (typeof document === 'undefined') return
|
|
50
|
+
if (document.querySelector('style[data-plugin-css=' + JSON.stringify(tagId) + ']') !== null) return
|
|
51
|
+
const tag = document.createElement('style')
|
|
52
|
+
tag.dataset.plugin = 'dsh-auto-open-web'
|
|
53
|
+
tag.dataset.pluginCss = tagId
|
|
54
|
+
tag.textContent = css
|
|
55
|
+
document.head.appendChild(tag)
|
|
56
|
+
}
|
|
57
|
+
injectCss(CARD_TAG, CARD_CSS)
|
|
58
|
+
injectCss(FIELDS_TAG, FIELDS_CSS)
|
|
59
|
+
injectCss(EXTRA_TAG, EXTRA_CSS)
|
|
60
|
+
|
|
61
|
+
// ---- 官方共享模块(冻结表):控件 + 草稿/校验模型 ----
|
|
62
|
+
// require 失败时降级而非崩溃(卡片仍然渲染,仅退回基础能力)
|
|
63
|
+
var prim = null
|
|
64
|
+
var form = null
|
|
65
|
+
try {
|
|
66
|
+
prim = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
67
|
+
form = require('@deepseek-ai/dsh-client-schema-form')
|
|
68
|
+
} catch (e) {
|
|
69
|
+
console.error('[auto-open-web] shared modules require failed:', e !== null && e !== undefined && e.message !== undefined ? e.message : String(e))
|
|
70
|
+
}
|
|
71
|
+
var Button = prim !== null && prim.Button !== undefined ? prim.Button : null
|
|
72
|
+
var Input = prim !== null && prim.Input !== undefined ? prim.Input : null
|
|
73
|
+
var ChevronIcon = prim !== null && prim.IconChevronDownOutline14 !== undefined ? prim.IconChevronDownOutline14 : null
|
|
74
|
+
// 注意:不在此处使用官方 rehydrateSchema/validateDraft——浏览器内置(frontend
|
|
75
|
+
// dist 冻结)副本对本插件 schema 校验会抛 "n is not a function" 异常(兼容性
|
|
76
|
+
// 差异),导致 invalid 恒真、保存按钮置灰;宿主侧 normalizeState + scope.update
|
|
77
|
+
// 才是权威校验,本卡片字段也无格式约束,故客户端只做草稿编辑(setPath)。
|
|
78
|
+
var setPath = form !== null ? form.setPath : null
|
|
79
|
+
|
|
80
|
+
// ---- 草稿编辑:优先官方 setPath,不可用时降级为本地实现 ----
|
|
81
|
+
function applySetPath(draftObj, path, value) {
|
|
82
|
+
if (setPath !== null) return setPath(draftObj, path, value)
|
|
83
|
+
const next = Object.assign({}, draftObj)
|
|
84
|
+
next[path[0]] = value
|
|
85
|
+
return next
|
|
86
|
+
}
|
|
87
|
+
// 控件:优先官方 Input/Button,不可用时退回原生元素(用内置字段样式)
|
|
88
|
+
function ProxyInput(props) {
|
|
89
|
+
if (Input !== null) return React.createElement(Input, props)
|
|
90
|
+
const rest = Object.assign({}, props)
|
|
91
|
+
delete rest.className
|
|
92
|
+
return React.createElement('input', Object.assign({ className: 'At1oFq_input' + (props.className !== undefined ? ' ' + props.className : '') }, rest))
|
|
93
|
+
}
|
|
94
|
+
function ProxyButton(props) {
|
|
95
|
+
if (Button !== null) return React.createElement(Button, props)
|
|
96
|
+
const rest = Object.assign({}, props)
|
|
97
|
+
delete rest.className
|
|
98
|
+
return React.createElement('button', Object.assign({ type: 'button', className: 'YyYd_a_discard' + (props.className !== undefined ? ' ' + props.className : '') }, rest))
|
|
99
|
+
}
|
|
100
|
+
function Checkbox(props) {
|
|
101
|
+
return React.createElement('label', { className: 'aow-check' },
|
|
102
|
+
React.createElement('input', {
|
|
103
|
+
type: 'checkbox',
|
|
104
|
+
checked: props.checked === true,
|
|
105
|
+
disabled: props.disabled === true,
|
|
106
|
+
onChange: (event) => props.onChange(event.target.checked)
|
|
107
|
+
}),
|
|
108
|
+
React.createElement('span', { className: 'At1oFq_hint' }, props.children)
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
function Radio(props) {
|
|
112
|
+
return React.createElement('label', { className: 'aow-check' },
|
|
113
|
+
React.createElement('input', {
|
|
114
|
+
type: 'radio',
|
|
115
|
+
name: 'auto-open-web-window-kind',
|
|
116
|
+
checked: props.checked === true,
|
|
117
|
+
disabled: props.disabled === true,
|
|
118
|
+
onChange: () => props.onChange(props.value)
|
|
119
|
+
}),
|
|
120
|
+
React.createElement('span', { className: 'At1oFq_hint' }, props.children)
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function AutoOpenSettingsPage() {
|
|
125
|
+
const [open, setOpen] = React.useState(false)
|
|
126
|
+
const [config, setConfig] = React.useState(null) // { appWindow, windowKind, browserPath, exitOnWindowClose, writable } | null
|
|
127
|
+
const [loadError, setLoadError] = React.useState('')
|
|
128
|
+
const [reloadTick, setReloadTick] = React.useState(0)
|
|
129
|
+
const [draft, setDraft] = React.useState({ appWindow: true, windowKind: 'webview2', browserPath: '', exitOnWindowClose: false })
|
|
130
|
+
const [busy, setBusy] = React.useState(false)
|
|
131
|
+
const [picking, setPicking] = React.useState(false)
|
|
132
|
+
const [testing, setTesting] = React.useState(false)
|
|
133
|
+
const [testResult, setTestResult] = React.useState(null) // { ok, text } | null
|
|
134
|
+
const [failedText, setFailedText] = React.useState('')
|
|
135
|
+
|
|
136
|
+
// 配置加载:官方 webServer API(GET /auto-open-web/config)
|
|
137
|
+
React.useEffect(() => {
|
|
138
|
+
let cancelled = false
|
|
139
|
+
fetch('/auto-open-web/config')
|
|
140
|
+
.then((response) => response.json())
|
|
141
|
+
.then((result) => {
|
|
142
|
+
if (cancelled) return
|
|
143
|
+
if (result === null || result === undefined || result.ok !== true) {
|
|
144
|
+
setLoadError(result !== null && result !== undefined && typeof result.error === 'string' ? result.error : '配置加载失败')
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
setConfig({
|
|
148
|
+
appWindow: result.appWindow !== false,
|
|
149
|
+
windowKind: result.windowKind === 'browser' ? 'browser' : 'webview2',
|
|
150
|
+
browserPath: typeof result.browserPath === 'string' ? result.browserPath : '',
|
|
151
|
+
exitOnWindowClose: result.exitOnWindowClose === true,
|
|
152
|
+
writable: result.writable !== false,
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
.catch((error) => {
|
|
156
|
+
if (!cancelled) setLoadError('配置加载失败: ' + String(error !== null && error !== undefined && error.message !== undefined ? error.message : error))
|
|
157
|
+
})
|
|
158
|
+
return () => { cancelled = true }
|
|
159
|
+
}, [reloadTick])
|
|
160
|
+
|
|
161
|
+
const saved = config !== null
|
|
162
|
+
? { appWindow: config.appWindow, windowKind: config.windowKind, browserPath: config.browserPath, exitOnWindowClose: config.exitOnWindowClose }
|
|
163
|
+
: null
|
|
164
|
+
|
|
165
|
+
// 快照值 → 草稿(外部变化/保存后同步)
|
|
166
|
+
React.useEffect(() => {
|
|
167
|
+
if (saved !== null) setDraft({
|
|
168
|
+
appWindow: saved.appWindow,
|
|
169
|
+
windowKind: saved.windowKind,
|
|
170
|
+
browserPath: saved.browserPath,
|
|
171
|
+
exitOnWindowClose: saved.exitOnWindowClose,
|
|
172
|
+
})
|
|
173
|
+
}, [saved !== null ? JSON.stringify(saved) : 'none'])
|
|
174
|
+
|
|
175
|
+
const loading = config === null && loadError === ''
|
|
176
|
+
const writable = config !== null ? config.writable !== false : true
|
|
177
|
+
// 客户端不做 schema 校验(浏览器内置 schema-form 兼容性问题,见上方注释);
|
|
178
|
+
// 保存由宿主权威校验(normalizeState + scope.update)。
|
|
179
|
+
const invalid = false
|
|
180
|
+
const dirty = saved !== null && (
|
|
181
|
+
draft.appWindow !== saved.appWindow ||
|
|
182
|
+
draft.windowKind !== saved.windowKind ||
|
|
183
|
+
draft.browserPath !== saved.browserPath ||
|
|
184
|
+
draft.exitOnWindowClose !== saved.exitOnWindowClose
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
const blocked = saved === null || !dirty || invalid || busy
|
|
188
|
+
|
|
189
|
+
const save = () => {
|
|
190
|
+
if (blocked) return
|
|
191
|
+
setBusy(true)
|
|
192
|
+
setFailedText('')
|
|
193
|
+
fetch('/auto-open-web/config', {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
headers: { 'content-type': 'application/json' },
|
|
196
|
+
body: JSON.stringify({
|
|
197
|
+
appWindow: draft.appWindow,
|
|
198
|
+
windowKind: draft.windowKind,
|
|
199
|
+
browserPath: draft.browserPath,
|
|
200
|
+
exitOnWindowClose: draft.exitOnWindowClose,
|
|
201
|
+
}),
|
|
202
|
+
})
|
|
203
|
+
.then((response) => response.json())
|
|
204
|
+
.then((result) => {
|
|
205
|
+
if (result !== null && result !== undefined && result.ok === true) {
|
|
206
|
+
setConfig((prev) => prev !== null ? {
|
|
207
|
+
appWindow: result.appWindow !== false,
|
|
208
|
+
windowKind: result.windowKind === 'browser' ? 'browser' : 'webview2',
|
|
209
|
+
browserPath: typeof result.browserPath === 'string' ? result.browserPath : '',
|
|
210
|
+
exitOnWindowClose: result.exitOnWindowClose === true,
|
|
211
|
+
writable: prev.writable,
|
|
212
|
+
} : prev)
|
|
213
|
+
} else {
|
|
214
|
+
setFailedText('保存失败: ' + (result !== null && result !== undefined && typeof result.error === 'string' ? result.error : '未知错误'))
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
.catch((error) => { setFailedText('保存失败: ' + String(error !== null && error !== undefined && error.message !== undefined ? error.message : error)) })
|
|
218
|
+
.then(() => { setBusy(false) })
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const discard = () => {
|
|
222
|
+
if (saved !== null) setDraft({
|
|
223
|
+
appWindow: saved.appWindow,
|
|
224
|
+
windowKind: saved.windowKind,
|
|
225
|
+
browserPath: saved.browserPath,
|
|
226
|
+
exitOnWindowClose: saved.exitOnWindowClose,
|
|
227
|
+
})
|
|
228
|
+
setFailedText('')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const retryLoad = () => {
|
|
232
|
+
setLoadError('')
|
|
233
|
+
setReloadTick((tick) => tick + 1)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 原生"浏览"对话框:宿主弹出文件选择器,选中后回填路径
|
|
237
|
+
const browse = () => {
|
|
238
|
+
if (!writable || picking) return
|
|
239
|
+
setPicking(true)
|
|
240
|
+
setFailedText('')
|
|
241
|
+
fetch('/auto-open-web/pick-browser', { method: 'POST' })
|
|
242
|
+
.then((response) => response.json())
|
|
243
|
+
.then((result) => {
|
|
244
|
+
if (result !== null && result !== undefined && result.ok === true && typeof result.path === 'string') {
|
|
245
|
+
setDraft(applySetPath(draft, ['browserPath'], result.path))
|
|
246
|
+
} else if (result === null || result === undefined || result.cancelled !== true) {
|
|
247
|
+
setFailedText('选择失败: ' + (result !== null && result !== undefined && typeof result.error === 'string' ? result.error : '未知错误'))
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
.catch((error) => { setFailedText('选择失败: ' + String(error !== null && error !== undefined && error.message !== undefined ? error.message : error)) })
|
|
251
|
+
.then(() => { setPicking(false) })
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// 测试:真正拉起 --app 专用测试实例,确认浏览器可被成功启动
|
|
255
|
+
// (使用当前草稿路径,未保存也能测试;结果展示在字段下方)
|
|
256
|
+
const testBrowser = () => {
|
|
257
|
+
if (testing) return
|
|
258
|
+
setTesting(true)
|
|
259
|
+
setTestResult(null)
|
|
260
|
+
setFailedText('')
|
|
261
|
+
fetch('/auto-open-web/test-browser', {
|
|
262
|
+
method: 'POST',
|
|
263
|
+
headers: { 'content-type': 'application/json' },
|
|
264
|
+
body: JSON.stringify({ browserPath: draft.browserPath }),
|
|
265
|
+
})
|
|
266
|
+
.then((response) => response.json())
|
|
267
|
+
.then((result) => {
|
|
268
|
+
if (result !== null && result !== undefined && result.ok === true) {
|
|
269
|
+
setTestResult({ ok: true, text: typeof result.message === 'string' ? result.message : '浏览器拉起成功' })
|
|
270
|
+
} else {
|
|
271
|
+
setTestResult({ ok: false, text: '测试失败: ' + (result !== null && result !== undefined && typeof result.error === 'string' ? result.error : '未知错误') })
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
.catch((error) => { setTestResult({ ok: false, text: '测试失败: ' + String(error !== null && error !== undefined && error.message !== undefined ? error.message : error) }) })
|
|
275
|
+
.then(() => { setTesting(false) })
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const chevronCls = 'YyYd_a_chevron' + (open ? ' YyYd_a_chevronOpen' : '')
|
|
279
|
+
|
|
280
|
+
return React.createElement('li', { className: 'YyYd_a_card' + (open ? ' YyYd_a_cardOpen' : '') },
|
|
281
|
+
React.createElement('button', {
|
|
282
|
+
type: 'button',
|
|
283
|
+
className: 'YyYd_a_header',
|
|
284
|
+
'aria-expanded': open,
|
|
285
|
+
'aria-label': (open ? '折叠' : '展开') + ': 自动打开网页',
|
|
286
|
+
onClick: () => setOpen(!open)
|
|
287
|
+
},
|
|
288
|
+
React.createElement('span', { className: 'YyYd_a_headText' },
|
|
289
|
+
React.createElement('span', { className: 'YyYd_a_name' }, '自动打开网页'),
|
|
290
|
+
React.createElement('span', { className: 'YyYd_a_description' }, 'DSH 启动后自动打开独立应用窗口或网页;可手动指定浏览器位置')
|
|
291
|
+
),
|
|
292
|
+
dirty ? React.createElement('span', { className: 'YyYd_a_pending' }, '未保存') : null,
|
|
293
|
+
ChevronIcon !== null
|
|
294
|
+
? React.createElement(ChevronIcon, { className: chevronCls })
|
|
295
|
+
: React.createElement('span', { className: chevronCls }, '▾')
|
|
296
|
+
),
|
|
297
|
+
open ? React.createElement('div', { className: 'YyYd_a_body' },
|
|
298
|
+
!writable ? React.createElement('p', { className: 'YyYd_a_readOnly', role: 'status' }, '当前配置只读(settings 不可写)。') : null,
|
|
299
|
+
loading
|
|
300
|
+
? React.createElement('p', { className: 'At1oFq_hint' }, '配置加载中…')
|
|
301
|
+
: (loadError !== ''
|
|
302
|
+
? React.createElement('div', { className: 'At1oFq_field' },
|
|
303
|
+
React.createElement('p', { className: 'At1oFq_invalid' }, loadError),
|
|
304
|
+
React.createElement('div', { className: 'aow-row' },
|
|
305
|
+
React.createElement(ProxyButton, { variant: 'outline', size: 'sm', onClick: retryLoad }, '重试')
|
|
306
|
+
)
|
|
307
|
+
)
|
|
308
|
+
: React.createElement(React.Fragment, null,
|
|
309
|
+
// ---- 独立应用窗口 ----
|
|
310
|
+
React.createElement('div', { className: 'At1oFq_field' },
|
|
311
|
+
React.createElement('div', { className: 'At1oFq_head' },
|
|
312
|
+
React.createElement('span', { className: 'At1oFq_label' }, '独立应用窗口')
|
|
313
|
+
),
|
|
314
|
+
React.createElement(Checkbox, { checked: draft.appWindow, disabled: !writable, onChange: (v) => setDraft(applySetPath(draft, ['appWindow'], v)) },
|
|
315
|
+
'启动时自动打开独立应用窗口;关闭后不自动打开任何窗口'
|
|
316
|
+
)
|
|
317
|
+
),
|
|
318
|
+
// ---- 窗口类型 ----
|
|
319
|
+
React.createElement('div', { className: 'At1oFq_field' },
|
|
320
|
+
React.createElement('div', { className: 'At1oFq_head' },
|
|
321
|
+
React.createElement('span', { className: 'At1oFq_label' }, '窗口类型')
|
|
322
|
+
),
|
|
323
|
+
React.createElement(Radio, { checked: draft.windowKind === 'webview2', disabled: !writable, value: 'webview2', onChange: (v) => setDraft(applySetPath(draft, ['windowKind'], v)) },
|
|
324
|
+
'WebView2 宿主(独立进程,任务栏 DSH 图标,随 DSH 退出;需 Windows + WebView2 运行时)'
|
|
325
|
+
),
|
|
326
|
+
React.createElement(Radio, { checked: draft.windowKind === 'browser', disabled: !writable, value: 'browser', onChange: (v) => setDraft(applySetPath(draft, ['windowKind'], v)) },
|
|
327
|
+
'浏览器应用窗口(--app 专用实例,不与其他浏览器页面共用进程)'
|
|
328
|
+
)
|
|
329
|
+
),
|
|
330
|
+
// ---- 浏览器可执行文件(仅"浏览器应用窗口"模式使能) ----
|
|
331
|
+
React.createElement('div', { className: 'At1oFq_field' },
|
|
332
|
+
React.createElement('div', { className: 'At1oFq_head' },
|
|
333
|
+
React.createElement('label', { className: 'At1oFq_label', htmlFor: 'auto-open-web-browser-path' }, '浏览器可执行文件')
|
|
334
|
+
),
|
|
335
|
+
React.createElement('div', { className: 'aow-row' },
|
|
336
|
+
React.createElement(ProxyInput, {
|
|
337
|
+
id: 'auto-open-web-browser-path',
|
|
338
|
+
className: 'aow-grow',
|
|
339
|
+
value: draft.browserPath,
|
|
340
|
+
placeholder: 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
|
|
341
|
+
disabled: !writable || draft.windowKind !== 'browser',
|
|
342
|
+
onChange: (event) => {
|
|
343
|
+
const next = applySetPath(draft, ['browserPath'], event.target.value)
|
|
344
|
+
setDraft(next)
|
|
345
|
+
}
|
|
346
|
+
}),
|
|
347
|
+
React.createElement(ProxyButton, { variant: 'outline', size: 'sm', disabled: !writable || picking || draft.windowKind !== 'browser', onClick: browse }, picking ? '选择中…' : '浏览'),
|
|
348
|
+
React.createElement(ProxyButton, { variant: 'outline', size: 'sm', disabled: testing || draft.windowKind !== 'browser', onClick: testBrowser }, testing ? '测试中…' : '测试')
|
|
349
|
+
),
|
|
350
|
+
testResult !== null
|
|
351
|
+
? React.createElement('p', { className: testResult.ok ? 'aow-test-ok' : 'aow-test-fail', role: 'status' },
|
|
352
|
+
(testResult.ok ? '✓ ' : '✗ ') + testResult.text
|
|
353
|
+
)
|
|
354
|
+
: null,
|
|
355
|
+
React.createElement('p', { className: 'At1oFq_hint' }, '仅"浏览器应用窗口"模式可编辑;留空使用内置候选(Edge → Chrome);路径不存在会被跳过并告警。「测试」会真实拉起一个专用测试实例验证可用性(数秒后自动关闭)')
|
|
356
|
+
),
|
|
357
|
+
// ---- 窗口关闭时退出 DSH(实验性,默认关闭) ----
|
|
358
|
+
React.createElement('div', { className: 'At1oFq_field' },
|
|
359
|
+
React.createElement('div', { className: 'At1oFq_head' },
|
|
360
|
+
React.createElement('span', { className: 'At1oFq_label' }, '窗口关闭时退出 DSH'),
|
|
361
|
+
React.createElement('span', { className: 'At1oFq_badges' },
|
|
362
|
+
React.createElement('span', { className: 'At1oFq_badge' }, '实验性')
|
|
363
|
+
)
|
|
364
|
+
),
|
|
365
|
+
React.createElement(Checkbox, { checked: draft.exitOnWindowClose === true, disabled: !writable, onChange: (v) => setDraft(applySetPath(draft, ['exitOnWindowClose'], v)) },
|
|
366
|
+
'关闭自动打开的窗口时,DSH 随之退出(默认关闭)'
|
|
367
|
+
)
|
|
368
|
+
),
|
|
369
|
+
// ---- 卡片底部(与内置 PluginCard 同款按钮) ----
|
|
370
|
+
React.createElement('div', { className: 'YyYd_a_footer' },
|
|
371
|
+
failedText !== '' ? React.createElement('p', { className: 'YyYd_a_failed', role: 'status' }, failedText) : null,
|
|
372
|
+
React.createElement('button', { type: 'button', className: 'YyYd_a_discard', disabled: !dirty || busy, onClick: discard }, '放弃修改'),
|
|
373
|
+
React.createElement('button', { type: 'button', className: 'YyYd_a_save', disabled: blocked, onClick: save }, busy ? '保存中…' : '保存')
|
|
374
|
+
)
|
|
375
|
+
))
|
|
376
|
+
) : null
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function apply(ctx) {
|
|
381
|
+
const slots = ctx.get('slots')
|
|
382
|
+
if (slots === undefined) {
|
|
383
|
+
console.error('[auto-open-web] slots service unavailable')
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
slots.inject('settings.plugin.item', () =>
|
|
387
|
+
slots.register(
|
|
388
|
+
{ name: 'settings.plugin.item', id: 'auto-open-web', order: 40, label: '自动打开网页' },
|
|
389
|
+
() => React.createElement(AutoOpenSettingsPage, null)
|
|
390
|
+
)
|
|
391
|
+
)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
exports.apply = apply
|
|
395
|
+
exports.name = 'auto-open-web'
|
|
396
|
+
return module.exports
|
|
397
|
+
}
|
|
398
|
+
})
|