@electerm/electerm-react 1.72.18 → 1.72.36
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/components/ai/ai-chat.jsx +1 -1
- package/client/components/bookmark-form/form-tabs.jsx +1 -1
- package/client/components/bookmark-form/local-form-ui.jsx +1 -1
- package/client/components/bookmark-form/ssh-form-ui.jsx +3 -1
- package/client/components/bookmark-form/x11.jsx +66 -10
- package/client/components/common/input-auto-focus.jsx +11 -3
- package/client/components/session/session.jsx +1 -3
- package/client/components/setting-panel/number-config.jsx +48 -0
- package/client/components/setting-panel/setting-terminal.jsx +18 -32
- package/client/components/setting-panel/terminal-bg-config.jsx +152 -0
- package/client/components/sftp/file-item.jsx +1 -0
- package/client/components/sftp/sftp.styl +4 -1
- package/client/components/terminal/term-search.jsx +13 -8
- package/client/components/terminal/terminal-search-bar.jsx +48 -0
- package/client/components/terminal/terminal.jsx +26 -4
- package/client/components/terminal/terminal.styl +8 -0
- package/package.json +1 -1
|
@@ -69,7 +69,9 @@ export default function BookmarkFormUI (props) {
|
|
|
69
69
|
sshTunnels: [],
|
|
70
70
|
runScripts: [{}],
|
|
71
71
|
category: initBookmarkGroupId,
|
|
72
|
-
connectionHoppings: []
|
|
72
|
+
connectionHoppings: [],
|
|
73
|
+
serverHostKey: [],
|
|
74
|
+
cipher: []
|
|
73
75
|
}
|
|
74
76
|
initialValues = defaultsDeep(initialValues, defaultValues)
|
|
75
77
|
function onChangeAuthType (e) {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import {
|
|
5
5
|
Switch,
|
|
6
6
|
Form,
|
|
7
|
+
Button,
|
|
7
8
|
Select
|
|
8
9
|
} from 'antd'
|
|
9
10
|
import { formItemLayout } from '../../common/form-layout'
|
|
@@ -46,7 +47,48 @@ const serverHostKeyOptions = [
|
|
|
46
47
|
'rsa-sha2-256'
|
|
47
48
|
]
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
const defaultCipherOptions = [
|
|
51
|
+
'aes128-ctr',
|
|
52
|
+
'aes192-ctr',
|
|
53
|
+
'aes256-ctr',
|
|
54
|
+
'aes128-gcm',
|
|
55
|
+
'aes128-gcm@openssh.com',
|
|
56
|
+
'aes256-gcm',
|
|
57
|
+
'aes256-gcm@openssh.com',
|
|
58
|
+
'aes256-cbc',
|
|
59
|
+
'aes192-cbc',
|
|
60
|
+
'aes128-cbc',
|
|
61
|
+
'blowfish-cbc',
|
|
62
|
+
'3des-cbc',
|
|
63
|
+
'arcfour256',
|
|
64
|
+
'arcfour128',
|
|
65
|
+
'cast128-cbc',
|
|
66
|
+
'arcfour'
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
const defaultServerHostKeyOptions = [
|
|
70
|
+
'ssh-rsa',
|
|
71
|
+
'ssh-ed25519',
|
|
72
|
+
'ecdsa-sha2-nistp256',
|
|
73
|
+
'ecdsa-sha2-nistp384',
|
|
74
|
+
'ecdsa-sha2-nistp521',
|
|
75
|
+
'ssh-dss',
|
|
76
|
+
'rsa-sha2-512',
|
|
77
|
+
'rsa-sha2-256'
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
export default function renderX11 (form) {
|
|
81
|
+
function setDefaults () {
|
|
82
|
+
// form.setFieldsValue({
|
|
83
|
+
// cipher: defaultCipherOptions
|
|
84
|
+
// })
|
|
85
|
+
|
|
86
|
+
// form.setFieldsValue({
|
|
87
|
+
// serverHostKey: defaultServerHostKeyOptions
|
|
88
|
+
// })
|
|
89
|
+
form.setFieldValue('cipher', defaultCipherOptions)
|
|
90
|
+
form.setFieldValue('serverHostKey', defaultServerHostKeyOptions)
|
|
91
|
+
}
|
|
50
92
|
return (
|
|
51
93
|
<>
|
|
52
94
|
<FormItem
|
|
@@ -67,17 +109,31 @@ export default function renderX11 () {
|
|
|
67
109
|
<FormItem
|
|
68
110
|
{...formItemLayout}
|
|
69
111
|
label='serverHostKey'
|
|
70
|
-
name='serverHostKey'
|
|
71
112
|
>
|
|
72
|
-
<
|
|
73
|
-
|
|
113
|
+
<FormItem
|
|
114
|
+
{...formItemLayout}
|
|
115
|
+
noStyle
|
|
116
|
+
name='serverHostKey'
|
|
74
117
|
>
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
118
|
+
<Select
|
|
119
|
+
mode='multiple'
|
|
120
|
+
>
|
|
121
|
+
{serverHostKeyOptions.map(key => (
|
|
122
|
+
<Option key={key} value={key}>
|
|
123
|
+
{key}
|
|
124
|
+
</Option>
|
|
125
|
+
))}
|
|
126
|
+
</Select>
|
|
127
|
+
</FormItem>
|
|
128
|
+
<div className='mg1t'>
|
|
129
|
+
<Button
|
|
130
|
+
type='dashed'
|
|
131
|
+
onClick={setDefaults}
|
|
132
|
+
size='small'
|
|
133
|
+
>
|
|
134
|
+
Set default cipher and serverHostKey
|
|
135
|
+
</Button>
|
|
136
|
+
</div>
|
|
81
137
|
</FormItem>
|
|
82
138
|
<FormItem
|
|
83
139
|
{...formItemLayout}
|
|
@@ -4,14 +4,22 @@ import {
|
|
|
4
4
|
} from 'antd'
|
|
5
5
|
|
|
6
6
|
export default function InputAutoFocus (props) {
|
|
7
|
-
const { type, ...rest } = props
|
|
7
|
+
const { type, selectall = false, ...rest } = props
|
|
8
8
|
const inputRef = useRef(null)
|
|
9
|
+
const isFirstRender = useRef(true)
|
|
9
10
|
|
|
10
11
|
useEffect(() => {
|
|
11
12
|
if (inputRef.current) {
|
|
12
|
-
|
|
13
|
+
const { value } = props
|
|
14
|
+
if (value && selectall && isFirstRender.current) {
|
|
15
|
+
inputRef.current.focus()
|
|
16
|
+
inputRef.current.setSelectionRange(0, value.length)
|
|
17
|
+
isFirstRender.current = false
|
|
18
|
+
} else {
|
|
19
|
+
inputRef.current.focus()
|
|
20
|
+
}
|
|
13
21
|
}
|
|
14
|
-
}, [props.value])
|
|
22
|
+
}, [props.value, props.selectall])
|
|
15
23
|
|
|
16
24
|
let InputComponent
|
|
17
25
|
switch (type) {
|
|
@@ -30,8 +30,7 @@ import {
|
|
|
30
30
|
terminalRdpType,
|
|
31
31
|
terminalVncType,
|
|
32
32
|
terminalWebType,
|
|
33
|
-
terminalTelnetType
|
|
34
|
-
splitMap
|
|
33
|
+
terminalTelnetType
|
|
35
34
|
} from '../../common/constants'
|
|
36
35
|
import { SplitViewIcon } from '../icons/split-view'
|
|
37
36
|
import { refs } from '../common/ref'
|
|
@@ -521,7 +520,6 @@ export default class SessionWrapper extends Component {
|
|
|
521
520
|
|
|
522
521
|
renderBroadcastIcon = () => {
|
|
523
522
|
if (
|
|
524
|
-
this.props.layout === splitMap.c1 ||
|
|
525
523
|
this.isSshDisabled()
|
|
526
524
|
) {
|
|
527
525
|
return null
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InputNumber
|
|
3
|
+
} from 'antd'
|
|
4
|
+
import { isNumber, isNaN } from 'lodash-es'
|
|
5
|
+
|
|
6
|
+
export default function NumberConfig ({
|
|
7
|
+
min,
|
|
8
|
+
max,
|
|
9
|
+
cls,
|
|
10
|
+
title = '',
|
|
11
|
+
value,
|
|
12
|
+
defaultValue,
|
|
13
|
+
onChange,
|
|
14
|
+
step,
|
|
15
|
+
extraDesc,
|
|
16
|
+
width = 136
|
|
17
|
+
}) {
|
|
18
|
+
const opts = {
|
|
19
|
+
step,
|
|
20
|
+
value,
|
|
21
|
+
min,
|
|
22
|
+
max,
|
|
23
|
+
onChange,
|
|
24
|
+
placeholder: defaultValue
|
|
25
|
+
}
|
|
26
|
+
if (title) {
|
|
27
|
+
opts.formatter = v => `${title}${extraDesc || ''}: ${v}`
|
|
28
|
+
opts.parser = (v) => {
|
|
29
|
+
let vv = isNumber(v)
|
|
30
|
+
? v
|
|
31
|
+
: Number(v.split(': ')[1], 10)
|
|
32
|
+
if (isNaN(vv)) {
|
|
33
|
+
vv = defaultValue
|
|
34
|
+
}
|
|
35
|
+
return vv
|
|
36
|
+
}
|
|
37
|
+
opts.style = {
|
|
38
|
+
width: width + 'px'
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return (
|
|
42
|
+
<div className={`pd2b ${cls || ''}`}>
|
|
43
|
+
<InputNumber
|
|
44
|
+
{...opts}
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
Switch,
|
|
10
10
|
Input,
|
|
11
11
|
Upload,
|
|
12
|
-
InputNumber,
|
|
13
12
|
Button,
|
|
14
13
|
AutoComplete,
|
|
15
14
|
Tooltip,
|
|
@@ -26,7 +25,6 @@ import defaultSettings from '../../common/default-setting'
|
|
|
26
25
|
import ShowItem from '../common/show-item'
|
|
27
26
|
import { osResolve } from '../../common/resolve'
|
|
28
27
|
import { chooseSaveDirectory } from '../../common/choose-save-folder'
|
|
29
|
-
import { isNumber, isNaN } from 'lodash-es'
|
|
30
28
|
import mapper from '../../common/auto-complete-data-mapper'
|
|
31
29
|
import KeywordForm from './keywords-form'
|
|
32
30
|
import Link from '../common/external-link'
|
|
@@ -35,6 +33,8 @@ import KeywordsTransport from './keywords-transport'
|
|
|
35
33
|
import fs from '../../common/fs'
|
|
36
34
|
import uid from '../../common/uid'
|
|
37
35
|
import createDefaultSessionLogPath from '../../common/default-log-path'
|
|
36
|
+
import TerminalBackgroundConfig from './terminal-bg-config'
|
|
37
|
+
import NumberConfig from './number-config'
|
|
38
38
|
import './setting.styl'
|
|
39
39
|
|
|
40
40
|
const { Option } = Select
|
|
@@ -211,40 +211,23 @@ export default class SettingTerminal extends Component {
|
|
|
211
211
|
step = 1,
|
|
212
212
|
min,
|
|
213
213
|
max,
|
|
214
|
-
cls
|
|
215
|
-
onChange = (v) => {
|
|
216
|
-
this.onChangeValue(v, name)
|
|
217
|
-
}
|
|
214
|
+
cls
|
|
218
215
|
} = options
|
|
219
216
|
const opts = {
|
|
220
|
-
step,
|
|
221
217
|
value,
|
|
222
218
|
min,
|
|
223
219
|
max,
|
|
224
|
-
onChange
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
: Number(v.split(': ')[1], 10)
|
|
233
|
-
if (isNaN(vv)) {
|
|
234
|
-
vv = defaultValue
|
|
235
|
-
}
|
|
236
|
-
return vv
|
|
237
|
-
}
|
|
238
|
-
opts.style = {
|
|
239
|
-
width: width + 'px'
|
|
240
|
-
}
|
|
220
|
+
onChange: (v) => {
|
|
221
|
+
this.onChangeValue(v, name)
|
|
222
|
+
},
|
|
223
|
+
defaultValue,
|
|
224
|
+
title,
|
|
225
|
+
width,
|
|
226
|
+
step,
|
|
227
|
+
cls
|
|
241
228
|
}
|
|
242
229
|
return (
|
|
243
|
-
<
|
|
244
|
-
<InputNumber
|
|
245
|
-
{...opts}
|
|
246
|
-
/>
|
|
247
|
-
</div>
|
|
230
|
+
<NumberConfig {...opts} />
|
|
248
231
|
)
|
|
249
232
|
}
|
|
250
233
|
|
|
@@ -499,6 +482,11 @@ export default class SettingTerminal extends Component {
|
|
|
499
482
|
submit: this.handleSubmitKeywords,
|
|
500
483
|
themeConfig: getThemeConfig()
|
|
501
484
|
}
|
|
485
|
+
const bgProps = {
|
|
486
|
+
onChangeValue: this.onChangeValue,
|
|
487
|
+
name: 'terminalBackgroundImagePath',
|
|
488
|
+
config: this.props.config
|
|
489
|
+
}
|
|
502
490
|
const tip = (
|
|
503
491
|
<div>
|
|
504
492
|
<span className='mg1r'>{e('supportRegexp')}</span>
|
|
@@ -569,9 +557,7 @@ export default class SettingTerminal extends Component {
|
|
|
569
557
|
}
|
|
570
558
|
</div>
|
|
571
559
|
<div className='pd1b'>{e('terminalBackgroundImage')}</div>
|
|
572
|
-
{
|
|
573
|
-
this.renderTerminalBgSelect('terminalBackgroundImagePath')
|
|
574
|
-
}
|
|
560
|
+
<TerminalBackgroundConfig {...bgProps} />
|
|
575
561
|
<div className='pd1b'>{e('terminalWordSeparator')}</div>
|
|
576
562
|
{
|
|
577
563
|
this.renderText('terminalWordSeparator', e('terminalWordSeparator'))
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
AutoComplete,
|
|
4
|
+
Upload,
|
|
5
|
+
Tooltip,
|
|
6
|
+
Input
|
|
7
|
+
} from 'antd'
|
|
8
|
+
import {
|
|
9
|
+
noTerminalBgValue
|
|
10
|
+
} from '../../common/constants'
|
|
11
|
+
import defaultSettings from '../../common/default-setting'
|
|
12
|
+
import NumberConfig from './number-config'
|
|
13
|
+
|
|
14
|
+
const e = window.translate
|
|
15
|
+
|
|
16
|
+
export default function TerminalBackgroundConfig ({
|
|
17
|
+
onChangeValue,
|
|
18
|
+
name,
|
|
19
|
+
config
|
|
20
|
+
}) {
|
|
21
|
+
const value = config[name]
|
|
22
|
+
const defaultValue = defaultSettings[name]
|
|
23
|
+
const onChange = (v) => onChangeValue(v, name)
|
|
24
|
+
const after = (
|
|
25
|
+
<Upload
|
|
26
|
+
beforeUpload={(file) => {
|
|
27
|
+
onChangeValue(file.path, name)
|
|
28
|
+
return false
|
|
29
|
+
}}
|
|
30
|
+
showUploadList={false}
|
|
31
|
+
>
|
|
32
|
+
<span>{e('chooseFile')}</span>
|
|
33
|
+
</Upload>
|
|
34
|
+
)
|
|
35
|
+
const dataSource = [
|
|
36
|
+
{
|
|
37
|
+
value: '',
|
|
38
|
+
desc: e('default')
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
value: noTerminalBgValue,
|
|
42
|
+
desc: e('noTerminalBg')
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
const numberOpts = { step: 0.05, min: 0, max: 1, cls: 'bg-img-setting' }
|
|
46
|
+
|
|
47
|
+
function renderNumber (name, options, title = '', width = 136) {
|
|
48
|
+
let value = config[name]
|
|
49
|
+
if (options.valueParser) {
|
|
50
|
+
value = options.valueParser(value)
|
|
51
|
+
}
|
|
52
|
+
const defaultValue = defaultSettings[name]
|
|
53
|
+
const {
|
|
54
|
+
step = 1,
|
|
55
|
+
min,
|
|
56
|
+
max,
|
|
57
|
+
cls
|
|
58
|
+
} = options
|
|
59
|
+
const opts = {
|
|
60
|
+
value,
|
|
61
|
+
min,
|
|
62
|
+
max,
|
|
63
|
+
onChange: (v) => {
|
|
64
|
+
onChangeValue(v, name)
|
|
65
|
+
},
|
|
66
|
+
defaultValue,
|
|
67
|
+
title,
|
|
68
|
+
width,
|
|
69
|
+
step,
|
|
70
|
+
cls
|
|
71
|
+
}
|
|
72
|
+
return (
|
|
73
|
+
<NumberConfig {...opts} />
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const renderFilter = () => {
|
|
78
|
+
if (config[name] === noTerminalBgValue) return
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div>
|
|
82
|
+
{
|
|
83
|
+
renderNumber(
|
|
84
|
+
'terminalBackgroundFilterOpacity',
|
|
85
|
+
numberOpts,
|
|
86
|
+
e('Opacity')
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
{
|
|
90
|
+
renderNumber(
|
|
91
|
+
'terminalBackgroundFilterBlur',
|
|
92
|
+
{ ...numberOpts, min: 0, max: 50, step: 0.5 },
|
|
93
|
+
e('Blur')
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
{
|
|
97
|
+
renderNumber(
|
|
98
|
+
'terminalBackgroundFilterBrightness',
|
|
99
|
+
{ ...numberOpts, min: 0, max: 10, step: 0.1 },
|
|
100
|
+
e('Brightness')
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
{
|
|
104
|
+
renderNumber(
|
|
105
|
+
'terminalBackgroundFilterGrayscale',
|
|
106
|
+
numberOpts,
|
|
107
|
+
e('Grayscale')
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
{
|
|
111
|
+
renderNumber(
|
|
112
|
+
'terminalBackgroundFilterContrast',
|
|
113
|
+
{ ...numberOpts, min: 0, max: 10, step: 0.1 },
|
|
114
|
+
e('Contrast')
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
</div>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const renderBgOption = item => {
|
|
122
|
+
return {
|
|
123
|
+
value: item.value,
|
|
124
|
+
label: item.desc
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return (
|
|
128
|
+
<div className='pd2b'>
|
|
129
|
+
<div className='pd1b'>
|
|
130
|
+
<Tooltip
|
|
131
|
+
title='eg: https://xx.com/xx.png or /path/to/xx.png'
|
|
132
|
+
>
|
|
133
|
+
<AutoComplete
|
|
134
|
+
value={value}
|
|
135
|
+
onChange={onChange}
|
|
136
|
+
placeholder={defaultValue}
|
|
137
|
+
className='width-100'
|
|
138
|
+
options={dataSource.map(renderBgOption)}
|
|
139
|
+
>
|
|
140
|
+
<Input
|
|
141
|
+
addonAfter={after}
|
|
142
|
+
/>
|
|
143
|
+
</AutoComplete>
|
|
144
|
+
</Tooltip>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
{
|
|
148
|
+
renderFilter()
|
|
149
|
+
}
|
|
150
|
+
</div>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
@@ -614,6 +614,7 @@ export default class FileSection extends React.Component {
|
|
|
614
614
|
if (this.watchingFile) {
|
|
615
615
|
window.pre.ipcOffEvent('file-change', this.onFileChange)
|
|
616
616
|
window.pre.runGlobalAsync('unwatchFile', this.watchingFile)
|
|
617
|
+
fs.unlink(this.watchingFile).catch(console.log)
|
|
617
618
|
delete this.watchingFile
|
|
618
619
|
}
|
|
619
620
|
}
|
|
@@ -60,15 +60,14 @@ export default class TermSearch extends PureComponent {
|
|
|
60
60
|
setTimeout(window.store.focus, 200)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
prev = () => {
|
|
63
|
+
prev = (v = this.props.termSearch) => {
|
|
64
64
|
const {
|
|
65
65
|
activeTabId,
|
|
66
|
-
termSearch,
|
|
67
66
|
termSearchOptions
|
|
68
67
|
} = this.props
|
|
69
68
|
refs.get('term-' + activeTabId)
|
|
70
69
|
?.searchPrev(
|
|
71
|
-
|
|
70
|
+
v,
|
|
72
71
|
copy(termSearchOptions)
|
|
73
72
|
)
|
|
74
73
|
}
|
|
@@ -82,13 +81,18 @@ export default class TermSearch extends PureComponent {
|
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
handleChange = e => {
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
const v = e.target.value
|
|
85
|
+
window.store.termSearch = v
|
|
86
|
+
this.prev(v)
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
clearSearch = () => {
|
|
90
|
-
refs.get('term-' + this.props.activeTabId)
|
|
91
|
-
|
|
90
|
+
const term = refs.get('term-' + this.props.activeTabId)
|
|
91
|
+
term?.searchAddon.clearDecorations()
|
|
92
|
+
term.setState({
|
|
93
|
+
searchResults: [],
|
|
94
|
+
matchIndex: -1
|
|
95
|
+
})
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
close = () => {
|
|
@@ -201,7 +205,8 @@ export default class TermSearch extends PureComponent {
|
|
|
201
205
|
onChange: this.handleChange,
|
|
202
206
|
suffix: this.renderSuffix(),
|
|
203
207
|
onPressEnter: this.next,
|
|
204
|
-
addonAfter: this.renderAfter()
|
|
208
|
+
addonAfter: this.renderAfter(),
|
|
209
|
+
selectall: true
|
|
205
210
|
}
|
|
206
211
|
return (
|
|
207
212
|
<div className='term-search-wrap'>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
export default function SearchResultBar ({
|
|
4
|
+
matches,
|
|
5
|
+
totalLines,
|
|
6
|
+
matchIndex,
|
|
7
|
+
height
|
|
8
|
+
}) {
|
|
9
|
+
const canvasRef = useRef(null)
|
|
10
|
+
const drawSearchResults = () => {
|
|
11
|
+
const canvas = canvasRef.current
|
|
12
|
+
if (!canvas) return
|
|
13
|
+
|
|
14
|
+
const container = canvas.parentElement
|
|
15
|
+
const containerHeight = container.clientHeight
|
|
16
|
+
const dpr = window.devicePixelRatio || 1
|
|
17
|
+
|
|
18
|
+
// Set both canvas dimensions and style
|
|
19
|
+
canvas.height = containerHeight * dpr
|
|
20
|
+
canvas.width = 16 * dpr
|
|
21
|
+
|
|
22
|
+
const ctx = canvas.getContext('2d')
|
|
23
|
+
// Scale the context to account for the pixel ratio
|
|
24
|
+
ctx.scale(dpr, dpr)
|
|
25
|
+
|
|
26
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
27
|
+
matches.forEach((match, index) => {
|
|
28
|
+
const y = (match / totalLines) * containerHeight
|
|
29
|
+
ctx.fillStyle = index === matchIndex ? 'rgba(243, 67, 9, 0.5)' : 'rgba(243, 196, 9, 0.5)'
|
|
30
|
+
ctx.fillRect(0, y, 16, 2)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
drawSearchResults()
|
|
36
|
+
}, [matches, totalLines, matchIndex])
|
|
37
|
+
|
|
38
|
+
if (!matches.length) {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<canvas
|
|
44
|
+
ref={canvasRef}
|
|
45
|
+
className='term-search-bar'
|
|
46
|
+
/>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -52,6 +52,7 @@ import * as fs from './fs.js'
|
|
|
52
52
|
import iconsMap from '../sys-menu/icons-map.jsx'
|
|
53
53
|
import { refs, refsStatic } from '../common/ref.js'
|
|
54
54
|
import createDefaultLogPath from '../../common/default-log-path.js'
|
|
55
|
+
import SearchResultBar from './terminal-search-bar'
|
|
55
56
|
|
|
56
57
|
const e = window.translate
|
|
57
58
|
|
|
@@ -64,7 +65,10 @@ class Term extends Component {
|
|
|
64
65
|
saveTerminalLogToFile: !!this.props.config.saveTerminalLogToFile,
|
|
65
66
|
addTimeStampToTermLog: !!this.props.config.addTimeStampToTermLog,
|
|
66
67
|
passType: 'password',
|
|
67
|
-
lines: []
|
|
68
|
+
lines: [],
|
|
69
|
+
searchResults: [],
|
|
70
|
+
matchIndex: -1,
|
|
71
|
+
totalLines: 0
|
|
68
72
|
}
|
|
69
73
|
this.id = `term-${this.props.tab.id}`
|
|
70
74
|
refs.add(this.id, this)
|
|
@@ -703,6 +707,20 @@ clear\r`
|
|
|
703
707
|
termSearchMatchCount: resultCount,
|
|
704
708
|
termSearchMatchIndex: resultIndex
|
|
705
709
|
})
|
|
710
|
+
|
|
711
|
+
this.updateSearchResults(resultIndex)
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
updateSearchResults = (resultIndex) => {
|
|
715
|
+
const matches = this.searchAddon._highlightDecorations.map((highlight, i) => {
|
|
716
|
+
return highlight.match.row
|
|
717
|
+
})
|
|
718
|
+
|
|
719
|
+
this.setState({
|
|
720
|
+
searchResults: matches,
|
|
721
|
+
matchIndex: resultIndex,
|
|
722
|
+
totalLines: this.term.buffer.active.length
|
|
723
|
+
})
|
|
706
724
|
}
|
|
707
725
|
|
|
708
726
|
searchPrev = (searchInput, options) => {
|
|
@@ -1161,10 +1179,7 @@ clear\r`
|
|
|
1161
1179
|
}
|
|
1162
1180
|
|
|
1163
1181
|
canReceiveBroadcast = (termRef) => {
|
|
1164
|
-
const tabId = termRef.props?.tab?.id
|
|
1165
|
-
const isActiveInBatch = termRef.props.currentBatchTabId === tabId
|
|
1166
1182
|
return (
|
|
1167
|
-
isActiveInBatch &&
|
|
1168
1183
|
termRef.socket &&
|
|
1169
1184
|
termRef.props?.tab.pane === paneMap.terminal
|
|
1170
1185
|
)
|
|
@@ -1349,6 +1364,12 @@ clear\r`
|
|
|
1349
1364
|
},
|
|
1350
1365
|
trigger: this.props.config.pasteWhenContextMenu ? [] : ['contextMenu']
|
|
1351
1366
|
}
|
|
1367
|
+
const barProps = {
|
|
1368
|
+
matchIndex: this.state.matchIndex,
|
|
1369
|
+
matches: this.state.searchResults,
|
|
1370
|
+
totalLines: this.state.totalLines,
|
|
1371
|
+
height
|
|
1372
|
+
}
|
|
1352
1373
|
return (
|
|
1353
1374
|
<Dropdown {...dropdownProps}>
|
|
1354
1375
|
<div
|
|
@@ -1361,6 +1382,7 @@ clear\r`
|
|
|
1361
1382
|
lines={this.state.lines}
|
|
1362
1383
|
close={this.closeNormalBuffer}
|
|
1363
1384
|
/>
|
|
1385
|
+
<SearchResultBar {...barProps} />
|
|
1364
1386
|
<Spin className='loading-wrapper' spinning={loading} />
|
|
1365
1387
|
</div>
|
|
1366
1388
|
</Dropdown>
|