@electerm/electerm-react 3.15.100 → 3.15.105
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/common/notification.jsx +42 -20
- package/client/components/common/placeholder.jsx +5 -0
- package/client/components/common/zoom-control.jsx +24 -2
- package/client/components/footer/footer.styl +7 -0
- package/client/components/main/main.jsx +1 -0
- package/client/components/setting-panel/col.jsx +8 -1
- package/client/components/setting-panel/setting-wrap.styl +18 -0
- package/client/components/sidebar/index.jsx +22 -2
- package/client/components/sidebar/info-modal.jsx +2 -0
- package/client/components/sidebar/sidebar.styl +4 -0
- package/client/components/terminal/terminal.jsx +46 -2
- package/client/components/terminal/terminal.styl +5 -0
- package/package.json +1 -1
|
@@ -9,7 +9,36 @@ import './notification.styl'
|
|
|
9
9
|
const notifications = []
|
|
10
10
|
const NOTIFICATION_EVENT = 'notification-update'
|
|
11
11
|
|
|
12
|
+
function getTextFromReactChildren (children) {
|
|
13
|
+
if (typeof children === 'string' || typeof children === 'number') {
|
|
14
|
+
return String(children)
|
|
15
|
+
}
|
|
16
|
+
if (React.isValidElement(children)) {
|
|
17
|
+
return getTextFromReactChildren(children.props.children)
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(children)) {
|
|
20
|
+
return children.map(getTextFromReactChildren).join('\n')
|
|
21
|
+
}
|
|
22
|
+
return ''
|
|
23
|
+
}
|
|
24
|
+
|
|
12
25
|
function addNotification (notif) {
|
|
26
|
+
notif.descriptions = notif.description ? [notif.description] : []
|
|
27
|
+
if (notif.type === 'error') {
|
|
28
|
+
const messageText = getTextFromReactChildren(notif.message)
|
|
29
|
+
const existingIndex = notifications.findIndex(n =>
|
|
30
|
+
n.type === 'error' &&
|
|
31
|
+
getTextFromReactChildren(n.message) === messageText
|
|
32
|
+
)
|
|
33
|
+
if (existingIndex > -1) {
|
|
34
|
+
const existing = notifications[existingIndex]
|
|
35
|
+
existing.count = (existing.count || 1) + 1
|
|
36
|
+
existing.descriptions.push(notif.description)
|
|
37
|
+
window.dispatchEvent(new CustomEvent(NOTIFICATION_EVENT))
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
notif.count = 1
|
|
41
|
+
}
|
|
13
42
|
notifications.push(notif)
|
|
14
43
|
window.dispatchEvent(new CustomEvent(NOTIFICATION_EVENT))
|
|
15
44
|
}
|
|
@@ -60,9 +89,10 @@ export function NotificationContainer () {
|
|
|
60
89
|
<NotificationItem
|
|
61
90
|
key={notif.key}
|
|
62
91
|
message={notif.message || notif.type}
|
|
63
|
-
|
|
92
|
+
descriptions={notif.descriptions}
|
|
64
93
|
type={notif.type}
|
|
65
94
|
duration={notif.duration}
|
|
95
|
+
count={notif.count || 1}
|
|
66
96
|
onClose={() => removeNotification(notif.key)}
|
|
67
97
|
/>
|
|
68
98
|
))}
|
|
@@ -70,20 +100,7 @@ export function NotificationContainer () {
|
|
|
70
100
|
)
|
|
71
101
|
}
|
|
72
102
|
|
|
73
|
-
function
|
|
74
|
-
if (typeof children === 'string' || typeof children === 'number') {
|
|
75
|
-
return String(children)
|
|
76
|
-
}
|
|
77
|
-
if (React.isValidElement(children)) {
|
|
78
|
-
return getTextFromReactChildren(children.props.children)
|
|
79
|
-
}
|
|
80
|
-
if (Array.isArray(children)) {
|
|
81
|
-
return children.map(getTextFromReactChildren).join('\n')
|
|
82
|
-
}
|
|
83
|
-
return ''
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function NotificationItem ({ message, description, type, onClose, duration = 18.5 }) {
|
|
103
|
+
function NotificationItem ({ message, descriptions = [], type, onClose, duration = 18.5, count = 1 }) {
|
|
87
104
|
const timeoutRef = useRef(null)
|
|
88
105
|
|
|
89
106
|
useEffect(() => {
|
|
@@ -96,7 +113,7 @@ function NotificationItem ({ message, description, type, onClose, duration = 18.
|
|
|
96
113
|
timeoutRef.current = null
|
|
97
114
|
}
|
|
98
115
|
}
|
|
99
|
-
}, [])
|
|
116
|
+
}, [count])
|
|
100
117
|
|
|
101
118
|
const handleMouseEnter = () => {
|
|
102
119
|
if (timeoutRef.current) {
|
|
@@ -118,6 +135,9 @@ function NotificationItem ({ message, description, type, onClose, duration = 18.
|
|
|
118
135
|
}
|
|
119
136
|
|
|
120
137
|
const className = classnames('notification', type)
|
|
138
|
+
const titleText = getTextFromReactChildren(message)
|
|
139
|
+
const title = count > 1 ? `(${count}) ${titleText}` : message
|
|
140
|
+
const validDescriptions = descriptions.filter(Boolean)
|
|
121
141
|
|
|
122
142
|
return (
|
|
123
143
|
<div
|
|
@@ -128,18 +148,20 @@ function NotificationItem ({ message, description, type, onClose, duration = 18.
|
|
|
128
148
|
<div className='notification-content'>
|
|
129
149
|
<div className='notification-message'>
|
|
130
150
|
<div className='notification-icon'>{messageIcons[type]}</div>
|
|
131
|
-
<div className='notification-title' title={
|
|
151
|
+
<div className='notification-title' title={titleText}>{title}</div>
|
|
132
152
|
<CopyOutlined
|
|
133
153
|
className='notification-copy-icon'
|
|
134
154
|
onClick={(e) => handleCopy(message, e)}
|
|
135
155
|
/>
|
|
136
156
|
</div>
|
|
137
|
-
{
|
|
157
|
+
{validDescriptions.length > 0 && (
|
|
138
158
|
<div className='notification-description'>
|
|
139
|
-
{
|
|
159
|
+
{validDescriptions.map((desc, i) => (
|
|
160
|
+
<div key={i}>{desc}</div>
|
|
161
|
+
))}
|
|
140
162
|
<CopyOutlined
|
|
141
163
|
className='notification-copy-icon'
|
|
142
|
-
onClick={(e) => handleCopy(
|
|
164
|
+
onClick={(e) => handleCopy(validDescriptions, e)}
|
|
143
165
|
/>
|
|
144
166
|
</div>
|
|
145
167
|
)}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import InputNumberConfirm from './input-number-confirm'
|
|
3
3
|
import {
|
|
4
|
-
Space
|
|
4
|
+
Space,
|
|
5
|
+
Tooltip
|
|
5
6
|
} from 'antd'
|
|
6
7
|
import {
|
|
7
8
|
MinusCircleOutlined,
|
|
8
|
-
PlusCircleOutlined
|
|
9
|
+
PlusCircleOutlined,
|
|
10
|
+
AimOutlined
|
|
9
11
|
} from '@ant-design/icons'
|
|
10
12
|
|
|
13
|
+
const e = window.translate
|
|
14
|
+
|
|
11
15
|
export default function ZoomControl (props) {
|
|
12
16
|
const {
|
|
13
17
|
value,
|
|
@@ -33,6 +37,23 @@ export default function ZoomControl (props) {
|
|
|
33
37
|
onChange(next)
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
const handleReset = () => {
|
|
41
|
+
onChange(1)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const isDefault = Math.round(value * 100) === 100
|
|
45
|
+
|
|
46
|
+
const resetIcon = isDefault
|
|
47
|
+
? null
|
|
48
|
+
: (
|
|
49
|
+
<Tooltip title={e('resetzoom')}>
|
|
50
|
+
<AimOutlined
|
|
51
|
+
onClick={handleReset}
|
|
52
|
+
className='pointer font16 mg1r'
|
|
53
|
+
/>
|
|
54
|
+
</Tooltip>
|
|
55
|
+
)
|
|
56
|
+
|
|
36
57
|
return (
|
|
37
58
|
<InputNumberConfirm
|
|
38
59
|
value={Math.round(value * 100)}
|
|
@@ -43,6 +64,7 @@ export default function ZoomControl (props) {
|
|
|
43
64
|
suffix='%'
|
|
44
65
|
addonBefore={
|
|
45
66
|
<Space.Compact>
|
|
67
|
+
{resetIcon}
|
|
46
68
|
<PlusCircleOutlined
|
|
47
69
|
onClick={handleAdd}
|
|
48
70
|
className='mg1r pointer font16'
|
|
@@ -31,6 +31,13 @@
|
|
|
31
31
|
.terminal-footer-info
|
|
32
32
|
margin-right 10px
|
|
33
33
|
line-height 39px
|
|
34
|
+
.terminal-footer-history
|
|
35
|
+
display flex
|
|
36
|
+
align-items center
|
|
37
|
+
.terminal-footer-qm
|
|
38
|
+
// Q is now an icon-only button (5px internal padding); reduce left
|
|
39
|
+
// margin so the history icon stays roughly equidistant from AI and Q
|
|
40
|
+
margin-left 3px
|
|
34
41
|
.batch-input-outer
|
|
35
42
|
.bi-full
|
|
36
43
|
display none
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* two column layout, left column fixed with, right column auto width
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import Placeholder from '../common/placeholder'
|
|
6
|
+
|
|
5
7
|
export default function SettingCol (props) {
|
|
6
8
|
return (
|
|
7
9
|
<div className='setting-col'>
|
|
@@ -11,7 +13,12 @@ export default function SettingCol (props) {
|
|
|
11
13
|
<div
|
|
12
14
|
className='setting-row setting-row-right'
|
|
13
15
|
>
|
|
14
|
-
|
|
16
|
+
<div className='setting-col-content'>
|
|
17
|
+
{props.children[1]}
|
|
18
|
+
</div>
|
|
19
|
+
<div className='setting-col-placeholder'>
|
|
20
|
+
<Placeholder />
|
|
21
|
+
</div>
|
|
15
22
|
</div>
|
|
16
23
|
</div>
|
|
17
24
|
)
|
|
@@ -38,6 +38,20 @@
|
|
|
38
38
|
bottom 0
|
|
39
39
|
top 129px
|
|
40
40
|
padding 20px
|
|
41
|
+
display flex
|
|
42
|
+
flex-direction column
|
|
43
|
+
overflow hidden
|
|
44
|
+
min-height 400px
|
|
45
|
+
.setting-col-content
|
|
46
|
+
flex 1
|
|
47
|
+
overflow-y auto
|
|
48
|
+
min-height 0
|
|
49
|
+
.setting-col-placeholder
|
|
50
|
+
flex-shrink 0
|
|
51
|
+
display flex
|
|
52
|
+
justify-content flex-end
|
|
53
|
+
.carbonads-wrap
|
|
54
|
+
margin 8px 0 0
|
|
41
55
|
.setting-tabs-bookmarks
|
|
42
56
|
.setting-row-left
|
|
43
57
|
width 440px
|
|
@@ -75,6 +89,10 @@
|
|
|
75
89
|
bottom auto
|
|
76
90
|
top auto
|
|
77
91
|
padding 10px
|
|
92
|
+
position static
|
|
93
|
+
overflow visible
|
|
94
|
+
.setting-col-content
|
|
95
|
+
overflow visible
|
|
78
96
|
.setting-tabs-bookmarks
|
|
79
97
|
.setting-row-left
|
|
80
98
|
width auto
|
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
SettingOutlined,
|
|
8
8
|
UpCircleOutlined,
|
|
9
9
|
AppstoreOutlined,
|
|
10
|
-
ThunderboltOutlined
|
|
10
|
+
ThunderboltOutlined,
|
|
11
|
+
AimOutlined
|
|
11
12
|
} from '@ant-design/icons'
|
|
12
13
|
import { Tooltip, Popover } from 'antd'
|
|
13
14
|
import SideBarPanel from './sidebar-panel'
|
|
@@ -42,7 +43,8 @@ export default function Sidebar (props) {
|
|
|
42
43
|
showModal,
|
|
43
44
|
showInfoModal,
|
|
44
45
|
sidebarPanelTab,
|
|
45
|
-
openWidgetsModal
|
|
46
|
+
openWidgetsModal,
|
|
47
|
+
zoom
|
|
46
48
|
} = props
|
|
47
49
|
|
|
48
50
|
const { store } = window
|
|
@@ -85,6 +87,10 @@ export default function Sidebar (props) {
|
|
|
85
87
|
window.store.upgradeInfo.showUpgradeModal = true
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
const handleZoomReset = () => {
|
|
91
|
+
store.onZoomReset()
|
|
92
|
+
}
|
|
93
|
+
|
|
88
94
|
const {
|
|
89
95
|
onNewSsh,
|
|
90
96
|
openSetting,
|
|
@@ -203,6 +209,20 @@ export default function Sidebar (props) {
|
|
|
203
209
|
onClick={openAbout}
|
|
204
210
|
/>
|
|
205
211
|
</SideIcon>
|
|
212
|
+
{
|
|
213
|
+
Math.round((zoom ?? 1) * 100) !== 100
|
|
214
|
+
? (
|
|
215
|
+
<SideIcon
|
|
216
|
+
title={e('resetzoom')}
|
|
217
|
+
>
|
|
218
|
+
<AimOutlined
|
|
219
|
+
className='iblock font16 control-icon zoom-reset-icon'
|
|
220
|
+
onClick={handleZoomReset}
|
|
221
|
+
/>
|
|
222
|
+
</SideIcon>
|
|
223
|
+
)
|
|
224
|
+
: null
|
|
225
|
+
}
|
|
206
226
|
{
|
|
207
227
|
!checkingRemoteVersion && !showUpgradeModal && shouldUpgrade
|
|
208
228
|
? (
|
|
@@ -14,6 +14,7 @@ import { Tabs, Button } from 'antd'
|
|
|
14
14
|
import Modal from '../common/modal'
|
|
15
15
|
import Link from '../common/external-link'
|
|
16
16
|
import LogoElem from '../common/logo-elem'
|
|
17
|
+
import Placeholder from '../common/placeholder'
|
|
17
18
|
import RunningTime from './app-running-time'
|
|
18
19
|
import { auto } from 'manate/react'
|
|
19
20
|
import { useState } from 'react'
|
|
@@ -230,6 +231,7 @@ export default auto(function InfoModal (props) {
|
|
|
230
231
|
<InfoCircleOutlined /> <b className='mg1r'>{window.store.installSrc}</b>
|
|
231
232
|
</p>
|
|
232
233
|
{renderCheckUpdate()}
|
|
234
|
+
<Placeholder />
|
|
233
235
|
</>
|
|
234
236
|
)
|
|
235
237
|
},
|
|
@@ -4,7 +4,8 @@ import clone from '../../common/to-simple-obj.js'
|
|
|
4
4
|
import resolve from '../../common/resolve.js'
|
|
5
5
|
import {
|
|
6
6
|
Spin,
|
|
7
|
-
Dropdown
|
|
7
|
+
Dropdown,
|
|
8
|
+
Button
|
|
8
9
|
} from 'antd'
|
|
9
10
|
import message from '../common/message'
|
|
10
11
|
import { notification } from '../common/notification'
|
|
@@ -40,6 +41,9 @@ import { getFolderFromFilePath } from '../sftp/file-read.js'
|
|
|
40
41
|
import { CommandTrackerAddon } from './command-tracker-addon.js'
|
|
41
42
|
import { Osc52Addon } from './osc52-addon.js'
|
|
42
43
|
import AIIcon from '../icons/ai-icon.jsx'
|
|
44
|
+
import {
|
|
45
|
+
AimOutlined
|
|
46
|
+
} from '@ant-design/icons'
|
|
43
47
|
import {
|
|
44
48
|
getShellIntegrationCommand,
|
|
45
49
|
detectRemoteShell,
|
|
@@ -90,7 +94,8 @@ class Term extends Component {
|
|
|
90
94
|
reconnectCountdown: null,
|
|
91
95
|
terminalError: null,
|
|
92
96
|
dropFileModalVisible: false,
|
|
93
|
-
droppedFiles: []
|
|
97
|
+
droppedFiles: [],
|
|
98
|
+
fontSizeChanged: false
|
|
94
99
|
}
|
|
95
100
|
this.id = `term-${this.props.tab.id}`
|
|
96
101
|
refs.add(this.id, this)
|
|
@@ -230,6 +235,10 @@ class Term extends Component {
|
|
|
230
235
|
if (['fontFamily', 'fontSize'].includes(name)) {
|
|
231
236
|
this.onResize()
|
|
232
237
|
}
|
|
238
|
+
if (name === 'fontSize') {
|
|
239
|
+
this.originalFontSize = curr
|
|
240
|
+
this.setState({ fontSizeChanged: false })
|
|
241
|
+
}
|
|
233
242
|
}
|
|
234
243
|
}
|
|
235
244
|
|
|
@@ -291,6 +300,23 @@ class Term extends Component {
|
|
|
291
300
|
}
|
|
292
301
|
term.options.fontSize = term.options.fontSize + v
|
|
293
302
|
window.store.triggerResize()
|
|
303
|
+
if (this.originalFontSize == null) {
|
|
304
|
+
this.originalFontSize = term.options.fontSize - v
|
|
305
|
+
}
|
|
306
|
+
this.setState({
|
|
307
|
+
fontSizeChanged: term.options.fontSize !== this.originalFontSize
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
handleResetFontSize = () => {
|
|
312
|
+
const { term } = this
|
|
313
|
+
if (!term || this.originalFontSize == null) {
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
term.options.fontSize = this.originalFontSize
|
|
317
|
+
window.store.triggerResize()
|
|
318
|
+
this.setState({ fontSizeChanged: false })
|
|
319
|
+
term.focus()
|
|
294
320
|
}
|
|
295
321
|
|
|
296
322
|
isActiveTerminal = () => {
|
|
@@ -1720,6 +1746,23 @@ class Term extends Component {
|
|
|
1720
1746
|
Object.assign(window.store.terminalInfoProps, infoProps)
|
|
1721
1747
|
}
|
|
1722
1748
|
|
|
1749
|
+
renderResetFontSizeButton = () => {
|
|
1750
|
+
if (!this.state.fontSizeChanged) {
|
|
1751
|
+
return null
|
|
1752
|
+
}
|
|
1753
|
+
const txt = `${e('reset')} ${e('fontSize')}`
|
|
1754
|
+
return (
|
|
1755
|
+
<Button
|
|
1756
|
+
className='terminal-fontsize-reset'
|
|
1757
|
+
onClick={this.handleResetFontSize}
|
|
1758
|
+
type='default'
|
|
1759
|
+
size='small'
|
|
1760
|
+
title={txt}
|
|
1761
|
+
icon={<AimOutlined />}
|
|
1762
|
+
/>
|
|
1763
|
+
)
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1723
1766
|
// getPwd = async () => {
|
|
1724
1767
|
// const { sessionId, config } = this.props
|
|
1725
1768
|
// const { pid } = this.state
|
|
@@ -1818,6 +1861,7 @@ class Term extends Component {
|
|
|
1818
1861
|
<ReconnectOverlay
|
|
1819
1862
|
countdown={this.state.reconnectCountdown}
|
|
1820
1863
|
/>
|
|
1864
|
+
{this.renderResetFontSizeButton()}
|
|
1821
1865
|
<DropFileModal
|
|
1822
1866
|
visible={this.state.dropFileModalVisible}
|
|
1823
1867
|
files={this.state.droppedFiles}
|