@electerm/electerm-react 1.35.6 → 1.36.1
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 +1 -0
- package/client/components/bookmark-form/color-picker.jsx +1 -0
- package/client/components/bookmark-form/local-form-ui.jsx +6 -2
- package/client/components/bookmark-form/telnet-form-ui.jsx +2 -0
- package/client/components/main/custom-css.jsx +1 -1
- package/client/components/setting-panel/keywords-form.jsx +124 -0
- package/client/components/setting-panel/{setting.jsx → setting-common.jsx} +2 -288
- package/client/components/setting-panel/setting-terminal.jsx +494 -0
- package/client/components/setting-panel/tab-settings.jsx +6 -2
- package/client/components/sftp/list-table-ui.jsx +3 -2
- package/client/components/terminal/highlight-addon.js +63 -0
- package/client/components/terminal/index.jsx +12 -41
- package/client/store/index.js +9 -3
- package/client/store/setting.js +2 -1
- package/package.json +1 -1
|
@@ -216,6 +216,7 @@ export const syncTokenCreateUrls = {
|
|
|
216
216
|
custom: 'https://github.com/electerm/electerm/wiki/Custom-sync-server'
|
|
217
217
|
}
|
|
218
218
|
export const settingSyncId = 'setting-sync'
|
|
219
|
+
export const settingTerminalId = 'setting-terminal'
|
|
219
220
|
export const settingShortcutsId = 'setting-shortcuts'
|
|
220
221
|
export const settingCommonId = 'setting-common'
|
|
221
222
|
export const defaultEnvLang = 'en_US.UTF-8'
|
|
@@ -3,6 +3,7 @@ import { Popover } from 'antd'
|
|
|
3
3
|
import { HexColorPicker } from 'react-colorful'
|
|
4
4
|
import { defaultColors, getRandomHexColor } from '../../common/rand-hex-color.js'
|
|
5
5
|
import { HexInput } from './hex-input.jsx'
|
|
6
|
+
import './color-picker.styl'
|
|
6
7
|
|
|
7
8
|
// Your Custom Color Picker component
|
|
8
9
|
export const ColorPicker = React.forwardRef((props, ref) => {
|
|
@@ -22,6 +22,8 @@ import useQm from './use-quick-commands'
|
|
|
22
22
|
import copy from 'json-deep-copy'
|
|
23
23
|
import { defaults } from 'lodash-es'
|
|
24
24
|
import renderRunScripts from './render-delayed-scripts.jsx'
|
|
25
|
+
import { ColorPickerItem } from './color-picker-item.jsx'
|
|
26
|
+
import { getRandomDefaultColor } from '../../common/rand-hex-color.js'
|
|
25
27
|
|
|
26
28
|
const FormItem = Form.Item
|
|
27
29
|
const { prefix } = window
|
|
@@ -60,6 +62,7 @@ export default function LocalFormUi (props) {
|
|
|
60
62
|
term: props.store.config.terminalType,
|
|
61
63
|
displayRaw: false,
|
|
62
64
|
type: terminalLocalType,
|
|
65
|
+
color: getRandomDefaultColor(),
|
|
63
66
|
runScripts: [{}],
|
|
64
67
|
enableSsh: true
|
|
65
68
|
}
|
|
@@ -75,10 +78,11 @@ export default function LocalFormUi (props) {
|
|
|
75
78
|
<FormItem
|
|
76
79
|
{...formItemLayout}
|
|
77
80
|
label={e('title')}
|
|
78
|
-
name='title'
|
|
79
81
|
hasFeedback
|
|
80
82
|
>
|
|
81
|
-
<
|
|
83
|
+
<FormItem noStyle name='title'>
|
|
84
|
+
<Input addonBefore={<ColorPickerItem />} />
|
|
85
|
+
</FormItem>
|
|
82
86
|
</FormItem>
|
|
83
87
|
<FormItem
|
|
84
88
|
{...formItemLayout}
|
|
@@ -19,6 +19,7 @@ import useSubmit from './use-submit'
|
|
|
19
19
|
import useUI from './use-ui'
|
|
20
20
|
import useQm from './use-quick-commands'
|
|
21
21
|
import renderCommon from './form-ssh-common'
|
|
22
|
+
import { getRandomDefaultColor } from '../../common/rand-hex-color.js'
|
|
22
23
|
import copy from 'json-deep-copy'
|
|
23
24
|
import { defaultsDeep, uniqBy } from 'lodash-es'
|
|
24
25
|
import './bookmark-form.styl'
|
|
@@ -59,6 +60,7 @@ export default function TelnetFormUI (props) {
|
|
|
59
60
|
id: '',
|
|
60
61
|
username: 'root',
|
|
61
62
|
password: 'guest',
|
|
63
|
+
color: getRandomDefaultColor(),
|
|
62
64
|
runScripts: [{}],
|
|
63
65
|
term: defaultSettings.terminalType,
|
|
64
66
|
displayRaw: false,
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Form,
|
|
3
|
+
Input,
|
|
4
|
+
Select,
|
|
5
|
+
Space,
|
|
6
|
+
Button
|
|
7
|
+
} from 'antd'
|
|
8
|
+
import { formItemLayout } from '../../common/form-layout'
|
|
9
|
+
import {
|
|
10
|
+
MinusCircleOutlined,
|
|
11
|
+
PlusOutlined
|
|
12
|
+
} from '@ant-design/icons'
|
|
13
|
+
|
|
14
|
+
const FormItem = Form.Item
|
|
15
|
+
const FormList = Form.List
|
|
16
|
+
const OptionSel = Select.Option
|
|
17
|
+
const { prefix } = window
|
|
18
|
+
const f = prefix('form')
|
|
19
|
+
|
|
20
|
+
export default function KeywordForm (props) {
|
|
21
|
+
const {
|
|
22
|
+
formData
|
|
23
|
+
} = props
|
|
24
|
+
console.log('formData', formData)
|
|
25
|
+
const [formChild] = Form.useForm()
|
|
26
|
+
function handleTrigger () {
|
|
27
|
+
formChild.submit()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function handleFinish (data) {
|
|
31
|
+
props.submit(data)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function renderItem (field, i, add, remove) {
|
|
35
|
+
return (
|
|
36
|
+
<Space
|
|
37
|
+
align='center'
|
|
38
|
+
key={field.key}
|
|
39
|
+
>
|
|
40
|
+
<FormItem
|
|
41
|
+
hasFeedback
|
|
42
|
+
>
|
|
43
|
+
<FormItem noStyle required name={[field.name, 'keyword']}>
|
|
44
|
+
<Input
|
|
45
|
+
addonBefore={renderBefore(field.name)}
|
|
46
|
+
/>
|
|
47
|
+
</FormItem>
|
|
48
|
+
</FormItem>
|
|
49
|
+
<Button
|
|
50
|
+
icon={<MinusCircleOutlined />}
|
|
51
|
+
onClick={() => remove(field.name)}
|
|
52
|
+
className='mg24b'
|
|
53
|
+
/>
|
|
54
|
+
</Space>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function renderBefore (name) {
|
|
59
|
+
const colors = ['red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white']
|
|
60
|
+
return (
|
|
61
|
+
<FormItem name={[name, 'color']} noStyle>
|
|
62
|
+
<Select>
|
|
63
|
+
{
|
|
64
|
+
colors.map(color => {
|
|
65
|
+
const ps = {
|
|
66
|
+
className: 'color-picker-choose iblock',
|
|
67
|
+
style: {
|
|
68
|
+
backgroundColor: color
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return (
|
|
72
|
+
<OptionSel key={color} value={color}>
|
|
73
|
+
<span
|
|
74
|
+
{...ps}
|
|
75
|
+
/>
|
|
76
|
+
</OptionSel>
|
|
77
|
+
)
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
</Select>
|
|
81
|
+
</FormItem>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div>
|
|
87
|
+
<Form
|
|
88
|
+
form={formChild}
|
|
89
|
+
onValuesChange={handleTrigger}
|
|
90
|
+
initialValues={formData}
|
|
91
|
+
onFinish={handleFinish}
|
|
92
|
+
>
|
|
93
|
+
<FormItem {...formItemLayout}>
|
|
94
|
+
<FormList
|
|
95
|
+
name='keywords'
|
|
96
|
+
>
|
|
97
|
+
{
|
|
98
|
+
(fields, { add, remove }, { errors }) => {
|
|
99
|
+
return (
|
|
100
|
+
<div>
|
|
101
|
+
{
|
|
102
|
+
fields.map((field, i) => {
|
|
103
|
+
return renderItem(field, i, add, remove)
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
<FormItem>
|
|
107
|
+
<Button
|
|
108
|
+
type='dashed'
|
|
109
|
+
onClick={() => add()}
|
|
110
|
+
icon={<PlusOutlined />}
|
|
111
|
+
>
|
|
112
|
+
{f('keyword')}
|
|
113
|
+
</Button>
|
|
114
|
+
</FormItem>
|
|
115
|
+
</div>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
</FormList>
|
|
120
|
+
</FormItem>
|
|
121
|
+
</Form>
|
|
122
|
+
</div>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React, { Component } from 'react'
|
|
2
2
|
import {
|
|
3
|
-
CodeOutlined,
|
|
4
3
|
ArrowRightOutlined,
|
|
5
4
|
LoadingOutlined
|
|
6
5
|
} from '@ant-design/icons'
|
|
@@ -9,29 +8,21 @@ import {
|
|
|
9
8
|
Select,
|
|
10
9
|
Switch,
|
|
11
10
|
Input,
|
|
12
|
-
Upload,
|
|
13
11
|
InputNumber,
|
|
14
12
|
Alert,
|
|
15
13
|
Button,
|
|
16
|
-
AutoComplete,
|
|
17
|
-
Tooltip,
|
|
18
14
|
Table,
|
|
19
15
|
Space
|
|
20
16
|
} from 'antd'
|
|
21
17
|
import deepCopy from 'json-deep-copy'
|
|
22
18
|
import {
|
|
23
|
-
noTerminalBgValue,
|
|
24
19
|
settingMap,
|
|
25
|
-
rendererTypes,
|
|
26
20
|
proxyHelpLink
|
|
27
21
|
} from '../../common/constants'
|
|
28
22
|
import defaultSettings from '../../common/default-setting'
|
|
29
|
-
import ShowItem from '../common/show-item'
|
|
30
|
-
import { osResolve } from '../../common/resolve'
|
|
31
23
|
import Link from '../common/external-link'
|
|
32
24
|
import { isNumber, isNaN } from 'lodash-es'
|
|
33
25
|
import createEditLangLink from '../../common/create-lang-edit-link'
|
|
34
|
-
import mapper from '../../common/auto-complete-data-mapper'
|
|
35
26
|
import StartSession from './start-session-select'
|
|
36
27
|
import HelpIcon from '../common/help-icon'
|
|
37
28
|
import delay from '../../common/wait.js'
|
|
@@ -41,7 +32,6 @@ const { Option } = Select
|
|
|
41
32
|
const { prefix } = window
|
|
42
33
|
const e = prefix('setting')
|
|
43
34
|
const f = prefix('form')
|
|
44
|
-
const s = prefix('ssh')
|
|
45
35
|
const p = prefix('sftp')
|
|
46
36
|
const t = prefix('terminalThemes')
|
|
47
37
|
|
|
@@ -58,7 +48,7 @@ const keys = [
|
|
|
58
48
|
})
|
|
59
49
|
]
|
|
60
50
|
|
|
61
|
-
export default class
|
|
51
|
+
export default class SettingCommon extends Component {
|
|
62
52
|
state = {
|
|
63
53
|
ready: false,
|
|
64
54
|
languageChanged: false,
|
|
@@ -202,20 +192,6 @@ export default class Setting extends Component {
|
|
|
202
192
|
this.onChangeValue(value, 'onStartSessions')
|
|
203
193
|
}
|
|
204
194
|
|
|
205
|
-
handleChangeFont = (values) => {
|
|
206
|
-
this.onChangeValue(
|
|
207
|
-
values.join(', '),
|
|
208
|
-
'fontFamily'
|
|
209
|
-
)
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
handleChangeCursorStyle = (cursorStyle) => {
|
|
213
|
-
this.onChangeValue(
|
|
214
|
-
cursorStyle,
|
|
215
|
-
'cursorStyle'
|
|
216
|
-
)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
195
|
saveConfig = async (ext) => {
|
|
220
196
|
const { config } = this.props
|
|
221
197
|
if (ext.hotkey && ext.hotkey !== config.hotkey) {
|
|
@@ -386,111 +362,6 @@ export default class Setting extends Component {
|
|
|
386
362
|
)
|
|
387
363
|
}
|
|
388
364
|
|
|
389
|
-
renderBgOption = item => {
|
|
390
|
-
return {
|
|
391
|
-
value: item.value,
|
|
392
|
-
label: item.desc
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
renderTerminalBgSelect = (name) => {
|
|
397
|
-
const value = this.props.config[name]
|
|
398
|
-
const defaultValue = defaultSettings[name]
|
|
399
|
-
const onChange = (v) => this.onChangeValue(v, name)
|
|
400
|
-
const after = (
|
|
401
|
-
<Upload
|
|
402
|
-
beforeUpload={(file) => {
|
|
403
|
-
this.onChangeValue(file.path, name)
|
|
404
|
-
return false
|
|
405
|
-
}}
|
|
406
|
-
showUploadList={false}
|
|
407
|
-
>
|
|
408
|
-
<span>{e('chooseFile')}</span>
|
|
409
|
-
</Upload>
|
|
410
|
-
)
|
|
411
|
-
const dataSource = [
|
|
412
|
-
{
|
|
413
|
-
value: '',
|
|
414
|
-
desc: t('default')
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
value: noTerminalBgValue,
|
|
418
|
-
desc: e('noTerminalBg')
|
|
419
|
-
}
|
|
420
|
-
]
|
|
421
|
-
const numberOpts = { step: 0.05, min: 0, max: 1, cls: 'bg-img-setting' }
|
|
422
|
-
|
|
423
|
-
const renderFilter = () => {
|
|
424
|
-
if (this.props.config[name] === noTerminalBgValue) return
|
|
425
|
-
|
|
426
|
-
return (
|
|
427
|
-
<div>
|
|
428
|
-
{
|
|
429
|
-
this.renderNumber(
|
|
430
|
-
'terminalBackgroundFilterOpacity',
|
|
431
|
-
numberOpts,
|
|
432
|
-
e('Opacity')
|
|
433
|
-
)
|
|
434
|
-
}
|
|
435
|
-
{
|
|
436
|
-
this.renderNumber(
|
|
437
|
-
'terminalBackgroundFilterBlur',
|
|
438
|
-
{ ...numberOpts, min: 0, max: 50, step: 0.5 },
|
|
439
|
-
e('Blur')
|
|
440
|
-
)
|
|
441
|
-
}
|
|
442
|
-
{
|
|
443
|
-
this.renderNumber(
|
|
444
|
-
'terminalBackgroundFilterBrightness',
|
|
445
|
-
{ ...numberOpts, min: 0, max: 10, step: 0.1 },
|
|
446
|
-
e('Brightness')
|
|
447
|
-
)
|
|
448
|
-
}
|
|
449
|
-
{
|
|
450
|
-
this.renderNumber(
|
|
451
|
-
'terminalBackgroundFilterGrayscale',
|
|
452
|
-
numberOpts,
|
|
453
|
-
e('Grayscale')
|
|
454
|
-
)
|
|
455
|
-
}
|
|
456
|
-
{
|
|
457
|
-
this.renderNumber(
|
|
458
|
-
'terminalBackgroundFilterContrast',
|
|
459
|
-
{ ...numberOpts, min: 0, max: 10, step: 0.1 },
|
|
460
|
-
e('Contrast')
|
|
461
|
-
)
|
|
462
|
-
}
|
|
463
|
-
</div>
|
|
464
|
-
)
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
return (
|
|
468
|
-
<div className='pd2b'>
|
|
469
|
-
<div className='pd1b'>
|
|
470
|
-
<Tooltip
|
|
471
|
-
title='eg: https://xx.com/xx.png or /path/to/xx.png'
|
|
472
|
-
>
|
|
473
|
-
<AutoComplete
|
|
474
|
-
value={value}
|
|
475
|
-
onChange={onChange}
|
|
476
|
-
placeholder={defaultValue}
|
|
477
|
-
className='width-100'
|
|
478
|
-
options={dataSource.map(this.renderBgOption)}
|
|
479
|
-
>
|
|
480
|
-
<Input
|
|
481
|
-
addonAfter={after}
|
|
482
|
-
/>
|
|
483
|
-
</AutoComplete>
|
|
484
|
-
</Tooltip>
|
|
485
|
-
</div>
|
|
486
|
-
|
|
487
|
-
{
|
|
488
|
-
renderFilter()
|
|
489
|
-
}
|
|
490
|
-
</div>
|
|
491
|
-
)
|
|
492
|
-
}
|
|
493
|
-
|
|
494
365
|
renderReset = () => {
|
|
495
366
|
return (
|
|
496
367
|
<div className='pd1b pd1t'>
|
|
@@ -503,20 +374,6 @@ export default class Setting extends Component {
|
|
|
503
374
|
)
|
|
504
375
|
}
|
|
505
376
|
|
|
506
|
-
renderDefaultTerminalType = () => {
|
|
507
|
-
const opts = this.props.config.terminalTypes.map(mapper)
|
|
508
|
-
return (
|
|
509
|
-
<AutoComplete
|
|
510
|
-
options={opts}
|
|
511
|
-
style={{
|
|
512
|
-
width: '200px'
|
|
513
|
-
}}
|
|
514
|
-
value={this.props.config.terminalType}
|
|
515
|
-
onChange={(v) => this.onChangeValue(v, 'terminalType')}
|
|
516
|
-
/>
|
|
517
|
-
)
|
|
518
|
-
}
|
|
519
|
-
|
|
520
377
|
renderProxy () {
|
|
521
378
|
const {
|
|
522
379
|
enableGlobalProxy
|
|
@@ -587,82 +444,6 @@ export default class Setting extends Component {
|
|
|
587
444
|
)
|
|
588
445
|
}
|
|
589
446
|
|
|
590
|
-
renderCursorStyleSelect = () => {
|
|
591
|
-
const {
|
|
592
|
-
cursorStyle = 'block'
|
|
593
|
-
} = this.props.config
|
|
594
|
-
const sets = [
|
|
595
|
-
{
|
|
596
|
-
id: 'block',
|
|
597
|
-
title: '▊'
|
|
598
|
-
},
|
|
599
|
-
{
|
|
600
|
-
id: 'underline',
|
|
601
|
-
title: '_'
|
|
602
|
-
},
|
|
603
|
-
{
|
|
604
|
-
id: 'bar',
|
|
605
|
-
title: '|'
|
|
606
|
-
}
|
|
607
|
-
]
|
|
608
|
-
const props = {
|
|
609
|
-
onChange: this.handleChangeCursorStyle,
|
|
610
|
-
value: cursorStyle,
|
|
611
|
-
style: {
|
|
612
|
-
width: '100px'
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
return (
|
|
616
|
-
<div className='pd1b'>
|
|
617
|
-
<span className='inline-title mg1r'>{e('cursorStyle')}</span>
|
|
618
|
-
<Select
|
|
619
|
-
{...props}
|
|
620
|
-
showSearch
|
|
621
|
-
>
|
|
622
|
-
{
|
|
623
|
-
sets.map(f => {
|
|
624
|
-
return (
|
|
625
|
-
<Option value={f.id} key={f.id}>
|
|
626
|
-
<b>{f.title}</b>
|
|
627
|
-
</Option>
|
|
628
|
-
)
|
|
629
|
-
})
|
|
630
|
-
}
|
|
631
|
-
</Select>
|
|
632
|
-
</div>
|
|
633
|
-
)
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
renderFontFamily = () => {
|
|
637
|
-
const { fonts } = this.props.store
|
|
638
|
-
const { fontFamily } = this.props.config
|
|
639
|
-
const props = {
|
|
640
|
-
mode: 'multiple',
|
|
641
|
-
onChange: this.handleChangeFont,
|
|
642
|
-
value: fontFamily.split(/, */g)
|
|
643
|
-
}
|
|
644
|
-
return (
|
|
645
|
-
<Select
|
|
646
|
-
{...props}
|
|
647
|
-
showSearch
|
|
648
|
-
>
|
|
649
|
-
{
|
|
650
|
-
fonts.map(f => {
|
|
651
|
-
return (
|
|
652
|
-
<Option value={f} key={f}>
|
|
653
|
-
<span style={{
|
|
654
|
-
fontFamily: f
|
|
655
|
-
}}
|
|
656
|
-
>{f}
|
|
657
|
-
</span>
|
|
658
|
-
</Option>
|
|
659
|
-
)
|
|
660
|
-
})
|
|
661
|
-
}
|
|
662
|
-
</Select>
|
|
663
|
-
)
|
|
664
|
-
}
|
|
665
|
-
|
|
666
447
|
renderLoginPassAfter () {
|
|
667
448
|
const {
|
|
668
449
|
loginPass,
|
|
@@ -726,15 +507,12 @@ export default class Setting extends Component {
|
|
|
726
507
|
const {
|
|
727
508
|
hotkey,
|
|
728
509
|
language,
|
|
729
|
-
rendererType,
|
|
730
510
|
theme,
|
|
731
511
|
customCss
|
|
732
512
|
} = this.props.config
|
|
733
513
|
const {
|
|
734
|
-
appPath,
|
|
735
514
|
langs
|
|
736
515
|
} = this.props.store
|
|
737
|
-
const terminalLogPath = appPath ? osResolve(appPath, 'electerm', 'session_logs') : window.et.sessionLogPath
|
|
738
516
|
const terminalThemes = this.props.store.getSidebarList(settingMap.terminalThemes)
|
|
739
517
|
const [modifier, key] = hotkey.split('+')
|
|
740
518
|
const pops = {
|
|
@@ -778,12 +556,6 @@ export default class Setting extends Component {
|
|
|
778
556
|
/>
|
|
779
557
|
</div>
|
|
780
558
|
{this.renderProxy()}
|
|
781
|
-
{
|
|
782
|
-
this.renderNumber('scrollback', {
|
|
783
|
-
step: 200,
|
|
784
|
-
min: 1000
|
|
785
|
-
}, e('scrollBackDesc'), 400)
|
|
786
|
-
}
|
|
787
559
|
{
|
|
788
560
|
this.renderNumber('sshReadyTimeout', {
|
|
789
561
|
step: 200,
|
|
@@ -810,7 +582,7 @@ export default class Setting extends Component {
|
|
|
810
582
|
}
|
|
811
583
|
|
|
812
584
|
<div className='pd2b'>
|
|
813
|
-
<span className='inline-title mg1r'>{
|
|
585
|
+
<span className='inline-title mg1r'>{t('uiThemes')}</span>
|
|
814
586
|
<Select
|
|
815
587
|
onChange={this.handleChangeTerminalTheme}
|
|
816
588
|
popupMatchSelectWidth={false}
|
|
@@ -855,52 +627,6 @@ export default class Setting extends Component {
|
|
|
855
627
|
<Link className='mg1l' to={createEditLangLink(language)}>{p('edit')}</Link>
|
|
856
628
|
</div>
|
|
857
629
|
{this.renderRestart('languageChanged')}
|
|
858
|
-
<div className='pd1y font16 bold'>
|
|
859
|
-
<CodeOutlined className='mg1r' />
|
|
860
|
-
{s('terminal')} {e('settings')}
|
|
861
|
-
</div>
|
|
862
|
-
<div className='pd2b'>
|
|
863
|
-
<span className='inline-title mg1r'>{e('rendererType')}</span>
|
|
864
|
-
<Select
|
|
865
|
-
onChange={v => this.onChangeValue(v, 'rendererType')}
|
|
866
|
-
value={rendererType}
|
|
867
|
-
popupMatchSelectWidth={false}
|
|
868
|
-
>
|
|
869
|
-
{
|
|
870
|
-
Object.keys(rendererTypes).map(id => {
|
|
871
|
-
return (
|
|
872
|
-
<Option key={id} value={id}>{id}</Option>
|
|
873
|
-
)
|
|
874
|
-
})
|
|
875
|
-
}
|
|
876
|
-
</Select>
|
|
877
|
-
</div>
|
|
878
|
-
{
|
|
879
|
-
this.renderNumber('fontSize', {
|
|
880
|
-
step: 1,
|
|
881
|
-
min: 9
|
|
882
|
-
}, `${t('default')} ${e('fontSize')}`, 400)
|
|
883
|
-
}
|
|
884
|
-
<div className='pd2b'>
|
|
885
|
-
<span className='inline-title mg1r'>{t('default')} {e('fontFamily')}</span>
|
|
886
|
-
{
|
|
887
|
-
this.renderFontFamily()
|
|
888
|
-
}
|
|
889
|
-
</div>
|
|
890
|
-
<div className='pd2b'>
|
|
891
|
-
<span className='inline-title mg1r'>{e('defaultTerminalType')}</span>
|
|
892
|
-
{
|
|
893
|
-
this.renderDefaultTerminalType()
|
|
894
|
-
}
|
|
895
|
-
</div>
|
|
896
|
-
<div className='pd1b'>{e('terminalBackgroundImage')}</div>
|
|
897
|
-
{
|
|
898
|
-
this.renderTerminalBgSelect('terminalBackgroundImagePath')
|
|
899
|
-
}
|
|
900
|
-
<div className='pd1b'>{e('terminalWordSeparator')}</div>
|
|
901
|
-
{
|
|
902
|
-
this.renderText('terminalWordSeparator', e('terminalWordSeparator'))
|
|
903
|
-
}
|
|
904
630
|
<div className='pd1b'>{t('default')} {e('execWindows')}</div>
|
|
905
631
|
{
|
|
906
632
|
this.renderTextExec('execWindows')
|
|
@@ -913,31 +639,19 @@ export default class Setting extends Component {
|
|
|
913
639
|
{
|
|
914
640
|
this.renderTextExec('execLinux')
|
|
915
641
|
}
|
|
916
|
-
{
|
|
917
|
-
this.renderCursorStyleSelect()
|
|
918
|
-
}
|
|
919
642
|
{
|
|
920
643
|
[
|
|
921
644
|
'autoRefreshWhenSwitchToSftp',
|
|
922
645
|
'screenReaderMode',
|
|
923
646
|
'initDefaultTabOnStart',
|
|
924
|
-
'cursorBlink',
|
|
925
|
-
'rightClickSelectsWord',
|
|
926
|
-
'pasteWhenContextMenu',
|
|
927
|
-
'copyWhenSelect',
|
|
928
647
|
'disableSshHistory',
|
|
929
648
|
'disableTransferHistory',
|
|
930
|
-
'ctrlOrMetaOpenTerminalLink',
|
|
931
649
|
'checkUpdateOnStart',
|
|
932
650
|
'useSystemTitleBar',
|
|
933
651
|
'confirmBeforeExit',
|
|
934
652
|
'debug'
|
|
935
653
|
].map(this.renderToggle)
|
|
936
654
|
}
|
|
937
|
-
{this.renderToggle('saveTerminalLogToFile', (
|
|
938
|
-
<ShowItem to={terminalLogPath} className='mg1l'>{p('open')}</ShowItem>
|
|
939
|
-
))}
|
|
940
|
-
|
|
941
655
|
{this.renderLoginPass()}
|
|
942
656
|
{this.renderReset()}
|
|
943
657
|
</div>
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import React, { Component } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
CodeOutlined,
|
|
4
|
+
LoadingOutlined
|
|
5
|
+
} from '@ant-design/icons'
|
|
6
|
+
import {
|
|
7
|
+
message,
|
|
8
|
+
Select,
|
|
9
|
+
Switch,
|
|
10
|
+
Input,
|
|
11
|
+
Upload,
|
|
12
|
+
InputNumber,
|
|
13
|
+
Button,
|
|
14
|
+
AutoComplete,
|
|
15
|
+
Tooltip
|
|
16
|
+
} from 'antd'
|
|
17
|
+
import deepCopy from 'json-deep-copy'
|
|
18
|
+
import {
|
|
19
|
+
noTerminalBgValue,
|
|
20
|
+
rendererTypes
|
|
21
|
+
} from '../../common/constants'
|
|
22
|
+
import defaultSettings from '../../common/default-setting'
|
|
23
|
+
import ShowItem from '../common/show-item'
|
|
24
|
+
import { osResolve } from '../../common/resolve'
|
|
25
|
+
import { isNumber, isNaN } from 'lodash-es'
|
|
26
|
+
import mapper from '../../common/auto-complete-data-mapper'
|
|
27
|
+
import KeywordForm from './keywords-form'
|
|
28
|
+
import HelpIcon from '../common/help-icon'
|
|
29
|
+
import './setting.styl'
|
|
30
|
+
|
|
31
|
+
const { Option } = Select
|
|
32
|
+
const { prefix } = window
|
|
33
|
+
const e = prefix('setting')
|
|
34
|
+
const s = prefix('ssh')
|
|
35
|
+
const p = prefix('sftp')
|
|
36
|
+
const t = prefix('terminalThemes')
|
|
37
|
+
const f = prefix('form')
|
|
38
|
+
|
|
39
|
+
export default class SettingTerminal extends Component {
|
|
40
|
+
state = {
|
|
41
|
+
ready: false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
componentDidMount () {
|
|
45
|
+
this.timer = setTimeout(() => {
|
|
46
|
+
this.setState({
|
|
47
|
+
ready: true
|
|
48
|
+
})
|
|
49
|
+
}, 200)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
componentWillUnmount () {
|
|
53
|
+
clearTimeout(this.timer)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
handleResetAll = () => {
|
|
57
|
+
this.saveConfig(
|
|
58
|
+
deepCopy(defaultSettings)
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
onChangeValue = (value, name) => {
|
|
63
|
+
if (name === 'useSystemTitleBar') {
|
|
64
|
+
message.info(e('useSystemTitleBarTip'), 5)
|
|
65
|
+
}
|
|
66
|
+
this.saveConfig({
|
|
67
|
+
[name]: value
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
handleChangeFont = (values) => {
|
|
72
|
+
this.onChangeValue(
|
|
73
|
+
values.join(', '),
|
|
74
|
+
'fontFamily'
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
handleChangeCursorStyle = (cursorStyle) => {
|
|
79
|
+
this.onChangeValue(
|
|
80
|
+
cursorStyle,
|
|
81
|
+
'cursorStyle'
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
saveConfig = async (ext) => {
|
|
86
|
+
const { config } = this.props
|
|
87
|
+
if (ext.hotkey && ext.hotkey !== config.hotkey) {
|
|
88
|
+
const res = await window.pre.runGlobalAsync('changeHotkey', ext.hotkey)
|
|
89
|
+
if (!res) {
|
|
90
|
+
message.warning(e('hotkeyNotOk'))
|
|
91
|
+
delete ext.hotkey
|
|
92
|
+
} else {
|
|
93
|
+
message.success(e('saved'))
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
this.props.store.setConfig(ext)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
handleSubmitKeywords = (data) => {
|
|
100
|
+
return this.saveConfig(data)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
renderToggle = (name, extra = null) => {
|
|
104
|
+
const checked = !!this.props.config[name]
|
|
105
|
+
return (
|
|
106
|
+
<div className='pd2b' key={'rt' + name}>
|
|
107
|
+
<Switch
|
|
108
|
+
checked={checked}
|
|
109
|
+
checkedChildren={e(name)}
|
|
110
|
+
unCheckedChildren={e(name)}
|
|
111
|
+
onChange={v => this.onChangeValue(v, name)}
|
|
112
|
+
/>
|
|
113
|
+
{isNumber(extra) ? null : extra}
|
|
114
|
+
</div>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
renderNumber = (name, options, title = '', width = 136) => {
|
|
119
|
+
let value = this.props.config[name]
|
|
120
|
+
if (options.valueParser) {
|
|
121
|
+
value = options.valueParser(value)
|
|
122
|
+
}
|
|
123
|
+
const defaultValue = defaultSettings[name]
|
|
124
|
+
const {
|
|
125
|
+
step = 1,
|
|
126
|
+
min,
|
|
127
|
+
max,
|
|
128
|
+
cls,
|
|
129
|
+
onChange = (v) => {
|
|
130
|
+
this.onChangeValue(v, name)
|
|
131
|
+
}
|
|
132
|
+
} = options
|
|
133
|
+
const opts = {
|
|
134
|
+
step,
|
|
135
|
+
value,
|
|
136
|
+
min,
|
|
137
|
+
max,
|
|
138
|
+
onChange,
|
|
139
|
+
placeholder: defaultValue
|
|
140
|
+
}
|
|
141
|
+
if (title) {
|
|
142
|
+
opts.formatter = v => `${title}${options.extraDesc || ''}: ${v}`
|
|
143
|
+
opts.parser = (v) => {
|
|
144
|
+
let vv = isNumber(v)
|
|
145
|
+
? v
|
|
146
|
+
: Number(v.split(': ')[1], 10)
|
|
147
|
+
if (isNaN(vv)) {
|
|
148
|
+
vv = defaultValue
|
|
149
|
+
}
|
|
150
|
+
return vv
|
|
151
|
+
}
|
|
152
|
+
opts.style = {
|
|
153
|
+
width: width + 'px'
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return (
|
|
157
|
+
<div className={`pd2b ${cls || ''}`}>
|
|
158
|
+
<InputNumber
|
|
159
|
+
{...opts}
|
|
160
|
+
/>
|
|
161
|
+
</div>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
renderText = (name, placeholder) => {
|
|
166
|
+
const value = this.props.config[name]
|
|
167
|
+
const defaultValue = defaultSettings[name]
|
|
168
|
+
const onChange = (e) => this.onChangeValue(e.target.value, name)
|
|
169
|
+
return (
|
|
170
|
+
<div className='pd2b'>
|
|
171
|
+
<Input
|
|
172
|
+
value={value}
|
|
173
|
+
onChange={onChange}
|
|
174
|
+
placeholder={placeholder || defaultValue}
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
renderBgOption = item => {
|
|
181
|
+
return {
|
|
182
|
+
value: item.value,
|
|
183
|
+
label: item.desc
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
renderTerminalBgSelect = (name) => {
|
|
188
|
+
const value = this.props.config[name]
|
|
189
|
+
const defaultValue = defaultSettings[name]
|
|
190
|
+
const onChange = (v) => this.onChangeValue(v, name)
|
|
191
|
+
const after = (
|
|
192
|
+
<Upload
|
|
193
|
+
beforeUpload={(file) => {
|
|
194
|
+
this.onChangeValue(file.path, name)
|
|
195
|
+
return false
|
|
196
|
+
}}
|
|
197
|
+
showUploadList={false}
|
|
198
|
+
>
|
|
199
|
+
<span>{e('chooseFile')}</span>
|
|
200
|
+
</Upload>
|
|
201
|
+
)
|
|
202
|
+
const dataSource = [
|
|
203
|
+
{
|
|
204
|
+
value: '',
|
|
205
|
+
desc: t('default')
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
value: noTerminalBgValue,
|
|
209
|
+
desc: e('noTerminalBg')
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
const numberOpts = { step: 0.05, min: 0, max: 1, cls: 'bg-img-setting' }
|
|
213
|
+
|
|
214
|
+
const renderFilter = () => {
|
|
215
|
+
if (this.props.config[name] === noTerminalBgValue) return
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<div>
|
|
219
|
+
{
|
|
220
|
+
this.renderNumber(
|
|
221
|
+
'terminalBackgroundFilterOpacity',
|
|
222
|
+
numberOpts,
|
|
223
|
+
e('Opacity')
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
{
|
|
227
|
+
this.renderNumber(
|
|
228
|
+
'terminalBackgroundFilterBlur',
|
|
229
|
+
{ ...numberOpts, min: 0, max: 50, step: 0.5 },
|
|
230
|
+
e('Blur')
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
{
|
|
234
|
+
this.renderNumber(
|
|
235
|
+
'terminalBackgroundFilterBrightness',
|
|
236
|
+
{ ...numberOpts, min: 0, max: 10, step: 0.1 },
|
|
237
|
+
e('Brightness')
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
{
|
|
241
|
+
this.renderNumber(
|
|
242
|
+
'terminalBackgroundFilterGrayscale',
|
|
243
|
+
numberOpts,
|
|
244
|
+
e('Grayscale')
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
{
|
|
248
|
+
this.renderNumber(
|
|
249
|
+
'terminalBackgroundFilterContrast',
|
|
250
|
+
{ ...numberOpts, min: 0, max: 10, step: 0.1 },
|
|
251
|
+
e('Contrast')
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
</div>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<div className='pd2b'>
|
|
260
|
+
<div className='pd1b'>
|
|
261
|
+
<Tooltip
|
|
262
|
+
title='eg: https://xx.com/xx.png or /path/to/xx.png'
|
|
263
|
+
>
|
|
264
|
+
<AutoComplete
|
|
265
|
+
value={value}
|
|
266
|
+
onChange={onChange}
|
|
267
|
+
placeholder={defaultValue}
|
|
268
|
+
className='width-100'
|
|
269
|
+
options={dataSource.map(this.renderBgOption)}
|
|
270
|
+
>
|
|
271
|
+
<Input
|
|
272
|
+
addonAfter={after}
|
|
273
|
+
/>
|
|
274
|
+
</AutoComplete>
|
|
275
|
+
</Tooltip>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
{
|
|
279
|
+
renderFilter()
|
|
280
|
+
}
|
|
281
|
+
</div>
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
renderReset = () => {
|
|
286
|
+
return (
|
|
287
|
+
<div className='pd1b pd1t'>
|
|
288
|
+
<Button
|
|
289
|
+
onClick={this.handleResetAll}
|
|
290
|
+
>
|
|
291
|
+
{e('resetAllToDefault')}
|
|
292
|
+
</Button>
|
|
293
|
+
</div>
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
renderDefaultTerminalType = () => {
|
|
298
|
+
const opts = this.props.config.terminalTypes.map(mapper)
|
|
299
|
+
return (
|
|
300
|
+
<AutoComplete
|
|
301
|
+
options={opts}
|
|
302
|
+
style={{
|
|
303
|
+
width: '200px'
|
|
304
|
+
}}
|
|
305
|
+
value={this.props.config.terminalType}
|
|
306
|
+
onChange={(v) => this.onChangeValue(v, 'terminalType')}
|
|
307
|
+
/>
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
renderCursorStyleSelect = () => {
|
|
312
|
+
const {
|
|
313
|
+
cursorStyle = 'block'
|
|
314
|
+
} = this.props.config
|
|
315
|
+
const sets = [
|
|
316
|
+
{
|
|
317
|
+
id: 'block',
|
|
318
|
+
title: '▊'
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
id: 'underline',
|
|
322
|
+
title: '_'
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
id: 'bar',
|
|
326
|
+
title: '|'
|
|
327
|
+
}
|
|
328
|
+
]
|
|
329
|
+
const props = {
|
|
330
|
+
onChange: this.handleChangeCursorStyle,
|
|
331
|
+
value: cursorStyle,
|
|
332
|
+
style: {
|
|
333
|
+
width: '100px'
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return (
|
|
337
|
+
<div className='pd1b'>
|
|
338
|
+
<span className='inline-title mg1r'>{e('cursorStyle')}</span>
|
|
339
|
+
<Select
|
|
340
|
+
{...props}
|
|
341
|
+
showSearch
|
|
342
|
+
>
|
|
343
|
+
{
|
|
344
|
+
sets.map(f => {
|
|
345
|
+
return (
|
|
346
|
+
<Option value={f.id} key={f.id}>
|
|
347
|
+
<b>{f.title}</b>
|
|
348
|
+
</Option>
|
|
349
|
+
)
|
|
350
|
+
})
|
|
351
|
+
}
|
|
352
|
+
</Select>
|
|
353
|
+
</div>
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
renderFontFamily = () => {
|
|
358
|
+
const { fonts } = this.props.store
|
|
359
|
+
const { fontFamily } = this.props.config
|
|
360
|
+
const props = {
|
|
361
|
+
mode: 'multiple',
|
|
362
|
+
onChange: this.handleChangeFont,
|
|
363
|
+
value: fontFamily.split(/, */g)
|
|
364
|
+
}
|
|
365
|
+
return (
|
|
366
|
+
<Select
|
|
367
|
+
{...props}
|
|
368
|
+
showSearch
|
|
369
|
+
>
|
|
370
|
+
{
|
|
371
|
+
fonts.map(f => {
|
|
372
|
+
return (
|
|
373
|
+
<Option value={f} key={f}>
|
|
374
|
+
<span style={{
|
|
375
|
+
fontFamily: f
|
|
376
|
+
}}
|
|
377
|
+
>{f}
|
|
378
|
+
</span>
|
|
379
|
+
</Option>
|
|
380
|
+
)
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
</Select>
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
render () {
|
|
388
|
+
const { ready } = this.state
|
|
389
|
+
if (!ready) {
|
|
390
|
+
return (
|
|
391
|
+
<div className='pd3 aligncenter'>
|
|
392
|
+
<LoadingOutlined />
|
|
393
|
+
</div>
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
const {
|
|
397
|
+
rendererType,
|
|
398
|
+
keywords = [{ color: 'red' }]
|
|
399
|
+
} = this.props.config
|
|
400
|
+
const {
|
|
401
|
+
appPath
|
|
402
|
+
} = this.props.store
|
|
403
|
+
const ps = {
|
|
404
|
+
formData: {
|
|
405
|
+
keywords
|
|
406
|
+
},
|
|
407
|
+
submit: this.handleSubmitKeywords
|
|
408
|
+
}
|
|
409
|
+
const terminalLogPath = appPath ? osResolve(appPath, 'electerm', 'session_logs') : window.et.sessionLogPath
|
|
410
|
+
return (
|
|
411
|
+
<div className='form-wrap pd1y pd2x'>
|
|
412
|
+
<div className='pd1y font16 bold'>
|
|
413
|
+
<CodeOutlined className='mg1r' />
|
|
414
|
+
{s('terminal')} {e('settings')}
|
|
415
|
+
</div>
|
|
416
|
+
{
|
|
417
|
+
this.renderNumber('scrollback', {
|
|
418
|
+
step: 200,
|
|
419
|
+
min: 1000
|
|
420
|
+
}, e('scrollBackDesc'), 400)
|
|
421
|
+
}
|
|
422
|
+
<div className='pd2b'>
|
|
423
|
+
<span className='inline-title mg1r'>{e('rendererType')}</span>
|
|
424
|
+
<Select
|
|
425
|
+
onChange={v => this.onChangeValue(v, 'rendererType')}
|
|
426
|
+
value={rendererType}
|
|
427
|
+
popupMatchSelectWidth={false}
|
|
428
|
+
>
|
|
429
|
+
{
|
|
430
|
+
Object.keys(rendererTypes).map(id => {
|
|
431
|
+
return (
|
|
432
|
+
<Option key={id} value={id}>{id}</Option>
|
|
433
|
+
)
|
|
434
|
+
})
|
|
435
|
+
}
|
|
436
|
+
</Select>
|
|
437
|
+
</div>
|
|
438
|
+
{
|
|
439
|
+
this.renderNumber('fontSize', {
|
|
440
|
+
step: 1,
|
|
441
|
+
min: 9
|
|
442
|
+
}, `${t('default')} ${e('fontSize')}`, 400)
|
|
443
|
+
}
|
|
444
|
+
<div className='pd2b'>
|
|
445
|
+
<span className='inline-title mg1r'>{t('default')} {e('fontFamily')}</span>
|
|
446
|
+
{
|
|
447
|
+
this.renderFontFamily()
|
|
448
|
+
}
|
|
449
|
+
</div>
|
|
450
|
+
<div className='pd2b'>
|
|
451
|
+
<div className='pd1b'>
|
|
452
|
+
<span className='inline-title mg1r'>{f('keywordsHighlight')}</span>
|
|
453
|
+
<HelpIcon
|
|
454
|
+
title={f('supportRegexp')}
|
|
455
|
+
/>
|
|
456
|
+
</div>
|
|
457
|
+
<KeywordForm
|
|
458
|
+
{...ps}
|
|
459
|
+
/>
|
|
460
|
+
</div>
|
|
461
|
+
<div className='pd2b'>
|
|
462
|
+
<span className='inline-title mg1r'>{e('defaultTerminalType')}</span>
|
|
463
|
+
{
|
|
464
|
+
this.renderDefaultTerminalType()
|
|
465
|
+
}
|
|
466
|
+
</div>
|
|
467
|
+
<div className='pd1b'>{e('terminalBackgroundImage')}</div>
|
|
468
|
+
{
|
|
469
|
+
this.renderTerminalBgSelect('terminalBackgroundImagePath')
|
|
470
|
+
}
|
|
471
|
+
<div className='pd1b'>{e('terminalWordSeparator')}</div>
|
|
472
|
+
{
|
|
473
|
+
this.renderText('terminalWordSeparator', e('terminalWordSeparator'))
|
|
474
|
+
}
|
|
475
|
+
{
|
|
476
|
+
this.renderCursorStyleSelect()
|
|
477
|
+
}
|
|
478
|
+
{
|
|
479
|
+
[
|
|
480
|
+
'cursorBlink',
|
|
481
|
+
'rightClickSelectsWord',
|
|
482
|
+
'pasteWhenContextMenu',
|
|
483
|
+
'copyWhenSelect',
|
|
484
|
+
'ctrlOrMetaOpenTerminalLink'
|
|
485
|
+
].map(this.renderToggle)
|
|
486
|
+
}
|
|
487
|
+
{this.renderToggle('saveTerminalLogToFile', (
|
|
488
|
+
<ShowItem to={terminalLogPath} className='mg1l'>{p('open')}</ShowItem>
|
|
489
|
+
))}
|
|
490
|
+
{this.renderReset()}
|
|
491
|
+
</div>
|
|
492
|
+
)
|
|
493
|
+
}
|
|
494
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import SettingCommon from './setting-common'
|
|
2
|
+
import SettingTerminal from './setting-terminal'
|
|
2
3
|
import SettingCol from './col'
|
|
3
4
|
import SyncSetting from '../setting-sync/setting-sync'
|
|
4
5
|
import Shortcuts from '../shortcuts/shortcuts'
|
|
@@ -6,6 +7,7 @@ import List from './list'
|
|
|
6
7
|
import {
|
|
7
8
|
settingMap,
|
|
8
9
|
settingSyncId,
|
|
10
|
+
settingTerminalId,
|
|
9
11
|
settingShortcutsId
|
|
10
12
|
} from '../../common/constants'
|
|
11
13
|
|
|
@@ -25,10 +27,12 @@ export default function TabSettings (props) {
|
|
|
25
27
|
const sid = settingItem.id
|
|
26
28
|
if (sid === settingSyncId) {
|
|
27
29
|
elem = <SyncSetting store={store} />
|
|
30
|
+
} else if (sid === settingTerminalId) {
|
|
31
|
+
elem = <SettingTerminal {...listProps} config={store.config} />
|
|
28
32
|
} else if (sid === settingShortcutsId) {
|
|
29
33
|
elem = <Shortcuts store={store} />
|
|
30
34
|
} else {
|
|
31
|
-
elem = <
|
|
35
|
+
elem = <SettingCommon {...listProps} config={store.config} />
|
|
32
36
|
}
|
|
33
37
|
return (
|
|
34
38
|
<div
|
|
@@ -94,7 +94,7 @@ export default class FileListTable extends React.Component {
|
|
|
94
94
|
style: {
|
|
95
95
|
width: w + 'px',
|
|
96
96
|
left: (w * i) + 'px',
|
|
97
|
-
zIndex: 3 + i
|
|
97
|
+
zIndex: 3 + i * 2
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
})
|
|
@@ -110,7 +110,8 @@ export default class FileListTable extends React.Component {
|
|
|
110
110
|
nextProp: properties[i + 1].name,
|
|
111
111
|
style: {
|
|
112
112
|
left: (w * (i + 1) - (splitDraggerWidth / 2)) + 'px',
|
|
113
|
-
width: splitDraggerWidth + 'px'
|
|
113
|
+
width: splitDraggerWidth + 'px',
|
|
114
|
+
zIndex: 4 + i * 2
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export class KeywordHighlighterAddon {
|
|
2
|
+
constructor (keywords) {
|
|
3
|
+
this.keywords = keywords
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
escape = str => {
|
|
7
|
+
return str.replace(/\\x1B/g, '\\x1B')
|
|
8
|
+
.replace(/\033/g, '\\033')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
colorize = (color) => {
|
|
12
|
+
// Use a switch statement to map color names to ANSI codes
|
|
13
|
+
switch (color) {
|
|
14
|
+
case 'green':
|
|
15
|
+
return '\u001b[32m$&\u001b[0m'
|
|
16
|
+
case 'yellow':
|
|
17
|
+
return '\u001b[33m$&\u001b[0m'
|
|
18
|
+
case 'blue':
|
|
19
|
+
return '\u001b[34m$&\u001b[0m'
|
|
20
|
+
case 'magenta':
|
|
21
|
+
return '\u001b[35m$&\u001b[0m'
|
|
22
|
+
case 'cyan':
|
|
23
|
+
return '\u001b[36m$&\u001b[0m'
|
|
24
|
+
case 'white':
|
|
25
|
+
return '\u001b[37m$&\u001b[0m'
|
|
26
|
+
default:
|
|
27
|
+
return '\u001b[31m$&\u001b[0m'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
highlightKeywords = (text) => {
|
|
32
|
+
console.log('this.keywords', this.keywords)
|
|
33
|
+
for (const obj of this.keywords) {
|
|
34
|
+
const {
|
|
35
|
+
keyword,
|
|
36
|
+
color = 'red'
|
|
37
|
+
} = obj || {}
|
|
38
|
+
if (keyword) {
|
|
39
|
+
const regex = new RegExp(`(${keyword})`, 'gi')
|
|
40
|
+
text = text.replace(regex, this.colorize(color))
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return text
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
activate (terminal) {
|
|
47
|
+
this.terminal = terminal
|
|
48
|
+
// Override the write method to automatically highlight keywords
|
|
49
|
+
const originalWrite = terminal.write
|
|
50
|
+
terminal.write = (data) => {
|
|
51
|
+
originalWrite.call(
|
|
52
|
+
terminal,
|
|
53
|
+
terminal.displayRaw ? this.escape(data) : this.highlightKeywords(data)
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
this.originalWrite = originalWrite
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
dispose () {
|
|
60
|
+
// Restore the original write method when disposing the addon
|
|
61
|
+
this.terminal.write = this.originalWrite
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -51,6 +51,7 @@ import NormalBuffer from './normal-buffer'
|
|
|
51
51
|
import { createTerm, resizeTerm } from './terminal-apis'
|
|
52
52
|
import createLsId from './build-ls-term-id'
|
|
53
53
|
import { shortcutExtend, shortcutDescExtend } from '../shortcuts/shortcut-handler.js'
|
|
54
|
+
import { KeywordHighlighterAddon } from './highlight-addon.js'
|
|
54
55
|
|
|
55
56
|
const { prefix } = window
|
|
56
57
|
const e = prefix('ssh')
|
|
@@ -820,10 +821,6 @@ class Term extends Component {
|
|
|
820
821
|
term.onData(this.onData)
|
|
821
822
|
this.term = term
|
|
822
823
|
term.attachCustomKeyEventHandler(this.handleKeyboardEvent.bind(this))
|
|
823
|
-
term.onKey(this.onKey)
|
|
824
|
-
// if (host && !password && !privateKey) {
|
|
825
|
-
// return this.promote()
|
|
826
|
-
// }
|
|
827
824
|
await this.remoteInit(term)
|
|
828
825
|
}
|
|
829
826
|
|
|
@@ -905,7 +902,13 @@ class Term extends Component {
|
|
|
905
902
|
})
|
|
906
903
|
const { cols, rows } = term
|
|
907
904
|
const { config } = this.props
|
|
908
|
-
const {
|
|
905
|
+
const {
|
|
906
|
+
host,
|
|
907
|
+
port,
|
|
908
|
+
tokenElecterm,
|
|
909
|
+
keywords = [],
|
|
910
|
+
server = ''
|
|
911
|
+
} = config
|
|
909
912
|
const { sessionId, terminalIndex, id, logName } = this.props
|
|
910
913
|
const tab = deepCopy(this.props.tab || {})
|
|
911
914
|
const {
|
|
@@ -1025,46 +1028,14 @@ class Term extends Component {
|
|
|
1025
1028
|
noTerminalWriteOutsideSession: true
|
|
1026
1029
|
}, this)
|
|
1027
1030
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
const th = this
|
|
1033
|
-
term.write = function (data) {
|
|
1034
|
-
// let str = ''
|
|
1035
|
-
// if (typeof data === 'object') {
|
|
1036
|
-
// if (data instanceof ArrayBuffer) {
|
|
1037
|
-
// str = th.decoder.decode(data)
|
|
1038
|
-
// oldWrite.call(term, th.escape(str))
|
|
1039
|
-
// } else {
|
|
1040
|
-
// const fileReader = new FileReader()
|
|
1041
|
-
// fileReader.addEventListener('load', () => {
|
|
1042
|
-
// str = th.decoder.decode(fileReader.result)
|
|
1043
|
-
// oldWrite.call(term, th.escape(str))
|
|
1044
|
-
// })
|
|
1045
|
-
// fileReader.readAsArrayBuffer(new window.Blob([data]))
|
|
1046
|
-
// }
|
|
1047
|
-
// } else if (typeof data === 'string') {
|
|
1048
|
-
// oldWrite.call(term, th.escape(data))
|
|
1049
|
-
// } else {
|
|
1050
|
-
// throw Error(`Cannot handle ${typeof data} websocket message.`)
|
|
1051
|
-
// }
|
|
1052
|
-
oldWrite.call(term, th.escape(data))
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1031
|
+
term.displayRaw = displayRaw
|
|
1032
|
+
term.loadAddon(
|
|
1033
|
+
new KeywordHighlighterAddon(keywords)
|
|
1034
|
+
)
|
|
1055
1035
|
this.term = term
|
|
1056
1036
|
window.store.triggerResize()
|
|
1057
1037
|
}
|
|
1058
1038
|
|
|
1059
|
-
escape = str => {
|
|
1060
|
-
return str.replace(/\\x1B/g, '\\x1B')
|
|
1061
|
-
.replace(/\033/g, '\\033')
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
onKey = (key, e) => {
|
|
1065
|
-
// log.log('onKey', key, e)
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
1039
|
onResize = throttle(() => {
|
|
1069
1040
|
const cid = this.props.currentTabId
|
|
1070
1041
|
const tid = this.props.tab?.id
|
package/client/store/index.js
CHANGED
|
@@ -35,7 +35,8 @@ import {
|
|
|
35
35
|
sidebarPanelWidth,
|
|
36
36
|
paneMap,
|
|
37
37
|
settingSyncId,
|
|
38
|
-
settingShortcutsId
|
|
38
|
+
settingShortcutsId,
|
|
39
|
+
settingTerminalId
|
|
39
40
|
} from '../common/constants'
|
|
40
41
|
import getInitItem from '../common/init-setting-item'
|
|
41
42
|
import {
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
const { prefix } = window
|
|
46
47
|
const ss = prefix('settingSync')
|
|
47
48
|
const s = prefix('setting')
|
|
49
|
+
const sss = prefix('ssh')
|
|
48
50
|
|
|
49
51
|
function getReverseColor (hex) {
|
|
50
52
|
// Check if the input is a valid hex color code
|
|
@@ -248,12 +250,16 @@ class Store {
|
|
|
248
250
|
get setting () {
|
|
249
251
|
return [
|
|
250
252
|
{
|
|
251
|
-
id:
|
|
252
|
-
title:
|
|
253
|
+
id: settingTerminalId,
|
|
254
|
+
title: sss('terminal')
|
|
253
255
|
},
|
|
254
256
|
{
|
|
255
257
|
id: settingShortcutsId,
|
|
256
258
|
title: s('settingShortcuts')
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: settingSyncId,
|
|
262
|
+
title: ss('settingSync')
|
|
257
263
|
}
|
|
258
264
|
]
|
|
259
265
|
}
|
package/client/store/setting.js
CHANGED
|
@@ -11,6 +11,7 @@ import copy from 'json-deep-copy'
|
|
|
11
11
|
import {
|
|
12
12
|
settingMap,
|
|
13
13
|
settingCommonId,
|
|
14
|
+
settingSyncId,
|
|
14
15
|
modals
|
|
15
16
|
} from '../common/constants'
|
|
16
17
|
import { buildNewTheme } from '../common/terminal-theme'
|
|
@@ -150,7 +151,7 @@ export default Store => {
|
|
|
150
151
|
store.storeAssign({
|
|
151
152
|
settingTab: settingMap.setting
|
|
152
153
|
})
|
|
153
|
-
store.setSettingItem(copy(store.setting
|
|
154
|
+
store.setSettingItem(copy(store.setting.find(d => d.id === settingSyncId)))
|
|
154
155
|
store.openSettingModal()
|
|
155
156
|
}
|
|
156
157
|
|