@electerm/electerm-react 2.8.16 → 2.10.26

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 (48) hide show
  1. package/client/common/constants.js +3 -3
  2. package/client/common/pre.js +1 -120
  3. package/client/components/ai/ai-config.jsx +26 -3
  4. package/client/components/ai/ai-history-item.jsx +46 -0
  5. package/client/components/ai/ai-history.jsx +104 -0
  6. package/client/components/bookmark-form/ai-bookmark-form.jsx +338 -0
  7. package/client/components/bookmark-form/bookmark-form.styl +1 -1
  8. package/client/components/bookmark-form/bookmark-schema.js +192 -0
  9. package/client/components/bookmark-form/common/ai-category-select.jsx +32 -0
  10. package/client/components/bookmark-form/common/category-select.jsx +2 -4
  11. package/client/components/bookmark-form/common/fields.jsx +0 -10
  12. package/client/components/bookmark-form/config/ftp.js +2 -0
  13. package/client/components/bookmark-form/config/rdp.js +0 -1
  14. package/client/components/bookmark-form/config/session-config.js +3 -1
  15. package/client/components/bookmark-form/config/spice.js +43 -0
  16. package/client/components/bookmark-form/config/vnc.js +1 -2
  17. package/client/components/bookmark-form/fix-bookmark-default.js +134 -0
  18. package/client/components/bookmark-form/index.jsx +74 -14
  19. package/client/components/common/notification.jsx +34 -2
  20. package/client/components/common/notification.styl +18 -2
  21. package/client/components/main/wrapper.styl +0 -7
  22. package/client/components/rdp/rdp-session.jsx +44 -11
  23. package/client/components/session/session.jsx +13 -3
  24. package/client/components/setting-panel/deep-link-control.jsx +3 -2
  25. package/client/components/setting-panel/keywords-transport.jsx +0 -1
  26. package/client/components/shortcuts/shortcut-editor.jsx +12 -36
  27. package/client/components/shortcuts/shortcut-handler.js +11 -5
  28. package/client/components/sidebar/index.jsx +11 -1
  29. package/client/components/spice/spice-session.jsx +296 -0
  30. package/client/components/spice/spice.styl +4 -0
  31. package/client/components/tabs/add-btn-menu.jsx +9 -2
  32. package/client/components/terminal/attach-addon-custom.js +20 -76
  33. package/client/components/terminal/terminal.jsx +34 -28
  34. package/client/components/terminal/transfer-client-base.js +232 -0
  35. package/client/components/terminal/trzsz-client.js +306 -0
  36. package/client/components/terminal/xterm-loader.js +109 -0
  37. package/client/components/terminal/zmodem-client.js +13 -166
  38. package/client/components/text-editor/simple-editor.jsx +1 -2
  39. package/client/components/vnc/vnc-session.jsx +1 -1
  40. package/client/entry/electerm.jsx +0 -2
  41. package/client/store/load-data.js +1 -1
  42. package/client/store/store.js +1 -1
  43. package/client/store/system-menu.js +10 -0
  44. package/package.json +1 -1
  45. package/client/common/trzsz.js +0 -46
  46. package/client/components/bookmark-form/common/wiki-alert.jsx +0 -9
  47. package/client/components/common/notification-with-details.jsx +0 -34
  48. package/client/components/terminal/fs.js +0 -59
@@ -26,18 +26,11 @@ import {
26
26
  } from '../../common/constants.js'
27
27
  import deepCopy from 'json-deep-copy'
28
28
  import { readClipboardAsync, readClipboard, copy } from '../../common/clipboard.js'
29
- import { FitAddon } from '@xterm/addon-fit'
30
29
  import AttachAddon from './attach-addon-custom.js'
31
- import { SearchAddon } from '@xterm/addon-search'
32
- import { WebLinksAddon } from '@xterm/addon-web-links'
33
- import { CanvasAddon } from '@xterm/addon-canvas'
34
- import { WebglAddon } from '@xterm/addon-webgl'
35
- import { LigaturesAddon } from '@xterm/addon-ligatures'
36
30
  import getProxy from '../../common/get-proxy.js'
37
31
  import { ZmodemClient } from './zmodem-client.js'
38
- import { Unicode11Addon } from '@xterm/addon-unicode11'
32
+ import { TrzszClient } from './trzsz-client.js'
39
33
  import keyControlPressed from '../../common/key-control-pressed.js'
40
- import { Terminal } from '@xterm/xterm'
41
34
  import NormalBuffer from './normal-buffer.jsx'
42
35
  import { createTerm, resizeTerm } from './terminal-apis.js'
43
36
  import { shortcutExtend, shortcutDescExtend } from '../shortcuts/shortcut-handler.js'
@@ -56,6 +49,16 @@ import ExternalLink from '../common/external-link.jsx'
56
49
  import createDefaultLogPath from '../../common/default-log-path.js'
57
50
  import SearchResultBar from './terminal-search-bar'
58
51
  import RemoteFloatControl from '../common/remote-float-control'
52
+ import {
53
+ loadTerminal,
54
+ loadFitAddon,
55
+ loadWebLinksAddon,
56
+ loadCanvasAddon,
57
+ loadWebglAddon,
58
+ loadSearchAddon,
59
+ loadLigaturesAddon,
60
+ loadUnicode11Addon
61
+ } from './xterm-loader.js'
59
62
 
60
63
  const e = window.translate
61
64
 
@@ -114,7 +117,7 @@ class Term extends Component {
114
117
  ) {
115
118
  this.onResize()
116
119
  }
117
- if (shouldChange) {
120
+ if (shouldChange && this.term) {
118
121
  this.term.focus()
119
122
  }
120
123
  this.checkConfigChange(
@@ -125,7 +128,7 @@ class Term extends Component {
125
128
  this.props.themeConfig,
126
129
  prevProps.themeConfig
127
130
  )
128
- if (themeChanged) {
131
+ if (themeChanged && this.term) {
129
132
  this.term.options.theme = {
130
133
  ...deepCopy(this.props.themeConfig),
131
134
  background: 'rgba(0,0,0,0)'
@@ -138,7 +141,9 @@ class Term extends Component {
138
141
  if (window.store.activeTerminalId === this.props.tab.id) {
139
142
  window.store.activeTerminalId = ''
140
143
  }
141
- this.term.parent = null
144
+ if (this.term) {
145
+ this.term.parent = null
146
+ }
142
147
  Object.keys(this.timers).forEach(k => {
143
148
  clearTimeout(this.timers[k])
144
149
  this.timers[k] = null
@@ -155,6 +160,7 @@ class Term extends Component {
155
160
  this.attachAddon = null
156
161
  this.fitAddon = null
157
162
  this.zmodemClient = null
163
+ this.trzszClient = null
158
164
  this.searchAddon = null
159
165
  this.fitAddon = null
160
166
  this.cmdAddon = null
@@ -180,7 +186,7 @@ class Term extends Component {
180
186
  }
181
187
  ]
182
188
 
183
- initAttachAddon = () => {
189
+ initAttachAddon = async () => {
184
190
  this.attachAddon = new AttachAddon(
185
191
  this.term,
186
192
  this.socket,
@@ -189,7 +195,7 @@ class Term extends Component {
189
195
  this.attachAddon.decoder = new TextDecoder(
190
196
  this.encode || this.props.tab.encode || 'utf-8'
191
197
  )
192
- this.term.loadAddon(this.attachAddon)
198
+ await this.attachAddon.activate(this.term)
193
199
  }
194
200
 
195
201
  getValue = (props, type, name) => {
@@ -751,25 +757,28 @@ class Term extends Component {
751
757
  }
752
758
  }
753
759
 
754
- loadRenderer = (term, config) => {
760
+ loadRenderer = async (term, config) => {
755
761
  if (config.rendererType === rendererTypes.canvas) {
762
+ const CanvasAddon = await loadCanvasAddon()
756
763
  term.loadAddon(new CanvasAddon())
757
764
  } else if (config.rendererType === rendererTypes.webGL) {
758
765
  try {
766
+ const WebglAddon = await loadWebglAddon()
759
767
  term.loadAddon(new WebglAddon())
760
768
  } catch (e) {
761
769
  console.error('render with webgl failed, fallback to canvas')
762
770
  console.error(e)
771
+ const CanvasAddon = await loadCanvasAddon()
763
772
  term.loadAddon(new CanvasAddon())
764
773
  }
765
774
  }
766
775
  }
767
776
 
768
777
  initTerminal = async () => {
769
- // let {password, privateKey, host} = this.props.tab
770
778
  const { themeConfig, tab = {}, config = {} } = this.props
771
779
  const tc = deepCopy(themeConfig)
772
780
  tc.background = 'rgba(0,0,0,0)'
781
+ const Terminal = await loadTerminal()
773
782
  const term = new Terminal({
774
783
  allowProposedApi: true,
775
784
  scrollback: config.scrollback,
@@ -777,7 +786,6 @@ class Term extends Component {
777
786
  fontFamily: tab.fontFamily || config.fontFamily,
778
787
  theme: tc,
779
788
  allowTransparency: true,
780
- // lineHeight: 1.2,
781
789
  wordSeparator: config.terminalWordSeparator,
782
790
  cursorStyle: config.cursorStyle,
783
791
  cursorBlink: config.cursorBlink,
@@ -785,36 +793,31 @@ class Term extends Component {
785
793
  screenReaderMode: config.screenReaderMode
786
794
  })
787
795
 
788
- // term.onLineFeed(this.onLineFeed)
789
- // term.onTitleChange(this.onTitleChange)
790
796
  term.parent = this
791
797
  term.onSelectionChange(this.onSelection)
792
798
  term.open(this.domRef.current, true)
793
- this.loadRenderer(term, config)
794
- // term.textarea.addEventListener('click', this.setActive)
795
- // term.onKey(this.onKey)
796
- // term.textarea.addEventListener('blur', this.onBlur)
799
+ await this.loadRenderer(term, config)
797
800
 
798
- // term.on('keydown', this.handleEvent)
801
+ const FitAddon = await loadFitAddon()
799
802
  this.fitAddon = new FitAddon()
800
803
  this.cmdAddon = new CommandTrackerAddon()
801
- // Register callback for shell integration command tracking
802
804
  this.cmdAddon.onCommandExecuted((cmd) => {
803
805
  if (cmd && cmd.trim()) {
804
806
  window.store.addCmdHistory(cmd.trim())
805
807
  }
806
808
  })
807
- // Register callback for shell integration CWD tracking
808
809
  this.cmdAddon.onCwdChanged((cwd) => {
809
810
  this.setCwd(cwd)
810
811
  })
812
+ const SearchAddon = await loadSearchAddon()
811
813
  this.searchAddon = new SearchAddon()
814
+ const LigaturesAddon = await loadLigaturesAddon()
812
815
  const ligtureAddon = new LigaturesAddon()
813
816
  this.searchAddon.onDidChangeResults(this.onSearchResultsChange)
817
+ const Unicode11Addon = await loadUnicode11Addon()
814
818
  const unicode11Addon = new Unicode11Addon()
815
819
  term.loadAddon(unicode11Addon)
816
820
  term.loadAddon(ligtureAddon)
817
- // activate the new version
818
821
  term.unicode.activeVersion = '11'
819
822
  term.loadAddon(this.fitAddon)
820
823
  term.loadAddon(this.searchAddon)
@@ -1122,8 +1125,8 @@ class Term extends Component {
1122
1125
  this.socket = socket
1123
1126
  this.initSocketEvents()
1124
1127
  this.term = term
1125
- socket.onopen = () => {
1126
- this.initAttachAddon()
1128
+ socket.onopen = async () => {
1129
+ await this.initAttachAddon()
1127
1130
  this.runInitScript()
1128
1131
  term._initialized = true
1129
1132
  }
@@ -1132,10 +1135,13 @@ class Term extends Component {
1132
1135
  if (pick(term, 'buffer._onBufferChange._listeners')) {
1133
1136
  term.buffer._onBufferChange._listeners.push(this.onBufferChange)
1134
1137
  }
1138
+ const WebLinksAddon = await loadWebLinksAddon()
1135
1139
  term.loadAddon(new WebLinksAddon(this.webLinkHandler))
1136
1140
  term.focus()
1137
1141
  this.zmodemClient = new ZmodemClient(this)
1138
1142
  this.zmodemClient.init(socket)
1143
+ this.trzszClient = new TrzszClient(this)
1144
+ this.trzszClient.init(socket)
1139
1145
  this.fitAddon.fit()
1140
1146
  term.displayRaw = displayRaw
1141
1147
  term.loadAddon(
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Base class for file transfer clients (zmodem, trzsz, etc.)
3
+ * Provides common functionality for UI interactions and server communication
4
+ */
5
+
6
+ // import { transferTypeMap } from '../../common/constants.js'
7
+ import { getLocalFileInfo } from '../sftp/file-read.js'
8
+
9
+ /**
10
+ * TransferClientBase class - abstract base for file transfer protocols
11
+ */
12
+ export class TransferClientBase {
13
+ constructor (terminal, storageKey) {
14
+ this.terminal = terminal
15
+ this.storageKey = storageKey
16
+ this.socket = null
17
+ this.isActive = false
18
+ this.currentTransfer = null
19
+ this.savePath = null
20
+ this.messageHandler = null
21
+ }
22
+
23
+ /**
24
+ * Initialize client with socket
25
+ * @param {WebSocket} socket - WebSocket connection
26
+ */
27
+ init (socket) {
28
+ this.socket = socket
29
+ this.setupMessageHandler()
30
+ }
31
+
32
+ /**
33
+ * Setup message handler for events from server
34
+ * Should be overridden by subclass
35
+ */
36
+ setupMessageHandler () {
37
+ if (!this.socket) return
38
+
39
+ this.messageHandler = (event) => {
40
+ if (typeof event.data === 'string') {
41
+ try {
42
+ const msg = JSON.parse(event.data)
43
+ if (msg.action === this.getActionName()) {
44
+ this.handleServerEvent(msg)
45
+ }
46
+ } catch (e) {
47
+ // Not JSON, ignore
48
+ }
49
+ }
50
+ }
51
+ this.socket.addEventListener('message', this.messageHandler)
52
+ }
53
+
54
+ /**
55
+ * Get the action name for this protocol
56
+ * Should be overridden by subclass
57
+ * @returns {string}
58
+ */
59
+ getActionName () {
60
+ throw new Error('getActionName must be implemented by subclass')
61
+ }
62
+
63
+ /**
64
+ * Handle server events
65
+ * Should be overridden by subclass
66
+ * @param {Object} msg - Message from server
67
+ */
68
+ handleServerEvent (msg) {
69
+ throw new Error('handleServerEvent must be implemented by subclass')
70
+ }
71
+
72
+ /**
73
+ * Send message to server
74
+ * @param {Object} msg - Message to send
75
+ */
76
+ sendToServer (msg) {
77
+ if (this.socket && this.socket.readyState === WebSocket.OPEN) {
78
+ this.socket.send(JSON.stringify({
79
+ action: this.getActionName(),
80
+ ...msg
81
+ }))
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Write text to terminal
87
+ * @param {string} text - Text to write
88
+ */
89
+ writeToTerminal (text) {
90
+ if (this.terminal && this.terminal.term) {
91
+ this.terminal.term.write(text)
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Write banner to terminal
97
+ * @param {string} type - 'RECEIVE' or 'SEND'
98
+ * @param {string} protocolName - Protocol name for display
99
+ */
100
+ writeBanner (type, protocolName) {
101
+ const border = '='.repeat(50)
102
+ this.writeToTerminal(`\r\n${border}\r\n`)
103
+ if (protocolName) {
104
+ this.writeToTerminal(`\x1b[33m\x1b[1m${protocolName}\x1b[0m\r\n`)
105
+ this.writeToTerminal(`${border}\r\n\r\n`)
106
+ }
107
+ this.writeToTerminal(`\x1b[32m\x1b[1m${this.getProtocolDisplayName()}::${type}::START\x1b[0m\r\n`)
108
+ }
109
+
110
+ /**
111
+ * Get protocol display name
112
+ * Should be overridden by subclass
113
+ * @returns {string}
114
+ */
115
+ getProtocolDisplayName () {
116
+ return 'TRANSFER'
117
+ }
118
+
119
+ /**
120
+ * Handle session end
121
+ */
122
+ onSessionEnd () {
123
+ this.isActive = false
124
+ this.currentTransfer = null
125
+ this.savePath = null
126
+ if (this.terminal && this.terminal.term) {
127
+ this.terminal.term.focus()
128
+ this.terminal.term.write('\r\n')
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Open file select dialog
134
+ * @param {Object} options - Options for file selection
135
+ * @returns {Promise<Array>} - Selected files
136
+ */
137
+ openFileSelect = async (options = {}) => {
138
+ const {
139
+ directory = false,
140
+ title = 'Choose some files to send',
141
+ message = 'Choose some files to send'
142
+ } = options
143
+
144
+ const properties = [
145
+ directory ? 'openDirectory' : 'openFile',
146
+ 'multiSelections',
147
+ 'showHiddenFiles',
148
+ 'noResolveAliases',
149
+ 'treatPackageAsDirectory',
150
+ 'dontAddToRecent'
151
+ ]
152
+
153
+ const files = await window.api.openDialog({
154
+ title,
155
+ message,
156
+ properties
157
+ }).catch(() => false)
158
+
159
+ if (!files || !files.length) {
160
+ return null
161
+ }
162
+
163
+ const r = []
164
+ for (const filePath of files) {
165
+ const stat = await getLocalFileInfo(filePath)
166
+ r.push({ ...stat, filePath, path: filePath })
167
+ }
168
+ return r
169
+ }
170
+
171
+ /**
172
+ * Open save folder select dialog
173
+ * @returns {Promise<string>} - Selected folder path
174
+ */
175
+ openSaveFolderSelect = async () => {
176
+ // Try to use last saved path
177
+ const lastPath = this.storageKey ? window.localStorage.getItem(this.storageKey) : null
178
+
179
+ const savePaths = await window.api.openDialog({
180
+ title: 'Choose a folder to save file(s)',
181
+ message: 'Choose a folder to save file(s)',
182
+ defaultPath: lastPath || undefined,
183
+ properties: [
184
+ 'openDirectory',
185
+ 'showHiddenFiles',
186
+ 'createDirectory',
187
+ 'noResolveAliases',
188
+ 'treatPackageAsDirectory',
189
+ 'dontAddToRecent'
190
+ ]
191
+ }).catch(() => false)
192
+
193
+ if (!savePaths || !savePaths.length) {
194
+ return null
195
+ }
196
+
197
+ // Save for next time
198
+ if (this.storageKey) {
199
+ window.localStorage.setItem(this.storageKey, savePaths[0])
200
+ }
201
+ return savePaths[0]
202
+ }
203
+
204
+ /**
205
+ * Cancel ongoing transfer
206
+ */
207
+ cancel () {
208
+ if (!this.isActive) return
209
+ this.writeToTerminal(`\r\n\x1b[33m\x1b[1m${this.getProtocolDisplayName()} transfer cancelled by user\x1b[0m\r\n`)
210
+ this.sendToServer({
211
+ event: 'cancel'
212
+ })
213
+ this.onSessionEnd()
214
+ }
215
+
216
+ /**
217
+ * Clean up resources
218
+ */
219
+ destroy () {
220
+ if (this.socket && this.messageHandler) {
221
+ this.socket.removeEventListener('message', this.messageHandler)
222
+ this.messageHandler = null
223
+ }
224
+ this.isActive = false
225
+ this.currentTransfer = null
226
+ this.savePath = null
227
+ this.socket = null
228
+ this.terminal = null
229
+ }
230
+ }
231
+
232
+ export default TransferClientBase