@electerm/electerm-react 2.1.8 → 2.1.26

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 (28) hide show
  1. package/client/common/constants.js +6 -0
  2. package/client/common/init-setting-item.js +1 -1
  3. package/client/components/bookmark-form/common/fields.jsx +1 -1
  4. package/client/components/bookmark-form/common/init-values.js +10 -1
  5. package/client/components/bookmark-form/common/terminal-background.jsx +11 -2
  6. package/client/components/bookmark-form/config/ftp.js +3 -2
  7. package/client/components/bookmark-form/config/local.js +8 -2
  8. package/client/components/bookmark-form/config/rdp.js +3 -2
  9. package/client/components/bookmark-form/config/ssh.js +3 -2
  10. package/client/components/bookmark-form/config/telnet.js +8 -2
  11. package/client/components/bookmark-form/config/vnc.js +3 -2
  12. package/client/components/bookmark-form/form-renderer.jsx +5 -3
  13. package/client/components/profile/profile-form-elem.jsx +22 -3
  14. package/client/components/profile/profile-tabs.jsx +33 -20
  15. package/client/components/quick-commands/quick-commands-form-elem.jsx +7 -7
  16. package/client/components/setting-panel/list.jsx +14 -1
  17. package/client/components/setting-panel/setting-common.jsx +1 -1
  18. package/client/components/setting-panel/terminal-bg-config.jsx +21 -9
  19. package/client/components/sftp/paged-list.jsx +1 -1
  20. package/client/components/shortcuts/shortcut-control.jsx +8 -0
  21. package/client/components/shortcuts/shortcuts-defaults.js +5 -0
  22. package/client/components/ssh-config/ssh-config-load-notify.jsx +2 -1
  23. package/client/components/tabs/tab.jsx +3 -1
  24. package/client/components/theme/theme-list.jsx +35 -13
  25. package/client/components/tree-list/tree-list.jsx +4 -0
  26. package/client/store/common.js +9 -0
  27. package/client/store/store.js +14 -5
  28. package/package.json +1 -1
@@ -107,6 +107,12 @@ export const settingMap = buildConst([
107
107
  'profiles'
108
108
  ])
109
109
 
110
+ export const staticNewItemTabs = new Set([
111
+ 'terminalThemes',
112
+ 'quickCommands',
113
+ 'profiles'
114
+ ])
115
+
110
116
  export const infoTabs = buildConst([
111
117
  'info',
112
118
  'deps',
@@ -24,7 +24,7 @@ export default (arr, tab) => {
24
24
  } else if (tab === settingMap.quickCommands) {
25
25
  return {
26
26
  id: '',
27
- name: encodeURIComponent(newQuickCommand)
27
+ name: e(newQuickCommand)
28
28
  }
29
29
  } else if (tab === settingMap.profiles) {
30
30
  return {
@@ -145,7 +145,7 @@ export function renderFormItem (item, formItemLayout, form, ctxProps, index) {
145
145
  />
146
146
  )
147
147
  case 'terminalBackground':
148
- return <TerminalBackgroundField key={name} form={form} />
148
+ return <TerminalBackgroundField key={name} />
149
149
  case 'profileItem':
150
150
  return <ProfileItem key={name} store={ctxProps.store} profileFilter={item.profileFilter} />
151
151
  case 'quickCommands':
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Common utilities for config initValues
3
3
  */
4
- import { newBookmarkIdPrefix } from '../../../common/constants.js'
4
+ import { newBookmarkIdPrefix, authTypeMap } from '../../../common/constants.js'
5
5
  import { getColorFromCategory } from '../../../common/get-category-color.js'
6
6
  import findBookmarkGroupId from '../../../common/find-bookmark-group-id.js'
7
7
  import deepCopy from 'json-deep-copy'
@@ -81,3 +81,12 @@ export function getTerminalBackgroundDefaults (defaultSetting) {
81
81
  }
82
82
  }
83
83
  }
84
+
85
+ export function getAuthTypeDefault (props) {
86
+ const r = {}
87
+ if (window.store.defaultProfileId) {
88
+ r.profile = window.store.defaultProfileId
89
+ r.authType = authTypeMap.profiles
90
+ }
91
+ return r
92
+ }
@@ -11,7 +11,7 @@ const FormItem = Form.Item
11
11
  const e = window.translate
12
12
 
13
13
  // Custom form control that implements the antd form control interface
14
- const TerminalBgControl = ({ value = {}, onChange }) => {
14
+ const TerminalBgControl = ({ value, onChange }) => {
15
15
  const handleChange = (newValue, name) => {
16
16
  const updatedValue = {
17
17
  ...value,
@@ -20,16 +20,25 @@ const TerminalBgControl = ({ value = {}, onChange }) => {
20
20
  onChange(updatedValue)
21
21
  }
22
22
 
23
+ const handleBatchUpdate = (updates) => {
24
+ const updatedValue = {
25
+ ...value,
26
+ ...updates
27
+ }
28
+ onChange(updatedValue)
29
+ }
30
+
23
31
  return (
24
32
  <TerminalBackgroundConfig
25
33
  config={value}
26
34
  onChangeValue={handleChange}
35
+ batchUpdate={handleBatchUpdate}
27
36
  name='terminalBackgroundImagePath'
28
37
  />
29
38
  )
30
39
  }
31
40
 
32
- export default function renderTermBg (form) {
41
+ export default function renderTermBg () {
33
42
  const formProps = {
34
43
  ...formItemLayout,
35
44
  name: 'terminalBackground',
@@ -1,6 +1,6 @@
1
1
  import { formItemLayout } from '../../../common/form-layout.js'
2
2
  import { terminalFtpType } from '../../../common/constants.js'
3
- import { createBaseInitValues } from '../common/init-values.js'
3
+ import { createBaseInitValues, getAuthTypeDefault } from '../common/init-values.js'
4
4
  import { commonFields } from './common-fields.js'
5
5
  import { isEmpty } from 'lodash-es'
6
6
 
@@ -14,7 +14,8 @@ const ftpConfig = {
14
14
  port: 21,
15
15
  user: '',
16
16
  password: '',
17
- secure: false
17
+ secure: false,
18
+ ...getAuthTypeDefault(props)
18
19
  })
19
20
  },
20
21
  layout: formItemLayout,
@@ -1,6 +1,11 @@
1
1
  import { formItemLayout } from '../../../common/form-layout.js'
2
2
  import { terminalLocalType, terminalTypes } from '../../../common/constants.js'
3
- import { createBaseInitValues, getTerminalDefaults, getSshDefaults } from '../common/init-values.js'
3
+ import {
4
+ createBaseInitValues,
5
+ getTerminalDefaults,
6
+ getSshDefaults,
7
+ getTerminalBackgroundDefaults
8
+ } from '../common/init-values.js'
4
9
  import defaultSettings from '../../../common/default-setting.js'
5
10
  import { commonFields } from './common-fields.js'
6
11
 
@@ -13,7 +18,8 @@ const localConfig = {
13
18
  const { store } = props
14
19
  return createBaseInitValues(props, terminalLocalType, {
15
20
  ...getTerminalDefaults(store),
16
- ...getSshDefaults()
21
+ ...getSshDefaults(),
22
+ ...getTerminalBackgroundDefaults(defaultSettings)
17
23
  })
18
24
  },
19
25
  layout: formItemLayout,
@@ -1,6 +1,6 @@
1
1
  import { formItemLayout } from '../../../common/form-layout.js'
2
2
  import { terminalRdpType } from '../../../common/constants.js'
3
- import { createBaseInitValues } from '../common/init-values.js'
3
+ import { createBaseInitValues, getAuthTypeDefault } from '../common/init-values.js'
4
4
  import { isEmpty } from 'lodash-es'
5
5
  import { commonFields } from './common-fields.js'
6
6
 
@@ -11,7 +11,8 @@ const rdpConfig = {
11
11
  type: terminalRdpType,
12
12
  initValues: (props) => {
13
13
  return createBaseInitValues(props, terminalRdpType, {
14
- port: 3389
14
+ port: 3389,
15
+ ...getAuthTypeDefault(props)
15
16
  })
16
17
  },
17
18
  layout: formItemLayout,
@@ -2,7 +2,7 @@
2
2
  import { formItemLayout } from '../../../common/form-layout.js'
3
3
  import { connectionMap, authTypeMap, defaultEnvLang } from '../../../common/constants.js'
4
4
  import defaultSetting from '../../../common/default-setting.js'
5
- import { createBaseInitValues, getTerminalDefaults, getSshDefaults, getTerminalBackgroundDefaults } from '../common/init-values.js'
5
+ import { createBaseInitValues, getTerminalDefaults, getSshDefaults, getTerminalBackgroundDefaults, getAuthTypeDefault } from '../common/init-values.js'
6
6
  import { sshAuthFields, sshSettings, quickCommandsTab, sshTunnelTab, connectionHoppingTab } from './common-fields.js'
7
7
 
8
8
  const e = window.translate
@@ -24,7 +24,8 @@ const sshConfig = {
24
24
  cipher: [],
25
25
  ...getTerminalDefaults(store),
26
26
  ...getSshDefaults(),
27
- ...getTerminalBackgroundDefaults(defaultSetting)
27
+ ...getTerminalBackgroundDefaults(defaultSetting),
28
+ ...getAuthTypeDefault(props)
28
29
  })
29
30
  },
30
31
  layout: formItemLayout,
@@ -1,7 +1,12 @@
1
1
  import { formItemLayout } from '../../../common/form-layout.js'
2
2
  import { terminalTelnetType, authTypeMap } from '../../../common/constants.js'
3
3
  import defaultSettings from '../../../common/default-setting.js'
4
- import { createBaseInitValues, getSshDefaults, getTerminalBackgroundDefaults } from '../common/init-values.js'
4
+ import {
5
+ createBaseInitValues,
6
+ getSshDefaults,
7
+ getTerminalBackgroundDefaults,
8
+ getAuthTypeDefault
9
+ } from '../common/init-values.js'
5
10
  import { telnetAuthFields, terminalSettings, quickCommandsTab } from './common-fields.js'
6
11
 
7
12
  const e = window.translate
@@ -18,7 +23,8 @@ const telnetConfig = {
18
23
  authType: authTypeMap.password,
19
24
  term: defaultSettings.terminalType,
20
25
  ...getSshDefaults(),
21
- ...getTerminalBackgroundDefaults(defaultSettings)
26
+ ...getTerminalBackgroundDefaults(defaultSettings),
27
+ ...getAuthTypeDefault(props)
22
28
  })
23
29
  },
24
30
  layout: formItemLayout,
@@ -1,6 +1,6 @@
1
1
  import { formItemLayout } from '../../../common/form-layout.js'
2
2
  import { terminalVncType } from '../../../common/constants.js'
3
- import { createBaseInitValues } from '../common/init-values.js'
3
+ import { createBaseInitValues, getAuthTypeDefault } from '../common/init-values.js'
4
4
  import { isEmpty } from 'lodash-es'
5
5
  import { commonFields, connectionHoppingTab } from './common-fields.js'
6
6
 
@@ -14,7 +14,8 @@ const vncConfig = {
14
14
  port: 5900,
15
15
  viewOnly: false,
16
16
  scaleViewport: true,
17
- connectionHoppings: []
17
+ connectionHoppings: [],
18
+ ...getAuthTypeDefault(props)
18
19
  })
19
20
  },
20
21
  layout: formItemLayout,
@@ -22,9 +22,10 @@ import { isValidIP } from '../../common/is-ip'
22
22
  import { action as manateAction } from 'manate'
23
23
 
24
24
  export default function FormRenderer ({ config, props }) {
25
+ const initialValues = config.initValues(props)
25
26
  const [form] = Form.useForm()
26
27
  const [ips, setIps] = useState([])
27
- const [authType, setAuthType] = useState(props.formData.authType || authTypeMap.password)
28
+ const [authType, setAuthType] = useState(initialValues.authType || authTypeMap.password)
28
29
  const [testing, setTesting] = useState(false)
29
30
  const action = useRef('submit')
30
31
 
@@ -156,6 +157,9 @@ export default function FormRenderer ({ config, props }) {
156
157
  ...props.formData,
157
158
  ...res
158
159
  }
160
+ if (!obj.terminalBackground?.terminalBackgroundImagePath) {
161
+ delete obj.terminalBackground
162
+ }
159
163
  if (isTest) {
160
164
  return test(obj)
161
165
  }
@@ -266,7 +270,6 @@ export default function FormRenderer ({ config, props }) {
266
270
  setAuthType(newAuthType)
267
271
  }
268
272
 
269
- // Context props for specialized components
270
273
  const ctxProps = {
271
274
  ...props,
272
275
  form,
@@ -282,7 +285,6 @@ export default function FormRenderer ({ config, props }) {
282
285
  useIp
283
286
  }
284
287
 
285
- const initialValues = config.initValues(props)
286
288
  const tabs = typeof config.tabs === 'function' ? (config.tabs() || []) : (config.tabs || [])
287
289
  let content = null
288
290
 
@@ -2,14 +2,16 @@ import { useState } from 'react'
2
2
  import {
3
3
  Form,
4
4
  message,
5
+ Switch,
5
6
  Button
6
7
  } from 'antd'
7
8
  import InputAutoFocus from '../common/input-auto-focus'
8
9
  import { formItemLayout } from '../../common/form-layout'
10
+ import HelpIcon from '../common/help-icon'
9
11
  import {
10
12
  settingMap
11
13
  } from '../../common/constants'
12
-
14
+ import { action } from 'manate'
13
15
  import ProfileTabs from './profile-tabs'
14
16
 
15
17
  const FormItem = Form.Item
@@ -28,13 +30,15 @@ export default function ProfileFormElem (props) {
28
30
  }
29
31
  return id
30
32
  }
31
- async function handleSubmit (res) {
33
+ const handleSubmit = action(async function (res) {
32
34
  const { formData } = props
33
35
  const update1 = {
34
36
  ...res,
35
37
  id: genId()
36
38
  }
39
+ let defaultId = update1.id
37
40
  if (formData.id) {
41
+ defaultId = formData.id
38
42
  props.store.editItem(formData.id, res, settingMap.profiles)
39
43
  } else {
40
44
  props.store.addItem(update1, settingMap.profiles)
@@ -43,14 +47,21 @@ export default function ProfileFormElem (props) {
43
47
  name: e('profile')
44
48
  })
45
49
  }
50
+ window.store.makeSureProfileDefault(defaultId)
46
51
  message.success(e('saved'))
47
- }
52
+ })
48
53
  const tabsProps = {
49
54
  activeTab,
50
55
  onChangeTab: setActiveTab,
51
56
  form,
52
57
  store: props.store
53
58
  }
59
+ const profileDefaultWikiLink = 'https://github.com/electerm/electerm/wiki/Default-Profile'
60
+ const defaultLabel = (
61
+ <span>
62
+ {e('default')} <HelpIcon link={profileDefaultWikiLink} />
63
+ </span>
64
+ )
54
65
  return (
55
66
  <Form
56
67
  form={form}
@@ -73,6 +84,14 @@ export default function ProfileFormElem (props) {
73
84
  >
74
85
  <InputAutoFocus />
75
86
  </FormItem>
87
+ <FormItem
88
+ {...formItemLayout}
89
+ label={defaultLabel}
90
+ name='isDefault'
91
+ valuePropName='checked'
92
+ >
93
+ <Switch />
94
+ </FormItem>
76
95
  <ProfileTabs {...tabsProps} />
77
96
  <FormItem>
78
97
  <Button
@@ -5,32 +5,45 @@ import ProfileFormVnc from './profile-form-vnc'
5
5
  import ProfileFormFtp from './profile-form-ftp'
6
6
  import ProfileFormTelnet from './profile-form-telnet'
7
7
 
8
- const { TabPane } = Tabs
9
-
10
8
  export default function ProfileTabs (props) {
11
9
  const { activeTab, onChangeTab, form, store } = props
12
10
  const tabsProps = {
13
11
  activeKey: activeTab,
14
12
  onChange: onChangeTab
15
13
  }
14
+ const items = [
15
+ {
16
+ label: 'ssh',
17
+ key: 'ssh',
18
+ forceRender: true,
19
+ children: <ProfileFormSsh form={form} store={store} />
20
+ },
21
+ {
22
+ label: 'telnet',
23
+ key: 'telnet',
24
+ forceRender: true,
25
+ children: <ProfileFormTelnet form={form} store={store} />
26
+ },
27
+ {
28
+ label: 'vnc',
29
+ key: 'vnc',
30
+ forceRender: true,
31
+ children: <ProfileFormVnc />
32
+ },
33
+ {
34
+ label: 'rdp',
35
+ key: 'rdp',
36
+ forceRender: true,
37
+ children: <ProfileFormRdp />
38
+ },
39
+ {
40
+ label: 'ftp',
41
+ key: 'ftp',
42
+ forceRender: true,
43
+ children: <ProfileFormFtp />
44
+ }
45
+ ]
16
46
  return (
17
- <Tabs {...tabsProps}>
18
- <TabPane tab='ssh' key='ssh' forceRender>
19
- <ProfileFormSsh form={form} store={store} />
20
- </TabPane>
21
- <TabPane tab='telnet' key='telnet' forceRender>
22
- <ProfileFormTelnet form={form} store={store} />
23
- </TabPane>
24
- <TabPane tab='vnc' key='vnc' forceRender>
25
- <ProfileFormVnc />
26
- </TabPane>
27
- <TabPane tab='rdp' key='rdp' forceRender>
28
- <ProfileFormRdp />
29
- </TabPane>
30
- <TabPane tab='ftp' key='ftp' forceRender>
31
- <ProfileFormFtp />
32
- </TabPane>
33
- </Tabs>
34
-
47
+ <Tabs {...tabsProps} items={items} />
35
48
  )
36
49
  }
@@ -138,13 +138,6 @@ export default function QuickCommandForm (props) {
138
138
  const wiki = 'https://github.com/electerm/electerm/wiki/quick-command-templates'
139
139
  return (
140
140
  <>
141
- <p>
142
- <b className='mg1r'>{e('templates')}:</b>
143
- <span className='mg1r'>{templatesStr}</span>
144
- <HelpIcon
145
- link={wiki}
146
- />
147
- </p>
148
141
  <Form
149
142
  form={form}
150
143
  onFinish={handleSubmit}
@@ -210,6 +203,13 @@ export default function QuickCommandForm (props) {
210
203
  </Button>
211
204
  </p>
212
205
  </FormItem>
206
+ <p>
207
+ <b className='mg1r'>{e('templates')}:</b>
208
+ <span className='mg1r'>{templatesStr}</span>
209
+ <HelpIcon
210
+ link={wiki}
211
+ />
212
+ </p>
213
213
  </Form>
214
214
  </>
215
215
  )
@@ -9,7 +9,8 @@ import createName, { createTitleTag } from '../../common/create-title'
9
9
  import classnames from 'classnames'
10
10
  import { noop } from 'lodash-es'
11
11
  import highlight from '../common/highlight'
12
- import { settingSyncId, settingCommonId } from '../../common/constants'
12
+ import { settingSyncId, settingCommonId, staticNewItemTabs } from '../../common/constants'
13
+ import getInitItem from '../../common/init-setting-item'
13
14
  import './list.styl'
14
15
 
15
16
  const e = window.translate
@@ -95,6 +96,17 @@ export default class ItemList extends React.PureComponent {
95
96
  return icon
96
97
  }
97
98
 
99
+ renderNewItem () {
100
+ const { type } = this.props
101
+
102
+ if (!staticNewItemTabs.has(type)) {
103
+ return null
104
+ }
105
+
106
+ const newItem = getInitItem([], type)
107
+ return this.renderItem(newItem, -1)
108
+ }
109
+
98
110
  renderItem = (item, i) => {
99
111
  const { onClickItem, type, activeItemId } = this.props
100
112
  const { id } = item
@@ -175,6 +187,7 @@ export default class ItemList extends React.PureComponent {
175
187
  {this.renderLabels ? this.renderLabels() : null}
176
188
  {this.renderSearch()}
177
189
  <div className='item-list-wrap' style={listStyle}>
190
+ {this.renderNewItem()}
178
191
  {
179
192
  list.map(this.renderItem)
180
193
  }
@@ -424,7 +424,7 @@ export default class SettingCommon extends Component {
424
424
  {e('global')} {e('proxy')}
425
425
  <HelpIcon
426
426
  title={table}
427
- overlayInnerStyle={style}
427
+ style={{ body: { style } }}
428
428
  />
429
429
  </span>
430
430
  <Switch
@@ -20,7 +20,8 @@ export default function TerminalBackgroundConfig ({
20
20
  onChangeValue,
21
21
  name,
22
22
  config,
23
- isGlobal = false
23
+ isGlobal = false,
24
+ batchUpdate
24
25
  }) {
25
26
  const [showTextModal, setShowTextModal] = useState(false)
26
27
  const value = config[name]
@@ -71,7 +72,7 @@ export default function TerminalBackgroundConfig ({
71
72
  // Show helpful text when text background is selected but no text is configured
72
73
  dataSource[2] = {
73
74
  value: textTerminalBgValue,
74
- desc: `📝 ${e('clickToConfigureText') || 'Click to configure text'}`
75
+ desc: '📝 Click to configure text'
75
76
  }
76
77
  }
77
78
 
@@ -93,12 +94,24 @@ export default function TerminalBackgroundConfig ({
93
94
  }
94
95
 
95
96
  const handleTextBgModalOk = (textConfig) => {
96
- // Store text configuration in the config
97
- onChangeValue(textConfig.text, 'terminalBackgroundText')
98
- onChangeValue(textConfig.fontSize, 'terminalBackgroundTextSize')
99
- onChangeValue(textConfig.color, 'terminalBackgroundTextColor')
100
- onChangeValue(textConfig.fontFamily, 'terminalBackgroundTextFontFamily')
101
- onChange(textTerminalBgValue)
97
+ if (batchUpdate) {
98
+ // Use batch update if available
99
+ const updates = {
100
+ terminalBackgroundText: textConfig.text,
101
+ terminalBackgroundTextSize: textConfig.fontSize,
102
+ terminalBackgroundTextColor: textConfig.color,
103
+ terminalBackgroundTextFontFamily: textConfig.fontFamily,
104
+ [name]: textTerminalBgValue
105
+ }
106
+ batchUpdate(updates)
107
+ } else {
108
+ // Fall back to sequential updates
109
+ onChangeValue(textConfig.text, 'terminalBackgroundText')
110
+ onChangeValue(textConfig.fontSize, 'terminalBackgroundTextSize')
111
+ onChangeValue(textConfig.color, 'terminalBackgroundTextColor')
112
+ onChangeValue(textConfig.fontFamily, 'terminalBackgroundTextFontFamily')
113
+ onChange(textTerminalBgValue)
114
+ }
102
115
  setShowTextModal(false)
103
116
  }
104
117
 
@@ -204,7 +217,6 @@ export default function TerminalBackgroundConfig ({
204
217
  label: item.desc
205
218
  }
206
219
  }
207
-
208
220
  return (
209
221
  <div className='pd2b'>
210
222
  <div className='pd1b'>
@@ -38,7 +38,7 @@ export default class ScrollFiles extends Component {
38
38
  pageSize: this.state.pageSize,
39
39
  total: this.props.list.length,
40
40
  showSizeChanger: false,
41
- size: 'small',
41
+ simple: true,
42
42
  onChange: this.onChange
43
43
  }
44
44
  return (
@@ -144,6 +144,14 @@ class ShortcutControl extends React.PureComponent {
144
144
  window.store.cloneToNextLayout()
145
145
  }, 500)
146
146
 
147
+ duplicateTabShortcut = throttle((e) => {
148
+ e.stopPropagation()
149
+ const { activeTabId } = window.store
150
+ if (activeTabId) {
151
+ window.store.duplicateTab(activeTabId)
152
+ }
153
+ }, 500)
154
+
147
155
  prevTabShortcut = throttle((e) => {
148
156
  e.stopPropagation()
149
157
  window.store.clickPrevTab()
@@ -21,6 +21,11 @@ export default () => {
21
21
  shortcut: 'ctrl+/',
22
22
  shortcutMac: 'meta+/'
23
23
  },
24
+ {
25
+ name: 'app_duplicateTab',
26
+ shortcut: 'alt+c',
27
+ shortcutMac: 'alt+c'
28
+ },
24
29
  {
25
30
  name: 'app_newBookmark',
26
31
  shortcut: 'ctrl+n',
@@ -49,7 +49,8 @@ export default function SshConfigLoadNotify (props) {
49
49
  ignoreSshConfig !== 'yes' &&
50
50
  settingTab === 'bookmarks' &&
51
51
  showModal &&
52
- sshConfigLoaded !== 'yes'
52
+ sshConfigLoaded !== 'yes' &&
53
+ window.store.hasNodePty
53
54
 
54
55
  if (shouldShow) {
55
56
  showNotification()
@@ -268,6 +268,7 @@ class Tab extends Component {
268
268
  const reloadShortcut = this.getShortcut('app_reloadCurrentTab')
269
269
  const closeShortcut = this.getShortcut('app_closeCurrentTab')
270
270
  const cloneToNextShortcut = this.getShortcut('app_cloneToNextLayout')
271
+ const duplicateShortcut = this.getShortcut('app_duplicateTab')
271
272
 
272
273
  const x = [
273
274
  {
@@ -294,7 +295,8 @@ class Tab extends Component {
294
295
  {
295
296
  key: 'handleDup',
296
297
  icon: <iconsMap.CopyOutlined />,
297
- label: e('duplicate')
298
+ label: e('duplicate'),
299
+ extra: duplicateShortcut
298
300
  },
299
301
  {
300
302
  key: 'cloneToNextLayout',
@@ -7,7 +7,8 @@ import { LoadingOutlined, CheckCircleOutlined } from '@ant-design/icons'
7
7
  import { pick } from 'lodash-es'
8
8
  import { Pagination } from 'antd'
9
9
  import ThemeListItem from './theme-list-item'
10
- import { defaultTheme } from '../../common/constants'
10
+ import { defaultTheme, settingMap } from '../../common/constants'
11
+ import getInitItem from '../../common/init-setting-item'
11
12
  import './terminal-theme-list.styl'
12
13
 
13
14
  const e = window.translate
@@ -58,13 +59,33 @@ export default class ThemeList extends List {
58
59
  )
59
60
  }
60
61
 
62
+ renderNewItem () {
63
+ const newThemeItem = getInitItem([], settingMap.terminalThemes)
64
+ const itemProps = {
65
+ item: newThemeItem,
66
+ renderDelBtn: this.renderDelBtn,
67
+ activeItemId: this.props.activeItemId,
68
+ ...pick(
69
+ this.props,
70
+ [
71
+ 'onClickItem',
72
+ 'theme',
73
+ 'keyword'
74
+ ]
75
+ )
76
+ }
77
+ return (
78
+ <ThemeListItem key='new-theme' {...itemProps} />
79
+ )
80
+ }
81
+
61
82
  filter = list => {
62
- const { keyword, ready } = this.state
83
+ const { keyword } = this.state
63
84
  return keyword
64
- ? list.slice(0, ready ? list.length : 2).filter(item => {
85
+ ? list.filter(item => {
65
86
  return item.name.toLowerCase().includes(keyword.toLowerCase())
66
87
  })
67
- : list.slice(0, ready ? list.length : 2)
88
+ : list
68
89
  }
69
90
 
70
91
  paged = list => {
@@ -77,6 +98,13 @@ export default class ThemeList extends List {
77
98
 
78
99
  render () {
79
100
  const { ready, page, pageSize } = this.state
101
+ if (!ready) {
102
+ return (
103
+ <div className='pd3 aligncenter'>
104
+ <LoadingOutlined />
105
+ </div>
106
+ )
107
+ }
80
108
  let {
81
109
  list = [],
82
110
  type,
@@ -92,6 +120,7 @@ export default class ThemeList extends List {
92
120
  {this.renderSearch()}
93
121
  {this.renderCurrentTheme()}
94
122
  <div className='item-list-wrap' style={listStyle}>
123
+ {this.renderNewItem()}
95
124
  {
96
125
  list.map(this.renderItem)
97
126
  }
@@ -101,17 +130,10 @@ export default class ThemeList extends List {
101
130
  total={all}
102
131
  current={page}
103
132
  pageSize={pageSize}
133
+ showLessItems
134
+ simple
104
135
  onShowSizeChange={this.handlePageSizeChange}
105
136
  />
106
- {
107
- ready
108
- ? null
109
- : (
110
- <div className='pd3 aligncenter'>
111
- <LoadingOutlined />
112
- </div>
113
- )
114
- }
115
137
  </div>
116
138
  )
117
139
  }
@@ -309,6 +309,10 @@ export default class ItemListTree extends Component {
309
309
  store.storeAssign({
310
310
  currentBookmarkGroupId: id
311
311
  })
312
+ const func = this.props.expandedKeys.includes(id)
313
+ ? this.onUnExpandKey
314
+ : this.onExpandKey
315
+ func({ id })
312
316
  } else {
313
317
  store.storeAssign({
314
318
  currentBookmarkGroupId: findBookmarkGroupId(store.bookmarkGroups, id)
@@ -313,6 +313,15 @@ export default Store => {
313
313
  }
314
314
  }
315
315
 
316
+ Store.prototype.makeSureProfileDefault = function (defaultId) {
317
+ const { profiles } = window.store
318
+ for (const p of profiles) {
319
+ if (p.id !== defaultId) {
320
+ delete p.isDefault
321
+ }
322
+ }
323
+ }
324
+
316
325
  Store.prototype.aiConfigMissing = function () {
317
326
  return aiConfigsArr.slice(0, -1).some(k => !window.store.config[k])
318
327
  }
@@ -32,7 +32,8 @@ import getBrand from '../components/ai/get-brand'
32
32
  import {
33
33
  settingMap,
34
34
  terminalSshConfigType,
35
- paneMap
35
+ paneMap,
36
+ staticNewItemTabs
36
37
  } from '../common/constants'
37
38
  import getInitItem from '../common/init-setting-item'
38
39
  import createTitle from '../common/create-title'
@@ -121,6 +122,12 @@ class Store {
121
122
  currentTab.pane === paneMap.terminal
122
123
  }
123
124
 
125
+ get defaultProfileId () {
126
+ const { profiles } = window.store
127
+ const defaultProfile = profiles.find(p => p.isDefault)
128
+ return defaultProfile?.id || ''
129
+ }
130
+
124
131
  get quickCommandTags () {
125
132
  const { quickCommands } = window.store
126
133
  return uniq(
@@ -149,10 +156,12 @@ class Store {
149
156
  const initItem = getInitItem(arr, settingTab)
150
157
  return settingTab === settingMap.history
151
158
  ? arr
152
- : [
153
- deepCopy(initItem),
154
- ...arr
155
- ]
159
+ : staticNewItemTabs.has(settingTab)
160
+ ? arr // Don't add initItem for these tabs, they will be handled separately
161
+ : [
162
+ deepCopy(initItem),
163
+ ...arr
164
+ ]
156
165
  }
157
166
 
158
167
  get terminalCommandSuggestions () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "2.1.8",
3
+ "version": "2.1.26",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",