@electerm/electerm-react 1.80.6 → 1.90.6

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 (46) hide show
  1. package/client/common/constants.js +3 -1
  2. package/client/common/default-setting.js +3 -2
  3. package/client/common/fetch-from-server.js +1 -1
  4. package/client/common/sftp.js +13 -9
  5. package/client/common/transfer.js +4 -4
  6. package/client/common/ws.js +1 -2
  7. package/client/components/batch-op/batch-op.jsx +3 -4
  8. package/client/components/bookmark-form/ftp-form-ui.jsx +168 -0
  9. package/client/components/bookmark-form/ftp-form.jsx +16 -0
  10. package/client/components/bookmark-form/index.jsx +8 -3
  11. package/client/components/bookmark-form/render-ssh-tunnel.jsx +2 -0
  12. package/client/components/bookmark-form/x11.jsx +6 -15
  13. package/client/components/file-transfer/conflict-resolve.jsx +11 -1
  14. package/client/components/file-transfer/transfer.jsx +26 -24
  15. package/client/components/footer/tab-select.jsx +1 -1
  16. package/client/components/main/upgrade.jsx +3 -0
  17. package/client/components/rdp/rdp-session.jsx +2 -3
  18. package/client/components/session/session.jsx +21 -22
  19. package/client/components/setting-panel/setting-terminal.jsx +5 -0
  20. package/client/components/sftp/address-bar.jsx +6 -1
  21. package/client/components/sftp/file-icon.jsx +1 -1
  22. package/client/components/sftp/file-info-modal.jsx +0 -2
  23. package/client/components/sftp/file-item.jsx +62 -26
  24. package/client/components/sftp/file-table-header.jsx +1 -1
  25. package/client/components/sftp/owner-list.js +4 -4
  26. package/client/components/sftp/sftp-entry.jsx +62 -21
  27. package/client/components/sftp/transfer-common.js +1 -2
  28. package/client/components/sidebar/bookmark.jsx +27 -7
  29. package/client/components/sidebar/info-modal.jsx +1 -1
  30. package/client/components/sidebar/sidebar.styl +2 -0
  31. package/client/components/sidebar/transfer-list-control.jsx +5 -5
  32. package/client/components/terminal/term-search.jsx +5 -2
  33. package/client/components/terminal/terminal-apis.js +4 -8
  34. package/client/components/terminal/terminal.jsx +12 -14
  35. package/client/components/terminal-info/base.jsx +4 -8
  36. package/client/components/terminal-info/run-cmd.jsx +2 -3
  37. package/client/components/terminal-info/terminal-info.jsx +2 -3
  38. package/client/components/theme/theme-list-item.jsx +22 -42
  39. package/client/components/tree-list/bookmark-toolbar.jsx +58 -46
  40. package/client/components/vnc/vnc-session.jsx +2 -3
  41. package/client/entry/electerm.jsx +1 -1
  42. package/client/entry/worker.js +1 -2
  43. package/client/store/tab.js +7 -5
  44. package/client/store/ui-theme.js +1 -1
  45. package/client/store/watch.js +3 -2
  46. package/package.json +1 -1
@@ -34,6 +34,9 @@ export default class Upgrade extends PureComponent {
34
34
  downloadTimer = null
35
35
 
36
36
  componentDidMount () {
37
+ if (window.et.isWebApp) {
38
+ return
39
+ }
37
40
  setTimeout(() => {
38
41
  getLatestReleaseVersion(1)
39
42
  }, 5000)
@@ -67,7 +67,7 @@ export default class RdpSession extends PureComponent {
67
67
  tokenElecterm,
68
68
  server = ''
69
69
  } = config
70
- const { sessionId, id } = this.props
70
+ const { id } = this.props
71
71
  const tab = window.store.applyProfile(deepCopy(this.props.tab || {}))
72
72
  const {
73
73
  type,
@@ -75,7 +75,6 @@ export default class RdpSession extends PureComponent {
75
75
  } = tab
76
76
  const opts = clone({
77
77
  term: terminalType || config.terminalType,
78
- sessionId,
79
78
  tabId: id,
80
79
  srcTabId: tab.id,
81
80
  termType: type,
@@ -101,7 +100,7 @@ export default class RdpSession extends PureComponent {
101
100
  : `${host}:${port}`
102
101
  const pre = server.startsWith('https') ? 'wss' : 'ws'
103
102
  const { width, height } = this.state
104
- const wsUrl = `${pre}://${hs}/rdp/${pid}?sessionId=${sessionId}&token=${tokenElecterm}&width=${width}&height=${height}`
103
+ const wsUrl = `${pre}://${hs}/rdp/${pid}?&token=${tokenElecterm}&width=${width}&height=${height}`
105
104
  const socket = new WebSocket(wsUrl)
106
105
  socket.onclose = this.oncloseSocket
107
106
  socket.onerror = this.onerrorSocket
@@ -21,7 +21,6 @@ import {
21
21
  Splitter
22
22
  } from 'antd'
23
23
  import { pick } from 'lodash-es'
24
- import generate from '../../common/uid'
25
24
  import copy from 'json-deep-copy'
26
25
  import classnames from 'classnames'
27
26
  import {
@@ -30,7 +29,8 @@ import {
30
29
  terminalRdpType,
31
30
  terminalVncType,
32
31
  terminalWebType,
33
- terminalTelnetType
32
+ terminalTelnetType,
33
+ terminalFtpType
34
34
  } from '../../common/constants'
35
35
  import { SplitViewIcon } from '../icons/split-view'
36
36
  import { refs } from '../common/ref'
@@ -50,7 +50,6 @@ export default class SessionWrapper extends Component {
50
50
  key: Math.random(),
51
51
  splitSize: [50, 50],
52
52
  sessionOptions: null,
53
- sessionId: generate(),
54
53
  delKeyPressed: false,
55
54
  broadcastInput: false
56
55
  }
@@ -60,11 +59,6 @@ export default class SessionWrapper extends Component {
60
59
  minWithForSplit = 640
61
60
  minHeightForSplit = 400
62
61
 
63
- componentDidMount () {
64
- this.updateTab()
65
- // this.initEvent()
66
- }
67
-
68
62
  componentWillUnmount () {
69
63
  clearTimeout(this.backspaceKeyPressedTimer)
70
64
  }
@@ -244,14 +238,6 @@ export default class SessionWrapper extends Component {
244
238
  this.editTab(update)
245
239
  }
246
240
 
247
- updateTab = () => {
248
- this.editTab(
249
- {
250
- sessionId: this.state.sessionId
251
- }
252
- )
253
- }
254
-
255
241
  computePosition = (index) => {
256
242
  return {
257
243
  left: 0,
@@ -266,7 +252,6 @@ export default class SessionWrapper extends Component {
266
252
  renderTerminals = () => {
267
253
  const {
268
254
  sessionOptions,
269
- sessionId,
270
255
  sftpPathFollowSsh,
271
256
  broadcastInput
272
257
  } = this.state
@@ -292,7 +277,6 @@ export default class SessionWrapper extends Component {
292
277
  if (type === terminalRdpType || type === terminalVncType) {
293
278
  const rdpProps = {
294
279
  tab: this.props.tab,
295
- sessionId,
296
280
  ...pick(this.props, [
297
281
  'resolutions',
298
282
  'height',
@@ -319,6 +303,7 @@ export default class SessionWrapper extends Component {
319
303
  />
320
304
  )
321
305
  }
306
+
322
307
  return (
323
308
  <RdpSession
324
309
  {...rdpProps}
@@ -326,6 +311,22 @@ export default class SessionWrapper extends Component {
326
311
  )
327
312
  }
328
313
 
314
+ if (type === terminalFtpType) {
315
+ const ftpProps = {
316
+ ...this.props,
317
+ ...pick(this, [
318
+ 'onChangePane',
319
+ 'setCwd'
320
+ ]),
321
+ isFtp: true
322
+ }
323
+ return (
324
+ <Sftp
325
+ {...ftpProps}
326
+ />
327
+ )
328
+ }
329
+
329
330
  const cls = pane === paneMap.terminal ||
330
331
  (sshSftpSplitView && this.canSplitView())
331
332
  ? 'terms-box'
@@ -363,7 +364,6 @@ export default class SessionWrapper extends Component {
363
364
  >
364
365
  <Term
365
366
  logName={logName}
366
- sessionId={sessionId}
367
367
  sessionOptions={sessionOptions}
368
368
  {...pops}
369
369
  />
@@ -376,7 +376,8 @@ export default class SessionWrapper extends Component {
376
376
  return type === terminalRdpType ||
377
377
  type === terminalVncType ||
378
378
  type === terminalWebType ||
379
- type === terminalTelnetType
379
+ type === terminalTelnetType ||
380
+ type === terminalFtpType
380
381
  }
381
382
 
382
383
  calcSftpWidthHeight = () => {
@@ -424,7 +425,6 @@ export default class SessionWrapper extends Component {
424
425
  renderSftp = () => {
425
426
  const {
426
427
  sessionOptions,
427
- sessionId,
428
428
  enableSftp,
429
429
  sftpPathFollowSsh,
430
430
  cwd
@@ -452,7 +452,6 @@ export default class SessionWrapper extends Component {
452
452
  enableSftp,
453
453
  sessionOptions,
454
454
  height,
455
- sessionId,
456
455
  pane,
457
456
  ...this.calcSftpWidthHeight()
458
457
  }
@@ -494,6 +494,7 @@ export default class SettingTerminal extends Component {
494
494
  <Link to={regexHelpLink}>wiki</Link>
495
495
  </div>
496
496
  )
497
+ const startDirectoryLocalTxt = `${e('startDirectory')}:${e('local')}`
497
498
  return (
498
499
  <div className='form-wrap pd1y pd2x'>
499
500
  <div className='pd1y font16 bold'>
@@ -559,6 +560,10 @@ export default class SettingTerminal extends Component {
559
560
  </div>
560
561
  <div className='pd1b'>{e('terminalBackgroundImage')}</div>
561
562
  <TerminalBackgroundConfig {...bgProps} />
563
+ <div className='pd1b'>{startDirectoryLocalTxt}</div>
564
+ {
565
+ this.renderText('startDirectoryLocal', startDirectoryLocalTxt)
566
+ }
562
567
  <div className='pd1b'>{e('terminalWordSeparator')}</div>
563
568
  {
564
569
  this.renderText('terminalWordSeparator', e('terminalWordSeparator'))
@@ -4,7 +4,8 @@ import {
4
4
  EyeFilled,
5
5
  ReloadOutlined,
6
6
  ArrowRightOutlined,
7
- LoadingOutlined
7
+ LoadingOutlined,
8
+ HomeOutlined
8
9
  } from '@ant-design/icons'
9
10
  import {
10
11
  Input,
@@ -55,6 +56,10 @@ function renderAddonBefore (props, realPath) {
55
56
  className='mg1r'
56
57
  />
57
58
  </Tooltip>
59
+ <HomeOutlined
60
+ onClick={() => props.gotoHome(type)}
61
+ className='mg1r'
62
+ />
58
63
  <KeywordFilter {...keywordProps} />
59
64
  <AddrBookmark
60
65
  store={window.store}
@@ -14,7 +14,7 @@ export default function FileIcon ({ file, ...extra }) {
14
14
  return (
15
15
  <img
16
16
  src={extIconPath + name}
17
- height={12}
17
+ height={16}
18
18
  alt=''
19
19
  {...extra}
20
20
  />
@@ -28,7 +28,6 @@ export default class FileMode extends React.PureComponent {
28
28
  loading: false,
29
29
  tab: null,
30
30
  pid: '',
31
- sessionId: '',
32
31
  size: 0,
33
32
  file: {},
34
33
  editPermission: false
@@ -128,7 +127,6 @@ export default class FileMode extends React.PureComponent {
128
127
  const cmd = `du -sh '${folder}'`
129
128
  const r = await runCmd(
130
129
  this.state.pid,
131
- this.state.sessionId,
132
130
  cmd
133
131
  ).catch(window.store.onError)
134
132
  return r ? r.split(/\s+/)[0] : 0
@@ -6,7 +6,8 @@ import React from 'react'
6
6
  import ExtIcon from './file-icon'
7
7
  import {
8
8
  FolderOutlined,
9
- FileOutlined
9
+ FileOutlined,
10
+ ArrowRightOutlined
10
11
  } from '@ant-design/icons'
11
12
  import classnames from 'classnames'
12
13
  import copy from 'json-deep-copy'
@@ -397,7 +398,6 @@ export default class FileSection extends React.Component {
397
398
  tab: this.props.tab,
398
399
  visible: true,
399
400
  pid: this.props.pid,
400
- sessionId: this.props.sessionId,
401
401
  uidTree: this.props[`${type}UidTree`],
402
402
  gidTree: this.props[`${type}GidTree`]
403
403
  })
@@ -671,7 +671,7 @@ export default class FileSection extends React.Component {
671
671
  const {
672
672
  path, name
673
673
  } = this.state.file
674
- const rp = resolve(path, name)
674
+ const rp = path ? resolve(path, name) : this.props[`${this.props.type}Path`]
675
675
  this.props.tab.pane = paneMap.terminal
676
676
  refs.get('term-' + this.props.tab.id)?.cd(rp)
677
677
  }
@@ -883,27 +883,55 @@ export default class FileSection extends React.Component {
883
883
  return !isWin
884
884
  }
885
885
 
886
+ handleContextMenuCapture = (e) => {
887
+ this.contextMenuPosition = {
888
+ clientY: e.clientY
889
+ }
890
+ }
891
+
892
+ itemToMenuFormat = (r) => {
893
+ const { func, text, disabled, icon, subText, requireConfirm } = r
894
+ const IconCom = iconsMap[icon]
895
+ return {
896
+ key: func,
897
+ label: text,
898
+ disabled,
899
+ icon: <IconCom />,
900
+ extra: subText,
901
+ danger: requireConfirm
902
+ }
903
+ }
904
+
886
905
  renderContextMenu = () => {
887
- return this.renderContextItems()
888
- .map(r => {
889
- const {
890
- func,
891
- text,
892
- disabled,
893
- icon,
894
- subText,
895
- requireConfirm
896
- } = r
897
- const IconCom = iconsMap[icon]
898
- return {
899
- key: func,
900
- label: text,
901
- disabled,
902
- icon: <IconCom />,
903
- extra: subText,
904
- danger: requireConfirm
906
+ const items = this.renderContextItems()
907
+
908
+ // Check if we need to split the menu
909
+ if (this.contextMenuPosition) {
910
+ const windowHeight = window.innerHeight
911
+ const { clientY } = this.contextMenuPosition
912
+ const estimatedMenuHeight = items.length * 32 // Approximate height per menu item
913
+ const availableHeight = windowHeight - clientY
914
+
915
+ // If menu would extend beyond window, split into two parts
916
+ if (estimatedMenuHeight > availableHeight && items.length > 6) {
917
+ const firstHalf = items.slice(0, Math.ceil(items.length / 2))
918
+ const secondHalf = items.slice(Math.ceil(items.length / 2))
919
+
920
+ // Create "More..." submenu with second half of items
921
+ const moreSubmenu = {
922
+ key: 'more-submenu',
923
+ label: '…',
924
+ icon: <ArrowRightOutlined />,
925
+ children: secondHalf.map(this.itemToMenuFormat)
905
926
  }
906
- })
927
+
928
+ // Return first half + "More..." submenu
929
+ return [...firstHalf.map(this.itemToMenuFormat), moreSubmenu]
930
+ }
931
+ }
932
+
933
+ // Otherwise return normal menu
934
+ return items.map(this.itemToMenuFormat)
907
935
  }
908
936
 
909
937
  renderContextItems () {
@@ -953,11 +981,12 @@ export default class FileSection extends React.Component {
953
981
  })
954
982
  }
955
983
  if (
956
- isDirectory && isRealFile &&
984
+ isDirectory &&
957
985
  (
958
986
  (hasHost && enableSsh !== false && isRemote) ||
959
987
  (isLocal && !hasHost)
960
- )
988
+ ) &&
989
+ !this.props.isFtp
961
990
  ) {
962
991
  res.push({
963
992
  func: 'gotoFolderInTerminal',
@@ -1055,7 +1084,10 @@ export default class FileSection extends React.Component {
1055
1084
  icon: 'ReloadOutlined',
1056
1085
  text: e('refresh')
1057
1086
  })
1058
- if (this.showModeEdit(type, isRealFile)) {
1087
+ if (
1088
+ this.showModeEdit(type, isRealFile) &&
1089
+ !this.props.isFtp
1090
+ ) {
1059
1091
  res.push({
1060
1092
  func: 'editPermission',
1061
1093
  icon: 'LockOutlined',
@@ -1073,7 +1105,10 @@ export default class FileSection extends React.Component {
1073
1105
  }
1074
1106
 
1075
1107
  onContextMenu = ({ key }) => {
1076
- this[key]()
1108
+ // If it's not the submenu itself
1109
+ if (key !== 'more-submenu') {
1110
+ this[key]()
1111
+ }
1077
1112
  }
1078
1113
 
1079
1114
  renderEditing (file) {
@@ -1207,6 +1242,7 @@ export default class FileSection extends React.Component {
1207
1242
  <div
1208
1243
  ref={this.domRef}
1209
1244
  {...props}
1245
+ onContextMenu={this.handleContextMenuCapture}
1210
1246
  >
1211
1247
  <div className='file-bg' />
1212
1248
  <div className='file-props-div'>
@@ -49,7 +49,7 @@ export default class FileListTableHeader extends Component {
49
49
  }
50
50
  const text = e(id || '')
51
51
  const directionIcon = isSorting
52
- ? (sortDirection === 'asc' ? <DownOutlined /> : <UpOutlined />)
52
+ ? (sortDirection === 'asc' ? <UpOutlined /> : <DownOutlined />)
53
53
  : null
54
54
  const itemProps = {
55
55
  onClick: this.props.onClickName,
@@ -27,8 +27,8 @@ function parseNames (str) {
27
27
  const linuxListUser = 'cat /etc/passwd'
28
28
  const linuxListGroup = 'cat /etc/group'
29
29
 
30
- export async function remoteListUsers (pid, sessionId) {
31
- const users = await runCmd(pid, sessionId, linuxListUser)
30
+ export async function remoteListUsers (pid) {
31
+ const users = await runCmd(pid, linuxListUser)
32
32
  .catch(log.error)
33
33
  if (users) {
34
34
  return parseNames(users)
@@ -36,8 +36,8 @@ export async function remoteListUsers (pid, sessionId) {
36
36
  return {}
37
37
  }
38
38
 
39
- export async function remoteListGroups (pid, sessionId) {
40
- const groups = await runCmd(pid, sessionId, linuxListGroup)
39
+ export async function remoteListGroups (pid) {
40
+ const groups = await runCmd(pid, linuxListGroup)
41
41
  .catch(log.error)
42
42
  if (groups) {
43
43
  return parseNames(groups)
@@ -15,6 +15,7 @@ import {
15
15
  typeMap, maxSftpHistory, paneMap,
16
16
  fileTypeMap,
17
17
  terminalSerialType,
18
+ terminalFtpType,
18
19
  unexpectedPacketErrorDesc,
19
20
  sftpRetryInterval
20
21
  } from '../../common/constants'
@@ -48,10 +49,12 @@ export default class Sftp extends Component {
48
49
  }
49
50
 
50
51
  componentDidMount () {
51
- this.id = 'sftp-' + this.props.sessionId
52
- this.tid = 'sftp-' + this.props.tab.id
52
+ this.id = 'sftp-' + this.props.tab.id
53
53
  refs.add(this.id, this)
54
- refs.add(this.tid, this)
54
+ if (this.props.isFtp) {
55
+ this.type = 'ftp'
56
+ this.initData()
57
+ }
55
58
  }
56
59
 
57
60
  componentDidUpdate (prevProps, prevState) {
@@ -89,11 +92,12 @@ export default class Sftp extends Component {
89
92
 
90
93
  componentWillUnmount () {
91
94
  refs.remove(this.id)
92
- refs.remove(this.tid)
93
95
  this.sftp && this.sftp.destroy()
94
96
  this.sftp = null
95
97
  clearTimeout(this.timer4)
96
98
  this.timer4 = null
99
+ clearTimeout(this.timer5)
100
+ this.timer5 = null
97
101
  }
98
102
 
99
103
  directions = [
@@ -112,15 +116,15 @@ export default class Sftp extends Component {
112
116
  [`sortProp.${k}`]: window.store.sftpSortSetting[k].prop,
113
117
  [`sortDirection.${k}`]: window.store.sftpSortSetting[k].direction,
114
118
  [k]: [],
115
- [`${k}FileTree`]: {},
119
+ [`${k}FileTree`]: new Map(),
116
120
  [`${k}Loading`]: false,
117
121
  [`${k}InputFocus`]: false,
118
122
  [`${k}ShowHiddenFile`]: def,
119
123
  [`${k}Path`]: '',
120
124
  [`${k}PathTemp`]: '',
121
125
  [`${k}PathHistory`]: [],
122
- [`${k}GidTree`]: {},
123
- [`${k}UidTree`]: {},
126
+ [`${k}GidTree`]: new Map(),
127
+ [`${k}UidTree`]: new Map(),
124
128
  [`${k}Keyword`]: ''
125
129
  })
126
130
  return prev
@@ -170,9 +174,11 @@ export default class Sftp extends Component {
170
174
  }, isEqual)
171
175
 
172
176
  isActive () {
173
- return this.props.currentBatchTabId === this.props.tab.id &&
174
- (this.props.pane === paneMap.fileManager ||
175
- this.props.sshSftpSplitView)
177
+ const { currentBatchTabId, pane, sshSftpSplitView } = this.props
178
+ const { tab } = this.props
179
+ const isFtp = tab.type === terminalFtpType
180
+
181
+ return (currentBatchTabId === tab.id && (pane === paneMap.fileManager || sshSftpSplitView)) || isFtp
176
182
  }
177
183
 
178
184
  updateKeyword = (keyword, type) => {
@@ -191,6 +197,26 @@ export default class Sftp extends Component {
191
197
  }
192
198
  }
193
199
 
200
+ gotoHome = async (type) => {
201
+ const n = `${type}Path`
202
+ const nt = n + 'Temp'
203
+ let path
204
+
205
+ if (type === typeMap.remote) {
206
+ path = this.props.tab.startDirectoryRemote
207
+ if (!path && this.sftp) {
208
+ path = await this.getPwd(this.props.tab.username)
209
+ }
210
+ } else {
211
+ path = this.getLocalHome()
212
+ }
213
+
214
+ this.setState({
215
+ [n]: path,
216
+ [nt]: path
217
+ }, () => this[`${type}List`]())
218
+ }
219
+
194
220
  updateCwd = (cwd) => {
195
221
  if (!this.state.inited) {
196
222
  return
@@ -401,7 +427,8 @@ export default class Sftp extends Component {
401
427
  this[type + 'Dom'].onPaste()
402
428
  }
403
429
 
404
- initData = () => {
430
+ initData = (terminalId) => {
431
+ this.terminalId = terminalId
405
432
  if (this.shouldRenderRemote()) {
406
433
  this.initRemoteAll()
407
434
  }
@@ -502,12 +529,10 @@ export default class Sftp extends Component {
502
529
 
503
530
  remoteListOwner = async () => {
504
531
  const remoteUidTree = await owner.remoteListUsers(
505
- this.props.pid,
506
- this.props.sessionId
532
+ this.props.pid
507
533
  )
508
534
  const remoteGidTree = await owner.remoteListGroups(
509
- this.props.pid,
510
- this.props.sessionId
535
+ this.props.pid
511
536
  )
512
537
  this.setState({
513
538
  remoteGidTree,
@@ -549,7 +574,7 @@ export default class Sftp extends Component {
549
574
  remotePathReal,
550
575
  oldPath
551
576
  ) => {
552
- const { tab, sessionOptions, sessionId } = this.props
577
+ const { tab, sessionOptions } = this.props
553
578
  const { username, startDirectory } = tab
554
579
  let remotePath
555
580
  const noPathInit = remotePathReal || this.state.remotePath
@@ -567,7 +592,7 @@ export default class Sftp extends Component {
567
592
  let sftp = this.sftp
568
593
  try {
569
594
  if (!this.sftp) {
570
- sftp = await Client(sessionId)
595
+ sftp = await Client(this.terminalId, this.type)
571
596
  if (!sftp) {
572
597
  return
573
598
  }
@@ -580,7 +605,7 @@ export default class Sftp extends Component {
580
605
  const opts = deepCopy({
581
606
  ...tab,
582
607
  readyTimeout: config.sshReadyTimeout,
583
- sessionId,
608
+ terminalId: this.terminalId,
584
609
  keepaliveInterval: config.keepaliveInterval,
585
610
  proxy: getProxy(tab, config),
586
611
  ...sessionOptions
@@ -649,11 +674,21 @@ export default class Sftp extends Component {
649
674
  ]).slice(0, maxSftpHistory)
650
675
  }
651
676
  this.setState(update, () => {
652
- this.updateRemoteList(remote, remotePath, sftp)
677
+ if (this.type !== 'ftp') {
678
+ this.updateRemoteList(remote, remotePath, sftp)
679
+ }
653
680
  this.props.editTab(tab.id, {
654
681
  sftpCreated: true
655
682
  })
656
683
  })
684
+ this.timer5 = setTimeout(() => {
685
+ if (this.type !== 'ftp') {
686
+ this.updateRemoteList(remote, remotePath, sftp)
687
+ }
688
+ this.props.editTab(tab.id, {
689
+ sftpCreated: true
690
+ })
691
+ }, 1000)
657
692
  } catch (e) {
658
693
  const update = {
659
694
  remoteLoading: false,
@@ -716,6 +751,12 @@ export default class Sftp extends Component {
716
751
  this.setState(update)
717
752
  }
718
753
 
754
+ getLocalHome = () => {
755
+ return this.props.tab.startDirectoryLocal ||
756
+ this.props.config.startDirectoryLocal ||
757
+ window.pre.homeOrTmp
758
+ }
759
+
719
760
  localList = async (returnList = false, localPathReal, oldPath) => {
720
761
  if (!fs) return
721
762
  if (!returnList) {
@@ -730,8 +771,7 @@ export default class Sftp extends Component {
730
771
  const noPathInit = localPathReal || this.state.localPath
731
772
  const localPath = noPathInit ||
732
773
  this.getCwdLocal() ||
733
- this.props.tab.startDirectoryLocal ||
734
- window.pre.homeOrTmp
774
+ this.getLocalHome()
735
775
  const locals = await fs.readdirAsync(localPath)
736
776
  const local = []
737
777
  for (const name of locals) {
@@ -1015,6 +1055,7 @@ export default class Sftp extends Component {
1015
1055
  [
1016
1056
  'onChange',
1017
1057
  'onGoto',
1058
+ 'gotoHome',
1018
1059
  'onInputFocus',
1019
1060
  'onInputBlur',
1020
1061
  'toggleShowHiddenFile',
@@ -3,7 +3,6 @@ import createTitle from '../../common/create-title'
3
3
  export function createTransferProps (props) {
4
4
  return {
5
5
  title: createTitle(props.tab, false),
6
- tabId: props.tab.id,
7
- sessionId: props.sessionId
6
+ tabId: props.tab.id
8
7
  }
9
8
  }
@@ -1,15 +1,35 @@
1
- /**
2
- * bookmark select
3
- */
4
-
1
+ import { refsStatic } from '../common/ref'
2
+ import { useEffect, useRef } from 'react'
5
3
  import BookmarkSelect from './bookmark-select'
4
+ import { debounce } from 'lodash-es'
6
5
 
7
6
  export default function BookmarkPanel (props) {
8
7
  const { store } = window
8
+ const bookmarksPanelRef = useRef(null)
9
+ const SCROLL_REF_ID = 'bookmarks-scroll-position'
10
+
11
+ // On component mount, restore scroll position
12
+ useEffect(() => {
13
+ if (store.openedSideBar) {
14
+ const savedPosition = refsStatic.get(SCROLL_REF_ID)
15
+ if (savedPosition) {
16
+ setTimeout(() => {
17
+ bookmarksPanelRef.current.scrollTop = savedPosition
18
+ }, 100)
19
+ }
20
+ }
21
+ }, [store.openedSideBar])
22
+
23
+ // Save scroll position when scrolling
24
+ const handleScroll = debounce((e) => {
25
+ const top = e.target.scrollTop
26
+ if (top > 0) {
27
+ refsStatic.add(SCROLL_REF_ID, e.target.scrollTop)
28
+ }
29
+ }, 100)
30
+
9
31
  return (
10
- <div
11
- className='sidebar-panel-bookmarks'
12
- >
32
+ <div className='sidebar-panel-bookmarks' ref={bookmarksPanelRef} onScroll={handleScroll}>
13
33
  <div className='pd2l sidebar-inner'>
14
34
  <BookmarkSelect store={store} from='sidebar' />
15
35
  </div>
@@ -32,7 +32,7 @@ export default auto(function InfoModal (props) {
32
32
  }
33
33
 
34
34
  const renderCheckUpdate = () => {
35
- if (srcsSkipUpgradeCheck.includes(props.installSrc)) {
35
+ if (window.et.isWebApp || srcsSkipUpgradeCheck.includes(props.installSrc)) {
36
36
  return null
37
37
  }
38
38
  const {
@@ -40,6 +40,8 @@
40
40
  top 112px
41
41
  bottom 0
42
42
  overflow-y scroll
43
+ .item-list-wrap
44
+ overflow-y hidden
43
45
  .not-system-ui.is-mac
44
46
  .sidebar-bar
45
47
  margin-top 20px