@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
|
@@ -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 {
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|