@electerm/electerm-react 1.100.30 → 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.
- package/client/common/constants.js +2 -1
- package/client/common/default-setting.js +4 -0
- package/client/components/ai/ai-chat-history.jsx +1 -1
- package/client/components/ai/ai-chat.jsx +108 -22
- package/client/components/ai/ai-output.jsx +8 -8
- package/client/components/ai/ai.styl +6 -6
- package/client/components/bg/css-overwrite.jsx +52 -4
- package/client/components/bookmark-form/ssh-form-ui.jsx +5 -1
- package/client/components/main/main.jsx +4 -2
- package/client/components/main/term-fullscreen.styl +18 -7
- package/client/components/session/session.jsx +3 -1
- package/client/components/setting-panel/terminal-bg-config.jsx +82 -5
- package/client/components/setting-panel/text-bg-modal.jsx +131 -0
- package/client/components/sftp/sftp-entry.jsx +2 -2
- package/client/components/tree-list/tree-list.jsx +1 -2
- package/client/store/init-state.js +3 -1
- package/client/store/sync.js +5 -1
- 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 = '
|
|
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,
|
|
@@ -39,42 +39,128 @@ export default function AIChat (props) {
|
|
|
39
39
|
}
|
|
40
40
|
if (!prompt.trim() || isLoading) return
|
|
41
41
|
setIsLoading(true)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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:
|
|
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:
|
|
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
|
-
<
|
|
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
|
-
|
|
27
|
-
&:hover
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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',
|
|
@@ -80,7 +80,11 @@ export default function BookmarkFormUI (props) {
|
|
|
80
80
|
'terminalBackgroundFilterBlur',
|
|
81
81
|
'terminalBackgroundFilterBrightness',
|
|
82
82
|
'terminalBackgroundFilterGrayscale',
|
|
83
|
-
'terminalBackgroundFilterContrast'
|
|
83
|
+
'terminalBackgroundFilterContrast',
|
|
84
|
+
'terminalBackgroundText',
|
|
85
|
+
'terminalBackgroundTextSize',
|
|
86
|
+
'terminalBackgroundTextColor',
|
|
87
|
+
'terminalBackgroundTextFontFamily'
|
|
84
88
|
])
|
|
85
89
|
}
|
|
86
90
|
initialValues = defaultsDeep(initialValues, defaultValues)
|
|
@@ -18,7 +18,7 @@ import TerminalCmdSuggestions from '../terminal/terminal-command-dropdown'
|
|
|
18
18
|
import TransportsActionStore from '../file-transfer/transports-action-store.jsx'
|
|
19
19
|
import classnames from 'classnames'
|
|
20
20
|
import ShortcutControl from '../shortcuts/shortcut-control.jsx'
|
|
21
|
-
import { isMac, isWin } from '../../common/constants'
|
|
21
|
+
import { isMac, isWin, textTerminalBgValue } from '../../common/constants'
|
|
22
22
|
import TermFullscreenControl from './term-fullscreen-control'
|
|
23
23
|
import TerminalInfo from '../terminal-info/terminal-info'
|
|
24
24
|
import { ConfigProvider, notification, message } from 'antd'
|
|
@@ -117,7 +117,9 @@ export default auto(function Index (props) {
|
|
|
117
117
|
const ext1 = {
|
|
118
118
|
className: cls
|
|
119
119
|
}
|
|
120
|
-
const bgTabs = config.terminalBackgroundImagePath === 'index' ||
|
|
120
|
+
const bgTabs = config.terminalBackgroundImagePath === 'index' ||
|
|
121
|
+
config.terminalBackgroundImagePath === 'randomShape' ||
|
|
122
|
+
config.terminalBackgroundImagePath === textTerminalBgValue
|
|
121
123
|
? store.getTabs()
|
|
122
124
|
: store.getTabs().filter(tab =>
|
|
123
125
|
tab.terminalBackground?.terminalBackgroundImagePath
|
|
@@ -14,17 +14,28 @@
|
|
|
14
14
|
position fixed
|
|
15
15
|
z-index 100
|
|
16
16
|
background rgba(45, 245, 108, 0.8)
|
|
17
|
-
|
|
18
|
-
.session-
|
|
17
|
+
// Hide all sessions first
|
|
18
|
+
.session-wrap
|
|
19
|
+
display none
|
|
20
|
+
// Only show the session that matches the fullscreen tab ID
|
|
21
|
+
.session-wrap.session-current
|
|
22
|
+
display block !important
|
|
19
23
|
position fixed
|
|
20
24
|
left 0 !important
|
|
21
25
|
top 0 !important
|
|
22
26
|
height 100% !important
|
|
23
27
|
width 100% !important
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
.term-wrap
|
|
29
|
+
.session-v-wrap
|
|
30
|
+
position fixed
|
|
31
|
+
left 0 !important
|
|
32
|
+
top 0 !important
|
|
33
|
+
height 100% !important
|
|
34
|
+
width 100% !important
|
|
35
|
+
.term-wrap-1
|
|
36
|
+
left 10px !important
|
|
37
|
+
top 10px !important
|
|
38
|
+
right 10px !important
|
|
39
|
+
bottom 10px !important
|
|
29
40
|
.term-fullscreen-control
|
|
30
41
|
display none
|
|
@@ -465,7 +465,9 @@ export default class SessionWrapper extends Component {
|
|
|
465
465
|
}
|
|
466
466
|
|
|
467
467
|
handleFullscreen = () => {
|
|
468
|
-
|
|
468
|
+
// Make this tab the active tab before fullscreening
|
|
469
|
+
window.store.activeTabId = this.props.tab.id
|
|
470
|
+
window.store.toggleTermFullscreen(true, this.props.tab.id)
|
|
469
471
|
}
|
|
470
472
|
|
|
471
473
|
toggleBroadcastInput = () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
AutoComplete,
|
|
4
4
|
Upload,
|
|
@@ -6,10 +6,12 @@ import {
|
|
|
6
6
|
Input
|
|
7
7
|
} from 'antd'
|
|
8
8
|
import {
|
|
9
|
-
noTerminalBgValue
|
|
9
|
+
noTerminalBgValue,
|
|
10
|
+
textTerminalBgValue
|
|
10
11
|
} from '../../common/constants'
|
|
11
12
|
import defaultSettings from '../../common/default-setting'
|
|
12
13
|
import NumberConfig from './number-config'
|
|
14
|
+
import TextBgModal from './text-bg-modal.jsx'
|
|
13
15
|
|
|
14
16
|
const e = window.translate
|
|
15
17
|
|
|
@@ -19,6 +21,7 @@ export default function TerminalBackgroundConfig ({
|
|
|
19
21
|
config,
|
|
20
22
|
isGlobal = false
|
|
21
23
|
}) {
|
|
24
|
+
const [showTextModal, setShowTextModal] = useState(false)
|
|
22
25
|
const value = config[name]
|
|
23
26
|
const defaultValue = defaultSettings[name]
|
|
24
27
|
const onChange = (v) => onChangeValue(v, name)
|
|
@@ -33,6 +36,7 @@ export default function TerminalBackgroundConfig ({
|
|
|
33
36
|
<span>{e('chooseFile')}</span>
|
|
34
37
|
</Upload>
|
|
35
38
|
)
|
|
39
|
+
|
|
36
40
|
const dataSource = [
|
|
37
41
|
{
|
|
38
42
|
value: '',
|
|
@@ -41,8 +45,34 @@ export default function TerminalBackgroundConfig ({
|
|
|
41
45
|
{
|
|
42
46
|
value: noTerminalBgValue,
|
|
43
47
|
desc: e('noTerminalBg')
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
value: textTerminalBgValue,
|
|
51
|
+
desc: `📝 ${e('textBackground')}`
|
|
44
52
|
}
|
|
45
53
|
]
|
|
54
|
+
|
|
55
|
+
// Add custom text background option if text is configured
|
|
56
|
+
if (value === textTerminalBgValue && config.terminalBackgroundText) {
|
|
57
|
+
const text = config.terminalBackgroundText
|
|
58
|
+
// Clean up the text for display: remove line breaks, trim whitespace
|
|
59
|
+
const cleanText = text.replace(/\s+/g, ' ').trim()
|
|
60
|
+
// Create a more user-friendly truncation
|
|
61
|
+
const truncatedText = cleanText.length > 25
|
|
62
|
+
? cleanText.substring(0, 25) + '...'
|
|
63
|
+
: cleanText
|
|
64
|
+
dataSource[2] = {
|
|
65
|
+
value: textTerminalBgValue,
|
|
66
|
+
desc: `📝 "${truncatedText}"`
|
|
67
|
+
}
|
|
68
|
+
} else if (value === textTerminalBgValue) {
|
|
69
|
+
// Show helpful text when text background is selected but no text is configured
|
|
70
|
+
dataSource[2] = {
|
|
71
|
+
value: textTerminalBgValue,
|
|
72
|
+
desc: `📝 ${e('clickToConfigureText') || 'Click to configure text'}`
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
46
76
|
if (isGlobal) {
|
|
47
77
|
dataSource.push(
|
|
48
78
|
{
|
|
@@ -51,10 +81,45 @@ export default function TerminalBackgroundConfig ({
|
|
|
51
81
|
},
|
|
52
82
|
{
|
|
53
83
|
value: 'randomShape',
|
|
54
|
-
desc: e('randomShape')
|
|
84
|
+
desc: `🎨 ${e('randomShape')}`
|
|
55
85
|
}
|
|
56
86
|
)
|
|
57
87
|
}
|
|
88
|
+
|
|
89
|
+
const handleTextBgClick = () => {
|
|
90
|
+
setShowTextModal(true)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const handleTextBgModalOk = (textConfig) => {
|
|
94
|
+
// Store text configuration in the config
|
|
95
|
+
onChangeValue(textConfig.text, 'terminalBackgroundText')
|
|
96
|
+
onChangeValue(textConfig.fontSize, 'terminalBackgroundTextSize')
|
|
97
|
+
onChangeValue(textConfig.color, 'terminalBackgroundTextColor')
|
|
98
|
+
onChangeValue(textConfig.fontFamily, 'terminalBackgroundTextFontFamily')
|
|
99
|
+
onChange(textTerminalBgValue)
|
|
100
|
+
setShowTextModal(false)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const handleTextBgModalCancel = () => {
|
|
104
|
+
setShowTextModal(false)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const handleAutocompleteSelect = (v) => {
|
|
108
|
+
if (v === textTerminalBgValue) {
|
|
109
|
+
handleTextBgClick()
|
|
110
|
+
} else {
|
|
111
|
+
onChange(v)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const handleAutocompleteChange = (v) => {
|
|
116
|
+
if (v === textTerminalBgValue) {
|
|
117
|
+
handleTextBgClick()
|
|
118
|
+
} else {
|
|
119
|
+
onChange(v)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
58
123
|
const numberOpts = { step: 0.05, min: 0, max: 1, cls: 'bg-img-setting' }
|
|
59
124
|
|
|
60
125
|
function renderNumber (name, options, title = '', width = 136) {
|
|
@@ -88,7 +153,7 @@ export default function TerminalBackgroundConfig ({
|
|
|
88
153
|
}
|
|
89
154
|
|
|
90
155
|
const renderFilter = () => {
|
|
91
|
-
if (config[name] === noTerminalBgValue || config[name] === 'index') return
|
|
156
|
+
if (config[name] === noTerminalBgValue || config[name] === 'index' || config[name] === textTerminalBgValue) return
|
|
92
157
|
|
|
93
158
|
return (
|
|
94
159
|
<div>
|
|
@@ -137,6 +202,7 @@ export default function TerminalBackgroundConfig ({
|
|
|
137
202
|
label: item.desc
|
|
138
203
|
}
|
|
139
204
|
}
|
|
205
|
+
|
|
140
206
|
return (
|
|
141
207
|
<div className='pd2b'>
|
|
142
208
|
<div className='pd1b'>
|
|
@@ -145,7 +211,8 @@ export default function TerminalBackgroundConfig ({
|
|
|
145
211
|
>
|
|
146
212
|
<AutoComplete
|
|
147
213
|
value={value}
|
|
148
|
-
onChange={
|
|
214
|
+
onChange={handleAutocompleteChange}
|
|
215
|
+
onSelect={handleAutocompleteSelect}
|
|
149
216
|
placeholder={defaultValue}
|
|
150
217
|
className='width-100'
|
|
151
218
|
options={dataSource.map(renderBgOption)}
|
|
@@ -160,6 +227,16 @@ export default function TerminalBackgroundConfig ({
|
|
|
160
227
|
{
|
|
161
228
|
renderFilter()
|
|
162
229
|
}
|
|
230
|
+
|
|
231
|
+
<TextBgModal
|
|
232
|
+
visible={showTextModal}
|
|
233
|
+
onOk={handleTextBgModalOk}
|
|
234
|
+
onCancel={handleTextBgModalCancel}
|
|
235
|
+
initialText={config.terminalBackgroundText || ''}
|
|
236
|
+
initialSize={config.terminalBackgroundTextSize || 48}
|
|
237
|
+
initialColor={config.terminalBackgroundTextColor || '#ffffff'}
|
|
238
|
+
initialFontFamily={config.terminalBackgroundTextFontFamily || 'monospace'}
|
|
239
|
+
/>
|
|
163
240
|
</div>
|
|
164
241
|
)
|
|
165
242
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Modal,
|
|
4
|
+
Input,
|
|
5
|
+
InputNumber,
|
|
6
|
+
Space,
|
|
7
|
+
Typography,
|
|
8
|
+
Select
|
|
9
|
+
} from 'antd'
|
|
10
|
+
import { ColorPicker } from '../bookmark-form/color-picker.jsx'
|
|
11
|
+
|
|
12
|
+
const { TextArea } = Input
|
|
13
|
+
const { Title } = Typography
|
|
14
|
+
const e = window.translate
|
|
15
|
+
|
|
16
|
+
export default function TextBgModal ({
|
|
17
|
+
visible,
|
|
18
|
+
onOk,
|
|
19
|
+
onCancel,
|
|
20
|
+
initialText = '',
|
|
21
|
+
initialSize = 48,
|
|
22
|
+
initialColor = '#ffffff',
|
|
23
|
+
initialFontFamily = 'Maple Mono'
|
|
24
|
+
}) {
|
|
25
|
+
const [text, setText] = useState(initialText)
|
|
26
|
+
const [fontSize, setFontSize] = useState(initialSize)
|
|
27
|
+
const [color, setColor] = useState(initialColor)
|
|
28
|
+
const [fontFamily, setFontFamily] = useState(initialFontFamily)
|
|
29
|
+
|
|
30
|
+
const { fonts = [] } = window.et || {}
|
|
31
|
+
|
|
32
|
+
const handleOk = () => {
|
|
33
|
+
onOk({
|
|
34
|
+
text,
|
|
35
|
+
fontSize,
|
|
36
|
+
color,
|
|
37
|
+
fontFamily
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const handleCancel = () => {
|
|
42
|
+
onCancel()
|
|
43
|
+
// Reset to initial values
|
|
44
|
+
setText(initialText)
|
|
45
|
+
setFontSize(initialSize)
|
|
46
|
+
setColor(initialColor)
|
|
47
|
+
setFontFamily(initialFontFamily)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Modal
|
|
52
|
+
title={e('terminalBackgroundText')}
|
|
53
|
+
open={visible}
|
|
54
|
+
onOk={handleOk}
|
|
55
|
+
onCancel={handleCancel}
|
|
56
|
+
width={500}
|
|
57
|
+
destroyOnClose
|
|
58
|
+
>
|
|
59
|
+
<div className='pd1'>
|
|
60
|
+
<Space direction='vertical' size='large' style={{ width: '100%' }}>
|
|
61
|
+
<div>
|
|
62
|
+
<Title level={5}>{e('text')}</Title>
|
|
63
|
+
<TextArea
|
|
64
|
+
value={text}
|
|
65
|
+
onChange={(e) => setText(e.target.value)}
|
|
66
|
+
placeholder={e('enterTextForBackground')}
|
|
67
|
+
rows={4}
|
|
68
|
+
maxLength={500}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div>
|
|
73
|
+
<Title level={5}>{e('fontSize')}</Title>
|
|
74
|
+
<InputNumber
|
|
75
|
+
value={fontSize}
|
|
76
|
+
onChange={setFontSize}
|
|
77
|
+
min={12}
|
|
78
|
+
max={200}
|
|
79
|
+
style={{ width: '100%' }}
|
|
80
|
+
placeholder={e('fontSize')}
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div>
|
|
85
|
+
<Title level={5}>{e('textColor')}</Title>
|
|
86
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
87
|
+
<ColorPicker
|
|
88
|
+
value={color}
|
|
89
|
+
onChange={setColor}
|
|
90
|
+
/>
|
|
91
|
+
<Input
|
|
92
|
+
value={color}
|
|
93
|
+
onChange={(e) => setColor(e.target.value)}
|
|
94
|
+
placeholder={e('colorValue')}
|
|
95
|
+
style={{ flex: 1 }}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div>
|
|
101
|
+
<Title level={5}>{e('fontFamily')}</Title>
|
|
102
|
+
<Select
|
|
103
|
+
value={fontFamily}
|
|
104
|
+
onChange={setFontFamily}
|
|
105
|
+
style={{ width: '100%' }}
|
|
106
|
+
placeholder={e('selectFontFamily')}
|
|
107
|
+
showSearch
|
|
108
|
+
>
|
|
109
|
+
{
|
|
110
|
+
fonts.map(f => {
|
|
111
|
+
return (
|
|
112
|
+
<Select.Option value={f} key={f}>
|
|
113
|
+
<span
|
|
114
|
+
className='font-option'
|
|
115
|
+
style={{
|
|
116
|
+
fontFamily: f
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
{f}
|
|
120
|
+
</span>
|
|
121
|
+
</Select.Option>
|
|
122
|
+
)
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
</Select>
|
|
126
|
+
</div>
|
|
127
|
+
</Space>
|
|
128
|
+
</div>
|
|
129
|
+
</Modal>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
@@ -1161,12 +1161,12 @@ export default class Sftp extends Component {
|
|
|
1161
1161
|
{
|
|
1162
1162
|
type === typeMap.remote
|
|
1163
1163
|
? (
|
|
1164
|
-
<div className='pd1t pd1b pd1x alignright'>
|
|
1164
|
+
<div className='sftp-panel-title pd1t pd1b pd1x alignright'>
|
|
1165
1165
|
{e('remote')}: {username}@{host}
|
|
1166
1166
|
</div>
|
|
1167
1167
|
)
|
|
1168
1168
|
: (
|
|
1169
|
-
<div className='pd1t pd1b pd1x'>
|
|
1169
|
+
<div className='sftp-panel-title pd1t pd1b pd1x'>
|
|
1170
1170
|
{e('local')}
|
|
1171
1171
|
</div>
|
|
1172
1172
|
)
|
|
@@ -354,7 +354,7 @@ export default class ItemListTree extends Component {
|
|
|
354
354
|
showNewBookmarkGroupForm: true,
|
|
355
355
|
parentId: item.id,
|
|
356
356
|
bookmarkGroupTitle: '',
|
|
357
|
-
bookmarkGroupColor:
|
|
357
|
+
bookmarkGroupColor: getRandomDefaultColor()
|
|
358
358
|
}
|
|
359
359
|
})
|
|
360
360
|
window.store.expandedKeys.push(item.id)
|
|
@@ -417,7 +417,6 @@ export default class ItemListTree extends Component {
|
|
|
417
417
|
if (tar) {
|
|
418
418
|
target = tar
|
|
419
419
|
}
|
|
420
|
-
console.log('tar', target, tar)
|
|
421
420
|
const dataDragged = e.dataTransfer.getData('idDragged')
|
|
422
421
|
const [idDragged, pidDrags, isGroupDragged] = dataDragged.split('@')
|
|
423
422
|
const isGroupDrag = isGroupDragged === 'true'
|
|
@@ -28,6 +28,7 @@ import { buildDefaultThemes } from '../common/terminal-theme'
|
|
|
28
28
|
import * as ls from '../common/safe-local-storage'
|
|
29
29
|
import { exclude } from 'manate'
|
|
30
30
|
import initSettingItem from '../common/init-setting-item'
|
|
31
|
+
import { getRandomDefaultColor } from '../common/rand-hex-color'
|
|
31
32
|
|
|
32
33
|
const e = window.translate
|
|
33
34
|
|
|
@@ -36,7 +37,8 @@ function getDefaultBookmarkGroups (bookmarks) {
|
|
|
36
37
|
JSON.stringify({
|
|
37
38
|
title: e(defaultBookmarkGroupId),
|
|
38
39
|
id: defaultBookmarkGroupId,
|
|
39
|
-
bookmarkIds: bookmarks.map(d => d.id)
|
|
40
|
+
bookmarkIds: bookmarks.map(d => d.id),
|
|
41
|
+
color: getRandomDefaultColor()
|
|
40
42
|
})
|
|
41
43
|
]
|
|
42
44
|
}
|
package/client/store/sync.js
CHANGED
|
@@ -515,7 +515,11 @@ export default (Store) => {
|
|
|
515
515
|
'roleAI',
|
|
516
516
|
'languageAI',
|
|
517
517
|
'proxyAI',
|
|
518
|
-
'disableDeveloperTool'
|
|
518
|
+
'disableDeveloperTool',
|
|
519
|
+
'terminalBackgroundText',
|
|
520
|
+
'terminalBackgroundTextSize',
|
|
521
|
+
'terminalBackgroundTextColor',
|
|
522
|
+
'terminalBackgroundTextFontFamily'
|
|
519
523
|
]
|
|
520
524
|
return pick(store.config, configSyncKeys)
|
|
521
525
|
}
|