dsh-desktop-windows 0.3.0 → 0.4.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.en.md +1 -1
- package/README.md +132 -104
- package/package.json +3 -1
- package/src/connect.html +88 -0
- package/src/main.js +1508 -914
- package/src/preload.js +186 -68
package/src/preload.js
CHANGED
|
@@ -1,68 +1,186 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
// preload
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
})
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// 统一 preload:按页面协议分派逻辑。
|
|
4
|
+
// http/https → DSH Web 应用页:注入底部工具条(截图 + 刷新)
|
|
5
|
+
// file:// → 连接页(connect.html):本机连接 / 远程地址输入 / 自动重试
|
|
6
|
+
// data: → 更新进度页等临时页:不做任何事
|
|
7
|
+
//
|
|
8
|
+
// 注意:BrowserWindow 只能绑定一个 preload,而连接页与应用页共用同一个窗口,
|
|
9
|
+
// 因此两套逻辑必须合流于此,靠 location.protocol 区分。
|
|
10
|
+
|
|
11
|
+
const { ipcRenderer } = require('electron')
|
|
12
|
+
|
|
13
|
+
const protocol = location.protocol
|
|
14
|
+
|
|
15
|
+
/* ------------------------------------------------------------------ *
|
|
16
|
+
* 应用页(http/https):底部工具条
|
|
17
|
+
* ------------------------------------------------------------------ */
|
|
18
|
+
|
|
19
|
+
if (protocol === 'http:' || protocol === 'https:') {
|
|
20
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
21
|
+
setTimeout(() => {
|
|
22
|
+
if (!document.body) return
|
|
23
|
+
|
|
24
|
+
// 底部工具条容器
|
|
25
|
+
const bar = document.createElement('div')
|
|
26
|
+
bar.style.cssText = `
|
|
27
|
+
position: fixed;
|
|
28
|
+
right: 12px;
|
|
29
|
+
bottom: 12px;
|
|
30
|
+
display: flex;
|
|
31
|
+
gap: 8px;
|
|
32
|
+
z-index: 2147483647;
|
|
33
|
+
`
|
|
34
|
+
|
|
35
|
+
const makeBtn = (text, title, onClick) => {
|
|
36
|
+
const b = document.createElement('button')
|
|
37
|
+
b.textContent = text
|
|
38
|
+
b.title = title
|
|
39
|
+
b.style.cssText = `
|
|
40
|
+
width: 34px;
|
|
41
|
+
height: 34px;
|
|
42
|
+
border-radius: 10px;
|
|
43
|
+
border: none;
|
|
44
|
+
background: rgba(30, 41, 59, 0.72);
|
|
45
|
+
color: #fff;
|
|
46
|
+
font-size: 17px;
|
|
47
|
+
line-height: 1;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
|
|
50
|
+
opacity: 0.45;
|
|
51
|
+
transition: opacity 0.2s;
|
|
52
|
+
`
|
|
53
|
+
b.onmouseenter = () => (b.style.opacity = '1')
|
|
54
|
+
b.onmouseleave = () => (b.style.opacity = '0.45')
|
|
55
|
+
b.onclick = onClick
|
|
56
|
+
return b
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 截图按钮:截屏 → 剪贴板 → 自动粘贴到聊天框
|
|
60
|
+
const shotBtn = makeBtn('📸', '截图并提问(截取当前屏幕,自动粘贴到聊天框)', () => {
|
|
61
|
+
shotBtn.style.opacity = '1'
|
|
62
|
+
shotBtn.textContent = '⏳'
|
|
63
|
+
ipcRenderer.invoke('dsh-desktop:capture').then((r) => {
|
|
64
|
+
shotBtn.textContent = '📸'
|
|
65
|
+
if (!r || !r.ok) {
|
|
66
|
+
ipcRenderer.send('dsh-desktop:toast', (r && r.error) || '截图失败')
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// 刷新按钮
|
|
72
|
+
const reloadBtn = makeBtn('⟳', '刷新页面(等价 F5)', () => {
|
|
73
|
+
ipcRenderer.send('dsh-desktop:reload')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
bar.appendChild(shotBtn)
|
|
77
|
+
bar.appendChild(reloadBtn)
|
|
78
|
+
document.body.appendChild(bar)
|
|
79
|
+
}, 1500)
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* ------------------------------------------------------------------ *
|
|
84
|
+
* 连接页(file://)
|
|
85
|
+
* ------------------------------------------------------------------ */
|
|
86
|
+
|
|
87
|
+
if (protocol === 'file:') {
|
|
88
|
+
window.addEventListener('DOMContentLoaded', async () => {
|
|
89
|
+
const $ = (id) => document.getElementById(id)
|
|
90
|
+
const addr = $('addr')
|
|
91
|
+
const status = $('status')
|
|
92
|
+
const hint = $('hint')
|
|
93
|
+
const btnGo = $('btn-go')
|
|
94
|
+
const btnLocal = $('btn-local')
|
|
95
|
+
const localLabel = btnLocal.textContent
|
|
96
|
+
const btnCancel = $('btn-cancel')
|
|
97
|
+
const autoWrap = $('auto-wrap')
|
|
98
|
+
const auto = $('auto')
|
|
99
|
+
|
|
100
|
+
let busy = false
|
|
101
|
+
let retryTimer = null
|
|
102
|
+
|
|
103
|
+
function setStatus(cls, text) {
|
|
104
|
+
status.className = cls
|
|
105
|
+
status.textContent = text
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let state
|
|
109
|
+
try { state = await ipcRenderer.invoke('connect:state') } catch { state = { mode: 'first', url: '', error: '' } }
|
|
110
|
+
|
|
111
|
+
addr.value = state.url || ''
|
|
112
|
+
if (state.error) setStatus('err', state.error)
|
|
113
|
+
if (state.mode === 'first') hint.textContent = '首次使用:点「本机连接」自动启动本机 dsh;或输入远程地址(局域网 IP 自动用 http,域名自动用 https)'
|
|
114
|
+
if (state.mode === 'offline') hint.textContent = '与服务器的连接中断了。服务器恢复后可自动重连,也可以改连其他地址(如本机)'
|
|
115
|
+
if (state.mode === 'switch') hint.textContent = '当前已连接。换一个地址并连接成功后即完成切换;点「本机连接」回到本机模式'
|
|
116
|
+
if (state.mode === 'offline') autoWrap.style.display = 'flex'
|
|
117
|
+
else autoWrap.style.display = 'none'
|
|
118
|
+
if (state.mode !== 'switch') btnCancel.style.display = 'none'
|
|
119
|
+
else btnCancel.addEventListener('click', () => ipcRenderer.send('connect:cancel'))
|
|
120
|
+
|
|
121
|
+
function stopAutoRetry() {
|
|
122
|
+
if (retryTimer) { clearInterval(retryTimer); retryTimer = null }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 远程地址:探测 → 连接
|
|
126
|
+
async function tryConnect(silent) {
|
|
127
|
+
if (busy) return false
|
|
128
|
+
const val = addr.value.trim()
|
|
129
|
+
if (!val) {
|
|
130
|
+
if (!silent) setStatus('err', '请输入服务器地址')
|
|
131
|
+
return false
|
|
132
|
+
}
|
|
133
|
+
busy = true
|
|
134
|
+
btnGo.disabled = true
|
|
135
|
+
btnGo.textContent = silent ? btnGo.textContent : '连接中…'
|
|
136
|
+
if (!silent) setStatus('muted', `正在探测 ${val} …`)
|
|
137
|
+
let r
|
|
138
|
+
try { r = await ipcRenderer.invoke('connect:attempt', val) } catch (e) { r = { ok: false, error: String(e) } }
|
|
139
|
+
busy = false
|
|
140
|
+
btnGo.disabled = false
|
|
141
|
+
btnGo.textContent = '连 接'
|
|
142
|
+
if (!r || !r.ok) {
|
|
143
|
+
setStatus('err', (r && r.error) || '连接失败')
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
stopAutoRetry()
|
|
147
|
+
setStatus('ok', `已连接 ${r.url},正在进入…`)
|
|
148
|
+
return true
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 本机连接:环境引导(如需)→ 探测/启动本机 dsh → 进入应用
|
|
152
|
+
async function connectLocal() {
|
|
153
|
+
if (busy) return
|
|
154
|
+
busy = true
|
|
155
|
+
btnLocal.disabled = true
|
|
156
|
+
btnGo.disabled = true
|
|
157
|
+
btnLocal.textContent = '连接中…(首次可能需要安装环境)'
|
|
158
|
+
setStatus('muted', '正在连接本机:检查环境 → 启动 dsh 服务…')
|
|
159
|
+
let r
|
|
160
|
+
try { r = await ipcRenderer.invoke('connect:local') } catch (e) { r = { ok: false, error: String(e) } }
|
|
161
|
+
busy = false
|
|
162
|
+
btnLocal.disabled = false
|
|
163
|
+
btnGo.disabled = false
|
|
164
|
+
btnLocal.textContent = localLabel
|
|
165
|
+
if (!r || !r.ok) {
|
|
166
|
+
setStatus('err', (r && r.error) || '本机连接失败')
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
stopAutoRetry()
|
|
170
|
+
setStatus('ok', `已连接 ${r.url},正在进入…`)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
btnGo.addEventListener('click', () => tryConnect(false))
|
|
174
|
+
addr.addEventListener('keydown', (e) => { if (e.key === 'Enter') tryConnect(false) })
|
|
175
|
+
btnLocal.addEventListener('click', connectLocal)
|
|
176
|
+
|
|
177
|
+
auto.addEventListener('change', () => { auto.checked ? startAutoRetry() : stopAutoRetry() })
|
|
178
|
+
function startAutoRetry() {
|
|
179
|
+
stopAutoRetry()
|
|
180
|
+
retryTimer = setInterval(async () => {
|
|
181
|
+
if (!busy && auto.checked) await tryConnect(true)
|
|
182
|
+
}, 8000)
|
|
183
|
+
}
|
|
184
|
+
if (state.mode === 'offline') startAutoRetry()
|
|
185
|
+
})
|
|
186
|
+
}
|