@electerm/electerm-react 1.60.18 → 1.60.29

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 (104) hide show
  1. package/client/common/clipboard.js +1 -14
  2. package/client/common/constants.js +0 -43
  3. package/client/common/data-compare.js +55 -0
  4. package/client/common/default-setting.js +2 -10
  5. package/client/common/resolve.js +18 -22
  6. package/client/common/sftp.js +0 -3
  7. package/client/components/ai/ai-chat.jsx +30 -6
  8. package/client/components/ai/ai-config.jsx +17 -6
  9. package/client/components/batch-op/batch-op.jsx +3 -24
  10. package/client/components/bookmark-form/bookmark-group-tree-format.js +7 -9
  11. package/client/components/bookmark-form/form-ssh-common.jsx +0 -2
  12. package/client/components/bookmark-form/ssh-form.jsx +8 -41
  13. package/client/components/bookmark-form/tree-delete.jsx +6 -15
  14. package/client/components/common/animate-text.jsx +3 -4
  15. package/client/components/common/drag-handle.jsx +59 -45
  16. package/client/components/common/drag-handle.styl +2 -1
  17. package/client/components/common/input-auto-focus.jsx +29 -63
  18. package/client/components/common/ref.js +24 -0
  19. package/client/components/footer/batch-input.jsx +1 -6
  20. package/client/components/footer/footer-entry.jsx +13 -16
  21. package/client/components/footer/footer.styl +0 -5
  22. package/client/components/icons/ai-icon.jsx +17 -0
  23. package/client/components/icons/ai-icon.styl +3 -0
  24. package/client/components/layout/layout-item.jsx +14 -0
  25. package/client/components/main/main.jsx +8 -19
  26. package/client/components/main/upgrade.jsx +13 -25
  27. package/client/components/profile/profile-form-elem.jsx +1 -2
  28. package/client/components/quick-commands/on-drop.js +1 -12
  29. package/client/components/quick-commands/quick-command-transport-mod.jsx +3 -13
  30. package/client/components/quick-commands/quick-commands-form-elem.jsx +1 -2
  31. package/client/components/rdp/rdp-session.jsx +4 -4
  32. package/client/components/session/session.jsx +9 -11
  33. package/client/components/setting-panel/on-tree-drop.js +4 -35
  34. package/client/components/setting-panel/setting-common.jsx +4 -1
  35. package/client/components/setting-panel/setting-modal.jsx +7 -5
  36. package/client/components/setting-panel/tab-settings.jsx +0 -1
  37. package/client/components/setting-sync/setting-sync.jsx +0 -1
  38. package/client/components/sftp/address-bookmark-item.jsx +1 -15
  39. package/client/components/sftp/confirm-modal-store.jsx +2 -2
  40. package/client/components/sftp/{file-mode-modal.jsx → file-info-modal.jsx} +137 -37
  41. package/client/components/sftp/file-item.jsx +156 -192
  42. package/client/components/sftp/file-table-header.jsx +98 -0
  43. package/client/components/sftp/list-table-ui.jsx +125 -416
  44. package/client/components/sftp/sftp-entry.jsx +102 -128
  45. package/client/components/sftp/sftp.styl +6 -22
  46. package/client/components/sftp/transfer-conflict-store.jsx +8 -12
  47. package/client/components/sftp/transport-action-store.jsx +7 -15
  48. package/client/components/shortcuts/shortcut-control.jsx +72 -3
  49. package/client/components/shortcuts/shortcut-handler.js +0 -1
  50. package/client/components/side-panel-r/side-panel-r.jsx +7 -4
  51. package/client/components/sidebar/history.jsx +3 -0
  52. package/client/components/sidebar/index.jsx +1 -1
  53. package/client/components/sidebar/info-modal.jsx +3 -0
  54. package/client/components/sidebar/side-panel.jsx +7 -4
  55. package/client/components/sidebar/sidebar-panel.jsx +1 -1
  56. package/client/components/sidebar/sidebar.styl +3 -3
  57. package/client/components/sys-menu/icons-map.jsx +52 -0
  58. package/client/components/{context-menu → sys-menu}/menu-btn.jsx +33 -45
  59. package/client/components/sys-menu/sys-menu.jsx +163 -0
  60. package/client/components/{context-menu/context-menu.styl → sys-menu/sys-menu.styl} +2 -11
  61. package/client/components/tabs/index.jsx +5 -97
  62. package/client/components/tabs/tab.jsx +121 -73
  63. package/client/components/tabs/tabs.styl +4 -1
  64. package/client/components/terminal/term-search.jsx +16 -28
  65. package/client/components/terminal/terminal-interactive.jsx +0 -2
  66. package/client/components/terminal/{index.jsx → terminal.jsx} +110 -240
  67. package/client/components/terminal-info/base.jsx +21 -46
  68. package/client/components/terminal-info/terminal-info.jsx +3 -0
  69. package/client/components/text-editor/text-editor.jsx +38 -53
  70. package/client/components/theme/theme-form.jsx +0 -2
  71. package/client/components/tree-list/bookmark-toolbar.jsx +23 -47
  72. package/client/components/tree-list/bookmark-transport.jsx +2 -90
  73. package/client/components/tree-list/move-item-modal.jsx +101 -0
  74. package/client/components/tree-list/tree-list-item.jsx +6 -8
  75. package/client/components/tree-list/tree-list.jsx +48 -273
  76. package/client/components/vnc/vnc-session.jsx +5 -3
  77. package/client/store/app-upgrade.js +2 -5
  78. package/client/store/bookmark-group.js +74 -28
  79. package/client/store/common.js +36 -54
  80. package/client/store/event.js +4 -37
  81. package/client/store/init-state.js +9 -12
  82. package/client/store/item.js +34 -39
  83. package/client/store/load-data.js +5 -1
  84. package/client/store/quick-command.js +2 -12
  85. package/client/store/session.js +6 -7
  86. package/client/store/setting.js +3 -7
  87. package/client/store/sidebar.js +2 -8
  88. package/client/store/store.js +0 -20
  89. package/client/store/system-menu.js +1 -2
  90. package/client/store/tab.js +29 -1
  91. package/client/store/terminal-theme.js +0 -4
  92. package/client/store/watch.js +26 -4
  93. package/package.json +1 -1
  94. package/client/common/post-msg.js +0 -3
  95. package/client/components/common/native-input.jsx +0 -30
  96. package/client/components/context-menu/context-menu.jsx +0 -339
  97. package/client/components/sftp/file-props-modal.jsx +0 -210
  98. package/client/store/context-menu.js +0 -23
  99. /package/client/components/{context-menu → sys-menu}/boomarks.jsx +0 -0
  100. /package/client/components/{context-menu → sys-menu}/history.jsx +0 -0
  101. /package/client/components/{context-menu → sys-menu}/icon-holder.jsx +0 -0
  102. /package/client/components/{context-menu → sys-menu}/sub-tab-menu.jsx +0 -0
  103. /package/client/components/{context-menu → sys-menu}/tabs.jsx +0 -0
  104. /package/client/components/{context-menu → sys-menu}/zoom.jsx +0 -0
@@ -3,9 +3,6 @@
3
3
  */
4
4
 
5
5
  import { message } from 'antd'
6
- import {
7
- copyBookmarkItemPrefix
8
- } from './constants'
9
6
 
10
7
  const fileRegWin = /^(remote:)?\w:\\.+/
11
8
  const fileReg = /^(remote:)?\/.+/
@@ -24,7 +21,7 @@ export const readClipboardAsync = () => {
24
21
 
25
22
  export const copy = (str) => {
26
23
  message.success({
27
- content: 'Copied',
24
+ content: window.translate('copied'),
28
25
  duation: 2,
29
26
  key: 'copy-message'
30
27
  })
@@ -45,13 +42,3 @@ export const hasFileInClipboardText = (
45
42
  (fileReg.test(t) || fileRegWin.test(t))
46
43
  }, true)
47
44
  }
48
-
49
- export const hasBookmarkOrGroupInClipboardText = (
50
- text = readClipboard()
51
- ) => {
52
- const arr = text.split('\n')
53
- return arr.reduce((prev, t = '') => {
54
- return prev &&
55
- t.startsWith(copyBookmarkItemPrefix)
56
- }, true)
57
- }
@@ -171,10 +171,6 @@ export const defaultTheme = {
171
171
  uiThemeConfig: getUiThemeConfig(window.et.stylus)
172
172
  }
173
173
 
174
- export const eventTypes = {
175
- resetFileListTable: 'reset-file-list-table'
176
- }
177
-
178
174
  export const commonBaudRates = [
179
175
  110,
180
176
  300,
@@ -229,17 +225,6 @@ const defaultThemeLightConf = _get(
229
225
  )
230
226
  defaultThemeLightConf.id = defaultThemeLightConf._id
231
227
  export const defaultThemeLight = defaultThemeLightConf
232
- export const terminalActions = {
233
- showInfoPanel: 'show-info-panel',
234
- changeEncode: 'change-encode',
235
- batchInput: 'batch-input',
236
- quickCommand: 'quick-command',
237
- openTerminalSearch: 'open-terminal-search',
238
- doSearchNext: 'do-search-next',
239
- doSearchPrev: 'do-search-prev',
240
- clearSearch: 'clear-search',
241
- zoom: 'zoom-terminal'
242
- }
243
228
  export const fileActions = {
244
229
  cancel: 'cancel',
245
230
  skip: 'skip',
@@ -250,32 +235,6 @@ export const fileActions = {
250
235
  renameAll: 'renameAll'
251
236
  }
252
237
 
253
- export const commonActions = {
254
- returnTermLogState: 'return-term-log-state',
255
- getTermLogState: 'get-term-log-state',
256
- setTermLogState: 'set-term-log-state',
257
- batchOp: 'batch-op',
258
- updateStore: 'update-store',
259
- editWithSystemEditorDone: 'edit-with-system-editor-done',
260
- editWithSystemEditor: 'edit-with-system-editor',
261
- onCloseTextEditor: 'on-close-text-editor',
262
- submitTextEditorText: 'submit-text-editor-text',
263
- fetchTextEditorText: 'fetch-text-editor-text',
264
- loadTextEditorText: 'load-text-editor-text',
265
- openTextEditor: 'open-text-editor',
266
- submitFileModeEdit: 'submit-file-mode-edit',
267
- submitFileModeClose: 'submit-file-mode-close',
268
- showFileModeModal: 'show-file-mode-modal',
269
- showFileInfoModal: 'show-file-info-modal',
270
- appUpdateCheck: 'check-app-update',
271
- closeContextMenu: 'close-context-menu',
272
- closeContextMenuAfter: 'close-context-menu-after',
273
- clickContextMenu: 'click-context-menu',
274
- openContextMenu: 'open-context-menu',
275
- addTransfer: 'add-transfer',
276
- sftpList: 'sftp-list'
277
- }
278
-
279
238
  export const srcsSkipUpgradeCheck = [
280
239
  '.appx',
281
240
  '.snap',
@@ -283,8 +242,6 @@ export const srcsSkipUpgradeCheck = [
283
242
  ]
284
243
  export const termLSPrefix = 'term:sess:'
285
244
  export const batchInputLsKey = 'batch-inputs'
286
- export const copyBookmarkItemPrefix = 'bookmark:'
287
- export const copyBookmarkGroupItemPrefix = 'bookmarkGroup:'
288
245
  export const rendererTypes = {
289
246
  dom: 'dom',
290
247
  canvas: 'canvas',
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Compare two arrays of objects and return differences
3
+ * @param {Array} oldArr - Original array of objects
4
+ * @param {Array} newArr - New array of objects to compare against
5
+ * @returns {Object} Object containing arrays of items to update, add, and remove
6
+ */
7
+ export default function compare (oldArr, newArr) {
8
+ if (!oldArr || !newArr) {
9
+ return {
10
+ updated: [],
11
+ added: [],
12
+ removed: []
13
+ }
14
+ }
15
+ // Create maps for faster lookup
16
+ const oldMap = new Map(
17
+ oldArr.map(item => [item.id, item])
18
+ )
19
+ const newMap = new Map(
20
+ newArr.map(item => [item.id, item])
21
+ )
22
+
23
+ const updated = []
24
+ const added = []
25
+ const removed = []
26
+
27
+ // Find items to update or add
28
+ for (const item of newArr) {
29
+ const oldItem = oldMap.get(item.id)
30
+ if (!oldItem) {
31
+ // Item doesn't exist in old array - need to add
32
+ added.push(item)
33
+ } else {
34
+ // Item exists - check if it needs updating
35
+ // Convert to JSON strings for deep comparison
36
+ const oldStr = JSON.stringify(oldItem)
37
+ const newStr = JSON.stringify(item)
38
+ if (oldStr !== newStr) {
39
+ updated.push(item)
40
+ }
41
+ }
42
+ }
43
+
44
+ // Find items to remove
45
+ for (const item of oldArr) {
46
+ if (!newMap.has(item.id)) {
47
+ removed.push(item)
48
+ }
49
+ }
50
+ return {
51
+ updated,
52
+ added,
53
+ removed
54
+ }
55
+ }
@@ -16,7 +16,7 @@ export default {
16
16
  execMacArgs: [],
17
17
  execLinuxArgs: [],
18
18
  enableGlobalProxy: false,
19
- disableSshHistory: false,
19
+ disableConnectionHistory: false,
20
20
  disableTransferHistory: false,
21
21
  terminalBackgroundImagePath: '',
22
22
  terminalBackgroundFilterOpacity: 1,
@@ -61,13 +61,5 @@ export default {
61
61
  dataSyncSelected: 'all',
62
62
  baseURLAI: 'https://api.deepseek.com',
63
63
  modelAI: 'deepseek-chat',
64
- roleAI: `You are a terminal command expert.
65
- - Provide clear, safe, and efficient shell commands
66
- - Always explain what each command does
67
- - Warn about potentially dangerous operations
68
- - Format command output with markdown code blocks
69
- - If multiple steps are needed, number them
70
- - Mention any prerequisites or dependencies
71
- - Include common flags and options
72
- - Specify which OS (Linux/Mac/Windows) the command is for`
64
+ roleAI: '终端专家,提供不同系统下安全命令,解释用法及风险,用markdown格式'
73
65
  }
@@ -6,31 +6,27 @@
6
6
  */
7
7
 
8
8
  export default (basePath, nameOrDot) => {
9
- const sep = (basePath.includes('\\') ||
10
- basePath.includes(':\\') ||
11
- /^[a-z]+:$/i.test(basePath) ||
12
- /^[a-z]+:$/i.test(nameOrDot)
13
- )
14
- ? '\\'
15
- : '/'
9
+ const hasWinDrive = (path) => /^[a-zA-Z]:/.test(path)
10
+ const isWin = basePath.includes('\\') || nameOrDot.includes('\\') || hasWinDrive(basePath) || hasWinDrive(nameOrDot)
11
+ const sep = isWin ? '\\' : '/'
12
+ // Handle Windows drive letters (with or without initial slash)
13
+ if (/^[a-zA-Z]:/.test(nameOrDot)) {
14
+ return nameOrDot.replace(/^\//, '').replace(/\//g, sep)
15
+ }
16
+ // Handle absolute paths
17
+ if (nameOrDot.startsWith('/')) {
18
+ return nameOrDot.replace(/\\/g, sep)
19
+ }
16
20
  if (nameOrDot === '..') {
17
- const arr = basePath.split(sep)
18
- const { length } = arr
19
- if (length === 1) {
20
- return '/'
21
+ const parts = basePath.split(sep)
22
+ if (parts.length > 1) {
23
+ parts.pop()
24
+ return isWin && parts.length === 1 ? '/' : parts.join(sep) || '/'
21
25
  }
22
- const res = arr.slice(0, length - 1).join(sep)
23
- return res || '/'
24
- }
25
- const pre = (nameOrDot.includes(':\\') || /^[a-z]+:$/i.test(nameOrDot)) && basePath === '/'
26
- ? ''
27
- : basePath
28
- const mid = (basePath.endsWith(sep) ? '' : sep)
29
- let ff = pre + mid + nameOrDot
30
- if (/^\\[a-z]+:$/i.test(ff)) {
31
- ff = ff.slice(1)
26
+ return '/'
32
27
  }
33
- return ff
28
+ const result = basePath.endsWith(sep) ? basePath + nameOrDot : basePath + sep + nameOrDot
29
+ return isWin && result.length === 3 && result.endsWith(':\\') ? '/' : result
34
30
  }
35
31
 
36
32
  export const osResolve = (...args) => {
@@ -7,7 +7,6 @@ import Transfer from './transfer'
7
7
  import { transferTypeMap, instSftpKeys as keys } from './constants'
8
8
  import initWs from './ws'
9
9
 
10
- window.sftps = {}
11
10
  const transferKeys = Object.keys(transferTypeMap)
12
11
 
13
12
  class Sftp {
@@ -57,7 +56,6 @@ class Sftp {
57
56
  }
58
57
 
59
58
  async destroy () {
60
- delete window.sftps[this.sessionId]
61
59
  const { ws } = this
62
60
  ws.s({
63
61
  action: 'sftp-destroy',
@@ -71,6 +69,5 @@ class Sftp {
71
69
  export default async (sessionId) => {
72
70
  const sftp = new Sftp()
73
71
  await sftp.init(sessionId)
74
- window.sftps[sessionId] = sftp
75
72
  return sftp
76
73
  }
@@ -15,6 +15,7 @@ import {
15
15
  aiConfigWikiLink
16
16
  } from '../../common/constants'
17
17
  import HelpIcon from '../common/help-icon'
18
+ import { refsStatic } from '../common/ref'
18
19
  import './ai.styl'
19
20
 
20
21
  const { TextArea } = Input
@@ -34,7 +35,12 @@ export default function AIChat (props) {
34
35
  setPrompt(e.target.value)
35
36
  }
36
37
 
37
- async function handleSubmit () {
38
+ function buildRole () {
39
+ const lang = props.config.languageAI || window.store.getLangName()
40
+ return props.config.roleAI + `;用[${lang}]回复`
41
+ }
42
+
43
+ const handleSubmit = useCallback(async function () {
38
44
  if (aiConfigMissing()) {
39
45
  window.store.toggleAIConfig()
40
46
  }
@@ -44,7 +50,7 @@ export default function AIChat (props) {
44
50
  'AIchat',
45
51
  prompt,
46
52
  props.config.modelAI,
47
- props.config.roleAI,
53
+ buildRole(),
48
54
  props.config.baseURLAI,
49
55
  props.config.apiKeyAI
50
56
  ).catch(
@@ -72,19 +78,26 @@ export default function AIChat (props) {
72
78
  }
73
79
  setPrompt('')
74
80
  setIsLoading(false)
75
- }
81
+ }, [prompt, isLoading, props.config])
76
82
 
77
83
  function handleConfigSubmit (values) {
78
84
  window.store.updateConfig(values)
79
85
  message.success('Saved')
80
86
  }
81
87
 
88
+ function getInitialValues () {
89
+ const res = pick(props.config, aiConfigsArr)
90
+ if (!res.languageAI) {
91
+ res.languageAI = window.store.getLangName()
92
+ }
93
+ return res
94
+ }
95
+
82
96
  const renderConfig = useCallback(() => {
83
97
  if (!props.showAIConfig) return null
84
- const aiConfigs = pick(props.config, aiConfigsArr)
85
98
  return (
86
99
  <AIConfigForm
87
- initialValues={aiConfigs}
100
+ initialValues={getInitialValues()}
88
101
  onSubmit={handleConfigSubmit}
89
102
  showAIConfig={props.showAIConfig}
90
103
  />
@@ -124,10 +137,21 @@ export default function AIChat (props) {
124
137
  }
125
138
 
126
139
  useEffect(() => {
140
+ refsStatic.add('AIChat', {
141
+ setPrompt,
142
+ handleSubmit
143
+ })
127
144
  if (aiConfigMissing()) {
128
145
  window.store.toggleAIConfig()
129
146
  }
130
- }, [])
147
+ return () => {
148
+ refsStatic.remove('AIChat')
149
+ }
150
+ }, [handleSubmit])
151
+
152
+ if (props.rightPanelTab !== 'ai') {
153
+ return null
154
+ }
131
155
 
132
156
  return (
133
157
  <Flex vertical className='ai-chat-container'>
@@ -16,6 +16,14 @@ import {
16
16
  import providers from './providers'
17
17
 
18
18
  const e = window.translate
19
+ const defaultRoles = [
20
+ {
21
+ value: 'Terminal expert, provide safe commands for different OS, explain usage and risks, use markdown format'
22
+ },
23
+ {
24
+ value: '终端专家,提供不同系统下安全命令,解释用法及风险,用markdown格式'
25
+ }
26
+ ]
19
27
 
20
28
  export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig }) {
21
29
  const [form] = Form.useForm()
@@ -86,6 +94,7 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
86
94
  form={form}
87
95
  onFinish={handleSubmit}
88
96
  initialValues={initialValues}
97
+ layout='vertical'
89
98
  >
90
99
  <Form.Item
91
100
  label='API URL'
@@ -105,7 +114,7 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
105
114
  </Form.Item>
106
115
 
107
116
  <Form.Item
108
- label='Model'
117
+ label={e('modelAi')}
109
118
  name='modelAI'
110
119
  rules={[{ required: true, message: 'Please input or select a model!' }]}
111
120
  >
@@ -124,14 +133,16 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
124
133
  </Form.Item>
125
134
 
126
135
  <Form.Item
127
- label='System Role'
136
+ label={e('roleAI')}
128
137
  name='roleAI'
129
138
  rules={[{ required: true, message: 'Please input the AI role!' }]}
130
139
  >
131
- <Input.TextArea
132
- placeholder='Enter AI role/system prompt'
133
- rows={4}
134
- />
140
+ <AutoComplete options={defaultRoles} placement='topLeft'>
141
+ <Input.TextArea
142
+ placeholder='Enter AI role/system prompt'
143
+ rows={1}
144
+ />
145
+ </AutoComplete>
135
146
  </Form.Item>
136
147
 
137
148
  <Form.Item>
@@ -17,7 +17,6 @@ import {
17
17
  import {
18
18
  sidebarWidth,
19
19
  statusMap,
20
- commonActions,
21
20
  batchOpHelpLink,
22
21
  modals
23
22
  } from '../../common/constants'
@@ -31,6 +30,7 @@ import uid from '../../common/uid'
31
30
  import wait from '../../common/wait'
32
31
  import { getFolderFromFilePath } from '../sftp/file-read'
33
32
  import resolveFilePath from '../../common/resolve'
33
+ import { refsStatic } from '../common/ref'
34
34
 
35
35
  const e = window.translate
36
36
 
@@ -85,19 +85,8 @@ export default class BatchOp extends PureComponent {
85
85
  ]
86
86
 
87
87
  componentDidMount () {
88
- this.watch()
89
- }
90
-
91
- componentWillUnmount () {
92
- this.unwatch()
93
- }
94
-
95
- watch () {
96
- window.addEventListener('message', this.handleEvent)
97
- }
98
-
99
- unwatch () {
100
- window.removeEventListener('message', this.handleEvent)
88
+ this.id = 'batch-op'
89
+ refsStatic.add(this.id, this)
101
90
  }
102
91
 
103
92
  handleDownloadExample = () => {
@@ -109,16 +98,6 @@ export default class BatchOp extends PureComponent {
109
98
  download('batch-op-example.csv', csvText)
110
99
  }
111
100
 
112
- handleEvent = e => {
113
- if (e && e.data && e.data.action === commonActions.batchOp) {
114
- const {
115
- func,
116
- args
117
- } = e.data.batchOp
118
- this[func](...args)
119
- }
120
- }
121
-
122
101
  handleCancel = () => {
123
102
  window.store.toggleBatchOp()
124
103
  }
@@ -2,16 +2,10 @@
2
2
  * create bookmark group tree data
3
3
  */
4
4
 
5
- export default (bookmarkGroups = []) => {
6
- const btree = bookmarkGroups
7
- .reduce((prev, k) => {
8
- return {
9
- ...prev,
10
- [k.id]: k
11
- }
12
- }, {})
5
+ export default (bookmarkGroups = [], disabledId = '', returnMap = false) => {
6
+ const btree = new Map(bookmarkGroups.map(d => [d.id, d]))
13
7
  function buildSubCats (id) {
14
- const x = btree[id]
8
+ const x = btree.get(id)
15
9
  if (!x) {
16
10
  return ''
17
11
  }
@@ -32,9 +26,13 @@ export default (bookmarkGroups = []) => {
32
26
  title: d.title,
33
27
  value: d.id,
34
28
  key: d.id,
29
+ disabled: d.id === disabledId,
35
30
  children: (d.bookmarkGroupIds || []).map(buildSubCats).filter(d => d)
36
31
  }
37
32
  return r
38
33
  }).filter(d => d)
34
+ if (returnMap) {
35
+ return [level1, btree]
36
+ }
39
37
  return level1
40
38
  }
@@ -32,7 +32,6 @@ const e = window.translate
32
32
 
33
33
  export default function renderCommon (props) {
34
34
  const {
35
- autofocustrigger,
36
35
  bookmarkGroups = [],
37
36
  ips,
38
37
  form,
@@ -85,7 +84,6 @@ export default function renderCommon (props) {
85
84
  }
86
85
  <FormItem noStyle name='host'>
87
86
  <InputAutoFocus
88
- autofocustrigger={autofocustrigger}
89
87
  selectall='yes'
90
88
  name='host'
91
89
  onBlur={props.onBlur}
@@ -5,7 +5,7 @@ import { PureComponent } from 'react'
5
5
  import {
6
6
  message
7
7
  } from 'antd'
8
- import { uniq, isEqual, pick } from 'lodash-es'
8
+ import { uniq, pick } from 'lodash-es'
9
9
  import copy from 'json-deep-copy'
10
10
  import generate from '../../common/uid'
11
11
  import {
@@ -20,6 +20,7 @@ import testCon from '../../common/test-connection'
20
20
  import FormUi from './ssh-form-ui'
21
21
  import findBookmarkGroupId from '../../common/find-bookmark-group-id'
22
22
  import newTerm from '../../common/new-terminal'
23
+ import { action } from 'manate'
23
24
 
24
25
  export default class BookmarkForm extends PureComponent {
25
26
  state = {
@@ -95,7 +96,10 @@ export default class BookmarkForm extends PureComponent {
95
96
  : currentBookmarkGroupId
96
97
  }
97
98
 
98
- updateBookmarkGroups = (bookmarkGroups, bookmark, categoryId) => {
99
+ updateBookmarkGroups = action((bookmark, categoryId) => {
100
+ const {
101
+ bookmarkGroups
102
+ } = window.store
99
103
  let index = bookmarkGroups.findIndex(
100
104
  bg => bg.id === categoryId
101
105
  )
@@ -106,50 +110,21 @@ export default class BookmarkForm extends PureComponent {
106
110
  }
107
111
  const bid = bookmark.id
108
112
  const bg = bookmarkGroups[index]
109
- const updates = []
110
- const old = copy(bg.bookmarkIds)
111
113
  if (!bg.bookmarkIds.includes(bid)) {
112
114
  bg.bookmarkIds.unshift(bid)
113
115
  }
114
116
  bg.bookmarkIds = uniq(bg.bookmarkIds)
115
- if (!isEqual(bg.bookmarkIds, old)) {
116
- updates.push({
117
- id: bg.id,
118
- db: 'bookmarkGroups',
119
- update: {
120
- bookmarkIds: bg.bookmarkIds
121
- }
122
- })
123
- }
124
- bookmarkGroups = bookmarkGroups.map((bg, i) => {
117
+ bookmarkGroups.forEach((bg, i) => {
125
118
  if (i === index) {
126
119
  return bg
127
120
  }
128
- const olde = copy(bg.bookmarkIds)
129
121
  bg.bookmarkIds = bg.bookmarkIds.filter(
130
122
  g => g !== bid
131
123
  )
132
- if (!isEqual(bg.bookmarkIds, olde)) {
133
- updates.push({
134
- id: bg.id,
135
- db: 'bookmarkGroups',
136
- update: {
137
- bookmarkIds: bg.bookmarkIds
138
- }
139
- })
140
- }
141
124
  return bg
142
125
  })
143
- runIdle(() => {
144
- this.props.store.setBookmarkGroups(
145
- bookmarkGroups
146
- )
147
- })
148
- runIdle(() => {
149
- this.props.store.batchDbUpdate(updates)
150
- })
151
126
  message.success('OK', 3)
152
- }
127
+ })
153
128
 
154
129
  submit = (evt, item, type = this.props.type) => {
155
130
  if (item.host) {
@@ -162,9 +137,6 @@ export default class BookmarkForm extends PureComponent {
162
137
  const { addItem, editItem } = this.props.store
163
138
  const categoryId = obj.category
164
139
  delete obj.category
165
- const bookmarkGroups = copy(
166
- this.props.bookmarkGroups
167
- )
168
140
  if (!obj.id.startsWith(newBookmarkIdPrefix)) {
169
141
  const tar = copy(obj)
170
142
  delete tar.id
@@ -172,7 +144,6 @@ export default class BookmarkForm extends PureComponent {
172
144
  editItem(obj.id, tar, settingMap.bookmarks)
173
145
  })
174
146
  this.updateBookmarkGroups(
175
- bookmarkGroups,
176
147
  obj,
177
148
  categoryId
178
149
  )
@@ -185,7 +156,6 @@ export default class BookmarkForm extends PureComponent {
185
156
  addItem(obj, settingMap.bookmarks)
186
157
  })
187
158
  this.updateBookmarkGroups(
188
- bookmarkGroups,
189
159
  obj,
190
160
  categoryId
191
161
  )
@@ -200,9 +170,6 @@ export default class BookmarkForm extends PureComponent {
200
170
  settingItem = getInitItem([], settingMap.bookmarks)
201
171
  ) => {
202
172
  const { store } = this.props
203
- this.props.store.storeAssign({
204
- autofocustrigger: Date.now()
205
- })
206
173
  store.setSettingItem(settingItem)
207
174
  }
208
175
 
@@ -5,6 +5,7 @@ import {
5
5
  } from 'antd'
6
6
  import { defaultBookmarkGroupId, settingMap } from '../../common/constants'
7
7
  import deepCopy from 'json-deep-copy'
8
+ import { action } from 'manate'
8
9
 
9
10
  const e = window.translate
10
11
 
@@ -20,23 +21,13 @@ export default class BookmarkTreeDelete extends StartSessionSelect {
20
21
  handleDel = () => {
21
22
  const { store } = window
22
23
  const {
23
- checkedKeys,
24
- bookmarks,
25
- bookmarkGroups
24
+ checkedKeys
26
25
  } = this.props
27
- store.setBookmarks(
28
- bookmarks.filter(f => {
29
- return !checkedKeys.includes(f.id)
30
- })
31
- )
32
- store.setBookmarkGroups(
33
- bookmarkGroups.filter(f => {
34
- return f.id === defaultBookmarkGroupId || !checkedKeys.includes(f.id)
35
- })
36
- )
37
26
  const arr = checkedKeys.filter(d => d !== defaultBookmarkGroupId)
38
- store.delItems(arr, settingMap.bookmarks)
39
- store.delItems(arr, settingMap.bookmarkGroups)
27
+ action(() => {
28
+ store.delItems(arr, settingMap.bookmarks)
29
+ store.delItems(arr, settingMap.bookmarkGroups)
30
+ })
40
31
  store.checkedKeys = []
41
32
  }
42
33
 
@@ -3,17 +3,16 @@
3
3
  */
4
4
 
5
5
  import React from 'react'
6
- import uid from '../../common/uid'
7
6
  import './animate-text.styl'
8
7
 
9
8
  export default class AnimateText extends React.PureComponent {
10
9
  constructor (props) {
11
10
  super(props)
12
- this.uid = 'AnimateText-' + uid()
11
+ this.textRef = React.createRef()
13
12
  }
14
13
 
15
14
  componentDidUpdate () {
16
- const dom = document.getElementById(this.uid)
15
+ const dom = this.textRef.current
17
16
  dom.className = (this.props.className || 'animate-text-wrap') + ' animated bounceIn'
18
17
  this.timer = setTimeout(() => {
19
18
  if (dom) {
@@ -29,7 +28,7 @@ export default class AnimateText extends React.PureComponent {
29
28
  render () {
30
29
  const { children, className } = this.props
31
30
  return (
32
- <div className={className} id={this.uid}>
31
+ <div className={className} ref={this.textRef}>
33
32
  {children}
34
33
  </div>
35
34
  )