@electerm/electerm-react 2.12.0 → 2.13.0

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.
@@ -29,7 +29,10 @@ export const getFilePath = (file) => {
29
29
  export const getDropFileList = (dataTransfer) => {
30
30
  const fromFile = dataTransfer.getData('fromFile')
31
31
  if (fromFile) {
32
- return [JSON.parse(fromFile)]
32
+ const parsed = JSON.parse(fromFile)
33
+ return Array.isArray(parsed)
34
+ ? parsed
35
+ : [parsed]
33
36
  }
34
37
 
35
38
  const { files } = dataTransfer
@@ -0,0 +1,176 @@
1
+ import { autoRun } from 'manate'
2
+ import copy from 'json-deep-copy'
3
+ import uid from '../../common/uid'
4
+ import resolve from '../../common/resolve'
5
+ import fs from '../../common/fs'
6
+ import { typeMap } from '../../common/constants'
7
+ import { getFolderFromFilePath, getLocalFileInfo } from '../sftp/file-read'
8
+
9
+ export default class Remote2RemoteHandler {
10
+ constructor (props) {
11
+ this.props = props
12
+ this.id = uid()
13
+ }
14
+
15
+ get store () {
16
+ return window.store
17
+ }
18
+
19
+ get fromFile () {
20
+ return this.props.fromFile
21
+ }
22
+
23
+ get fromPath () {
24
+ const { path, name } = this.fromFile
25
+ return resolve(path, name)
26
+ }
27
+
28
+ get toPath () {
29
+ return this.props.toPath
30
+ }
31
+
32
+ buildTempPath = () => {
33
+ const { name, ext, base } = getFolderFromFilePath(this.fromPath, true)
34
+ const tail = uid()
35
+ const tempName = ext
36
+ ? `${base}-${tail}.${ext}`
37
+ : `${name}-${tail}`
38
+ return resolve(window.pre.tempDir, tempName)
39
+ }
40
+
41
+ buildStep1Transfer = () => {
42
+ const {
43
+ title,
44
+ tabType,
45
+ sourceTabId,
46
+ sourceHost
47
+ } = this.props
48
+ const transfer = {
49
+ id: uid(),
50
+ typeFrom: typeMap.remote,
51
+ typeTo: typeMap.local,
52
+ fromPath: this.fromPath,
53
+ toPath: this.tempPath,
54
+ tabId: sourceTabId,
55
+ host: sourceHost,
56
+ title,
57
+ tabType,
58
+ operation: '',
59
+ remote2remoteStep: 1,
60
+ remote2remoteId: this.id
61
+ }
62
+ return transfer
63
+ }
64
+
65
+ buildStep2Transfer = (fromFile) => {
66
+ const {
67
+ targetTabId,
68
+ targetHost,
69
+ targetTitle,
70
+ targetTabType
71
+ } = this.props
72
+ const transfer = {
73
+ id: uid(),
74
+ typeFrom: typeMap.local,
75
+ typeTo: typeMap.remote,
76
+ fromPath: this.tempPath,
77
+ toPath: this.toPath,
78
+ fromFile,
79
+ tabId: targetTabId,
80
+ host: targetHost,
81
+ title: targetTitle,
82
+ tabType: targetTabType,
83
+ operation: '',
84
+ remote2remoteStep: 2,
85
+ remote2remoteId: this.id,
86
+ originalId: this.step1Transfer?.id
87
+ }
88
+ return transfer
89
+ }
90
+
91
+ start = () => {
92
+ this.tempPath = this.buildTempPath()
93
+ this.step1Transfer = this.buildStep1Transfer()
94
+ this.store.addTransferList([copy(this.step1Transfer)])
95
+ this.startWatch()
96
+ }
97
+
98
+ startWatch = () => {
99
+ this.ref = autoRun(() => {
100
+ this.tick()
101
+ return this.store.transferHistory
102
+ })
103
+ this.ref.start()
104
+ }
105
+
106
+ stopWatch = () => {
107
+ this.ref?.stop()
108
+ this.ref = null
109
+ }
110
+
111
+ tick = async () => {
112
+ const step1 = this.findHistory(this.step1Transfer?.id)
113
+
114
+ if (!this.step2Transfer) {
115
+ if (this.creatingStep2) {
116
+ return
117
+ }
118
+ if (!step1) {
119
+ return
120
+ }
121
+ if (step1.error) {
122
+ return this.finish(step1.error)
123
+ }
124
+ this.creatingStep2 = true
125
+ const localFromFile = await getLocalFileInfo(this.tempPath).catch(() => null)
126
+ if (!localFromFile) {
127
+ this.creatingStep2 = false
128
+ return this.finish('local temp file/folder not found')
129
+ }
130
+ this.step2Transfer = this.buildStep2Transfer(localFromFile)
131
+ this.creatingStep2 = false
132
+ this.store.addTransferList([copy(this.step2Transfer)])
133
+ return
134
+ }
135
+
136
+ const step2 = this.findHistory(this.step2Transfer.id)
137
+ if (!step2) {
138
+ return
139
+ }
140
+
141
+ return this.finish(step2.error)
142
+ }
143
+
144
+ findHistory = (transferId) => {
145
+ if (!transferId) {
146
+ return null
147
+ }
148
+ return this.store.transferHistory.find(item => {
149
+ return item.id === transferId || item.originalId === transferId
150
+ })
151
+ }
152
+
153
+ cleanup = async () => {
154
+ if (!this.tempPath) {
155
+ return
156
+ }
157
+ await fs.rmrf(this.tempPath).catch(() => {})
158
+ }
159
+
160
+ finish = async (error) => {
161
+ if (this.finished) {
162
+ return
163
+ }
164
+ this.finished = true
165
+ this.stopWatch()
166
+ await this.cleanup()
167
+ this.props.onDone?.({
168
+ id: this.id,
169
+ error
170
+ })
171
+ }
172
+
173
+ stop = async () => {
174
+ await this.finish()
175
+ }
176
+ }
@@ -0,0 +1,81 @@
1
+ import { Component } from 'react'
2
+ import resolve from '../../common/resolve'
3
+ import { typeMap } from '../../common/constants'
4
+ import { refsStatic } from '../common/ref'
5
+ import Remote2RemoteHandler from './remote2remote-handler'
6
+
7
+ const handlerRefId = 'remote2remote-handlers'
8
+
9
+ export default class Remote2RemoteHandlers extends Component {
10
+ constructor (props) {
11
+ super(props)
12
+ this.handlers = new Map()
13
+ }
14
+
15
+ componentDidMount () {
16
+ refsStatic.add(handlerRefId, this)
17
+ }
18
+
19
+ componentWillUnmount () {
20
+ refsStatic.remove(handlerRefId)
21
+ this.handlers.forEach(handler => {
22
+ handler.stop()
23
+ })
24
+ this.handlers.clear()
25
+ }
26
+
27
+ canHandle = ({ fromFile, targetHost }) => {
28
+ return fromFile?.type === typeMap.remote &&
29
+ fromFile?.host &&
30
+ targetHost &&
31
+ fromFile.host !== targetHost &&
32
+ fromFile?.tabId
33
+ }
34
+
35
+ createHandler = ({ fromFile, targetPathBase, targetTab }) => {
36
+ const handler = new Remote2RemoteHandler({
37
+ fromFile,
38
+ toPath: resolve(targetPathBase, fromFile.name),
39
+ sourceHost: fromFile.host,
40
+ sourceTabId: fromFile.tabId,
41
+ title: fromFile.title,
42
+ tabType: fromFile.tabType,
43
+ targetHost: targetTab.host,
44
+ targetTabId: targetTab.id,
45
+ targetTitle: targetTab.title || targetTab.host,
46
+ targetTabType: targetTab.type,
47
+ onDone: this.onDone
48
+ })
49
+ this.handlers.set(handler.id, handler)
50
+ handler.start()
51
+ }
52
+
53
+ onDone = ({ id, error }) => {
54
+ this.handlers.delete(id)
55
+ if (error) {
56
+ window.store.onError(new Error(error))
57
+ }
58
+ }
59
+
60
+ onRemote2RemoteDrop = ({ fromFiles, toFile, targetTab }) => {
61
+ const targetPathBase = resolve(toFile.path, toFile.name)
62
+ const targetHost = targetTab?.host
63
+ let handled = false
64
+ for (const fromFile of fromFiles) {
65
+ if (!this.canHandle({ fromFile, targetHost })) {
66
+ continue
67
+ }
68
+ handled = true
69
+ this.createHandler({
70
+ fromFile,
71
+ targetPathBase,
72
+ targetTab
73
+ })
74
+ }
75
+ return handled
76
+ }
77
+
78
+ render () {
79
+ return null
80
+ }
81
+ }
@@ -14,6 +14,7 @@ import Resolutions from '../rdp/resolution-edit'
14
14
  import TerminalInteractive from '../terminal/terminal-interactive'
15
15
  import ConfirmModalStore from '../file-transfer/conflict-resolve.jsx'
16
16
  import TransferQueue from '../file-transfer/transfer-queue'
17
+ import Remote2RemoteHandlers from '../file-transfer/remote2remote-handlers.jsx'
17
18
  import TerminalCmdSuggestions from '../terminal/terminal-command-dropdown'
18
19
  import TransportsActionStore from '../file-transfer/transports-action-store.jsx'
19
20
  import classnames from 'classnames'
@@ -280,6 +281,7 @@ export default auto(function Index (props) {
280
281
  {...conflictStoreProps}
281
282
  config={config}
282
283
  />
284
+ <Remote2RemoteHandlers />
283
285
  <Resolutions {...resProps} />
284
286
  <InfoModal {...infoModalProps} />
285
287
  <RightSidePanel {...rightPanelProps}>
@@ -26,7 +26,7 @@ export default function ProfileFormSsh (props) {
26
26
  {...formItemLayout}
27
27
  label={e('password')}
28
28
  hasFeedback
29
- name={['rdp', 'password']}
29
+ name={['ftp', 'password']}
30
30
  >
31
31
  <Password />
32
32
  </FormItem>
@@ -225,7 +225,21 @@ export default class FileSection extends React.Component {
225
225
  ? onDragCls + ' ' + onMultiDragCls
226
226
  : onDragCls
227
227
  addClass(this.domRef.current, cls)
228
- e.dataTransfer.setData('fromFile', JSON.stringify(this.props.file))
228
+ const transferProps = createTransferProps(this.props)
229
+ const selected = this.isSelected(this.props.file.id)
230
+ const dragFiles = selected
231
+ ? this.props.getSelectedFiles()
232
+ : [this.props.file]
233
+ const filesWithMeta = dragFiles.map(file => {
234
+ return {
235
+ ...file,
236
+ host: this.props.tab?.host,
237
+ tabType: this.props.tab?.type,
238
+ tabId: transferProps.tabId,
239
+ title: transferProps.title
240
+ }
241
+ })
242
+ e.dataTransfer.setData('fromFile', JSON.stringify(filesWithMeta))
229
243
  }
230
244
 
231
245
  getDropFileList = data => {
@@ -283,6 +297,23 @@ export default class FileSection extends React.Component {
283
297
  } = toFile
284
298
 
285
299
  let operation = ''
300
+ const targetHost = this.props.tab?.host
301
+ const isCrossHostRemoteDrop = !fromFileManager &&
302
+ fromType === typeMap.remote &&
303
+ toType === typeMap.remote &&
304
+ fromFiles.every(file => file?.host && file.host !== targetHost)
305
+
306
+ if (isCrossHostRemoteDrop) {
307
+ const handled = refsStatic.get('remote2remote-handlers')?.onRemote2RemoteDrop({
308
+ fromFiles,
309
+ toFile,
310
+ targetTab: this.props.tab
311
+ })
312
+ if (handled) {
313
+ return
314
+ }
315
+ }
316
+
286
317
  // same side and drop to file = drop to folder
287
318
  if (!fromFileManager && fromType === toType && !isDirectoryTo) {
288
319
  return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "2.12.0",
3
+ "version": "2.13.0",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",