@electerm/electerm-react 1.72.36 → 1.80.2

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 (50) hide show
  1. package/client/common/constants.js +1 -1
  2. package/client/common/sftp.js +3 -1
  3. package/client/components/ai/ai-chat.jsx +1 -1
  4. package/client/components/ai/ai-config.jsx +7 -1
  5. package/client/components/batch-op/batch-op.jsx +4 -5
  6. package/client/components/bg/css-overwrite.jsx +179 -0
  7. package/client/components/bg/shapes.js +501 -0
  8. package/client/components/bookmark-form/form-tabs.jsx +1 -0
  9. package/client/components/bookmark-form/local-form-ui.jsx +7 -1
  10. package/client/components/bookmark-form/render-bg.jsx +43 -0
  11. package/client/components/bookmark-form/serial-form-ui.jsx +7 -1
  12. package/client/components/bookmark-form/ssh-form-ui.jsx +14 -3
  13. package/client/components/bookmark-form/telnet-form-ui.jsx +7 -1
  14. package/client/components/bookmark-form/use-ui.jsx +68 -72
  15. package/client/components/common/ref.js +2 -0
  16. package/client/components/{sftp/confirm-modal-store.jsx → file-transfer/conflict-resolve.jsx} +86 -48
  17. package/client/components/file-transfer/transfer-queue.jsx +151 -0
  18. package/client/components/file-transfer/transfer.jsx +582 -0
  19. package/client/components/{sftp → file-transfer}/transports-action-store.jsx +35 -32
  20. package/client/components/{sftp → file-transfer}/transports-ui-store.jsx +2 -2
  21. package/client/components/main/main.jsx +25 -18
  22. package/client/components/main/wrapper.styl +16 -0
  23. package/client/components/profile/profile-list.jsx +1 -1
  24. package/client/components/quick-commands/qm.styl +4 -1
  25. package/client/components/quick-commands/quick-commands-list.jsx +16 -4
  26. package/client/components/session/session.styl +1 -1
  27. package/client/components/setting-panel/list.jsx +1 -1
  28. package/client/components/setting-panel/setting-terminal.jsx +2 -1
  29. package/client/components/setting-panel/terminal-bg-config.jsx +15 -2
  30. package/client/components/sftp/file-info-modal.jsx +3 -0
  31. package/client/components/sftp/file-item.jsx +25 -23
  32. package/client/components/sftp/file-read.js +1 -27
  33. package/client/components/sftp/list-table-ui.jsx +2 -2
  34. package/client/components/sidebar/transfer-history-modal.jsx +1 -1
  35. package/client/components/sidebar/transfer-list-control.jsx +1 -1
  36. package/client/components/sidebar/transport-ui.jsx +16 -9
  37. package/client/components/terminal/terminal.jsx +23 -1
  38. package/client/components/text-editor/simple-editor.jsx +164 -0
  39. package/client/components/text-editor/text-editor-form.jsx +6 -9
  40. package/client/css/includes/box.styl +2 -2
  41. package/client/store/tab.js +5 -1
  42. package/client/store/transfer-list.js +10 -51
  43. package/package.json +1 -1
  44. package/client/components/main/css-overwrite.jsx +0 -91
  45. package/client/components/sftp/transfer-conflict-store.jsx +0 -284
  46. package/client/components/sftp/transport-action-store.jsx +0 -422
  47. package/client/components/sftp/zip.js +0 -42
  48. /package/client/components/{main → bg}/custom-css.jsx +0 -0
  49. /package/client/components/{sftp → file-transfer}/transfer-speed-format.js +0 -0
  50. /package/client/components/{sftp → file-transfer}/transfer.styl +0 -0
@@ -7,15 +7,15 @@ import SettingModal from '../setting-panel/setting-modal'
7
7
  import TextEditor from '../text-editor/text-editor'
8
8
  import Sidebar from '../sidebar'
9
9
  import BatchOp from '../batch-op/batch-op-entry'
10
- import CssOverwrite from './css-overwrite'
10
+ import CssOverwrite from '../bg/css-overwrite'
11
11
  import UiTheme from './ui-theme'
12
- import CustomCss from './custom-css.jsx'
12
+ import CustomCss from '../bg/custom-css.jsx'
13
13
  import Resolutions from '../rdp/resolution-edit'
14
14
  import TerminalInteractive from '../terminal/terminal-interactive'
15
- import ConfirmModalStore from '../sftp/confirm-modal-store.jsx'
16
- import TransferConflictStore from '../sftp/transfer-conflict-store.jsx'
15
+ import ConfirmModalStore from '../file-transfer/conflict-resolve.jsx'
16
+ import TransferQueue from '../file-transfer/transfer-queue'
17
17
  import TerminalCmdSuggestions from '../terminal/terminal-command-dropdown'
18
- import TransportsActionStore from '../sftp/transports-action-store.jsx'
18
+ import TransportsActionStore from '../file-transfer/transports-action-store.jsx'
19
19
  import classnames from 'classnames'
20
20
  import ShortcutControl from '../shortcuts/shortcut-control.jsx'
21
21
  import { isMac, isWin } from '../../common/constants'
@@ -117,16 +117,26 @@ export default auto(function Index (props) {
117
117
  const ext1 = {
118
118
  className: cls
119
119
  }
120
- const cpConf = config
121
- const confsCss = Object
122
- .keys((cpConf))
123
- .filter(d => d.startsWith('terminalBackground'))
124
- .reduce((p, k) => {
125
- return {
120
+ const bgTabs = config.terminalBackgroundImagePath === 'index' || config.terminalBackgroundImagePath === 'randomShape'
121
+ ? store.getTabs()
122
+ : store.getTabs().filter(tab =>
123
+ tab.terminalBackground?.terminalBackgroundImagePath
124
+ )
125
+ const confsCss = {
126
+ ...Object.keys(config)
127
+ .filter(d => d.startsWith('terminalBackground'))
128
+ .reduce((p, k) => ({
126
129
  ...p,
127
- [k]: cpConf[k]
130
+ [k]: config[k]
131
+ }), {}),
132
+ tabs: bgTabs.map(tab => {
133
+ return {
134
+ tabCount: tab.tabCount,
135
+ terminalBackground: tab.terminalBackground,
136
+ id: tab.id
128
137
  }
129
- }, {})
138
+ })
139
+ }
130
140
  const themeProps = {
131
141
  themeConfig: store.getUiThemeConfig()
132
142
  }
@@ -246,7 +256,7 @@ export default auto(function Index (props) {
246
256
  <CustomCss customCss={config.customCss} />
247
257
  <TextEditor />
248
258
  <UpdateCheck
249
- skipVersion={cpConf.skipVersion}
259
+ skipVersion={config.skipVersion}
250
260
  upgradeInfo={upgradeInfo}
251
261
  installSrc={installSrc}
252
262
  />
@@ -265,10 +275,6 @@ export default auto(function Index (props) {
265
275
  <ConfirmModalStore
266
276
  transferToConfirm={transferToConfirm}
267
277
  />
268
- <TransferConflictStore
269
- {...conflictStoreProps}
270
- transferToConfirm={transferToConfirm}
271
- />
272
278
  <TransportsActionStore
273
279
  {...conflictStoreProps}
274
280
  config={config}
@@ -286,6 +292,7 @@ export default auto(function Index (props) {
286
292
  />
287
293
  <ConnectionHoppingWarning {...warningProps} />
288
294
  <TerminalCmdSuggestions {...cmdSuggestionsProps} />
295
+ <TransferQueue />
289
296
  </div>
290
297
  </ConfigProvider>
291
298
  )
@@ -36,3 +36,19 @@
36
36
  .loading-data
37
37
  display none
38
38
 
39
+ #container
40
+ .session-batch-active
41
+ .xterm-screen
42
+ &::before
43
+ font-size 30vmin
44
+ font-weight bold
45
+ color alpha(text, 0.15)
46
+ display flex
47
+ justify-content center
48
+ align-items center
49
+ position absolute
50
+ top 0
51
+ left 0
52
+ right 0
53
+ bottom 0
54
+ z-index -1
@@ -40,7 +40,7 @@ export default class ProfileList extends List {
40
40
  )
41
41
  return (
42
42
  <div
43
- key={i + id}
43
+ key={id}
44
44
  className={cls}
45
45
  onClick={() => this.onClickItem(item)}
46
46
  data-id={id}
@@ -29,4 +29,7 @@
29
29
  opacity 1
30
30
  @media (max-width: 500px)
31
31
  .qm-search-input
32
- width 100px
32
+ width 100px
33
+
34
+ .item-list-unit.dragover
35
+ border: 1px dashed primary
@@ -38,12 +38,22 @@ export default class QuickCommandsList extends List {
38
38
  }
39
39
 
40
40
  handleDragStart = e => {
41
- e.dataTransfer.setData('idDragged', e.target.getAttribute('data-id'))
41
+ const dragElement = e.target.closest('.item-list-unit')
42
+ if (dragElement) {
43
+ e.dataTransfer.setData('idDragged', dragElement.getAttribute('data-id'))
44
+ }
45
+ }
46
+
47
+ handleDragEnter = e => {
48
+ e.target.closest('.item-list-unit').classList.add('dragover')
49
+ }
50
+
51
+ handleDragLeave = e => {
52
+ e.target.closest('.item-list-unit').classList.remove('dragover')
42
53
  }
43
54
 
44
- // adjust window.store.quickCommands array order when drop, so that the dragged item will be placed at the right position, e.target.getAttribute('data-id') would target item id, e.dataTransfer.getData('idDragged') would target dragged item id
45
55
  handleDrop = e => {
46
- onDrop(e)
56
+ onDrop(e, '.item-list-unit')
47
57
  }
48
58
 
49
59
  renderItem = (item, i) => {
@@ -65,7 +75,7 @@ export default class QuickCommandsList extends List {
65
75
  )
66
76
  return (
67
77
  <div
68
- key={i + id}
78
+ key={id}
69
79
  className={cls}
70
80
  onClick={() => this.onClickItem(item)}
71
81
  data-id={id}
@@ -73,6 +83,8 @@ export default class QuickCommandsList extends List {
73
83
  onDragOver={this.handleDragOver}
74
84
  onDragStart={this.handleDragStart}
75
85
  onDrop={this.handleDrop}
86
+ onDragEnter={this.handleDragEnter}
87
+ onDragLeave={this.handleDragLeave}
76
88
  >
77
89
  <div className='elli pd1y pd2x' title={name}>
78
90
  {
@@ -70,7 +70,7 @@
70
70
  display block !important
71
71
  width auto !important
72
72
  height auto !important
73
- .not-split-view .ant-splitter-bar-dragger
73
+ .not-split-view > .ant-splitter-bar .ant-splitter-bar-dragger
74
74
  display none
75
75
 
76
76
  .sess-icon
@@ -113,7 +113,7 @@ export default class ItemList extends React.PureComponent {
113
113
  const isGroup = false
114
114
  return (
115
115
  <div
116
- key={i + '__' + id}
116
+ key={id}
117
117
  className={cls}
118
118
  onClick={() => onClickItem(item, type)}
119
119
  >
@@ -485,7 +485,8 @@ export default class SettingTerminal extends Component {
485
485
  const bgProps = {
486
486
  onChangeValue: this.onChangeValue,
487
487
  name: 'terminalBackgroundImagePath',
488
- config: this.props.config
488
+ config: this.props.config,
489
+ isGlobal: true
489
490
  }
490
491
  const tip = (
491
492
  <div>
@@ -16,7 +16,8 @@ const e = window.translate
16
16
  export default function TerminalBackgroundConfig ({
17
17
  onChangeValue,
18
18
  name,
19
- config
19
+ config,
20
+ isGlobal = false
20
21
  }) {
21
22
  const value = config[name]
22
23
  const defaultValue = defaultSettings[name]
@@ -42,6 +43,18 @@ export default function TerminalBackgroundConfig ({
42
43
  desc: e('noTerminalBg')
43
44
  }
44
45
  ]
46
+ if (isGlobal) {
47
+ dataSource.push(
48
+ {
49
+ value: 'index',
50
+ desc: e('index')
51
+ },
52
+ {
53
+ value: 'randomShape',
54
+ desc: e('randomCats')
55
+ }
56
+ )
57
+ }
45
58
  const numberOpts = { step: 0.05, min: 0, max: 1, cls: 'bg-img-setting' }
46
59
 
47
60
  function renderNumber (name, options, title = '', width = 136) {
@@ -75,7 +88,7 @@ export default function TerminalBackgroundConfig ({
75
88
  }
76
89
 
77
90
  const renderFilter = () => {
78
- if (config[name] === noTerminalBgValue) return
91
+ if (config[name] === noTerminalBgValue || config[name] === 'index') return
79
92
 
80
93
  return (
81
94
  <div>
@@ -101,6 +101,9 @@ export default class FileMode extends React.PureComponent {
101
101
  file: {},
102
102
  visible: false
103
103
  })
104
+ if (this.state.fileId) {
105
+ refs.get(this.state.fileId)?.clearRef()
106
+ }
104
107
  }
105
108
 
106
109
  getSize = (str = '') => {
@@ -22,11 +22,12 @@ import wait from '../../common/wait'
22
22
  import {
23
23
  fileOperationsMap,
24
24
  isWin, transferTypeMap, typeMap,
25
+ paneMap,
25
26
  isMac, maxEditFileSize, ctrlOrCmd
26
27
  } from '../../common/constants'
27
28
  import findParent from '../../common/find-parent'
28
29
  import sorter from '../../common/index-sorter'
29
- import { getFolderFromFilePath, getLocalFileInfo, checkFolderSize } from './file-read'
30
+ import { getFolderFromFilePath, getLocalFileInfo } from './file-read'
30
31
  import { readClipboard, copy as copyToClipboard, hasFileInClipboardText } from '../../common/clipboard'
31
32
  import fs from '../../common/fs'
32
33
  import time from '../../common/time'
@@ -51,15 +52,15 @@ export default class FileSection extends React.Component {
51
52
  super(props)
52
53
  this.state = {
53
54
  file: copy(props.file),
54
- overwriteStrategy: ''
55
+ overwriteStrategy: '',
56
+ dropdownOpen: false
55
57
  }
56
58
  // Create ref
57
59
  this.domRef = React.createRef()
60
+ this.id = 'file-' + this.props.file.id
58
61
  }
59
62
 
60
63
  componentDidMount () {
61
- this.id = 'file-' + (this.props.file?.id || generate())
62
- refs.add(this.id, this)
63
64
  this.applyStyle()
64
65
  }
65
66
 
@@ -73,7 +74,6 @@ export default class FileSection extends React.Component {
73
74
  }
74
75
 
75
76
  componentWillUnmount () {
76
- refsStatic.remove(this.id)
77
77
  clearTimeout(this.timer)
78
78
  this.timer = null
79
79
  this.domRef = null
@@ -81,10 +81,20 @@ export default class FileSection extends React.Component {
81
81
  this.removeFileEditEvent()
82
82
  }
83
83
 
84
+ clearRef = () => {
85
+ refs.remove(this.id)
86
+ }
87
+
84
88
  get editor () {
85
89
  return refsStatic.get('text-editor')
86
90
  }
87
91
 
92
+ handleDropdownOpenChange = (open) => {
93
+ if (open) {
94
+ this.forceUpdate()
95
+ }
96
+ }
97
+
88
98
  applyStyle = () => {
89
99
  if (!this.domRef) {
90
100
  return
@@ -511,6 +521,7 @@ export default class FileSection extends React.Component {
511
521
  }
512
522
 
513
523
  changeFileMode = async (file) => {
524
+ this.clearRef()
514
525
  const { permission, type, path, name } = file
515
526
  const func = type === typeMap.local
516
527
  ? fs.chmod
@@ -522,6 +533,7 @@ export default class FileSection extends React.Component {
522
533
 
523
534
  openFileModeModal = () => {
524
535
  const { type } = this.props
536
+ refs.add(this.id, this)
525
537
  refsStatic.get('file-modal')?.showFileModeModal(
526
538
  {
527
539
  tab: this.props.tab,
@@ -611,6 +623,7 @@ export default class FileSection extends React.Component {
611
623
  }
612
624
 
613
625
  removeFileEditEvent = () => {
626
+ this.clearRef()
614
627
  if (this.watchingFile) {
615
628
  window.pre.ipcOffEvent('file-change', this.onFileChange)
616
629
  window.pre.runGlobalAsync('unwatchFile', this.watchingFile)
@@ -659,15 +672,8 @@ export default class FileSection extends React.Component {
659
672
  path, name
660
673
  } = this.state.file
661
674
  const rp = resolve(path, name)
662
- ;(
663
- document.querySelector('.session-current .term-sftp-tabs .type-tab.terminal') ||
664
- document.querySelector('.session-current .term-sftp-tabs .type-tab.ssh')
665
- ).click()
666
- this.timer = setTimeout(() => {
667
- window.store.runQuickCommand(
668
- `cd "${rp}"`
669
- )
670
- }, 500)
675
+ this.props.tab.pane = paneMap.terminal
676
+ refs.get('term-' + this.props.tab.id)?.cd(rp)
671
677
  }
672
678
 
673
679
  fetchEditorText = async (path, type) => {
@@ -698,6 +704,7 @@ export default class FileSection extends React.Component {
698
704
  data.file = null
699
705
  data.text = ''
700
706
  }
707
+ this.clearRef()
701
708
  this.editor?.setState(data)
702
709
  if (r && !noClose) {
703
710
  this.props[`${type}List`]()
@@ -705,6 +712,7 @@ export default class FileSection extends React.Component {
705
712
  }
706
713
 
707
714
  editFile = () => {
715
+ refs.add(this.id, this)
708
716
  this.editor?.openEditor({
709
717
  id: this.id,
710
718
  file: this.state.file
@@ -741,7 +749,7 @@ export default class FileSection extends React.Component {
741
749
  _typeTo,
742
750
  operation
743
751
  ) => {
744
- const { name, path, type, isDirectory } = file
752
+ const { name, path, type } = file
745
753
  const isLocal = type === typeMap.local
746
754
  let typeTo = isLocal
747
755
  ? typeMap.remote
@@ -766,13 +774,6 @@ export default class FileSection extends React.Component {
766
774
  ...createTransferProps(this.props),
767
775
  operation
768
776
  }
769
- if (isDirectory) {
770
- const zip = await checkFolderSize(this.props, file)
771
- Object.assign(obj, {
772
- zip,
773
- skipExpand: zip
774
- })
775
- }
776
777
  return [obj]
777
778
  }
778
779
 
@@ -1198,7 +1199,8 @@ export default class FileSection extends React.Component {
1198
1199
  items: this.renderContextMenu(),
1199
1200
  onClick: this.onContextMenu
1200
1201
  },
1201
- trigger: ['contextMenu']
1202
+ trigger: ['contextMenu'],
1203
+ onOpenChange: this.handleDropdownOpenChange
1202
1204
  }
1203
1205
  return (
1204
1206
  <Dropdown {...ddProps}>
@@ -4,8 +4,7 @@
4
4
 
5
5
  import generate from '../../common/uid'
6
6
  import fs from '../../common/fs'
7
- import { isWin, typeMap } from '../../common/constants'
8
- import resolve from '../../common/resolve'
7
+ import { isWin } from '../../common/constants'
9
8
 
10
9
  export const getFileExt = fileName => {
11
10
  const sep = '.'
@@ -75,28 +74,3 @@ export const getRemoteFileInfo = async (sftp, filePath) => {
75
74
  isDirectory: stat.isDirectory
76
75
  }
77
76
  }
78
-
79
- export async function checkFolderSize (props, f) {
80
- const pth = resolve(f.path, f.name)
81
- const func = f.type === typeMap.remote
82
- ? props.sftp
83
- : window.fs
84
- const {
85
- size,
86
- count
87
- } = await func.getFolderSize(pth)
88
- .catch(err => {
89
- console.log('get folder size fail', err)
90
- return { size: 0, count: 0 }
91
- })
92
- if (count === 0) {
93
- return true
94
- }
95
- if (size >= 600) {
96
- return false
97
- }
98
- if (size / count >= 100) {
99
- return false
100
- }
101
- return true
102
- }
@@ -250,10 +250,10 @@ export default class FileListTable extends Component {
250
250
  this.setState(this.initFromProps())
251
251
  }
252
252
 
253
- renderItem = (item, i) => {
253
+ renderItem = (item) => {
254
254
  const { type } = this.props
255
255
  const cls = item.isParent ? 'parent-file-item' : 'real-file-item'
256
- const key = i + '*f*' + item.id
256
+ const key = item.id
257
257
  return (
258
258
  <FileSection
259
259
  {...this.props.getFileProps(item, type)}
@@ -28,7 +28,7 @@ export default memo(function TransferHistoryModal (props) {
28
28
  const {
29
29
  clearTransferHistory
30
30
  } = window.store
31
- const transferHistory = props.transferHistory.filter(d => !d.unzip)
31
+ const transferHistory = props.transferHistory
32
32
  const columns = [{
33
33
  title: e('startTime'),
34
34
  dataIndex: 'startTime',
@@ -118,7 +118,7 @@ export default class TransferModalUI extends Component {
118
118
  <Transport
119
119
  transfer={t}
120
120
  index={i}
121
- key={id + ':tr:' + i}
121
+ key={id}
122
122
  />
123
123
  )
124
124
  })
@@ -12,6 +12,7 @@ import {
12
12
  } from '@ant-design/icons'
13
13
  import { action } from 'manate'
14
14
  import { addClass, removeClass } from '../../common/class'
15
+ import { refsStatic } from '../common/ref'
15
16
  import './transfer.styl'
16
17
 
17
18
  const e = window.translate
@@ -38,19 +39,25 @@ export default function Transporter (props) {
38
39
  const onDragCls = 'ondrag-tr'
39
40
  const onDragOverCls = 'dragover-tr'
40
41
  function moveToTop () {
41
- action(function () {
42
- const arr = window.store.fileTransfers
43
- if (index > 0) {
44
- const [item] = arr.splice(index, 1)
45
- arr.unshift(item)
46
- }
47
- })()
42
+ refsStatic.get('transfer-queue')?.addToQueue(
43
+ 'moveTop',
44
+ id
45
+ )
48
46
  }
49
47
  function cancel () {
50
- window.store.cancelTransfer(id)
48
+ refsStatic.get('transfer-queue')?.addToQueue(
49
+ 'delete',
50
+ id
51
+ )
51
52
  }
52
53
  function handlePauseOrResume () {
53
- window.store.toggleTransfer(id)
54
+ refsStatic.get('transfer-queue')?.addToQueue(
55
+ 'update',
56
+ id,
57
+ {
58
+ pausing: !pausing
59
+ }
60
+ )
54
61
  }
55
62
 
56
63
  function clearCls () {
@@ -11,7 +11,8 @@ import {
11
11
  notification,
12
12
  Spin,
13
13
  Button,
14
- Dropdown
14
+ Dropdown,
15
+ message
15
16
  } from 'antd'
16
17
  import classnames from 'classnames'
17
18
  import './terminal.styl'
@@ -314,15 +315,31 @@ clear\r`
314
315
  this.term.focus()
315
316
  }
316
317
 
318
+ isUnsafeFilename = (filename) => {
319
+ return /["'\n\r]/.test(filename)
320
+ }
321
+
322
+ cd = (p) => {
323
+ if (this.isUnsafeFilename(p)) {
324
+ return message.error('File name contains unsafe characters')
325
+ }
326
+ this.runQuickCommand(`cd "${p}"`)
327
+ }
328
+
317
329
  onDrop = e => {
318
330
  const dt = e.dataTransfer
319
331
  const fromFile = dt.getData('fromFile')
332
+ const notSafeMsg = 'File name contains unsafe characters'
320
333
 
321
334
  if (fromFile) {
322
335
  // Handle SFTP file drop
323
336
  try {
324
337
  const fileData = JSON.parse(fromFile)
325
338
  const filePath = resolve(fileData.path, fileData.name)
339
+ if (this.isUnsafeFilename(filePath)) {
340
+ message.error(notSafeMsg)
341
+ return
342
+ }
326
343
  this.attachAddon._sendData(`"${filePath}" `)
327
344
  return
328
345
  } catch (e) {
@@ -333,6 +350,11 @@ clear\r`
333
350
  // Handle regular file drop
334
351
  const files = dt.files
335
352
  if (files && files.length) {
353
+ const filesAll = Array.from(files).map(f => `"${f.path}"`).join(' ')
354
+ if (this.isUnsafeFilename(filesAll)) {
355
+ message.error(notSafeMsg)
356
+ return
357
+ }
336
358
  this.attachAddon._sendData(
337
359
  Array.from(files).map(f => `"${f.path}"`).join(' ')
338
360
  )