@electerm/electerm-react 2.8.16 → 2.10.6
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/common/constants.js +3 -3
- package/client/common/pre.js +1 -120
- package/client/components/bookmark-form/ai-bookmark-form.jsx +324 -0
- package/client/components/bookmark-form/bookmark-form.styl +1 -1
- package/client/components/bookmark-form/bookmark-schema.js +179 -0
- package/client/components/bookmark-form/common/ai-category-select.jsx +32 -0
- package/client/components/bookmark-form/common/category-select.jsx +2 -4
- package/client/components/bookmark-form/common/fields.jsx +0 -10
- package/client/components/bookmark-form/config/rdp.js +0 -1
- package/client/components/bookmark-form/config/session-config.js +3 -1
- package/client/components/bookmark-form/config/spice.js +44 -0
- package/client/components/bookmark-form/config/vnc.js +1 -2
- package/client/components/bookmark-form/fix-bookmark-default.js +134 -0
- package/client/components/bookmark-form/index.jsx +74 -13
- package/client/components/session/session.jsx +13 -3
- package/client/components/setting-panel/keywords-transport.jsx +0 -1
- package/client/components/shortcuts/shortcut-handler.js +11 -5
- package/client/components/sidebar/index.jsx +11 -1
- package/client/components/spice/spice-session.jsx +276 -0
- package/client/components/tabs/add-btn-menu.jsx +9 -2
- package/client/components/terminal/attach-addon-custom.js +20 -76
- package/client/components/terminal/terminal.jsx +34 -28
- package/client/components/terminal/transfer-client-base.js +232 -0
- package/client/components/terminal/trzsz-client.js +306 -0
- package/client/components/terminal/xterm-loader.js +109 -0
- package/client/components/terminal/zmodem-client.js +13 -166
- package/client/components/text-editor/simple-editor.jsx +1 -2
- package/client/entry/electerm.jsx +0 -2
- package/client/store/system-menu.js +10 -0
- package/package.json +1 -1
- package/client/common/trzsz.js +0 -46
- package/client/components/bookmark-form/common/wiki-alert.jsx +0 -9
- package/client/components/terminal/fs.js +0 -59
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trzsz client handler for web terminal
|
|
3
|
+
* Handles UI interactions and communicates with server-side trzsz
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { throttle } from 'lodash-es'
|
|
7
|
+
import { filesize } from 'filesize'
|
|
8
|
+
import { TransferClientBase } from './transfer-client-base.js'
|
|
9
|
+
import { transferTypeMap } from '../../common/constants.js'
|
|
10
|
+
|
|
11
|
+
const TRZSZ_SAVE_PATH_KEY = 'trzsz-save-path'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* TrzszClient class handles trzsz UI and client-side logic
|
|
15
|
+
*/
|
|
16
|
+
export class TrzszClient extends TransferClientBase {
|
|
17
|
+
constructor (terminal) {
|
|
18
|
+
super(terminal, TRZSZ_SAVE_PATH_KEY)
|
|
19
|
+
this.transferStartTime = 0
|
|
20
|
+
this.totalTransferred = 0
|
|
21
|
+
this.totalSpeed = 0
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get the action name for this protocol
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
getActionName () {
|
|
29
|
+
return 'trzsz-event'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get protocol display name
|
|
34
|
+
* @returns {string}
|
|
35
|
+
*/
|
|
36
|
+
getProtocolDisplayName () {
|
|
37
|
+
return 'TRZSZ'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Handle server trzsz events
|
|
42
|
+
* @param {Object} msg - Message from server
|
|
43
|
+
*/
|
|
44
|
+
handleServerEvent (msg) {
|
|
45
|
+
const { event } = msg
|
|
46
|
+
|
|
47
|
+
switch (event) {
|
|
48
|
+
case 'receive-start':
|
|
49
|
+
this.onReceiveStart()
|
|
50
|
+
break
|
|
51
|
+
case 'send-start':
|
|
52
|
+
this.onSendStart(msg.directory)
|
|
53
|
+
break
|
|
54
|
+
case 'file-count':
|
|
55
|
+
this.onFileCount(msg.count)
|
|
56
|
+
break
|
|
57
|
+
case 'file-start':
|
|
58
|
+
this.onFileStart(msg.name, msg.size)
|
|
59
|
+
break
|
|
60
|
+
case 'file-size':
|
|
61
|
+
this.onFileSize(msg.name, msg.size)
|
|
62
|
+
break
|
|
63
|
+
case 'progress':
|
|
64
|
+
this.onProgress(msg)
|
|
65
|
+
break
|
|
66
|
+
case 'file-complete':
|
|
67
|
+
this.onFileComplete(msg.name, msg.path)
|
|
68
|
+
break
|
|
69
|
+
case 'session-complete':
|
|
70
|
+
this.onSessionComplete(msg)
|
|
71
|
+
break
|
|
72
|
+
case 'session-error':
|
|
73
|
+
this.onError(msg.error)
|
|
74
|
+
break
|
|
75
|
+
case 'session-end':
|
|
76
|
+
this.onSessionEnd()
|
|
77
|
+
break
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Handle receive session start (download from remote)
|
|
83
|
+
*/
|
|
84
|
+
async onReceiveStart () {
|
|
85
|
+
this.isActive = true
|
|
86
|
+
this.writeBanner('RECEIVE')
|
|
87
|
+
|
|
88
|
+
// Ask user for save directory
|
|
89
|
+
const savePath = await this.openSaveFolderSelect()
|
|
90
|
+
if (savePath) {
|
|
91
|
+
this.savePath = savePath
|
|
92
|
+
this.sendToServer({
|
|
93
|
+
event: 'set-save-path',
|
|
94
|
+
path: savePath
|
|
95
|
+
})
|
|
96
|
+
} else {
|
|
97
|
+
// User cancelled, end session
|
|
98
|
+
this.sendToServer({
|
|
99
|
+
event: 'set-save-path',
|
|
100
|
+
path: null
|
|
101
|
+
})
|
|
102
|
+
this.onSessionEnd()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Handle send session start (upload to remote)
|
|
108
|
+
* @param {boolean} directory - Whether to select directories
|
|
109
|
+
*/
|
|
110
|
+
async onSendStart (directory = false) {
|
|
111
|
+
this.isActive = true
|
|
112
|
+
this.writeBanner('SEND')
|
|
113
|
+
|
|
114
|
+
// Ask user to select files or directories
|
|
115
|
+
const files = await this.openFileSelect({
|
|
116
|
+
directory,
|
|
117
|
+
title: directory ? 'Choose directories to send' : 'Choose some files to send',
|
|
118
|
+
message: directory ? 'Choose directories to send' : 'Choose some files to send'
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
if (files && files.length > 0) {
|
|
122
|
+
this.sendToServer({
|
|
123
|
+
event: 'send-files',
|
|
124
|
+
files
|
|
125
|
+
})
|
|
126
|
+
} else {
|
|
127
|
+
// User cancelled, end session
|
|
128
|
+
this.sendToServer({
|
|
129
|
+
event: 'send-files',
|
|
130
|
+
files: []
|
|
131
|
+
})
|
|
132
|
+
this.onSessionEnd()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Handle file count event
|
|
138
|
+
* @param {number} count - Number of files
|
|
139
|
+
*/
|
|
140
|
+
onFileCount (count) {
|
|
141
|
+
this.writeToTerminal(`\r\n\x1b[36mReceiving ${count} file${count > 1 ? 's' : ''}...\x1b[0m\r\n`)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Handle file start event
|
|
146
|
+
* @param {string} name - File name
|
|
147
|
+
* @param {number} size - File size
|
|
148
|
+
*/
|
|
149
|
+
onFileStart (name, size) {
|
|
150
|
+
this.currentTransfer = {
|
|
151
|
+
name,
|
|
152
|
+
size,
|
|
153
|
+
transferred: 0,
|
|
154
|
+
type: this.savePath ? transferTypeMap.download : transferTypeMap.upload,
|
|
155
|
+
path: null
|
|
156
|
+
}
|
|
157
|
+
this.transferStartTime = Date.now()
|
|
158
|
+
this.writeProgress()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Handle file size event
|
|
163
|
+
* @param {string} name - File name
|
|
164
|
+
* @param {number} size - File size
|
|
165
|
+
*/
|
|
166
|
+
onFileSize (name, size) {
|
|
167
|
+
if (this.currentTransfer) {
|
|
168
|
+
this.currentTransfer.size = size
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Handle progress update
|
|
174
|
+
* @param {Object} msg - Progress message
|
|
175
|
+
*/
|
|
176
|
+
onProgress (msg) {
|
|
177
|
+
if (!this.currentTransfer) {
|
|
178
|
+
this.currentTransfer = {
|
|
179
|
+
name: msg.name,
|
|
180
|
+
size: msg.size,
|
|
181
|
+
transferred: 0,
|
|
182
|
+
type: msg.type,
|
|
183
|
+
path: msg.path || null
|
|
184
|
+
}
|
|
185
|
+
this.transferStartTime = Date.now()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.currentTransfer.transferred = msg.transferred
|
|
189
|
+
this.currentTransfer.serverSpeed = msg.speed // Use server's speed calculation
|
|
190
|
+
this.currentTransfer.path = msg.path || this.currentTransfer.path
|
|
191
|
+
this.writeProgress()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Handle file complete
|
|
196
|
+
* @param {string} name - File name
|
|
197
|
+
* @param {string} path - File path
|
|
198
|
+
*/
|
|
199
|
+
onFileComplete (name, path) {
|
|
200
|
+
if (this.currentTransfer) {
|
|
201
|
+
this.currentTransfer.transferred = this.currentTransfer.size
|
|
202
|
+
this.currentTransfer.path = path
|
|
203
|
+
// Call directly to ensure 100% is displayed immediately
|
|
204
|
+
this._doWriteProgress(true)
|
|
205
|
+
// Add newline after completion
|
|
206
|
+
this.writeToTerminal('\r\n')
|
|
207
|
+
}
|
|
208
|
+
this.currentTransfer = null
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Write progress to terminal (throttled for updates, immediate for completion)
|
|
213
|
+
* @param {boolean} isComplete - Whether this is the final completion display
|
|
214
|
+
*/
|
|
215
|
+
writeProgress = throttle((isComplete = false) => {
|
|
216
|
+
this._doWriteProgress(isComplete)
|
|
217
|
+
}, 500)
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Internal function to actually write progress
|
|
221
|
+
* @param {boolean} isComplete - Whether this is the final completion display
|
|
222
|
+
*/
|
|
223
|
+
_doWriteProgress (isComplete = false) {
|
|
224
|
+
if (!this.currentTransfer || !this.terminal?.term) return
|
|
225
|
+
|
|
226
|
+
const { name, size, transferred, path, serverSpeed } = this.currentTransfer
|
|
227
|
+
const percent = size > 0 ? Math.floor(transferred * 100 / size) : 100
|
|
228
|
+
|
|
229
|
+
// Use server's speed if available, otherwise calculate locally
|
|
230
|
+
const speed = serverSpeed || 0
|
|
231
|
+
|
|
232
|
+
// Use full path if available, otherwise just name
|
|
233
|
+
const displayName = path || name
|
|
234
|
+
|
|
235
|
+
// filesize expects bytes and formats to human readable
|
|
236
|
+
const formatSize = (bytes) => filesize(bytes)
|
|
237
|
+
|
|
238
|
+
// Clear line and write progress
|
|
239
|
+
const str = `\r\x1b[2K\x1b[32m${displayName}\x1b[0m: ${percent}%, ${formatSize(transferred)}/${formatSize(size)}, ${formatSize(speed)}/s${isComplete ? ' \x1b[32m\x1b[1m[DONE]\x1b[0m' : ''}`
|
|
240
|
+
this.writeToTerminal(str + '\r')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Handle session complete
|
|
245
|
+
* @param {Object} msg - Completion message with files and savePath
|
|
246
|
+
*/
|
|
247
|
+
onSessionComplete (msg) {
|
|
248
|
+
// Add newline to preserve progress display
|
|
249
|
+
this.writeToTerminal('\r\n')
|
|
250
|
+
|
|
251
|
+
// Display completion message
|
|
252
|
+
if (msg.message) {
|
|
253
|
+
this.writeToTerminal(`\x1b[32m\x1b[1m${msg.message}\x1b[0m\r\n`)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Display saved files for download
|
|
257
|
+
if (msg.files && msg.files.length > 0) {
|
|
258
|
+
const fileCount = msg.files.length
|
|
259
|
+
const savePath = msg.savePath || ''
|
|
260
|
+
|
|
261
|
+
// Format file size
|
|
262
|
+
const formatSize = (bytes) => filesize(bytes)
|
|
263
|
+
|
|
264
|
+
// Display total transfer info
|
|
265
|
+
if (msg.totalBytes && msg.totalBytes > 0) {
|
|
266
|
+
const totalSize = formatSize(msg.totalBytes)
|
|
267
|
+
const elapsed = msg.totalElapsed ? msg.totalElapsed.toFixed(1) : '0.0'
|
|
268
|
+
const speed = msg.avgSpeed ? formatSize(msg.avgSpeed) : '0 B'
|
|
269
|
+
this.writeToTerminal(`\x1b[32m\x1b[1mTransferred ${fileCount} ${fileCount > 1 ? 'files' : 'file'} (${totalSize}) in ${elapsed}s, avg speed: ${speed}/s\x1b[0m\r\n`)
|
|
270
|
+
} else {
|
|
271
|
+
this.writeToTerminal(`\x1b[32m\x1b[1mSaved ${fileCount} ${fileCount > 1 ? 'files' : 'file'}\x1b[0m\r\n`)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (savePath) {
|
|
275
|
+
this.writeToTerminal(`\x1b[36mDestination: ${savePath}\x1b[0m\r\n`)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Display file list with sizes
|
|
279
|
+
if (msg.completedFiles && msg.completedFiles.length > 0) {
|
|
280
|
+
for (const file of msg.completedFiles) {
|
|
281
|
+
const sizeStr = file.size ? ` (${formatSize(file.size)})` : ''
|
|
282
|
+
this.writeToTerminal(` - ${file.name}${sizeStr}\r\n`)
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
// Fallback to just file names
|
|
286
|
+
for (const file of msg.files) {
|
|
287
|
+
const fileName = file.split('/').pop().split('\\').pop()
|
|
288
|
+
this.writeToTerminal(` - ${fileName}\r\n`)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
this.onSessionEnd()
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Handle error
|
|
298
|
+
* @param {string} message - Error message
|
|
299
|
+
*/
|
|
300
|
+
onError (message) {
|
|
301
|
+
this.writeToTerminal(`\r\n\x1b[31m\x1b[1mTRZSZ Error: ${message}\x1b[0m\r\n`)
|
|
302
|
+
this.onSessionEnd()
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export default TrzszClient
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
window.xtermAddons = window.xtermAddons || {}
|
|
2
|
+
|
|
3
|
+
let xtermCssLoaded = false
|
|
4
|
+
|
|
5
|
+
function loadXtermCss () {
|
|
6
|
+
if (xtermCssLoaded) return
|
|
7
|
+
xtermCssLoaded = true
|
|
8
|
+
import('@xterm/xterm/css/xterm.css')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function loadTerminal () {
|
|
12
|
+
if (window.xtermAddons.Terminal) return window.xtermAddons.Terminal
|
|
13
|
+
loadXtermCss()
|
|
14
|
+
const mod = await import('@xterm/xterm')
|
|
15
|
+
window.xtermAddons.Terminal = mod.Terminal
|
|
16
|
+
return window.xtermAddons.Terminal
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function loadFitAddon () {
|
|
20
|
+
if (window.xtermAddons.FitAddon) return window.xtermAddons.FitAddon
|
|
21
|
+
const mod = await import('@xterm/addon-fit')
|
|
22
|
+
window.xtermAddons.FitAddon = mod.FitAddon
|
|
23
|
+
return window.xtermAddons.FitAddon
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function loadAttachAddon () {
|
|
27
|
+
if (window.xtermAddons.AttachAddon) return window.xtermAddons.AttachAddon
|
|
28
|
+
const mod = await import('@xterm/addon-attach')
|
|
29
|
+
window.xtermAddons.AttachAddon = mod.AttachAddon
|
|
30
|
+
return window.xtermAddons.AttachAddon
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function loadWebLinksAddon () {
|
|
34
|
+
if (window.xtermAddons.WebLinksAddon) return window.xtermAddons.WebLinksAddon
|
|
35
|
+
const mod = await import('@xterm/addon-web-links')
|
|
36
|
+
window.xtermAddons.WebLinksAddon = mod.WebLinksAddon
|
|
37
|
+
return window.xtermAddons.WebLinksAddon
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function loadCanvasAddon () {
|
|
41
|
+
if (window.xtermAddons.CanvasAddon) return window.xtermAddons.CanvasAddon
|
|
42
|
+
const mod = await import('@xterm/addon-canvas')
|
|
43
|
+
window.xtermAddons.CanvasAddon = mod.CanvasAddon
|
|
44
|
+
return window.xtermAddons.CanvasAddon
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function loadWebglAddon () {
|
|
48
|
+
if (window.xtermAddons.WebglAddon) return window.xtermAddons.WebglAddon
|
|
49
|
+
const mod = await import('@xterm/addon-webgl')
|
|
50
|
+
window.xtermAddons.WebglAddon = mod.WebglAddon
|
|
51
|
+
return window.xtermAddons.WebglAddon
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function loadSearchAddon () {
|
|
55
|
+
if (window.xtermAddons.SearchAddon) return window.xtermAddons.SearchAddon
|
|
56
|
+
const mod = await import('@xterm/addon-search')
|
|
57
|
+
window.xtermAddons.SearchAddon = mod.SearchAddon
|
|
58
|
+
return window.xtermAddons.SearchAddon
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function loadLigaturesAddon () {
|
|
62
|
+
if (window.xtermAddons.LigaturesAddon) return window.xtermAddons.LigaturesAddon
|
|
63
|
+
const mod = await import('@xterm/addon-ligatures')
|
|
64
|
+
window.xtermAddons.LigaturesAddon = mod.LigaturesAddon
|
|
65
|
+
return window.xtermAddons.LigaturesAddon
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function loadUnicode11Addon () {
|
|
69
|
+
if (window.xtermAddons.Unicode11Addon) return window.xtermAddons.Unicode11Addon
|
|
70
|
+
const mod = await import('@xterm/addon-unicode11')
|
|
71
|
+
window.xtermAddons.Unicode11Addon = mod.Unicode11Addon
|
|
72
|
+
return window.xtermAddons.Unicode11Addon
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getTerminal () {
|
|
76
|
+
return window.xtermAddons.Terminal
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getFitAddon () {
|
|
80
|
+
return window.xtermAddons.FitAddon
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getAttachAddon () {
|
|
84
|
+
return window.xtermAddons.AttachAddon
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getWebLinksAddon () {
|
|
88
|
+
return window.xtermAddons.WebLinksAddon
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function getCanvasAddon () {
|
|
92
|
+
return window.xtermAddons.CanvasAddon
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function getWebglAddon () {
|
|
96
|
+
return window.xtermAddons.WebglAddon
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getSearchAddon () {
|
|
100
|
+
return window.xtermAddons.SearchAddon
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function getLigaturesAddon () {
|
|
104
|
+
return window.xtermAddons.LigaturesAddon
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getUnicode11Addon () {
|
|
108
|
+
return window.xtermAddons.Unicode11Addon
|
|
109
|
+
}
|
|
@@ -5,54 +5,34 @@
|
|
|
5
5
|
|
|
6
6
|
import { throttle } from 'lodash-es'
|
|
7
7
|
import { filesize } from 'filesize'
|
|
8
|
+
import { TransferClientBase } from './transfer-client-base.js'
|
|
8
9
|
import { transferTypeMap } from '../../common/constants.js'
|
|
9
|
-
import { getLocalFileInfo } from '../sftp/file-read.js'
|
|
10
10
|
|
|
11
11
|
const ZMODEM_SAVE_PATH_KEY = 'zmodem-save-path'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* ZmodemClient class handles zmodem UI and client-side logic
|
|
15
15
|
*/
|
|
16
|
-
export class ZmodemClient {
|
|
16
|
+
export class ZmodemClient extends TransferClientBase {
|
|
17
17
|
constructor (terminal) {
|
|
18
|
-
|
|
19
|
-
this.socket = null
|
|
20
|
-
this.isActive = false
|
|
21
|
-
this.currentTransfer = null
|
|
22
|
-
this.savePath = null
|
|
18
|
+
super(terminal, ZMODEM_SAVE_PATH_KEY)
|
|
23
19
|
this.transferStartTime = 0
|
|
24
20
|
}
|
|
25
21
|
|
|
26
22
|
/**
|
|
27
|
-
*
|
|
28
|
-
* @
|
|
23
|
+
* Get the action name for this protocol
|
|
24
|
+
* @returns {string}
|
|
29
25
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
this.setupMessageHandler()
|
|
26
|
+
getActionName () {
|
|
27
|
+
return 'zmodem-event'
|
|
33
28
|
}
|
|
34
29
|
|
|
35
30
|
/**
|
|
36
|
-
*
|
|
31
|
+
* Get protocol display name
|
|
32
|
+
* @returns {string}
|
|
37
33
|
*/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// Use addEventListener to avoid conflicting with attach-addon's onmessage
|
|
42
|
-
this.messageHandler = (event) => {
|
|
43
|
-
// Check if it's a JSON message (zmodem control message)
|
|
44
|
-
if (typeof event.data === 'string') {
|
|
45
|
-
try {
|
|
46
|
-
const msg = JSON.parse(event.data)
|
|
47
|
-
if (msg.action === 'zmodem-event') {
|
|
48
|
-
this.handleServerEvent(msg)
|
|
49
|
-
}
|
|
50
|
-
} catch (e) {
|
|
51
|
-
// Not JSON, ignore
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
this.socket.addEventListener('message', this.messageHandler)
|
|
34
|
+
getProtocolDisplayName () {
|
|
35
|
+
return 'ZMODEM'
|
|
56
36
|
}
|
|
57
37
|
|
|
58
38
|
/**
|
|
@@ -90,25 +70,12 @@ export class ZmodemClient {
|
|
|
90
70
|
}
|
|
91
71
|
}
|
|
92
72
|
|
|
93
|
-
/**
|
|
94
|
-
* Send message to server
|
|
95
|
-
* @param {Object} msg - Message to send
|
|
96
|
-
*/
|
|
97
|
-
sendToServer (msg) {
|
|
98
|
-
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
99
|
-
this.socket.send(JSON.stringify({
|
|
100
|
-
action: 'zmodem-event',
|
|
101
|
-
...msg
|
|
102
|
-
}))
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
73
|
/**
|
|
107
74
|
* Handle receive session start
|
|
108
75
|
*/
|
|
109
76
|
async onReceiveStart () {
|
|
110
77
|
this.isActive = true
|
|
111
|
-
this.writeBanner('RECEIVE')
|
|
78
|
+
this.writeBanner('RECEIVE', 'Recommend use trzsz instead: https://github.com/trzsz/trzsz')
|
|
112
79
|
|
|
113
80
|
// Ask user for save directory
|
|
114
81
|
const savePath = await this.openSaveFolderSelect()
|
|
@@ -132,7 +99,7 @@ export class ZmodemClient {
|
|
|
132
99
|
*/
|
|
133
100
|
async onSendStart () {
|
|
134
101
|
this.isActive = true
|
|
135
|
-
this.writeBanner('SEND')
|
|
102
|
+
this.writeBanner('SEND', 'Recommend use trzsz instead: https://github.com/trzsz/trzsz')
|
|
136
103
|
|
|
137
104
|
// Ask user to select files
|
|
138
105
|
const files = await this.openFileSelect()
|
|
@@ -229,31 +196,6 @@ export class ZmodemClient {
|
|
|
229
196
|
this.currentTransfer = null
|
|
230
197
|
}
|
|
231
198
|
|
|
232
|
-
/**
|
|
233
|
-
* Handle session end
|
|
234
|
-
*/
|
|
235
|
-
onSessionEnd () {
|
|
236
|
-
this.isActive = false
|
|
237
|
-
this.currentTransfer = null
|
|
238
|
-
this.savePath = null
|
|
239
|
-
if (this.terminal && this.terminal.term) {
|
|
240
|
-
this.terminal.term.focus()
|
|
241
|
-
this.terminal.term.write('\r\n')
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Write banner to terminal
|
|
247
|
-
* @param {string} type - 'RECEIVE' or 'SEND'
|
|
248
|
-
*/
|
|
249
|
-
writeBanner (type) {
|
|
250
|
-
const border = '='.repeat(50)
|
|
251
|
-
this.writeToTerminal(`\r\n${border}\r\n`)
|
|
252
|
-
this.writeToTerminal('\x1b[33m\x1b[1mRecommend use trzsz instead: https://github.com/trzsz/trzsz\x1b[0m\r\n')
|
|
253
|
-
this.writeToTerminal(`${border}\r\n\r\n`)
|
|
254
|
-
this.writeToTerminal(`\x1b[32m\x1b[1mZMODEM::${type}::START\x1b[0m\r\n`)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
199
|
/**
|
|
258
200
|
* Write progress to terminal (throttled for updates, immediate for completion)
|
|
259
201
|
* @param {boolean} isComplete - Whether this is the final completion display
|
|
@@ -285,101 +227,6 @@ export class ZmodemClient {
|
|
|
285
227
|
const str = `\r\x1b[2K\x1b[32m${displayName}\x1b[0m: ${percent}%, ${formatSize(transferred)}/${formatSize(size)}, ${formatSize(speed)}/s${isComplete ? ' \x1b[32m\x1b[1m[DONE]\x1b[0m' : ''}`
|
|
286
228
|
this.writeToTerminal(str + '\r')
|
|
287
229
|
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Write text to terminal
|
|
291
|
-
* @param {string} text - Text to write
|
|
292
|
-
*/
|
|
293
|
-
writeToTerminal (text) {
|
|
294
|
-
if (this.terminal && this.terminal.term) {
|
|
295
|
-
this.terminal.term.write(text)
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Open file select dialog
|
|
301
|
-
* @returns {Promise<Array>} - Selected files
|
|
302
|
-
*/
|
|
303
|
-
openFileSelect = async () => {
|
|
304
|
-
const properties = [
|
|
305
|
-
'openFile',
|
|
306
|
-
'multiSelections',
|
|
307
|
-
'showHiddenFiles',
|
|
308
|
-
'noResolveAliases',
|
|
309
|
-
'treatPackageAsDirectory',
|
|
310
|
-
'dontAddToRecent'
|
|
311
|
-
]
|
|
312
|
-
const files = await window.api.openDialog({
|
|
313
|
-
title: 'Choose some files to send',
|
|
314
|
-
message: 'Choose some files to send',
|
|
315
|
-
properties
|
|
316
|
-
}).catch(() => false)
|
|
317
|
-
if (!files || !files.length) {
|
|
318
|
-
return null
|
|
319
|
-
}
|
|
320
|
-
const r = []
|
|
321
|
-
for (const filePath of files) {
|
|
322
|
-
const stat = await getLocalFileInfo(filePath)
|
|
323
|
-
r.push({ ...stat, filePath, path: filePath })
|
|
324
|
-
}
|
|
325
|
-
return r
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Open save folder select dialog
|
|
330
|
-
* @returns {Promise<string>} - Selected folder path
|
|
331
|
-
*/
|
|
332
|
-
openSaveFolderSelect = async () => {
|
|
333
|
-
// Try to use last saved path
|
|
334
|
-
const lastPath = window.localStorage.getItem(ZMODEM_SAVE_PATH_KEY)
|
|
335
|
-
|
|
336
|
-
const savePaths = await window.api.openDialog({
|
|
337
|
-
title: 'Choose a folder to save file(s)',
|
|
338
|
-
message: 'Choose a folder to save file(s)',
|
|
339
|
-
defaultPath: lastPath || undefined,
|
|
340
|
-
properties: [
|
|
341
|
-
'openDirectory',
|
|
342
|
-
'showHiddenFiles',
|
|
343
|
-
'createDirectory',
|
|
344
|
-
'noResolveAliases',
|
|
345
|
-
'treatPackageAsDirectory',
|
|
346
|
-
'dontAddToRecent'
|
|
347
|
-
]
|
|
348
|
-
}).catch(() => false)
|
|
349
|
-
if (!savePaths || !savePaths.length) {
|
|
350
|
-
return null
|
|
351
|
-
}
|
|
352
|
-
// Save for next time
|
|
353
|
-
window.localStorage.setItem(ZMODEM_SAVE_PATH_KEY, savePaths[0])
|
|
354
|
-
return savePaths[0]
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Cancel ongoing transfer
|
|
359
|
-
*/
|
|
360
|
-
cancel () {
|
|
361
|
-
if (!this.isActive) return
|
|
362
|
-
this.writeToTerminal('\r\n\x1b[33m\x1b[1mZMODEM transfer cancelled by user\x1b[0m\r\n')
|
|
363
|
-
this.sendToServer({
|
|
364
|
-
event: 'cancel'
|
|
365
|
-
})
|
|
366
|
-
this.onSessionEnd()
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Clean up resources
|
|
371
|
-
*/
|
|
372
|
-
destroy () {
|
|
373
|
-
if (this.socket && this.messageHandler) {
|
|
374
|
-
this.socket.removeEventListener('message', this.messageHandler)
|
|
375
|
-
this.messageHandler = null
|
|
376
|
-
}
|
|
377
|
-
this.isActive = false
|
|
378
|
-
this.currentTransfer = null
|
|
379
|
-
this.savePath = null
|
|
380
|
-
this.socket = null
|
|
381
|
-
this.terminal = null
|
|
382
|
-
}
|
|
383
230
|
}
|
|
384
231
|
|
|
385
232
|
export default ZmodemClient
|
|
@@ -155,7 +155,7 @@ export default function SimpleEditor (props) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
return (
|
|
158
|
-
<div>
|
|
158
|
+
<div className='simple-editor'>
|
|
159
159
|
<Flex className='mg1b' justify='space-between'>
|
|
160
160
|
<Input.Search
|
|
161
161
|
value={searchKeyword}
|
|
@@ -182,6 +182,5 @@ export default function SimpleEditor (props) {
|
|
|
182
182
|
rows={20}
|
|
183
183
|
/>
|
|
184
184
|
</div>
|
|
185
|
-
|
|
186
185
|
)
|
|
187
186
|
}
|
|
@@ -61,6 +61,16 @@ export default Store => {
|
|
|
61
61
|
store.openSettingModal()
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
Store.prototype.onNewSshAI = function () {
|
|
65
|
+
const { store } = window
|
|
66
|
+
if (store.aiConfigMissing()) {
|
|
67
|
+
store.toggleAIConfig()
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
window.et.openBookmarkWithAIMode = true
|
|
71
|
+
store.onNewSsh()
|
|
72
|
+
}
|
|
73
|
+
|
|
64
74
|
Store.prototype.confirmExit = function (type) {
|
|
65
75
|
const { store } = window
|
|
66
76
|
let mod = null
|