@electerm/electerm-react 2.15.8 → 2.16.6
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/components/ai/ai-chat.jsx +44 -2
- package/client/components/ai/ai-stop-icon.jsx +13 -0
- package/client/components/ai/ai.styl +10 -0
- package/client/components/bg/css-overwrite.jsx +158 -187
- package/client/components/bg/custom-css.jsx +8 -17
- package/client/components/bookmark-form/bookmark-schema.js +7 -1
- package/client/components/bookmark-form/common/color-picker.jsx +4 -8
- package/client/components/bookmark-form/common/exec-settings-field.jsx +44 -0
- package/client/components/bookmark-form/common/fields.jsx +3 -0
- package/client/components/bookmark-form/config/common-fields.js +1 -0
- package/client/components/bookmark-form/config/local.js +3 -1
- package/client/components/common/animate-text.jsx +22 -23
- package/client/components/common/modal.jsx +2 -0
- package/client/components/common/password.jsx +19 -32
- package/client/components/footer/cmd-history.jsx +154 -0
- package/client/components/footer/cmd-history.styl +73 -0
- package/client/components/footer/footer-entry.jsx +15 -1
- package/client/components/main/main.jsx +2 -3
- package/client/components/quick-commands/quick-commands-select.jsx +1 -4
- package/client/components/rdp/rdp-session.jsx +23 -4
- package/client/components/session/session.styl +1 -3
- package/client/components/setting-panel/terminal-bg-config.jsx +2 -0
- package/client/components/setting-panel/text-bg-modal.jsx +9 -9
- package/client/components/sftp/file-item.jsx +22 -0
- package/client/components/sidebar/history-item.jsx +6 -3
- package/client/components/sidebar/history.jsx +48 -5
- package/client/components/sidebar/sidebar-panel.jsx +0 -13
- package/client/components/sidebar/sidebar.styl +19 -0
- package/client/components/tabs/add-btn-menu.jsx +28 -4
- package/client/components/tabs/add-btn.jsx +1 -1
- package/client/components/tabs/add-btn.styl +8 -0
- package/client/components/terminal/terminal.jsx +28 -11
- package/client/components/terminal/transfer-client-base.js +18 -2
- package/client/components/terminal/trzsz-client.js +2 -1
- package/client/components/terminal/zmodem-client.js +2 -1
- package/client/components/text-editor/edit-with-custom-editor.jsx +49 -0
- package/client/components/text-editor/text-editor-form.jsx +13 -5
- package/client/components/text-editor/text-editor.jsx +20 -1
- package/client/components/vnc/vnc-session.jsx +3 -0
- package/client/components/vnc/vnc.styl +1 -1
- package/client/store/common.js +31 -4
- package/client/store/init-state.js +26 -1
- package/client/store/store.js +1 -1
- package/client/store/watch.js +8 -1
- package/package.json +1 -1
|
@@ -6,7 +6,6 @@ import uid from '../../common/uid'
|
|
|
6
6
|
import { pick } from 'lodash-es'
|
|
7
7
|
import {
|
|
8
8
|
SettingOutlined,
|
|
9
|
-
LoadingOutlined,
|
|
10
9
|
SendOutlined,
|
|
11
10
|
UnorderedListOutlined
|
|
12
11
|
} from '@ant-design/icons'
|
|
@@ -15,6 +14,7 @@ import {
|
|
|
15
14
|
} from '../../common/constants'
|
|
16
15
|
import HelpIcon from '../common/help-icon'
|
|
17
16
|
import { refsStatic } from '../common/ref'
|
|
17
|
+
import AIStopIcon from './ai-stop-icon'
|
|
18
18
|
import './ai.styl'
|
|
19
19
|
|
|
20
20
|
const { TextArea } = Input
|
|
@@ -23,6 +23,7 @@ const MAX_HISTORY = 100
|
|
|
23
23
|
export default function AIChat (props) {
|
|
24
24
|
const [prompt, setPrompt] = useState('')
|
|
25
25
|
const [isLoading, setIsLoading] = useState(false)
|
|
26
|
+
const [currentSessionId, setCurrentSessionId] = useState(null)
|
|
26
27
|
|
|
27
28
|
function handlePromptChange (e) {
|
|
28
29
|
setPrompt(e.target.value)
|
|
@@ -91,6 +92,8 @@ export default function AIChat (props) {
|
|
|
91
92
|
window.store.aiChatHistory[index].sessionId = aiResponse.sessionId
|
|
92
93
|
window.store.aiChatHistory[index].response = aiResponse.content || ''
|
|
93
94
|
}
|
|
95
|
+
// Store current session ID for stop functionality
|
|
96
|
+
setCurrentSessionId(aiResponse.sessionId)
|
|
94
97
|
|
|
95
98
|
// Start polling for updates
|
|
96
99
|
pollStreamContent(aiResponse.sessionId, chatId)
|
|
@@ -125,6 +128,12 @@ export default function AIChat (props) {
|
|
|
125
128
|
const streamResponse = await window.pre.runGlobalAsync('getStreamContent', sessionId)
|
|
126
129
|
|
|
127
130
|
if (streamResponse && streamResponse.error) {
|
|
131
|
+
// Session not found or error - stop polling
|
|
132
|
+
if (streamResponse.error === 'Session not found') {
|
|
133
|
+
setCurrentSessionId(null)
|
|
134
|
+
setIsLoading(false)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
128
137
|
// Remove the entry and show error
|
|
129
138
|
const index = window.store.aiChatHistory.findIndex(item => item.id === chatId)
|
|
130
139
|
if (index !== -1) {
|
|
@@ -147,6 +156,7 @@ export default function AIChat (props) {
|
|
|
147
156
|
if (streamResponse.hasMore) {
|
|
148
157
|
setTimeout(() => pollStreamContent(sessionId, chatId), 200) // Poll every 200ms
|
|
149
158
|
} else {
|
|
159
|
+
setCurrentSessionId(null)
|
|
150
160
|
setIsLoading(false)
|
|
151
161
|
}
|
|
152
162
|
}
|
|
@@ -156,6 +166,7 @@ export default function AIChat (props) {
|
|
|
156
166
|
if (index !== -1) {
|
|
157
167
|
window.store.aiChatHistory.splice(index, 1)
|
|
158
168
|
}
|
|
169
|
+
setCurrentSessionId(null)
|
|
159
170
|
setIsLoading(false)
|
|
160
171
|
window.store.onError(error)
|
|
161
172
|
}
|
|
@@ -177,9 +188,40 @@ export default function AIChat (props) {
|
|
|
177
188
|
window.store.aiChatHistory = []
|
|
178
189
|
}
|
|
179
190
|
|
|
191
|
+
const handleStop = useCallback(async function () {
|
|
192
|
+
if (!currentSessionId || !isLoading) return
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
// Call server to stop the stream
|
|
196
|
+
await window.pre.runGlobalAsync('stopStream', currentSessionId)
|
|
197
|
+
|
|
198
|
+
// Reset state
|
|
199
|
+
setCurrentSessionId(null)
|
|
200
|
+
setIsLoading(false)
|
|
201
|
+
|
|
202
|
+
// Update the chat entry to mark as stopped
|
|
203
|
+
const chatEntries = window.store.aiChatHistory
|
|
204
|
+
for (let i = chatEntries.length - 1; i >= 0; i--) {
|
|
205
|
+
if (chatEntries[i].isStreaming) {
|
|
206
|
+
chatEntries[i].isStreaming = false
|
|
207
|
+
break
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
window.store.aiChatHistory = [...chatEntries]
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error('Error stopping stream:', error)
|
|
213
|
+
setCurrentSessionId(null)
|
|
214
|
+
setIsLoading(false)
|
|
215
|
+
}
|
|
216
|
+
}, [currentSessionId, isLoading])
|
|
217
|
+
|
|
180
218
|
function renderSendIcon () {
|
|
181
219
|
if (isLoading) {
|
|
182
|
-
return
|
|
220
|
+
return (
|
|
221
|
+
<AIStopIcon
|
|
222
|
+
onClick={handleStop}
|
|
223
|
+
/>
|
|
224
|
+
)
|
|
183
225
|
}
|
|
184
226
|
return (
|
|
185
227
|
<SendOutlined
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LoadingOutlined } from '@ant-design/icons'
|
|
2
|
+
|
|
3
|
+
export default function AIStopIcon (props) {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
className='ai-stop-icon-square mg1l pointer'
|
|
7
|
+
onClick={props.onClick}
|
|
8
|
+
title={props.title || 'Stop AI request'}
|
|
9
|
+
>
|
|
10
|
+
<LoadingOutlined spin />
|
|
11
|
+
</div>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
@@ -1,100 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* btns
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { useEffect, useRef } from 'react'
|
|
5
5
|
import fs from '../../common/fs'
|
|
6
6
|
import { noTerminalBgValue, textTerminalBgValue } from '../../common/constants'
|
|
7
7
|
import { generateMosaicBackground } from './shapes'
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
static styleTag = null
|
|
9
|
+
const themeDomId = 'css-overwrite-terminal-backgrounds'
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const bgProps = [
|
|
18
|
-
'terminalBackgroundImagePath',
|
|
19
|
-
'terminalBackgroundFilterBlur',
|
|
20
|
-
'terminalBackgroundFilterOpacity',
|
|
21
|
-
'terminalBackgroundFilterBrightness',
|
|
22
|
-
'terminalBackgroundFilterContrast',
|
|
23
|
-
'terminalBackgroundFilterGrayscale',
|
|
24
|
-
'terminalBackgroundText',
|
|
25
|
-
'terminalBackgroundTextSize',
|
|
26
|
-
'terminalBackgroundTextColor',
|
|
27
|
-
'terminalBackgroundTextFontFamily'
|
|
28
|
-
]
|
|
29
|
-
const globalChanged = bgProps.some(prop => this.props[prop] !== nextProps[prop])
|
|
30
|
-
if (globalChanged) {
|
|
31
|
-
return true
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const currentTabs = this.props.tabs || []
|
|
35
|
-
const nextTabs = nextProps.tabs || []
|
|
36
|
-
if (currentTabs.length !== nextTabs.length) {
|
|
37
|
-
return true
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// If no tabs in both cases
|
|
41
|
-
if (!currentTabs.length && !nextTabs.length) {
|
|
42
|
-
return false
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Since tab bg settings never change, we only need to compare tab IDs
|
|
46
|
-
const currentIds = new Set(currentTabs.map(t => t.id))
|
|
47
|
-
const nextIds = new Set(nextTabs.map(t => t.id))
|
|
48
|
-
|
|
49
|
-
// Check if all current IDs exist in next IDs
|
|
50
|
-
for (const id of currentIds) {
|
|
51
|
-
if (!nextIds.has(id)) return true
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return false
|
|
11
|
+
function createBackgroundStyle (imagePath) {
|
|
12
|
+
if (!imagePath || imagePath === '') {
|
|
13
|
+
return ''
|
|
55
14
|
}
|
|
56
15
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
} else if (noTerminalBgValue === imagePath) {
|
|
79
|
-
st = 'none'
|
|
80
|
-
} else if (textTerminalBgValue === imagePath) {
|
|
81
|
-
st = 'text'
|
|
82
|
-
} else if (imagePath && !isWebImg) {
|
|
83
|
-
content = await fs.readFileAsBase64(imagePath)
|
|
84
|
-
.catch(console.error)
|
|
85
|
-
if (content) {
|
|
86
|
-
st = `url(data:image;base64,${content})`
|
|
87
|
-
}
|
|
88
|
-
} else if (imagePath && isWebImg) {
|
|
89
|
-
st = `url(${imagePath})`
|
|
90
|
-
}
|
|
91
|
-
return st
|
|
16
|
+
let st = ''
|
|
17
|
+
const isWebImg = /^https?:\/\//.test(imagePath)
|
|
18
|
+
if (imagePath === 'randomShape') {
|
|
19
|
+
st = `url(${generateMosaicBackground()})`
|
|
20
|
+
} else if (imagePath === 'index') {
|
|
21
|
+
st = 'index'
|
|
22
|
+
} else if (noTerminalBgValue === imagePath) {
|
|
23
|
+
st = 'none'
|
|
24
|
+
} else if (textTerminalBgValue === imagePath) {
|
|
25
|
+
st = 'text'
|
|
26
|
+
} else if (imagePath && !isWebImg) {
|
|
27
|
+
return fs.readFileAsBase64(imagePath)
|
|
28
|
+
.then(content => {
|
|
29
|
+
if (content) {
|
|
30
|
+
return `url(data:image;base64,${content})`
|
|
31
|
+
}
|
|
32
|
+
return ''
|
|
33
|
+
})
|
|
34
|
+
.catch(() => '')
|
|
35
|
+
} else if (imagePath && isWebImg) {
|
|
36
|
+
st = `url(${imagePath})`
|
|
92
37
|
}
|
|
38
|
+
return st
|
|
39
|
+
}
|
|
93
40
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
(tabProps?.terminalBackgroundFilterBlur || props.terminalBackgroundFilterBlur)
|
|
41
|
+
function createFilterStyle (props, tabProps = null) {
|
|
42
|
+
return `blur(${
|
|
43
|
+
(tabProps?.terminalBackgroundFilterBlur || props.terminalBackgroundFilterBlur)
|
|
98
44
|
}px) opacity(${
|
|
99
45
|
+(tabProps?.terminalBackgroundFilterOpacity || props.terminalBackgroundFilterOpacity)
|
|
100
46
|
}) brightness(${
|
|
@@ -104,128 +50,153 @@ export default class CssOverwrite extends Component {
|
|
|
104
50
|
}) grayscale(${
|
|
105
51
|
+(tabProps?.terminalBackgroundFilterGrayscale || props.terminalBackgroundFilterGrayscale)
|
|
106
52
|
})`
|
|
107
|
-
|
|
53
|
+
}
|
|
108
54
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
55
|
+
async function createStyleForTab (tab, props) {
|
|
56
|
+
const bg = tab.terminalBackground || {}
|
|
57
|
+
const img = bg.terminalBackgroundImagePath || props.terminalBackgroundImagePath
|
|
58
|
+
const st = await createBackgroundStyle(img)
|
|
113
59
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
60
|
+
if (!st) {
|
|
61
|
+
return ''
|
|
62
|
+
}
|
|
117
63
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
styles.push(
|
|
133
|
-
`content: '${text.replace(/'/g, "\\'").replace(/\n/g, '\\A ')}'`,
|
|
134
|
-
`font-size: ${size}px`,
|
|
135
|
-
`color: ${color}`,
|
|
136
|
-
'white-space: pre-wrap',
|
|
137
|
-
'word-wrap: break-word',
|
|
138
|
-
'text-align: center',
|
|
139
|
-
'display: flex',
|
|
140
|
-
'align-items: center',
|
|
141
|
-
'justify-content: center',
|
|
142
|
-
`font-family: ${fontFamily}`,
|
|
143
|
-
'opacity: 0.3',
|
|
144
|
-
'background-image: none' // Override default background when text is set
|
|
145
|
-
)
|
|
146
|
-
}
|
|
147
|
-
} else if (st !== 'none') {
|
|
64
|
+
const selector = `#container .sessions .session-${tab.id} .xterm-screen::before`
|
|
65
|
+
const styles = []
|
|
66
|
+
if (st === 'index') {
|
|
67
|
+
styles.push(
|
|
68
|
+
`content: '${tab.tabCount}'`,
|
|
69
|
+
'background-image: none',
|
|
70
|
+
'opacity: 0.1'
|
|
71
|
+
)
|
|
72
|
+
} else if (st === 'text') {
|
|
73
|
+
const text = bg.terminalBackgroundText || props.terminalBackgroundText || ''
|
|
74
|
+
const size = bg.terminalBackgroundTextSize || props.terminalBackgroundTextSize || 48
|
|
75
|
+
const color = bg.terminalBackgroundTextColor || props.terminalBackgroundTextColor || '#ffffff'
|
|
76
|
+
const fontFamily = bg.terminalBackgroundTextFontFamily || props.terminalBackgroundTextFontFamily || 'monospace'
|
|
77
|
+
if (text) {
|
|
148
78
|
styles.push(
|
|
149
|
-
`
|
|
150
|
-
|
|
151
|
-
`
|
|
79
|
+
`content: '${text.replace(/'/g, "\\'").replace(/\n/g, '\\A ')}'`,
|
|
80
|
+
`font-size: ${size}px`,
|
|
81
|
+
`color: ${color}`,
|
|
82
|
+
'white-space: pre-wrap',
|
|
83
|
+
'word-wrap: break-word',
|
|
84
|
+
'text-align: center',
|
|
85
|
+
'display: flex',
|
|
86
|
+
'align-items: center',
|
|
87
|
+
'justify-content: center',
|
|
88
|
+
`font-family: ${fontFamily}`,
|
|
89
|
+
'opacity: 0.3',
|
|
90
|
+
'background-image: none'
|
|
152
91
|
)
|
|
153
92
|
}
|
|
154
|
-
|
|
93
|
+
} else if (st !== 'none') {
|
|
94
|
+
styles.push(
|
|
95
|
+
`background-image: ${st}`,
|
|
96
|
+
'background-position: center',
|
|
97
|
+
`filter: ${createFilterStyle(props, tab)}`
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
return `${selector} {
|
|
155
101
|
${styles.join(';')};
|
|
156
102
|
}`
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function createGlobalStyle (props) {
|
|
106
|
+
const st = await createBackgroundStyle(props.terminalBackgroundImagePath)
|
|
107
|
+
if (!st) {
|
|
108
|
+
return '#container .session-batch-active .xterm-screen::before {' +
|
|
109
|
+
'background-image: url("./images/electerm-watermark.png");' +
|
|
110
|
+
'}'
|
|
157
111
|
}
|
|
158
112
|
|
|
159
|
-
|
|
160
|
-
const st = await this.createBackgroundStyle(this.props.terminalBackgroundImagePath)
|
|
161
|
-
if (!st) {
|
|
162
|
-
return '#container .session-batch-active .xterm-screen::before {' +
|
|
163
|
-
'background-image: url("./images/electerm-watermark.png");' +
|
|
164
|
-
'}'
|
|
165
|
-
}
|
|
113
|
+
const styles = []
|
|
166
114
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const fontFamily = this.props.terminalBackgroundTextFontFamily || 'monospace'
|
|
174
|
-
if (text) {
|
|
175
|
-
styles.push(
|
|
176
|
-
`content: '${text.replace(/'/g, "\\'").replace(/\n/g, '\\A ')}'`,
|
|
177
|
-
`font-size: ${size}px`,
|
|
178
|
-
`color: ${color}`,
|
|
179
|
-
'white-space: pre-wrap',
|
|
180
|
-
'word-wrap: break-word',
|
|
181
|
-
'text-align: center',
|
|
182
|
-
'display: flex',
|
|
183
|
-
'align-items: center',
|
|
184
|
-
'justify-content: center',
|
|
185
|
-
`font-family: ${fontFamily}`,
|
|
186
|
-
'opacity: 0.3',
|
|
187
|
-
'background-image: none' // Override default background when text is set
|
|
188
|
-
)
|
|
189
|
-
}
|
|
190
|
-
} else if (st !== 'none' && st !== 'index') {
|
|
115
|
+
if (st === 'text') {
|
|
116
|
+
const text = props.terminalBackgroundText || ''
|
|
117
|
+
const size = props.terminalBackgroundTextSize || 48
|
|
118
|
+
const color = props.terminalBackgroundTextColor || '#ffffff'
|
|
119
|
+
const fontFamily = props.terminalBackgroundTextFontFamily || 'monospace'
|
|
120
|
+
if (text) {
|
|
191
121
|
styles.push(
|
|
192
|
-
`
|
|
193
|
-
|
|
194
|
-
`
|
|
122
|
+
`content: '${text.replace(/'/g, "\\'").replace(/\n/g, '\\A ')}'`,
|
|
123
|
+
`font-size: ${size}px`,
|
|
124
|
+
`color: ${color}`,
|
|
125
|
+
'white-space: pre-wrap',
|
|
126
|
+
'word-wrap: break-word',
|
|
127
|
+
'text-align: center',
|
|
128
|
+
'display: flex',
|
|
129
|
+
'align-items: center',
|
|
130
|
+
'justify-content: center',
|
|
131
|
+
`font-family: ${fontFamily}`,
|
|
132
|
+
'opacity: 0.3',
|
|
133
|
+
'background-image: none'
|
|
195
134
|
)
|
|
196
135
|
}
|
|
136
|
+
} else if (st !== 'none' && st !== 'index') {
|
|
137
|
+
styles.push(
|
|
138
|
+
`background-image: ${st}`,
|
|
139
|
+
'background-position: center',
|
|
140
|
+
`filter: ${createFilterStyle(props)}`
|
|
141
|
+
)
|
|
142
|
+
}
|
|
197
143
|
|
|
198
|
-
|
|
144
|
+
return `#container .session-batch-active .xterm-screen::before {
|
|
199
145
|
${styles.join(';')};
|
|
200
146
|
}`
|
|
201
|
-
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function writeCss (props, styleTag) {
|
|
150
|
+
const { tabs = [] } = props
|
|
151
|
+
const tabStyles = await Promise.all(
|
|
152
|
+
tabs
|
|
153
|
+
.map(tab => createStyleForTab(tab, props))
|
|
154
|
+
)
|
|
155
|
+
const globalStyle = await createGlobalStyle(props)
|
|
156
|
+
const allStyles = [
|
|
157
|
+
globalStyle,
|
|
158
|
+
...tabStyles
|
|
159
|
+
].filter(Boolean).join('\n')
|
|
160
|
+
styleTag.innerHTML = allStyles
|
|
161
|
+
}
|
|
202
162
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
163
|
+
export default function CssOverwrite (props) {
|
|
164
|
+
const { configLoaded } = props
|
|
165
|
+
const styleTagRef = useRef(null)
|
|
166
|
+
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (!configLoaded) {
|
|
169
|
+
return
|
|
209
170
|
}
|
|
210
171
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const allStyles = [
|
|
218
|
-
globalStyle,
|
|
219
|
-
...tabStyles
|
|
220
|
-
].filter(Boolean).join('\n')
|
|
221
|
-
CssOverwrite.styleTag.innerHTML = allStyles
|
|
222
|
-
}
|
|
172
|
+
if (!styleTagRef.current) {
|
|
173
|
+
styleTagRef.current = document.createElement('style')
|
|
174
|
+
styleTagRef.current.type = 'text/css'
|
|
175
|
+
styleTagRef.current.id = themeDomId
|
|
176
|
+
document.getElementsByTagName('head')[0].appendChild(styleTagRef.current)
|
|
177
|
+
}
|
|
223
178
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
179
|
+
const timeoutId = setTimeout(() => {
|
|
180
|
+
writeCss(props, styleTagRef.current)
|
|
181
|
+
}, 100)
|
|
227
182
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
183
|
+
return () => {
|
|
184
|
+
clearTimeout(timeoutId)
|
|
185
|
+
}
|
|
186
|
+
}, [
|
|
187
|
+
configLoaded,
|
|
188
|
+
props.terminalBackgroundImagePath,
|
|
189
|
+
props.terminalBackgroundFilterBlur,
|
|
190
|
+
props.terminalBackgroundFilterOpacity,
|
|
191
|
+
props.terminalBackgroundFilterBrightness,
|
|
192
|
+
props.terminalBackgroundFilterContrast,
|
|
193
|
+
props.terminalBackgroundFilterGrayscale,
|
|
194
|
+
props.terminalBackgroundText,
|
|
195
|
+
props.terminalBackgroundTextSize,
|
|
196
|
+
props.terminalBackgroundTextColor,
|
|
197
|
+
props.terminalBackgroundTextFontFamily,
|
|
198
|
+
props.tabs
|
|
199
|
+
])
|
|
200
|
+
|
|
201
|
+
return null
|
|
231
202
|
}
|
|
@@ -2,30 +2,21 @@
|
|
|
2
2
|
* ui theme
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { useEffect
|
|
6
|
-
import eq from 'fast-deep-equal'
|
|
5
|
+
import { useEffect } from 'react'
|
|
7
6
|
|
|
8
7
|
const themeDomId = 'custom-css'
|
|
9
8
|
|
|
10
9
|
export default function CustomCss (props) {
|
|
11
|
-
const { customCss } = props
|
|
12
|
-
const prevRef = useRef(null)
|
|
13
|
-
|
|
14
|
-
async function applyTheme () {
|
|
15
|
-
const style = document.getElementById(themeDomId)
|
|
16
|
-
style.innerHTML = customCss
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
applyTheme()
|
|
21
|
-
}, [])
|
|
10
|
+
const { customCss, configLoaded } = props
|
|
22
11
|
|
|
23
12
|
useEffect(() => {
|
|
24
|
-
if (
|
|
25
|
-
|
|
13
|
+
if (configLoaded) {
|
|
14
|
+
const style = document.getElementById(themeDomId)
|
|
15
|
+
if (style) {
|
|
16
|
+
style.innerHTML = customCss || ''
|
|
17
|
+
}
|
|
26
18
|
}
|
|
27
|
-
|
|
28
|
-
}, [customCss])
|
|
19
|
+
}, [customCss, configLoaded])
|
|
29
20
|
|
|
30
21
|
return null
|
|
31
22
|
}
|
|
@@ -137,7 +137,13 @@ const bookmarkSchema = {
|
|
|
137
137
|
title: 'string - bookmark title',
|
|
138
138
|
description: 'string - bookmark description',
|
|
139
139
|
startDirectoryLocal: 'string - local starting directory',
|
|
140
|
-
runScripts: 'array - run scripts after connected ({delay,script})'
|
|
140
|
+
runScripts: 'array - run scripts after connected ({delay,script})',
|
|
141
|
+
execWindows: 'string - Windows exec path (overrides global setting)',
|
|
142
|
+
execMac: 'string - Mac exec path (overrides global setting)',
|
|
143
|
+
execLinux: 'string - Linux exec path (overrides global setting)',
|
|
144
|
+
execWindowsArgs: 'array - Windows exec arguments',
|
|
145
|
+
execMacArgs: 'array - Mac exec arguments',
|
|
146
|
+
execLinuxArgs: 'array - Linux exec arguments'
|
|
141
147
|
},
|
|
142
148
|
spice: {
|
|
143
149
|
type: 'spice',
|
|
@@ -4,8 +4,7 @@ import { defaultColors, getRandomHexColor } from '../../../common/rand-hex-color
|
|
|
4
4
|
import { HexInput } from './hex-input.jsx'
|
|
5
5
|
import './color-picker.styl'
|
|
6
6
|
|
|
7
|
-
export
|
|
8
|
-
const { value, onChange } = props
|
|
7
|
+
export function ColorPicker ({ value, onChange, ref, disabled, isRgba }) {
|
|
9
8
|
const [visible, setVisible] = useState(false)
|
|
10
9
|
|
|
11
10
|
const handleChange = (color) => {
|
|
@@ -18,7 +17,7 @@ export const ColorPicker = React.forwardRef((props, ref) => {
|
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
function onColorChange (color) {
|
|
21
|
-
handleChange(
|
|
20
|
+
handleChange(isRgba ? color.toRgbString() : color.toHexString())
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
function renderContent () {
|
|
@@ -59,7 +58,7 @@ export const ColorPicker = React.forwardRef((props, ref) => {
|
|
|
59
58
|
<div ref={ref} className='color-picker-choose' style={{ backgroundColor: value }} />
|
|
60
59
|
)
|
|
61
60
|
|
|
62
|
-
if (
|
|
61
|
+
if (disabled) return inner
|
|
63
62
|
|
|
64
63
|
return (
|
|
65
64
|
<Popover
|
|
@@ -72,7 +71,4 @@ export const ColorPicker = React.forwardRef((props, ref) => {
|
|
|
72
71
|
{inner}
|
|
73
72
|
</Popover>
|
|
74
73
|
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
ColorPicker.displayName = 'ColorPicker'
|
|
78
|
-
ColorPicker.static = true
|
|
74
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bookmark form - exec settings field
|
|
3
|
+
* Renders exec path and arguments fields for Windows/Mac/Linux
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react'
|
|
6
|
+
import { Form, Input, Select, Space } from 'antd'
|
|
7
|
+
import { formItemLayout } from '../../../common/form-layout'
|
|
8
|
+
|
|
9
|
+
const FormItem = Form.Item
|
|
10
|
+
|
|
11
|
+
export default function ExecSettingsField () {
|
|
12
|
+
const platforms = ['linux', 'mac', 'windows']
|
|
13
|
+
return platforms.map((platform) => {
|
|
14
|
+
const platformCapitalized = platform.charAt(0).toUpperCase() + platform.slice(1)
|
|
15
|
+
const label = `exec${platformCapitalized}`
|
|
16
|
+
return (
|
|
17
|
+
<React.Fragment key={platform}>
|
|
18
|
+
<FormItem
|
|
19
|
+
{...formItemLayout}
|
|
20
|
+
label={label}
|
|
21
|
+
>
|
|
22
|
+
<Space.Compact className='width-100'>
|
|
23
|
+
<FormItem noStyle name={label}>
|
|
24
|
+
<Input
|
|
25
|
+
placeholder={`${platformCapitalized} exec path`}
|
|
26
|
+
maxLength={500}
|
|
27
|
+
/>
|
|
28
|
+
</FormItem>
|
|
29
|
+
<FormItem
|
|
30
|
+
noStyle
|
|
31
|
+
name={`exec${platformCapitalized}Args`}
|
|
32
|
+
>
|
|
33
|
+
<Select
|
|
34
|
+
mode='tags'
|
|
35
|
+
placeholder={`${platformCapitalized} exec arguments`}
|
|
36
|
+
tokenSeparators={['\n']}
|
|
37
|
+
/>
|
|
38
|
+
</FormItem>
|
|
39
|
+
</Space.Compact>
|
|
40
|
+
</FormItem>
|
|
41
|
+
</React.Fragment>
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
}
|
|
@@ -13,6 +13,7 @@ import SshTunnels from './ssh-tunnels.jsx'
|
|
|
13
13
|
import SshAgent from './ssh-agent.jsx'
|
|
14
14
|
import ConnectionHopping from './connection-hopping.jsx'
|
|
15
15
|
import TerminalBackgroundField from './terminal-background.jsx'
|
|
16
|
+
import ExecSettingsField from './exec-settings-field.jsx'
|
|
16
17
|
import useQuickCmds from './quick-commands.jsx'
|
|
17
18
|
import ProfileItem from './profile-item.jsx'
|
|
18
19
|
import renderRunScripts from './run-scripts.jsx'
|
|
@@ -150,6 +151,8 @@ export function renderFormItem (item, formItemLayout, form, ctxProps, index) {
|
|
|
150
151
|
)
|
|
151
152
|
case 'terminalBackground':
|
|
152
153
|
return <TerminalBackgroundField key={name} />
|
|
154
|
+
case 'execSettings':
|
|
155
|
+
return <ExecSettingsField key={name} />
|
|
153
156
|
case 'profileItem':
|
|
154
157
|
return <ProfileItem key={name} store={ctxProps.store} profileFilter={item.profileFilter} />
|
|
155
158
|
case 'quickCommands':
|