@electerm/electerm-react 2.12.0 → 2.13.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/file-drop-utils.js +4 -1
- package/client/components/file-transfer/remote2remote-handler.jsx +176 -0
- package/client/components/file-transfer/remote2remote-handlers.jsx +81 -0
- package/client/components/main/main.jsx +2 -0
- package/client/components/profile/profile-form-ftp.jsx +1 -1
- package/client/components/rdp/rdp-session.jsx +24 -15
- package/client/components/rdp/rdp.styl +21 -1
- package/client/components/sftp/file-item.jsx +33 -1
- package/client/components/sftp/file-read.js +58 -3
- package/client/components/spice/spice-session.jsx +9 -6
- package/client/components/spice/spice.styl +27 -9
- package/client/components/tree-list/bookmark-toolbar.jsx +6 -2
- package/client/components/tree-list/tree-list.jsx +0 -5
- package/client/components/vnc/vnc-session.jsx +9 -5
- package/client/components/vnc/vnc.styl +17 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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}>
|
|
@@ -601,35 +601,44 @@ export default class RdpSession extends PureComponent {
|
|
|
601
601
|
|
|
602
602
|
render () {
|
|
603
603
|
const { width: w, height: h } = this.props
|
|
604
|
-
const rdpProps = {
|
|
605
|
-
style: {
|
|
606
|
-
width: w + 'px',
|
|
607
|
-
height: h + 'px'
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
604
|
const { width, height, loading, scaleViewport } = this.state
|
|
605
|
+
const innerWidth = w - 10
|
|
606
|
+
const innerHeight = h - 80
|
|
607
|
+
const wrapperStyle = {
|
|
608
|
+
width: innerWidth + 'px',
|
|
609
|
+
height: innerHeight + 'px',
|
|
610
|
+
overflow: scaleViewport ? 'hidden' : 'auto'
|
|
611
|
+
}
|
|
611
612
|
const canvasProps = {
|
|
612
613
|
width,
|
|
613
614
|
height,
|
|
614
615
|
tabIndex: 0
|
|
615
616
|
}
|
|
616
|
-
if (scaleViewport) {
|
|
617
|
-
canvasProps.className = 'scale-viewport'
|
|
618
|
-
}
|
|
619
617
|
const cls = `rdp-session-wrap session-v-wrap${scaleViewport ? ' scale-viewport' : ''}`
|
|
618
|
+
const sessProps = {
|
|
619
|
+
className: cls,
|
|
620
|
+
style: {
|
|
621
|
+
width: w + 'px',
|
|
622
|
+
height: h + 'px'
|
|
623
|
+
}
|
|
624
|
+
}
|
|
620
625
|
const controlProps = this.getControlProps()
|
|
621
626
|
return (
|
|
622
627
|
<Spin spinning={loading}>
|
|
623
628
|
<div
|
|
624
|
-
{...
|
|
625
|
-
className={cls}
|
|
629
|
+
{...sessProps}
|
|
626
630
|
>
|
|
627
631
|
{this.renderControl()}
|
|
628
|
-
<canvas
|
|
629
|
-
{...canvasProps}
|
|
630
|
-
ref={this.canvasRef}
|
|
631
|
-
/>
|
|
632
632
|
<RemoteFloatControl {...controlProps} />
|
|
633
|
+
<div
|
|
634
|
+
style={wrapperStyle}
|
|
635
|
+
className='rdp-scroll-wrapper s-scroll-wrapper'
|
|
636
|
+
>
|
|
637
|
+
<canvas
|
|
638
|
+
{...canvasProps}
|
|
639
|
+
ref={this.canvasRef}
|
|
640
|
+
/>
|
|
641
|
+
</div>
|
|
633
642
|
</div>
|
|
634
643
|
</Spin>
|
|
635
644
|
)
|
|
@@ -9,7 +9,27 @@
|
|
|
9
9
|
left: 0
|
|
10
10
|
width: 100%
|
|
11
11
|
&.scale-viewport
|
|
12
|
-
canvas
|
|
12
|
+
.rdp-scroll-wrapper canvas
|
|
13
13
|
width: 100% !important
|
|
14
|
+
height: 100% !important
|
|
14
15
|
object-fit: contain
|
|
15
16
|
|
|
17
|
+
.s-scroll-wrapper
|
|
18
|
+
&::-webkit-scrollbar
|
|
19
|
+
width 16px
|
|
20
|
+
height 16px
|
|
21
|
+
background var(--main-darker)
|
|
22
|
+
&::-webkit-scrollbar-track
|
|
23
|
+
background var(--main-darker)
|
|
24
|
+
box-shadow inset 0 0 5px var(--main-darker)
|
|
25
|
+
&::-webkit-scrollbar-thumb
|
|
26
|
+
background var(--primary)
|
|
27
|
+
border-radius 0
|
|
28
|
+
&::-webkit-scrollbar-corner
|
|
29
|
+
background var(--main-darker)
|
|
30
|
+
.rdp-scroll-wrapper
|
|
31
|
+
position relative
|
|
32
|
+
background var(--main)
|
|
33
|
+
z-index 299
|
|
34
|
+
|
|
35
|
+
|
|
@@ -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
|
-
|
|
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
|
|
@@ -739,6 +770,7 @@ export default class FileSection extends React.Component {
|
|
|
739
770
|
typeTo,
|
|
740
771
|
fromPath: resolve(path, name),
|
|
741
772
|
toPath,
|
|
773
|
+
fromFile: file,
|
|
742
774
|
id: generate(),
|
|
743
775
|
...createTransferProps(this.props),
|
|
744
776
|
operation
|
|
@@ -22,6 +22,61 @@ export const getFileExt = fileName => {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const modeDirectoryMask = 0o170000
|
|
26
|
+
const modeDirectoryValue = 0o040000
|
|
27
|
+
|
|
28
|
+
const toIsDirectory = (stat) => {
|
|
29
|
+
if (!stat) {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (typeof stat.isDirectory === 'function') {
|
|
34
|
+
return stat.isDirectory()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof stat.isDirectory === 'boolean') {
|
|
38
|
+
return stat.isDirectory
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof stat.type === 'string') {
|
|
42
|
+
return stat.type === 'd'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (typeof stat.type === 'number') {
|
|
46
|
+
return stat.type === 2
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (typeof stat.mode === 'number') {
|
|
50
|
+
return (stat.mode & modeDirectoryMask) === modeDirectoryValue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const toIsSymbolicLink = (stat) => {
|
|
57
|
+
if (!stat) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof stat.isSymbolicLink === 'function') {
|
|
62
|
+
return stat.isSymbolicLink()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (typeof stat.isSymbolicLink === 'boolean') {
|
|
66
|
+
return stat.isSymbolicLink
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (typeof stat.type === 'string') {
|
|
70
|
+
return stat.type === 'l'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (typeof stat.type === 'number') {
|
|
74
|
+
return stat.type === 3
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
|
|
25
80
|
export const getFolderFromFilePath = (filePath, isRemote) => {
|
|
26
81
|
const sep = isRemote ? '/' : window.pre.sep
|
|
27
82
|
const arr = filePath.split(sep)
|
|
@@ -54,8 +109,8 @@ export const getLocalFileInfo = async (filePath) => {
|
|
|
54
109
|
type: 'local',
|
|
55
110
|
...getFolderFromFilePath(filePath, false),
|
|
56
111
|
id: generate(),
|
|
57
|
-
isDirectory: statr
|
|
58
|
-
isSymbolicLink: stat
|
|
112
|
+
isDirectory: toIsDirectory(statr),
|
|
113
|
+
isSymbolicLink: toIsSymbolicLink(stat)
|
|
59
114
|
}
|
|
60
115
|
}
|
|
61
116
|
|
|
@@ -71,6 +126,6 @@ export const getRemoteFileInfo = async (sftp, filePath) => {
|
|
|
71
126
|
type: 'remote',
|
|
72
127
|
...getFolderFromFilePath(filePath, true),
|
|
73
128
|
id: generate(),
|
|
74
|
-
isDirectory: stat
|
|
129
|
+
isDirectory: toIsDirectory(stat)
|
|
75
130
|
}
|
|
76
131
|
}
|
|
@@ -269,20 +269,23 @@ export default class SpiceSession extends PureComponent {
|
|
|
269
269
|
}
|
|
270
270
|
const cls = `spice-session-wrap session-v-wrap${scaleViewport ? ' scale-viewport' : ''}`
|
|
271
271
|
const contrlProps = this.getControlProps()
|
|
272
|
+
const sessProps = {
|
|
273
|
+
className: cls,
|
|
274
|
+
style: {
|
|
275
|
+
width: w + 'px',
|
|
276
|
+
height: h + 'px'
|
|
277
|
+
}
|
|
278
|
+
}
|
|
272
279
|
return (
|
|
273
280
|
<Spin spinning={loading}>
|
|
274
281
|
<div
|
|
275
|
-
|
|
276
|
-
style={{
|
|
277
|
-
width: w + 'px',
|
|
278
|
-
height: h + 'px'
|
|
279
|
-
}}
|
|
282
|
+
{...sessProps}
|
|
280
283
|
>
|
|
281
284
|
{this.renderControl()}
|
|
282
285
|
<RemoteFloatControl {...contrlProps} />
|
|
283
286
|
<div
|
|
284
287
|
style={wrapperStyle}
|
|
285
|
-
className='spice-scroll-wrapper'
|
|
288
|
+
className='spice-scroll-wrapper s-scroll-wrapper'
|
|
286
289
|
>
|
|
287
290
|
<div
|
|
288
291
|
ref={this.domRef}
|
|
@@ -1,11 +1,29 @@
|
|
|
1
|
-
.spice-session-wrap
|
|
2
|
-
canvas
|
|
3
|
-
width: 100% !important
|
|
4
|
-
object-fit: contain
|
|
5
|
-
.spice-scroll-wrapper
|
|
6
|
-
display block
|
|
7
|
-
.spice-scroll-wrapper
|
|
1
|
+
.spice-session-wrap
|
|
8
2
|
display: flex
|
|
9
3
|
flex-direction: column
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
align-items: center
|
|
5
|
+
.session-v-info
|
|
6
|
+
position: relative
|
|
7
|
+
width: 100%
|
|
8
|
+
z-index: 300
|
|
9
|
+
&.scale-viewport
|
|
10
|
+
.spice-scroll-wrapper
|
|
11
|
+
display flex
|
|
12
|
+
align-items center
|
|
13
|
+
justify-content center
|
|
14
|
+
> div
|
|
15
|
+
width 100% !important
|
|
16
|
+
height 100% !important
|
|
17
|
+
display flex
|
|
18
|
+
align-items center
|
|
19
|
+
justify-content center
|
|
20
|
+
canvas
|
|
21
|
+
width 100% !important
|
|
22
|
+
height 100% !important
|
|
23
|
+
max-width 100% !important
|
|
24
|
+
max-height 100% !important
|
|
25
|
+
object-fit contain
|
|
26
|
+
.spice-scroll-wrapper
|
|
27
|
+
position relative
|
|
28
|
+
background var(--main)
|
|
29
|
+
z-index 299
|
|
@@ -23,7 +23,6 @@ export default function BookmarkToolbar (props) {
|
|
|
23
23
|
const {
|
|
24
24
|
onNewBookmark,
|
|
25
25
|
onNewBookmarkGroup,
|
|
26
|
-
onImport,
|
|
27
26
|
onExport,
|
|
28
27
|
onSshConfigs,
|
|
29
28
|
bookmarkGroups,
|
|
@@ -121,7 +120,12 @@ export default function BookmarkToolbar (props) {
|
|
|
121
120
|
},
|
|
122
121
|
{
|
|
123
122
|
label: e('import'),
|
|
124
|
-
onClick:
|
|
123
|
+
onClick: () => {
|
|
124
|
+
const fileInput = document.querySelector('.upload-bookmark-icon')
|
|
125
|
+
if (fileInput) {
|
|
126
|
+
fileInput.click()
|
|
127
|
+
}
|
|
128
|
+
},
|
|
125
129
|
icon: <ImportOutlined />
|
|
126
130
|
},
|
|
127
131
|
{
|
|
@@ -680,10 +680,6 @@ export default class ItemListTree extends Component {
|
|
|
680
680
|
)
|
|
681
681
|
}
|
|
682
682
|
|
|
683
|
-
handleImport = () => {
|
|
684
|
-
document.querySelector('.upload-bookmark-icon input')?.click()
|
|
685
|
-
}
|
|
686
|
-
|
|
687
683
|
handleExport = () => {
|
|
688
684
|
document.querySelector('.download-bookmark-icon')?.click()
|
|
689
685
|
}
|
|
@@ -697,7 +693,6 @@ export default class ItemListTree extends Component {
|
|
|
697
693
|
<NewButtonsGroup
|
|
698
694
|
onNewBookmark={this.handleNewBookmark}
|
|
699
695
|
onNewBookmarkGroup={this.handleNewBookmarkGroup}
|
|
700
|
-
onImport={this.handleImport}
|
|
701
696
|
onExport={this.handleExport}
|
|
702
697
|
onSshConfigs={this.handleSshConfigs}
|
|
703
698
|
bookmarkGroups={this.props.bookmarkGroups}
|
|
@@ -18,6 +18,7 @@ import Modal from '../common/modal'
|
|
|
18
18
|
import { copy } from '../../common/clipboard'
|
|
19
19
|
import VncForm from './vnc-form'
|
|
20
20
|
import RemoteFloatControl from '../common/remote-float-control'
|
|
21
|
+
import './vnc.styl'
|
|
21
22
|
|
|
22
23
|
// noVNC module imports — loaded dynamically
|
|
23
24
|
async function loadVncModule () {
|
|
@@ -602,14 +603,17 @@ export default class VncSession extends PureComponent {
|
|
|
602
603
|
className: 'vnc-session-wrap session-v-wrap'
|
|
603
604
|
}
|
|
604
605
|
const contrlProps = this.getControlProps()
|
|
606
|
+
const sessProps = {
|
|
607
|
+
className: 'vnc-session-wrap',
|
|
608
|
+
style: {
|
|
609
|
+
width: w + 'px',
|
|
610
|
+
height: h + 'px'
|
|
611
|
+
}
|
|
612
|
+
}
|
|
605
613
|
return (
|
|
606
614
|
<Spin spinning={loading}>
|
|
607
615
|
<div
|
|
608
|
-
|
|
609
|
-
style={{
|
|
610
|
-
width: w + 'px',
|
|
611
|
-
height: h + 'px'
|
|
612
|
-
}}
|
|
616
|
+
{...sessProps}
|
|
613
617
|
>
|
|
614
618
|
{this.renderControl()}
|
|
615
619
|
<RemoteFloatControl
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
.vnc-session-wrap
|
|
2
|
+
display: flex
|
|
3
|
+
flex-direction: column
|
|
4
|
+
justify-content: center
|
|
5
|
+
align-items: center
|
|
6
|
+
.session-v-info
|
|
7
|
+
position: absolute
|
|
8
|
+
top: 0
|
|
9
|
+
left: 0
|
|
10
|
+
width: 100%
|
|
11
|
+
&.scale-viewport
|
|
12
|
+
.rdp-scroll-wrapper canvas
|
|
13
|
+
width: 100% !important
|
|
14
|
+
height: 100% !important
|
|
15
|
+
object-fit: contain
|
|
16
|
+
> div
|
|
17
|
+
background: transparent !important
|