@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.
@@ -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
- description={notif.description}
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 getTextFromReactChildren (children) {
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={message}>{message}</div>
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
- {description && (
157
+ {validDescriptions.length > 0 && (
138
158
  <div className='notification-description'>
139
- {description}
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(description, e)}
164
+ onClick={(e) => handleCopy(validDescriptions, e)}
143
165
  />
144
166
  </div>
145
167
  )}
@@ -0,0 +1,5 @@
1
+ export default function Placeholder () {
2
+ return (
3
+ <div id='placeholder' className='placeholder-wrap' />
4
+ )
5
+ }
@@ -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
@@ -169,6 +169,7 @@ export default auto(function Index (props) {
169
169
  'sidebarPanelTab',
170
170
  'openWidgetsModal'
171
171
  ]),
172
+ zoom: config.zoom,
172
173
  fileTransfers: copiedTransfer,
173
174
  transferHistory: copiedHistory,
174
175
  upgradeInfo,
@@ -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
- {props.children[1]}
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
  },
@@ -118,6 +118,10 @@
118
118
  color var(--text-dark)
119
119
  &:hover
120
120
  color var(--text-light)
121
+ .zoom-reset-icon
122
+ color var(--primary)
123
+ &:hover
124
+ color var(--text-light)
121
125
 
122
126
  .btns .upgrade-icon
123
127
  color var(--success)
@@ -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}
@@ -171,3 +171,8 @@
171
171
  z-index 100
172
172
  background var(--main)
173
173
  max-width calc(100% - 24px)
174
+
175
+ .terminal-fontsize-reset
176
+ position absolute
177
+ left 8px
178
+ bottom 4px
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "3.15.100",
3
+ "version": "3.15.105",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",