@electerm/electerm-react 2.8.8 → 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.
Files changed (33) hide show
  1. package/client/common/constants.js +3 -3
  2. package/client/common/pre.js +1 -120
  3. package/client/components/bookmark-form/ai-bookmark-form.jsx +324 -0
  4. package/client/components/bookmark-form/bookmark-form.styl +1 -1
  5. package/client/components/bookmark-form/bookmark-schema.js +179 -0
  6. package/client/components/bookmark-form/common/ai-category-select.jsx +32 -0
  7. package/client/components/bookmark-form/common/category-select.jsx +2 -4
  8. package/client/components/bookmark-form/common/fields.jsx +0 -10
  9. package/client/components/bookmark-form/config/rdp.js +0 -1
  10. package/client/components/bookmark-form/config/session-config.js +3 -1
  11. package/client/components/bookmark-form/config/spice.js +44 -0
  12. package/client/components/bookmark-form/config/vnc.js +1 -2
  13. package/client/components/bookmark-form/fix-bookmark-default.js +134 -0
  14. package/client/components/bookmark-form/index.jsx +74 -13
  15. package/client/components/session/session.jsx +13 -3
  16. package/client/components/setting-panel/keywords-transport.jsx +0 -1
  17. package/client/components/shortcuts/shortcut-handler.js +11 -5
  18. package/client/components/sidebar/index.jsx +11 -1
  19. package/client/components/spice/spice-session.jsx +276 -0
  20. package/client/components/tabs/add-btn-menu.jsx +9 -2
  21. package/client/components/terminal/attach-addon-custom.js +20 -76
  22. package/client/components/terminal/terminal.jsx +34 -28
  23. package/client/components/terminal/transfer-client-base.js +232 -0
  24. package/client/components/terminal/trzsz-client.js +306 -0
  25. package/client/components/terminal/xterm-loader.js +109 -0
  26. package/client/components/terminal/zmodem-client.js +13 -166
  27. package/client/components/text-editor/simple-editor.jsx +1 -2
  28. package/client/entry/electerm.jsx +0 -2
  29. package/client/store/system-menu.js +10 -0
  30. package/package.json +1 -1
  31. package/client/common/trzsz.js +0 -46
  32. package/client/components/bookmark-form/common/wiki-alert.jsx +0 -9
  33. 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
- this.terminal = terminal
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
- * Initialize zmodem client with socket
28
- * @param {WebSocket} socket - WebSocket connection
23
+ * Get the action name for this protocol
24
+ * @returns {string}
29
25
  */
30
- init (socket) {
31
- this.socket = socket
32
- this.setupMessageHandler()
26
+ getActionName () {
27
+ return 'zmodem-event'
33
28
  }
34
29
 
35
30
  /**
36
- * Setup message handler for zmodem events from server
31
+ * Get protocol display name
32
+ * @returns {string}
37
33
  */
38
- setupMessageHandler () {
39
- if (!this.socket) return
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
  }
@@ -1,7 +1,5 @@
1
1
  import { createRoot } from 'react-dom/client'
2
2
  import 'antd/dist/reset.css'
3
- import '@xterm/xterm/css/xterm.css'
4
- import '../common/trzsz.js'
5
3
  import '@fontsource/maple-mono/index.css'
6
4
  import Main from '../components/main/index.jsx'
7
5
 
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "2.8.8",
3
+ "version": "2.10.6",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",