@electerm/electerm-react 1.38.60 → 1.38.70

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 (36) hide show
  1. package/client/common/constants.js +3 -2
  2. package/client/common/create-title.jsx +9 -1
  3. package/client/common/sftp.js +3 -0
  4. package/client/components/batch-op/batch-op.jsx +1 -6
  5. package/client/components/bookmark-form/bookmark-form.styl +3 -1
  6. package/client/components/bookmark-form/render-ssh-tunnel.jsx +210 -88
  7. package/client/components/bookmark-form/ssh-form-ui.jsx +1 -1
  8. package/client/components/main/main.jsx +14 -0
  9. package/client/components/quick-commands/quick-commands-box.jsx +5 -4
  10. package/client/components/sftp/{confirm-modal.jsx → confirm-modal-store.jsx} +81 -50
  11. package/client/components/sftp/file-item.jsx +2 -0
  12. package/client/components/sftp/sftp-entry.jsx +34 -40
  13. package/client/components/sftp/transfer-conflict-store.jsx +291 -0
  14. package/client/components/sftp/transport-action-store.jsx +430 -0
  15. package/client/components/sftp/transports-action-store.jsx +102 -0
  16. package/client/components/sftp/transports-ui-store.jsx +30 -0
  17. package/client/components/sidebar/transfer-list-control.jsx +5 -14
  18. package/client/components/sidebar/transport-ui.jsx +2 -12
  19. package/client/components/tabs/tab.jsx +43 -2
  20. package/client/components/tabs/tabs.styl +1 -1
  21. package/client/components/terminal/index.jsx +1 -0
  22. package/client/components/terminal/terminal-interactive.jsx +15 -0
  23. package/client/components/terminal-info/disk.jsx +9 -0
  24. package/client/store/index.js +4 -0
  25. package/client/store/init-state.js +2 -3
  26. package/client/store/sync.js +5 -2
  27. package/client/store/tab.js +1 -1
  28. package/client/store/transfer-list.js +55 -2
  29. package/client/store/watch.js +0 -8
  30. package/package.json +1 -1
  31. package/client/components/sftp/transfer-conflict.jsx +0 -323
  32. package/client/components/sftp/transport-action.jsx +0 -412
  33. package/client/components/sftp/transport-entry.jsx +0 -108
  34. package/client/components/sftp/transport-types.js +0 -8
  35. package/client/components/sftp/transports-action.jsx +0 -111
  36. package/client/components/sftp/transports-ui.jsx +0 -93
@@ -3,6 +3,7 @@
3
3
  *
4
4
  */
5
5
 
6
+ import { Component } from '../common/react-subx'
6
7
  import { Modal, Button } from 'antd'
7
8
  import { isString } from 'lodash-es'
8
9
  import AnimateText from '../common/animate-text'
@@ -23,42 +24,51 @@ function formatTimeAuto (strOrDigit) {
23
24
  return formatTime(strOrDigit * 1000)
24
25
  }
25
26
 
26
- export default (props) => {
27
- if (!props.transferToConfirm) {
28
- return null
29
- }
30
- const {
31
- fromPath,
32
- toPath,
33
- fromFile: {
34
- isDirectory,
35
- name,
36
- id: fileId,
37
- modifyTime: modifyTimeFrom,
38
- size: sizeFrom,
39
- type: typeFrom
40
- },
41
- toFile: {
42
- modifyTime: modifyTimeTo,
43
- size: sizeTo,
44
- type: typeTo
45
- },
46
- id,
47
- transferGroupId
48
- } = props.transferToConfirm
49
- function act (action) {
50
- props.modifier({
51
- transferToConfirm: null
52
- })
27
+ export default class ConfirmModalStore extends Component {
28
+ act (action) {
29
+ const { store } = this.props
30
+ const {
31
+ transferToConfirm
32
+ } = store
33
+ store.setState(
34
+ 'transferToConfirm', {}
35
+ )
36
+ const {
37
+ fromFile: {
38
+ id: fileId
39
+ },
40
+ id,
41
+ transferGroupId
42
+ } = transferToConfirm
53
43
  postMessage({
54
44
  transferGroupId,
55
45
  fileId,
56
46
  id,
57
- transfer: props.transferToConfirm,
47
+ transfer: transferToConfirm,
58
48
  action
59
49
  })
60
50
  }
61
- function renderContent () {
51
+
52
+ renderContent () {
53
+ const {
54
+ transferToConfirm
55
+ } = this.props.store
56
+ const {
57
+ fromPath,
58
+ toPath,
59
+ fromFile: {
60
+ isDirectory,
61
+ name,
62
+ modifyTime: modifyTimeFrom,
63
+ size: sizeFrom,
64
+ type: typeFrom
65
+ },
66
+ toFile: {
67
+ modifyTime: modifyTimeTo,
68
+ size: sizeTo,
69
+ type: typeTo
70
+ }
71
+ } = transferToConfirm
62
72
  const action = isDirectory ? e('merge') : e('replace')
63
73
  const typeTxt = isDirectory ? e('folder') : e('file')
64
74
  const Icon = isDirectory ? FolderOutlined : FileOutlined
@@ -95,27 +105,39 @@ export default (props) => {
95
105
  </div>
96
106
  )
97
107
  }
98
- function renderFooter () {
108
+
109
+ renderFooter () {
110
+ const {
111
+ transferToConfirm
112
+ } = this.props.store
113
+ if (!transferToConfirm) {
114
+ return null
115
+ }
116
+ const {
117
+ fromFile: {
118
+ isDirectory
119
+ }
120
+ } = transferToConfirm
99
121
  return (
100
122
  <div className='mgq1t pd1y alignright'>
101
123
  <Button
102
124
  type='dashed'
103
125
  className='mg1l'
104
- onClick={() => act(fileActions.cancel)}
126
+ onClick={() => this.act(fileActions.cancel)}
105
127
  >
106
128
  {e('cancel')}
107
129
  </Button>
108
130
  <Button
109
131
  type='dashed'
110
132
  className='mg1l'
111
- onClick={() => act(fileActions.skip)}
133
+ onClick={() => this.act(fileActions.skip)}
112
134
  >
113
135
  {e('skip')}
114
136
  </Button>
115
137
  <Button
116
138
  type='dashed'
117
139
  className='mg1l'
118
- onClick={() => act(fileActions.skipAll)}
140
+ onClick={() => this.act(fileActions.skipAll)}
119
141
  >
120
142
  {e('skipAll')}
121
143
  </Button>
@@ -123,7 +145,7 @@ export default (props) => {
123
145
  danger
124
146
  className='mg1l'
125
147
  onClick={
126
- () => act(fileActions.mergeOrOverwrite)
148
+ () => this.act(fileActions.mergeOrOverwrite)
127
149
  }
128
150
  >
129
151
  {isDirectory ? e('merge') : e('overwrite')}
@@ -132,7 +154,7 @@ export default (props) => {
132
154
  type='primary'
133
155
  className='mg1l'
134
156
  onClick={
135
- () => act(fileActions.rename)
157
+ () => this.act(fileActions.rename)
136
158
  }
137
159
  >
138
160
  {e('rename')}
@@ -148,7 +170,7 @@ export default (props) => {
148
170
  : e('overwriteDesc')
149
171
  }
150
172
  onClick={
151
- () => act(fileActions.mergeOrOverwriteAll)
173
+ () => this.act(fileActions.mergeOrOverwriteAll)
152
174
  }
153
175
  >
154
176
  {isDirectory ? e('mergeAll') : e('overwriteAll')}
@@ -158,7 +180,7 @@ export default (props) => {
158
180
  className='mg1l'
159
181
  title={e('renameDesc')}
160
182
  onClick={
161
- () => act(fileActions.renameAll)
183
+ () => this.act(fileActions.renameAll)
162
184
  }
163
185
  >
164
186
  {e('renameAll')}
@@ -167,18 +189,27 @@ export default (props) => {
167
189
  </div>
168
190
  )
169
191
  }
170
- const modalProps = {
171
- open: true,
172
- width: 500,
173
- title: e('fileConflict'),
174
- footer: renderFooter(),
175
- onCancel: () => act(fileActions.cancel)
192
+
193
+ render () {
194
+ const {
195
+ transferToConfirm
196
+ } = this.props.store
197
+ if (!transferToConfirm.id) {
198
+ return null
199
+ }
200
+ const modalProps = {
201
+ open: true,
202
+ width: 500,
203
+ title: e('fileConflict'),
204
+ footer: this.renderFooter(),
205
+ onCancel: () => this.act(fileActions.cancel)
206
+ }
207
+ return (
208
+ <Modal
209
+ {...modalProps}
210
+ >
211
+ {this.renderContent()}
212
+ </Modal>
213
+ )
176
214
  }
177
- return (
178
- <Modal
179
- {...modalProps}
180
- >
181
- {renderContent()}
182
- </Modal>
183
- )
184
215
  }
@@ -168,6 +168,7 @@ export default class FileSection extends React.Component {
168
168
  fromPath,
169
169
  toPath,
170
170
  id: generate(),
171
+ host: this.props.tab?.host,
171
172
  ...createTransferProps(this.props),
172
173
  operation
173
174
  })
@@ -804,6 +805,7 @@ export default class FileSection extends React.Component {
804
805
  }
805
806
  toPath = resolve(toPath, name)
806
807
  const obj = {
808
+ host: this.props.tab?.host,
807
809
  typeFrom: type,
808
810
  typeTo,
809
811
  fromPath: resolve(path, name),
@@ -14,8 +14,10 @@ import {
14
14
  typeMap, maxSftpHistory, paneMap,
15
15
  eventTypes,
16
16
  fileTypeMap,
17
- terminalSshConfigType, terminalSerialType,
18
- unexpectedPacketErrorDesc, sftpRetryInterval,
17
+ terminalSshConfigType,
18
+ terminalSerialType,
19
+ unexpectedPacketErrorDesc,
20
+ sftpRetryInterval,
19
21
  commonActions
20
22
  } from '../../common/constants'
21
23
  import { hasFileInClipboardText } from '../../common/clipboard'
@@ -27,7 +29,6 @@ import ListTable from './list-table-ui'
27
29
  import deepCopy from 'json-deep-copy'
28
30
  import isValidPath from '../../common/is-valid-path'
29
31
  import memoizeOne from 'memoize-one'
30
- import TransportEntry from './transport-entry'
31
32
  import postMessage from '../../common/post-msg'
32
33
  import { runCmd } from '../terminal/terminal-apis'
33
34
  import * as owner from './owner-list'
@@ -58,9 +59,6 @@ export default class Sftp extends Component {
58
59
  onEditFile: false,
59
60
  ...this.defaultState(),
60
61
  loadingSftp: false,
61
- transferToConfirm: null,
62
- transferList: [],
63
- pauseAll: false,
64
62
  inited: false
65
63
  }
66
64
  this.retryCount = 0
@@ -190,11 +188,27 @@ export default class Sftp extends Component {
190
188
  }, isEqual)
191
189
 
192
190
  initEvent () {
191
+ window.addEventListener('message', this.handleMsg)
193
192
  window.addEventListener('keydown', this.handleEvent)
194
193
  }
195
194
 
196
195
  destroyEvent () {
197
196
  window.removeEventListener('keydown', this.handleEvent)
197
+ window.removeEventListener('message', this.handleMsg)
198
+ }
199
+
200
+ handleMsg = event => {
201
+ const {
202
+ action,
203
+ sessionId,
204
+ type
205
+ } = event?.data || {}
206
+ if (
207
+ action === commonActions.sftpList &&
208
+ sessionId === this.props.sessionId
209
+ ) {
210
+ this[type + 'List']()
211
+ }
198
212
  }
199
213
 
200
214
  isActive () {
@@ -437,32 +451,28 @@ export default class Sftp extends Component {
437
451
  }
438
452
  const { type } = lastClickedFile
439
453
  const { inputFocus, onDelete } = this
454
+ e.stopPropagation()
440
455
  if (keyControlPressed(e) && keyPressed(e, 'keyA') && !inputFocus) {
441
- e.stopPropagation()
442
456
  this.selectAll(type, e)
443
457
  } else if (keyPressed(e, 'arrowdown') && !inputFocus) {
444
- e.stopPropagation()
445
458
  this.selectNext(type)
446
459
  } else if (keyPressed(e, 'arrowup') && !inputFocus) {
447
- e.stopPropagation()
448
460
  this.selectPrev(type)
449
- } else if (keyPressed(e, 'delete') && !inputFocus) {
450
- e.stopPropagation()
461
+ } else if (
462
+ keyPressed(e, 'delete') &&
463
+ !inputFocus &&
464
+ !this.state.onEditFile
465
+ ) {
451
466
  this.onDel(type)
452
467
  } else if (keyPressed(e, 'enter') && !inputFocus && !onDelete) {
453
- e.stopPropagation()
454
468
  this.enter(type, e)
455
469
  } else if (keyControlPressed(e) && keyPressed(e, 'keyC') && !inputFocus) {
456
- e.stopPropagation()
457
470
  this.doCopy(type, e)
458
471
  } else if (keyControlPressed(e) && keyPressed(e, 'keyX') && !inputFocus) {
459
- e.stopPropagation()
460
472
  this.doCut(type, e)
461
473
  } else if (keyControlPressed(e) && keyPressed(e, 'keyV') && !inputFocus) {
462
- e.stopPropagation()
463
474
  this.doPaste(type, e)
464
475
  } else if (keyPressed(e, 'f5')) {
465
- e.stopPropagation()
466
476
  this.onGoto(type)
467
477
  }
468
478
  }
@@ -496,11 +506,7 @@ export default class Sftp extends Component {
496
506
  }
497
507
 
498
508
  addTransferList = list => {
499
- postMessage({
500
- list,
501
- action: commonActions.addTransfer,
502
- sessionId: this.props.sessionId
503
- })
509
+ window.store.addTransferList(list)
504
510
  }
505
511
 
506
512
  computeListHeight = () => {
@@ -568,10 +574,14 @@ export default class Sftp extends Component {
568
574
  return arr.map(item => {
569
575
  const { type } = item
570
576
  return {
571
- ...pick(item, ['name', 'size', 'accessTime', 'modifyTime', 'mode', 'owner', 'group']),
577
+ ...pick(
578
+ item,
579
+ ['name', 'size', 'accessTime', 'modifyTime', 'mode', 'owner', 'group']
580
+ ),
572
581
  isDirectory: type === fileTypeMap.directory,
573
582
  type: typeMap.remote,
574
583
  path: remotePath,
584
+ isSymbol: type === fileTypeMap.link,
575
585
  id: generate()
576
586
  }
577
587
  })
@@ -711,8 +721,8 @@ export default class Sftp extends Component {
711
721
  ) => {
712
722
  const remote = []
713
723
  for (const r of remotes) {
714
- const { type, name } = r
715
- if (type === fileTypeMap.link) {
724
+ const { name } = r
725
+ if (r.isSymbol) {
716
726
  const linkPath = resolve(remotePath, name)
717
727
  let realpath = await sftp.readlink(linkPath)
718
728
  .catch(e => {
@@ -902,11 +912,8 @@ export default class Sftp extends Component {
902
912
  typeMap.remote,
903
913
  'lastClickedFile',
904
914
  'lastMataKey',
905
- 'transferToConfirm',
906
- 'transferList',
907
915
  'targetTransferType',
908
916
  'selectedFiles',
909
- 'pauseAll',
910
917
  'localGidTree',
911
918
  'remoteUidTree',
912
919
  'localUidTree',
@@ -1104,18 +1111,6 @@ export default class Sftp extends Component {
1104
1111
  const {
1105
1112
  id
1106
1113
  } = this.state
1107
- const prps = {
1108
- localList: this.localList,
1109
- remoteList: this.remoteList,
1110
- sftp: this.sftp,
1111
- sessionId: this.props.sessionId,
1112
- host: this.props.tab.host,
1113
- localListDebounce: this.localListDebounce,
1114
- remoteListDebounce: this.remoteListDebounce,
1115
- config: this.props.config,
1116
- tab: this.props.tab,
1117
- pid: this.props.pid
1118
- }
1119
1114
  const all = {
1120
1115
  className: 'sftp-wrap overhide relative',
1121
1116
  id: `id-${id}`,
@@ -1128,7 +1123,6 @@ export default class Sftp extends Component {
1128
1123
  {
1129
1124
  this.renderSections()
1130
1125
  }
1131
- <TransportEntry {...prps} />
1132
1126
  </div>
1133
1127
  )
1134
1128
  }
@@ -0,0 +1,291 @@
1
+ /**
2
+ * pass transfer list from props
3
+ * when list changes, do transfer and other op
4
+ */
5
+
6
+ import { Component } from '../common/react-subx'
7
+ import { typeMap } from '../../common/constants'
8
+ import {
9
+ getLocalFileInfo,
10
+ getRemoteFileInfo,
11
+ getFolderFromFilePath,
12
+ getFileExt,
13
+ checkFolderSize
14
+ } from './file-read'
15
+ import { findIndex, find } from 'lodash-es'
16
+ import generate from '../../common/uid'
17
+ import resolve from '../../common/resolve'
18
+
19
+ export default class TransferConflictStore extends Component {
20
+ state = {
21
+ currentId: ''
22
+ }
23
+
24
+ componentDidMount () {
25
+ this.watchFile()
26
+ }
27
+
28
+ componentDidUpdate (prevProps) {
29
+ if (
30
+ prevProps._fileTransfers !== this.props._fileTransfers
31
+ ) {
32
+ this.watchFile()
33
+ }
34
+ }
35
+
36
+ localCheckExist = (path) => {
37
+ return getLocalFileInfo(path)
38
+ }
39
+
40
+ remoteCheckExist = (path, sessionId) => {
41
+ const sftp = window.sftps[sessionId]
42
+ return getRemoteFileInfo(sftp, path)
43
+ .then(r => r)
44
+ .catch(() => false)
45
+ }
46
+
47
+ checkExist = (type, path, sessionId) => {
48
+ return this[type + 'CheckExist'](path, sessionId)
49
+ }
50
+
51
+ rename = (tr, action, _renameId) => {
52
+ const isRemote = tr.typeTo === typeMap.remote
53
+ const { path, name } = getFolderFromFilePath(tr.toPath, isRemote)
54
+ const { base, ext } = getFileExt(name)
55
+ const renameId = _renameId || generate()
56
+ const newName = ext
57
+ ? `${base}(rename-${renameId}).${ext}`
58
+ : `${base}(rename-${renameId})`
59
+ const res = {
60
+ ...tr,
61
+ renameId,
62
+ newName,
63
+ oldName: base,
64
+ toPath: resolve(path, newName)
65
+ }
66
+ if (action) {
67
+ res.action = action
68
+ }
69
+ return res
70
+ }
71
+
72
+ updateTransferAction = (data) => {
73
+ const {
74
+ id,
75
+ action,
76
+ transfer
77
+ } = data
78
+ const {
79
+ fromFile
80
+ } = transfer
81
+ this.clear()
82
+ const { store } = this.props
83
+ let {
84
+ fileTransfers
85
+ } = store
86
+ const index = findIndex(fileTransfers, d => d.id === id)
87
+ if (index < 0) {
88
+ return store.setFileTransfers(fileTransfers)
89
+ }
90
+ fileTransfers[index].fromFile = fromFile
91
+ fileTransfers[index].action = action
92
+ if (action === 'skip') {
93
+ fileTransfers.splice(index, 1)
94
+ } else if (action === 'cancel') {
95
+ fileTransfers = fileTransfers.slice(0, index)
96
+ }
97
+ if (action.includes('All')) {
98
+ fileTransfers = fileTransfers.map((t, i) => {
99
+ if (i < index) {
100
+ return t
101
+ }
102
+ return {
103
+ ...t,
104
+ action: action.replace('All', '')
105
+ }
106
+ })
107
+ }
108
+ if (action.includes('rename')) {
109
+ fileTransfers[index] = this.rename(fileTransfers[index])
110
+ } else if (action === 'skipAll') {
111
+ fileTransfers.splice(index, 1)
112
+ }
113
+ store.setFileTransfers(fileTransfers)
114
+ }
115
+
116
+ tagTransferError = (id, errorMsg) => {
117
+ const { store } = this.props
118
+ const {
119
+ fileTransfers
120
+ } = store
121
+ const tr = find(fileTransfers, d => d.id === id)
122
+ if (!tr) {
123
+ return
124
+ }
125
+ window.store.addTransferHistory({
126
+ ...tr,
127
+ host: tr.host,
128
+ error: errorMsg,
129
+ finishTime: Date.now()
130
+ })
131
+ const index = findIndex(fileTransfers, d => d.id === id)
132
+ if (index >= 0) {
133
+ fileTransfers.splice(index, 1)
134
+ }
135
+ store.setFileTransfers(fileTransfers)
136
+ }
137
+
138
+ setConflict (tr) {
139
+ if (window.store.transferToConfirm.id) {
140
+ return
141
+ }
142
+ window.store.setState(
143
+ 'transferToConfirm', tr
144
+ )
145
+ }
146
+
147
+ onDecision = (event) => {
148
+ if (
149
+ event &&
150
+ event.data &&
151
+ event.data.id === this.currentId
152
+ ) {
153
+ this.currentId = ''
154
+ this.updateTransferAction(event.data)
155
+ this.onConfirm = false
156
+ window.removeEventListener('message', this.onDecision)
157
+ }
158
+ }
159
+
160
+ waitForSignal = () => {
161
+ window.addEventListener('message', this.onDecision)
162
+ }
163
+
164
+ setCanTransfer = (fromFile, tr) => {
165
+ this.clear()
166
+ const {
167
+ store
168
+ } = this.props
169
+ const {
170
+ fileTransfers
171
+ } = store
172
+ const index = findIndex(fileTransfers, t => {
173
+ return t.id === tr.id
174
+ })
175
+ if (index >= 0) {
176
+ const up = {
177
+ action: 'transfer',
178
+ fromFile
179
+ }
180
+ Object.assign(fileTransfers[index], up)
181
+ } else {
182
+ fileTransfers[0].r = Math.random()
183
+ }
184
+ store.setFileTransfers(fileTransfers)
185
+ }
186
+
187
+ clear = () => {
188
+ this.currentId = ''
189
+ }
190
+
191
+ watchFile = async () => {
192
+ const { store } = this.props
193
+ const {
194
+ fileTransfers
195
+ } = store
196
+ if (!fileTransfers.length && this.currentId) {
197
+ return this.clear()
198
+ }
199
+ const tr = fileTransfers
200
+ .filter(t => {
201
+ return (
202
+ !t.action ||
203
+ !t.fromFile ||
204
+ t.fromFile.isDirectory
205
+ )
206
+ })[0]
207
+ if (!tr) {
208
+ this.onConfirm = false
209
+ return this.clear()
210
+ }
211
+ if (this.currentId) {
212
+ // fileTransfers[0].r = Math.random()
213
+ return store.setFileTransfers(fileTransfers)
214
+ }
215
+ this.currentId = tr.id
216
+ const {
217
+ typeFrom,
218
+ typeTo,
219
+ fromPath,
220
+ toPath,
221
+ id,
222
+ action,
223
+ renameId,
224
+ parentId,
225
+ skipConfirm,
226
+ sessionId
227
+ } = tr
228
+ const fromFile = tr.fromFile
229
+ ? tr.fromFile
230
+ : await this.checkExist(typeFrom, fromPath, sessionId)
231
+ if (!fromFile) {
232
+ this.currentId = ''
233
+ return this.tagTransferError(id, 'file not exist')
234
+ }
235
+ let toFile = false
236
+ if (renameId || parentId) {
237
+ toFile = false
238
+ } else if (fromPath === toPath && typeFrom === typeTo) {
239
+ toFile = true
240
+ } else {
241
+ toFile = await this.checkExist(typeTo, toPath, sessionId)
242
+ }
243
+ if (fromFile.isDirectory) {
244
+ const props = {
245
+ sftp: window.sftps[sessionId]
246
+ }
247
+ const skip = await checkFolderSize(props, fromFile)
248
+ .then(d => d && typeFrom !== typeTo)
249
+ if (!skip) {
250
+ return this.tagTransferError(id, 'folder too big or too many files in folder')
251
+ }
252
+ tr.zip = true
253
+ tr.skipExpand = true
254
+ }
255
+ if (fromPath === toPath && typeFrom === typeTo) {
256
+ return this.updateTransferAction({
257
+ id,
258
+ action: 'rename',
259
+ transfer: {
260
+ ...tr,
261
+ operation: 'cp',
262
+ fromFile
263
+ }
264
+ })
265
+ } else if (toFile && !action && !skipConfirm) {
266
+ this.waitForSignal(id)
267
+ if (!this.onConfirm) {
268
+ this.onConfirm = true
269
+ return this.setConflict({
270
+ ...tr,
271
+ fromFile,
272
+ toFile
273
+ })
274
+ }
275
+ } else if (toFile && !tr.fromFile && action) {
276
+ return this.updateTransferAction({
277
+ id,
278
+ action,
279
+ transfer: {
280
+ ...tr,
281
+ fromFile
282
+ }
283
+ })
284
+ }
285
+ this.setCanTransfer(fromFile, tr)
286
+ }
287
+
288
+ render () {
289
+ return null
290
+ }
291
+ }