@electerm/electerm-react 1.39.119 → 1.40.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.
Files changed (30) hide show
  1. package/client/common/constants.js +0 -1
  2. package/client/components/bookmark-form/color-picker.jsx +18 -9
  3. package/client/components/bookmark-form/form-ssh-common.jsx +4 -2
  4. package/client/components/bookmark-form/profile-form-item.jsx +43 -0
  5. package/client/components/bookmark-form/rdp-form-ui.jsx +10 -9
  6. package/client/components/bookmark-form/render-auth-ssh.jsx +6 -3
  7. package/client/components/bookmark-form/render-profile-item.jsx +0 -0
  8. package/client/components/bookmark-form/telnet-form-ui.jsx +19 -40
  9. package/client/components/bookmark-form/vnc-form-ui.jsx +6 -1
  10. package/client/components/bookmark-form/web-form-ui.jsx +1 -1
  11. package/client/components/profile/profile-form-elem.jsx +14 -28
  12. package/client/components/profile/profile-form-rdp.jsx +31 -0
  13. package/client/components/profile/profile-form-ssh.jsx +41 -0
  14. package/client/components/profile/profile-form-telnet.jsx +35 -0
  15. package/client/components/profile/profile-form-vnc.jsx +31 -0
  16. package/client/components/profile/profile-tabs.jsx +32 -0
  17. package/client/components/rdp/rdp-session.jsx +3 -9
  18. package/client/components/setting-panel/tab-themes.jsx +2 -2
  19. package/client/components/terminal/attach-addon-custom.js +11 -11
  20. package/client/components/terminal/index.jsx +37 -3
  21. package/client/components/theme/theme-edit-slot.jsx +28 -0
  22. package/client/components/theme/theme-editor.jsx +37 -0
  23. package/client/components/{terminal-theme/index.jsx → theme/theme-form.jsx} +103 -34
  24. package/client/components/theme/theme-form.styl +10 -0
  25. package/client/components/vnc/vnc-session.jsx +2 -1
  26. package/client/store/common.js +25 -4
  27. package/client/store/sync.js +0 -3
  28. package/package.json +1 -1
  29. /package/client/components/{terminal-theme → theme}/terminal-theme-list.styl +0 -0
  30. /package/client/components/{terminal-theme → theme}/theme-list.jsx +0 -0
@@ -324,7 +324,6 @@ export const sshTunnelHelpLink = 'https://github.com/electerm/electerm/wiki/How-
324
324
  export const batchOpHelpLink = 'https://github.com/electerm/electerm/wiki/batch-operation'
325
325
  export const proxyHelpLink = 'https://github.com/electerm/electerm/wiki/proxy-format'
326
326
  export const regexHelpLink = 'https://github.com/electerm/electerm/wiki/Terminal-keywords-highlight-regular-expression-exmaples'
327
- export const rdpHelpLink = 'https://github.com/electerm/electerm/wiki/RDP-limitation'
328
327
  export const modals = {
329
328
  hide: 0,
330
329
  setting: 1,
@@ -1,6 +1,6 @@
1
1
  import React, { useState } from 'react'
2
2
  import { Popover } from 'antd'
3
- import { HexColorPicker } from 'react-colorful'
3
+ import { HexColorPicker, RgbaColorPicker } from 'react-colorful'
4
4
  import { defaultColors, getRandomHexColor } from '../../common/rand-hex-color.js'
5
5
  import { HexInput } from './hex-input.jsx'
6
6
  import './color-picker.styl'
@@ -20,6 +20,7 @@ export const ColorPicker = React.forwardRef((props, ref) => {
20
20
  }
21
21
 
22
22
  function renderContent () {
23
+ const Picker = props.isRgba ? RgbaColorPicker : HexColorPicker
23
24
  return (
24
25
  <div className='color-picker-box'>
25
26
  <div className='fix'>
@@ -55,7 +56,7 @@ export const ColorPicker = React.forwardRef((props, ref) => {
55
56
  }
56
57
  </div>
57
58
  <div className='fright'>
58
- <HexColorPicker
59
+ <Picker
59
60
  color={value}
60
61
  onChange={handleChange}
61
62
  />
@@ -71,6 +72,20 @@ export const ColorPicker = React.forwardRef((props, ref) => {
71
72
  )
72
73
  }
73
74
 
75
+ const inner = (
76
+ <div
77
+ ref={ref}
78
+ className='color-picker-choose'
79
+ style={{
80
+ backgroundColor: value
81
+ }}
82
+ />
83
+ )
84
+
85
+ if (props.disabled) {
86
+ return inner
87
+ }
88
+
74
89
  return (
75
90
  <Popover
76
91
  content={renderContent()}
@@ -79,13 +94,7 @@ export const ColorPicker = React.forwardRef((props, ref) => {
79
94
  placement='bottomLeft'
80
95
  onOpenChange={handleVisibleChange}
81
96
  >
82
- <div
83
- ref={ref}
84
- className='color-picker-choose'
85
- style={{
86
- backgroundColor: value
87
- }}
88
- />
97
+ {inner}
89
98
  </Popover>
90
99
  )
91
100
  })
@@ -36,9 +36,11 @@ export default function renderCommon (props) {
36
36
  bookmarkGroups = [],
37
37
  ips,
38
38
  form,
39
- onChangeAuthType
39
+ onChangeAuthType,
40
+ filterAuthType = a => a
40
41
  } = props
41
42
  const tree = formatBookmarkGroups(bookmarkGroups)
43
+ const authTypesFiltered = authTypes.filter(filterAuthType)
42
44
 
43
45
  // ips is ipaddress string[]
44
46
  function renderIps () {
@@ -115,7 +117,7 @@ export default function renderCommon (props) {
115
117
  buttonStyle='solid'
116
118
  >
117
119
  {
118
- authTypes.map(t => {
120
+ authTypesFiltered.map(t => {
119
121
  return (
120
122
  <RadioButton value={t} key={t}>
121
123
  {e(t)}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * bookmark form
3
+ */
4
+ import {
5
+ Form,
6
+ Select
7
+ } from 'antd'
8
+ import { formItemLayout } from '../../common/form-layout'
9
+ import './bookmark-form.styl'
10
+
11
+ const FormItem = Form.Item
12
+ const e = window.translate
13
+
14
+ export default function ProfileItem (props) {
15
+ const {
16
+ store,
17
+ profileFilter = (d) => d
18
+ } = props
19
+ const opts = {
20
+ options: store.profiles
21
+ .filter(profileFilter)
22
+ .map(d => {
23
+ return {
24
+ label: d.name,
25
+ value: d.id
26
+ }
27
+ }),
28
+ placeholder: e('profiles'),
29
+ allowClear: true
30
+ }
31
+ return (
32
+ <FormItem
33
+ {...formItemLayout}
34
+ label={e('profiles')}
35
+ name='profile'
36
+ hasFeedback
37
+ >
38
+ <Select
39
+ {...opts}
40
+ />
41
+ </FormItem>
42
+ )
43
+ }
@@ -12,17 +12,16 @@ import {
12
12
  import { formItemLayout } from '../../common/form-layout'
13
13
  import {
14
14
  newBookmarkIdPrefix,
15
- terminalRdpType,
16
- rdpHelpLink
15
+ terminalRdpType
17
16
  } from '../../common/constants'
18
17
  import useSubmit from './use-submit'
19
18
  import copy from 'json-deep-copy'
20
- import Link from '../common/external-link.jsx'
21
- import { defaults } from 'lodash-es'
19
+ import { defaults, isEmpty } from 'lodash-es'
22
20
  import { ColorPickerItem } from './color-picker-item.jsx'
23
21
  import { getRandomDefaultColor } from '../../common/rand-hex-color.js'
24
22
  import formatBookmarkGroups from './bookmark-group-tree-format'
25
23
  import findBookmarkGroupId from '../../common/find-bookmark-group-id'
24
+ import ProfileItem from './profile-form-item'
26
25
 
27
26
  const FormItem = Form.Item
28
27
  const e = window.translate
@@ -56,6 +55,7 @@ export default function RdpFormUi (props) {
56
55
  port: 3389,
57
56
  category: initBookmarkGroupId,
58
57
  color: getRandomDefaultColor()
58
+
59
59
  }
60
60
  initialValues = defaults(initialValues, defaultValues)
61
61
  function renderCommon () {
@@ -65,9 +65,6 @@ export default function RdpFormUi (props) {
65
65
  const tree = formatBookmarkGroups(bookmarkGroups)
66
66
  return (
67
67
  <div className='pd1x'>
68
- <p className='alignright'>
69
- <Link to={rdpHelpLink}>Wiki: {rdpHelpLink}</Link>
70
- </p>
71
68
  <FormItem
72
69
  {...formItemLayout}
73
70
  label={e('title')}
@@ -102,11 +99,15 @@ export default function RdpFormUi (props) {
102
99
  step={1}
103
100
  />
104
101
  </FormItem>
102
+ <ProfileItem
103
+ store={props.store}
104
+ profileFilter={d => !isEmpty(d.rdp)}
105
+ />
105
106
  <FormItem
106
107
  {...formItemLayout}
107
- label={e('username')}
108
+ label={e('userName')}
108
109
  hasFeedback
109
- name='username'
110
+ name='userName'
110
111
  required
111
112
  >
112
113
  <Input />
@@ -21,7 +21,9 @@ export default function renderAuth (props) {
21
21
  const {
22
22
  store,
23
23
  form,
24
- authType
24
+ authType,
25
+ formItemName = 'password',
26
+ profileFilter = (d) => d
25
27
  } = props
26
28
  const beforeUpload = async (file) => {
27
29
  const privateKey = await window.fs.readFile(file.path)
@@ -50,7 +52,7 @@ export default function renderAuth (props) {
50
52
  <FormItem
51
53
  {...formItemLayout}
52
54
  label={e('password')}
53
- name='password'
55
+ name={formItemName}
54
56
  hasFeedback
55
57
  rules={[{
56
58
  max: 1024, message: '1024 chars max'
@@ -67,6 +69,7 @@ export default function renderAuth (props) {
67
69
  if (authType === 'profiles') {
68
70
  const opts = {
69
71
  options: store.profiles
72
+ .filter(profileFilter)
70
73
  .map(d => {
71
74
  return {
72
75
  label: d.name,
@@ -74,7 +77,7 @@ export default function renderAuth (props) {
74
77
  }
75
78
  }),
76
79
  placeholder: e('profiles'),
77
- allowClear: false
80
+ allowClear: true
78
81
  }
79
82
  return (
80
83
  <FormItem
@@ -1,16 +1,17 @@
1
1
  /**
2
2
  * bookmark form
3
3
  */
4
- import { useEffect } from 'react'
4
+
5
5
  import {
6
6
  Input,
7
7
  Tabs,
8
- AutoComplete,
9
8
  Form
10
9
  } from 'antd'
10
+ import { useState, useEffect } from 'react'
11
11
  import {
12
12
  newBookmarkIdPrefix,
13
- terminalTelnetType
13
+ terminalTelnetType,
14
+ authTypeMap
14
15
  } from '../../common/constants'
15
16
  import { formItemLayout } from '../../common/form-layout'
16
17
  import findBookmarkGroupId from '../../common/find-bookmark-group-id'
@@ -21,7 +22,8 @@ import useQm from './use-quick-commands'
21
22
  import renderCommon from './form-ssh-common'
22
23
  import { getRandomDefaultColor } from '../../common/rand-hex-color.js'
23
24
  import copy from 'json-deep-copy'
24
- import { defaultsDeep, uniqBy } from 'lodash-es'
25
+ import { defaultsDeep, isEmpty } from 'lodash-es'
26
+ import renderAuth from './render-auth-ssh'
25
27
  import './bookmark-form.styl'
26
28
 
27
29
  const FormItem = Form.Item
@@ -33,6 +35,7 @@ export default function TelnetFormUI (props) {
33
35
  handleFinish,
34
36
  submitUi
35
37
  ] = useSubmit(props)
38
+ const [authType, setAuthType] = useState(props.formData.authType || authTypeMap.password)
36
39
  useEffect(() => {
37
40
  if ((props.formData.id || '').startsWith(newBookmarkIdPrefix)) {
38
41
  form.setFieldsValue({
@@ -53,6 +56,9 @@ export default function TelnetFormUI (props) {
53
56
  ? findBookmarkGroupId(bookmarkGroups, id)
54
57
  : currentBookmarkGroupId
55
58
  let initialValues = copy(props.formData)
59
+ function onChangeAuthType (e) {
60
+ setAuthType(e.target.value)
61
+ }
56
62
  const defaultValues = {
57
63
  port: 23,
58
64
  id: '',
@@ -63,47 +69,20 @@ export default function TelnetFormUI (props) {
63
69
  term: defaultSettings.terminalType,
64
70
  displayRaw: false,
65
71
  type: terminalTelnetType,
66
- category: initBookmarkGroupId
72
+ category: initBookmarkGroupId,
73
+ authType: authTypeMap.password
67
74
  }
68
75
  initialValues = defaultsDeep(initialValues, defaultValues)
69
- function renderAuth () {
70
- const opts = {
71
- options: uniqBy(
72
- props.store.bookmarks
73
- .filter(d => d.password),
74
- (d) => d.password
75
- )
76
- .map(d => {
77
- return {
78
- label: `${d.title ? `(${d.title})` : ''}${d.username || ''}:${d.host}-******`,
79
- value: d.password
80
- }
81
- }),
82
- placeholder: e('password'),
83
- allowClear: false
84
- }
85
- return (
86
- <FormItem
87
- {...formItemLayout}
88
- label={e('password')}
89
- name='password'
90
- hasFeedback
91
- rules={[{
92
- max: 1024, message: '1024 chars max'
93
- }]}
94
- >
95
- <AutoComplete
96
- {...opts}
97
- >
98
- <Input.Password />
99
- </AutoComplete>
100
- </FormItem>
101
- )
102
- }
76
+
103
77
  const tprops = {
104
78
  ...props,
105
79
  renderAuth,
106
- form
80
+ authType,
81
+ onChangeAuthType,
82
+ form,
83
+ bookmarkType: terminalTelnetType,
84
+ filterAuthType: a => a !== 'privateKey',
85
+ profileFilter: d => !isEmpty(d.telnet)
107
86
  }
108
87
 
109
88
  function renderTabs () {
@@ -18,13 +18,14 @@ import {
18
18
  } from '../../common/constants'
19
19
  import useSubmit from './use-submit'
20
20
  import copy from 'json-deep-copy'
21
- import { defaults } from 'lodash-es'
21
+ import { defaults, isEmpty } from 'lodash-es'
22
22
  import { ColorPickerItem } from './color-picker-item.jsx'
23
23
  import { getRandomDefaultColor } from '../../common/rand-hex-color.js'
24
24
  import formatBookmarkGroups from './bookmark-group-tree-format'
25
25
  import findBookmarkGroupId from '../../common/find-bookmark-group-id'
26
26
  import renderProxy from './proxy'
27
27
  import ConnectionHopping from './render-connection-hopping.jsx'
28
+ import ProfileItem from './profile-form-item'
28
29
 
29
30
  const FormItem = Form.Item
30
31
  const e = window.translate
@@ -152,6 +153,10 @@ export default function VncFormUi (props) {
152
153
  >
153
154
  <Switch />
154
155
  </FormItem>
156
+ <ProfileItem
157
+ store={props.store}
158
+ profileFilter={d => !isEmpty(d.vnc)}
159
+ />
155
160
  <FormItem
156
161
  {...formItemLayout}
157
162
  label={e('username')}
@@ -77,7 +77,7 @@ export default function LocalFormUi (props) {
77
77
  name='url'
78
78
  required
79
79
  >
80
- <Input addonBefore={<ColorPickerItem />} />
80
+ <Input />
81
81
  </FormItem>
82
82
  <FormItem
83
83
  {...formItemLayout}
@@ -1,20 +1,23 @@
1
+ import { useState } from 'react'
1
2
  import {
2
3
  Form,
3
4
  message,
4
- Button,
5
- Input
5
+ Button
6
6
  } from 'antd'
7
7
  import InputAutoFocus from '../common/input-auto-focus'
8
- import renderAuth from '../bookmark-form/render-auth-ssh'
9
8
  import { formItemLayout } from '../../common/form-layout'
10
9
  import {
11
10
  settingMap
12
11
  } from '../../common/constants'
12
+
13
+ import ProfileTabs from './profile-tabs'
14
+
13
15
  const FormItem = Form.Item
14
16
  const e = window.translate
15
17
 
16
- export default function QuickCommandForm (props) {
18
+ export default function ProfileFormElem (props) {
17
19
  const [form] = Form.useForm()
20
+ const [activeTab, setActiveTab] = useState('ssh')
18
21
  const { autofocustrigger, profiles } = props.store
19
22
  function genId () {
20
23
  let count = profiles.length ? profiles.length : ''
@@ -42,6 +45,12 @@ export default function QuickCommandForm (props) {
42
45
  }
43
46
  message.success(e('saved'))
44
47
  }
48
+ const tabsProps = {
49
+ activeTab,
50
+ onChangeTab: setActiveTab,
51
+ form,
52
+ store: props.store
53
+ }
45
54
  return (
46
55
  <Form
47
56
  form={form}
@@ -67,30 +76,7 @@ export default function QuickCommandForm (props) {
67
76
  autofocustrigger={autofocustrigger}
68
77
  />
69
78
  </FormItem>
70
- <FormItem
71
- {...formItemLayout}
72
- label={e('username')}
73
- hasFeedback
74
- name='username'
75
- rules={[{
76
- max: 128, message: '128 chars max'
77
- }]}
78
- >
79
- <Input />
80
- </FormItem>
81
- {
82
- renderAuth({
83
- store: props.store,
84
- form,
85
- authType: 'password'
86
- })
87
- }
88
- {
89
- renderAuth({
90
- store: props.store,
91
- form
92
- })
93
- }
79
+ <ProfileTabs {...tabsProps} />
94
80
  <FormItem>
95
81
  <Button
96
82
  type='primary'
@@ -0,0 +1,31 @@
1
+ import {
2
+ Form,
3
+ Input
4
+ } from 'antd'
5
+ import { formItemLayout } from '../../common/form-layout'
6
+
7
+ const FormItem = Form.Item
8
+ const e = window.translate
9
+
10
+ export default function ProfileFormRdp (props) {
11
+ return (
12
+ <>
13
+ <FormItem
14
+ {...formItemLayout}
15
+ label={e('username')}
16
+ hasFeedback
17
+ name={['rdp', 'userName']}
18
+ >
19
+ <Input />
20
+ </FormItem>
21
+ <FormItem
22
+ {...formItemLayout}
23
+ label={e('password')}
24
+ hasFeedback
25
+ name={['rdp', 'password']}
26
+ >
27
+ <Input.Password />
28
+ </FormItem>
29
+ </>
30
+ )
31
+ }
@@ -0,0 +1,41 @@
1
+ import {
2
+ Form,
3
+ Input
4
+ } from 'antd'
5
+ import renderAuth from '../bookmark-form/render-auth-ssh'
6
+ import { formItemLayout } from '../../common/form-layout'
7
+
8
+ const FormItem = Form.Item
9
+ const e = window.translate
10
+
11
+ export default function ProfileFormSsh (props) {
12
+ const { form } = props.store
13
+ return (
14
+ <>
15
+ <FormItem
16
+ {...formItemLayout}
17
+ label={e('username')}
18
+ hasFeedback
19
+ name='username'
20
+ rules={[{
21
+ max: 128, message: '128 chars max'
22
+ }]}
23
+ >
24
+ <Input />
25
+ </FormItem>
26
+ {
27
+ renderAuth({
28
+ store: props.store,
29
+ form,
30
+ authType: 'password'
31
+ })
32
+ }
33
+ {
34
+ renderAuth({
35
+ store: props.store,
36
+ form
37
+ })
38
+ }
39
+ </>
40
+ )
41
+ }
@@ -0,0 +1,35 @@
1
+ import {
2
+ Form,
3
+ Input
4
+ } from 'antd'
5
+ import { formItemLayout } from '../../common/form-layout'
6
+ import renderAuth from '../bookmark-form/render-auth-ssh'
7
+
8
+ const FormItem = Form.Item
9
+ const e = window.translate
10
+
11
+ export default function ProfileFormTelnet (props) {
12
+ return (
13
+ <>
14
+ <FormItem
15
+ {...formItemLayout}
16
+ label={e('username')}
17
+ hasFeedback
18
+ name={['telnet', 'username']}
19
+ rules={[{
20
+ max: 128, message: '128 chars max'
21
+ }]}
22
+ >
23
+ <Input />
24
+ </FormItem>
25
+ {
26
+ renderAuth({
27
+ store: props.store,
28
+ form: props.form,
29
+ authType: 'password',
30
+ formItemName: ['telnet', 'password']
31
+ })
32
+ }
33
+ </>
34
+ )
35
+ }
@@ -0,0 +1,31 @@
1
+ import {
2
+ Form,
3
+ Input
4
+ } from 'antd'
5
+ import { formItemLayout } from '../../common/form-layout'
6
+
7
+ const FormItem = Form.Item
8
+ const e = window.translate
9
+
10
+ export default function ProfileFormVnc (props) {
11
+ return (
12
+ <>
13
+ <FormItem
14
+ {...formItemLayout}
15
+ label={e('username')}
16
+ hasFeedback
17
+ name={['vnc', 'username']}
18
+ >
19
+ <Input />
20
+ </FormItem>
21
+ <FormItem
22
+ {...formItemLayout}
23
+ label={e('password')}
24
+ hasFeedback
25
+ name={['vnc', 'password']}
26
+ >
27
+ <Input.Password />
28
+ </FormItem>
29
+ </>
30
+ )
31
+ }
@@ -0,0 +1,32 @@
1
+ import { Tabs } from 'antd'
2
+ import ProfileFormSsh from './profile-form-ssh'
3
+ import ProfileFormRdp from './profile-form-rdp'
4
+ import ProfileFormVnc from './profile-form-vnc'
5
+ import ProfileFormTelnet from './profile-form-telnet'
6
+
7
+ const { TabPane } = Tabs
8
+
9
+ export default function ProfileTabs (props) {
10
+ const { activeTab, onChangeTab, form, store } = props
11
+ const tabsProps = {
12
+ activeKey: activeTab,
13
+ onChange: onChangeTab
14
+ }
15
+ return (
16
+ <Tabs {...tabsProps}>
17
+ <TabPane tab='ssh' key='ssh' forceRender>
18
+ <ProfileFormSsh form={form} store={store} />
19
+ </TabPane>
20
+ <TabPane tab='telnet' key='telnet' forceRender>
21
+ <ProfileFormTelnet form={form} store={store} />
22
+ </TabPane>
23
+ <TabPane tab='vnc' key='vnc' forceRender>
24
+ <ProfileFormVnc />
25
+ </TabPane>
26
+ <TabPane tab='rdp' key='rdp' forceRender>
27
+ <ProfileFormRdp />
28
+ </TabPane>
29
+ </Tabs>
30
+
31
+ )
32
+ }
@@ -4,8 +4,7 @@ import deepCopy from 'json-deep-copy'
4
4
  import clone from '../../common/to-simple-obj'
5
5
  import { handleErr } from '../../common/fetch'
6
6
  import {
7
- statusMap,
8
- rdpHelpLink
7
+ statusMap
9
8
  } from '../../common/constants'
10
9
  import {
11
10
  notification,
@@ -17,7 +16,6 @@ import {
17
16
  ReloadOutlined,
18
17
  EditOutlined
19
18
  } from '@ant-design/icons'
20
- import HelpIcon from '../common/help-icon'
21
19
  import * as ls from '../../common/safe-local-storage'
22
20
  import scanCode from './code-scan'
23
21
  import resolutions from './resolutions'
@@ -84,7 +82,7 @@ export default class RdpSession extends Component {
84
82
  server = ''
85
83
  } = config
86
84
  const { sessionId, id } = this.props
87
- const tab = deepCopy(this.props.tab || {})
85
+ const tab = window.store.applyProfile(deepCopy(this.props.tab || {}))
88
86
  const {
89
87
  type,
90
88
  term: terminalType
@@ -347,11 +345,7 @@ export default class RdpSession extends Component {
347
345
  }
348
346
 
349
347
  renderHelp = () => {
350
- return (
351
- <HelpIcon
352
- link={rdpHelpLink}
353
- />
354
- )
348
+ return null
355
349
  }
356
350
 
357
351
  renderControl = () => {
@@ -1,6 +1,6 @@
1
1
  import SettingCol from './col'
2
- import TerminalThemeForm from '../terminal-theme'
3
- import TerminalThemeList from '../terminal-theme/theme-list'
2
+ import TerminalThemeForm from '../theme/theme-form'
3
+ import TerminalThemeList from '../theme/theme-list'
4
4
  import {
5
5
  settingMap
6
6
  } from '../../common/constants'
@@ -2,7 +2,7 @@
2
2
  * customize AttachAddon
3
3
  */
4
4
  import { AttachAddon } from 'xterm-addon-attach'
5
- import strip from '@electerm/strip-ansi'
5
+ import regEscape from 'escape-string-regexp'
6
6
 
7
7
  export default class AttachAddonCustom extends AttachAddon {
8
8
  constructor (term, socket, isWindowsShell) {
@@ -63,16 +63,16 @@ export default class AttachAddonCustom extends AttachAddon {
63
63
  const nss = str.split('\r')
64
64
  const nnss = []
65
65
  for (const str1 of nss) {
66
- const ns = strip(str1).trim()
67
- if (ns.includes(cwdId) && ns.includes('$PWD')) {
68
- nnss.push(str1.replace(`echo "${cwdId}$PWD"`, ''))
69
- } else if (
70
- (cwdId && ns.startsWith(cwdId))
71
- ) {
72
- delete term.cwdId
73
- const cwd = ns.replace(cwdId, '').trim()
74
- term.parent.setCwd(cwd)
75
- nnss.push('\x1b[2A\x1b[0J')
66
+ const ns = str1.trim()
67
+ if (cwdId) {
68
+ const cwdIdEscaped = regEscape(cwdId)
69
+ const dirRegex = new RegExp(`${cwdIdEscaped}([^\\n]+?)${cwdIdEscaped}`, 'g')
70
+ if (ns.match(dirRegex)) {
71
+ const cwd = dirRegex.exec(ns)[1].trim()
72
+ if (cwd === '~' || cwd === '%d' || cwd === '%/' || cwd === '$PWD') term.parent.setCwd('')
73
+ else term.parent.setCwd(cwd)
74
+ nnss.push(ns.replaceAll(dirRegex, ''))
75
+ } else nnss.push(str1)
76
76
  } else {
77
77
  nnss.push(str1)
78
78
  }
@@ -125,6 +125,34 @@ class Term extends Component {
125
125
  if (themeChanged) {
126
126
  this.term.options.theme = deepCopy(this.props.themeConfig)
127
127
  }
128
+
129
+ const sftpPathFollowSshChanged = !isEqual(
130
+ this.props.sftpPathFollowSsh,
131
+ prevProps.sftpPathFollowSsh
132
+ )
133
+
134
+ if (sftpPathFollowSshChanged) {
135
+ const ps1Cmd = `\recho $0|grep csh >/dev/null && set prompt_bak="$prompt" && set prompt="$prompt${cwdId}%/${cwdId}"\r
136
+ echo $0|grep zsh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'${cwdId}%d${cwdId}'\r
137
+ echo $0|grep ash >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
138
+ echo $0|grep ksh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
139
+ echo $0|grep '^sh' >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
140
+ clear\r`
141
+ const ps1RestoreCmd = `\recho $0|grep csh >/dev/null && set prompt="$prompt_bak"\r
142
+ echo $0|grep zsh >/dev/null && PS1="$PS1_bak"\r
143
+ echo $0|grep ash >/dev/null && PS1="$PS1_bak"\r
144
+ echo $0|grep ksh >/dev/null && PS1="$PS1_bak"\r
145
+ echo $0|grep '^sh' >/dev/null && PS1="$PS1_bak"\r
146
+ clear\r`
147
+
148
+ if (this.props.sftpPathFollowSsh) {
149
+ this.socket.send(ps1Cmd)
150
+ this.term.cwdId = cwdId
151
+ } else {
152
+ this.socket.send(ps1RestoreCmd)
153
+ delete this.term.cwdId
154
+ }
155
+ }
128
156
  }
129
157
 
130
158
  componentWillUnmount () {
@@ -908,11 +936,17 @@ class Term extends Component {
908
936
  getCwd = () => {
909
937
  if (
910
938
  this.props.sftpPathFollowSsh &&
911
- this.term.buffer.active.type !== 'alternate'
939
+ this.term.buffer.active.type !== 'alternate' && !this.term.cwdId
912
940
  ) {
913
- const cmd = `\recho "${cwdId}$PWD"\r`
914
941
  this.term.cwdId = cwdId
915
- this.socket.send(cmd)
942
+
943
+ const ps1Cmd = `\recho $0|grep csh >/dev/null && set prompt_bak="$prompt" && set prompt="$prompt${cwdId}%/${cwdId}"\r
944
+ echo $0|grep zsh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'${cwdId}%d${cwdId}'\r
945
+ echo $0|grep ash >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
946
+ echo $0|grep ksh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
947
+ clear\r`
948
+
949
+ this.socket.send(ps1Cmd)
916
950
  }
917
951
  }
918
952
 
@@ -0,0 +1,28 @@
1
+ import { ColorPicker } from '../bookmark-form/color-picker'
2
+
3
+ export default function ThemeEditSlot (props) {
4
+ const {
5
+ name,
6
+ value,
7
+ disabled
8
+ } = props
9
+ function onChange (v) {
10
+ props.onChange(v, name)
11
+ }
12
+ const pickerProps = {
13
+ value,
14
+ onChange,
15
+ isRgba: value.startsWith('rgba'),
16
+ disabled
17
+ }
18
+ return (
19
+ <div className='theme-edit-slot'>
20
+ <span className='iblock mg1r'>{name}</span>
21
+ <span className='iblock'>
22
+ <ColorPicker
23
+ {...pickerProps}
24
+ />
25
+ </span>
26
+ </div>
27
+ )
28
+ }
@@ -0,0 +1,37 @@
1
+ // import { buildDefaultThemes } from '../../common/terminal-theme'
2
+ import ThemeEditSlot from './theme-edit-slot'
3
+
4
+ export default function ThemeEditor (props) {
5
+ const { themeText, disabled } = props
6
+ const obj = themeText.split('\n').reduce((prev, line) => {
7
+ let [key = '', value = ''] = line.split('=')
8
+ key = key.trim()
9
+ value = value.trim()
10
+ if (!key || !value) {
11
+ return prev
12
+ }
13
+ prev[key] = value
14
+ return prev
15
+ }, {})
16
+ const keys = Object.keys(obj)
17
+ function onChange (value, name) {
18
+ props.onChange(value, name)
19
+ }
20
+ return (
21
+ <div className='editor-u-picker'>
22
+ {
23
+ keys.map(k => {
24
+ return (
25
+ <ThemeEditSlot
26
+ key={k}
27
+ name={k}
28
+ value={obj[k]}
29
+ disabled={disabled}
30
+ onChange={onChange}
31
+ />
32
+ )
33
+ })
34
+ }
35
+ </div>
36
+ )
37
+ }
@@ -1,10 +1,12 @@
1
- import { useRef } from 'react'
2
- import { Button, Input, message, Upload, Form } from 'antd'
1
+ import { useRef, useState } from 'react'
2
+ import { Button, Input, message, Upload, Form, Space } from 'antd'
3
3
  import { convertTheme, convertThemeToText, exportTheme, validThemeProps, requiredThemeProps } from '../../common/terminal-theme'
4
4
  import { defaultTheme, defaultThemeLight } from '../../common/constants'
5
5
  import generate from '../../common/uid'
6
6
  import Link from '../common/external-link'
7
7
  import InputAutoFocus from '../common/input-auto-focus'
8
+ import ThemePicker from './theme-editor'
9
+ // import './theme-form.styl'
8
10
 
9
11
  const { TextArea } = Input
10
12
  const FormItem = Form.Item
@@ -12,6 +14,8 @@ const e = window.translate
12
14
 
13
15
  export default function ThemeForm (props) {
14
16
  const [form] = Form.useForm()
17
+ const [txt, setTxt] = useState(convertThemeToText(props.formData))
18
+ const [editor, setEditor] = useState('theme-editor-color-picker')
15
19
  const action = useRef('submit')
16
20
  function exporter () {
17
21
  exportTheme(props.formData.id)
@@ -82,18 +86,27 @@ export default function ThemeForm (props) {
82
86
  return Promise.reject(message)
83
87
  }
84
88
  // Return an object with the flag and the message
89
+ setTxt(value)
85
90
  return Promise.resolve()
86
91
  }
87
92
 
88
93
  async function handleSubmit (res) {
94
+ if (!res.themeText) {
95
+ res.themeText = txt
96
+ }
89
97
  const { formData } = props
90
98
  const {
91
99
  themeName,
92
100
  themeText
93
101
  } = res
102
+ const converted = convertTheme(themeText)
103
+
104
+ if (converted.uiThemeConfig.main !== converted.themeConfig.background) {
105
+ converted.themeConfig.background = converted.uiThemeConfig.main
106
+ }
94
107
  const update = {
95
108
  name: themeName,
96
- ...convertTheme(themeText)
109
+ ...converted
97
110
  }
98
111
  const update1 = {
99
112
  ...update,
@@ -135,15 +148,22 @@ export default function ThemeForm (props) {
135
148
  async function beforeUpload (file) {
136
149
  const txt = await window.fs.readFile(file.path)
137
150
  const { name, themeConfig, uiThemeConfig } = convertTheme(txt)
151
+ const tt = convertThemeToText({
152
+ themeConfig, uiThemeConfig
153
+ })
138
154
  form.setFieldsValue({
139
155
  themeName: name,
140
- themeText: convertThemeToText({
141
- themeConfig, uiThemeConfig
142
- })
156
+ themeText: tt
143
157
  })
158
+ setTxt(tt)
144
159
  return false
145
160
  }
146
161
 
162
+ function handleSwitchEditor (e) {
163
+ e.preventDefault()
164
+ setEditor(editor === 'theme-editor-txt' ? 'theme-editor-color-picker' : 'theme-editor-txt')
165
+ }
166
+
147
167
  function renderFuncs (id) {
148
168
  if (!id) {
149
169
  return null
@@ -160,6 +180,43 @@ export default function ThemeForm (props) {
160
180
  )
161
181
  }
162
182
 
183
+ function onPickerChange (value, name) {
184
+ const realName = name.includes('terminal:')
185
+ ? name.replace('terminal:', '')
186
+ : name
187
+ const text = form.getFieldValue('themeText')
188
+ const obj = convertTheme(text)
189
+ if (obj.themeConfig[realName]) {
190
+ obj.themeConfig[realName] = value
191
+ } else if (obj.uiThemeConfig[realName]) {
192
+ obj.uiThemeConfig[realName] = value
193
+ }
194
+ form.setFieldsValue({
195
+ themeText: convertThemeToText(obj)
196
+ })
197
+ setTxt(convertThemeToText(obj))
198
+ }
199
+
200
+ function renderTxt () {
201
+ return (
202
+ <FormItem
203
+ noStyle
204
+ name='themeText'
205
+ hasFeedback
206
+ rules={[{
207
+ max: 1000, message: '1000 chars max'
208
+ }, {
209
+ required: true,
210
+ message: 'theme config required'
211
+ }, {
212
+ validator: validateInput
213
+ }]}
214
+ >
215
+ <TextArea rows={33} disabled={disabled} />
216
+ </FormItem>
217
+ )
218
+ }
219
+
163
220
  const {
164
221
  readonly,
165
222
  id,
@@ -173,12 +230,18 @@ export default function ThemeForm (props) {
173
230
  const { autofocustrigger } = props.store
174
231
  const isDefaultTheme = id === defaultTheme.id || id === defaultThemeLight.id
175
232
  const disabled = readonly || isDefaultTheme
233
+ const switchTxt = editor === 'theme-editor-txt' ? e('editWithColorPicker') : e('editWithTextEditor')
234
+ const pickerProps = {
235
+ onChange: onPickerChange,
236
+ themeText: txt,
237
+ disabled
238
+ }
176
239
  return (
177
240
  <Form
178
241
  onFinish={handleSubmit}
179
242
  form={form}
180
243
  initialValues={initialValues}
181
- className='form-wrap'
244
+ className={editor}
182
245
  name='terminal-theme-form'
183
246
  layout='vertical'
184
247
  >
@@ -202,35 +265,41 @@ export default function ThemeForm (props) {
202
265
  <FormItem
203
266
  label={e('themeConfig')}
204
267
  >
205
- <div className='pd1b'>
206
- <Upload
207
- beforeUpload={beforeUpload}
208
- fileList={[]}
209
- className='mg1b'
210
- >
211
- <Button
212
- type='dashed'
213
- disabled={disabled}
268
+ <div className='mg1b fix'>
269
+ <span className='fleft'>
270
+ <Space compact>
271
+ <Button
272
+ type='dashed'
273
+ onClick={handleSwitchEditor}
274
+ >
275
+ {switchTxt}
276
+ </Button>
277
+ </Space>
278
+ </span>
279
+ <span className='fright'>
280
+ <Upload
281
+ beforeUpload={beforeUpload}
282
+ fileList={[]}
283
+ className='mg1b'
214
284
  >
215
- {e('importFromFile')}
216
- </Button>
217
- </Upload>
285
+ <Button
286
+ type='dashed'
287
+ disabled={disabled}
288
+ >
289
+ {e('importFromFile')}
290
+ </Button>
291
+ </Upload>
292
+ </span>
218
293
  </div>
219
- <FormItem
220
- noStyle
221
- name='themeText'
222
- hasFeedback
223
- rules={[{
224
- max: 1000, message: '1000 chars max'
225
- }, {
226
- required: true,
227
- message: 'theme config required'
228
- }, {
229
- validator: validateInput
230
- }]}
231
- >
232
- <TextArea rows={33} disabled={disabled} />
233
- </FormItem>
294
+ {
295
+ editor === 'theme-editor-txt'
296
+ ? renderTxt()
297
+ : (
298
+ <ThemePicker
299
+ {...pickerProps}
300
+ />
301
+ )
302
+ }
234
303
  </FormItem>
235
304
  {
236
305
  disabled
@@ -0,0 +1,10 @@
1
+ .theme-editor-txt
2
+ .editor-u-textarea
3
+ display block !important
4
+ .editor-u-picker
5
+ display none !important
6
+ .theme-editor-color-picker
7
+ .editor-u-textarea
8
+ display none !important
9
+ .editor-u-picker
10
+ display block !important
@@ -71,7 +71,8 @@ export default class VncSession extends RdpSession {
71
71
  server = ''
72
72
  } = config
73
73
  const { sessionId, id } = this.props
74
- const tab = deepCopy(this.props.tab || {})
74
+ const tab = window.store.applyProfile(deepCopy(this.props.tab || {}))
75
+ console.log('vnc tab', this.props.tab)
75
76
  const {
76
77
  type,
77
78
  term: terminalType,
@@ -12,7 +12,8 @@ import {
12
12
  modals,
13
13
  leftSidebarWidthKey,
14
14
  rightSidebarWidthKey,
15
- dismissDelKeyTipLsKey
15
+ dismissDelKeyTipLsKey,
16
+ connectionMap
16
17
  } from '../common/constants'
17
18
  import * as ls from '../common/safe-local-storage'
18
19
 
@@ -212,10 +213,11 @@ export default Store => {
212
213
 
213
214
  Store.prototype.applyProfile = function (tab) {
214
215
  const {
215
- authType,
216
- profile
216
+ profile,
217
+ type,
218
+ authType
217
219
  } = tab
218
- if (authType !== 'profiles') {
220
+ if (!profile || authType !== 'profiles') {
219
221
  return tab
220
222
  }
221
223
  const p = window.store.profiles.find(x => x.id === profile)
@@ -227,6 +229,25 @@ export default Store => {
227
229
  // delete tab.passphrase
228
230
  delete p.name
229
231
  delete p.id
232
+ if (type === connectionMap.rdp) {
233
+ return {
234
+ ...tab,
235
+ ...p.rdp
236
+ }
237
+ } else if (type === connectionMap.vnc) {
238
+ return {
239
+ ...tab,
240
+ ...p.vnc
241
+ }
242
+ } else if (type === connectionMap.telnet) {
243
+ return {
244
+ ...tab,
245
+ ...p.telnet
246
+ }
247
+ }
248
+ delete p.rdp
249
+ delete p.vnc
250
+ delete p.telnet
230
251
  return {
231
252
  ...tab,
232
253
  ...p
@@ -120,7 +120,6 @@ export default (Store) => {
120
120
  const { store } = window
121
121
  const currentSyncType = store.syncType
122
122
  const currentSettings = store.config.syncSetting || {}
123
- console.log('currentSettings: ', currentSyncType, currentSettings)
124
123
  // Create a new object without the current sync type's settings
125
124
  const updatedSettings = Object.keys(currentSettings).reduce((acc, key) => {
126
125
  if (!key.startsWith(currentSyncType)) {
@@ -128,8 +127,6 @@ export default (Store) => {
128
127
  }
129
128
  return acc
130
129
  }, {})
131
- console.log('updatedSettings: ', updatedSettings)
132
-
133
130
  store.setConfig({
134
131
  syncSetting: updatedSettings
135
132
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "1.39.119",
3
+ "version": "1.40.1",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",