@electerm/electerm-react 5.3.0 → 5.3.15
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/constants.js +0 -1
- package/client/components/layout/layout.jsx +0 -2
- package/client/components/session/session-control.jsx +1 -23
- package/client/components/session/session.jsx +1 -31
- package/client/components/session/sessions.jsx +0 -1
- package/client/components/shortcuts/shortcut-handler.js +0 -1
- package/client/components/sys-menu/icons-map.jsx +6 -2
- package/client/components/tabs/index.jsx +16 -3
- package/client/components/tabs/tab.jsx +25 -4
- package/client/components/tabs/tabs.styl +4 -0
- package/client/store/common.js +0 -5
- package/client/store/init-state.js +1 -4
- package/client/store/mcp-handler.js +3 -1
- package/client/store/tab.js +15 -1
- package/package.json +1 -1
|
@@ -263,7 +263,6 @@ export const resolutionsLsKey = 'custom-resolution-key'
|
|
|
263
263
|
export const checkedKeysLsKey = 'checked-keys'
|
|
264
264
|
export const quickCommandLabelsLsKey = 'quick-command-label'
|
|
265
265
|
export const localAddrBookmarkLsKey = 'local-addr-bookmark-keys'
|
|
266
|
-
export const dismissDelKeyTipLsKey = 'dismiss-del-key-tip'
|
|
267
266
|
export const treeSortLsKey = 'tree-sort'
|
|
268
267
|
export const sshTunnelHelpLink = 'https://github.com/electerm/electerm/wiki/How-to-use-ssh-tunnel'
|
|
269
268
|
export const proxyHelpLink = 'https://github.com/electerm/electerm/wiki/proxy-format'
|
|
@@ -119,7 +119,6 @@ export default auto(function Layout (props) {
|
|
|
119
119
|
'isMaximized',
|
|
120
120
|
'config',
|
|
121
121
|
'resolutions',
|
|
122
|
-
'hideDelKeyTip',
|
|
123
122
|
'fileOperation',
|
|
124
123
|
'pinnedQuickCommandBar',
|
|
125
124
|
'tabsHeight',
|
|
@@ -181,7 +180,6 @@ export default auto(function Layout (props) {
|
|
|
181
180
|
'activeTabId3',
|
|
182
181
|
'batch',
|
|
183
182
|
'resolutions',
|
|
184
|
-
'hideDelKeyTip',
|
|
185
183
|
'fileOperation',
|
|
186
184
|
'file',
|
|
187
185
|
'pinnedQuickCommandBar',
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
SearchOutlined,
|
|
7
7
|
FullscreenOutlined,
|
|
8
8
|
PaperClipOutlined,
|
|
9
|
-
CloseOutlined,
|
|
10
9
|
ApartmentOutlined,
|
|
11
10
|
MoreOutlined,
|
|
12
11
|
ColumnWidthOutlined,
|
|
@@ -37,8 +36,6 @@ export default function SessionControl (props) {
|
|
|
37
36
|
keepaliveEnabled,
|
|
38
37
|
broadcastInput,
|
|
39
38
|
wrapDisabled,
|
|
40
|
-
delKeyPressed,
|
|
41
|
-
hideDelKeyTip,
|
|
42
39
|
onChangePane,
|
|
43
40
|
toggleCheckSftpPathFollowSsh,
|
|
44
41
|
onSshSftpSplitView,
|
|
@@ -47,7 +44,6 @@ export default function SessionControl (props) {
|
|
|
47
44
|
toggleWrap,
|
|
48
45
|
onFullscreen,
|
|
49
46
|
onOpenSearch,
|
|
50
|
-
onDismissDelKeyTip,
|
|
51
47
|
onExitGracefully
|
|
52
48
|
} = props
|
|
53
49
|
|
|
@@ -108,26 +104,11 @@ export default function SessionControl (props) {
|
|
|
108
104
|
)
|
|
109
105
|
}
|
|
110
106
|
|
|
111
|
-
function renderDelTip (isS) {
|
|
112
|
-
if (!isS || hideDelKeyTip || !delKeyPressed) {
|
|
113
|
-
return null
|
|
114
|
-
}
|
|
115
|
-
return (
|
|
116
|
-
<div className='type-tab'>
|
|
117
|
-
<span className='mg1r'>Try <b>Shift + Backspace</b>?</span>
|
|
118
|
-
<CloseOutlined
|
|
119
|
-
onClick={onDismissDelKeyTip}
|
|
120
|
-
className='pointer'
|
|
121
|
-
/>
|
|
122
|
-
</div>
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
107
|
function renderSftpPathFollowControl () {
|
|
127
108
|
if (isDisabled) {
|
|
128
109
|
return null
|
|
129
110
|
}
|
|
130
|
-
const {
|
|
111
|
+
const { enableSsh } = tab
|
|
131
112
|
const checkTxt = e('sftpPathFollowSsh')
|
|
132
113
|
const checkProps = {
|
|
133
114
|
onClick: toggleCheckSftpPathFollowSsh,
|
|
@@ -138,8 +119,6 @@ export default function SessionControl (props) {
|
|
|
138
119
|
}
|
|
139
120
|
)
|
|
140
121
|
}
|
|
141
|
-
const isS = pane === paneMap.terminal ||
|
|
142
|
-
sshSftpSplitView
|
|
143
122
|
return (
|
|
144
123
|
<>
|
|
145
124
|
{
|
|
@@ -153,7 +132,6 @@ export default function SessionControl (props) {
|
|
|
153
132
|
)
|
|
154
133
|
: null
|
|
155
134
|
}
|
|
156
|
-
{renderDelTip(isS)}
|
|
157
135
|
</>
|
|
158
136
|
)
|
|
159
137
|
}
|
|
@@ -46,7 +46,6 @@ export default class SessionWrapper extends Component {
|
|
|
46
46
|
key: Math.random(),
|
|
47
47
|
splitSize: [50, 50],
|
|
48
48
|
sessionOptions: null,
|
|
49
|
-
delKeyPressed: false,
|
|
50
49
|
broadcastInput: false,
|
|
51
50
|
keepaliveEnabled: false,
|
|
52
51
|
wrapDisabled: false
|
|
@@ -59,10 +58,6 @@ export default class SessionWrapper extends Component {
|
|
|
59
58
|
minWithForSplit = 640
|
|
60
59
|
minHeightForSplit = 400
|
|
61
60
|
|
|
62
|
-
componentWillUnmount () {
|
|
63
|
-
clearTimeout(this.backspaceKeyPressedTimer)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
61
|
getDom = () => {
|
|
67
62
|
return this.domRef.current
|
|
68
63
|
}
|
|
@@ -178,27 +173,6 @@ export default class SessionWrapper extends Component {
|
|
|
178
173
|
e && e.dataTransfer && e.dataTransfer.clearData()
|
|
179
174
|
}
|
|
180
175
|
|
|
181
|
-
onDelKeyPressed = () => {
|
|
182
|
-
this.setState({
|
|
183
|
-
delKeyPressed: true
|
|
184
|
-
})
|
|
185
|
-
this.backspaceKeyPressedTimer = setTimeout(() => {
|
|
186
|
-
this.setState({
|
|
187
|
-
delKeyPressed: false
|
|
188
|
-
})
|
|
189
|
-
}, 5000)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
handleChangeDelMode = (backspaceMode) => {
|
|
193
|
-
this.setState({
|
|
194
|
-
backspaceMode
|
|
195
|
-
})
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
handleDismissDelKeyTip = () => {
|
|
199
|
-
window.store.dismissDelKeyTip()
|
|
200
|
-
}
|
|
201
|
-
|
|
202
176
|
setCwd = (cwd) => {
|
|
203
177
|
this.setState({
|
|
204
178
|
cwd
|
|
@@ -351,8 +325,7 @@ export default class SessionWrapper extends Component {
|
|
|
351
325
|
this,
|
|
352
326
|
[
|
|
353
327
|
'onChangePane',
|
|
354
|
-
'setCwd'
|
|
355
|
-
'onDelKeyPressed'
|
|
328
|
+
'setCwd'
|
|
356
329
|
]),
|
|
357
330
|
...this.computePosition(),
|
|
358
331
|
width,
|
|
@@ -633,8 +606,6 @@ export default class SessionWrapper extends Component {
|
|
|
633
606
|
keepaliveEnabled={this.state.keepaliveEnabled}
|
|
634
607
|
broadcastInput={this.state.broadcastInput}
|
|
635
608
|
wrapDisabled={this.state.wrapDisabled}
|
|
636
|
-
delKeyPressed={this.state.delKeyPressed}
|
|
637
|
-
hideDelKeyTip={this.props.hideDelKeyTip}
|
|
638
609
|
onChangePane={(pane) => this.onChangePane(pane)}
|
|
639
610
|
toggleCheckSftpPathFollowSsh={this.toggleCheckSftpPathFollowSsh}
|
|
640
611
|
onSshSftpSplitView={this.handleSshSftpSplitView}
|
|
@@ -643,7 +614,6 @@ export default class SessionWrapper extends Component {
|
|
|
643
614
|
toggleWrap={this.toggleWrap}
|
|
644
615
|
onFullscreen={this.handleFullscreen}
|
|
645
616
|
onOpenSearch={this.handleOpenSearch}
|
|
646
|
-
onDismissDelKeyTip={this.handleDismissDelKeyTip}
|
|
647
617
|
onExitGracefully={this.handleExitGracefully}
|
|
648
618
|
/>
|
|
649
619
|
{this.renderViews()}
|
|
@@ -187,7 +187,6 @@ export function shortcutExtend (Cls) {
|
|
|
187
187
|
!altKey &&
|
|
188
188
|
!ctrlKey
|
|
189
189
|
) {
|
|
190
|
-
this.props.onDelKeyPressed()
|
|
191
190
|
const delKey = this.props.config.backspaceMode === '^?' ? 8 : 127
|
|
192
191
|
const altDelDelKey = delKey === 8 ? 127 : 8
|
|
193
192
|
const char = String.fromCharCode(shiftKey ? delKey : altDelDelKey)
|
|
@@ -29,7 +29,9 @@ import {
|
|
|
29
29
|
PlayCircleFilled,
|
|
30
30
|
StopOutlined,
|
|
31
31
|
DownloadOutlined,
|
|
32
|
-
SwapOutlined
|
|
32
|
+
SwapOutlined,
|
|
33
|
+
PushpinOutlined,
|
|
34
|
+
PushpinFilled
|
|
33
35
|
} from '@ant-design/icons'
|
|
34
36
|
import IconHolder from './icon-holder'
|
|
35
37
|
|
|
@@ -62,5 +64,7 @@ export default {
|
|
|
62
64
|
PlayCircleFilled,
|
|
63
65
|
StopOutlined,
|
|
64
66
|
DownloadOutlined,
|
|
65
|
-
SwapOutlined
|
|
67
|
+
SwapOutlined,
|
|
68
|
+
PushpinOutlined,
|
|
69
|
+
PushpinFilled
|
|
66
70
|
}
|
|
@@ -122,11 +122,23 @@ export default class Tabs extends Component {
|
|
|
122
122
|
)
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
// desktop tabs bar renders pinned tabs first: stable partition that keeps
|
|
126
|
+
// the original relative order inside the pinned and unpinned groups.
|
|
127
|
+
// store.tabs order itself is untouched by pinning.
|
|
128
|
+
getSortedTabs = () => {
|
|
129
|
+
const { tabs = [] } = this.props
|
|
130
|
+
return [
|
|
131
|
+
...tabs.filter(t => t.isPinned),
|
|
132
|
+
...tabs.filter(t => !t.isPinned)
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
|
|
125
136
|
adjustScroll = () => {
|
|
126
137
|
if (this.props.isMobile || !this.domRef.current) {
|
|
127
138
|
return
|
|
128
139
|
}
|
|
129
|
-
const {
|
|
140
|
+
const { currentBatchTabId, batch } = this.props
|
|
141
|
+
const tabs = this.getSortedTabs()
|
|
130
142
|
const index = tabs.findIndex(t => t.id === currentBatchTabId)
|
|
131
143
|
const tabsDomWith = Array.from(
|
|
132
144
|
document.querySelectorAll(`.v${batch + 1} .tab`)
|
|
@@ -209,7 +221,7 @@ export default class Tabs extends Component {
|
|
|
209
221
|
}
|
|
210
222
|
|
|
211
223
|
renderExtra () {
|
|
212
|
-
const items = this.
|
|
224
|
+
const items = this.getSortedTabs().map((t, i) => {
|
|
213
225
|
return {
|
|
214
226
|
key: i + '##' + t.id,
|
|
215
227
|
label: (
|
|
@@ -261,7 +273,8 @@ export default class Tabs extends Component {
|
|
|
261
273
|
}
|
|
262
274
|
|
|
263
275
|
renderContentInner () {
|
|
264
|
-
const {
|
|
276
|
+
const { width, config } = this.props
|
|
277
|
+
const tabs = this.getSortedTabs()
|
|
265
278
|
const len = tabs.length
|
|
266
279
|
const tabsWidthAll = tabMargin * len + 10 + this.tabsWidth()
|
|
267
280
|
const { overflow } = this.state
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
CloseOutlined,
|
|
10
10
|
Loading3QuartersOutlined,
|
|
11
11
|
BorderlessTableOutlined,
|
|
12
|
-
LockOutlined
|
|
12
|
+
LockOutlined,
|
|
13
|
+
PushpinFilled
|
|
13
14
|
} from '@ant-design/icons'
|
|
14
15
|
import {
|
|
15
16
|
Tooltip,
|
|
@@ -239,8 +240,15 @@ class Tab extends Component {
|
|
|
239
240
|
}
|
|
240
241
|
}
|
|
241
242
|
|
|
242
|
-
handleReloadTab = async () => {
|
|
243
|
-
|
|
243
|
+
handleReloadTab = async (e) => {
|
|
244
|
+
// stop propagation so the parent tab-title click doesn't re-activate
|
|
245
|
+
// this tab by its now-stale (removed) id after reloadTab replaces it
|
|
246
|
+
e && e.stopPropagation()
|
|
247
|
+
const { tab, batch } = this.props
|
|
248
|
+
const newTab = window.store.reloadTab(tab.id)
|
|
249
|
+
if (newTab) {
|
|
250
|
+
window.store.clickTab(newTab.id, batch)
|
|
251
|
+
}
|
|
244
252
|
}
|
|
245
253
|
|
|
246
254
|
handleReloadAll = () => {
|
|
@@ -309,8 +317,13 @@ class Tab extends Component {
|
|
|
309
317
|
window.store.closeTabsRight(this.props.tab.id)
|
|
310
318
|
}
|
|
311
319
|
|
|
320
|
+
togglePin = () => {
|
|
321
|
+
const { tab } = this.props
|
|
322
|
+
window.store.pinTab(tab.id, !tab.isPinned)
|
|
323
|
+
}
|
|
324
|
+
|
|
312
325
|
renderContext = () => {
|
|
313
|
-
const { tabs, tabIndex } = this.props
|
|
326
|
+
const { tabs, tabIndex, tab } = this.props
|
|
314
327
|
const len = tabs.length
|
|
315
328
|
const index = tabIndex
|
|
316
329
|
const noRight = index >= len - 1
|
|
@@ -359,6 +372,13 @@ class Tab extends Component {
|
|
|
359
372
|
icon: <iconsMap.EditOutlined />,
|
|
360
373
|
label: e('rename')
|
|
361
374
|
},
|
|
375
|
+
!window.store.isMobile && {
|
|
376
|
+
key: 'togglePin',
|
|
377
|
+
icon: tab.isPinned
|
|
378
|
+
? <iconsMap.PushpinFilled />
|
|
379
|
+
: <iconsMap.PushpinOutlined />,
|
|
380
|
+
label: tab.isPinned ? e('unpin') : e('pin')
|
|
381
|
+
},
|
|
362
382
|
{
|
|
363
383
|
key: 'handleReloadTab',
|
|
364
384
|
icon: <iconsMap.ReloadOutlined />,
|
|
@@ -542,6 +562,7 @@ class Tab extends Component {
|
|
|
542
562
|
}
|
|
543
563
|
<span className='tab-title'>
|
|
544
564
|
<span className='iblock mg1r tab-count' style={styleTag}>{tabCount}</span>
|
|
565
|
+
{tab.isPinned && <PushpinFilled className='tab-pin' />}
|
|
545
566
|
<span className='mg1r'>{title}</span>
|
|
546
567
|
</span>
|
|
547
568
|
</div>
|
package/client/store/common.js
CHANGED
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
leftSideBarOpenKey,
|
|
12
12
|
rightSidebarWidthKey,
|
|
13
13
|
addPanelWidthLsKey,
|
|
14
|
-
dismissDelKeyTipLsKey,
|
|
15
14
|
connectionMap,
|
|
16
15
|
lastAiChatSessionIdKey,
|
|
17
16
|
mobileBreakpoint,
|
|
@@ -148,10 +147,6 @@ export default Store => {
|
|
|
148
147
|
ls.setItem(rightSidebarWidthKey, v)
|
|
149
148
|
window.store._rightPanelWidth = v
|
|
150
149
|
}
|
|
151
|
-
Store.prototype.dismissDelKeyTip = function (v) {
|
|
152
|
-
ls.setItem(dismissDelKeyTipLsKey, 'y')
|
|
153
|
-
window.store.hideDelKeyTip = true
|
|
154
|
-
}
|
|
155
150
|
Store.prototype.beforeExit = function (evt) {
|
|
156
151
|
const { confirmBeforeExit } = window.store.config
|
|
157
152
|
if (
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
leftSidePanelWidthKey,
|
|
20
20
|
rightSidebarWidthKey,
|
|
21
21
|
addPanelWidthLsKey,
|
|
22
|
-
dismissDelKeyTipLsKey,
|
|
23
22
|
qmSortByFrequencyKey,
|
|
24
23
|
resolutionsLsKey,
|
|
25
24
|
syncServerDataKey,
|
|
@@ -30,7 +29,6 @@ import {
|
|
|
30
29
|
import * as ls from '../common/safe-local-storage'
|
|
31
30
|
import { exclude } from 'manate'
|
|
32
31
|
import initSettingItem from '../common/init-setting-item'
|
|
33
|
-
import { getRandomDefaultColor } from '../common/rand-hex-color'
|
|
34
32
|
|
|
35
33
|
const e = window.translate
|
|
36
34
|
|
|
@@ -40,7 +38,7 @@ function getDefaultBookmarkGroups (bookmarks) {
|
|
|
40
38
|
title: e(defaultBookmarkGroupId),
|
|
41
39
|
id: defaultBookmarkGroupId,
|
|
42
40
|
bookmarkIds: bookmarks.map(d => d.id),
|
|
43
|
-
color:
|
|
41
|
+
color: '#0088cc'
|
|
44
42
|
})
|
|
45
43
|
]
|
|
46
44
|
}
|
|
@@ -216,7 +214,6 @@ export default () => {
|
|
|
216
214
|
hasNodePty: window.pre.runSync('nodePtyCheck'),
|
|
217
215
|
isMobile: window.innerWidth <= mobileBreakpoint,
|
|
218
216
|
fullscreen: false,
|
|
219
|
-
hideDelKeyTip: ls.getItem(dismissDelKeyTipLsKey) === 'y',
|
|
220
217
|
tabsHeight: 36,
|
|
221
218
|
|
|
222
219
|
// widgets
|
|
@@ -810,7 +810,9 @@ export default Store => {
|
|
|
810
810
|
const { command, tabId, timeoutMs } = args
|
|
811
811
|
const marker = `__ET_EXEC_${uid()}__`
|
|
812
812
|
const startLine = `${marker}S`
|
|
813
|
-
|
|
813
|
+
// Run in a subshell so commands like `exit`/`exec` cannot kill the outer
|
|
814
|
+
// shell before the end sentinel is emitted; $? holds the subshell's code.
|
|
815
|
+
const wrapped = `echo "${startLine}"; ( ${command} ); echo "${marker}E$?"`
|
|
814
816
|
|
|
815
817
|
store.runQuickCommand(wrapped, false, tabId)
|
|
816
818
|
|
package/client/store/tab.js
CHANGED
|
@@ -127,6 +127,8 @@ export default Store => {
|
|
|
127
127
|
if (store[batchProp] === tabId) {
|
|
128
128
|
store[batchProp] = newTab.id
|
|
129
129
|
}
|
|
130
|
+
|
|
131
|
+
return newTab
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
Store.prototype.reloadAllTabs = function () {
|
|
@@ -168,6 +170,17 @@ export default Store => {
|
|
|
168
170
|
store[`activeTabId${sourceTab.batch}`] = duplicatedTab.id
|
|
169
171
|
}
|
|
170
172
|
|
|
173
|
+
// only toggles the flag: the tabs bar renders pinned tabs first,
|
|
174
|
+
// store.tabs order itself is never changed by pinning
|
|
175
|
+
Store.prototype.pinTab = function (tabId, isPinned = true) {
|
|
176
|
+
const { store } = window
|
|
177
|
+
const tab = store.tabs.find(t => t.id === tabId)
|
|
178
|
+
if (!tab || Boolean(tab.isPinned) === isPinned) {
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
tab.isPinned = isPinned
|
|
182
|
+
}
|
|
183
|
+
|
|
171
184
|
Store.prototype.closeOtherTabs = function (id) {
|
|
172
185
|
const { store } = window
|
|
173
186
|
const { tabs } = store
|
|
@@ -534,7 +547,8 @@ export default Store => {
|
|
|
534
547
|
'sshSftpSplitView',
|
|
535
548
|
'sshTunnelResults',
|
|
536
549
|
'displayRaw',
|
|
537
|
-
'autoReConnect'
|
|
550
|
+
'autoReConnect',
|
|
551
|
+
'isPinned'
|
|
538
552
|
]
|
|
539
553
|
const { history } = store
|
|
540
554
|
const index = history.filter(d => d.id && d.tab).findIndex(d => {
|