@electerm/electerm-react 2.3.181 → 2.3.190

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 (26) hide show
  1. package/client/components/bookmark-form/common/fields.jsx +1 -1
  2. package/client/components/bookmark-form/common/ssh-auth-selector.jsx +36 -32
  3. package/client/components/bookmark-form/common/ssh-auth-type-selector.jsx +4 -1
  4. package/client/components/bookmark-form/config/common-fields.js +29 -23
  5. package/client/components/bookmark-form/config/ftp.js +5 -5
  6. package/client/components/bookmark-form/config/local.js +9 -9
  7. package/client/components/bookmark-form/config/rdp.js +3 -3
  8. package/client/components/bookmark-form/config/serial.js +4 -4
  9. package/client/components/bookmark-form/config/ssh.js +3 -2
  10. package/client/components/bookmark-form/config/telnet.js +2 -2
  11. package/client/components/bookmark-form/config/vnc.js +5 -5
  12. package/client/components/bookmark-form/config/web.js +3 -3
  13. package/client/components/quick-commands/quick-commands-form-elem.jsx +9 -39
  14. package/client/components/setting-panel/hotkey.jsx +132 -0
  15. package/client/components/setting-panel/setting-common.jsx +8 -62
  16. package/client/components/shortcuts/get-key-char.js +1 -1
  17. package/client/components/shortcuts/shortcut-control.jsx +5 -0
  18. package/client/components/shortcuts/shortcut-editor.jsx +3 -0
  19. package/client/components/shortcuts/shortcut-handler.js +8 -0
  20. package/client/components/shortcuts/shortcut-utils.js +49 -0
  21. package/client/components/shortcuts/shortcuts-defaults.js +5 -0
  22. package/client/components/shortcuts/shortcuts.jsx +4 -40
  23. package/client/components/sidebar/index.jsx +3 -0
  24. package/client/components/tabs/tab.jsx +11 -0
  25. package/client/store/tab.js +11 -0
  26. package/package.json +1 -1
@@ -101,7 +101,7 @@ export function renderFormItem (item, formItemLayout, form, ctxProps, index) {
101
101
  const formItemProps = {
102
102
  ...formItemLayout,
103
103
  className: cls,
104
- label,
104
+ label: typeof label === 'string' ? label : label(),
105
105
  name,
106
106
  rules,
107
107
  valuePropName,
@@ -26,14 +26,44 @@ export default function renderAuth (props) {
26
26
  formItemName = 'password',
27
27
  profileFilter = (d) => d
28
28
  } = props
29
- const beforeUpload = async (file) => {
29
+ const commonBeforeUpload = (fieldName) => async (file) => {
30
30
  const filePath = getFilePath(file)
31
- const privateKey = await window.fs.readFile(filePath)
31
+ const content = await window.fs.readFile(filePath)
32
32
  form.setFieldsValue({
33
- privateKey
33
+ [fieldName]: content
34
34
  })
35
35
  return false
36
36
  }
37
+ const renderKeyField = (key, label, desc) => (
38
+ <FormItem
39
+ {...formItemLayout}
40
+ label={e(label)}
41
+ hasFeedback
42
+ key={key}
43
+ className='mg1b'
44
+ rules={[{
45
+ max: 13000, message: '13000 chars max'
46
+ }]}
47
+ >
48
+ <FormItem noStyle name={key}>
49
+ <TextArea
50
+ placeholder={e(desc)}
51
+ autoSize={{ minRows: 1 }}
52
+ />
53
+ </FormItem>
54
+ <Upload
55
+ beforeUpload={commonBeforeUpload(key)}
56
+ fileList={[]}
57
+ >
58
+ <Button
59
+ type='dashed'
60
+ className='mg2b mg1t'
61
+ >
62
+ {e('importFromFile')}
63
+ </Button>
64
+ </Upload>
65
+ </FormItem>
66
+ )
37
67
  if (authType === 'password') {
38
68
  const opts = {
39
69
  options: uniqBy(
@@ -95,34 +125,7 @@ export default function renderAuth (props) {
95
125
  )
96
126
  }
97
127
  return [
98
- <FormItem
99
- {...formItemLayout}
100
- label={e('privateKey')}
101
- hasFeedback
102
- key='privateKey'
103
- className='mg1b'
104
- rules={[{
105
- max: 13000, message: '13000 chars max'
106
- }]}
107
- >
108
- <FormItem noStyle name='privateKey'>
109
- <TextArea
110
- placeholder={e('privateKeyDesc')}
111
- autoSize={{ minRows: 1 }}
112
- />
113
- </FormItem>
114
- <Upload
115
- beforeUpload={beforeUpload}
116
- fileList={[]}
117
- >
118
- <Button
119
- type='dashed'
120
- className='mg2b mg1t'
121
- >
122
- {e('importFromFile')}
123
- </Button>
124
- </Upload>
125
- </FormItem>,
128
+ renderKeyField('privateKey', 'privateKey', 'privateKeyDesc'),
126
129
  <FormItem
127
130
  key='passphrase'
128
131
  {...formItemLayout}
@@ -136,6 +139,7 @@ export default function renderAuth (props) {
136
139
  <Password
137
140
  placeholder={e('passphraseDesc')}
138
141
  />
139
- </FormItem>
142
+ </FormItem>,
143
+ renderKeyField('certificate', 'certificate', 'certificate')
140
144
  ]
141
145
  }
@@ -25,9 +25,12 @@ export default function SshAuthTypeSelector ({ handleChangeAuthType, filterAuthT
25
25
  >
26
26
  {
27
27
  authTypesFiltered.map(t => {
28
+ const str = t === 'privateKey'
29
+ ? e(t) + '/' + e('certificate')
30
+ : e(t)
28
31
  return (
29
32
  <RadioButton value={t} key={t}>
30
- {e(t)}
33
+ {str}
31
34
  </RadioButton>
32
35
  )
33
36
  })
@@ -15,65 +15,65 @@ export const commonFields = {
15
15
  host: {
16
16
  type: 'colorTitle',
17
17
  name: 'host',
18
- label: e('host'),
18
+ label: () => e('host'),
19
19
  rules: [{ required: true, message: e('host') + ' required' }]
20
20
  },
21
21
 
22
22
  colorTitle: {
23
23
  type: 'colorTitle',
24
24
  name: 'title',
25
- label: e('title')
25
+ label: () => e('title')
26
26
  },
27
27
 
28
28
  title: {
29
29
  type: 'input',
30
30
  name: 'title',
31
- label: e('title')
31
+ label: () => e('title')
32
32
  },
33
33
 
34
34
  username: {
35
35
  type: 'input',
36
36
  name: 'username',
37
- label: e('username')
37
+ label: () => e('username')
38
38
  },
39
39
 
40
40
  password: {
41
41
  type: 'password',
42
42
  name: 'password',
43
- label: e('password')
43
+ label: () => e('password')
44
44
  },
45
45
 
46
46
  loginPrompt: {
47
47
  type: 'input',
48
48
  name: 'loginPrompt',
49
- label: e('loginPrompt'),
49
+ label: () => e('loginPrompt'),
50
50
  props: { placeholder: '/login[: ]*$/i' }
51
51
  },
52
52
 
53
53
  passwordPrompt: {
54
54
  type: 'input',
55
55
  name: 'passwordPrompt',
56
- label: e('passwordPrompt'),
56
+ label: () => e('passwordPrompt'),
57
57
  props: { placeholder: '/password[: ]*$/i' }
58
58
  },
59
59
 
60
60
  port: {
61
61
  type: 'number',
62
62
  name: 'port',
63
- label: e('port'),
63
+ label: () => e('port'),
64
64
  rules: [{ required: true, message: 'port required' }]
65
65
  },
66
66
 
67
67
  description: {
68
68
  type: 'textarea',
69
69
  name: 'description',
70
- label: e('description')
70
+ label: () => e('description')
71
71
  },
72
72
 
73
73
  category: {
74
74
  type: 'categorySelect',
75
75
  name: 'category',
76
- label: e('category')
76
+ label: () => e('category')
77
77
  },
78
78
 
79
79
  type: {
@@ -106,13 +106,13 @@ export const commonFields = {
106
106
  interactiveValues: {
107
107
  type: 'textarea',
108
108
  name: 'interactiveValues',
109
- label: e('interactiveValues')
109
+ label: () => e('interactiveValues')
110
110
  },
111
111
 
112
112
  encode: {
113
113
  type: 'select',
114
114
  name: 'encode',
115
- label: e('encode'),
115
+ label: () => e('encode'),
116
116
  options: encodes.map(k => ({ label: k.toUpperCase(), value: k }))
117
117
  },
118
118
 
@@ -128,7 +128,7 @@ export const commonFields = {
128
128
  terminalType: {
129
129
  type: 'autocomplete',
130
130
  name: 'term',
131
- label: e('terminalType'),
131
+ label: () => e('terminalType'),
132
132
  rules: [{ required: true, message: 'terminal type required' }],
133
133
  options: terminalTypes.map(t => ({ label: t, value: t }))
134
134
  },
@@ -136,14 +136,14 @@ export const commonFields = {
136
136
  displayRaw: {
137
137
  type: 'switch',
138
138
  name: 'displayRaw',
139
- label: e('displayRaw'),
139
+ label: () => e('displayRaw'),
140
140
  valuePropName: 'checked'
141
141
  },
142
142
 
143
143
  fontFamily: {
144
144
  type: 'input',
145
145
  name: 'fontFamily',
146
- label: e('fontFamily'),
146
+ label: () => e('fontFamily'),
147
147
  rules: [{ max: 130, message: '130 chars max' }],
148
148
  props: { placeholder: defaultSettings.fontFamily }
149
149
  },
@@ -151,7 +151,7 @@ export const commonFields = {
151
151
  fontSize: {
152
152
  type: 'number',
153
153
  name: 'fontSize',
154
- label: e('fontSize'),
154
+ label: () => e('fontSize'),
155
155
  props: {
156
156
  min: 9,
157
157
  max: 65535,
@@ -163,7 +163,7 @@ export const commonFields = {
163
163
  keepaliveInterval: {
164
164
  type: 'number',
165
165
  name: 'keepaliveInterval',
166
- label: e('keepaliveIntervalDesc'),
166
+ label: () => e('keepaliveIntervalDesc'),
167
167
  props: {
168
168
  min: 0,
169
169
  max: 20000000,
@@ -174,13 +174,13 @@ export const commonFields = {
174
174
  terminalBackground: {
175
175
  type: 'terminalBackground',
176
176
  name: 'terminalBackground',
177
- label: e('terminalBackgroundImage')
177
+ label: () => e('terminalBackgroundImage')
178
178
  },
179
179
 
180
180
  proxy: {
181
181
  type: 'proxy',
182
182
  name: '__proxy__',
183
- label: e('proxy')
183
+ label: () => e('proxy')
184
184
  },
185
185
 
186
186
  x11: {
@@ -243,7 +243,7 @@ export const sshSettings = [
243
243
  {
244
244
  type: 'switch',
245
245
  name: 'ignoreKeyboardInteractive',
246
- label: e('ignoreKeyboardInteractive'),
246
+ label: () => e('ignoreKeyboardInteractive'),
247
247
  valuePropName: 'checked'
248
248
  },
249
249
  ...terminalSettings.slice(0, -1), // All except terminalBackground
@@ -271,6 +271,12 @@ export const sshAuthFields = [
271
271
  { type: 'sshAuthTypeSelector', name: 'authType', label: '' },
272
272
  { type: 'sshAuthSelector', name: '__auth__', label: '', formItemName: 'password' },
273
273
  commonFields.port,
274
+ {
275
+ type: 'switch',
276
+ name: 'useSshAgent',
277
+ label: () => e('useSshAgent'),
278
+ valuePropName: 'checked'
279
+ },
274
280
  commonFields.runScripts,
275
281
  commonFields.description,
276
282
  commonFields.setEnv,
@@ -305,18 +311,18 @@ export const telnetAuthFields = [
305
311
  // Common tab configurations - functions to ensure translation happens at render time
306
312
  export const quickCommandsTab = () => ({
307
313
  key: 'quickCommands',
308
- label: e('quickCommands'),
314
+ label: () => e('quickCommands'),
309
315
  fields: [commonFields.quickCommands]
310
316
  })
311
317
 
312
318
  export const sshTunnelTab = () => ({
313
319
  key: 'tunnel',
314
- label: e('sshTunnel'),
320
+ label: () => e('sshTunnel'),
315
321
  fields: [commonFields.sshTunnels]
316
322
  })
317
323
 
318
324
  export const connectionHoppingTab = () => ({
319
325
  key: 'connectionHopping',
320
- label: e('connectionHopping'),
326
+ label: () => e('connectionHopping'),
321
327
  fields: [commonFields.connectionHopping]
322
328
  })
@@ -22,16 +22,16 @@ const ftpConfig = {
22
22
  tabs: () => [
23
23
  {
24
24
  key: 'auth',
25
- label: e('auth'),
25
+ label: () => e('auth'),
26
26
  fields: [
27
27
  commonFields.category,
28
28
  commonFields.colorTitle,
29
- { type: 'input', name: 'host', label: e('host'), rules: [{ required: true, message: e('host') + ' required' }] },
29
+ { type: 'input', name: 'host', label: () => e('host'), rules: [{ required: true, message: e('host') + ' required' }] },
30
30
  commonFields.port,
31
31
  { type: 'profileItem', name: '__profile__', label: '', profileFilter: d => !isEmpty(d.ftp) },
32
- { type: 'input', name: 'user', label: e('username') },
33
- { type: 'password', name: 'password', label: e('password') },
34
- { type: 'switch', name: 'secure', label: e('secure'), valuePropName: 'checked' },
32
+ { type: 'input', name: 'user', label: () => e('username') },
33
+ { type: 'password', name: 'password', label: () => e('password') },
34
+ { type: 'switch', name: 'secure', label: () => e('secure'), valuePropName: 'checked' },
35
35
  commonFields.type
36
36
  ]
37
37
  }
@@ -26,7 +26,7 @@ const localConfig = {
26
26
  tabs: () => [
27
27
  {
28
28
  key: 'auth',
29
- label: e('auth'),
29
+ label: () => e('auth'),
30
30
  fields: [
31
31
  commonFields.category,
32
32
  commonFields.colorTitle,
@@ -37,7 +37,7 @@ const localConfig = {
37
37
  },
38
38
  {
39
39
  key: 'settings',
40
- label: e('settings'),
40
+ label: () => e('settings'),
41
41
  fields: [
42
42
  {
43
43
  type: 'input',
@@ -48,27 +48,27 @@ const localConfig = {
48
48
  {
49
49
  type: 'autocomplete',
50
50
  name: 'term',
51
- label: e('terminalType'),
51
+ label: () => e('terminalType'),
52
52
  rules: [{ required: true, message: 'terminal type required' }],
53
53
  options: terminalTypes.map(t => ({ label: t, value: t }))
54
54
  },
55
55
  {
56
56
  type: 'switch',
57
57
  name: 'displayRaw',
58
- label: e('displayRaw'),
58
+ label: () => e('displayRaw'),
59
59
  valuePropName: 'checked'
60
60
  },
61
61
  {
62
62
  type: 'input',
63
63
  name: 'fontFamily',
64
- label: e('fontFamily'),
64
+ label: () => e('fontFamily'),
65
65
  rules: [{ max: 130, message: '130 chars max' }],
66
66
  props: { placeholder: defaultSettings.fontFamily }
67
67
  },
68
68
  {
69
69
  type: 'number',
70
70
  name: 'fontSize',
71
- label: e('fontSize'),
71
+ label: () => e('fontSize'),
72
72
  props: {
73
73
  min: 9,
74
74
  max: 65535,
@@ -79,19 +79,19 @@ const localConfig = {
79
79
  {
80
80
  type: 'number',
81
81
  name: 'keepaliveInterval',
82
- label: e('keepaliveIntervalDesc'),
82
+ label: () => e('keepaliveIntervalDesc'),
83
83
  props: {
84
84
  min: 0,
85
85
  max: 20000000,
86
86
  step: 1000
87
87
  }
88
88
  },
89
- { type: 'terminalBackground', name: 'terminalBackground', label: e('terminalBackgroundImage') }
89
+ { type: 'terminalBackground', name: 'terminalBackground', label: () => e('terminalBackgroundImage') }
90
90
  ]
91
91
  },
92
92
  {
93
93
  key: 'quickCommands',
94
- label: e('quickCommands'),
94
+ label: () => e('quickCommands'),
95
95
  fields: [
96
96
  { type: 'quickCommands', name: '__quick__', label: '' }
97
97
  ]
@@ -19,18 +19,18 @@ const rdpConfig = {
19
19
  tabs: () => [
20
20
  {
21
21
  key: 'auth',
22
- label: e('auth'),
22
+ label: () => e('auth'),
23
23
  fields: [
24
24
  { type: 'rdpWarning', name: 'rdpWarning' },
25
25
  commonFields.category,
26
26
  commonFields.colorTitle,
27
- { type: 'input', name: 'host', label: e('host'), rules: [{ required: true, message: e('host') + ' required' }] },
27
+ { type: 'input', name: 'host', label: () => e('host'), rules: [{ required: true, message: e('host') + ' required' }] },
28
28
  commonFields.port,
29
29
  { type: 'profileItem', name: '__profile__', label: '', profileFilter: d => !isEmpty(d.rdp) },
30
30
  { ...commonFields.username, rules: [{ required: true, message: e('username') + ' required' }] },
31
31
  { ...commonFields.password, rules: [{ required: true, message: e('password') + ' required' }] },
32
32
  commonFields.description,
33
- { type: 'input', name: 'domain', label: e('domain') },
33
+ { type: 'input', name: 'domain', label: () => e('domain') },
34
34
  commonFields.type
35
35
  ]
36
36
  }
@@ -31,7 +31,7 @@ const serialConfig = {
31
31
  tabs: () => [
32
32
  {
33
33
  key: 'auth',
34
- label: e('auth'),
34
+ label: () => e('auth'),
35
35
  fields: [
36
36
  commonFields.category,
37
37
  commonFields.colorTitle,
@@ -64,14 +64,14 @@ const serialConfig = {
64
64
  },
65
65
  {
66
66
  key: 'settings',
67
- label: e('settings'),
67
+ label: () => e('settings'),
68
68
  fields: [
69
- { type: 'terminalBackground', name: 'terminalBackground', label: e('terminalBackgroundImage') }
69
+ { type: 'terminalBackground', name: 'terminalBackground', label: () => e('terminalBackgroundImage') }
70
70
  ]
71
71
  },
72
72
  {
73
73
  key: 'quickCommands',
74
- label: e('quickCommands'),
74
+ label: () => e('quickCommands'),
75
75
  fields: [
76
76
  { type: 'quickCommands', name: '__quick__', label: '' }
77
77
  ]
@@ -20,6 +20,7 @@ const sshConfig = {
20
20
  enableSftp: true,
21
21
  sshTunnels: [],
22
22
  connectionHoppings: [],
23
+ useSshAgent: true,
23
24
  serverHostKey: [],
24
25
  cipher: [],
25
26
  ...getTerminalDefaults(store),
@@ -32,12 +33,12 @@ const sshConfig = {
32
33
  tabs: () => [
33
34
  {
34
35
  key: 'auth',
35
- label: e('auth'),
36
+ label: () => e('auth'),
36
37
  fields: sshAuthFields
37
38
  },
38
39
  {
39
40
  key: 'settings',
40
- label: e('settings'),
41
+ label: () => e('settings'),
41
42
  fields: sshSettings
42
43
  },
43
44
  quickCommandsTab(),
@@ -31,12 +31,12 @@ const telnetConfig = {
31
31
  tabs: () => [
32
32
  {
33
33
  key: 'auth',
34
- label: e('auth'),
34
+ label: () => e('auth'),
35
35
  fields: telnetAuthFields
36
36
  },
37
37
  {
38
38
  key: 'settings',
39
- label: e('settings'),
39
+ label: () => e('settings'),
40
40
  fields: terminalSettings
41
41
  },
42
42
  quickCommandsTab()
@@ -23,16 +23,16 @@ const vncConfig = {
23
23
  tabs: () => [
24
24
  {
25
25
  key: 'auth',
26
- label: e('auth'),
26
+ label: () => e('auth'),
27
27
  fields: [
28
28
  { type: 'vncWarning', name: 'vncWarning' },
29
29
  commonFields.category,
30
30
  commonFields.colorTitle,
31
- { type: 'input', name: 'host', label: e('host'), rules: [{ required: true, message: e('host') + ' required' }] },
31
+ { type: 'input', name: 'host', label: () => e('host'), rules: [{ required: true, message: e('host') + ' required' }] },
32
32
  commonFields.port,
33
- { type: 'switch', name: 'viewOnly', label: e('viewOnly'), valuePropName: 'checked' },
34
- { type: 'switch', name: 'clipViewport', label: e('clipViewport'), valuePropName: 'checked' },
35
- { type: 'switch', name: 'scaleViewport', label: e('scaleViewport'), valuePropName: 'checked' },
33
+ { type: 'switch', name: 'viewOnly', label: () => e('viewOnly'), valuePropName: 'checked' },
34
+ { type: 'switch', name: 'clipViewport', label: () => e('clipViewport'), valuePropName: 'checked' },
35
+ { type: 'switch', name: 'scaleViewport', label: () => e('scaleViewport'), valuePropName: 'checked' },
36
36
  { type: 'profileItem', name: '__profile__', label: '', profileFilter: d => !isEmpty(d.vnc) },
37
37
  commonFields.username,
38
38
  commonFields.password,
@@ -15,14 +15,14 @@ const webConfig = {
15
15
  tabs: () => [
16
16
  {
17
17
  key: 'main',
18
- label: e('auth'),
18
+ label: () => e('auth'),
19
19
  fields: [
20
20
  commonFields.category,
21
21
  commonFields.colorTitle,
22
22
  {
23
23
  type: 'input',
24
24
  name: 'url',
25
- label: e('URL'),
25
+ label: () => e('URL'),
26
26
  rules: [
27
27
  { required: true, message: e('Please input URL') },
28
28
  {
@@ -34,7 +34,7 @@ const webConfig = {
34
34
  ]
35
35
  },
36
36
  commonFields.description,
37
- { type: 'input', name: 'useragent', label: e('useragent') },
37
+ { type: 'input', name: 'useragent', label: () => e('useragent') },
38
38
  { type: 'switch', name: 'hideAddressBar', label: 'hideAddressBar', valuePropName: 'checked' },
39
39
  commonFields.type
40
40
  ]
@@ -11,18 +11,14 @@ import generate from '../../common/uid'
11
11
  import InputAutoFocus from '../common/input-auto-focus'
12
12
  import renderQm from './quick-commands-list-form'
13
13
  import ShortcutEdit from '../shortcuts/shortcut-editor'
14
- import shortcutsDefaultsGen from '../shortcuts/shortcuts-defaults'
14
+ import { getKeysTakenData } from '../shortcuts/shortcut-utils'
15
15
  import deepCopy from 'json-deep-copy'
16
- import {
17
- isMacJs as isMac
18
- } from '../../common/constants.js'
19
16
  import templates from './templates'
20
17
  import HelpIcon from '../common/help-icon'
21
18
 
22
19
  const FormItem = Form.Item
23
20
  const { Option } = Select
24
21
  const e = window.translate
25
- const shortcutsDefaults = shortcutsDefaultsGen()
26
22
 
27
23
  export default function QuickCommandForm (props) {
28
24
  const [form] = Form.useForm()
@@ -42,41 +38,15 @@ export default function QuickCommandForm (props) {
42
38
  })
43
39
  setShortcut('')
44
40
  }
45
- const getKeysTakenData = () => {
46
- const { shortcuts = {} } = store.config
47
- const { quickCommands = [] } = store
48
-
49
- // Gather system shortcuts
50
- const systemShortcuts = shortcutsDefaults.reduce((p, k) => {
51
- const propName = isMac ? 'shortcutMac' : 'shortcut'
52
- const name = k.name + '_' + propName
53
- const vv = k.readonly ? k[propName] : (shortcuts[name] || k[propName])
54
- const v = vv
55
- .split(',')
56
- .map(f => f.trim())
57
- .reduce((p, k) => ({
58
- ...p,
59
- [k]: true
60
- }), {})
61
- return {
62
- ...p,
63
- ...v
64
- }
65
- }, {})
41
+ const getKeysTaken = () => {
42
+ const keysTaken = getKeysTakenData()
66
43
 
67
- // Gather quick command shortcuts
68
- const quickCommandShortcuts = quickCommands.reduce((acc, command) => {
69
- if (command.shortcut) {
70
- acc[command.shortcut] = true
71
- }
72
- return acc
73
- }, {})
74
-
75
- // Combine system shortcuts and quick command shortcuts
76
- return {
77
- ...systemShortcuts,
78
- ...quickCommandShortcuts
44
+ // Exclude current shortcut if editing existing command
45
+ if (formData.shortcut) {
46
+ delete keysTaken[formData.shortcut]
79
47
  }
48
+
49
+ return keysTaken
80
50
  }
81
51
 
82
52
  async function handleSubmit (res) {
@@ -126,7 +96,7 @@ export default function QuickCommandForm (props) {
126
96
  name: uid,
127
97
  shortcut
128
98
  },
129
- keysTaken: getKeysTakenData(),
99
+ keysTaken: getKeysTaken(),
130
100
  store,
131
101
  updateConfig,
132
102
  handleClear,
@@ -0,0 +1,132 @@
1
+ import React, { Component } from 'react'
2
+ import ShortcutEdit from '../shortcuts/shortcut-editor'
3
+ import { getKeysTakenData } from '../shortcuts/shortcut-utils'
4
+ import './setting.styl'
5
+
6
+ const e = window.translate
7
+
8
+ // Bidirectional maps for key conversions
9
+ const MODIFIER_MAP = {
10
+ // Display format -> Electron format
11
+ ctrl: 'CommandOrControl',
12
+ meta: 'CommandOrControl',
13
+ alt: 'Alt',
14
+ shift: 'Shift',
15
+ // Electron format -> Display format
16
+ CommandOrControl: 'ctrl',
17
+ Command: 'meta',
18
+ Control: 'ctrl',
19
+ Alt: 'alt',
20
+ Shift: 'shift',
21
+ Super: 'meta',
22
+ Meta: 'meta'
23
+ }
24
+
25
+ const KEY_MAP = {
26
+ // Display format -> Electron format
27
+ '←': 'Left',
28
+ '↑': 'Up',
29
+ '→': 'Right',
30
+ '↓': 'Down',
31
+ '▲': 'mouseWheelUp',
32
+ '▼': 'mouseWheelDown',
33
+ enter: 'Return',
34
+ // Electron format -> Display format
35
+ Left: '←',
36
+ Up: '↑',
37
+ Right: '→',
38
+ Down: '↓',
39
+ Return: 'enter',
40
+ Enter: 'enter',
41
+ mouseWheelUp: '▲',
42
+ mouseWheelDown: '▼'
43
+ }
44
+
45
+ export default class HotkeySetting extends Component {
46
+ onChangeHotkey = (name, shortcut) => {
47
+ // Convert shortcut from ShortcutEdit format to Electron accelerator format
48
+ const electronShortcut = this.convertToElectronAccelerator(shortcut)
49
+ return this.props.onSaveConfig({
50
+ [name]: electronShortcut
51
+ })
52
+ }
53
+
54
+ convertToElectronAccelerator = (shortcut) => {
55
+ if (!shortcut) return shortcut
56
+
57
+ // Split the shortcut into parts
58
+ const parts = shortcut.split('+')
59
+ const modifiers = []
60
+ let key = ''
61
+
62
+ // Process each part
63
+ for (const part of parts) {
64
+ const lowerPart = part.toLowerCase()
65
+ if (MODIFIER_MAP[lowerPart]) {
66
+ modifiers.push(MODIFIER_MAP[lowerPart])
67
+ } else {
68
+ // Handle special key mappings
69
+ key = KEY_MAP[part] || part.toUpperCase()
70
+ }
71
+ }
72
+
73
+ // Combine modifiers and key
74
+ return [...modifiers, key].join('+')
75
+ }
76
+
77
+ convertFromElectronAccelerator = (electronShortcut) => {
78
+ if (!electronShortcut) return electronShortcut
79
+
80
+ // Split the shortcut into parts
81
+ const parts = electronShortcut.split('+')
82
+ const modifiers = []
83
+ let key = ''
84
+
85
+ // Process each part
86
+ for (const part of parts) {
87
+ if (MODIFIER_MAP[part]) {
88
+ modifiers.push(MODIFIER_MAP[part])
89
+ } else {
90
+ // Handle special key mappings
91
+ key = KEY_MAP[part] || part.toLowerCase()
92
+ }
93
+ }
94
+
95
+ // Combine modifiers and key
96
+ return [...modifiers, key].join('+')
97
+ }
98
+
99
+ getKeysTaken = (currentHotkey) => {
100
+ const keysTaken = getKeysTakenData()
101
+
102
+ // Convert current hotkey to display format for comparison
103
+ const currentShortcutDisplay = this.convertFromElectronAccelerator(currentHotkey)
104
+
105
+ // Remove current hotkey from taken keys (allow re-setting the same hotkey)
106
+ delete keysTaken[currentShortcutDisplay]
107
+
108
+ return keysTaken
109
+ }
110
+
111
+ render () {
112
+ const { hotkey } = this.props
113
+ const shortcutProps = {
114
+ data: {
115
+ name: 'hotkey',
116
+ shortcut: this.convertFromElectronAccelerator(hotkey),
117
+ index: 0
118
+ },
119
+ updateConfig: this.onChangeHotkey,
120
+ keysTaken: this.getKeysTaken(hotkey)
121
+ }
122
+
123
+ return (
124
+ <div className='pd2b'>
125
+ <div className='pd1b'>{e('hotkeyDesc')}</div>
126
+ <ShortcutEdit
127
+ {...shortcutProps}
128
+ />
129
+ </div>
130
+ )
131
+ }
132
+ }
@@ -32,24 +32,12 @@ import HelpIcon from '../common/help-icon'
32
32
  import delay from '../../common/wait.js'
33
33
  import isColorDark from '../../common/is-color-dark'
34
34
  import DeepLinkControl from './deep-link-control'
35
+ import HotkeySetting from './hotkey'
35
36
  import './setting.styl'
36
37
 
37
38
  const { Option } = Select
38
39
  const e = window.translate
39
40
 
40
- const modifiers = [
41
- 'Command',
42
- 'Control',
43
- 'Alt',
44
- 'Shift'
45
- ]
46
- const keys = [
47
- ...'0123456789~ABCDEFGHIJKLMNOPQRTSUVWXYZ'.split(''),
48
- ...new Array(12).fill(0).map((m, i) => {
49
- return 'F' + (i + 1)
50
- })
51
- ]
52
-
53
41
  export default class SettingCommon extends Component {
54
42
  state = {
55
43
  ready: false,
@@ -142,28 +130,12 @@ export default class SettingCommon extends Component {
142
130
  )
143
131
  }
144
132
 
145
- handleChangeModifier = modifier => {
146
- const { hotkey } = this.props.config
147
- const key = hotkey.split('+')[1]
148
- return this.saveConfig({
149
- hotkey: `${modifier}+${key}`
150
- })
151
- }
152
-
153
133
  onChangeTimeout = sshReadyTimeout => {
154
134
  return this.saveConfig({
155
135
  sshReadyTimeout
156
136
  })
157
137
  }
158
138
 
159
- handleChangeKey = key => {
160
- const { hotkey } = this.props.config
161
- const modifier = hotkey.split('+')[0]
162
- return this.saveConfig({
163
- hotkey: `${modifier}+${key}`
164
- })
165
- }
166
-
167
139
  handleChangeLang = language => {
168
140
  this.setState({
169
141
  languageChanged: true
@@ -211,12 +183,6 @@ export default class SettingCommon extends Component {
211
183
  this.props.store.setConfig(ext)
212
184
  }
213
185
 
214
- renderOption = (m, i) => {
215
- return (
216
- <Option value={m} key={m + 'opt' + i}>{m}</Option>
217
- )
218
- }
219
-
220
186
  renderToggle = (name, extra = null) => {
221
187
  const checked = !!this.props.config[name]
222
188
  return (
@@ -513,7 +479,6 @@ export default class SettingCommon extends Component {
513
479
  langs = []
514
480
  } = window.et
515
481
  const terminalThemes = props.store.getSidebarList(settingMap.terminalThemes)
516
- const [modifier, key] = hotkey.split('+')
517
482
  const pops = {
518
483
  onStartSessions: props.config.onStartSessions,
519
484
  bookmarks: props.bookmarks,
@@ -521,35 +486,16 @@ export default class SettingCommon extends Component {
521
486
  workspaces: props.store.workspaces,
522
487
  onChangeStartSessions: this.onChangeStartSessions
523
488
  }
489
+ const hotkeyProps = {
490
+ hotkey,
491
+ onSaveConfig: this.saveConfig
492
+ }
524
493
  return (
525
494
  <div className='form-wrap pd1y pd2x'>
526
495
  <h2>{e('settings')}</h2>
527
- <div className='pd1b'>{e('hotkeyDesc')}</div>
528
- <div className='pd2b'>
529
- <Select
530
- value={modifier}
531
- onChange={this.handleChangeModifier}
532
- className='width100'
533
- popupMatchSelectWidth={false}
534
- showSearch
535
- >
536
- {
537
- modifiers.map(this.renderOption)
538
- }
539
- </Select>
540
- <span className='mg1x'>+</span>
541
- <Select
542
- value={key}
543
- className='width100'
544
- onChange={this.handleChangeKey}
545
- popupMatchSelectWidth={false}
546
- showSearch
547
- >
548
- {
549
- keys.map(this.renderOption)
550
- }
551
- </Select>
552
- </div>
496
+ <HotkeySetting
497
+ {...hotkeyProps}
498
+ />
553
499
  <div className='pd1b'>{e('onStartBookmarks')}</div>
554
500
  <div className='pd2b'>
555
501
  <StartSession
@@ -37,7 +37,7 @@ export function getKeyCharacter (code = '') {
37
37
  }
38
38
  if (code.startsWith('Key') && code.length === 4) {
39
39
  return code[3].toLowerCase()
40
- } else if (code.startsWith('Digit') && code.length === 5) {
40
+ } else if (code.startsWith('Digit') && code.length === 6) {
41
41
  return code[5]
42
42
  } else {
43
43
  return mapping[code] || code
@@ -139,6 +139,11 @@ class ShortcutControl extends React.PureComponent {
139
139
  window.store.reloadTab()
140
140
  }, 500)
141
141
 
142
+ reloadAllShortcut = throttle((e) => {
143
+ e.stopPropagation()
144
+ window.store.reloadAllTabs()
145
+ }, 500)
146
+
142
147
  cloneToNextLayoutShortcut = throttle((e) => {
143
148
  e.stopPropagation()
144
149
  window.store.cloneToNextLayout()
@@ -106,6 +106,8 @@ export default class ShortcutEdit extends PureComponent {
106
106
  altKey,
107
107
  wheelDeltaY
108
108
  } = e
109
+ e.preventDefault()
110
+ e.stopPropagation()
109
111
  const codeName = e instanceof window.WheelEvent
110
112
  ? (wheelDeltaY > 0 ? 'mouseWheelUp' : 'mouseWheelDown')
111
113
  : code
@@ -201,6 +203,7 @@ export default class ShortcutEdit extends PureComponent {
201
203
  <Input
202
204
  addonAfter={this.renderAfter()}
203
205
  value={shortcut}
206
+ className='shortcut-input'
204
207
  />
205
208
  </div>
206
209
  )
@@ -44,6 +44,11 @@ function buildConfigForSearch (config) {
44
44
  }, {})
45
45
  }
46
46
 
47
+ function isInAntdInput () {
48
+ const activeElement = document.activeElement
49
+ return activeElement && activeElement.classList.contains('shortcut-input')
50
+ }
51
+
47
52
  export function shortcutExtend (Cls) {
48
53
  Cls.prototype.handleKeyboardEvent = function (event) {
49
54
  const {
@@ -57,6 +62,9 @@ export function shortcutExtend (Cls) {
57
62
  type,
58
63
  key
59
64
  } = event
65
+ if (isInAntdInput()) {
66
+ return
67
+ }
60
68
  if (this.cmdAddon) {
61
69
  this.cmdAddon.handleKey(event)
62
70
  }
@@ -0,0 +1,49 @@
1
+ import { isMacJs as isMac } from '../../common/constants.js'
2
+ import shortcutsDefaultsGen from './shortcuts-defaults'
3
+
4
+ /**
5
+ * Get keys taken data for shortcut conflict checking
6
+ * @returns {object} Object with shortcut strings as keys and true as values
7
+ */
8
+ export function getKeysTakenData () {
9
+ const { store } = window
10
+ const { config, quickCommands } = store
11
+ const { shortcuts = {} } = config
12
+
13
+ // Get shortcuts defaults
14
+ const shortcutsDefaults = shortcutsDefaultsGen()
15
+
16
+ // Gather system shortcuts
17
+ const systemShortcuts = shortcutsDefaults.reduce((p, k) => {
18
+ const propName = isMac ? 'shortcutMac' : 'shortcut'
19
+ const name = k.name + '_' + propName
20
+ const vv = k.readonly ? k[propName] : (shortcuts[name] || k[propName])
21
+ if (!vv) return p
22
+
23
+ const v = vv
24
+ .split(',')
25
+ .map(f => f.trim())
26
+ .reduce((p, k) => ({
27
+ ...p,
28
+ [k]: true
29
+ }), {})
30
+ return {
31
+ ...p,
32
+ ...v
33
+ }
34
+ }, {})
35
+
36
+ // Gather quick command shortcuts
37
+ const quickCommandShortcuts = quickCommands.reduce((acc, command) => {
38
+ if (command.shortcut) {
39
+ acc[command.shortcut] = true
40
+ }
41
+ return acc
42
+ }, {})
43
+
44
+ // Combine system shortcuts and quick command shortcuts
45
+ return {
46
+ ...systemShortcuts,
47
+ ...quickCommandShortcuts
48
+ }
49
+ }
@@ -16,6 +16,11 @@ export default () => {
16
16
  shortcut: 'alt+r',
17
17
  shortcutMac: 'alt+r'
18
18
  },
19
+ {
20
+ name: 'app_reloadAll',
21
+ shortcut: 'alt+y',
22
+ shortcutMac: 'alt+y'
23
+ },
19
24
  {
20
25
  name: 'app_cloneToNextLayout',
21
26
  shortcut: 'ctrl+/',
@@ -6,9 +6,10 @@ import {
6
6
  Table,
7
7
  Button
8
8
  } from 'antd'
9
+ import { isMacJs as isMac } from '../../common/constants.js'
9
10
  import {
10
- isMacJs as isMac
11
- } from '../../common/constants.js'
11
+ getKeysTakenData
12
+ } from './shortcut-utils.js'
12
13
 
13
14
  const e = window.translate
14
15
  const shortcutsDefaults = shortcutsDefaultsGen()
@@ -45,43 +46,6 @@ export default class Shortcuts extends PureComponent {
45
46
  })
46
47
  }
47
48
 
48
- getKeysTakenData = () => {
49
- const { config, quickCommands = [] } = this.props
50
- const { shortcuts = {} } = config
51
-
52
- // Gather system shortcuts
53
- const systemShortcuts = shortcutsDefaults.reduce((p, k) => {
54
- const propName = isMac ? 'shortcutMac' : 'shortcut'
55
- const name = k.name + '_' + propName
56
- const vv = k.readonly ? k[propName] : (shortcuts[name] || k[propName])
57
- const v = vv
58
- .split(',')
59
- .map(f => f.trim())
60
- .reduce((p, k) => ({
61
- ...p,
62
- [k]: true
63
- }), {})
64
- return {
65
- ...p,
66
- ...v
67
- }
68
- }, {})
69
-
70
- // Gather quick command shortcuts
71
- const quickCommandShortcuts = quickCommands.reduce((acc, command) => {
72
- if (command.shortcut) {
73
- acc[command.shortcut] = true
74
- }
75
- return acc
76
- }, {})
77
-
78
- // Combine system shortcuts and quick command shortcuts
79
- return {
80
- ...systemShortcuts,
81
- ...quickCommandShortcuts
82
- }
83
- }
84
-
85
49
  render () {
86
50
  const columns = [
87
51
  {
@@ -124,7 +88,7 @@ export default class Shortcuts extends PureComponent {
124
88
  return (
125
89
  <ShortcutEdit
126
90
  data={inst}
127
- keysTaken={this.getKeysTakenData()}
91
+ keysTaken={getKeysTakenData()}
128
92
  updateConfig={this.updateConfig}
129
93
  />
130
94
  )
@@ -61,6 +61,9 @@ export default function Sidebar (props) {
61
61
  }
62
62
 
63
63
  const handleClickBookmark = () => {
64
+ if (showModal) {
65
+ store.showModal = 0
66
+ }
64
67
  if (pinned) {
65
68
  return
66
69
  }
@@ -190,6 +190,10 @@ class Tab extends Component {
190
190
  window.store.reloadTab(this.props.tab.id)
191
191
  }
192
192
 
193
+ handleReloadAll = () => {
194
+ window.store.reloadAllTabs()
195
+ }
196
+
193
197
  onDragEnd = e => {
194
198
  removeClass(this.tabRef.current, onDragCls)
195
199
  this.clearCls()
@@ -260,6 +264,7 @@ class Tab extends Component {
260
264
  const closeShortcut = this.getShortcut('app_closeCurrentTab')
261
265
  const cloneToNextShortcut = this.getShortcut('app_cloneToNextLayout')
262
266
  const duplicateShortcut = this.getShortcut('app_duplicateTab')
267
+ const reloadAllShortcut = this.getShortcut('app_reloadAll')
263
268
 
264
269
  const x = [
265
270
  {
@@ -305,6 +310,12 @@ class Tab extends Component {
305
310
  icon: <iconsMap.ReloadOutlined />,
306
311
  label: e('reload'),
307
312
  extra: reloadShortcut
313
+ },
314
+ {
315
+ key: 'handleReloadAll',
316
+ icon: <iconsMap.ReloadOutlined />,
317
+ label: e('reloadAll'),
318
+ extra: reloadAllShortcut
308
319
  }
309
320
  ].filter(Boolean)
310
321
  return x
@@ -124,6 +124,17 @@ export default Store => {
124
124
  }, 0)
125
125
  }
126
126
 
127
+ Store.prototype.reloadAllTabs = function () {
128
+ const { store } = window
129
+ const { tabs } = store
130
+ // Reload all tabs with a small delay between each to avoid conflicts
131
+ tabs.forEach((tab, index) => {
132
+ setTimeout(() => {
133
+ store.reloadTab(tab.id)
134
+ }, index * 100) // 100ms delay between each reload
135
+ })
136
+ }
137
+
127
138
  Store.prototype.duplicateTab = function (tabId) {
128
139
  const { store } = window
129
140
  const { tabs } = store
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "2.3.181",
3
+ "version": "2.3.190",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",