import { animate, state, style, transition, trigger } from '@angular/animations'; import { ChangeDetectionStrategy, Component, ContentChild, ElementRef, Input, OnDestroy, OnInit, ViewChild, ViewEncapsulation, HostListener, Output, } from '@angular/core'; import { BehaviorSubject, Subscription } from 'rxjs'; import * as screenfull from 'screenfull'; import { RemoteDesktopManager } from '../services'; import { ConnectingMessageComponent } from './messages/connecting-message.component'; import { DisconnectedMessageComponent } from './messages/disconnected-message.component'; import { ErrorMessageComponent } from './messages/error-message.component'; /** * The main component for displaying a remote desktop */ @Component({ selector: 'ngx-remote-desktop', template: `
`, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.Default, animations: [ trigger('toolbarAnimation', [ state('1', style({ transform: 'translateX(0%)' })), state('0', style({ transform: 'translateX(-100%)' })), transition('1 => 0', animate('200ms 200ms ease-out')), transition('0 => 1', animate('225ms ease-in')) ]) ], }) export class RemoteDesktopComponent implements OnInit, OnDestroy { /** * Client that manages the connection to the remote desktop */ @Input() public manager: RemoteDesktopManager; /** * Guacamole has more states than the list below however for the component we are only interested * in managing four states. */ public states = { CONNECTING: 'CONNECTING', CONNECTED: 'CONNECTED', DISCONNECTED: 'DISCONNECTED', ERROR: 'ERROR' }; /** * Manage the component state */ public state: BehaviorSubject = new BehaviorSubject(this.states.CONNECTING); @ContentChild(ConnectingMessageComponent) private connectingMessage: ConnectingMessageComponent; @ContentChild(DisconnectedMessageComponent) private disconnectedMessage: DisconnectedMessageComponent; @ContentChild(ErrorMessageComponent) private errorMessage: ErrorMessageComponent; @ViewChild('container') private container: ElementRef; @ViewChild('toolbar') private toolbar: ElementRef; /** * Subscriptions */ private subscriptions: Subscription[] = []; /** * Hide or show the toolbar */ private toolbarVisible: boolean = true; /** * Subscribe to the connection state and full screen state when the component is initialised */ ngOnInit(): void { this.bindSubscriptions(); } /** * Remove all subscriptions when the component is destroyed */ ngOnDestroy(): void { this.unbindSubscriptions(); } /** * Bind the subscriptions */ private bindSubscriptions(): void { this.subscriptions.push(this.manager.onStateChange.subscribe(this.handleState.bind(this))); this.subscriptions.push(this.manager.onFullScreen.subscribe(this.handleFullScreen.bind(this))); } /** * Unbind the subscriptions */ private unbindSubscriptions(): void { this.subscriptions.forEach(subscription => subscription.unsubscribe()); } /** * Set the component state to the new guacamole state * @param newState */ private setState(newState: string): void { this.state.next(newState); } /** * Receive the state from the desktop client and update this components state * @param newState - state received from the guacamole client */ private handleState(newState: string) { switch (newState) { case RemoteDesktopManager.STATE.CONNECTED: this.setState(this.states.CONNECTED); break; case RemoteDesktopManager.STATE.DISCONNECTED: this.exitFullScreen(); this.setState(this.states.DISCONNECTED); break; case RemoteDesktopManager.STATE.CONNECTING: case RemoteDesktopManager.STATE.WAITING: this.setState(this.states.CONNECTING); break; case RemoteDesktopManager.STATE.CLIENT_ERROR: case RemoteDesktopManager.STATE.TUNNEL_ERROR: this.exitFullScreen(); this.setState(this.states.ERROR); break; } } /** * Exit full screen and show the toolbar */ private exitFullScreen(): void { if (!screenfull.isFullscreen) { return; } const containerElement = this.container.nativeElement; screenfull.exit(containerElement); } /** * Enter full screen mode and auto hide the toolbar */ private enterFullScreen(): void { if (screenfull.isFullscreen) { return; } const containerElement = this.container.nativeElement; screenfull.request(containerElement); screenfull.on('change', (change: any) => { if (!screenfull.isFullscreen) { this.manager.setFullScreen(false); } this.handleToolbar(); }); } /** * Go in and out of full screen */ private handleFullScreen(newFullScreen: boolean): void { if (newFullScreen) { this.enterFullScreen(); } else { this.exitFullScreen(); } } private handleToolbar(): void { this.toolbarVisible = (this.manager.isFullScreen()) ? false : true; } /** * Handle the display mouse movement * @param event Mouse event */ private handleDisplayMouseMove($event: any): void { if (!this.manager.isFullScreen()) { return; } const toolbarWidth = this.toolbar.nativeElement.clientWidth; if ($event.x >= toolbarWidth) { this.toolbarVisible = false; } } @HostListener('document:mousemove', ['$event']) private onDocumentMousemove($event: MouseEvent) { if (!this.manager.isFullScreen()) { return; } const toolbarWidth = this.toolbar.nativeElement.clientWidth; const x = $event.x; if (x >= -1 && x <= 0) { this.toolbarVisible = true; } if (x >= toolbarWidth) { this.toolbarVisible = false; } } }