dsh-clean-desktop-shell 0.1.11 → 0.1.13
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/CONTRIBUTORS.md +25 -19
- package/README.en.md +422 -333
- package/README.md +363 -278
- package/electron/aumid.js +17 -17
- package/electron/crashGuard.js +60 -60
- package/electron/error.html +113 -113
- package/electron/main.js +203 -187
- package/electron/outage.js +60 -0
- package/electron/preload.js +216 -49
- package/electron/progress-preload.js +11 -11
- package/electron/progress.html +79 -79
- package/electron/progress.js +56 -56
- package/electron/service.js +559 -458
- package/electron/shortcut.js +122 -122
- package/electron/tray.js +232 -232
- package/electron/window.js +603 -264
- package/lib/client.js +41 -6
- package/lib/common.js +74 -74
- package/lib/icon.js +85 -51
- package/lib/index.js +90 -15
- package/lib/runtime.js +423 -423
- package/package.json +140 -127
- package/scripts/check-syntax.mjs +54 -54
- package/scripts/selftest-recovery.mjs +140 -0
- package/scripts/selftest-runtime.mjs +90 -90
- package/src/client/client.js +41 -6
- package/src/host/common.js +74 -74
- package/src/host/icon.js +85 -51
- package/src/host/index.js +90 -15
- package/src/host/runtime.js +423 -423
- package/version.txt +1 -1
package/electron/tray.js
CHANGED
|
@@ -1,232 +1,232 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* System tray: window show/hide, backend management, auto-launch, quit.
|
|
3
|
-
*
|
|
4
|
-
* All backend controls live here (tray only) — the main window stays a
|
|
5
|
-
* pure shell with no backend chrome.
|
|
6
|
-
*/
|
|
7
|
-
import { Tray, Menu, dialog, nativeImage } from 'electron'
|
|
8
|
-
import { join } from 'node:path'
|
|
9
|
-
import { fileURLToPath } from 'node:url'
|
|
10
|
-
import { loadConfig, saveConfig } from './config.js'
|
|
11
|
-
import {
|
|
12
|
-
getStatus,
|
|
13
|
-
start,
|
|
14
|
-
stop,
|
|
15
|
-
restart,
|
|
16
|
-
detect,
|
|
17
|
-
findDshInFolder,
|
|
18
|
-
detectInstallFolder,
|
|
19
|
-
onStatusChange,
|
|
20
|
-
} from './service.js'
|
|
21
|
-
import { showProgress, setProgress, closeProgress } from './progress.js'
|
|
22
|
-
import { checkForUpdatesAuto, openRepo } from './update.js'
|
|
23
|
-
import { shortcutSupported, createDesktopShortcut } from './shortcut.js'
|
|
24
|
-
|
|
25
|
-
const trayIconPath = join(
|
|
26
|
-
fileURLToPath(new URL('.', import.meta.url)),
|
|
27
|
-
'assets',
|
|
28
|
-
process.platform === 'win32' ? 'tray-16.png' : 'trayTemplate.png',
|
|
29
|
-
)
|
|
30
|
-
// macOS: the filename must end in "Template" (e.g. trayTemplate.png) and
|
|
31
|
-
// Electron will automatically pick trayTemplate@2x.png on Retina.
|
|
32
|
-
// The image itself must be a black silhouette + alpha channel so it
|
|
33
|
-
// inverts correctly in both light and dark menu bars.
|
|
34
|
-
|
|
35
|
-
let trayInstance = null
|
|
36
|
-
let handlers = null
|
|
37
|
-
|
|
38
|
-
export function createTray({ onShow, onReload, onQuit }) {
|
|
39
|
-
const icon = nativeImage.createFromPath(trayIconPath)
|
|
40
|
-
|
|
41
|
-
handlers = { onShow, onReload, onQuit }
|
|
42
|
-
|
|
43
|
-
trayInstance = new Tray(icon)
|
|
44
|
-
trayInstance.setToolTip('DSH Clean Desktop Shell')
|
|
45
|
-
trayInstance.on('click', onShow)
|
|
46
|
-
|
|
47
|
-
// Keep the menu in sync whenever the backend state machine changes
|
|
48
|
-
// (starting → running / error / stopped happens asynchronously).
|
|
49
|
-
onStatusChange(() => refreshTrayMenu())
|
|
50
|
-
|
|
51
|
-
refreshTrayMenu()
|
|
52
|
-
return trayInstance
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Rebuild the context menu (call after backend state changes). */
|
|
56
|
-
export function refreshTrayMenu() {
|
|
57
|
-
if (!trayInstance || !handlers) return
|
|
58
|
-
const { onShow, onReload, onQuit } = handlers
|
|
59
|
-
const st = getStatus()
|
|
60
|
-
const label = statusLabel(st)
|
|
61
|
-
|
|
62
|
-
const menu = Menu.buildFromTemplate([
|
|
63
|
-
{ label: '显示 / 打开窗口', click: onShow },
|
|
64
|
-
{
|
|
65
|
-
label: '刷新窗口',
|
|
66
|
-
click: onReload,
|
|
67
|
-
},
|
|
68
|
-
// Windows-only: desktop .lnk shortcut. Hidden on other platforms instead of
|
|
69
|
-
// disabled so the menu stays relevant to the OS it is running on.
|
|
70
|
-
{
|
|
71
|
-
label: '创建桌面快捷方式',
|
|
72
|
-
visible: shortcutSupported(),
|
|
73
|
-
click: async () => {
|
|
74
|
-
const ok = await createDesktopShortcut()
|
|
75
|
-
if (ok) {
|
|
76
|
-
dialog.showMessageBoxSync({
|
|
77
|
-
type: 'info',
|
|
78
|
-
title: '已创建',
|
|
79
|
-
message: '桌面快捷方式已创建。',
|
|
80
|
-
})
|
|
81
|
-
} else {
|
|
82
|
-
dialog.showMessageBoxSync({
|
|
83
|
-
type: 'warning',
|
|
84
|
-
title: '创建失败',
|
|
85
|
-
message: '无法创建桌面快捷方式,请稍后重试。',
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
{ type: 'separator' },
|
|
91
|
-
{ label: `后端:${label}`, enabled: false },
|
|
92
|
-
{
|
|
93
|
-
label: '启动后端',
|
|
94
|
-
enabled: st.status !== 'running' && st.status !== 'starting',
|
|
95
|
-
click: () => startBackendWithProgress(),
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
label: '重启后端',
|
|
99
|
-
enabled: st.status === 'running' || st.status === 'starting',
|
|
100
|
-
click: async () => {
|
|
101
|
-
showProgress({ title: '重启后端', message: '正在重启 dsh 后端…' })
|
|
102
|
-
try {
|
|
103
|
-
await restart({ backendPath: loadConfig().backendPath })
|
|
104
|
-
setProgress({ title: '重启后端', message: '后端已重启', state: 'ok' })
|
|
105
|
-
setTimeout(closeProgress, 1200)
|
|
106
|
-
} catch (err) {
|
|
107
|
-
closeProgress()
|
|
108
|
-
dialog.showErrorBox('后端重启失败', err.message)
|
|
109
|
-
}
|
|
110
|
-
refreshTrayMenu()
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
label: '关闭后端',
|
|
115
|
-
enabled: st.status === 'running' || st.status === 'starting',
|
|
116
|
-
click: async () => {
|
|
117
|
-
showProgress({ title: '关闭后端', message: '正在关闭 dsh 后端…' })
|
|
118
|
-
try {
|
|
119
|
-
await stop()
|
|
120
|
-
setProgress({ title: '关闭后端', message: '后端已关闭', state: 'ok' })
|
|
121
|
-
setTimeout(closeProgress, 1200)
|
|
122
|
-
} catch (err) {
|
|
123
|
-
closeProgress()
|
|
124
|
-
dialog.showErrorBox('后端关闭失败', err.message)
|
|
125
|
-
}
|
|
126
|
-
refreshTrayMenu()
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
{ type: 'separator' },
|
|
130
|
-
{
|
|
131
|
-
label: '自动探测后端',
|
|
132
|
-
click: async () => {
|
|
133
|
-
await detect()
|
|
134
|
-
refreshTrayMenu()
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
label: '设置后端安装文件夹…',
|
|
139
|
-
click: () => chooseBackendFolder(),
|
|
140
|
-
},
|
|
141
|
-
{ type: 'separator' },
|
|
142
|
-
{
|
|
143
|
-
label: '检查更新…',
|
|
144
|
-
click: async () => {
|
|
145
|
-
// checkForUpdatesAuto() handles all three modes internally:
|
|
146
|
-
// Windows packaged → electron-updater auto-download
|
|
147
|
-
// Plugin mode → npm registry check + one-click update
|
|
148
|
-
// macOS packaged → GitHub Releases manual link
|
|
149
|
-
await checkForUpdatesAuto()
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
label: '仓库主页',
|
|
154
|
-
click: () => openRepo(),
|
|
155
|
-
},
|
|
156
|
-
{ type: 'separator' },
|
|
157
|
-
{ label: '退出', click: onQuit },
|
|
158
|
-
])
|
|
159
|
-
trayInstance.setContextMenu(menu)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function statusLabel(st) {
|
|
163
|
-
switch (st.status) {
|
|
164
|
-
case 'running':
|
|
165
|
-
return `运行中 (PID ${st.pid ?? '外部'})`
|
|
166
|
-
case 'starting':
|
|
167
|
-
return '启动中…'
|
|
168
|
-
case 'error':
|
|
169
|
-
return `错误:${st.error || '未知'}`
|
|
170
|
-
default:
|
|
171
|
-
return '未运行'
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Start the backend with a progress window. Shared by the tray menu and
|
|
177
|
-
* the offline screen buttons. Returns true on success.
|
|
178
|
-
*/
|
|
179
|
-
export async function startBackendWithProgress() {
|
|
180
|
-
showProgress({ title: '启动后端', message: '正在启动 dsh 后端…' })
|
|
181
|
-
try {
|
|
182
|
-
await start({ backendPath: loadConfig().backendPath })
|
|
183
|
-
setProgress({ title: '启动后端', message: '后端已启动', state: 'ok' })
|
|
184
|
-
setTimeout(closeProgress, 1200)
|
|
185
|
-
return true
|
|
186
|
-
} catch (err) {
|
|
187
|
-
closeProgress()
|
|
188
|
-
dialog.showErrorBox('后端启动失败', err.message)
|
|
189
|
-
return false
|
|
190
|
-
} finally {
|
|
191
|
-
refreshTrayMenu()
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Pick the dsh install folder via a native dialog (auto-detect default).
|
|
197
|
-
* Shared by the tray menu and the offline screen. Saves the choice to
|
|
198
|
-
* config and validates that a dsh executable exists inside.
|
|
199
|
-
*/
|
|
200
|
-
export async function chooseBackendFolder() {
|
|
201
|
-
const config = loadConfig()
|
|
202
|
-
const detected = await detectInstallFolder()
|
|
203
|
-
const defaultFolder = detected || config.backendPath || undefined
|
|
204
|
-
const result = await dialog.showOpenDialog({
|
|
205
|
-
title: '选择dsh后端安装文件夹,默认安装用自动探测',
|
|
206
|
-
buttonLabel: '选择此文件夹',
|
|
207
|
-
message: '默认安装用自动探测。可手动指定 dsh 后端安装文件夹(包含 dsh 可执行文件)。',
|
|
208
|
-
properties: ['openDirectory'],
|
|
209
|
-
defaultPath: defaultFolder,
|
|
210
|
-
})
|
|
211
|
-
if (!result.canceled && result.filePaths[0]) {
|
|
212
|
-
const folder = result.filePaths[0]
|
|
213
|
-
saveConfig({ ...loadConfig(), backendPath: folder })
|
|
214
|
-
const found = await findDshInFolder(folder)
|
|
215
|
-
if (found) {
|
|
216
|
-
dialog.showMessageBox({
|
|
217
|
-
type: 'info',
|
|
218
|
-
title: '后端安装文件夹已设置',
|
|
219
|
-
message: `已找到 dsh:\n${found}`,
|
|
220
|
-
})
|
|
221
|
-
} else {
|
|
222
|
-
dialog.showMessageBox({
|
|
223
|
-
type: 'warning',
|
|
224
|
-
title: '未在此文件夹找到 dsh',
|
|
225
|
-
message:
|
|
226
|
-
'此文件夹内未找到 dsh 可执行文件。已保存该路径,但启动后端时可能失败——\n' +
|
|
227
|
-
'请确认选择的是包含 dsh(dsh.cmd / bin/dsh.cmd / node_modules/.bin/dsh.cmd)的文件夹。',
|
|
228
|
-
})
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
refreshTrayMenu()
|
|
232
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* System tray: window show/hide, backend management, auto-launch, quit.
|
|
3
|
+
*
|
|
4
|
+
* All backend controls live here (tray only) — the main window stays a
|
|
5
|
+
* pure shell with no backend chrome.
|
|
6
|
+
*/
|
|
7
|
+
import { Tray, Menu, dialog, nativeImage } from 'electron'
|
|
8
|
+
import { join } from 'node:path'
|
|
9
|
+
import { fileURLToPath } from 'node:url'
|
|
10
|
+
import { loadConfig, saveConfig } from './config.js'
|
|
11
|
+
import {
|
|
12
|
+
getStatus,
|
|
13
|
+
start,
|
|
14
|
+
stop,
|
|
15
|
+
restart,
|
|
16
|
+
detect,
|
|
17
|
+
findDshInFolder,
|
|
18
|
+
detectInstallFolder,
|
|
19
|
+
onStatusChange,
|
|
20
|
+
} from './service.js'
|
|
21
|
+
import { showProgress, setProgress, closeProgress } from './progress.js'
|
|
22
|
+
import { checkForUpdatesAuto, openRepo } from './update.js'
|
|
23
|
+
import { shortcutSupported, createDesktopShortcut } from './shortcut.js'
|
|
24
|
+
|
|
25
|
+
const trayIconPath = join(
|
|
26
|
+
fileURLToPath(new URL('.', import.meta.url)),
|
|
27
|
+
'assets',
|
|
28
|
+
process.platform === 'win32' ? 'tray-16.png' : 'trayTemplate.png',
|
|
29
|
+
)
|
|
30
|
+
// macOS: the filename must end in "Template" (e.g. trayTemplate.png) and
|
|
31
|
+
// Electron will automatically pick trayTemplate@2x.png on Retina.
|
|
32
|
+
// The image itself must be a black silhouette + alpha channel so it
|
|
33
|
+
// inverts correctly in both light and dark menu bars.
|
|
34
|
+
|
|
35
|
+
let trayInstance = null
|
|
36
|
+
let handlers = null
|
|
37
|
+
|
|
38
|
+
export function createTray({ onShow, onReload, onQuit }) {
|
|
39
|
+
const icon = nativeImage.createFromPath(trayIconPath)
|
|
40
|
+
|
|
41
|
+
handlers = { onShow, onReload, onQuit }
|
|
42
|
+
|
|
43
|
+
trayInstance = new Tray(icon)
|
|
44
|
+
trayInstance.setToolTip('DSH Clean Desktop Shell')
|
|
45
|
+
trayInstance.on('click', onShow)
|
|
46
|
+
|
|
47
|
+
// Keep the menu in sync whenever the backend state machine changes
|
|
48
|
+
// (starting → running / error / stopped happens asynchronously).
|
|
49
|
+
onStatusChange(() => refreshTrayMenu())
|
|
50
|
+
|
|
51
|
+
refreshTrayMenu()
|
|
52
|
+
return trayInstance
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Rebuild the context menu (call after backend state changes). */
|
|
56
|
+
export function refreshTrayMenu() {
|
|
57
|
+
if (!trayInstance || !handlers) return
|
|
58
|
+
const { onShow, onReload, onQuit } = handlers
|
|
59
|
+
const st = getStatus()
|
|
60
|
+
const label = statusLabel(st)
|
|
61
|
+
|
|
62
|
+
const menu = Menu.buildFromTemplate([
|
|
63
|
+
{ label: '显示 / 打开窗口', click: onShow },
|
|
64
|
+
{
|
|
65
|
+
label: '刷新窗口',
|
|
66
|
+
click: onReload,
|
|
67
|
+
},
|
|
68
|
+
// Windows-only: desktop .lnk shortcut. Hidden on other platforms instead of
|
|
69
|
+
// disabled so the menu stays relevant to the OS it is running on.
|
|
70
|
+
{
|
|
71
|
+
label: '创建桌面快捷方式',
|
|
72
|
+
visible: shortcutSupported(),
|
|
73
|
+
click: async () => {
|
|
74
|
+
const ok = await createDesktopShortcut()
|
|
75
|
+
if (ok) {
|
|
76
|
+
dialog.showMessageBoxSync({
|
|
77
|
+
type: 'info',
|
|
78
|
+
title: '已创建',
|
|
79
|
+
message: '桌面快捷方式已创建。',
|
|
80
|
+
})
|
|
81
|
+
} else {
|
|
82
|
+
dialog.showMessageBoxSync({
|
|
83
|
+
type: 'warning',
|
|
84
|
+
title: '创建失败',
|
|
85
|
+
message: '无法创建桌面快捷方式,请稍后重试。',
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{ type: 'separator' },
|
|
91
|
+
{ label: `后端:${label}`, enabled: false },
|
|
92
|
+
{
|
|
93
|
+
label: '启动后端',
|
|
94
|
+
enabled: st.status !== 'running' && st.status !== 'starting',
|
|
95
|
+
click: () => startBackendWithProgress(),
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
label: '重启后端',
|
|
99
|
+
enabled: st.status === 'running' || st.status === 'starting',
|
|
100
|
+
click: async () => {
|
|
101
|
+
showProgress({ title: '重启后端', message: '正在重启 dsh 后端…' })
|
|
102
|
+
try {
|
|
103
|
+
await restart({ backendPath: loadConfig().backendPath })
|
|
104
|
+
setProgress({ title: '重启后端', message: '后端已重启', state: 'ok' })
|
|
105
|
+
setTimeout(closeProgress, 1200)
|
|
106
|
+
} catch (err) {
|
|
107
|
+
closeProgress()
|
|
108
|
+
dialog.showErrorBox('后端重启失败', err.message)
|
|
109
|
+
}
|
|
110
|
+
refreshTrayMenu()
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
label: '关闭后端',
|
|
115
|
+
enabled: st.status === 'running' || st.status === 'starting',
|
|
116
|
+
click: async () => {
|
|
117
|
+
showProgress({ title: '关闭后端', message: '正在关闭 dsh 后端…' })
|
|
118
|
+
try {
|
|
119
|
+
await stop()
|
|
120
|
+
setProgress({ title: '关闭后端', message: '后端已关闭', state: 'ok' })
|
|
121
|
+
setTimeout(closeProgress, 1200)
|
|
122
|
+
} catch (err) {
|
|
123
|
+
closeProgress()
|
|
124
|
+
dialog.showErrorBox('后端关闭失败', err.message)
|
|
125
|
+
}
|
|
126
|
+
refreshTrayMenu()
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{ type: 'separator' },
|
|
130
|
+
{
|
|
131
|
+
label: '自动探测后端',
|
|
132
|
+
click: async () => {
|
|
133
|
+
await detect()
|
|
134
|
+
refreshTrayMenu()
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
label: '设置后端安装文件夹…',
|
|
139
|
+
click: () => chooseBackendFolder(),
|
|
140
|
+
},
|
|
141
|
+
{ type: 'separator' },
|
|
142
|
+
{
|
|
143
|
+
label: '检查更新…',
|
|
144
|
+
click: async () => {
|
|
145
|
+
// checkForUpdatesAuto() handles all three modes internally:
|
|
146
|
+
// Windows packaged → electron-updater auto-download
|
|
147
|
+
// Plugin mode → npm registry check + one-click update
|
|
148
|
+
// macOS packaged → GitHub Releases manual link
|
|
149
|
+
await checkForUpdatesAuto()
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
label: '仓库主页',
|
|
154
|
+
click: () => openRepo(),
|
|
155
|
+
},
|
|
156
|
+
{ type: 'separator' },
|
|
157
|
+
{ label: '退出', click: onQuit },
|
|
158
|
+
])
|
|
159
|
+
trayInstance.setContextMenu(menu)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function statusLabel(st) {
|
|
163
|
+
switch (st.status) {
|
|
164
|
+
case 'running':
|
|
165
|
+
return `运行中 (PID ${st.pid ?? '外部'})`
|
|
166
|
+
case 'starting':
|
|
167
|
+
return '启动中…'
|
|
168
|
+
case 'error':
|
|
169
|
+
return `错误:${st.error || '未知'}`
|
|
170
|
+
default:
|
|
171
|
+
return '未运行'
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Start the backend with a progress window. Shared by the tray menu and
|
|
177
|
+
* the offline screen buttons. Returns true on success.
|
|
178
|
+
*/
|
|
179
|
+
export async function startBackendWithProgress() {
|
|
180
|
+
showProgress({ title: '启动后端', message: '正在启动 dsh 后端…' })
|
|
181
|
+
try {
|
|
182
|
+
await start({ backendPath: loadConfig().backendPath })
|
|
183
|
+
setProgress({ title: '启动后端', message: '后端已启动', state: 'ok' })
|
|
184
|
+
setTimeout(closeProgress, 1200)
|
|
185
|
+
return true
|
|
186
|
+
} catch (err) {
|
|
187
|
+
closeProgress()
|
|
188
|
+
dialog.showErrorBox('后端启动失败', err.message)
|
|
189
|
+
return false
|
|
190
|
+
} finally {
|
|
191
|
+
refreshTrayMenu()
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Pick the dsh install folder via a native dialog (auto-detect default).
|
|
197
|
+
* Shared by the tray menu and the offline screen. Saves the choice to
|
|
198
|
+
* config and validates that a dsh executable exists inside.
|
|
199
|
+
*/
|
|
200
|
+
export async function chooseBackendFolder() {
|
|
201
|
+
const config = loadConfig()
|
|
202
|
+
const detected = await detectInstallFolder()
|
|
203
|
+
const defaultFolder = detected || config.backendPath || undefined
|
|
204
|
+
const result = await dialog.showOpenDialog({
|
|
205
|
+
title: '选择dsh后端安装文件夹,默认安装用自动探测',
|
|
206
|
+
buttonLabel: '选择此文件夹',
|
|
207
|
+
message: '默认安装用自动探测。可手动指定 dsh 后端安装文件夹(包含 dsh 可执行文件)。',
|
|
208
|
+
properties: ['openDirectory'],
|
|
209
|
+
defaultPath: defaultFolder,
|
|
210
|
+
})
|
|
211
|
+
if (!result.canceled && result.filePaths[0]) {
|
|
212
|
+
const folder = result.filePaths[0]
|
|
213
|
+
saveConfig({ ...loadConfig(), backendPath: folder })
|
|
214
|
+
const found = await findDshInFolder(folder)
|
|
215
|
+
if (found) {
|
|
216
|
+
dialog.showMessageBox({
|
|
217
|
+
type: 'info',
|
|
218
|
+
title: '后端安装文件夹已设置',
|
|
219
|
+
message: `已找到 dsh:\n${found}`,
|
|
220
|
+
})
|
|
221
|
+
} else {
|
|
222
|
+
dialog.showMessageBox({
|
|
223
|
+
type: 'warning',
|
|
224
|
+
title: '未在此文件夹找到 dsh',
|
|
225
|
+
message:
|
|
226
|
+
'此文件夹内未找到 dsh 可执行文件。已保存该路径,但启动后端时可能失败——\n' +
|
|
227
|
+
'请确认选择的是包含 dsh(dsh.cmd / bin/dsh.cmd / node_modules/.bin/dsh.cmd)的文件夹。',
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
refreshTrayMenu()
|
|
232
|
+
}
|