@electerm/electerm-react 2.17.16 → 3.0.18

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 (32) hide show
  1. package/client/common/cache.js +4 -2
  2. package/client/common/db.js +3 -5
  3. package/client/common/default-setting.js +1 -1
  4. package/client/common/pass-enc.js +0 -21
  5. package/client/common/safe-local-storage.js +106 -1
  6. package/client/components/ai/ai-chat-history-item.jsx +132 -8
  7. package/client/components/ai/ai-chat.jsx +10 -159
  8. package/client/components/ai/ai.styl +6 -1
  9. package/client/components/bookmark-form/common/render-auth-ssh.jsx +1 -117
  10. package/client/components/bookmark-form/config/common-fields.js +8 -0
  11. package/client/components/bookmark-form/config/ftp.js +1 -0
  12. package/client/components/bookmark-form/config/local.js +10 -51
  13. package/client/components/session/sessions.jsx +1 -0
  14. package/client/components/setting-panel/setting-terminal.jsx +2 -2
  15. package/client/components/sftp/address-bookmark-item.jsx +1 -1
  16. package/client/components/sftp/address-bookmark.jsx +11 -1
  17. package/client/components/sftp/file-item.jsx +1 -1
  18. package/client/components/sftp/sftp-entry.jsx +35 -12
  19. package/client/components/sidebar/history.jsx +1 -1
  20. package/client/components/tabs/app-drag.jsx +13 -12
  21. package/client/components/tabs/tabs.styl +1 -1
  22. package/client/components/terminal/reconnect-overlay.jsx +27 -0
  23. package/client/components/terminal/socket-close-warning.jsx +94 -0
  24. package/client/components/terminal/terminal.jsx +87 -58
  25. package/client/components/terminal/terminal.styl +12 -0
  26. package/client/components/terminal/transfer-client-base.js +3 -3
  27. package/client/components/text-editor/edit-with-custom-editor.jsx +3 -2
  28. package/client/store/init-state.js +3 -3
  29. package/client/store/sync.js +0 -1
  30. package/client/store/tab.js +2 -1
  31. package/client/store/watch.js +3 -3
  32. package/package.json +1 -1
@@ -3,12 +3,8 @@ import { handleErr } from '../../common/fetch.jsx'
3
3
  import { isEqual, pick, debounce, throttle } from 'lodash-es'
4
4
  import clone from '../../common/to-simple-obj.js'
5
5
  import resolve from '../../common/resolve.js'
6
- import {
7
- ReloadOutlined
8
- } from '@ant-design/icons'
9
6
  import {
10
7
  Spin,
11
- Button,
12
8
  Dropdown
13
9
  } from 'antd'
14
10
  import { notification } from '../common/notification'
@@ -50,6 +46,8 @@ import ExternalLink from '../common/external-link.jsx'
50
46
  import createDefaultLogPath from '../../common/default-log-path.js'
51
47
  import SearchResultBar from './terminal-search-bar'
52
48
  import RemoteFloatControl from '../common/remote-float-control'
49
+ import { showSocketCloseWarning } from './socket-close-warning.jsx'
50
+ import ReconnectOverlay from './reconnect-overlay.jsx'
53
51
  import {
54
52
  loadTerminal,
55
53
  loadFitAddon,
@@ -76,7 +74,8 @@ class Term extends Component {
76
74
  lines: [],
77
75
  searchResults: [],
78
76
  matchIndex: -1,
79
- totalLines: 0
77
+ totalLines: 0,
78
+ reconnectCountdown: null
80
79
  }
81
80
  this.id = `term-${this.props.tab.id}`
82
81
  refs.add(this.id, this)
@@ -833,25 +832,19 @@ class Term extends Component {
833
832
  this.searchAddon.onDidChangeResults(this.onSearchResultsChange)
834
833
  const Unicode11Addon = await loadUnicode11Addon()
835
834
  const unicode11Addon = new Unicode11Addon()
836
- if (config.enableSixel !== false) {
837
- try {
838
- const ImageAddon = await loadImageAddon()
839
- this.imageAddon = new ImageAddon({
840
- enableSizeReports: false,
841
- sixelSupport: true,
842
- iipSupport: false
843
- })
844
- term.loadAddon(this.imageAddon)
845
- } catch (err) {
846
- console.error('load sixel addon failed', err)
847
- }
848
- }
849
835
  term.loadAddon(unicode11Addon)
850
836
  term.loadAddon(ligtureAddon)
851
837
  term.unicode.activeVersion = '11'
852
838
  term.loadAddon(this.fitAddon)
853
839
  term.loadAddon(this.searchAddon)
854
840
  term.loadAddon(this.cmdAddon)
841
+ if (tab.enableTerminalImage) {
842
+ const ImageAddon = await loadImageAddon()
843
+ this.imageAddon = new ImageAddon({
844
+ pixelLimit: 33554432
845
+ })
846
+ term.loadAddon(this.imageAddon)
847
+ }
855
848
  term.onData(this.onData)
856
849
  this.term = term
857
850
  term.onSelectionChange(this.onSelectionChange)
@@ -1145,10 +1138,13 @@ class Term extends Component {
1145
1138
  ? typeMap.remote
1146
1139
  : typeMap.local
1147
1140
  })
1141
+ const isAutoReconnect = !!(tab.autoReConnect && this.props.config.autoReconnectTerminal)
1148
1142
  const r = await createTerm(opts)
1149
1143
  .catch(err => {
1150
- const text = err.message
1151
- handleErr({ message: text })
1144
+ if (!isAutoReconnect) {
1145
+ const text = err.message
1146
+ handleErr({ message: text })
1147
+ }
1152
1148
  })
1153
1149
  if (typeof r === 'string' && r.includes('fail')) {
1154
1150
  return this.promote()
@@ -1160,6 +1156,10 @@ class Term extends Component {
1160
1156
  loading: false
1161
1157
  })
1162
1158
  if (!r) {
1159
+ if (isAutoReconnect) {
1160
+ this.scheduleAutoReconnect(3000)
1161
+ return
1162
+ }
1163
1163
  this.setStatus(statusMap.error)
1164
1164
  return
1165
1165
  }
@@ -1260,46 +1260,70 @@ class Term extends Component {
1260
1260
  this.setStatus(
1261
1261
  statusMap.error
1262
1262
  )
1263
- if (!this.isActiveTerminal() || !window.focused) {
1264
- return false
1265
- }
1266
1263
  if (this.userTypeExit) {
1267
1264
  return this.props.delTab(this.props.tab.id)
1268
1265
  }
1269
- const key = `open${Date.now()}`
1270
- function closeMsg () {
1271
- notification.destroy(key)
1272
- }
1273
- this.socketCloseWarning = notification.warning({
1274
- key,
1275
- message: e('socketCloseTip'),
1276
- duration: 30,
1277
- description: (
1278
- <div className='pd2y'>
1279
- <Button
1280
- className='mg1r'
1281
- type='primary'
1282
- onClick={() => {
1283
- closeMsg()
1284
- this.props.delTab(this.props.tab.id)
1285
- }}
1286
- >
1287
- {e('close')}
1288
- </Button>
1289
- <Button
1290
- icon={<ReloadOutlined />}
1291
- onClick={() => {
1292
- closeMsg()
1293
- this.props.reloadTab(
1294
- this.props.tab
1295
- )
1296
- }}
1297
- >
1298
- {e('reload')}
1299
- </Button>
1300
- </div>
1301
- )
1302
- })
1266
+ const { autoReconnectTerminal } = this.props.config
1267
+ const isActive = this.isActiveTerminal()
1268
+ const isFocused = window.focused
1269
+ if (autoReconnectTerminal) {
1270
+ if (isActive && isFocused) {
1271
+ this.socketCloseWarning = showSocketCloseWarning({
1272
+ tabId: this.props.tab.id,
1273
+ tab: this.props.tab,
1274
+ autoReconnect: true,
1275
+ delTab: this.props.delTab,
1276
+ reloadTab: this.props.reloadTab
1277
+ })
1278
+ } else {
1279
+ this.scheduleAutoReconnect(3000)
1280
+ }
1281
+ } else {
1282
+ if (!isActive || !isFocused) {
1283
+ return false
1284
+ }
1285
+ this.socketCloseWarning = showSocketCloseWarning({
1286
+ tabId: this.props.tab.id,
1287
+ tab: this.props.tab,
1288
+ autoReconnect: false,
1289
+ delTab: this.props.delTab,
1290
+ reloadTab: this.props.reloadTab
1291
+ })
1292
+ }
1293
+ }
1294
+
1295
+ scheduleAutoReconnect = (delay = 3000) => {
1296
+ clearTimeout(this.timers.reconnectTimer)
1297
+ clearInterval(this.timers.reconnectCountdown)
1298
+ const seconds = Math.round(delay / 1000)
1299
+ this.setState({ reconnectCountdown: seconds })
1300
+ let remaining = seconds
1301
+ this.timers.reconnectCountdown = setInterval(() => {
1302
+ remaining -= 1
1303
+ if (remaining <= 0) {
1304
+ clearInterval(this.timers.reconnectCountdown)
1305
+ this.timers.reconnectCountdown = null
1306
+ }
1307
+ this.setState({ reconnectCountdown: remaining <= 0 ? null : remaining })
1308
+ }, 1000)
1309
+ this.timers.reconnectTimer = setTimeout(() => {
1310
+ clearInterval(this.timers.reconnectCountdown)
1311
+ this.timers.reconnectCountdown = null
1312
+ this.setState({ reconnectCountdown: null })
1313
+ if (this.onClose || !this.props.config.autoReconnectTerminal) {
1314
+ return
1315
+ }
1316
+ const reconnectCount = (this.props.tab.autoReConnect || 0) + 1
1317
+ this.props.reloadTab({ ...this.props.tab, autoReConnect: reconnectCount })
1318
+ }, delay)
1319
+ }
1320
+
1321
+ handleCancelAutoReconnect = () => {
1322
+ clearTimeout(this.timers.reconnectTimer)
1323
+ clearInterval(this.timers.reconnectCountdown)
1324
+ this.timers.reconnectTimer = null
1325
+ this.timers.reconnectCountdown = null
1326
+ this.setState({ reconnectCountdown: null })
1303
1327
  }
1304
1328
 
1305
1329
  batchInput = (cmd) => {
@@ -1401,6 +1425,7 @@ class Term extends Component {
1401
1425
  totalLines: this.state.totalLines,
1402
1426
  height
1403
1427
  }
1428
+ const spin = loading ? <Spin className='loading-wrapper' spinning={loading} /> : null
1404
1429
  return (
1405
1430
  <Dropdown {...dropdownProps}>
1406
1431
  <div
@@ -1417,7 +1442,11 @@ class Term extends Component {
1417
1442
  <RemoteFloatControl
1418
1443
  isFullScreen={fullscreen}
1419
1444
  />
1420
- <Spin className='loading-wrapper' spinning={loading} />
1445
+ <ReconnectOverlay
1446
+ countdown={this.state.reconnectCountdown}
1447
+ onCancel={this.handleCancelAutoReconnect}
1448
+ />
1449
+ {spin}
1421
1450
  </div>
1422
1451
  </Dropdown>
1423
1452
  )
@@ -141,3 +141,15 @@
141
141
  .paste-text
142
142
  max-height 200px
143
143
  overflow-y scroll
144
+
145
+ .terminal-reconnect-overlay
146
+ position absolute
147
+ left 0
148
+ top 0
149
+ width 100%
150
+ height 100%
151
+ display flex
152
+ align-items center
153
+ justify-content center
154
+ z-index 110
155
+
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  // import { transferTypeMap } from '../../common/constants.js'
7
+ import { safeGetItem, safeSetItem } from '../../common/safe-local-storage.js'
7
8
  import { getLocalFileInfo } from '../sftp/file-read.js'
8
9
 
9
10
  /**
@@ -218,7 +219,7 @@ export class TransferClientBase {
218
219
  */
219
220
  openSaveFolderSelect = async () => {
220
221
  // Try to use last saved path
221
- const lastPath = this.storageKey ? window.localStorage.getItem(this.storageKey) : null
222
+ const lastPath = this.storageKey ? safeGetItem(this.storageKey) : null
222
223
 
223
224
  const savePaths = await window.api.openDialog({
224
225
  title: 'Choose a folder to save file(s)',
@@ -238,9 +239,8 @@ export class TransferClientBase {
238
239
  return null
239
240
  }
240
241
 
241
- // Save for next time
242
242
  if (this.storageKey) {
243
- window.localStorage.setItem(this.storageKey, savePaths[0])
243
+ safeSetItem(this.storageKey, savePaths[0])
244
244
  }
245
245
  return savePaths[0]
246
246
  }
@@ -4,19 +4,20 @@
4
4
 
5
5
  import { useState } from 'react'
6
6
  import { Button, Input, Space } from 'antd'
7
+ import { safeGetItem, safeSetItem } from '../../common/safe-local-storage.js'
7
8
 
8
9
  const LS_KEY = 'customEditorCommand'
9
10
  const e = window.translate
10
11
 
11
12
  export default function EditWithCustomEditor ({ loading, editWithCustom }) {
12
13
  const [editorCommand, setEditorCommand] = useState(
13
- () => window.localStorage.getItem(LS_KEY) || ''
14
+ () => safeGetItem(LS_KEY) || ''
14
15
  )
15
16
 
16
17
  function handleChange (ev) {
17
18
  const val = ev.target.value
18
19
  setEditorCommand(val)
19
- window.localStorage.setItem(LS_KEY, val)
20
+ safeSetItem(LS_KEY, val)
20
21
  }
21
22
 
22
23
  function handleClick () {
@@ -54,7 +54,7 @@ export default () => {
54
54
  lastDataUpdateTime: 0,
55
55
  tabs: [],
56
56
  activeTabId: '',
57
- history: ls.getItemJSON('history', []),
57
+ history: ls.safeGetItemJSON('history', []),
58
58
  sshConfigs: [],
59
59
  bookmarks: [],
60
60
  bookmarksMap: new Map(),
@@ -77,7 +77,7 @@ export default () => {
77
77
  // terminalCommandHistory: Map<cmdString, {count: Number, lastUseTime: DateString}>
78
78
  // Load from localStorage and migrate from old format (Set of strings) if needed
79
79
  terminalCommandHistory: (() => {
80
- const savedData = ls.getItemJSON(cmdHistoryKey, [])
80
+ const savedData = ls.safeGetItemJSON(cmdHistoryKey, [])
81
81
  const map = new Map()
82
82
  if (Array.isArray(savedData)) {
83
83
  // Check if old format (array of strings) or new format (array of objects)
@@ -111,7 +111,7 @@ export default () => {
111
111
 
112
112
  // batch input selected tab ids
113
113
  _batchInputSelectedTabIds: new Set(),
114
- aiChatHistory: ls.getItemJSON(aiChatHistoryKey, []),
114
+ aiChatHistory: ls.safeGetItemJSON(aiChatHistoryKey, []),
115
115
 
116
116
  // sftp
117
117
  fileOperation: fileOperationsMap.cp, // cp or mv
@@ -487,7 +487,6 @@ export default (Store) => {
487
487
  'hotkey',
488
488
  'sshReadyTimeout',
489
489
  'scrollback',
490
- 'enableSixel',
491
490
  'fontSize',
492
491
  'execWindows',
493
492
  'execMac',
@@ -505,7 +505,8 @@ export default Store => {
505
505
  'sftpCreated',
506
506
  'sshSftpSplitView',
507
507
  'sshTunnelResults',
508
- 'displayRaw'
508
+ 'displayRaw',
509
+ 'autoReConnect'
509
510
  ]
510
511
  const { history } = store
511
512
  const index = history.findIndex(d => {
@@ -135,12 +135,12 @@ export default store => {
135
135
  }).start()
136
136
 
137
137
  autoRun(() => {
138
- ls.setItemJSON('history', store.history)
138
+ ls.safeSetItemJSON('history', store.history)
139
139
  return store.history
140
140
  }).start()
141
141
 
142
142
  autoRun(() => {
143
- ls.setItemJSON(aiChatHistoryKey, store.aiChatHistory)
143
+ ls.safeSetItemJSON(aiChatHistoryKey, store.aiChatHistory)
144
144
  return store.aiChatHistory
145
145
  }).start()
146
146
 
@@ -152,7 +152,7 @@ export default store => {
152
152
  count: info.count,
153
153
  lastUseTime: info.lastUseTime
154
154
  }))
155
- ls.setItemJSON(cmdHistoryKey, data)
155
+ ls.safeSetItemJSON(cmdHistoryKey, data)
156
156
  return store.terminalCommandHistory
157
157
  }).start()
158
158
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "2.17.16",
3
+ "version": "3.0.18",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",