@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.
@@ -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 { pane, enableSsh, sshSftpSplitView } = tab
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()}
@@ -69,7 +69,6 @@ export default class Sessions extends Component {
69
69
  height,
70
70
  ...pick(this.props, [
71
71
  'resolutions',
72
- 'hideDelKeyTip',
73
72
  'fileOperation',
74
73
  'pinnedQuickCommandBar',
75
74
  'tabsHeight',
@@ -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 { tabs, currentBatchTabId, batch } = this.props
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.props.tabs.map((t, i) => {
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 { tabs = [], width, config } = this.props
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
- window.store.reloadTab(this.props.tab.id)
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>
@@ -225,6 +225,10 @@
225
225
  height 20px
226
226
  line-height 21px
227
227
 
228
+ .tab-pin
229
+ margin-right 3px
230
+ font-size 11px
231
+
228
232
  .ssh-tunnel-list-wrapper
229
233
  max-height 300px
230
234
  overflow-y auto
@@ -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: getRandomDefaultColor()
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
- const wrapped = `echo "${startLine}"; ${command}; echo "${marker}E$?"`
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
 
@@ -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 => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "5.3.0",
3
+ "version": "5.3.15",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",