@electerm/electerm-react 3.2.0 → 3.5.6
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.
- package/client/common/constants.js +1 -8
- package/client/common/fs.js +84 -0
- package/client/components/batch-op/batch-op-alert.jsx +23 -0
- package/client/components/batch-op/batch-op-editor.jsx +206 -0
- package/client/components/batch-op/batch-op-logs.jsx +53 -0
- package/client/components/batch-op/batch-op-runner.jsx +315 -0
- package/client/components/bookmark-form/ai-bookmark-form.jsx +2 -1
- package/client/components/bookmark-form/bookmark-from-history-modal.jsx +2 -1
- package/client/components/bookmark-form/common/fields.jsx +15 -0
- package/client/components/bookmark-form/config/rdp.js +5 -0
- package/client/components/common/auto-check-update.jsx +31 -0
- package/client/components/common/notification.styl +1 -1
- package/client/components/file-transfer/conflict-resolve.jsx +3 -0
- package/client/components/footer/batch-input.jsx +10 -7
- package/client/components/main/error-wrapper.jsx +18 -7
- package/client/components/main/main.jsx +6 -7
- package/client/components/main/upgrade.jsx +133 -104
- package/client/components/main/upgrade.styl +2 -2
- package/client/components/rdp/file-transfer.js +375 -0
- package/client/components/rdp/rdp-session.jsx +169 -76
- package/client/components/rdp/rdp.styl +27 -0
- package/client/components/setting-sync/auto-sync.jsx +53 -0
- package/client/components/setting-sync/data-import.jsx +69 -8
- package/client/components/sftp/address-bar.jsx +23 -3
- package/client/components/sidebar/bookmark-select.jsx +3 -2
- package/client/components/sidebar/history-item.jsx +3 -1
- package/client/components/sidebar/index.jsx +0 -9
- package/client/components/sidebar/info-modal.jsx +7 -2
- package/client/components/tabs/add-btn-menu.jsx +1 -1
- package/client/components/tabs/add-btn.jsx +9 -15
- package/client/components/tabs/quick-connect.jsx +6 -10
- package/client/components/terminal/terminal.jsx +4 -5
- package/client/components/tree-list/tree-list.jsx +115 -10
- package/client/components/tree-list/tree-list.styl +3 -0
- package/client/components/tree-list/tree-search.jsx +9 -1
- package/client/components/widgets/widget-form.jsx +6 -0
- package/client/store/app-upgrade.js +2 -2
- package/client/store/common.js +0 -28
- package/client/store/load-data.js +3 -3
- package/client/store/mcp-handler.js +2 -2
- package/client/store/sync.js +25 -1
- package/client/store/tab.js +1 -1
- package/client/store/watch.js +10 -18
- package/client/views/index.pug +1 -2
- package/package.json +1 -1
- package/client/components/batch-op/batch-op.jsx +0 -694
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { Component } from 'react'
|
|
2
|
+
import { refsStatic } from '../common/ref'
|
|
3
|
+
import { statusMap } from '../../common/constants'
|
|
4
|
+
import { autoRun } from 'manate'
|
|
5
|
+
import uid from '../../common/uid'
|
|
6
|
+
|
|
7
|
+
const STATIC_KEY = 'batch-op-runner'
|
|
8
|
+
|
|
9
|
+
export default class BatchOpRunner extends Component {
|
|
10
|
+
constructor () {
|
|
11
|
+
super()
|
|
12
|
+
this.steps = []
|
|
13
|
+
this.currentIndex = 0
|
|
14
|
+
this.status = 'idle'
|
|
15
|
+
this.currentTabId = null
|
|
16
|
+
this.currentStep = null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
componentDidMount () {
|
|
20
|
+
refsStatic.add(STATIC_KEY, this)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getActiveTabId () {
|
|
24
|
+
return window.store?.activeTabId
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getState () {
|
|
28
|
+
return {
|
|
29
|
+
steps: this.steps,
|
|
30
|
+
currentIndex: this.currentIndex,
|
|
31
|
+
status: this.status,
|
|
32
|
+
currentStep: this.currentStep
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
reset () {
|
|
37
|
+
this.steps = []
|
|
38
|
+
this.currentIndex = 0
|
|
39
|
+
this.status = 'idle'
|
|
40
|
+
this.currentTabId = null
|
|
41
|
+
this.currentStep = null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
runBatchOpFromFile (filePath) {
|
|
45
|
+
return this._runBatchOpFromFile(filePath)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
executeWorkflow (workflows) {
|
|
49
|
+
return this._executeWorkflow(workflows)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async _runBatchOpFromFile (filePath) {
|
|
53
|
+
try {
|
|
54
|
+
const content = await window.fs.readFile(filePath)
|
|
55
|
+
let workflows
|
|
56
|
+
try {
|
|
57
|
+
workflows = JSON.parse(content)
|
|
58
|
+
if (!Array.isArray(workflows)) {
|
|
59
|
+
throw new Error('Workflow must be an array')
|
|
60
|
+
}
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.error('Invalid batch operation JSON:', e.message)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.reset()
|
|
67
|
+
await this._executeWorkflow(workflows)
|
|
68
|
+
console.log('Batch operation completed from file')
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.error('Failed to run batch operation from file:', e.message)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async _executeWorkflow (workflows) {
|
|
75
|
+
if (!Array.isArray(workflows)) {
|
|
76
|
+
throw new Error('Workflow must be an array')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.steps = []
|
|
80
|
+
this.currentIndex = 0
|
|
81
|
+
this.status = 'running'
|
|
82
|
+
|
|
83
|
+
const results = []
|
|
84
|
+
const logsRef = refsStatic.get('batch-op-logs')
|
|
85
|
+
|
|
86
|
+
for (let i = 0; i < workflows.length; i++) {
|
|
87
|
+
const step = workflows[i]
|
|
88
|
+
this.currentIndex = i
|
|
89
|
+
this.currentStep = step.name
|
|
90
|
+
|
|
91
|
+
logsRef?.setLogs(this.getState())
|
|
92
|
+
|
|
93
|
+
let result
|
|
94
|
+
try {
|
|
95
|
+
result = await this._executeStep(step, results)
|
|
96
|
+
this.steps.push({ name: step.name, status: 'success', result })
|
|
97
|
+
results.push(result)
|
|
98
|
+
console.log(`Batch op step ${i + 1} completed:`, step.name || 'unnamed')
|
|
99
|
+
} catch (e) {
|
|
100
|
+
console.log(e)
|
|
101
|
+
this.steps.push({ name: step.name, status: 'error', error: e.message })
|
|
102
|
+
logsRef?.setLogs(this.getState())
|
|
103
|
+
console.error(`Batch op step ${i + 1} failed:`, step.name || 'unnamed', e.message)
|
|
104
|
+
this.status = 'error'
|
|
105
|
+
logsRef?.setLogs(this.getState())
|
|
106
|
+
throw e
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.status = 'completed'
|
|
111
|
+
this.currentStep = null
|
|
112
|
+
logsRef?.setLogs(this.getState())
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async _executeStep (step, previousResults) {
|
|
116
|
+
const { action, prevDelay, afterDelay } = step
|
|
117
|
+
|
|
118
|
+
if (!action) {
|
|
119
|
+
throw new Error('Step must have an "action" field')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (prevDelay > 0) {
|
|
123
|
+
await new Promise(resolve => setTimeout(resolve, prevDelay))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const s = step.params ? { ...step, ...step.params } : step
|
|
127
|
+
|
|
128
|
+
let result
|
|
129
|
+
switch (action) {
|
|
130
|
+
case 'connect':
|
|
131
|
+
result = await this._batchStepConnect(s)
|
|
132
|
+
this.currentTabId = result.tabId
|
|
133
|
+
break
|
|
134
|
+
case 'command':
|
|
135
|
+
result = await this._batchStepCommand(s)
|
|
136
|
+
break
|
|
137
|
+
case 'sftp_upload':
|
|
138
|
+
result = await this._batchStepSftpUpload(s)
|
|
139
|
+
break
|
|
140
|
+
case 'sftp_download':
|
|
141
|
+
result = await this._batchStepSftpDownload(s)
|
|
142
|
+
break
|
|
143
|
+
default:
|
|
144
|
+
throw new Error(`Unknown action: ${action}`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (afterDelay > 0) {
|
|
148
|
+
await new Promise(resolve => setTimeout(resolve, afterDelay))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return result
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async _batchStepConnect (step) {
|
|
155
|
+
const { store } = window
|
|
156
|
+
const p = step.params || step
|
|
157
|
+
|
|
158
|
+
const tabId = uid()
|
|
159
|
+
const tab = {
|
|
160
|
+
id: tabId,
|
|
161
|
+
type: 'ssh',
|
|
162
|
+
host: p.host || '',
|
|
163
|
+
port: p.port || 22,
|
|
164
|
+
username: p.username || '',
|
|
165
|
+
password: p.password || '',
|
|
166
|
+
privateKey: p.privateKey || '',
|
|
167
|
+
passphrase: p.passphrase || '',
|
|
168
|
+
certificate: p.certificate || '',
|
|
169
|
+
authType: p.authType || 'password',
|
|
170
|
+
profile: p.profile || '',
|
|
171
|
+
enableSftp: p.enableSftp !== false,
|
|
172
|
+
enableSsh: p.enableSsh !== false,
|
|
173
|
+
useSshAgent: p.useSshAgent !== false,
|
|
174
|
+
sshAgent: p.sshAgent || '',
|
|
175
|
+
term: p.term || 'xterm-256color',
|
|
176
|
+
encode: p.encode || 'utf8',
|
|
177
|
+
envLang: p.envLang || 'en_US.UTF-8',
|
|
178
|
+
setEnv: p.setEnv || '',
|
|
179
|
+
startDirectoryRemote: p.startDirectoryRemote || '',
|
|
180
|
+
startDirectoryLocal: p.startDirectoryLocal || '',
|
|
181
|
+
proxy: p.proxy || '',
|
|
182
|
+
x11: p.x11 || false,
|
|
183
|
+
displayRaw: p.displayRaw || false,
|
|
184
|
+
sshTunnels: p.sshTunnels || [],
|
|
185
|
+
connectionHoppings: p.connectionHoppings || [],
|
|
186
|
+
title: step.name || `SSH: ${p.host}`,
|
|
187
|
+
status: 'processing',
|
|
188
|
+
pane: 'terminal'
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
store.addTab(tab)
|
|
192
|
+
await this._waitForConnection(tabId)
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
success: true,
|
|
196
|
+
action: 'connect',
|
|
197
|
+
host: p.host,
|
|
198
|
+
port: p.port,
|
|
199
|
+
tabId
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
_waitForConnection = async (tabId) => {
|
|
204
|
+
return new Promise((resolve, reject) => {
|
|
205
|
+
const timeout = setTimeout(() => {
|
|
206
|
+
if (this._refWait) {
|
|
207
|
+
this._refWait.stop()
|
|
208
|
+
delete this._refWait
|
|
209
|
+
}
|
|
210
|
+
reject(new Error('Connection timeout'))
|
|
211
|
+
}, 30000)
|
|
212
|
+
|
|
213
|
+
this._refWait = autoRun(() => {
|
|
214
|
+
const { tabs } = window.store
|
|
215
|
+
const tab = tabs.find(t => t.id === tabId)
|
|
216
|
+
if (tab && tab.status === statusMap.success) {
|
|
217
|
+
clearTimeout(timeout)
|
|
218
|
+
this._refWait && this._refWait.stop()
|
|
219
|
+
delete this._refWait
|
|
220
|
+
resolve(tab)
|
|
221
|
+
} else if (tab && tab.status === statusMap.error) {
|
|
222
|
+
clearTimeout(timeout)
|
|
223
|
+
this._refWait && this._refWait.stop()
|
|
224
|
+
delete this._refWait
|
|
225
|
+
reject(new Error('Connection failed: ' + (tab.errorMsg || 'unknown error')))
|
|
226
|
+
}
|
|
227
|
+
return window.store.tabs
|
|
228
|
+
})
|
|
229
|
+
this._refWait.start()
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async _batchStepCommand (step) {
|
|
234
|
+
const tabId = this.currentTabId
|
|
235
|
+
|
|
236
|
+
if (!tabId) {
|
|
237
|
+
throw new Error('No active tab. Please connect first.')
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const { refs } = await import('../common/ref')
|
|
241
|
+
const term = refs.get('term-' + tabId)
|
|
242
|
+
if (!term || !term.term) {
|
|
243
|
+
throw new Error('Terminal not found')
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let waited = 0
|
|
247
|
+
while (!term.attachAddon && waited < 10000) {
|
|
248
|
+
await new Promise(resolve => setTimeout(resolve, 200))
|
|
249
|
+
waited += 200
|
|
250
|
+
}
|
|
251
|
+
if (!term.attachAddon) {
|
|
252
|
+
throw new Error('Terminal not ready: attach addon not initialized')
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
term.runQuickCommand(step.command)
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
success: true,
|
|
259
|
+
action: 'command',
|
|
260
|
+
command: step.command,
|
|
261
|
+
tabId
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async _batchStepSftpUpload (step) {
|
|
266
|
+
const tabId = this.currentTabId
|
|
267
|
+
const { store } = window
|
|
268
|
+
const stepWithTabId = { ...step, tabId, conflictPolicy: 'mergeOrOverwriteAll' }
|
|
269
|
+
const { transferId } = await store.mcpSftpUpload(stepWithTabId)
|
|
270
|
+
await this._batchWaitForTransfer(transferId)
|
|
271
|
+
return { success: true, action: 'sftp_upload', localPath: step.localPath, remotePath: step.remotePath, transferId, tabId }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async _batchStepSftpDownload (step) {
|
|
275
|
+
const tabId = this.currentTabId
|
|
276
|
+
const { store } = window
|
|
277
|
+
const stepWithTabId = { ...step, tabId, conflictPolicy: 'mergeOrOverwriteAll' }
|
|
278
|
+
const { transferId } = await store.mcpSftpDownload(stepWithTabId)
|
|
279
|
+
await this._batchWaitForTransfer(transferId)
|
|
280
|
+
return { success: true, action: 'sftp_download', remotePath: step.remotePath, localPath: step.localPath, transferId, tabId }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async _batchWaitForTransfer (transferId) {
|
|
284
|
+
return new Promise((resolve, reject) => {
|
|
285
|
+
const timeout = setTimeout(() => {
|
|
286
|
+
if (this._refTransferWait) {
|
|
287
|
+
this._refTransferWait.stop()
|
|
288
|
+
delete this._refTransferWait
|
|
289
|
+
}
|
|
290
|
+
reject(new Error('Transfer timeout (1 hour)'))
|
|
291
|
+
}, 60 * 60 * 1000)
|
|
292
|
+
|
|
293
|
+
this._refTransferWait = autoRun(() => {
|
|
294
|
+
const { transferHistory } = window.store
|
|
295
|
+
const item = transferHistory.find(t => t.id === transferId || t.originalId === transferId)
|
|
296
|
+
if (item) {
|
|
297
|
+
clearTimeout(timeout)
|
|
298
|
+
this._refTransferWait && this._refTransferWait.stop()
|
|
299
|
+
delete this._refTransferWait
|
|
300
|
+
if (item.error) {
|
|
301
|
+
reject(new Error('Transfer failed: ' + item.error))
|
|
302
|
+
} else {
|
|
303
|
+
resolve(item)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return window.store.transferHistory
|
|
307
|
+
})
|
|
308
|
+
this._refTransferWait.start()
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
render () {
|
|
313
|
+
return null
|
|
314
|
+
}
|
|
315
|
+
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* AI-powered bookmark generation form
|
|
3
3
|
*/
|
|
4
4
|
import { useState, useEffect } from 'react'
|
|
5
|
-
import { Button, Input,
|
|
5
|
+
import { Button, Input, Space, Alert } from 'antd'
|
|
6
|
+
import message from '../common/message'
|
|
6
7
|
import {
|
|
7
8
|
RobotOutlined,
|
|
8
9
|
LoadingOutlined,
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import React from 'react'
|
|
6
|
-
import { Button
|
|
6
|
+
import { Button } from 'antd'
|
|
7
|
+
import message from '../common/message'
|
|
7
8
|
import { PlusOutlined } from '@ant-design/icons'
|
|
8
9
|
import Modal from '../common/modal'
|
|
9
10
|
import { refsStatic } from '../common/ref'
|
|
@@ -22,6 +22,7 @@ import SshHostSelector from './ssh-host-selector.jsx'
|
|
|
22
22
|
import SshAuthTypeSelector from './ssh-auth-type-selector.jsx'
|
|
23
23
|
import SshAuthSelector from './ssh-auth-selector.jsx'
|
|
24
24
|
import CategorySelect from './category-select.jsx'
|
|
25
|
+
import ExternalLink from '../../common/external-link.jsx'
|
|
25
26
|
const Fragment = React.Fragment
|
|
26
27
|
const FormItem = Form.Item
|
|
27
28
|
|
|
@@ -115,6 +116,20 @@ export function renderFormItem (item, formItemLayout, form, ctxProps, index) {
|
|
|
115
116
|
|
|
116
117
|
// Render complex/custom components directly (no extra wrapper component)
|
|
117
118
|
switch (type) {
|
|
119
|
+
case 'wiki':
|
|
120
|
+
return (
|
|
121
|
+
<Alert
|
|
122
|
+
key={name}
|
|
123
|
+
type='warning'
|
|
124
|
+
className='mg2b'
|
|
125
|
+
showIcon
|
|
126
|
+
description={
|
|
127
|
+
<>
|
|
128
|
+
<ExternalLink to={item.link}>{item.link}</ExternalLink>
|
|
129
|
+
</>
|
|
130
|
+
}
|
|
131
|
+
/>
|
|
132
|
+
)
|
|
118
133
|
case 'alert':
|
|
119
134
|
return <Alert key={name} {...item.props} />
|
|
120
135
|
case 'info':
|
|
@@ -21,6 +21,11 @@ const rdpConfig = {
|
|
|
21
21
|
key: 'auth',
|
|
22
22
|
label: e('auth'),
|
|
23
23
|
fields: [
|
|
24
|
+
{
|
|
25
|
+
type: 'wiki',
|
|
26
|
+
name: 'rdp-limitation-warning',
|
|
27
|
+
link: 'https://github.com/electerm/electerm/wiki/RDP-limitation'
|
|
28
|
+
},
|
|
24
29
|
commonFields.category,
|
|
25
30
|
commonFields.colorTitle,
|
|
26
31
|
{ type: 'input', name: 'host', label: () => e('host'), rules: [{ required: true, message: e('host') + ' required' }] },
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
}
|
|
@@ -136,12 +136,10 @@ export default class BatchInput extends Component {
|
|
|
136
136
|
} = this.props
|
|
137
137
|
const opts = {
|
|
138
138
|
options: this.buildOptions(),
|
|
139
|
-
placeholder: e('batchInput'),
|
|
140
139
|
value: cmd,
|
|
141
140
|
onChange: this.handleChange,
|
|
142
141
|
defaultOpen: false,
|
|
143
142
|
open,
|
|
144
|
-
allowClear: true,
|
|
145
143
|
className: 'batch-input-wrap'
|
|
146
144
|
}
|
|
147
145
|
const cls = classNames(
|
|
@@ -155,6 +153,15 @@ export default class BatchInput extends Component {
|
|
|
155
153
|
placeholder: e('batchInput'),
|
|
156
154
|
className: 'batch-input-holder'
|
|
157
155
|
}
|
|
156
|
+
const textAreaProps = {
|
|
157
|
+
onPressEnter: this.handleEnter,
|
|
158
|
+
onClick: this.handleClick,
|
|
159
|
+
onBlur: this.handleBlur,
|
|
160
|
+
size: 'small',
|
|
161
|
+
autoSize: { minRows: 1 },
|
|
162
|
+
placeholder: e('batchInput'),
|
|
163
|
+
allowClear: true
|
|
164
|
+
}
|
|
158
165
|
const tabSelectProps = {
|
|
159
166
|
activeTabId: this.props.activeTabId,
|
|
160
167
|
tabs: this.getTabs(),
|
|
@@ -179,11 +186,7 @@ export default class BatchInput extends Component {
|
|
|
179
186
|
{...opts}
|
|
180
187
|
>
|
|
181
188
|
<Input.TextArea
|
|
182
|
-
|
|
183
|
-
onClick={this.handleClick}
|
|
184
|
-
onBlur={this.handleBlur}
|
|
185
|
-
size='small'
|
|
186
|
-
autoSize={{ minRows: 1 }}
|
|
189
|
+
{...textAreaProps}
|
|
187
190
|
/>
|
|
188
191
|
</AutoComplete>
|
|
189
192
|
<TabSelect {...tabSelectProps} />
|
|
@@ -77,12 +77,9 @@ export default class ErrorBoundary extends React.PureComponent {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
renderTroubleShoot = () => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
} = packInfo
|
|
85
|
-
const bugUrl = `${bugReportLink}/new/choose`
|
|
80
|
+
if (window.et.isWebApp) {
|
|
81
|
+
return this.renderContacts()
|
|
82
|
+
}
|
|
86
83
|
return (
|
|
87
84
|
<div className='pd1y wordbreak'>
|
|
88
85
|
<h2>{e('troubleShoot')}</h2>
|
|
@@ -99,6 +96,20 @@ export default class ErrorBoundary extends React.PureComponent {
|
|
|
99
96
|
)
|
|
100
97
|
})
|
|
101
98
|
}
|
|
99
|
+
{this.renderContacts()}
|
|
100
|
+
</div>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
renderContacts () {
|
|
105
|
+
const {
|
|
106
|
+
bugs: {
|
|
107
|
+
url: bugReportLink
|
|
108
|
+
}
|
|
109
|
+
} = packInfo
|
|
110
|
+
const bugUrl = `${bugReportLink}/new/choose`
|
|
111
|
+
return (
|
|
112
|
+
<>
|
|
102
113
|
<div className='pd1b'>
|
|
103
114
|
<Link to={bugUrl}>{e('bugReport')}</Link>
|
|
104
115
|
</div>
|
|
@@ -112,7 +123,7 @@ export default class ErrorBoundary extends React.PureComponent {
|
|
|
112
123
|
className='mwm-100'
|
|
113
124
|
/>
|
|
114
125
|
</div>
|
|
115
|
-
|
|
126
|
+
</>
|
|
116
127
|
)
|
|
117
128
|
}
|
|
118
129
|
|
|
@@ -6,7 +6,6 @@ import UpdateCheck from './upgrade'
|
|
|
6
6
|
import SettingModal from '../setting-panel/setting-modal'
|
|
7
7
|
import TextEditor from '../text-editor/text-editor'
|
|
8
8
|
import Sidebar from '../sidebar'
|
|
9
|
-
import BatchOp from '../batch-op/batch-op'
|
|
10
9
|
import CssOverwrite from '../bg/css-overwrite'
|
|
11
10
|
import UiTheme from './ui-theme'
|
|
12
11
|
import CustomCss from '../bg/custom-css.jsx'
|
|
@@ -34,6 +33,9 @@ import MoveItemModal from '../tree-list/move-item-modal'
|
|
|
34
33
|
import InputContextMenu from '../common/input-context-menu'
|
|
35
34
|
import WorkspaceSaveModal from '../tabs/workspace-save-modal'
|
|
36
35
|
import BookmarkFromHistoryModal from '../bookmark-form/bookmark-from-history-modal'
|
|
36
|
+
import AutoSync from '../setting-sync/auto-sync'
|
|
37
|
+
import AutoCheckUpdate from '../common/auto-check-update'
|
|
38
|
+
import BatchOpRunner from '../batch-op/batch-op-runner'
|
|
37
39
|
import { pick } from 'lodash-es'
|
|
38
40
|
import deepCopy from 'json-deep-copy'
|
|
39
41
|
import './wrapper.styl'
|
|
@@ -184,11 +186,6 @@ export default auto(function Index (props) {
|
|
|
184
186
|
fileTransferChanged: JSON.stringify(copiedTransfer),
|
|
185
187
|
fileTransfers: copiedTransfer
|
|
186
188
|
}
|
|
187
|
-
const batchOpProps = {
|
|
188
|
-
transferHistory,
|
|
189
|
-
showModal: store.showModal,
|
|
190
|
-
innerWidth: store.innerWidth
|
|
191
|
-
}
|
|
192
189
|
const resProps = {
|
|
193
190
|
resolutions: deepCopy(store.resolutions),
|
|
194
191
|
openResolutionEdit
|
|
@@ -263,7 +260,6 @@ export default auto(function Index (props) {
|
|
|
263
260
|
/>
|
|
264
261
|
<FileInfoModal />
|
|
265
262
|
<SettingModal store={store} />
|
|
266
|
-
<BatchOp {...batchOpProps} />
|
|
267
263
|
<MoveItemModal store={store} />
|
|
268
264
|
<div
|
|
269
265
|
id='outside-context'
|
|
@@ -295,9 +291,12 @@ export default auto(function Index (props) {
|
|
|
295
291
|
<ConnectionHoppingWarning {...warningProps} />
|
|
296
292
|
<TerminalCmdSuggestions {...cmdSuggestionsProps} />
|
|
297
293
|
<TransferQueue />
|
|
294
|
+
<AutoSync config={config} />
|
|
295
|
+
<AutoCheckUpdate config={config} />
|
|
298
296
|
<WorkspaceSaveModal store={store} />
|
|
299
297
|
<BookmarkFromHistoryModal />
|
|
300
298
|
<NotificationContainer />
|
|
299
|
+
<BatchOpRunner />
|
|
301
300
|
</div>
|
|
302
301
|
</ConfigProvider>
|
|
303
302
|
)
|