@electerm/electerm-react 1.37.81 → 1.37.92
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 +2 -1
- package/client/common/default-setting.js +3 -1
- package/client/components/footer/footer-entry.jsx +4 -9
- package/client/components/session/session.jsx +80 -23
- package/client/components/session/session.styl +2 -0
- package/client/components/session/sessions.jsx +2 -1
- package/client/components/setting-panel/setting-common.jsx +2 -9
- package/client/components/sftp/sftp-entry.jsx +54 -8
- package/client/components/shortcuts/shortcut-control.jsx +9 -8
- package/client/components/shortcuts/shortcut-handler.js +19 -13
- package/client/components/tabs/app-drag.jsx +6 -0
- package/client/components/tabs/index.jsx +56 -39
- package/client/components/terminal/attach-addon-custom.js +26 -1
- package/client/components/terminal/index.jsx +38 -8
- package/client/store/event.js +1 -0
- package/package.json +1 -1
- package/client/components/terminal-info/index.jsx +0 -25
|
@@ -42,7 +42,7 @@ export const maxHistory = 50
|
|
|
42
42
|
export const maxTransport = 5
|
|
43
43
|
export const maxSftpHistory = 20
|
|
44
44
|
export const maxZoom = 8
|
|
45
|
-
export const minZoom = 0.
|
|
45
|
+
export const minZoom = 0.5
|
|
46
46
|
export const extraTabWidth = 113
|
|
47
47
|
// export const maxTabs = 20
|
|
48
48
|
|
|
@@ -336,3 +336,4 @@ export const instSftpKeys = [
|
|
|
336
336
|
'readFile',
|
|
337
337
|
'writeFile'
|
|
338
338
|
]
|
|
339
|
+
export const cwdId = '=__+__'
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Component } from '../common/react-subx'
|
|
2
2
|
import {
|
|
3
|
-
Tooltip,
|
|
4
3
|
Select
|
|
5
4
|
} from 'antd'
|
|
6
5
|
import { InfoCircleOutlined } from '@ant-design/icons'
|
|
@@ -109,14 +108,10 @@ export default class SystemMenu extends Component {
|
|
|
109
108
|
}
|
|
110
109
|
return (
|
|
111
110
|
<div className='terminal-footer-unit terminal-footer-info'>
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
onClick={this.handleInfoPanel}
|
|
117
|
-
className='pointer font18 terminal-info-icon'
|
|
118
|
-
/>
|
|
119
|
-
</Tooltip>
|
|
111
|
+
<InfoCircleOutlined
|
|
112
|
+
onClick={this.handleInfoPanel}
|
|
113
|
+
className='pointer font18 terminal-info-icon'
|
|
114
|
+
/>
|
|
120
115
|
</div>
|
|
121
116
|
)
|
|
122
117
|
}
|
|
@@ -9,9 +9,12 @@ import {
|
|
|
9
9
|
BorderHorizontalOutlined,
|
|
10
10
|
CloseSquareFilled,
|
|
11
11
|
SearchOutlined,
|
|
12
|
-
FullscreenOutlined
|
|
12
|
+
FullscreenOutlined,
|
|
13
|
+
PaperClipOutlined
|
|
13
14
|
} from '@ant-design/icons'
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
Tooltip
|
|
17
|
+
} from 'antd'
|
|
15
18
|
import { last, findIndex, pick } from 'lodash-es'
|
|
16
19
|
import generate from '../../common/uid'
|
|
17
20
|
import copy from 'json-deep-copy'
|
|
@@ -72,6 +75,8 @@ export default class SessionWrapper extends Component {
|
|
|
72
75
|
this.state = {
|
|
73
76
|
pid: null,
|
|
74
77
|
enableSftp: false,
|
|
78
|
+
cwd: '',
|
|
79
|
+
sftpPathFollowSsh: !!props.config.sftpPathFollowSsh,
|
|
75
80
|
splitDirection: terminalSplitDirectionMap.horizontal,
|
|
76
81
|
activeSplitId,
|
|
77
82
|
infoPanelPinned: false,
|
|
@@ -89,14 +94,22 @@ export default class SessionWrapper extends Component {
|
|
|
89
94
|
// this.initEvent()
|
|
90
95
|
}
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
setCwd = (cwd, tid) => {
|
|
98
|
+
this.setState(old => {
|
|
99
|
+
return {
|
|
100
|
+
cwd,
|
|
101
|
+
terminals: old.terminals.map(t => {
|
|
102
|
+
if (t.id === tid) {
|
|
103
|
+
return {
|
|
104
|
+
...t,
|
|
105
|
+
cwd
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return t
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
}
|
|
100
113
|
|
|
101
114
|
handleShowInfo = (infoPanelProps) => {
|
|
102
115
|
this.setState({
|
|
@@ -111,6 +124,12 @@ export default class SessionWrapper extends Component {
|
|
|
111
124
|
})
|
|
112
125
|
}
|
|
113
126
|
|
|
127
|
+
toggleCheckSftpPathFollowSsh = () => {
|
|
128
|
+
this.setState({
|
|
129
|
+
sftpPathFollowSsh: !this.state.sftpPathFollowSsh
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
114
133
|
hideInfoPanel = () => {
|
|
115
134
|
this.setState({
|
|
116
135
|
showInfo: false
|
|
@@ -266,7 +285,8 @@ export default class SessionWrapper extends Component {
|
|
|
266
285
|
activeSplitId,
|
|
267
286
|
splitDirection,
|
|
268
287
|
sessionOptions,
|
|
269
|
-
sessionId
|
|
288
|
+
sessionId,
|
|
289
|
+
sftpPathFollowSsh
|
|
270
290
|
} = this.state
|
|
271
291
|
const {
|
|
272
292
|
pane
|
|
@@ -297,6 +317,7 @@ export default class SessionWrapper extends Component {
|
|
|
297
317
|
...this.props,
|
|
298
318
|
...t,
|
|
299
319
|
activeSplitId,
|
|
320
|
+
sftpPathFollowSsh,
|
|
300
321
|
themeConfig,
|
|
301
322
|
pane,
|
|
302
323
|
...pick(
|
|
@@ -308,7 +329,8 @@ export default class SessionWrapper extends Component {
|
|
|
308
329
|
'setSessionState',
|
|
309
330
|
'handleShowInfo',
|
|
310
331
|
'onChangePane',
|
|
311
|
-
'hideInfoPanel'
|
|
332
|
+
'hideInfoPanel',
|
|
333
|
+
'setCwd'
|
|
312
334
|
]),
|
|
313
335
|
...this.computePosition(t.position / 10)
|
|
314
336
|
}
|
|
@@ -330,22 +352,34 @@ export default class SessionWrapper extends Component {
|
|
|
330
352
|
}
|
|
331
353
|
|
|
332
354
|
renderSftp = () => {
|
|
333
|
-
const {
|
|
355
|
+
const {
|
|
356
|
+
sessionOptions,
|
|
357
|
+
sessionId,
|
|
358
|
+
pid,
|
|
359
|
+
enableSftp,
|
|
360
|
+
sftpPathFollowSsh,
|
|
361
|
+
cwd
|
|
362
|
+
} = this.state
|
|
334
363
|
const { pane } = this.props.tab
|
|
335
364
|
const height = this.computeHeight()
|
|
336
365
|
const cls = pane === paneMap.terminal
|
|
337
366
|
? 'hide'
|
|
338
367
|
: ''
|
|
368
|
+
const exts = {
|
|
369
|
+
sftpPathFollowSsh,
|
|
370
|
+
cwd,
|
|
371
|
+
pid,
|
|
372
|
+
enableSftp,
|
|
373
|
+
sessionOptions,
|
|
374
|
+
height,
|
|
375
|
+
sessionId,
|
|
376
|
+
pane,
|
|
377
|
+
...this.props
|
|
378
|
+
}
|
|
339
379
|
return (
|
|
340
380
|
<div className={cls}>
|
|
341
381
|
<Sftp
|
|
342
|
-
|
|
343
|
-
enableSftp={enableSftp}
|
|
344
|
-
sessionOptions={sessionOptions}
|
|
345
|
-
height={height}
|
|
346
|
-
sessionId={sessionId}
|
|
347
|
-
pane={pane}
|
|
348
|
-
{...this.props}
|
|
382
|
+
{...exts}
|
|
349
383
|
/>
|
|
350
384
|
</div>
|
|
351
385
|
)
|
|
@@ -365,7 +399,7 @@ export default class SessionWrapper extends Component {
|
|
|
365
399
|
renderSearchIcon = () => {
|
|
366
400
|
const title = e('search')
|
|
367
401
|
return (
|
|
368
|
-
<Tooltip title={title}>
|
|
402
|
+
<Tooltip title={title} placement='bottomLeft'>
|
|
369
403
|
<SearchOutlined
|
|
370
404
|
className='mg1r icon-info font16 iblock pointer spliter'
|
|
371
405
|
onClick={this.handleOpenSearch}
|
|
@@ -377,7 +411,7 @@ export default class SessionWrapper extends Component {
|
|
|
377
411
|
fullscreenIcon = () => {
|
|
378
412
|
const title = e('fullscreen')
|
|
379
413
|
return (
|
|
380
|
-
<Tooltip title={title}>
|
|
414
|
+
<Tooltip title={title} placement='bottomLeft'>
|
|
381
415
|
<FullscreenOutlined
|
|
382
416
|
className='mg1r icon-info font16 iblock pointer spliter term-fullscreen-control1'
|
|
383
417
|
onClick={this.handleFullscreen}
|
|
@@ -387,7 +421,7 @@ export default class SessionWrapper extends Component {
|
|
|
387
421
|
}
|
|
388
422
|
|
|
389
423
|
renderControl = () => {
|
|
390
|
-
const { splitDirection, terminals } = this.state
|
|
424
|
+
const { splitDirection, terminals, sftpPathFollowSsh } = this.state
|
|
391
425
|
const { props } = this
|
|
392
426
|
const { pane } = props.tab
|
|
393
427
|
const termType = props.tab?.type
|
|
@@ -413,6 +447,16 @@ export default class SessionWrapper extends Component {
|
|
|
413
447
|
if (isSsh || isLocal) {
|
|
414
448
|
controls.push(isSsh ? paneMap.sftp : paneMap.fileManager)
|
|
415
449
|
}
|
|
450
|
+
const checkTxt = e('sftpPathFollowSsh') + ' [Beta]'
|
|
451
|
+
const checkProps = {
|
|
452
|
+
onClick: this.toggleCheckSftpPathFollowSsh,
|
|
453
|
+
className: classnames(
|
|
454
|
+
'sftp-follow-ssh-icon',
|
|
455
|
+
{
|
|
456
|
+
active: sftpPathFollowSsh
|
|
457
|
+
}
|
|
458
|
+
)
|
|
459
|
+
}
|
|
416
460
|
return (
|
|
417
461
|
<div
|
|
418
462
|
className='terminal-control fix'
|
|
@@ -442,6 +486,17 @@ export default class SessionWrapper extends Component {
|
|
|
442
486
|
})
|
|
443
487
|
}
|
|
444
488
|
</div>
|
|
489
|
+
{
|
|
490
|
+
isSsh || isLocal
|
|
491
|
+
? (
|
|
492
|
+
<Tooltip title={checkTxt}>
|
|
493
|
+
<span {...checkProps}>
|
|
494
|
+
<PaperClipOutlined />
|
|
495
|
+
</span>
|
|
496
|
+
</Tooltip>
|
|
497
|
+
)
|
|
498
|
+
: null
|
|
499
|
+
}
|
|
445
500
|
{
|
|
446
501
|
pane === paneMap.terminal
|
|
447
502
|
? (
|
|
@@ -461,6 +516,7 @@ export default class SessionWrapper extends Component {
|
|
|
461
516
|
}
|
|
462
517
|
<Tooltip
|
|
463
518
|
title={`${e('split')}`}
|
|
519
|
+
placement='bottomLeft'
|
|
464
520
|
>
|
|
465
521
|
<Icon1
|
|
466
522
|
className={cls1}
|
|
@@ -469,6 +525,7 @@ export default class SessionWrapper extends Component {
|
|
|
469
525
|
</Tooltip>
|
|
470
526
|
<Tooltip
|
|
471
527
|
title={e('changeDirection')}
|
|
528
|
+
placement='bottomLeft'
|
|
472
529
|
>
|
|
473
530
|
<Icon2
|
|
474
531
|
className={cls2}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
position relative
|
|
17
17
|
display inline-block
|
|
18
18
|
|
|
19
|
+
.sftp-follow-ssh-icon
|
|
19
20
|
.type-tab
|
|
20
21
|
display inline-block
|
|
21
22
|
vertical-align middle
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
&.active
|
|
32
33
|
.type-tab-line
|
|
33
34
|
display inline-block
|
|
35
|
+
|
|
34
36
|
.is-transporting
|
|
35
37
|
.type-tab.sftp
|
|
36
38
|
.type-tab-line
|
|
@@ -340,6 +340,8 @@ class Sessions extends Component {
|
|
|
340
340
|
currentTabId,
|
|
341
341
|
tab: toSimpleObj(tab),
|
|
342
342
|
...pick(store, [
|
|
343
|
+
'fileOperation',
|
|
344
|
+
'file',
|
|
343
345
|
'height',
|
|
344
346
|
'width',
|
|
345
347
|
'activeTerminalId',
|
|
@@ -382,7 +384,6 @@ class Sessions extends Component {
|
|
|
382
384
|
currentTabId,
|
|
383
385
|
config,
|
|
384
386
|
...pick(store, [
|
|
385
|
-
'fileOperation',
|
|
386
387
|
'height',
|
|
387
388
|
'width',
|
|
388
389
|
'activeTerminalId',
|
|
@@ -320,23 +320,16 @@ export default class SettingCommon extends Component {
|
|
|
320
320
|
const defaultValue = defaultSettings[name]
|
|
321
321
|
const onChange = (e) => this.onChangeValue(e.target.value, name)
|
|
322
322
|
const onChangeArgs = (v) => this.onChangeValue(v, agrsProp)
|
|
323
|
-
const style = {
|
|
324
|
-
style: {
|
|
325
|
-
width: '40%'
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
323
|
const styleArg = {
|
|
329
324
|
style: {
|
|
330
|
-
width: '40%'
|
|
331
|
-
marginLeft: '3px'
|
|
325
|
+
width: '40%'
|
|
332
326
|
}
|
|
333
327
|
}
|
|
334
328
|
return (
|
|
335
329
|
<div className='pd2b'>
|
|
336
|
-
<Space.Compact>
|
|
330
|
+
<Space.Compact block>
|
|
337
331
|
<Input
|
|
338
332
|
value={value}
|
|
339
|
-
{...style}
|
|
340
333
|
onChange={onChange}
|
|
341
334
|
placeholder={defaultValue}
|
|
342
335
|
/>
|
|
@@ -111,6 +111,12 @@ export default class Sftp extends Component {
|
|
|
111
111
|
selectedFiles: []
|
|
112
112
|
})
|
|
113
113
|
}
|
|
114
|
+
if (
|
|
115
|
+
this.props.sftpPathFollowSsh &&
|
|
116
|
+
prevProps.cwd !== this.props.cwd
|
|
117
|
+
) {
|
|
118
|
+
this.updateCwd(this.props.cwd)
|
|
119
|
+
}
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
componentWillUnmount () {
|
|
@@ -196,7 +202,42 @@ export default class Sftp extends Component {
|
|
|
196
202
|
this.props.pane === paneMap.fileManager
|
|
197
203
|
}
|
|
198
204
|
|
|
205
|
+
getCwdLocal = () => {
|
|
206
|
+
if (
|
|
207
|
+
!this.shouldRenderRemote() &&
|
|
208
|
+
this.props.sftpPathFollowSsh &&
|
|
209
|
+
this.props.cwd
|
|
210
|
+
) {
|
|
211
|
+
return this.props.cwd
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
updateCwd = (cwd) => {
|
|
216
|
+
if (!this.state.inited) {
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
const type = this.shouldRenderRemote()
|
|
220
|
+
? typeMap.remote
|
|
221
|
+
: typeMap.local
|
|
222
|
+
// this.setState({
|
|
223
|
+
// [`${type}PathTemp`]: cwd
|
|
224
|
+
// }, () => {
|
|
225
|
+
// this.onGoto(
|
|
226
|
+
// type
|
|
227
|
+
// )
|
|
228
|
+
// })
|
|
229
|
+
const n = `${type}Path`
|
|
230
|
+
const nt = n + 'Temp'
|
|
231
|
+
this.setState({
|
|
232
|
+
[n]: cwd,
|
|
233
|
+
[nt]: cwd
|
|
234
|
+
}, () => this[`${type}List`]())
|
|
235
|
+
}
|
|
236
|
+
|
|
199
237
|
getPwd = async (username) => {
|
|
238
|
+
if (this.props.sftpPathFollowSsh && this.props.cwd) {
|
|
239
|
+
return this.props.cwd
|
|
240
|
+
}
|
|
200
241
|
const home = await runCmd(
|
|
201
242
|
this.props.pid,
|
|
202
243
|
this.props.sessionId,
|
|
@@ -427,16 +468,19 @@ export default class Sftp extends Component {
|
|
|
427
468
|
}
|
|
428
469
|
|
|
429
470
|
initData = async () => {
|
|
430
|
-
|
|
431
|
-
const host = props.tab?.host &&
|
|
432
|
-
props.tab?.type !== terminalSshConfigType &&
|
|
433
|
-
props.tab?.type !== terminalSerialType
|
|
434
|
-
if (host) {
|
|
471
|
+
if (this.shouldRenderRemote()) {
|
|
435
472
|
this.initRemoteAll()
|
|
436
473
|
}
|
|
437
474
|
this.initLocalAll()
|
|
438
475
|
}
|
|
439
476
|
|
|
477
|
+
shouldRenderRemote = () => {
|
|
478
|
+
const { props } = this
|
|
479
|
+
return props.tab?.host &&
|
|
480
|
+
props.tab?.type !== terminalSshConfigType &&
|
|
481
|
+
props.tab?.type !== terminalSerialType
|
|
482
|
+
}
|
|
483
|
+
|
|
440
484
|
initLocalAll = () => {
|
|
441
485
|
this.localListOwner()
|
|
442
486
|
this.localList()
|
|
@@ -699,7 +743,10 @@ export default class Sftp extends Component {
|
|
|
699
743
|
)
|
|
700
744
|
try {
|
|
701
745
|
const noPathInit = localPathReal || this.state.localPath
|
|
702
|
-
const localPath = noPathInit ||
|
|
746
|
+
const localPath = noPathInit ||
|
|
747
|
+
this.getCwdLocal() ||
|
|
748
|
+
this.props.tab.startDirectoryLocal ||
|
|
749
|
+
window.pre.homeOrTmp
|
|
703
750
|
const locals = await fs.readdirAsync(localPath)
|
|
704
751
|
const local = []
|
|
705
752
|
for (const name of locals) {
|
|
@@ -1010,8 +1057,7 @@ export default class Sftp extends Component {
|
|
|
1010
1057
|
const {
|
|
1011
1058
|
height, width
|
|
1012
1059
|
} = this.props
|
|
1013
|
-
const shouldRenderRemote = this.
|
|
1014
|
-
this.props.tab?.enableSftp !== false
|
|
1060
|
+
const shouldRenderRemote = this.shouldRenderRemote()
|
|
1015
1061
|
if (!shouldRenderRemote) {
|
|
1016
1062
|
return (
|
|
1017
1063
|
this.renderSection(arr[0], {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import React from 'react'
|
|
7
7
|
import { shortcutExtend } from './shortcut-handler.js'
|
|
8
|
+
import { throttle } from 'lodash-es'
|
|
8
9
|
|
|
9
10
|
class ShortcutControl extends React.PureComponent {
|
|
10
11
|
componentDidMount () {
|
|
@@ -40,33 +41,33 @@ class ShortcutControl extends React.PureComponent {
|
|
|
40
41
|
x && x.click()
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
zoominShortcut = (e) => {
|
|
44
|
+
zoominShortcut = throttle((e) => {
|
|
44
45
|
e.stopPropagation()
|
|
45
46
|
window.store.zoom(0.25, true)
|
|
46
|
-
}
|
|
47
|
+
}, 1000)
|
|
47
48
|
|
|
48
|
-
zoomoutShortcut = (e) => {
|
|
49
|
+
zoomoutShortcut = throttle((e) => {
|
|
49
50
|
e.stopPropagation()
|
|
50
51
|
window.store.zoom(-0.25, true)
|
|
51
|
-
}
|
|
52
|
+
}, 1000)
|
|
52
53
|
|
|
53
|
-
zoominTerminalShortcut = (event) => {
|
|
54
|
+
zoominTerminalShortcut = throttle((event) => {
|
|
54
55
|
if (window.store.inActiveTerminal) {
|
|
55
56
|
window.store.zoomTerminal(event.wheelDeltaY || 120)
|
|
56
57
|
} else {
|
|
57
58
|
const plus = 0.2
|
|
58
59
|
window.store.zoom(plus, true)
|
|
59
60
|
}
|
|
60
|
-
}
|
|
61
|
+
}, 1000)
|
|
61
62
|
|
|
62
|
-
zoomoutTerminalShortcut = (event) => {
|
|
63
|
+
zoomoutTerminalShortcut = throttle((event) => {
|
|
63
64
|
if (window.store.inActiveTerminal) {
|
|
64
65
|
window.store.zoomTerminal(event.wheelDeltaY || -120)
|
|
65
66
|
} else {
|
|
66
67
|
const plus = -0.2
|
|
67
68
|
window.store.zoom(plus, true)
|
|
68
69
|
}
|
|
69
|
-
}
|
|
70
|
+
}, 1000)
|
|
70
71
|
|
|
71
72
|
render () {
|
|
72
73
|
return null
|
|
@@ -53,21 +53,27 @@ export function shortcutExtend (Cls) {
|
|
|
53
53
|
shiftKey,
|
|
54
54
|
metaKey,
|
|
55
55
|
altKey,
|
|
56
|
-
wheelDeltaY
|
|
56
|
+
wheelDeltaY,
|
|
57
|
+
type,
|
|
58
|
+
key
|
|
57
59
|
} = event
|
|
58
|
-
if (this.isTerm) {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
]
|
|
63
|
-
if (event.type === 'keydown') {
|
|
64
|
-
for (const i in keymap) {
|
|
65
|
-
if (keymap[i].key === event.key && keymap[i].shiftKey === shiftKey) {
|
|
66
|
-
this.socket.send(String.fromCharCode(keymap[i].mapCode))
|
|
67
|
-
return false
|
|
68
|
-
}
|
|
69
|
-
}
|
|
60
|
+
if (key === 'Backspace' && this.isTerm && type === 'keydown') {
|
|
61
|
+
const now = Date.now()
|
|
62
|
+
if (!this.lastTimePressDel) {
|
|
63
|
+
this.lastTimePressDel = now
|
|
70
64
|
}
|
|
65
|
+
const timer = now - this.lastTimePressDel
|
|
66
|
+
const count = Math.ceil(timer / 800)
|
|
67
|
+
let char = String.fromCharCode(
|
|
68
|
+
shiftKey ? 127 : 8
|
|
69
|
+
)
|
|
70
|
+
char = new Array(count).fill(char).join('')
|
|
71
|
+
this.socket.send(
|
|
72
|
+
char
|
|
73
|
+
)
|
|
74
|
+
return false
|
|
75
|
+
} else if (key === 'Backspace' && this.isTerm && type === 'keyup') {
|
|
76
|
+
delete this.lastTimePressDel
|
|
71
77
|
}
|
|
72
78
|
const codeName = event instanceof window.WheelEvent
|
|
73
79
|
? (wheelDeltaY > 0 ? 'mouseWheelUp' : 'mouseWheelDown')
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
|
|
1
3
|
export default function AppDrag (props) {
|
|
2
4
|
function canOperate (e) {
|
|
3
5
|
const {
|
|
@@ -37,6 +39,10 @@ export default function AppDrag (props) {
|
|
|
37
39
|
window.pre.runGlobalAsync('maximize')
|
|
38
40
|
}
|
|
39
41
|
}
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
window.addEventListener('contextmenu', onMouseUp)
|
|
45
|
+
}, [])
|
|
40
46
|
return (
|
|
41
47
|
<div
|
|
42
48
|
className='app-drag'
|
|
@@ -219,7 +219,19 @@ export default class Tabs extends React.Component {
|
|
|
219
219
|
)
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
renderContent () {
|
|
223
|
+
const { config } = this.props
|
|
224
|
+
if (config.useSystemTitleBar) {
|
|
225
|
+
return this.renderContentInner()
|
|
226
|
+
}
|
|
227
|
+
return (
|
|
228
|
+
<AppDrag>
|
|
229
|
+
{this.renderContentInner()}
|
|
230
|
+
</AppDrag>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
renderContentInner () {
|
|
223
235
|
const { tabs = [], width } = this.props
|
|
224
236
|
const len = tabs.length
|
|
225
237
|
const tabsWidthAll = tabMargin * len + 10 + this.tabsWidth()
|
|
@@ -230,46 +242,51 @@ export default class Tabs extends React.Component {
|
|
|
230
242
|
const style = {
|
|
231
243
|
width: width - windowControlWidth - 86
|
|
232
244
|
}
|
|
245
|
+
return (
|
|
246
|
+
<div
|
|
247
|
+
className='tabs-inner'
|
|
248
|
+
style={style}
|
|
249
|
+
>
|
|
250
|
+
<div
|
|
251
|
+
style={{
|
|
252
|
+
left
|
|
253
|
+
}}
|
|
254
|
+
/>
|
|
255
|
+
<div
|
|
256
|
+
className='tabs-wrapper relative'
|
|
257
|
+
style={{
|
|
258
|
+
width: tabsWidthAll + extraTabWidth + 10
|
|
259
|
+
}}
|
|
260
|
+
onDoubleClick={this.handleAdd}
|
|
261
|
+
>
|
|
262
|
+
{
|
|
263
|
+
tabs.map((tab, i) => {
|
|
264
|
+
const isLast = i === len - 1
|
|
265
|
+
return (
|
|
266
|
+
<Tab
|
|
267
|
+
{...this.props}
|
|
268
|
+
tab={tab}
|
|
269
|
+
isLast={isLast}
|
|
270
|
+
key={tab.id}
|
|
271
|
+
/>
|
|
272
|
+
)
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
{
|
|
276
|
+
!overflow
|
|
277
|
+
? this.renderAddBtn()
|
|
278
|
+
: null
|
|
279
|
+
}
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
render () {
|
|
286
|
+
const overflow = this.isOverflow()
|
|
233
287
|
return (
|
|
234
288
|
<div className='tabs' ref={this.tabsRef}>
|
|
235
|
-
|
|
236
|
-
<div
|
|
237
|
-
className='tabs-inner'
|
|
238
|
-
style={style}
|
|
239
|
-
>
|
|
240
|
-
<div
|
|
241
|
-
style={{
|
|
242
|
-
left
|
|
243
|
-
}}
|
|
244
|
-
/>
|
|
245
|
-
<div
|
|
246
|
-
className='tabs-wrapper relative'
|
|
247
|
-
style={{
|
|
248
|
-
width: tabsWidthAll + extraTabWidth + 10
|
|
249
|
-
}}
|
|
250
|
-
onDoubleClick={this.handleAdd}
|
|
251
|
-
>
|
|
252
|
-
{
|
|
253
|
-
tabs.map((tab, i) => {
|
|
254
|
-
const isLast = i === len - 1
|
|
255
|
-
return (
|
|
256
|
-
<Tab
|
|
257
|
-
{...this.props}
|
|
258
|
-
tab={tab}
|
|
259
|
-
isLast={isLast}
|
|
260
|
-
key={tab.id}
|
|
261
|
-
/>
|
|
262
|
-
)
|
|
263
|
-
})
|
|
264
|
-
}
|
|
265
|
-
{
|
|
266
|
-
!overflow
|
|
267
|
-
? this.renderAddBtn()
|
|
268
|
-
: null
|
|
269
|
-
}
|
|
270
|
-
</div>
|
|
271
|
-
</div>
|
|
272
|
-
</AppDrag>
|
|
289
|
+
{this.renderContent()}
|
|
273
290
|
<WindowControl
|
|
274
291
|
store={window.store}
|
|
275
292
|
/>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* customize AttachAddon
|
|
3
3
|
*/
|
|
4
4
|
import { AttachAddon } from 'xterm-addon-attach'
|
|
5
|
+
import strip from '@electerm/strip-ansi'
|
|
5
6
|
|
|
6
7
|
export default class AttachAddonCustom extends AttachAddon {
|
|
7
8
|
constructor (term, options, encode, isWindowsShell) {
|
|
@@ -22,7 +23,31 @@ export default class AttachAddonCustom extends AttachAddon {
|
|
|
22
23
|
const fileReader = new FileReader()
|
|
23
24
|
fileReader.addEventListener('load', () => {
|
|
24
25
|
const str = this.decoder.decode(fileReader.result)
|
|
25
|
-
terminal.
|
|
26
|
+
if (terminal.parent.props.sftpPathFollowSsh && terminal.buffer.active.type !== 'alternate') {
|
|
27
|
+
const {
|
|
28
|
+
cwdId
|
|
29
|
+
} = terminal
|
|
30
|
+
const nss = str.split('\r')
|
|
31
|
+
const nnss = []
|
|
32
|
+
for (const str1 of nss) {
|
|
33
|
+
const ns = strip(str1).trim()
|
|
34
|
+
if (ns.includes(cwdId) && ns.includes('$PWD')) {
|
|
35
|
+
nnss.push(str1.replace(`echo "${cwdId}$PWD"`, ''))
|
|
36
|
+
} else if (
|
|
37
|
+
(cwdId && ns.startsWith(cwdId))
|
|
38
|
+
) {
|
|
39
|
+
delete terminal.cwdId
|
|
40
|
+
const cwd = ns.replace(cwdId, '').trim()
|
|
41
|
+
terminal.parent.setCwd(cwd)
|
|
42
|
+
nnss.push('\x1b[2A\x1b[0J')
|
|
43
|
+
} else {
|
|
44
|
+
nnss.push(str1)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
terminal.write(nnss.join('\r'))
|
|
48
|
+
} else {
|
|
49
|
+
terminal.write(str)
|
|
50
|
+
}
|
|
26
51
|
})
|
|
27
52
|
fileReader.readAsArrayBuffer(new window.Blob([data]))
|
|
28
53
|
}
|
|
@@ -25,7 +25,9 @@ import {
|
|
|
25
25
|
transferTypeMap,
|
|
26
26
|
terminalActions,
|
|
27
27
|
commonActions,
|
|
28
|
-
rendererTypes
|
|
28
|
+
rendererTypes,
|
|
29
|
+
cwdId,
|
|
30
|
+
isMac
|
|
29
31
|
} from '../../common/constants'
|
|
30
32
|
import deepCopy from 'json-deep-copy'
|
|
31
33
|
import { readClipboardAsync, copy } from '../../common/clipboard'
|
|
@@ -76,8 +78,6 @@ class Term extends Component {
|
|
|
76
78
|
|
|
77
79
|
isTerm = true
|
|
78
80
|
|
|
79
|
-
dataCache = ''
|
|
80
|
-
|
|
81
81
|
componentDidMount () {
|
|
82
82
|
this.initTerminal()
|
|
83
83
|
this.initEvt()
|
|
@@ -130,6 +130,7 @@ class Term extends Component {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
componentWillUnmount () {
|
|
133
|
+
delete this.term.parent
|
|
133
134
|
Object.keys(this.timers).forEach(k => {
|
|
134
135
|
clearTimeout(this.timers[k])
|
|
135
136
|
})
|
|
@@ -242,6 +243,19 @@ class Term extends Component {
|
|
|
242
243
|
this.tryInsertSelected()
|
|
243
244
|
}
|
|
244
245
|
|
|
246
|
+
pasteShortcut = (e) => {
|
|
247
|
+
if (isMac) {
|
|
248
|
+
return true
|
|
249
|
+
}
|
|
250
|
+
if (!this.isRemote()) {
|
|
251
|
+
return true
|
|
252
|
+
}
|
|
253
|
+
if (this.term.buffer.active.type !== 'alternate') {
|
|
254
|
+
return false
|
|
255
|
+
}
|
|
256
|
+
return true
|
|
257
|
+
}
|
|
258
|
+
|
|
245
259
|
showNormalBufferShortcut = (e) => {
|
|
246
260
|
e.stopPropagation()
|
|
247
261
|
this.openNormalBuffer()
|
|
@@ -762,8 +776,22 @@ class Term extends Component {
|
|
|
762
776
|
const str = this.serializeAddon.serialize()
|
|
763
777
|
const arr = strip(str).split(/ +/)
|
|
764
778
|
const len = arr.length
|
|
765
|
-
|
|
766
|
-
|
|
779
|
+
return arr[len - 1]
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
getCwd = () => {
|
|
783
|
+
if (
|
|
784
|
+
this.props.sftpPathFollowSsh &&
|
|
785
|
+
this.term.buffer.active.type !== 'alternate'
|
|
786
|
+
) {
|
|
787
|
+
const cmd = `\recho "${cwdId}$PWD"\r`
|
|
788
|
+
this.term.cwdId = cwdId
|
|
789
|
+
this.socket.send(cmd)
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
setCwd = (cwd) => {
|
|
794
|
+
this.props.setCwd(cwd, this.state.id)
|
|
767
795
|
}
|
|
768
796
|
|
|
769
797
|
onData = (d) => {
|
|
@@ -771,9 +799,10 @@ class Term extends Component {
|
|
|
771
799
|
if (!d.includes('\r')) {
|
|
772
800
|
delete this.userTypeExit
|
|
773
801
|
} else {
|
|
774
|
-
this.getCmd()
|
|
775
|
-
|
|
776
|
-
|
|
802
|
+
const data = this.getCmd()
|
|
803
|
+
if (this.term.buffer.active.type !== 'alternate') {
|
|
804
|
+
setTimeout(this.getCwd, 200)
|
|
805
|
+
}
|
|
777
806
|
const exitCmds = [
|
|
778
807
|
'exit',
|
|
779
808
|
'logout'
|
|
@@ -822,6 +851,7 @@ class Term extends Component {
|
|
|
822
851
|
|
|
823
852
|
// term.onLineFeed(this.onLineFeed)
|
|
824
853
|
// term.onTitleChange(this.onTitleChange)
|
|
854
|
+
term.parent = this
|
|
825
855
|
term.onSelectionChange(this.onSelection)
|
|
826
856
|
term.open(document.getElementById(id), true)
|
|
827
857
|
this.loadRenderer(term, config)
|
package/client/store/event.js
CHANGED
package/package.json
CHANGED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* show terminal info
|
|
3
|
-
* inluding id log path, and system info for remote session
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { InfoCircleOutlined } from '@ant-design/icons'
|
|
7
|
-
|
|
8
|
-
import { Tooltip } from 'antd'
|
|
9
|
-
import './terminal-info.styl'
|
|
10
|
-
|
|
11
|
-
export default function TerminalInfoIndex (props) {
|
|
12
|
-
const pops = {
|
|
13
|
-
onClick: props.showInfoPanel,
|
|
14
|
-
className: 'pointer font18 terminal-info-icon'
|
|
15
|
-
}
|
|
16
|
-
return (
|
|
17
|
-
<Tooltip
|
|
18
|
-
title='Terminal Info'
|
|
19
|
-
>
|
|
20
|
-
<InfoCircleOutlined
|
|
21
|
-
{...pops}
|
|
22
|
-
/>
|
|
23
|
-
</Tooltip>
|
|
24
|
-
)
|
|
25
|
-
}
|