@electerm/electerm-react 1.72.48 → 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 (48) hide show
  1. package/client/common/constants.js +1 -1
  2. package/client/common/sftp.js +3 -1
  3. package/client/components/ai/ai-config.jsx +7 -1
  4. package/client/components/batch-op/batch-op.jsx +4 -5
  5. package/client/components/bg/css-overwrite.jsx +179 -0
  6. package/client/components/bg/shapes.js +501 -0
  7. package/client/components/bookmark-form/form-tabs.jsx +1 -0
  8. package/client/components/bookmark-form/local-form-ui.jsx +7 -1
  9. package/client/components/bookmark-form/render-bg.jsx +43 -0
  10. package/client/components/bookmark-form/serial-form-ui.jsx +7 -1
  11. package/client/components/bookmark-form/ssh-form-ui.jsx +14 -3
  12. package/client/components/bookmark-form/telnet-form-ui.jsx +7 -1
  13. package/client/components/bookmark-form/use-ui.jsx +68 -72
  14. package/client/components/common/ref.js +2 -0
  15. package/client/components/{sftp/confirm-modal-store.jsx → file-transfer/conflict-resolve.jsx} +86 -48
  16. package/client/components/file-transfer/transfer-queue.jsx +151 -0
  17. package/client/components/file-transfer/transfer.jsx +582 -0
  18. package/client/components/{sftp → file-transfer}/transports-action-store.jsx +35 -32
  19. package/client/components/{sftp → file-transfer}/transports-ui-store.jsx +2 -2
  20. package/client/components/main/main.jsx +25 -18
  21. package/client/components/main/wrapper.styl +16 -0
  22. package/client/components/profile/profile-list.jsx +1 -1
  23. package/client/components/quick-commands/qm.styl +4 -1
  24. package/client/components/quick-commands/quick-commands-list.jsx +16 -4
  25. package/client/components/setting-panel/list.jsx +1 -1
  26. package/client/components/setting-panel/setting-terminal.jsx +2 -1
  27. package/client/components/setting-panel/terminal-bg-config.jsx +15 -2
  28. package/client/components/sftp/file-info-modal.jsx +3 -0
  29. package/client/components/sftp/file-item.jsx +25 -23
  30. package/client/components/sftp/file-read.js +1 -27
  31. package/client/components/sftp/list-table-ui.jsx +2 -2
  32. package/client/components/sidebar/transfer-history-modal.jsx +1 -1
  33. package/client/components/sidebar/transfer-list-control.jsx +1 -1
  34. package/client/components/sidebar/transport-ui.jsx +16 -9
  35. package/client/components/terminal/terminal.jsx +23 -1
  36. package/client/components/text-editor/simple-editor.jsx +164 -0
  37. package/client/components/text-editor/text-editor-form.jsx +6 -9
  38. package/client/css/includes/box.styl +2 -2
  39. package/client/store/tab.js +5 -1
  40. package/client/store/transfer-list.js +10 -51
  41. package/package.json +1 -1
  42. package/client/components/main/css-overwrite.jsx +0 -91
  43. package/client/components/sftp/transfer-conflict-store.jsx +0 -284
  44. package/client/components/sftp/transport-action-store.jsx +0 -422
  45. package/client/components/sftp/zip.js +0 -42
  46. /package/client/components/{main → bg}/custom-css.jsx +0 -0
  47. /package/client/components/{sftp → file-transfer}/transfer-speed-format.js +0 -0
  48. /package/client/components/{sftp → file-transfer}/transfer.styl +0 -0
@@ -0,0 +1,164 @@
1
+ import React, { useState, useEffect, useRef } from 'react'
2
+ import { Input, Button, Flex } from 'antd'
3
+ import {
4
+ ArrowUpOutlined,
5
+ ArrowDownOutlined,
6
+ SearchOutlined,
7
+ CopyOutlined
8
+ } from '@ant-design/icons'
9
+ import { copy } from '../../common/clipboard'
10
+
11
+ export default function SimpleEditor (props) {
12
+ const [searchKeyword, setSearchKeyword] = useState('')
13
+ const [occurrences, setOccurrences] = useState([])
14
+ const [currentMatch, setCurrentMatch] = useState(-1)
15
+ const [isNavigating, setIsNavigating] = useState(false)
16
+ const editorRef = useRef(null)
17
+
18
+ // When currentMatch changes, highlight the match in textarea
19
+ useEffect(() => {
20
+ if (currentMatch >= 0 && occurrences.length > 0) {
21
+ const match = occurrences[currentMatch]
22
+ if (editorRef.current) {
23
+ // Set selection range to select the matched text
24
+ editorRef.current.resizableTextArea.textArea.setSelectionRange(match.start, match.end)
25
+
26
+ // Only focus the textarea when explicitly navigating between matches
27
+ if (isNavigating) {
28
+ editorRef.current.resizableTextArea.textArea.focus()
29
+ }
30
+
31
+ // Scroll to the selection position
32
+ const textarea = editorRef.current.resizableTextArea.textArea
33
+ const textBeforeSelection = props.value.substring(0, match.start)
34
+ const lineBreaks = textBeforeSelection.split('\n').length - 1
35
+
36
+ // Estimate the scroll position
37
+ const lineHeight = 20 // Approximate line height in pixels
38
+ const scrollPosition = lineHeight * lineBreaks
39
+
40
+ textarea.scrollTop = Math.max(0, scrollPosition - textarea.clientHeight / 2)
41
+ }
42
+ }
43
+ // Reset navigating flag after using it
44
+ setIsNavigating(false)
45
+ }, [currentMatch, occurrences])
46
+ // Copy the editor content to clipboard
47
+ const copyEditorContent = () => {
48
+ copy(props.value || '')
49
+ }
50
+
51
+ // Find all matches of the search keyword in text
52
+ const findMatches = () => {
53
+ if (!searchKeyword) {
54
+ setOccurrences([])
55
+ setCurrentMatch(-1)
56
+ return
57
+ }
58
+
59
+ const matches = []
60
+ const text = props.value || ''
61
+ const regex = new RegExp(searchKeyword, 'gi')
62
+ let match
63
+
64
+ while ((match = regex.exec(text)) !== null) {
65
+ matches.push({
66
+ start: match.index,
67
+ end: match.index + searchKeyword.length
68
+ })
69
+ }
70
+ setOccurrences(matches)
71
+ setCurrentMatch(matches.length ? 0 : -1)
72
+ }
73
+
74
+ // Handle search action when user presses enter or clicks the search button
75
+ const handleSearch = (e) => {
76
+ e.stopPropagation()
77
+ e.preventDefault()
78
+ findMatches()
79
+ }
80
+
81
+ // Navigate to next match
82
+ const goToNextMatch = () => {
83
+ setIsNavigating(true)
84
+ if (currentMatch < occurrences.length - 1) {
85
+ setCurrentMatch(currentMatch + 1)
86
+ } else {
87
+ setCurrentMatch(0) // Loop back to first match
88
+ }
89
+ }
90
+
91
+ // Navigate to previous match
92
+ const goToPrevMatch = () => {
93
+ setIsNavigating(true)
94
+ if (currentMatch > 0) {
95
+ setCurrentMatch(currentMatch - 1)
96
+ } else {
97
+ setCurrentMatch(occurrences.length - 1) // Loop to last match
98
+ }
99
+ }
100
+
101
+ // Render navigation buttons for search results
102
+ const renderNavigationButtons = () => {
103
+ if (occurrences.length === 0) {
104
+ return null
105
+ }
106
+ return (
107
+ <>
108
+ <Button onClick={goToPrevMatch}>
109
+ <ArrowUpOutlined />
110
+ </Button>
111
+ <Button onClick={goToNextMatch}>
112
+ <ArrowDownOutlined />
113
+ </Button>
114
+ </>
115
+ )
116
+ }
117
+
118
+ // Render search results counter
119
+ const renderSearchCounter = () => {
120
+ return occurrences.length
121
+ ? `${currentMatch + 1}/${occurrences.length}`
122
+ : '0/0'
123
+ }
124
+
125
+ function renderAfter () {
126
+ return (
127
+ <>
128
+ <b className='pd1x'>{renderSearchCounter()}</b>
129
+ {renderNavigationButtons()}
130
+ </>
131
+ )
132
+ }
133
+
134
+ return (
135
+ <div>
136
+ <Flex className='mg1b' justify='space-between'>
137
+ <Input.Search
138
+ value={searchKeyword}
139
+ onChange={e => setSearchKeyword(e.target.value)}
140
+ placeholder='Search in text...'
141
+ allowClear
142
+ enterButton={<SearchOutlined />}
143
+ onSearch={handleSearch}
144
+ onPressEnter={handleSearch}
145
+ addonAfter={renderAfter()}
146
+ style={{ width: 'auto' }}
147
+ />
148
+ <Button
149
+ onClick={copyEditorContent}
150
+ className='mg3l'
151
+ >
152
+ <CopyOutlined />
153
+ </Button>
154
+ </Flex>
155
+ <Input.TextArea
156
+ ref={editorRef}
157
+ value={props.value}
158
+ onChange={props.onChange}
159
+ rows={20}
160
+ />
161
+ </div>
162
+
163
+ )
164
+ }
@@ -3,7 +3,8 @@
3
3
  */
4
4
 
5
5
  import { useEffect } from 'react'
6
- import { Input, Form, Button } from 'antd'
6
+ import { Form, Button } from 'antd'
7
+ import SimpleEditor from './simple-editor'
7
8
 
8
9
  const FormItem = Form.Item
9
10
  const e = window.translate
@@ -23,9 +24,9 @@ export default function TextEditorForm (props) {
23
24
  props.submit(res)
24
25
  }
25
26
 
26
- function onPressEnter (e) {
27
- e.stopPropagation()
28
- }
27
+ // function onPressEnter (e) {
28
+ // e.stopPropagation()
29
+ // }
29
30
 
30
31
  function reset () {
31
32
  form.resetFields()
@@ -58,11 +59,7 @@ export default function TextEditorForm (props) {
58
59
  <FormItem
59
60
  name='text'
60
61
  >
61
- <Input.TextArea
62
- rows={20}
63
- onPressEnter={onPressEnter}
64
- >{text}
65
- </Input.TextArea>
62
+ <SimpleEditor />
66
63
  </FormItem>
67
64
  <div className='pd1t pd2b'>
68
65
  <Button
@@ -1,10 +1,10 @@
1
1
 
2
2
  for $i, $index in 5 16 32
3
-
3
+
4
4
  //padding
5
5
  .pd{$index+1}
6
6
  padding ($i)px
7
-
7
+
8
8
  .pd{$index+1}x
9
9
  padding-left ($i)px
10
10
  padding-right ($i)px
@@ -467,7 +467,11 @@ export default Store => {
467
467
  'status',
468
468
  'pane',
469
469
  'batch',
470
- 'tabCount'
470
+ 'tabCount',
471
+ 'sftpCreated',
472
+ 'sshSftpSplitView',
473
+ 'sshTunnelResults',
474
+ 'displayRaw'
471
475
  ]
472
476
  const { history } = store
473
477
  const index = history.findIndex(d => {
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * file transfer list related functions
3
3
  */
4
+
5
+ import uid from '../common/uid'
6
+
4
7
  const { assign } = Object
5
8
 
6
9
  export default Store => {
@@ -18,17 +21,13 @@ export default Store => {
18
21
  }
19
22
 
20
23
  Store.prototype.addTransferList = function (items) {
24
+ // console.log('addTransferList', JSON.stringify(items, null, 2))
21
25
  const { fileTransfers } = window.store
22
- fileTransfers.push(...items)
23
- }
24
-
25
- Store.prototype.toggleTransfer = function (itemId) {
26
- const { fileTransfers } = window.store
27
- const index = fileTransfers.findIndex(t => t.id === itemId)
28
- if (index < 0) {
29
- return
30
- }
31
- fileTransfers[index].pausing = !fileTransfers[index].pausing
26
+ const transferBatch = uid()
27
+ fileTransfers.push(...items.map(t => {
28
+ t.transferBatch = transferBatch
29
+ return t
30
+ }))
32
31
  }
33
32
 
34
33
  Store.prototype.pauseAll = function () {
@@ -50,46 +49,6 @@ export default Store => {
50
49
  }
51
50
 
52
51
  Store.prototype.cancelAll = function () {
53
- const { fileTransfers } = window.store
54
- const len = fileTransfers.length
55
- for (let i = len - 1; i >= 0; i--) {
56
- fileTransfers[i].cancel = true
57
- fileTransfers.splice(i, 1)
58
- }
59
- }
60
-
61
- Store.prototype.cancelTransfer = function (itemId) {
62
- const { fileTransfers } = window.store
63
- const index = fileTransfers.findIndex(t => t.id === itemId)
64
- if (index < 0) {
65
- return
66
- }
67
- fileTransfers[index].cancel = true
68
- fileTransfers.splice(index, 1)
69
- }
70
-
71
- Store.prototype.skipAllTransfersSinceIndex = function (index) {
72
- window.store.fileTransfers.splice(index)
73
- }
74
-
75
- Store.prototype.updateTransfersFromIndex = function (index, update) {
76
- const { fileTransfers } = window.store
77
- if (index < 0 || index >= fileTransfers.length) {
78
- return
79
- }
80
- const len = fileTransfers.length
81
- for (let i = index; i < len; i++) {
82
- assign(fileTransfers[i], update)
83
- }
84
- }
85
-
86
- // Add a new method to find index by ID and then update
87
- Store.prototype.updateTransfersFromId = function (id, update) {
88
- const { fileTransfers } = window.store
89
- const index = fileTransfers.findIndex(t => t.id === id)
90
- if (index < 0) {
91
- return
92
- }
93
- window.store.updateTransfersFromIndex(index, update)
52
+ window.store.fileTransfers = []
94
53
  }
95
54
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "1.72.48",
3
+ "version": "1.80.2",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",
@@ -1,91 +0,0 @@
1
- /**
2
- * btns
3
- */
4
-
5
- import { PureComponent } from 'react'
6
- import fs from '../../common/fs'
7
- import { noTerminalBgValue } from '../../common/constants'
8
-
9
- export default class CssOverwrite extends PureComponent {
10
- // componentDidMount () {
11
- // setTimeout(this.writeCss, 3000)
12
- // }
13
-
14
- componentDidUpdate (prevProps) {
15
- Object.keys(this.props).some(key => {
16
- if (key.startsWith('terminalBackground') && prevProps[key] !== this.props[key]) {
17
- this.updateCss()
18
- return true
19
- }
20
- return false
21
- })
22
- if (!prevProps.wsInited && this.props.wsInited) {
23
- this.writeCss()
24
- }
25
- }
26
-
27
- createStyle = async () => {
28
- const { terminalBackgroundImagePath } = this.props
29
- let content = ''
30
- let st = ''
31
- const isWebImg = /^https?:\/\//.test(terminalBackgroundImagePath)
32
- if (noTerminalBgValue === terminalBackgroundImagePath) {
33
- st = 'none'
34
- } else if (terminalBackgroundImagePath && !isWebImg) {
35
- content = await fs.readFileAsBase64(terminalBackgroundImagePath)
36
- .catch(log.error)
37
- if (content) {
38
- st = `url(data:image;base64,${content}) !important`
39
- }
40
- } else if (terminalBackgroundImagePath && isWebImg) {
41
- st = `url(${terminalBackgroundImagePath}) !important`
42
- }
43
- if (!st) {
44
- return `#container .session-batch-active .xterm-screen::before {
45
- background-image: url("./images/electerm-watermark.png");
46
- }`
47
- }
48
-
49
- const styles = [
50
- `background-image: ${st}`,
51
- 'background-position: center'
52
- ]
53
-
54
- if (st !== 'none') {
55
- styles.push(`filter: blur(${
56
- this.props.terminalBackgroundFilterBlur
57
- }px) opacity(${
58
- +this.props.terminalBackgroundFilterOpacity
59
- }) brightness(${
60
- +this.props.terminalBackgroundFilterBrightness
61
- }) contrast(${
62
- +this.props.terminalBackgroundFilterContrast
63
- }) grayscale(${
64
- +this.props.terminalBackgroundFilterGrayscale
65
- })`)
66
- }
67
-
68
- return `#container .session-batch-active .xterm-screen::before {
69
- ${styles.join(';')}
70
- }`
71
- }
72
-
73
- writeCss = async () => {
74
- const style = document.createElement('style')
75
- style.type = 'text/css'
76
- style.innerHTML = await this.createStyle()
77
- style.id = 'css-overwrite'
78
- document.getElementsByTagName('head')[0].appendChild(style)
79
- }
80
-
81
- updateCss = async () => {
82
- const style = document.getElementById('css-overwrite')
83
- if (style) {
84
- style.innerHTML = await this.createStyle()
85
- }
86
- }
87
-
88
- render () {
89
- return null
90
- }
91
- }
@@ -1,284 +0,0 @@
1
- /**
2
- * pass transfer list from props
3
- * when list changes, do transfer and other op
4
- */
5
-
6
- import { PureComponent } from 'react'
7
- import { typeMap } from '../../common/constants'
8
- import {
9
- getLocalFileInfo,
10
- getRemoteFileInfo,
11
- getFolderFromFilePath,
12
- getFileExt,
13
- checkFolderSize
14
- } from './file-read'
15
- import { refsStatic, refs } from '../common/ref'
16
- import generate from '../../common/uid'
17
- import resolve from '../../common/resolve'
18
- import deepCopy from 'json-deep-copy'
19
-
20
- const { assign } = Object
21
-
22
- export default class TransferConflictStore extends PureComponent {
23
- state = {
24
- currentId: ''
25
- }
26
-
27
- componentDidMount () {
28
- this.id = 'transfer-conflict'
29
- refsStatic.add(this.id, this)
30
- this.watchFile()
31
- }
32
-
33
- componentDidUpdate (prevProps) {
34
- if (
35
- prevProps.fileTransferChanged !== this.props.fileTransferChanged
36
- ) {
37
- this.watchFile()
38
- }
39
- }
40
-
41
- localCheckExist = (path) => {
42
- return getLocalFileInfo(path).catch(console.log)
43
- }
44
-
45
- remoteCheckExist = (path, sessionId) => {
46
- const sftp = refs.get('sftp-' + sessionId).sftp
47
- return getRemoteFileInfo(sftp, path)
48
- .then(r => r)
49
- .catch(() => false)
50
- }
51
-
52
- checkExist = (type, path, sessionId) => {
53
- return this[type + 'CheckExist'](path, sessionId)
54
- }
55
-
56
- rename = (tr, action, _renameId) => {
57
- const isRemote = tr.typeTo === typeMap.remote
58
- const { path, name } = getFolderFromFilePath(tr.toPath, isRemote)
59
- const { base, ext } = getFileExt(name)
60
- const renameId = _renameId || generate()
61
- const newName = ext
62
- ? `${base}(rename-${renameId}).${ext}`
63
- : `${base}(rename-${renameId})`
64
- assign(tr, {
65
- renameId,
66
- newName,
67
- oldName: base,
68
- toPath: resolve(path, newName)
69
- })
70
- if (action) {
71
- tr.action = action
72
- }
73
- return tr
74
- }
75
-
76
- updateTransferAction = (data) => {
77
- const {
78
- id,
79
- action,
80
- transfer
81
- } = data
82
- const {
83
- fromFile
84
- } = transfer
85
- this.clear()
86
-
87
- const { store } = window
88
- const { fileTransfers } = store
89
- const index = fileTransfers.findIndex(d => d.id === id)
90
- if (index < 0) {
91
- return
92
- }
93
- const tr = fileTransfers[index]
94
- tr.fromFile = deepCopy(fromFile)
95
- tr.action = action
96
- tr.r = Math.random()
97
- if (action === 'skip') {
98
- return fileTransfers.splice(index, 1)
99
- } else if (action === 'cancel') {
100
- return store.skipAllTransfersSinceIndex(index)
101
- }
102
- if (action.includes('All')) {
103
- return store.updateTransfersFromIndex(index, {
104
- action: action.replace('All', '')
105
- })
106
- }
107
- if (action.includes('rename')) {
108
- return this.rename(tr)
109
- }
110
- }
111
-
112
- tagTransferError = (id, errorMsg) => {
113
- this.clear()
114
- const { store } = window
115
- const { fileTransfers } = store
116
- const index = fileTransfers.findIndex(d => d.id === id)
117
- if (index < 0) {
118
- return
119
- }
120
-
121
- const [tr] = fileTransfers.splice(index, 1)
122
- assign(tr, {
123
- host: tr.host,
124
- error: errorMsg,
125
- finishTime: Date.now()
126
- })
127
- store.addTransferHistory(tr)
128
- }
129
-
130
- setConflict (tr) {
131
- if (this.props.transferToConfirm.id) {
132
- return
133
- }
134
- window.store.transferToConfirm = tr
135
- }
136
-
137
- onDecision = (data) => {
138
- if (
139
- data.id === this.currentId
140
- ) {
141
- this.currentId = ''
142
- this.updateTransferAction(data)
143
- this.onConfirm = false
144
- window.removeEventListener('message', this.onDecision)
145
- }
146
- }
147
-
148
- updateData = () => {
149
- const {
150
- store
151
- } = window
152
- const {
153
- fileTransfers
154
- } = store
155
- if (fileTransfers.length > 0) {
156
- fileTransfers[0].r = Math.random()
157
- }
158
- }
159
-
160
- setCanTransfer = (fromFile, tr) => {
161
- this.clear()
162
- const {
163
- store
164
- } = window
165
- const {
166
- fileTransfers
167
- } = store
168
- const index = fileTransfers.findIndex(t => {
169
- return t.id === tr.id
170
- })
171
- if (index < 0) {
172
- setTimeout(this.updateData, 0)
173
- return
174
- }
175
- const up = {
176
- action: 'transfer',
177
- fromFile
178
- }
179
- assign(fileTransfers[index], up)
180
- // may have issue
181
- }
182
-
183
- clear = () => {
184
- this.currentId = ''
185
- }
186
-
187
- watchFile = async () => {
188
- const { store } = window
189
- const {
190
- fileTransfers
191
- } = store
192
- if (!fileTransfers.length) {
193
- return this.clear()
194
- }
195
- const tr = fileTransfers
196
- .find(t => {
197
- return (
198
- !t.action ||
199
- !t.fromFile ||
200
- t.fromFile.isDirectory
201
- )
202
- })
203
- if (!tr) {
204
- this.onConfirm = false
205
- return this.clear()
206
- }
207
- if (this.currentId) {
208
- return
209
- }
210
- this.currentId = tr.id
211
- const {
212
- typeFrom,
213
- typeTo,
214
- fromPath,
215
- toPath,
216
- id,
217
- action,
218
- renameId,
219
- parentId,
220
- skipConfirm,
221
- sessionId
222
- } = tr
223
- const fromFile = tr.fromFile
224
- ? tr.fromFile
225
- : await this.checkExist(typeFrom, fromPath, sessionId)
226
- if (!fromFile) {
227
- return this.tagTransferError(id, 'file not exist')
228
- }
229
- let toFile = false
230
- if (renameId || parentId) {
231
- toFile = false
232
- } else if (fromPath === toPath && typeFrom === typeTo) {
233
- toFile = true
234
- } else {
235
- toFile = await this.checkExist(typeTo, toPath, sessionId)
236
- }
237
- if (fromFile.isDirectory && typeFrom !== typeTo) {
238
- const props = {
239
- sftp: refs.get('sftp-' + sessionId).sftp
240
- }
241
- const skip = await checkFolderSize(props, fromFile)
242
- .then(d => d && typeFrom !== typeTo)
243
- if (!skip) {
244
- return this.tagTransferError(id, 'folder too big or too many files in folder')
245
- }
246
- tr.zip = true
247
- tr.skipExpand = true
248
- }
249
- if (fromPath === toPath && typeFrom === typeTo) {
250
- assign(tr, {
251
- operation: 'cp',
252
- fromFile
253
- })
254
- return this.updateTransferAction({
255
- id,
256
- action: 'rename',
257
- transfer: tr
258
- })
259
- } else if (toFile && !action && !skipConfirm) {
260
- if (!this.onConfirm) {
261
- this.onConfirm = true
262
- assign(tr, {
263
- fromFile,
264
- toFile
265
- })
266
- return this.setConflict(tr)
267
- }
268
- } else if (toFile && !tr.fromFile && action) {
269
- assign(tr, {
270
- fromFile
271
- })
272
- return this.updateTransferAction({
273
- id,
274
- action,
275
- transfer: tr
276
- })
277
- }
278
- this.setCanTransfer(fromFile, tr)
279
- }
280
-
281
- render () {
282
- return null
283
- }
284
- }