@electerm/electerm-react 1.100.20 → 1.100.46

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 (33) hide show
  1. package/client/common/constants.js +2 -1
  2. package/client/common/default-setting.js +6 -1
  3. package/client/common/get-category-color.js +19 -0
  4. package/client/components/ai/ai-chat-history.jsx +1 -1
  5. package/client/components/ai/ai-chat.jsx +108 -22
  6. package/client/components/ai/ai-output.jsx +8 -8
  7. package/client/components/ai/ai.styl +6 -6
  8. package/client/components/bg/css-overwrite.jsx +52 -4
  9. package/client/components/bookmark-form/bookmark-category-select.jsx +80 -0
  10. package/client/components/bookmark-form/form-ssh-common.jsx +6 -14
  11. package/client/components/bookmark-form/ftp-form-ui.jsx +7 -16
  12. package/client/components/bookmark-form/local-form-ui.jsx +7 -16
  13. package/client/components/bookmark-form/rdp-form-ui.jsx +7 -16
  14. package/client/components/bookmark-form/serial-form-ui.jsx +9 -17
  15. package/client/components/bookmark-form/ssh-form-ui.jsx +7 -3
  16. package/client/components/bookmark-form/telnet-form-ui.jsx +2 -2
  17. package/client/components/bookmark-form/vnc-form-ui.jsx +7 -16
  18. package/client/components/bookmark-form/web-form-ui.jsx +7 -16
  19. package/client/components/main/main.jsx +4 -2
  20. package/client/components/main/term-fullscreen.styl +18 -7
  21. package/client/components/session/session.jsx +3 -1
  22. package/client/components/setting-panel/setting-common.jsx +1 -0
  23. package/client/components/setting-panel/terminal-bg-config.jsx +82 -5
  24. package/client/components/setting-panel/text-bg-modal.jsx +131 -0
  25. package/client/components/sftp/sftp-entry.jsx +8 -3
  26. package/client/components/terminal/terminal.jsx +4 -4
  27. package/client/components/tree-list/category-color-picker.jsx +11 -0
  28. package/client/components/tree-list/tree-list-item.jsx +11 -1
  29. package/client/components/tree-list/tree-list.jsx +54 -7
  30. package/client/components/tree-list/tree-list.styl +10 -1
  31. package/client/store/init-state.js +3 -1
  32. package/client/store/sync.js +6 -1
  33. package/package.json +1 -1
@@ -24,7 +24,8 @@ export const maxEditFileSize = 1024 * 3000
24
24
  export const defaultBookmarkGroupId = 'default'
25
25
  export const newBookmarkIdPrefix = 'new-bookmark'
26
26
  export const unexpectedPacketErrorDesc = 'Unexpected packet'
27
- export const noTerminalBgValue = 'no-termimal-bg'
27
+ export const noTerminalBgValue = '[🚫]'
28
+ export const textTerminalBgValue = '[📝]'
28
29
  export const sftpRetryInterval = 3000
29
30
  export const maxBookmarkGroupTitleLength = 33
30
31
  export const termControlHeight = 32
@@ -24,6 +24,10 @@ export default {
24
24
  terminalBackgroundFilterBrightness: 1,
25
25
  terminalBackgroundFilterGrayscale: 0,
26
26
  terminalBackgroundFilterContrast: 1,
27
+ terminalBackgroundText: '',
28
+ terminalBackgroundTextSize: 48,
29
+ terminalBackgroundTextColor: '#ffffff',
30
+ terminalBackgroundTextFontFamily: 'Maple Mono',
27
31
  rendererType: 'canvas',
28
32
  terminalType: 'xterm-256color',
29
33
  keepaliveCountMax: 10,
@@ -68,5 +72,6 @@ export default {
68
72
  sshSftpSplitView: false,
69
73
  showCmdSuggestions: false,
70
74
  startDirectoryLocal: '',
71
- allowMultiInstance: false
75
+ allowMultiInstance: false,
76
+ disableDeveloperTool: false
72
77
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Get default color from current category or random color
3
+ */
4
+
5
+ import { getRandomDefaultColor } from './rand-hex-color.js'
6
+
7
+ export function getColorFromCategory (bookmarkGroups, currentBookmarkGroupId) {
8
+ if (!currentBookmarkGroupId || !bookmarkGroups) {
9
+ return getRandomDefaultColor()
10
+ }
11
+
12
+ const currentGroup = bookmarkGroups.find(group => group.id === currentBookmarkGroupId)
13
+
14
+ if (currentGroup && currentGroup.color) {
15
+ return currentGroup.color
16
+ }
17
+
18
+ return getRandomDefaultColor()
19
+ }
@@ -10,7 +10,7 @@ export default auto(function AIChatHistory ({ history }) {
10
10
  if (historyRef.current) {
11
11
  historyRef.current.scrollTop = historyRef.current.scrollHeight
12
12
  }
13
- }, [history.length])
13
+ }, [history])
14
14
  if (!history.length) {
15
15
  return <div />
16
16
  }
@@ -39,42 +39,128 @@ export default function AIChat (props) {
39
39
  }
40
40
  if (!prompt.trim() || isLoading) return
41
41
  setIsLoading(true)
42
- const aiResponse = await window.pre.runGlobalAsync(
43
- 'AIchat',
44
- prompt,
45
- props.config.modelAI,
46
- buildRole(),
47
- props.config.baseURLAI,
48
- props.config.apiPathAI,
49
- props.config.apiKeyAI,
50
- props.config.proxyAI
51
- ).catch(
52
- window.store.onError
53
- )
54
- if (aiResponse && aiResponse.error) {
55
- return window.store.onError(
56
- new Error(aiResponse.error)
57
- )
58
- }
59
- window.store.aiChatHistory.push({
42
+
43
+ // Create a placeholder entry for the streaming response
44
+ const chatId = uid()
45
+ const chatEntry = {
60
46
  prompt,
61
- response: aiResponse.response,
47
+ response: '', // Will be updated as stream arrives
48
+ isStreaming: false,
49
+ sessionId: null,
62
50
  ...pick(props.config, [
63
51
  'modelAI',
64
52
  'roleAI',
65
53
  'baseURLAI'
66
54
  ]),
67
55
  timestamp: Date.now(),
68
- id: uid()
69
- })
56
+ id: chatId
57
+ }
58
+
59
+ window.store.aiChatHistory.push(chatEntry)
60
+
61
+ try {
62
+ const aiResponse = await window.pre.runGlobalAsync(
63
+ 'AIchat',
64
+ prompt,
65
+ props.config.modelAI,
66
+ buildRole(),
67
+ props.config.baseURLAI,
68
+ props.config.apiPathAI,
69
+ props.config.apiKeyAI,
70
+ props.config.proxyAI,
71
+ true // Enable streaming for chat
72
+ )
73
+
74
+ if (aiResponse && aiResponse.error) {
75
+ // Remove the placeholder entry and show error
76
+ const index = window.store.aiChatHistory.findIndex(item => item.id === chatId)
77
+ if (index !== -1) {
78
+ window.store.aiChatHistory.splice(index, 1)
79
+ }
80
+ setIsLoading(false)
81
+ return window.store.onError(
82
+ new Error(aiResponse.error)
83
+ )
84
+ }
85
+
86
+ if (aiResponse && aiResponse.isStream && aiResponse.sessionId) {
87
+ // Handle streaming response with polling
88
+ const index = window.store.aiChatHistory.findIndex(item => item.id === chatId)
89
+ if (index !== -1) {
90
+ window.store.aiChatHistory[index].isStreaming = true
91
+ window.store.aiChatHistory[index].sessionId = aiResponse.sessionId
92
+ window.store.aiChatHistory[index].response = aiResponse.content || ''
93
+ }
94
+
95
+ // Start polling for updates
96
+ pollStreamContent(aiResponse.sessionId, chatId)
97
+ } else if (aiResponse && aiResponse.response) {
98
+ // Handle non-streaming response (fallback)
99
+ const index = window.store.aiChatHistory.findIndex(item => item.id === chatId)
100
+ if (index !== -1) {
101
+ window.store.aiChatHistory[index].response = aiResponse.response
102
+ window.store.aiChatHistory[index].isStreaming = false
103
+ }
104
+ setIsLoading(false)
105
+ }
106
+ } catch (error) {
107
+ // Remove the placeholder entry and show error
108
+ const index = window.store.aiChatHistory.findIndex(item => item.id === chatId)
109
+ if (index !== -1) {
110
+ window.store.aiChatHistory.splice(index, 1)
111
+ }
112
+ setIsLoading(false)
113
+ window.store.onError(error)
114
+ }
70
115
 
71
116
  if (window.store.aiChatHistory.length > MAX_HISTORY) {
72
117
  window.store.aiChatHistory.splice(MAX_HISTORY)
73
118
  }
74
119
  setPrompt('')
75
- setIsLoading(false)
76
120
  }, [prompt, isLoading])
77
121
 
122
+ // Function to poll for streaming content updates
123
+ const pollStreamContent = async (sessionId, chatId) => {
124
+ try {
125
+ const streamResponse = await window.pre.runGlobalAsync('getStreamContent', sessionId)
126
+
127
+ if (streamResponse && streamResponse.error) {
128
+ // Remove the entry and show error
129
+ const index = window.store.aiChatHistory.findIndex(item => item.id === chatId)
130
+ if (index !== -1) {
131
+ window.store.aiChatHistory.splice(index, 1)
132
+ }
133
+ setIsLoading(false)
134
+ return window.store.onError(new Error(streamResponse.error))
135
+ }
136
+
137
+ // Update the chat entry with new content
138
+ const index = window.store.aiChatHistory.findIndex(item => item.id === chatId)
139
+ if (index !== -1) {
140
+ window.store.aiChatHistory[index].response = streamResponse.content || ''
141
+ window.store.aiChatHistory[index].isStreaming = streamResponse.hasMore
142
+
143
+ // Force re-render by updating the array reference
144
+ window.store.aiChatHistory = [...window.store.aiChatHistory]
145
+
146
+ // Continue polling if there's more content
147
+ if (streamResponse.hasMore) {
148
+ setTimeout(() => pollStreamContent(sessionId, chatId), 200) // Poll every 200ms
149
+ } else {
150
+ setIsLoading(false)
151
+ }
152
+ }
153
+ } catch (error) {
154
+ // Remove the entry and show error
155
+ const index = window.store.aiChatHistory.findIndex(item => item.id === chatId)
156
+ if (index !== -1) {
157
+ window.store.aiChatHistory.splice(index, 1)
158
+ }
159
+ setIsLoading(false)
160
+ window.store.onError(error)
161
+ }
162
+ }
163
+
78
164
  function renderHistory () {
79
165
  return (
80
166
  <AiChatHistory
@@ -40,22 +40,22 @@ export default function AIOutput ({ item }) {
40
40
 
41
41
  return (
42
42
  <div className='code-block'>
43
- <pre>
44
- <code className={className} {...rest}>
45
- {children}
46
- </code>
47
- </pre>
48
- <div className='code-block-actions'>
43
+ <div className='code-block-actions alignright'>
49
44
  <CopyOutlined
50
- className='code-action-icon pointer'
45
+ className='code-action-icon pointer iblock'
51
46
  onClick={copyToClipboard}
52
47
  title={e('copy')}
53
48
  />
54
49
  <PlayCircleOutlined
55
- className='code-action-icon pointer mg1l'
50
+ className='code-action-icon pointer mg1l iblock'
56
51
  onClick={runInTerminal}
57
52
  />
58
53
  </div>
54
+ <pre>
55
+ <code className={className} {...rest}>
56
+ {children}
57
+ </code>
58
+ </pre>
59
59
  </div>
60
60
  )
61
61
  }
@@ -22,11 +22,11 @@
22
22
  border-radius 3px
23
23
  pre
24
24
  margin-bottom 0
25
- .code-block-actions
26
- display none
27
- &:hover
28
- .code-block-actions
29
- display block
25
+ // .code-block-actions
26
+ // display block
27
+ // &:hover
28
+ // .code-block-actions
29
+ // display block
30
30
 
31
31
  .ai-chat-input
32
32
  position relative
@@ -63,4 +63,4 @@
63
63
  font-size 12px
64
64
  top 8px
65
65
  right -4px
66
- font-weight bold
66
+ font-weight bold
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { Component } from 'react'
5
5
  import fs from '../../common/fs'
6
- import { noTerminalBgValue } from '../../common/constants'
6
+ import { noTerminalBgValue, textTerminalBgValue } from '../../common/constants'
7
7
  import { generateMosaicBackground } from './shapes'
8
8
 
9
9
  export default class CssOverwrite extends Component {
@@ -20,7 +20,11 @@ export default class CssOverwrite extends Component {
20
20
  'terminalBackgroundFilterOpacity',
21
21
  'terminalBackgroundFilterBrightness',
22
22
  'terminalBackgroundFilterContrast',
23
- 'terminalBackgroundFilterGrayscale'
23
+ 'terminalBackgroundFilterGrayscale',
24
+ 'terminalBackgroundText',
25
+ 'terminalBackgroundTextSize',
26
+ 'terminalBackgroundTextColor',
27
+ 'terminalBackgroundTextFontFamily'
24
28
  ]
25
29
  const globalChanged = bgProps.some(prop => this.props[prop] !== nextProps[prop])
26
30
  if (globalChanged) {
@@ -59,7 +63,7 @@ export default class CssOverwrite extends Component {
59
63
  }
60
64
 
61
65
  // Common function to handle background image style creation
62
- createBackgroundStyle = async (imagePath) => {
66
+ createBackgroundStyle = async (imagePath, textBgProps = null) => {
63
67
  if (!imagePath || imagePath === '') {
64
68
  return ''
65
69
  }
@@ -73,6 +77,8 @@ export default class CssOverwrite extends Component {
73
77
  st = 'index'
74
78
  } else if (noTerminalBgValue === imagePath) {
75
79
  st = 'none'
80
+ } else if (textTerminalBgValue === imagePath) {
81
+ st = 'text'
76
82
  } else if (imagePath && !isWebImg) {
77
83
  content = await fs.readFileAsBase64(imagePath)
78
84
  .catch(log.error)
@@ -113,6 +119,27 @@ export default class CssOverwrite extends Component {
113
119
  const styles = []
114
120
  if (st === 'index') {
115
121
  styles.push(`content: '${tab.tabCount}'`)
122
+ } else if (st === 'text') {
123
+ const text = bg.terminalBackgroundText || this.props.terminalBackgroundText || ''
124
+ const size = bg.terminalBackgroundTextSize || this.props.terminalBackgroundTextSize || 48
125
+ const color = bg.terminalBackgroundTextColor || this.props.terminalBackgroundTextColor || '#ffffff'
126
+ const fontFamily = bg.terminalBackgroundTextFontFamily || this.props.terminalBackgroundTextFontFamily || 'monospace'
127
+ if (text) {
128
+ styles.push(
129
+ `content: '${text.replace(/'/g, "\\'").replace(/\n/g, '\\A ')}'`,
130
+ `font-size: ${size}px`,
131
+ `color: ${color}`,
132
+ 'white-space: pre-wrap',
133
+ 'word-wrap: break-word',
134
+ 'text-align: center',
135
+ 'display: flex',
136
+ 'align-items: center',
137
+ 'justify-content: center',
138
+ `font-family: ${fontFamily}`,
139
+ 'opacity: 0.3',
140
+ 'background-image: none' // Override default background when text is set
141
+ )
142
+ }
116
143
  } else if (st !== 'none') {
117
144
  styles.push(
118
145
  `background-image: ${st}`,
@@ -135,7 +162,28 @@ export default class CssOverwrite extends Component {
135
162
 
136
163
  const styles = []
137
164
 
138
- if (st !== 'none' && st !== 'index') {
165
+ if (st === 'text') {
166
+ const text = this.props.terminalBackgroundText || ''
167
+ const size = this.props.terminalBackgroundTextSize || 48
168
+ const color = this.props.terminalBackgroundTextColor || '#ffffff'
169
+ const fontFamily = this.props.terminalBackgroundTextFontFamily || 'monospace'
170
+ if (text) {
171
+ styles.push(
172
+ `content: '${text.replace(/'/g, "\\'").replace(/\n/g, '\\A ')}'`,
173
+ `font-size: ${size}px`,
174
+ `color: ${color}`,
175
+ 'white-space: pre-wrap',
176
+ 'word-wrap: break-word',
177
+ 'text-align: center',
178
+ 'display: flex',
179
+ 'align-items: center',
180
+ 'justify-content: center',
181
+ `font-family: ${fontFamily}`,
182
+ 'opacity: 0.3',
183
+ 'background-image: none' // Override default background when text is set
184
+ )
185
+ }
186
+ } else if (st !== 'none' && st !== 'index') {
139
187
  styles.push(
140
188
  `background-image: ${st}`,
141
189
  'background-position: center',
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Common bookmark category select component that automatically updates color when category changes
3
+ */
4
+
5
+ import { useEffect } from 'react'
6
+ import { TreeSelect, Form } from 'antd'
7
+ import formatBookmarkGroups from './bookmark-group-tree-format'
8
+
9
+ const FormItem = Form.Item
10
+ const e = window.translate
11
+
12
+ export default function BookmarkCategorySelect ({
13
+ bookmarkGroups = [],
14
+ form,
15
+ formItemLayout,
16
+ name = 'category',
17
+ onChange
18
+ }) {
19
+ const tree = formatBookmarkGroups(bookmarkGroups)
20
+
21
+ // Watch for category field changes using Form.useWatch
22
+ const categoryId = Form.useWatch(name, form)
23
+
24
+ // Find the selected category
25
+ const findCategory = (groups, id) => {
26
+ for (const group of groups) {
27
+ if (group.id === id) {
28
+ return group
29
+ }
30
+ if (group.bookmarkGroupTree && group.bookmarkGroupTree.length > 0) {
31
+ const found = findCategory(group.bookmarkGroupTree, id)
32
+ if (found) return found
33
+ }
34
+ }
35
+ return null
36
+ }
37
+
38
+ // Watch for category field changes and update color accordingly
39
+ useEffect(() => {
40
+ if (categoryId) {
41
+ const category = findCategory(bookmarkGroups, categoryId)
42
+ if (category && category.color) {
43
+ form.setFieldsValue({
44
+ color: category.color
45
+ })
46
+ }
47
+ }
48
+ }, [categoryId, bookmarkGroups, form])
49
+
50
+ const handleCategoryChange = (categoryId) => {
51
+ const category = findCategory(bookmarkGroups, categoryId)
52
+
53
+ // Only update the color field if the category has a color
54
+ if (category && category.color) {
55
+ form.setFieldsValue({
56
+ color: category.color
57
+ })
58
+ }
59
+
60
+ // Call the original onChange if provided
61
+ if (onChange) {
62
+ onChange(categoryId)
63
+ }
64
+ }
65
+
66
+ return (
67
+ <FormItem
68
+ {...formItemLayout}
69
+ label={e('bookmarkCategory')}
70
+ name={name}
71
+ >
72
+ <TreeSelect
73
+ treeData={tree}
74
+ treeDefaultExpandAll
75
+ showSearch
76
+ onChange={handleCategoryChange}
77
+ />
78
+ </FormItem>
79
+ )
80
+ }
@@ -5,7 +5,6 @@ import {
5
5
  Input,
6
6
  InputNumber,
7
7
  Radio,
8
- TreeSelect,
9
8
  Select,
10
9
  Form
11
10
  } from 'antd'
@@ -15,9 +14,9 @@ import {
15
14
  import { formItemLayout, tailFormItemLayout } from '../../common/form-layout'
16
15
  import InputAutoFocus from '../common/input-auto-focus'
17
16
  import encodes from './encodes'
18
- import formatBookmarkGroups from './bookmark-group-tree-format'
19
17
  import renderRunScripts from './render-delayed-scripts.jsx'
20
18
  import { ColorPickerItem } from './color-picker-item.jsx'
19
+ import BookmarkCategorySelect from './bookmark-category-select.jsx'
21
20
 
22
21
  import './bookmark-form.styl'
23
22
 
@@ -38,7 +37,6 @@ export default function renderCommon (props) {
38
37
  onChangeAuthType,
39
38
  filterAuthType = a => a
40
39
  } = props
41
- const tree = formatBookmarkGroups(bookmarkGroups)
42
40
  const authTypesFiltered = authTypes.filter(filterAuthType)
43
41
 
44
42
  // ips is ipaddress string[]
@@ -140,17 +138,11 @@ export default function renderCommon (props) {
140
138
  step={1}
141
139
  />
142
140
  </FormItem>
143
- <FormItem
144
- {...formItemLayout}
145
- label={e('bookmarkCategory')}
146
- name='category'
147
- >
148
- <TreeSelect
149
- treeData={tree}
150
- treeDefaultExpandAll
151
- showSearch
152
- />
153
- </FormItem>
141
+ <BookmarkCategorySelect
142
+ bookmarkGroups={bookmarkGroups}
143
+ form={form}
144
+ formItemLayout={formItemLayout}
145
+ />
154
146
  <FormItem
155
147
  {...formItemLayout}
156
148
  label={e('title')}
@@ -7,7 +7,6 @@ import {
7
7
  Input,
8
8
  Form,
9
9
  InputNumber,
10
- TreeSelect,
11
10
  Switch
12
11
  } from 'antd'
13
12
  import { formItemLayout } from '../../common/form-layout'
@@ -19,10 +18,10 @@ import useSubmit from './use-submit'
19
18
  import copy from 'json-deep-copy'
20
19
  import { defaults, isEmpty } from 'lodash-es'
21
20
  import { ColorPickerItem } from './color-picker-item.jsx'
22
- import { getRandomDefaultColor } from '../../common/rand-hex-color.js'
23
- import formatBookmarkGroups from './bookmark-group-tree-format'
21
+ import { getColorFromCategory } from '../../common/get-category-color.js'
24
22
  import findBookmarkGroupId from '../../common/find-bookmark-group-id'
25
23
  import ProfileItem from './profile-form-item'
24
+ import BookmarkCategorySelect from './bookmark-category-select.jsx'
26
25
 
27
26
  const FormItem = Form.Item
28
27
  const e = window.translate
@@ -55,7 +54,7 @@ export default function FtpFormUi (props) {
55
54
  type: terminalFtpType,
56
55
  port: 21,
57
56
  category: initBookmarkGroupId,
58
- color: getRandomDefaultColor(),
57
+ color: getColorFromCategory(bookmarkGroups, currentBookmarkGroupId),
59
58
  user: '',
60
59
  password: '',
61
60
  secure: false
@@ -66,7 +65,6 @@ export default function FtpFormUi (props) {
66
65
  const {
67
66
  bookmarkGroups = []
68
67
  } = props
69
- const tree = formatBookmarkGroups(bookmarkGroups)
70
68
  return (
71
69
  <div className='pd1x'>
72
70
  <FormItem
@@ -131,17 +129,10 @@ export default function FtpFormUi (props) {
131
129
  >
132
130
  <Switch />
133
131
  </FormItem>
134
- <FormItem
135
- {...formItemLayout}
136
- label={e('bookmarkCategory')}
137
- name='category'
138
- >
139
- <TreeSelect
140
- treeData={tree}
141
- treeDefaultExpandAll
142
- showSearch
143
- />
144
- </FormItem>
132
+ <BookmarkCategorySelect
133
+ bookmarkGroups={bookmarkGroups}
134
+ form={form}
135
+ />
145
136
  <FormItem
146
137
  {...formItemLayout}
147
138
  label='type'
@@ -6,7 +6,6 @@ import { useEffect } from 'react'
6
6
  import {
7
7
  Tabs,
8
8
  Input,
9
- TreeSelect,
10
9
  Form
11
10
  } from 'antd'
12
11
  import { formItemLayout } from '../../common/form-layout'
@@ -14,7 +13,6 @@ import {
14
13
  newBookmarkIdPrefix,
15
14
  terminalLocalType
16
15
  } from '../../common/constants'
17
- import formatBookmarkGroups from './bookmark-group-tree-format'
18
16
  import findBookmarkGroupId from '../../common/find-bookmark-group-id'
19
17
  import useSubmit from './use-submit'
20
18
  import useUI from './use-ui'
@@ -24,7 +22,8 @@ import renderTermBg from './render-bg'
24
22
  import { defaults } from 'lodash-es'
25
23
  import renderRunScripts from './render-delayed-scripts.jsx'
26
24
  import { ColorPickerItem } from './color-picker-item.jsx'
27
- import { getRandomDefaultColor } from '../../common/rand-hex-color.js'
25
+ import { getColorFromCategory } from '../../common/get-category-color.js'
26
+ import BookmarkCategorySelect from './bookmark-category-select.jsx'
28
27
 
29
28
  const FormItem = Form.Item
30
29
  const e = window.translate
@@ -60,7 +59,7 @@ export default function LocalFormUi (props) {
60
59
  term: props.store.config.terminalType,
61
60
  displayRaw: false,
62
61
  type: terminalLocalType,
63
- color: getRandomDefaultColor(),
62
+ color: getColorFromCategory(bookmarkGroups, currentBookmarkGroupId),
64
63
  runScripts: [{}],
65
64
  enableSsh: true
66
65
  }
@@ -69,7 +68,6 @@ export default function LocalFormUi (props) {
69
68
  const {
70
69
  bookmarkGroups = []
71
70
  } = props
72
- const tree = formatBookmarkGroups(bookmarkGroups)
73
71
  return (
74
72
  <div className='pd1x'>
75
73
  {renderRunScripts()}
@@ -90,17 +88,10 @@ export default function LocalFormUi (props) {
90
88
  >
91
89
  <Input.TextArea autoSize={{ minRows: 1 }} />
92
90
  </FormItem>
93
- <FormItem
94
- {...formItemLayout}
95
- label={e('bookmarkCategory')}
96
- name='category'
97
- >
98
- <TreeSelect
99
- treeData={tree}
100
- treeDefaultExpandAll
101
- showSearch
102
- />
103
- </FormItem>
91
+ <BookmarkCategorySelect
92
+ bookmarkGroups={bookmarkGroups}
93
+ form={form}
94
+ />
104
95
  <FormItem
105
96
  {...formItemLayout}
106
97
  label='type'
@@ -7,7 +7,6 @@ import {
7
7
  Input,
8
8
  Form,
9
9
  InputNumber,
10
- TreeSelect,
11
10
  Alert
12
11
  } from 'antd'
13
12
  import { formItemLayout } from '../../common/form-layout'
@@ -20,11 +19,11 @@ import useSubmit from './use-submit'
20
19
  import copy from 'json-deep-copy'
21
20
  import { defaults, isEmpty } from 'lodash-es'
22
21
  import { ColorPickerItem } from './color-picker-item.jsx'
23
- import { getRandomDefaultColor } from '../../common/rand-hex-color.js'
24
- import formatBookmarkGroups from './bookmark-group-tree-format'
22
+ import { getColorFromCategory } from '../../common/get-category-color.js'
25
23
  import findBookmarkGroupId from '../../common/find-bookmark-group-id'
26
24
  import ProfileItem from './profile-form-item'
27
25
  import Link from '../common/external-link'
26
+ import BookmarkCategorySelect from './bookmark-category-select.jsx'
28
27
 
29
28
  const FormItem = Form.Item
30
29
  const e = window.translate
@@ -57,7 +56,7 @@ export default function RdpFormUi (props) {
57
56
  type: terminalRdpType,
58
57
  port: 3389,
59
58
  category: initBookmarkGroupId,
60
- color: getRandomDefaultColor()
59
+ color: getColorFromCategory(bookmarkGroups, currentBookmarkGroupId)
61
60
 
62
61
  }
63
62
  initialValues = defaults(initialValues, defaultValues)
@@ -65,7 +64,6 @@ export default function RdpFormUi (props) {
65
64
  const {
66
65
  bookmarkGroups = []
67
66
  } = props
68
- const tree = formatBookmarkGroups(bookmarkGroups)
69
67
  const alertProps = {
70
68
  message: (
71
69
  <Link to={rdpWikiLink}>WIKI: {rdpWikiLink}</Link>
@@ -150,17 +148,10 @@ export default function RdpFormUi (props) {
150
148
  >
151
149
  <Input />
152
150
  </FormItem>
153
- <FormItem
154
- {...formItemLayout}
155
- label={e('bookmarkCategory')}
156
- name='category'
157
- >
158
- <TreeSelect
159
- treeData={tree}
160
- treeDefaultExpandAll
161
- showSearch
162
- />
163
- </FormItem>
151
+ <BookmarkCategorySelect
152
+ bookmarkGroups={bookmarkGroups}
153
+ form={form}
154
+ />
164
155
  <FormItem
165
156
  {...formItemLayout}
166
157
  label='type'