@electerm/electerm-react 3.6.6 → 3.7.9

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.
@@ -370,7 +370,12 @@ function parseQuickConnect (str) {
370
370
  opts.port = parseInt(port, 10)
371
371
  }
372
372
  if (username !== undefined && username !== '') {
373
- opts.username = username
373
+ // FTP form uses 'user' instead of 'username'
374
+ if (finalProtocol === 'ftp') {
375
+ opts.user = username
376
+ } else {
377
+ opts.username = username
378
+ }
374
379
  }
375
380
  if (password !== undefined && password !== '') {
376
381
  opts.password = password
@@ -80,8 +80,18 @@ export default function AIBookmarkForm (props) {
80
80
 
81
81
  let bookmarkData
82
82
  if (aiResponse && aiResponse.response) {
83
+ // Normalize response payload to string before JSON extraction.
84
+ const rawResponse = aiResponse.response
85
+ let jsonStr = ''
86
+ if (typeof rawResponse === 'string') {
87
+ jsonStr = rawResponse
88
+ } else if (typeof rawResponse === 'object') {
89
+ jsonStr = JSON.stringify(rawResponse)
90
+ } else {
91
+ jsonStr = String(rawResponse)
92
+ }
83
93
  // Parse the JSON response
84
- let jsonStr = aiResponse.response.trim()
94
+ jsonStr = jsonStr.trim()
85
95
  // Remove markdown code blocks if present
86
96
  if (jsonStr.startsWith('```json')) {
87
97
  jsonStr = jsonStr.slice(7)
@@ -93,7 +103,7 @@ export default function AIBookmarkForm (props) {
93
103
  }
94
104
  jsonStr = jsonStr.trim()
95
105
 
96
- bookmarkData = JSON.parse(jsonStr)
106
+ bookmarkData = getGeneratedData(jsonStr)
97
107
  const pretty = JSON.stringify(bookmarkData, null, 2)
98
108
  setEditorText(pretty)
99
109
  // set default category when preview opens
@@ -103,22 +113,23 @@ export default function AIBookmarkForm (props) {
103
113
  }
104
114
  } catch (error) {
105
115
  console.error('AI bookmark generation error:', error)
106
- message.error(e('aiGenerateError') || 'AI generation failed: ' + error.message)
116
+ message.error('Can not generate bookmarks from AI response: ' + error.message)
107
117
  } finally {
108
118
  setLoading(false)
109
119
  }
110
120
  }
111
121
 
112
- function getGeneratedData () {
113
- if (!editorText) return []
122
+ function getGeneratedData (txt = editorText) {
123
+ if (!txt) return []
114
124
  let parsed = null
115
125
  try {
116
- parsed = fixBookmarkData(JSON.parse(editorText))
126
+ parsed = JSON.parse(txt)
117
127
  } catch (err) {
118
128
  return []
119
129
  }
120
130
  if (!parsed) return []
121
- return Array.isArray(parsed) ? parsed : [parsed]
131
+ const arr = Array.isArray(parsed) ? parsed : [parsed]
132
+ return arr.map(d => fixBookmarkData(d))
122
133
  }
123
134
 
124
135
  const createBookmark = async (bm) => {
@@ -228,6 +239,7 @@ export default function AIBookmarkForm (props) {
228
239
  }
229
240
  return (
230
241
  <SimpleEditor
242
+ key='editor'
231
243
  {...editorProps}
232
244
  />
233
245
  )
@@ -238,7 +250,7 @@ export default function AIBookmarkForm (props) {
238
250
  return renderEditor()
239
251
  }
240
252
  return (
241
- <pre className='ai-bookmark-json-preview'>
253
+ <pre key='preview' className='ai-bookmark-json-preview'>
242
254
  {editorText}
243
255
  </pre>
244
256
  )
@@ -17,17 +17,13 @@ export default function renderProxy (props) {
17
17
  }
18
18
  return prev
19
19
  }, {})
20
- const opts = {
21
- options: Object.keys(proxyTree)
22
- .map(d => {
23
- return {
24
- label: d,
25
- value: d
26
- }
27
- }),
28
- placeholder: 'socks5://127.0.0.1:1080',
29
- allowClear: true
30
- }
20
+ const options = Object.keys(proxyTree)
21
+ .map(d => {
22
+ return {
23
+ label: d,
24
+ value: d
25
+ }
26
+ })
31
27
  return (
32
28
  <FormItem
33
29
  {...formItemLayout}
@@ -38,10 +34,8 @@ export default function renderProxy (props) {
38
34
  max: 1024, message: '1024 chars max'
39
35
  }]}
40
36
  >
41
- <AutoComplete
42
- {...opts}
43
- >
44
- <Input />
37
+ <AutoComplete options={options}>
38
+ <Input allowClear placeholder='socks5://127.0.0.1:1080' />
45
39
  </AutoComplete>
46
40
  </FormItem>
47
41
  )
@@ -0,0 +1,183 @@
1
+ import {
2
+ Form,
3
+ Input,
4
+ InputNumber,
5
+ Radio,
6
+ Space,
7
+ Button,
8
+ Tooltip
9
+ } from 'antd'
10
+ import {
11
+ PlusOutlined,
12
+ QuestionCircleOutlined,
13
+ SaveOutlined,
14
+ UserOutlined
15
+ } from '@ant-design/icons'
16
+ import { formItemLayout, tailFormItemLayout } from '../../../common/form-layout'
17
+ import { useState } from 'react'
18
+
19
+ const FormItem = Form.Item
20
+ const {
21
+ Button: RadioButton,
22
+ Group: RadioGroup
23
+ } = Radio
24
+ const e = window.translate
25
+
26
+ export default function SshTunnelForm (props) {
27
+ const {
28
+ formChild,
29
+ initialValues,
30
+ onFinish,
31
+ isEdit
32
+ } = props
33
+
34
+ const [isDynamic, setIsDynamic] = useState(
35
+ (initialValues?.sshTunnel || 'forwardRemoteToLocal') === 'dynamicForward'
36
+ )
37
+
38
+ function onChange (ev) {
39
+ setIsDynamic(ev.target.value === 'dynamicForward')
40
+ }
41
+
42
+ function onSubmit () {
43
+ formChild.submit()
44
+ }
45
+
46
+ function renderSshTunnelFlow (direction) {
47
+ const localToRemote = direction === 'localToRemote'
48
+ const middle = localToRemote ? e('local') : e('remote')
49
+ const last = localToRemote ? e('remote') : e('local')
50
+ return (
51
+ <div>
52
+ <p>{e(direction)}</p>
53
+ <p><UserOutlined /> → {middle} → {last}</p>
54
+ </div>
55
+ )
56
+ }
57
+
58
+ function renderDynamicForward () {
59
+ return (
60
+ <p><UserOutlined /> → socks proxy → url</p>
61
+ )
62
+ }
63
+
64
+ function renderRemote () {
65
+ if (isDynamic) {
66
+ return null
67
+ }
68
+ return (
69
+ <FormItem
70
+ label={e('remote')}
71
+ {...formItemLayout}
72
+ required
73
+ className='ssh-tunnels-host'
74
+ >
75
+ <Space.Compact>
76
+ <FormItem
77
+ name='sshTunnelRemoteHost'
78
+ label=''
79
+ required
80
+ >
81
+ <Input
82
+ placeholder={e('host')}
83
+ />
84
+ </FormItem>
85
+ <FormItem
86
+ label=''
87
+ name='sshTunnelRemotePort'
88
+ required
89
+ >
90
+ <InputNumber
91
+ min={1}
92
+ max={65535}
93
+ placeholder={e('port')}
94
+ />
95
+ </FormItem>
96
+ </Space.Compact>
97
+ </FormItem>
98
+ )
99
+ }
100
+
101
+ return (
102
+ <Form
103
+ form={formChild}
104
+ onFinish={onFinish}
105
+ initialValues={initialValues}
106
+ component='div'
107
+ >
108
+ <FormItem
109
+ label={e('sshTunnel')}
110
+ name='sshTunnel'
111
+ {...formItemLayout}
112
+ required
113
+ >
114
+ <RadioGroup onChange={onChange}>
115
+ <RadioButton value='forwardRemoteToLocal'>
116
+ <Tooltip title={renderSshTunnelFlow('remoteToLocal')}>
117
+ <span>R→L <QuestionCircleOutlined /></span>
118
+ </Tooltip>
119
+ </RadioButton>
120
+ <RadioButton value='forwardLocalToRemote'>
121
+ <Tooltip title={renderSshTunnelFlow('localToRemote')}>
122
+ <span>L→R <QuestionCircleOutlined /></span>
123
+ </Tooltip>
124
+ </RadioButton>
125
+ <RadioButton value='dynamicForward'>
126
+ <Tooltip title={renderDynamicForward()}>
127
+ <span>{e('dynamicForward')}(socks proxy) <QuestionCircleOutlined /></span>
128
+ </Tooltip>
129
+ </RadioButton>
130
+ </RadioGroup>
131
+ </FormItem>
132
+ {renderRemote()}
133
+ <FormItem
134
+ label={e('local')}
135
+ {...formItemLayout}
136
+ required
137
+ className='ssh-tunnels-host'
138
+ >
139
+ <Space.Compact>
140
+ <FormItem
141
+ name='sshTunnelLocalHost'
142
+ label=''
143
+ required
144
+ >
145
+ <Input
146
+ placeholder={e('host')}
147
+ />
148
+ </FormItem>
149
+ <FormItem
150
+ label=''
151
+ name='sshTunnelLocalPort'
152
+ required
153
+ >
154
+ <InputNumber
155
+ min={1}
156
+ max={65535}
157
+ placeholder={e('port')}
158
+ />
159
+ </FormItem>
160
+ </Space.Compact>
161
+ </FormItem>
162
+ <FormItem
163
+ name='name'
164
+ label={e('name')}
165
+ {...formItemLayout}
166
+ >
167
+ <Input
168
+ placeholder={e('name')}
169
+ />
170
+ </FormItem>
171
+ <FormItem {...tailFormItemLayout} className='mg60b'>
172
+ <Button
173
+ type='default'
174
+ htmlType='button'
175
+ icon={isEdit ? <SaveOutlined /> : <PlusOutlined />}
176
+ onClick={onSubmit}
177
+ >
178
+ {isEdit ? e('save') : e('sshTunnel')}
179
+ </Button>
180
+ </FormItem>
181
+ </Form>
182
+ )
183
+ }
@@ -1,46 +1,37 @@
1
1
  import {
2
2
  Form,
3
3
  Input,
4
- InputNumber,
5
- Radio,
6
- Space,
7
- Button,
8
- Tooltip,
9
4
  Table
10
5
  } from 'antd'
11
6
  import { useState } from 'react'
12
- import { PlusOutlined, QuestionCircleOutlined, MinusCircleFilled, UserOutlined } from '@ant-design/icons'
13
- import { formItemLayout, tailFormItemLayout } from '../../../common/form-layout'
7
+ import { MinusCircleFilled, EditOutlined } from '@ant-design/icons'
8
+ import { tailFormItemLayout } from '../../../common/form-layout'
14
9
  import uid from '../../../common/uid'
10
+ import Modal from '../../common/modal'
11
+ import SshTunnelForm from './ssh-tunnel-form'
15
12
 
16
13
  const FormItem = Form.Item
17
- const {
18
- Button: RadioButton,
19
- Group: RadioGroup
20
- } = Radio
21
14
  const e = window.translate
22
15
 
16
+ const defaultInitialValues = {
17
+ sshTunnel: 'forwardRemoteToLocal',
18
+ sshTunnelLocalPort: 12200,
19
+ sshTunnelLocalHost: '127.0.0.1',
20
+ sshTunnelRemotePort: 12300,
21
+ sshTunnelRemoteHost: '127.0.0.1'
22
+ }
23
+
23
24
  export default function renderSshTunnels (props) {
24
25
  const {
25
26
  form,
26
27
  formData
27
28
  } = props
28
29
  const [formChild] = Form.useForm()
29
- const [initialValues] = useState({
30
- sshTunnel: 'forwardRemoteToLocal',
31
- sshTunnelLocalPort: 12200,
32
- sshTunnelLocalHost: '127.0.0.1',
33
- sshTunnelRemotePort: 12300,
34
- sshTunnelRemoteHost: '127.0.0.1'
35
- })
36
- const [isDynamic, setter] = useState(formData.sshTunnel === 'dynamicForward')
30
+ const [editFormChild] = Form.useForm()
31
+ const [initialValues] = useState(defaultInitialValues)
32
+ const [editModalVisible, setEditModalVisible] = useState(false)
33
+ const [editingItem, setEditingItem] = useState(null)
37
34
  const [list, setList] = useState(formData.sshTunnels || [])
38
- function onSubmit () {
39
- formChild.submit()
40
- }
41
- function onChange (e) {
42
- setter(e.target.value === 'dynamicForward')
43
- }
44
35
  function handleFinish (data) {
45
36
  const nd = {
46
37
  ...data,
@@ -56,7 +47,7 @@ export default function renderSshTunnels (props) {
56
47
  setList(old => {
57
48
  return [
58
49
  ...old,
59
- data
50
+ nd
60
51
  ]
61
52
  })
62
53
  formChild.resetFields()
@@ -72,6 +63,38 @@ export default function renderSshTunnels (props) {
72
63
  })
73
64
  formChild.resetFields()
74
65
  }
66
+
67
+ function openEdit (record) {
68
+ setEditingItem(record)
69
+ setEditModalVisible(true)
70
+ setTimeout(() => {
71
+ editFormChild.setFieldsValue(record)
72
+ }, 100)
73
+ }
74
+
75
+ function handleEditFinish (data) {
76
+ const updatedItem = {
77
+ ...data,
78
+ id: editingItem.id
79
+ }
80
+ setList(old => old.map(item => item.id === editingItem.id ? updatedItem : item))
81
+ const v = (form.getFieldValue('sshTunnels') || []).map(
82
+ item => item.id === editingItem.id ? updatedItem : item
83
+ )
84
+ form.setFieldsValue({
85
+ sshTunnels: v
86
+ })
87
+ setEditModalVisible(false)
88
+ setEditingItem(null)
89
+ editFormChild.resetFields()
90
+ }
91
+
92
+ function closeEditModal () {
93
+ setEditModalVisible(false)
94
+ setEditingItem(null)
95
+ editFormChild.resetFields()
96
+ }
97
+
75
98
  const cols = [
76
99
  {
77
100
  title: 'NO.',
@@ -108,15 +131,21 @@ export default function renderSshTunnels (props) {
108
131
  )
109
132
  }
110
133
  }, {
111
- title: e('del'),
134
+ title: e('op'),
112
135
  key: 'op',
113
136
  dataIndex: 'id',
114
- render: (id) => {
137
+ render: (id, record) => {
115
138
  return (
116
- <MinusCircleFilled
117
- className='pointer'
118
- onClick={() => remove(id)}
119
- />
139
+ <span>
140
+ <EditOutlined
141
+ className='pointer mg1r'
142
+ onClick={() => openEdit(record)}
143
+ />
144
+ <MinusCircleFilled
145
+ className='pointer'
146
+ onClick={() => remove(id)}
147
+ />
148
+ </span>
120
149
  )
121
150
  }
122
151
  }
@@ -141,62 +170,6 @@ export default function renderSshTunnels (props) {
141
170
  )
142
171
  }
143
172
 
144
- // direction = localToRemote or remoteToLocal, should render user, remote port, local port visit directions connected with arrows accordingly
145
- function renderSshTunnelFlow (direction) {
146
- const localToRemote = direction === 'localToRemote'
147
- const middle = localToRemote ? e('local') : e('remote')
148
- const last = localToRemote ? e('remote') : e('local')
149
- return (
150
- <div>
151
- <p>{e(direction)}</p>
152
- <p><UserOutlined /> → {middle} → {last}</p>
153
- </div>
154
- )
155
- }
156
-
157
- function renderDynamicForward () {
158
- return (
159
- <p><UserOutlined /> → socks proxy → url</p>
160
- )
161
- }
162
-
163
- function renderRemote () {
164
- if (isDynamic) {
165
- return null
166
- }
167
- return (
168
- <FormItem
169
- label={e('remote')}
170
- {...formItemLayout}
171
- required
172
- className='ssh-tunnels-host'
173
- >
174
- <Space.Compact>
175
- <FormItem
176
- name='sshTunnelRemoteHost'
177
- label=''
178
- required
179
- >
180
- <Input
181
- placeholder={e('host')}
182
- />
183
- </FormItem>
184
- <FormItem
185
- label=''
186
- name='sshTunnelRemotePort'
187
- required
188
- >
189
- <InputNumber
190
- min={1}
191
- max={65535}
192
- placeholder={e('port')}
193
- />
194
- </FormItem>
195
- </Space.Compact>
196
- </FormItem>
197
- )
198
- }
199
-
200
173
  return (
201
174
  <>
202
175
  <FormItem
@@ -205,94 +178,29 @@ export default function renderSshTunnels (props) {
205
178
  >
206
179
  <Input />
207
180
  </FormItem>
208
- <Form
209
- form={formChild}
210
- onFinish={handleFinish}
181
+ {renderList()}
182
+ <SshTunnelForm
183
+ formChild={formChild}
211
184
  initialValues={initialValues}
212
- component='div'
213
- >
214
- {renderList()}
215
- <FormItem
216
- label={e('sshTunnel')}
217
- name='sshTunnel'
218
- {...formItemLayout}
219
- defaultValue='forwardRemoteToLocal'
220
- required
221
- >
222
- <RadioGroup onChange={onChange}>
223
- <RadioButton
224
- value='forwardRemoteToLocal'
225
- >
226
- <Tooltip title={renderSshTunnelFlow('remoteToLocal')}>
227
- <span>R→L <QuestionCircleOutlined /></span>
228
- </Tooltip>
229
- </RadioButton>
230
- <RadioButton
231
- value='forwardLocalToRemote'
232
- >
233
- <Tooltip title={renderSshTunnelFlow('localToRemote')}>
234
- <span>L→R <QuestionCircleOutlined /></span>
235
- </Tooltip>
236
- </RadioButton>
237
- <RadioButton
238
- value='dynamicForward'
239
- >
240
- <Tooltip title={renderDynamicForward()}>
241
- <span>{e('dynamicForward')}(socks proxy) <QuestionCircleOutlined /></span>
242
- </Tooltip>
243
- </RadioButton>
244
- </RadioGroup>
245
- </FormItem>
246
- {renderRemote()}
247
- <FormItem
248
- label={e('local')}
249
- {...formItemLayout}
250
- required
251
- className='ssh-tunnels-host'
252
- >
253
- <Space.Compact>
254
- <FormItem
255
- name='sshTunnelLocalHost'
256
- label=''
257
- required
258
- >
259
- <Input
260
- placeholder={e('host')}
261
- />
262
- </FormItem>
263
- <FormItem
264
- label=''
265
- name='sshTunnelLocalPort'
266
- required
267
- >
268
- <InputNumber
269
- min={1}
270
- max={65535}
271
- placeholder={e('port')}
272
- />
273
- </FormItem>
274
- </Space.Compact>
275
- </FormItem>
276
- <FormItem
277
- name='name'
278
- label={e('name')}
279
- {...formItemLayout}
185
+ onFinish={handleFinish}
186
+ />
187
+ {editModalVisible && (
188
+ <Modal
189
+ open={editModalVisible}
190
+ onCancel={closeEditModal}
191
+ footer={null}
192
+ title={e('edit') + ' ' + e('sshTunnel')}
193
+ width={600}
280
194
  >
281
- <Input
282
- placeholder={e('name')}
195
+ <SshTunnelForm
196
+ key={editingItem?.id}
197
+ formChild={editFormChild}
198
+ initialValues={editingItem}
199
+ onFinish={handleEditFinish}
200
+ isEdit
283
201
  />
284
- </FormItem>
285
- <FormItem {...tailFormItemLayout} className='mg60b'>
286
- <Button
287
- type='default'
288
- htmlType='button'
289
- icon={<PlusOutlined />}
290
- onClick={onSubmit}
291
- >
292
- {e('sshTunnel')}
293
- </Button>
294
- </FormItem>
295
- </Form>
202
+ </Modal>
203
+ )}
296
204
  </>
297
205
  )
298
206
  }
@@ -9,8 +9,10 @@ const defaultValues = {
9
9
  x11: false,
10
10
  term: 'xterm-256color',
11
11
  displayRaw: false,
12
+ authType: 'password',
12
13
  encode: 'utf8',
13
- envLang: 'en_US.UTF-8'
14
+ envLang: 'en_US.UTF-8',
15
+ username: 'root'
14
16
  },
15
17
  telnet: {
16
18
  port: 23
@@ -44,7 +46,7 @@ const defaultValues = {
44
46
  }
45
47
 
46
48
  const requiredFields = {
47
- ssh: ['host', 'username', 'term'],
49
+ ssh: ['host'],
48
50
  telnet: ['host'],
49
51
  serial: ['path'],
50
52
  vnc: ['host'],
@@ -113,6 +113,7 @@ export default function BookmarkTreeSelect (props) {
113
113
  const [expandedKeys, setExpandedKeys] = useState(() => deepCopy(propExpandedKeys || []))
114
114
  const [checkedKeys, setCheckedKeys] = useState(() => deepCopy(propCheckedKeys || []))
115
115
  const [searchText, setSearchText] = useState('')
116
+ const [refreshKey, setRefreshKey] = useState(0)
116
117
 
117
118
  const onCheck = setCheckedKeys
118
119
 
@@ -122,6 +123,7 @@ export default function BookmarkTreeSelect (props) {
122
123
  if (type === 'delete') {
123
124
  store.delItems(arr, settingMap.bookmarks)
124
125
  store.delItems(arr, settingMap.bookmarkGroups)
126
+ setRefreshKey(k => k + 1)
125
127
  } else {
126
128
  store.openBookmarks(arr)
127
129
  if (props.onClose) {
@@ -141,7 +143,7 @@ export default function BookmarkTreeSelect (props) {
141
143
  setCheckedKeys([])
142
144
  }
143
145
 
144
- const treeData = useMemo(() => buildData(bookmarks, bookmarkGroups, searchText), [bookmarks, bookmarkGroups, searchText])
146
+ const treeData = useMemo(() => buildData(bookmarks, bookmarkGroups, searchText), [bookmarks, bookmarkGroups, searchText, refreshKey])
145
147
 
146
148
  // Auto expand parent nodes when searching
147
149
  const handleExpand = (keys) => {
@@ -71,9 +71,6 @@ export default class TransportAction extends Component {
71
71
  this.transport = null
72
72
  this.fromFile = null
73
73
  refsTransfers.remove(this.id)
74
- if (this.isFtp) {
75
- window.initingFtpTabIds?.delete(this.tabId)
76
- }
77
74
  }
78
75
 
79
76
  localCheckExist = (path) => {
@@ -241,10 +238,11 @@ export default class TransportAction extends Component {
241
238
  operation // 'mv' or 'cp'
242
239
  } = transfer
243
240
 
244
- let finalToPath = toPath
241
+ // Use this.newPath when set (e.g. user chose rename from conflict modal)
242
+ let finalToPath = this.newPath || toPath
245
243
 
246
- // Check if it's a copy operation to the same path
247
- if (fromPath === toPath && operation === fileOperationsMap.cp) {
244
+ // Check if it's a copy operation to the same path (no rename decision pending)
245
+ if (!this.newPath && fromPath === toPath && operation === fileOperationsMap.cp) {
248
246
  finalToPath = this.handleRename(toPath, typeFrom === typeMap.remote).newPath
249
247
  transfer.toPath = finalToPath
250
248
  this.update({
@@ -410,6 +408,10 @@ export default class TransportAction extends Component {
410
408
  this.newName = newName
411
409
  }
412
410
 
411
+ const { typeFrom, typeTo } = this.props.transfer
412
+ if (typeFrom === typeTo) {
413
+ return this.mvOrCp()
414
+ }
413
415
  this.startTransfer()
414
416
  }
415
417
 
@@ -758,9 +760,9 @@ export default class TransportAction extends Component {
758
760
 
759
761
  const list = await this.list(typeFrom, fromPath, tabId)
760
762
  const bigFileSize = 1024 * 1024
761
- const smallFilesBatch = this.isFtp ? 1 : 30
762
- const BigFilesBatch = this.isFtp ? 1 : 3
763
- const foldersBatch = this.isFtp ? 1 : 50
763
+ const smallFilesBatch = 30
764
+ const BigFilesBatch = 3
765
+ const foldersBatch = 50
764
766
 
765
767
  const {
766
768
  folders,
@@ -9,8 +9,6 @@ import { maxTransport } from '../../common/constants'
9
9
  import { refsStatic } from '../common/ref'
10
10
  // import { action } from 'manate'
11
11
 
12
- window.initingFtpTabIds = new Set()
13
-
14
12
  export default class TransportsActionStore extends Component {
15
13
  constructor (props) {
16
14
  super(props)
@@ -84,9 +82,7 @@ export default class TransportsActionStore extends Component {
84
82
  typeTo,
85
83
  typeFrom,
86
84
  inited,
87
- id,
88
- tabType,
89
- tabId
85
+ id
90
86
  } = tr
91
87
 
92
88
  const isTransfer = typeTo !== typeFrom
@@ -95,15 +91,6 @@ export default class TransportsActionStore extends Component {
95
91
  continue
96
92
  }
97
93
 
98
- // For ftp transfers, ensure only one per tabId is inited
99
- if (tabType === 'ftp') {
100
- const hasInited = fileTransfers.some(t => t.tabId === tabId && t.inited && t.id !== id)
101
- if (hasInited || window.initingFtpTabIds.has(tabId)) {
102
- continue
103
- }
104
- window.initingFtpTabIds.add(tabId)
105
- }
106
-
107
94
  if (count < maxTransport) {
108
95
  count++
109
96
  this.pendingInitIds.add(id)
@@ -34,7 +34,6 @@ import InputContextMenu from '../common/input-context-menu'
34
34
  import WorkspaceSaveModal from '../tabs/workspace-save-modal'
35
35
  import BookmarkFromHistoryModal from '../bookmark-form/bookmark-from-history-modal'
36
36
  import AutoSync from '../setting-sync/auto-sync'
37
- import AutoCheckUpdate from '../common/auto-check-update'
38
37
  import BatchOpRunner from '../batch-op/batch-op-runner'
39
38
  import { pick } from 'lodash-es'
40
39
  import deepCopy from 'json-deep-copy'
@@ -292,7 +291,6 @@ export default auto(function Index (props) {
292
291
  <TerminalCmdSuggestions {...cmdSuggestionsProps} />
293
292
  <TransferQueue />
294
293
  <AutoSync config={config} />
295
- <AutoCheckUpdate config={config} />
296
294
  <WorkspaceSaveModal store={store} />
297
295
  <BookmarkFromHistoryModal />
298
296
  <NotificationContainer />
@@ -28,7 +28,8 @@ const {
28
28
  const downloadMirrorList = [
29
29
  'github',
30
30
  'gh-proxy',
31
- 'sourceforge'
31
+ 'sourceforge',
32
+ 'r2'
32
33
  ]
33
34
 
34
35
  export default class Upgrade extends PureComponent {
@@ -214,10 +214,10 @@ export default class FileListTable extends Component {
214
214
  this.currentFileId = id
215
215
  }
216
216
 
217
- renderItem = (item) => {
217
+ renderItem = (item, index) => {
218
218
  const { type } = this.props
219
219
  const cls = item.isParent ? 'parent-file-item' : 'real-file-item'
220
- const key = item.id
220
+ const key = item.id ?? index + 'file-item'
221
221
  const fileProps = {
222
222
  ...this.props.getFileProps(item, type),
223
223
  cls,
@@ -28,7 +28,7 @@ export default class ScrollFiles extends Component {
28
28
  const arr = hasPager
29
29
  ? list.slice(start, end)
30
30
  : list
31
- return arr.map(this.props.renderItem)
31
+ return arr.map((item, index) => this.props.renderItem(item, index))
32
32
  }
33
33
 
34
34
  renderPager () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "3.6.6",
3
+ "version": "3.7.9",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",
@@ -1,31 +0,0 @@
1
- import { useEffect, useRef } from 'react'
2
-
3
- export default function AutoCheckUpdate ({ config }) {
4
- const lastCheckTimeRef = useRef(0)
5
- const intervalIdRef = useRef(null)
6
-
7
- useEffect(() => {
8
- if (!config.checkUpdateOnStart) {
9
- clearInterval(intervalIdRef.current)
10
- return
11
- }
12
-
13
- const checkForUpdate = () => {
14
- const { store } = window
15
- if (store.config.checkUpdateOnStart) {
16
- store.onCheckUpdate(false)
17
- }
18
- lastCheckTimeRef.current = Date.now()
19
- }
20
-
21
- intervalIdRef.current = setInterval(checkForUpdate, 60 * 60 * 1000)
22
-
23
- return () => {
24
- if (intervalIdRef.current) {
25
- clearInterval(intervalIdRef.current)
26
- }
27
- }
28
- }, [config.checkUpdateOnStart])
29
-
30
- return null
31
- }