@electerm/electerm-react 5.5.9 → 5.5.15
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/download.jsx +12 -0
- package/client/common/resolve.js +20 -14
- package/client/components/ai/ai-history-item.jsx +1 -1
- package/client/components/ai/ai-history.jsx +1 -0
- package/client/components/ai/ai-history.styl +23 -0
- package/client/components/bookmark-form/common/color-picker.styl +3 -0
- package/client/components/common/remote-pointer.js +49 -0
- package/client/components/common/remote-session-shell.jsx +137 -0
- package/client/components/common/remote-session-shell.styl +80 -0
- package/client/components/common/responsive-tabs.jsx +89 -0
- package/client/components/common/responsive-tabs.styl +34 -0
- package/client/components/rdp/rdp-session.jsx +94 -124
- package/client/components/rdp/rdp.styl +13 -36
- package/client/components/session/session.styl +0 -16
- package/client/components/setting-panel/setting-modal.jsx +8 -3
- package/client/components/sftp/sftp-entry.jsx +26 -0
- package/client/components/spice/spice-session.jsx +69 -72
- package/client/components/spice/spice.styl +10 -19
- package/client/components/theme/theme-form.jsx +18 -21
- package/client/components/theme/theme-form.styl +8 -10
- package/client/components/vnc/vnc-session.jsx +72 -104
- package/client/components/vnc/vnc.styl +25 -16
- package/client/store/setting.js +4 -0
- package/package.json +1 -1
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
DownloadOutlined
|
|
15
15
|
} from '@ant-design/icons'
|
|
16
16
|
import {
|
|
17
|
-
Spin,
|
|
18
17
|
Select,
|
|
19
18
|
Tooltip
|
|
20
19
|
} from 'antd'
|
|
@@ -23,6 +22,8 @@ import scanCode from './code-scan'
|
|
|
23
22
|
import resolutions from './resolutions'
|
|
24
23
|
import { readClipboardAsync } from '../../common/clipboard'
|
|
25
24
|
import RemoteFloatControl from '../common/remote-float-control'
|
|
25
|
+
import RemoteSessionShell from '../common/remote-session-shell'
|
|
26
|
+
import { eventToRemotePos } from '../common/remote-pointer'
|
|
26
27
|
import SwitchLabel from '../common/switch'
|
|
27
28
|
import HelpIcon from '../common/help-icon'
|
|
28
29
|
import Modal from '../common/modal'
|
|
@@ -102,6 +103,12 @@ export default class RdpSession extends PureComponent {
|
|
|
102
103
|
|
|
103
104
|
runInitScript = () => { }
|
|
104
105
|
|
|
106
|
+
// fullscreen always fills the screen, the per tab setting only applies
|
|
107
|
+
// windowed; the switch is not reachable in fullscreen anyway
|
|
108
|
+
getFit = () => {
|
|
109
|
+
return !!this.props.fullscreen || !!this.state.scaleViewport
|
|
110
|
+
}
|
|
111
|
+
|
|
105
112
|
setStatus = status => {
|
|
106
113
|
const id = this.props.tab?.id
|
|
107
114
|
this.props.editTab(id, {
|
|
@@ -458,30 +465,14 @@ export default class RdpSession extends PureComponent {
|
|
|
458
465
|
canvas.addEventListener('mousemove', (e) => {
|
|
459
466
|
if (!this.session) return
|
|
460
467
|
try {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const canvasRatio = canvas.width / canvas.height
|
|
470
|
-
let renderWidth, renderHeight
|
|
471
|
-
if (containerRatio > canvasRatio) {
|
|
472
|
-
renderHeight = rect.height
|
|
473
|
-
renderWidth = rect.height * canvasRatio
|
|
474
|
-
offsetX = (rect.width - renderWidth) / 2
|
|
475
|
-
} else {
|
|
476
|
-
renderWidth = rect.width
|
|
477
|
-
renderHeight = rect.width / canvasRatio
|
|
478
|
-
offsetY = (rect.height - renderHeight) / 2
|
|
479
|
-
}
|
|
480
|
-
scaleX = canvas.width / renderWidth
|
|
481
|
-
scaleY = canvas.height / renderHeight
|
|
482
|
-
}
|
|
483
|
-
const x = Math.round((e.clientX - rect.left - offsetX) * scaleX)
|
|
484
|
-
const y = Math.round((e.clientY - rect.top - offsetY) * scaleY)
|
|
468
|
+
// the canvas is rendered with object-fit: contain, so the pointer has
|
|
469
|
+
// to be mapped through the real surface rect (see remote-pointer.js)
|
|
470
|
+
const { x, y } = eventToRemotePos(
|
|
471
|
+
e,
|
|
472
|
+
canvas,
|
|
473
|
+
canvas.width,
|
|
474
|
+
canvas.height
|
|
475
|
+
)
|
|
485
476
|
const event = window.ironRdp.DeviceEvent.mouseMove(x, y)
|
|
486
477
|
const tx = new window.ironRdp.InputTransaction()
|
|
487
478
|
tx.addEvent(event)
|
|
@@ -705,12 +696,7 @@ export default class RdpSession extends PureComponent {
|
|
|
705
696
|
this.setupInputHandlers()
|
|
706
697
|
}
|
|
707
698
|
|
|
708
|
-
|
|
709
|
-
const contrlProps = this.getControlProps({
|
|
710
|
-
fixedPosition: false,
|
|
711
|
-
showExitFullscreen: false,
|
|
712
|
-
className: 'mg1l'
|
|
713
|
-
})
|
|
699
|
+
renderControlLeft = () => {
|
|
714
700
|
const {
|
|
715
701
|
id,
|
|
716
702
|
hasRemoteFiles,
|
|
@@ -730,61 +716,67 @@ export default class RdpSession extends PureComponent {
|
|
|
730
716
|
const uploadTitle = window.translate('upload') || 'Upload files to remote'
|
|
731
717
|
const downloadTitle = window.translate('download') || 'Download files from remote'
|
|
732
718
|
return (
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
{
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
719
|
+
<>
|
|
720
|
+
<ReloadOutlined
|
|
721
|
+
onClick={this.handleReInit}
|
|
722
|
+
className='mg2r mg1l pointer'
|
|
723
|
+
/>
|
|
724
|
+
<Select
|
|
725
|
+
{...sleProps}
|
|
726
|
+
>
|
|
727
|
+
{
|
|
728
|
+
this.getAllRes().map(d => {
|
|
729
|
+
const v = d.id
|
|
730
|
+
return (
|
|
731
|
+
<Option
|
|
732
|
+
key={v}
|
|
733
|
+
value={v}
|
|
734
|
+
>
|
|
735
|
+
{d.width}x{d.height}
|
|
736
|
+
</Option>
|
|
737
|
+
)
|
|
738
|
+
})
|
|
739
|
+
}
|
|
740
|
+
</Select>
|
|
741
|
+
<EditOutlined
|
|
742
|
+
onClick={this.handleEditResolutions}
|
|
743
|
+
className='mg2r mg1l pointer'
|
|
744
|
+
/>
|
|
745
|
+
{this.renderInfo()}
|
|
746
|
+
<SwitchLabel
|
|
747
|
+
{...scaleProps}
|
|
748
|
+
/>
|
|
749
|
+
<Tooltip title={uploadTitle}>
|
|
750
|
+
<UploadOutlined
|
|
751
|
+
onClick={this.handleUploadButtonClick}
|
|
752
|
+
className={`mg1r mg2l pointer rdp-file-transfer-btn${uploadReady ? ' rdp-download-flash' : ''}`}
|
|
765
753
|
/>
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
</Tooltip>
|
|
772
|
-
<Tooltip title={downloadTitle}>
|
|
773
|
-
<DownloadOutlined
|
|
774
|
-
onClick={this.handleDownloadButtonClick}
|
|
775
|
-
className={`mg2r mg1l pointer rdp-file-transfer-btn${hasRemoteFiles ? ' rdp-download-flash' : ' rdp-download-disabled'}`}
|
|
776
|
-
/>
|
|
777
|
-
</Tooltip>
|
|
778
|
-
<HelpIcon
|
|
779
|
-
link='https://github.com/electerm/electerm/wiki/RDP-File-Transfer'
|
|
780
|
-
className='mg2r mg1l'
|
|
754
|
+
</Tooltip>
|
|
755
|
+
<Tooltip title={downloadTitle}>
|
|
756
|
+
<DownloadOutlined
|
|
757
|
+
onClick={this.handleDownloadButtonClick}
|
|
758
|
+
className={`mg2r mg1l pointer rdp-file-transfer-btn${hasRemoteFiles ? ' rdp-download-flash' : ' rdp-download-disabled'}`}
|
|
781
759
|
/>
|
|
782
|
-
</
|
|
783
|
-
<
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
760
|
+
</Tooltip>
|
|
761
|
+
<HelpIcon
|
|
762
|
+
link='https://github.com/electerm/electerm/wiki/RDP-File-Transfer'
|
|
763
|
+
className='mg2r mg1l'
|
|
764
|
+
/>
|
|
765
|
+
</>
|
|
766
|
+
)
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
renderControlRight = () => {
|
|
770
|
+
const contrlProps = this.getControlProps({
|
|
771
|
+
fixedPosition: false,
|
|
772
|
+
showExitFullscreen: false,
|
|
773
|
+
className: 'mg1l'
|
|
774
|
+
})
|
|
775
|
+
return (
|
|
776
|
+
<>
|
|
777
|
+
{this.props.fullscreenIcon()}
|
|
778
|
+
<RemoteFloatControl {...contrlProps} />
|
|
779
|
+
</>
|
|
788
780
|
)
|
|
789
781
|
}
|
|
790
782
|
|
|
@@ -802,48 +794,26 @@ export default class RdpSession extends PureComponent {
|
|
|
802
794
|
}
|
|
803
795
|
|
|
804
796
|
render () {
|
|
805
|
-
const { width
|
|
806
|
-
const { width, height, loading, scaleViewport } = this.state
|
|
807
|
-
const innerWidth = w - 10
|
|
808
|
-
const innerHeight = h - 80
|
|
809
|
-
const wrapperStyle = {
|
|
810
|
-
width: innerWidth + 'px',
|
|
811
|
-
height: innerHeight + 'px',
|
|
812
|
-
overflow: scaleViewport ? 'hidden' : 'auto'
|
|
813
|
-
}
|
|
814
|
-
const canvasProps = {
|
|
815
|
-
width,
|
|
816
|
-
height,
|
|
817
|
-
tabIndex: 0
|
|
818
|
-
}
|
|
819
|
-
const cls = `rdp-session-wrap session-v-wrap${scaleViewport ? ' scale-viewport' : ''}`
|
|
820
|
-
const sessProps = {
|
|
821
|
-
className: cls,
|
|
822
|
-
style: {
|
|
823
|
-
width: w + 'px',
|
|
824
|
-
height: h + 'px'
|
|
825
|
-
}
|
|
826
|
-
}
|
|
797
|
+
const { width, height, loading } = this.state
|
|
827
798
|
const controlProps = this.getControlProps()
|
|
828
799
|
return (
|
|
829
|
-
<
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
</Spin>
|
|
800
|
+
<RemoteSessionShell
|
|
801
|
+
loading={loading}
|
|
802
|
+
fit={this.getFit()}
|
|
803
|
+
wrapClassName='rdp-session-wrap'
|
|
804
|
+
controlLeft={this.renderControlLeft()}
|
|
805
|
+
controlRight={this.renderControlRight()}
|
|
806
|
+
floatControl={<RemoteFloatControl {...controlProps} />}
|
|
807
|
+
overlay={this.renderCredPrompt()}
|
|
808
|
+
>
|
|
809
|
+
<canvas
|
|
810
|
+
className='rdp-canvas'
|
|
811
|
+
width={width}
|
|
812
|
+
height={height}
|
|
813
|
+
tabIndex={0}
|
|
814
|
+
ref={this.canvasRef}
|
|
815
|
+
/>
|
|
816
|
+
</RemoteSessionShell>
|
|
847
817
|
)
|
|
848
818
|
}
|
|
849
819
|
}
|
|
@@ -1,36 +1,18 @@
|
|
|
1
|
+
// RDP only styling — the session layout (control bar, viewport, fit mode)
|
|
2
|
+
// lives in the shared remote session shell: common/remote-session-shell.styl
|
|
3
|
+
|
|
4
|
+
// fit: the canvas box fills the viewport and the remote surface is letterboxed
|
|
5
|
+
// inside it with object-fit: contain, so the aspect ratio is kept and the
|
|
6
|
+
// letterbox keeps the app background
|
|
1
7
|
.rdp-session-wrap
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
8
|
+
&.fit
|
|
9
|
+
.rdp-canvas
|
|
10
|
+
object-fit contain
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
12
|
+
// fit off: rendered at the remote desktop size, the viewport scrolls
|
|
13
|
+
.rdp-canvas
|
|
14
|
+
display block
|
|
15
|
+
outline none
|
|
34
16
|
|
|
35
17
|
.rdp-file-transfer-btn
|
|
36
18
|
font-size: 16px
|
|
@@ -45,9 +27,6 @@
|
|
|
45
27
|
&:hover
|
|
46
28
|
color: var(--text)
|
|
47
29
|
|
|
48
|
-
canvas
|
|
49
|
-
outline: none
|
|
50
|
-
|
|
51
30
|
@keyframes rdp-download-flash
|
|
52
31
|
0%, 100%
|
|
53
32
|
color: var(--primary)
|
|
@@ -58,5 +37,3 @@ canvas
|
|
|
58
37
|
|
|
59
38
|
.rdp-download-flash
|
|
60
39
|
animation: rdp-download-flash 1s ease-in-out infinite
|
|
61
|
-
|
|
62
|
-
|
|
@@ -71,22 +71,6 @@
|
|
|
71
71
|
overflow hidden
|
|
72
72
|
.session-v-info
|
|
73
73
|
line-height 30px
|
|
74
|
-
.vnc-scroll-wrapper
|
|
75
|
-
position relative
|
|
76
|
-
background var(--main)
|
|
77
|
-
z-index 299
|
|
78
|
-
&::-webkit-scrollbar
|
|
79
|
-
width 16px
|
|
80
|
-
height 16px
|
|
81
|
-
background var(--main-darker)
|
|
82
|
-
&::-webkit-scrollbar-track
|
|
83
|
-
background var(--main-darker)
|
|
84
|
-
box-shadow inset 0 0 5px var(--main-darker)
|
|
85
|
-
&::-webkit-scrollbar-thumb
|
|
86
|
-
background var(--primary)
|
|
87
|
-
border-radius 0
|
|
88
|
-
&::-webkit-scrollbar-corner
|
|
89
|
-
background var(--main-darker)
|
|
90
74
|
|
|
91
75
|
.not-split-view > .ant-splitter-bar .ant-splitter-bar-dragger
|
|
92
76
|
display none
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import { auto } from 'manate/react'
|
|
6
6
|
import { pick } from 'lodash-es'
|
|
7
|
-
import {
|
|
7
|
+
import { Spin } from 'antd'
|
|
8
8
|
import { lazy, Suspense } from 'react'
|
|
9
9
|
import SettingModal from './setting-wrap'
|
|
10
10
|
import LazyBoundary from '../common/lazy-boundary'
|
|
11
|
+
import ResponsiveTabs from '../common/responsive-tabs'
|
|
11
12
|
import {
|
|
12
13
|
settingMap,
|
|
13
14
|
modals,
|
|
@@ -132,11 +133,15 @@ export default auto(function SettingModalWrap (props) {
|
|
|
132
133
|
onChange: store.handleChangeSettingTab,
|
|
133
134
|
destroyOnHidden: true,
|
|
134
135
|
className: 'setting-tabs',
|
|
135
|
-
type: 'card'
|
|
136
|
+
type: 'card',
|
|
137
|
+
// the panel turns into a phone style drill-down at 800px (same
|
|
138
|
+
// breakpoint as the media query in setting-wrap.styl), collapse the tab
|
|
139
|
+
// bar there too instead of letting antd hide the overflow tabs
|
|
140
|
+
isMobile: store.innerWidth <= settingPanelMobileBreakpoint
|
|
136
141
|
}
|
|
137
142
|
return (
|
|
138
143
|
<>
|
|
139
|
-
<
|
|
144
|
+
<ResponsiveTabs
|
|
140
145
|
{...tabsProps}
|
|
141
146
|
/>
|
|
142
147
|
<LazyBoundary>
|
|
@@ -293,11 +293,29 @@ export default class Sftp extends Component {
|
|
|
293
293
|
if (!path && this.sftp) {
|
|
294
294
|
path = await this.getPwd(this.props.tab.username)
|
|
295
295
|
}
|
|
296
|
+
if (!path) {
|
|
297
|
+
// No sftp connection yet (still connecting, or the previous attempt
|
|
298
|
+
// failed). There is no remote home to resolve, and navigating to an
|
|
299
|
+
// unknown path would blank the panel, so (re)start the connection and
|
|
300
|
+
// let its own listing decide the path.
|
|
301
|
+
if (
|
|
302
|
+
!this.sftp &&
|
|
303
|
+
!this.state.loadingSftp &&
|
|
304
|
+
!this.state.remoteLoading
|
|
305
|
+
) {
|
|
306
|
+
this.initData(this.terminalId, this.port)
|
|
307
|
+
}
|
|
308
|
+
return
|
|
309
|
+
}
|
|
296
310
|
path = normalizeRemotePath(path)
|
|
297
311
|
} else {
|
|
298
312
|
path = this.getLocalHome()
|
|
299
313
|
}
|
|
300
314
|
|
|
315
|
+
if (typeof path !== 'string' || !path) {
|
|
316
|
+
return
|
|
317
|
+
}
|
|
318
|
+
|
|
301
319
|
this.setState({
|
|
302
320
|
[n]: path,
|
|
303
321
|
[nt]: path
|
|
@@ -1033,6 +1051,9 @@ export default class Sftp extends Component {
|
|
|
1033
1051
|
goParent = (type) => {
|
|
1034
1052
|
const n = `${type}Path`
|
|
1035
1053
|
const p = this.state[n]
|
|
1054
|
+
if (typeof p !== 'string' || !p) {
|
|
1055
|
+
return
|
|
1056
|
+
}
|
|
1036
1057
|
let np = resolve(p, '..')
|
|
1037
1058
|
if (type === typeMap.remote) {
|
|
1038
1059
|
np = normalizeRemotePath(np)
|
|
@@ -1136,6 +1157,11 @@ export default class Sftp extends Component {
|
|
|
1136
1157
|
|
|
1137
1158
|
renderParentItem = (type) => {
|
|
1138
1159
|
const currentPath = this.state[`${type}Path`]
|
|
1160
|
+
// The path can be momentarily unknown (e.g. the remote home while the sftp
|
|
1161
|
+
// client is still connecting). Show no parent item rather than throwing.
|
|
1162
|
+
if (typeof currentPath !== 'string') {
|
|
1163
|
+
return null
|
|
1164
|
+
}
|
|
1139
1165
|
const parentPath = resolve(currentPath, '..')
|
|
1140
1166
|
// Don't render parent item if we're at the root
|
|
1141
1167
|
if (parentPath === currentPath) {
|
|
@@ -9,11 +9,10 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
ReloadOutlined
|
|
11
11
|
} from '@ant-design/icons'
|
|
12
|
-
import {
|
|
13
|
-
Spin
|
|
14
|
-
} from 'antd'
|
|
15
12
|
import * as ls from '../../common/safe-local-storage'
|
|
16
13
|
import RemoteFloatControl from '../common/remote-float-control'
|
|
14
|
+
import RemoteSessionShell from '../common/remote-session-shell'
|
|
15
|
+
import { eventToRemotePos } from '../common/remote-pointer'
|
|
17
16
|
import SwitchLabel from '../common/switch'
|
|
18
17
|
import './spice.styl'
|
|
19
18
|
|
|
@@ -53,24 +52,19 @@ export default class SpiceSession extends PureComponent {
|
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
// The spice client sends event.offsetX/offsetY to the remote as-is, but when
|
|
56
|
-
// the canvas is CSS-scaled (
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
// letterboxed by object-fit: contain) before the lib's handlers run.
|
|
55
|
+
// the canvas is CSS-scaled (fit mode), those are display pixels instead of
|
|
56
|
+
// remote surface pixels. On high resolution remote screens the pointer then
|
|
57
|
+
// only reaches the top-left region. Convert display coords to surface coords
|
|
58
|
+
// before the lib's handlers run (same mapping as rdp, see remote-pointer.js).
|
|
61
59
|
handleMouseScaleFix = (e) => {
|
|
62
60
|
const canvas = this.domRef.current?.querySelector('canvas')
|
|
63
61
|
if (!canvas || !canvas.width || !canvas.height) return
|
|
64
|
-
const
|
|
65
|
-
if (!rect.width || !rect.height) return
|
|
66
|
-
const scale = Math.min(rect.width / canvas.width, rect.height / canvas.height)
|
|
67
|
-
const x = (e.clientX - rect.left - (rect.width - canvas.width * scale) / 2) / scale
|
|
68
|
-
const y = (e.clientY - rect.top - (rect.height - canvas.height * scale) / 2) / scale
|
|
62
|
+
const { x, y } = eventToRemotePos(e, canvas, canvas.width, canvas.height)
|
|
69
63
|
Object.defineProperty(e, 'offsetX', {
|
|
70
|
-
value:
|
|
64
|
+
value: x
|
|
71
65
|
})
|
|
72
66
|
Object.defineProperty(e, 'offsetY', {
|
|
73
|
-
value:
|
|
67
|
+
value: y
|
|
74
68
|
})
|
|
75
69
|
}
|
|
76
70
|
|
|
@@ -123,13 +117,30 @@ export default class SpiceSession extends PureComponent {
|
|
|
123
117
|
}
|
|
124
118
|
|
|
125
119
|
calcCanvasSize = () => {
|
|
126
|
-
const { width, height } = this.
|
|
120
|
+
const { width, height } = this.viewSize || {}
|
|
121
|
+
if (width && height) {
|
|
122
|
+
return { width, height }
|
|
123
|
+
}
|
|
124
|
+
const { width: w, height: h } = this.props
|
|
127
125
|
return {
|
|
128
|
-
width:
|
|
129
|
-
height:
|
|
126
|
+
width: w - 10,
|
|
127
|
+
height: h - 80
|
|
130
128
|
}
|
|
131
129
|
}
|
|
132
130
|
|
|
131
|
+
// fullscreen always fills the screen, the per tab setting only applies
|
|
132
|
+
// windowed; the switch is not reachable in fullscreen anyway
|
|
133
|
+
getFit = () => {
|
|
134
|
+
if (this.props.fullscreen) {
|
|
135
|
+
return true
|
|
136
|
+
}
|
|
137
|
+
return !!this.state.scaleViewport
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
handleViewportResize = (width, height) => {
|
|
141
|
+
this.viewSize = { width, height }
|
|
142
|
+
}
|
|
143
|
+
|
|
133
144
|
handleSendCtrlAltDel = () => {
|
|
134
145
|
if (this.spiceConn && window.spiceHtml5?.sendCtrlAltDel) {
|
|
135
146
|
window.spiceHtml5.sendCtrlAltDel(this.spiceConn)
|
|
@@ -163,12 +174,7 @@ export default class SpiceSession extends PureComponent {
|
|
|
163
174
|
}
|
|
164
175
|
}
|
|
165
176
|
|
|
166
|
-
|
|
167
|
-
const contrlProps = this.getControlProps({
|
|
168
|
-
fixedPosition: false,
|
|
169
|
-
showExitFullscreen: false,
|
|
170
|
-
className: 'mg1l'
|
|
171
|
-
})
|
|
177
|
+
renderControlLeft = () => {
|
|
172
178
|
const scaleProps = {
|
|
173
179
|
checked: this.state.scaleViewport,
|
|
174
180
|
onChange: this.handleScaleViewChange,
|
|
@@ -176,22 +182,30 @@ export default class SpiceSession extends PureComponent {
|
|
|
176
182
|
className: 'mg1l'
|
|
177
183
|
}
|
|
178
184
|
return (
|
|
179
|
-
|
|
180
|
-
<
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
185
|
+
<>
|
|
186
|
+
<ReloadOutlined
|
|
187
|
+
onClick={this.handleReInit}
|
|
188
|
+
className='mg2r mg1l pointer'
|
|
189
|
+
/>
|
|
190
|
+
{this.renderInfo()}
|
|
191
|
+
<SwitchLabel
|
|
192
|
+
{...scaleProps}
|
|
193
|
+
/>
|
|
194
|
+
</>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
renderControlRight = () => {
|
|
199
|
+
const contrlProps = this.getControlProps({
|
|
200
|
+
fixedPosition: false,
|
|
201
|
+
showExitFullscreen: false,
|
|
202
|
+
className: 'mg1l'
|
|
203
|
+
})
|
|
204
|
+
return (
|
|
205
|
+
<>
|
|
206
|
+
{this.props.fullscreenIcon()}
|
|
207
|
+
<RemoteFloatControl {...contrlProps} />
|
|
208
|
+
</>
|
|
195
209
|
)
|
|
196
210
|
}
|
|
197
211
|
|
|
@@ -298,41 +312,24 @@ export default class SpiceSession extends PureComponent {
|
|
|
298
312
|
}
|
|
299
313
|
|
|
300
314
|
render () {
|
|
301
|
-
const {
|
|
302
|
-
const { loading, scaleViewport } = this.state
|
|
303
|
-
const { width: innerWidth, height: innerHeight } = this.calcCanvasSize()
|
|
304
|
-
const wrapperStyle = {
|
|
305
|
-
width: innerWidth + 'px',
|
|
306
|
-
height: innerHeight + 'px',
|
|
307
|
-
overflow: scaleViewport ? 'hidden' : 'auto'
|
|
308
|
-
}
|
|
309
|
-
const cls = `spice-session-wrap session-v-wrap${scaleViewport ? ' scale-viewport' : ''}`
|
|
315
|
+
const { loading } = this.state
|
|
310
316
|
const contrlProps = this.getControlProps()
|
|
311
|
-
const sessProps = {
|
|
312
|
-
className: cls,
|
|
313
|
-
style: {
|
|
314
|
-
width: w + 'px',
|
|
315
|
-
height: h + 'px'
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
317
|
return (
|
|
319
|
-
<
|
|
318
|
+
<RemoteSessionShell
|
|
319
|
+
loading={loading}
|
|
320
|
+
fit={this.getFit()}
|
|
321
|
+
wrapClassName='spice-session-wrap'
|
|
322
|
+
onViewportResize={this.handleViewportResize}
|
|
323
|
+
controlLeft={this.renderControlLeft()}
|
|
324
|
+
controlRight={this.renderControlRight()}
|
|
325
|
+
floatControl={<RemoteFloatControl {...contrlProps} />}
|
|
326
|
+
>
|
|
320
327
|
<div
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
{this.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
style={wrapperStyle}
|
|
327
|
-
className='spice-scroll-wrapper s-scroll-wrapper'
|
|
328
|
-
>
|
|
329
|
-
<div
|
|
330
|
-
ref={this.domRef}
|
|
331
|
-
id={this.screenId}
|
|
332
|
-
/>
|
|
333
|
-
</div>
|
|
334
|
-
</div>
|
|
335
|
-
</Spin>
|
|
328
|
+
className='spice-session-mount'
|
|
329
|
+
id={this.screenId}
|
|
330
|
+
ref={this.domRef}
|
|
331
|
+
/>
|
|
332
|
+
</RemoteSessionShell>
|
|
336
333
|
)
|
|
337
334
|
}
|
|
338
335
|
}
|
|
@@ -1,29 +1,20 @@
|
|
|
1
|
+
// SPICE only styling — the session layout (control bar, viewport, fit mode)
|
|
2
|
+
// lives in the shared remote session shell: common/remote-session-shell.styl
|
|
1
3
|
.spice-session-wrap
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
.spice-scroll-wrapper
|
|
4
|
+
// fit: the mount box fills the viewport and the canvas (sized by the spice
|
|
5
|
+
// client to the remote resolution) is scaled to it, keeping the ratio.
|
|
6
|
+
// !important because the client writes the surface size into the mount box
|
|
7
|
+
// inline style (`screen_id` height = surface height).
|
|
8
|
+
&.fit
|
|
9
|
+
.spice-session-mount
|
|
10
|
+
width 100% !important
|
|
11
|
+
height 100% !important
|
|
11
12
|
display flex
|
|
12
13
|
align-items center
|
|
13
14
|
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
15
|
canvas
|
|
21
16
|
width 100% !important
|
|
22
17
|
height 100% !important
|
|
23
18
|
max-width 100% !important
|
|
24
19
|
max-height 100% !important
|
|
25
20
|
object-fit contain
|
|
26
|
-
.spice-scroll-wrapper
|
|
27
|
-
position relative
|
|
28
|
-
background var(--main)
|
|
29
|
-
z-index 299
|