1 | <template>
|
2 | <div class='guacamoleClient' :class="[viewState, clientState, {'hide-overflow': options.autoScale, 'no-control': !control} ]" ref="main">
|
3 |
|
4 | <div class="guac_client__header guac_client-slot">
|
5 | <slot name="header"/>
|
6 | </div>
|
7 |
|
8 | <div class='flex-expand display-wrapper' ref="displayWrapper">
|
9 |
|
10 | <img class='static-image' :src="staticImage.src" v-show="staticImage.src && !isConnected" >
|
11 | <div class="static-overlay" v-show="!isConnected"/>
|
12 |
|
13 | <div v-show='isConnected'
|
14 | id='display'
|
15 | @click='hideNav'
|
16 | ref="display"
|
17 | />
|
18 |
|
19 | <guac-options-drawer
|
20 | :options="options"
|
21 | :viewState="viewState"
|
22 | :client="client"
|
23 | :resolution="resolution"
|
24 | :control="control"
|
25 | @setClipboard="setClipboard"
|
26 | @disconnect="disconnect"
|
27 | @connect="connect"
|
28 | @exitFullscreen="exitFullscreen"
|
29 | @resolutionChange="resolutionChange"
|
30 | v-if="!hideOptions"
|
31 | />
|
32 |
|
33 | <transition name="'fade'">
|
34 | <div class="guac_client-status guac-center" v-show="!isConnected">
|
35 | <guac-notification :status="options.clientState" :options="options" :message="message ">
|
36 | <slot name="status" slot="statusOverride" />
|
37 | </guac-notification>
|
38 | </div>
|
39 | </transition>
|
40 |
|
41 |
|
42 | </div>
|
43 |
|
44 | <div class="guac_client__footer guac_client-slot">
|
45 | <slot v-show='!isFullscreen' name="footer"/>
|
46 | </div>
|
47 |
|
48 | </div>
|
49 | </template>
|
50 |
|
51 | <script>
|
52 |
|
53 |
|
54 | import GuacDisplay from './components/GuacDisplay'
|
55 | import GuacConnection from './components/GuacConnection'
|
56 | import GuacState from './components/GuacState'
|
57 | import GuacInput from './components/GuacInput'
|
58 | import GuacErrors from './components/GuacErrors'
|
59 | import fscreen from 'fscreen'
|
60 |
|
61 |
|
62 | import GuacOptionsDrawer from './components/GuacOptionsDrawer'
|
63 | import GuacNotification from './components/GuacNotification'
|
64 |
|
65 | import Guacamole from 'guacamole-common-js'
|
66 | export default {
|
67 | components: {GuacOptionsDrawer, GuacNotification},
|
68 | props: { connectionID: { default: null },
|
69 | control: { default: true },
|
70 | webSocketTunnel: { default: null },
|
71 | httpTunnel: { default: null },
|
72 | defaultView: { default: 'thumbnail' },
|
73 | resolution: { default: '1920x1080' },
|
74 | hideOptions: { default: false }
|
75 | },
|
76 |
|
77 | name: 'GuacamoleClient',
|
78 |
|
79 | mixins: [ GuacDisplay, GuacConnection, GuacState, GuacInput ],
|
80 |
|
81 | beforeDestroy: function(){
|
82 | try{ this.tunnel.disconnect() }catch(err){ }
|
83 | try{ this.client.disconnect() }catch(err){ }
|
84 |
|
85 | this.tunnel = this.display = this.client = this.keyboard = this.error = null;
|
86 | this.staticImage = this.options = this.errors = null;
|
87 | },
|
88 |
|
89 | data () {
|
90 | return {
|
91 | errors: GuacErrors.ERRORS,
|
92 | options: {
|
93 | showOptionsDrawer: false,
|
94 | showConnectionStatus: true,
|
95 | autoScale: true,
|
96 | scale: 1,
|
97 | tunnelState: 'Not Connected',
|
98 | clientState: 'Not Connected'
|
99 | },
|
100 | tunnel: null,
|
101 | staticImage: {
|
102 | src: null,
|
103 | width: null,
|
104 | height: null
|
105 | },
|
106 | client: null,
|
107 | display: null,
|
108 | keyboard: null,
|
109 | mouse: null,
|
110 | error: null,
|
111 | viewState: this.defaultView,
|
112 | updateActivity: _.throttle(this.emitActivityUpdate, 5000),
|
113 | }
|
114 | },
|
115 | methods: {
|
116 |
|
117 | updateStaticImage () {
|
118 | if (!this.$refs.display || !this.display || !this.isConnected) return;
|
119 | this.staticImage.src = this.display.flatten().toDataURL('image/png')
|
120 | },
|
121 |
|
122 | fullscreen() {
|
123 | fscreen.addEventListener('fullscreenchange', this.fullScreedExited, false)
|
124 | fscreen.requestFullscreen(this.$refs.displayWrapper)
|
125 | setTimeout(this.adjustScale, 1000)
|
126 |
|
127 |
|
128 | },
|
129 |
|
130 | fullScreedExited(){
|
131 | if (!fscreen.fullscreenElement) {
|
132 | fscreen.removeEventListener('fullscreenchange', this.fullScreedExited, false)
|
133 | this.viewState = 'full'
|
134 | }
|
135 | },
|
136 |
|
137 | emitActivityUpdate(){
|
138 | this.$emit('activity', Date.now())
|
139 | },
|
140 |
|
141 | resolutionChange(data){
|
142 | this.$emit('resolutionChange', data)
|
143 | },
|
144 |
|
145 | exitFullscreen(){
|
146 | fscreen.exitFullscreen();
|
147 | this.viewState = 'full';
|
148 | },
|
149 |
|
150 | hideNav () {
|
151 | this.options.showOptionsDrawer = false
|
152 | this.$refs.display.focus()
|
153 | },
|
154 |
|
155 | setViewState(state) {
|
156 | this.viewState = state
|
157 | if (this.viewState == 'fullscreen'){
|
158 | this.fullscreen()
|
159 | }
|
160 | }
|
161 | },
|
162 | computed: {
|
163 | isFullscreen () {
|
164 | return this.viewState == 'fullscreen'
|
165 | },
|
166 | isConnected () {
|
167 | return this.connectionID && this.status == 'Connected'
|
168 | },
|
169 |
|
170 | enlargedView () {
|
171 | if (!this.isConnected) return false
|
172 | if (this.viewState == 'thumbnail') return false
|
173 | return true
|
174 | },
|
175 |
|
176 | clientState () {
|
177 | return this.options.clientState.toLowerCase()
|
178 | },
|
179 |
|
180 | message () {
|
181 | if (this.error) {
|
182 |
|
183 | let message = this.error.message ? this.error.message : 'Connection has been disconnected'
|
184 | console.log('Guacamole Error: ' + this.error.message)
|
185 |
|
186 |
|
187 | if (this.error.reconnect){
|
188 | this.disconnect()
|
189 | this.$emit('reconnect')
|
190 | console.log('Guacamole Error: ' + this.error.message)
|
191 | return
|
192 | }
|
193 |
|
194 | return message
|
195 | }
|
196 | return 'Waiting for Guacamole Connection'
|
197 | }
|
198 | },
|
199 | watch: {
|
200 | 'viewState': function (newState) {
|
201 | this.$nextTick( () => {
|
202 | this.adjustScale()
|
203 | this.$emit('viewStateChanged', newState)
|
204 | })
|
205 | let interval = setInterval(this.adjustScale, 500)
|
206 | setTimeout(() => {
|
207 | clearInterval(interval)
|
208 | }, 10000)
|
209 | },
|
210 | 'options.showOptionsDrawer': function (showOptionsDrawer) {
|
211 | if (!this.keyboard) return;
|
212 |
|
213 | if (!showOptionsDrawer) {
|
214 | this.keyboard.onkeydown = (keysym) => this.client.sendKeyEvent(1, keysym)
|
215 | this.keyboard.onkeyup = (keysym) => this.client.sendKeyEvent(0, keysym)
|
216 | } else {
|
217 | this.keyboard.onkeydown = null
|
218 | this.keyboard.onkeyup = null
|
219 | }
|
220 | }
|
221 | },
|
222 | mounted() {
|
223 | this.disconnect()
|
224 | this.connect()
|
225 | setInterval(this.updateStaticImage, 5000)
|
226 | }
|
227 | }
|
228 | </script>
|
229 |
|
230 | <style scoped>
|
231 |
|
232 | .static-image{
|
233 | position: absolute;
|
234 | height: 100%;
|
235 | }
|
236 |
|
237 |
|
238 | .static-overlay{
|
239 | width: 100%;
|
240 | height: 100%;
|
241 | position: absolute;
|
242 | background-color: #676a6cb5
|
243 | }
|
244 |
|
245 | .guacamoleClient {
|
246 | position: inherit;
|
247 | padding: 0px;
|
248 | border: 0px;
|
249 | display: flex;
|
250 | flex-flow: column nowrap;
|
251 |
|
252 | }
|
253 |
|
254 | .guacamoleClient.thumbnail {
|
255 | width: 450px;
|
256 | z-index: 7;
|
257 | border: 1px solid #e8e8e8;
|
258 | border-radius: 5px;
|
259 | box-sizing: border-box;
|
260 | }
|
261 |
|
262 | .guacamoleClient.full {
|
263 | position: fixed !important;
|
264 | top: 0px !important;
|
265 | left: 0px !important;
|
266 |
|
267 | height: 100% !important;
|
268 | width: 100% !important;
|
269 | z-index: 9999;
|
270 | }
|
271 |
|
272 |
|
273 |
|
274 | .display-wrapper{
|
275 | background-color: #3d3d5c;;
|
276 | display: flex;
|
277 | width: 100%;
|
278 | flex-direction: column;
|
279 | justify-content: center;
|
280 | overflow: hidden;
|
281 | z-index: 8;
|
282 | position: relative;
|
283 | }
|
284 |
|
285 | .thumbnail .display-wrapper{
|
286 | height: 260px;
|
287 | width: 450px;
|
288 | pointer-events: none;
|
289 | z-index: 7;
|
290 | }
|
291 |
|
292 | .full .client-slot{
|
293 | z-index: 10;
|
294 | }
|
295 |
|
296 |
|
297 | .flex-expand{
|
298 | flex: 1 1 auto;
|
299 | align-items: center;
|
300 | }
|
301 |
|
302 | .flex-fixed{
|
303 | flex: 0 0 auto;
|
304 | }
|
305 |
|
306 |
|
307 | .static-wrapper img{
|
308 | margin: auto;
|
309 | }
|
310 | #display div{
|
311 | margin: auto;
|
312 | }
|
313 |
|
314 | .guacamoleClient.connected #display:hover {
|
315 | cursor: none;
|
316 | }
|
317 |
|
318 | .guacamoleClient.connected.no-control #display:hover {
|
319 | cursor: inherit;
|
320 | }
|
321 |
|
322 | .hide-overflow {
|
323 | overflow: hidden;
|
324 | }
|
325 |
|
326 | .guac_client-slot{
|
327 | background-color: white;
|
328 | }
|
329 |
|
330 | .guac-center{
|
331 | margin: auto;
|
332 | position: absolute;
|
333 | top: 0; left: 0; bottom: 0; right: 0;
|
334 | height: 125px;
|
335 | width: 400px;
|
336 | background-color: #e8e8e8;
|
337 | }
|
338 |
|
339 | .fade-enter-active, .fade-leave-active {
|
340 | transition: opacity 5s;
|
341 | }
|
342 | .fade-enter, .fade-leave-to {
|
343 | opacity: 0;
|
344 | }
|
345 |
|
346 | </style>
|