frontend-tutorial-framework/src/app/dashboard/dashboard.component.ts

179 lines
5.8 KiB
TypeScript

import { Component, OnDestroy, OnInit, ChangeDetectorRef, ElementRef, ViewChild } from '@angular/core';
import { DeploymentNotificationService } from '../services/deployment-notification.service';
import { Subscription, BehaviorSubject } from 'rxjs';
import { WebSocketService } from '../services/websocket.service';
import { WebSocketMessage } from '../message-types/websocket-message';
import { DashboardConfigService } from '../services/config.service';
import { HttpClient } from '@angular/common/http';
import { delay, retryWhen, tap } from 'rxjs/operators';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit, OnDestroy {
deploying = false;
polling = new BehaviorSubject<boolean>(false);
deploymentNotificationSubscription: Subscription;
@ViewChild('webiframe', {static: false}) webiframe: ElementRef;
@ViewChild('tfwmessages', {static: false}) messages: ElementRef;
@ViewChild('urlbar', {static: false}) urlbar: ElementRef;
layout = this.configService.layout;
hideMessages = this.configService.hideMessages;
showUrlBar = this.configService.showUrlBar;
iframeUrl = this.configService.iframeUrl;
actualIframeUrl = this.iframeUrl.value;
terminalMenuItem = this.configService.terminalMenuItem;
iframeReloadSubscription: Subscription;
command_handlers = {
'dashboard.reloadFrontend': this.reloadFrontendHandlder.bind(this),
'dashboard.reloadIframe': this.reloadIframeHandler.bind(this)
};
constructor(private deploymentNotificationService: DeploymentNotificationService,
private webSocketService: WebSocketService,
private changeDetectorRef: ChangeDetectorRef,
private http: HttpClient,
private configService: DashboardConfigService) {}
ngOnInit() {
this.webSocketService.connect();
this.configService.init();
this.hideIframeUntilResponseOk();
this.subscribeResizeOnLayoutChange();
this.initCommandHandling();
this.initDeploymentNotifications();
this.sendReady();
}
hideIframeUntilResponseOk() {
// TODO: hide iframe and show it after this whole deal...
this.reloadIframeWhenResponseOk();
}
subscribeResizeOnLayoutChange() {
this.configService.layout.subscribe(() => {
this.emitResizeEvent();
setTimeout(() => this.scrollMessagesToBottom(), 0);
});
}
initCommandHandling() {
this.webSocketService.observeKey<WebSocketMessage>('dashboard').subscribe(message => {
this.command_handlers[message.key](message);
this.changeDetectorRef.detectChanges();
});
}
initDeploymentNotifications() {
this.deploymentNotificationSubscription = this.deploymentNotificationService.deploying.subscribe(
(deploying) => {
this.deploying = deploying;
if (!deploying && this.configService.reloadIframeOnDeploy.value) {
this.reloadIframeWhenResponseOk();
}
});
}
sendReady() {
setTimeout(() => this.webSocketService.send({'key': 'frontend.ready'}));
}
reloadFrontendHandlder(message: WebSocketMessage) {
setTimeout(() => window.location.reload(), 2000);
}
reloadIframeHandler(message: WebSocketMessage) {
setTimeout(() => this.reloadIframeNoSubmit(), 200);
}
setLayout(layout: string) {
this.layout.next(layout);
}
emitResizeEvent() {
// We need to trigger a 'resize' event manually, otherwise editor stays collapsed
// editor 'resize' event listener requires a parameter of force=true
setTimeout(() => window.dispatchEvent(new Event('resize', {force: true} as any)), 0);
}
reloadIframe() {
setTimeout(() => {
this.webiframe.nativeElement.contentWindow.location.reload(true);
});
}
reloadIframeNoSubmit() {
// Sometimes it is needed to reload the iframe without resending the previous form data
setTimeout(() => {
this.webiframe.nativeElement.contentWindow.location = this.webiframe.nativeElement.contentWindow.location.href;
});
}
selectTerminalMenuItem(item: string) {
if (!item.match('(terminal|console)')) {
return;
}
this.terminalMenuItem.next(item);
}
scrollMessagesToBottom() {
const element = this.messages.nativeElement;
// This must be done in the Angular event loop to avoid messing up
// change detection (not in the template like ConsoleComponent does)
element.scrollTop = element.scrollHeight;
}
iframeLoad() {
if (this.webiframe && this.iframeUrl.value) {
const href = this.webiframe.nativeElement.contentWindow.frames.location.href;
const niceURL = href.match(/.*?\/\/.*?(\/.*)/)[1];
this.actualIframeUrl = niceURL;
}
}
changeIframeURL() {
const userGivenValue = this.urlbar.nativeElement.value.trim();
if (
userGivenValue === '/' ||
userGivenValue.startsWith('dashboard') ||
userGivenValue.startsWith('/dashboard')
) {
return;
}
this.webiframe.nativeElement.contentWindow.frames.location.href = this.urlbar.nativeElement.value;
}
reloadIframeWhenResponseOk() {
if (this.polling.value) {
this.iframeReloadSubscription.unsubscribe();
}
this.polling.next(true);
this.iframeReloadSubscription = this.http.get(this.actualIframeUrl, {observe: 'response'}).pipe(
retryWhen(errors =>
errors.pipe(
tap(
response => {
if (response.status === 200) {
this.iframeReloadSubscription.unsubscribe();
this.polling.next(false);
this.reloadIframe();
}}),
delay(1000)
))).subscribe();
}
ngOnDestroy() {
if (this.deploymentNotificationSubscription) {
this.deploymentNotificationSubscription.unsubscribe();
}
if (this.iframeReloadSubscription) {
this.iframeReloadSubscription.unsubscribe();
}
}
}