@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.
- package/client/common/cache.js +4 -2
- package/client/common/db.js +3 -5
- package/client/common/default-setting.js +1 -1
- package/client/common/pass-enc.js +0 -21
- package/client/common/safe-local-storage.js +106 -1
- package/client/components/ai/ai-chat-history-item.jsx +132 -8
- package/client/components/ai/ai-chat.jsx +10 -159
- package/client/components/ai/ai.styl +6 -1
- package/client/components/bookmark-form/common/render-auth-ssh.jsx +1 -117
- package/client/components/bookmark-form/config/common-fields.js +8 -0
- package/client/components/bookmark-form/config/ftp.js +1 -0
- package/client/components/bookmark-form/config/local.js +10 -51
- package/client/components/session/sessions.jsx +1 -0
- package/client/components/setting-panel/setting-terminal.jsx +2 -2
- package/client/components/sftp/address-bookmark-item.jsx +1 -1
- package/client/components/sftp/address-bookmark.jsx +11 -1
- package/client/components/sftp/file-item.jsx +1 -1
- package/client/components/sftp/sftp-entry.jsx +35 -12
- package/client/components/sidebar/history.jsx +1 -1
- package/client/components/tabs/app-drag.jsx +13 -12
- package/client/components/tabs/tabs.styl +1 -1
- package/client/components/terminal/reconnect-overlay.jsx +27 -0
- package/client/components/terminal/socket-close-warning.jsx +94 -0
- package/client/components/terminal/terminal.jsx +87 -58
- package/client/components/terminal/terminal.styl +12 -0
- package/client/components/terminal/transfer-client-base.js +3 -3
- package/client/components/text-editor/edit-with-custom-editor.jsx +3 -2
- package/client/store/init-state.js +3 -3
- package/client/store/sync.js +0 -1
- package/client/store/tab.js +2 -1
- package/client/store/watch.js +3 -3
- 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
|
-
|
|
1151
|
-
|
|
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
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
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
|
-
<
|
|
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 ?
|
|
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
|
-
|
|
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
|
-
() =>
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
114
|
+
aiChatHistory: ls.safeGetItemJSON(aiChatHistoryKey, []),
|
|
115
115
|
|
|
116
116
|
// sftp
|
|
117
117
|
fileOperation: fileOperationsMap.cp, // cp or mv
|
package/client/store/sync.js
CHANGED
package/client/store/tab.js
CHANGED
package/client/store/watch.js
CHANGED
|
@@ -135,12 +135,12 @@ export default store => {
|
|
|
135
135
|
}).start()
|
|
136
136
|
|
|
137
137
|
autoRun(() => {
|
|
138
|
-
ls.
|
|
138
|
+
ls.safeSetItemJSON('history', store.history)
|
|
139
139
|
return store.history
|
|
140
140
|
}).start()
|
|
141
141
|
|
|
142
142
|
autoRun(() => {
|
|
143
|
-
ls.
|
|
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.
|
|
155
|
+
ls.safeSetItemJSON(cmdHistoryKey, data)
|
|
156
156
|
return store.terminalCommandHistory
|
|
157
157
|
}).start()
|
|
158
158
|
|