Make whole frontend dynamically configurable to avoid rebuilds

This commit is contained in:
Kristóf Tóth 2019-08-28 10:19:14 +02:00
parent 41eebc60f6
commit a88415bad1
17 changed files with 187 additions and 207 deletions

View File

@ -1 +1,2 @@
<router-outlet></router-outlet> <div #router><router-outlet></router-outlet></div>
<app-loader [toBlur]="router"></app-loader>

View File

@ -0,0 +1,3 @@
.blur {
filter: blur(3px);
}

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { config } from './config'; import { SiteConfigService } from './services/config.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@ -8,18 +8,27 @@ import { config } from './config';
styleUrls: ['./app.component.scss'] styleUrls: ['./app.component.scss']
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
documentTitle: string = config.documentTitle; constructor(
private titleService: Title,
constructor(private titleService: Title) {} private configService: SiteConfigService
) {}
ngOnInit() { ngOnInit() {
this.titleService.setTitle(this.documentTitle); this.configService.init();
if (config.dashboard.askReloadSite) { this.configService.documentTitle.subscribe(title => this.titleService.setTitle(title));
window.addEventListener('beforeunload', (event) => {
const confirmationMessage = 'Refreshing this page may mess up your challenge/tutorial state. Are you sure?'; this.configService.askReloadSite.subscribe(askReloadSite => {
event.returnValue = confirmationMessage; if (askReloadSite) {
return confirmationMessage; window.addEventListener('beforeunload', this.beforeUnloadHandler);
}); } else {
} window.removeEventListener('beforeunload', this.beforeUnloadHandler);
}
});
}
beforeUnloadHandler(event) {
const confirmationMessage = 'Refreshing this page may mess up your challenge/tutorial state. Are you sure?';
event.returnValue = confirmationMessage;
return confirmationMessage;
} }
} }

View File

@ -8,7 +8,6 @@ import { AppComponent } from './app.component';
import { DashboardComponent } from './dashboard/dashboard.component'; import { DashboardComponent } from './dashboard/dashboard.component';
import { HeaderComponent } from './header/header.component'; import { HeaderComponent } from './header/header.component';
import { SidebarComponent } from './sidebar/sidebar.component'; import { SidebarComponent } from './sidebar/sidebar.component';
import { WebComponent } from './web/web.component';
import { MarkdownService } from './services/markdown.service'; import { MarkdownService } from './services/markdown.service';
import { TerminadoService } from './services/terminado.service'; import { TerminadoService } from './services/terminado.service';
import { IdeComponent } from './ide/ide.component'; import { IdeComponent } from './ide/ide.component';
@ -21,7 +20,13 @@ import { DeploymentNotificationService } from './services/deployment-notificatio
import { SafePipe } from './pipes/safe.pipe'; import { SafePipe } from './pipes/safe.pipe';
import { ConsoleComponent } from './console/console.component'; import { ConsoleComponent } from './console/console.component';
import { MonacoEditorModule } from 'ngx-monaco-editor'; import { MonacoEditorModule } from 'ngx-monaco-editor';
import { IdeConfigService } from './services/config.service'; import {
ConfigReadyService,
IdeConfigService,
DashboardConfigService,
SiteConfigService
} from './services/config.service';
import { LoaderComponent } from './loader/loader.component';
@NgModule({ @NgModule({
@ -29,14 +34,14 @@ import { IdeConfigService } from './services/config.service';
AppComponent, AppComponent,
HeaderComponent, HeaderComponent,
SidebarComponent, SidebarComponent,
WebComponent,
IdeComponent, IdeComponent,
MessagesComponent, MessagesComponent,
TerminalComponent, TerminalComponent,
DashboardComponent, DashboardComponent,
TestmessengerComponent, TestmessengerComponent,
SafePipe, SafePipe,
ConsoleComponent ConsoleComponent,
LoaderComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -51,10 +56,13 @@ import { IdeConfigService } from './services/config.service';
WebSocketService, WebSocketService,
TerminadoService, TerminadoService,
DeploymentNotificationService, DeploymentNotificationService,
IdeConfigService ConfigReadyService,
IdeConfigService,
DashboardConfigService,
SiteConfigService
], ],
bootstrap: [ bootstrap: [
AppComponent AppComponent
] ]
}) })
export class AppModule { } export class AppModule {}

View File

@ -1,53 +0,0 @@
export const config = {
documentTitle: 'Avatao Tutorials',
dashboard: {
route: 'dashboard',
triggerFirstFSMStep: 'step_1',
askReloadSite: false,
sendReadyAfterPageReload: true,
terminalOrConsole: 'terminal',
currentLayout: 'terminal-ide-web',
enabledLayouts: [
'terminal-ide-web',
'terminal-ide-vertical',
'terminal-web',
'ide-web-vertical',
'terminal-ide-horizontal',
'terminal-only',
'ide-only',
'web-only'
],
iframeUrl: '/webservice',
showUrlBar: false,
hideMessages: false
},
ide: {
route: 'ide',
autoSaveInterval: 444,
defaultCode: 'Loading your file...',
defaultLanguage: 'text',
deployButtonText: {
'TODEPLOY': 'Deploy',
'DEPLOYED': 'Deployed',
'DEPLOYING': 'Reloading app...',
'FAILED': 'Deployment failed. Retry'
},
showDeployButton: true,
reloadIframeOnDeploy: false,
autoDetectFileLanguage: true,
showMiniMap: false
},
terminal: {
route: 'shell'
},
messages: {
route: 'messages'
},
console: {
route: 'console',
defaultContent: '',
},
testmessenger: {
route: 'testmessenger'
}
};

View File

@ -2,7 +2,6 @@ import { Component, OnInit, Output, EventEmitter } from '@angular/core';
import { WebSocketService } from '../services/websocket.service'; import { WebSocketService } from '../services/websocket.service';
import { WebSocketMessage } from '../message-types/websocket-message'; import { WebSocketMessage } from '../message-types/websocket-message';
import { ConsoleReadMessage, ConsoleWriteMessage } from '../message-types/console-messages'; import { ConsoleReadMessage, ConsoleWriteMessage } from '../message-types/console-messages';
import { config } from '../config';
@Component({ @Component({
selector: 'app-console', selector: 'app-console',
@ -10,7 +9,7 @@ import { config } from '../config';
styleUrls: ['./console.component.scss'] styleUrls: ['./console.component.scss']
}) })
export class ConsoleComponent implements OnInit { export class ConsoleComponent implements OnInit {
console_content: string = config.console.defaultContent; console_content = '';
@Output() contentChanged = new EventEmitter<string>(); @Output() contentChanged = new EventEmitter<string>();

View File

@ -1,31 +1,30 @@
<div [attr.class]="layout"> <div [attr.class]="layout | async">
<div class="tfw-grid-main-components"> <div class="tfw-grid-main-components">
<div class="tfw-header"><app-header></app-header></div> <div class="tfw-header"><app-header></app-header></div>
<div [ngClass]="{'hide-attribute': hideMessages}" <div [ngClass]="{'hide-attribute': hideMessages | async}"
class="tfw-messages" class="tfw-messages"
#tfwmessages> #tfwmessages>
<app-messages (newMessageEvent)="scrollMessagesToBottom()"></app-messages> <app-messages (newMessageEvent)="scrollMessagesToBottom()"></app-messages>
</div> </div>
<div class="tfw-web tao-grid-top-left" <div class="tfw-web tao-grid-top-left"
[ngClass]="{'deploy-blur': deploying || polling}"> [ngClass]="{'deploy-blur': deploying || polling}">
<app-web *ngIf="!iframeUrl"></app-web> <div class="iframe-container">
<div *ngIf="showUrlBar" class="urlbar-container"> <div *ngIf="showUrlBar | async" class="urlbar-container">
<button class="refresh btn btn-sm rounded-circle" (click)="reloadIframe()">&#8635;</button> <button class="refresh btn btn-sm rounded-circle" (click)="reloadIframe()">&#8635;</button>
<input type="text" <input type="text"
#urlbar #urlbar
class="urlbar form-control" class="urlbar form-control"
value="{{actualIframeUrl}}" value="{{actualIframeUrl}}"
(keyup.enter)="changeIframeURL()"> (keyup.enter)="changeIframeURL()">
<button class="go btn btn-sm rounded-circle" (click)="changeIframeURL()">&#8680;</button> <button class="go btn btn-sm rounded-circle" (click)="changeIframeURL()">&#8680;</button>
</div> </div>
<div *ngIf="iframeUrl" class="iframe-container"> <iframe class="iframe"
<iframe class="iframe" #webiframe
#webiframe scrolling="yes"
scrolling="yes" frameborder="0"
frameborder="0" (load)="iframeLoad()"
(load)="iframeLoad()" [src]="iframeUrl | async | safe">
[src]="iframeUrl | safe"> </iframe>
</iframe>
</div> </div>
</div> </div>
<div class="tfw-ide"> <div class="tfw-ide">
@ -35,17 +34,17 @@
<div class="btn-group btn-group-sm flex-wrap tao-grid-center-left tfw-console-terminal-menu"> <div class="btn-group btn-group-sm flex-wrap tao-grid-center-left tfw-console-terminal-menu">
<button class="tfw-console-terminal-menu-button" <button class="tfw-console-terminal-menu-button"
(click)="selectTerminalMenuItem('terminal')" (click)="selectTerminalMenuItem('terminal')"
[class.selected]="selectedTerminalMenuItem === 'terminal'">TERMINAL</button> [class.selected]="(terminalMenuItem | async) === 'terminal'">TERMINAL</button>
<button class="tfw-console-terminal-menu-button" <button class="tfw-console-terminal-menu-button"
(click)="selectTerminalMenuItem('console')" (click)="selectTerminalMenuItem('console')"
[class.selected]="selectedTerminalMenuItem === 'console'">CONSOLE</button> [class.selected]="(terminalMenuItem | async) === 'console'">CONSOLE</button>
</div> </div>
<hr> <hr>
<app-terminal [class.hidden]="selectedTerminalMenuItem !== 'terminal'"></app-terminal> <app-terminal [class.hidden]="(terminalMenuItem | async) !== 'terminal'"></app-terminal>
<app-console [class.hidden]="selectedTerminalMenuItem !== 'console'"></app-console> <app-console [class.hidden]="(terminalMenuItem | async) !== 'console'"></app-console>
</div> </div>
<div class="tfw-sidebar"> <div class="tfw-sidebar">
<app-sidebar (layoutChanged)="setLayout($event)" [layout]="layout"></app-sidebar> <app-sidebar (layoutChanged)="setLayout($event)" [layout]="layout | async"></app-sidebar>
</div> </div>
<div class="tfw-terminal-footer"></div> <div class="tfw-terminal-footer"></div>

View File

@ -3,9 +3,7 @@ import { DeploymentNotificationService } from '../services/deployment-notificati
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { WebSocketService } from '../services/websocket.service'; import { WebSocketService } from '../services/websocket.service';
import { WebSocketMessage } from '../message-types/websocket-message'; import { WebSocketMessage } from '../message-types/websocket-message';
import { HideMessagesMessage, LayoutMessage, TerminalMenuItemMessage } from '../message-types/dashboard-messages'; import { DashboardConfigService } from '../services/config.service';
import { config } from '../config';
import { LogMessage } from '../message-types/log-message';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { delay, retryWhen, tap } from 'rxjs/operators'; import { delay, retryWhen, tap } from 'rxjs/operators';
@ -18,22 +16,19 @@ export class DashboardComponent implements OnInit, OnDestroy {
deploying = false; deploying = false;
polling = false; polling = false;
deploymentNotificationSubscription: Subscription; deploymentNotificationSubscription: Subscription;
@ViewChild('webiframe', {static: true}) webiframe: ElementRef; @ViewChild('webiframe', {static: false}) webiframe: ElementRef;
@ViewChild('tfwmessages', {static: true}) messages: ElementRef; @ViewChild('tfwmessages', {static: false}) messages: ElementRef;
@ViewChild('urlbar', {static: true}) urlbar: ElementRef; @ViewChild('urlbar', {static: false}) urlbar: ElementRef;
layout: string = config.dashboard.currentLayout; layout = this.configService.layout;
hideMessages: boolean = config.dashboard.hideMessages; hideMessages = this.configService.hideMessages;
iframeUrl: string = config.dashboard.iframeUrl; showUrlBar = this.configService.showUrlBar;
showUrlBar = config.dashboard.showUrlBar; iframeUrl = this.configService.iframeUrl;
actualIframeUrl: string = this.iframeUrl; actualIframeUrl = this.iframeUrl.value;
selectedTerminalMenuItem: string = config.dashboard.terminalOrConsole; terminalMenuItem = this.configService.terminalMenuItem;
iframeReloadSubscription: Subscription; iframeReloadSubscription: Subscription;
command_handlers = { command_handlers = {
'dashboard.layout': this.layoutHandler.bind(this),
'dashboard.hideMessages': this.hideMessagesHandler.bind(this),
'dashboard.terminalMenuItem': this.terminalMenuItemHandler.bind(this),
'dashboard.reloadFrontend': this.reloadFrontendHandlder.bind(this), 'dashboard.reloadFrontend': this.reloadFrontendHandlder.bind(this),
'dashboard.reloadIframe': this.reloadIframeHandler.bind(this) 'dashboard.reloadIframe': this.reloadIframeHandler.bind(this)
}; };
@ -41,13 +36,20 @@ export class DashboardComponent implements OnInit, OnDestroy {
constructor(private deploymentNotificationService: DeploymentNotificationService, constructor(private deploymentNotificationService: DeploymentNotificationService,
private webSocketService: WebSocketService, private webSocketService: WebSocketService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private http: HttpClient) {} private http: HttpClient,
private configService: DashboardConfigService) {}
ngOnInit() { ngOnInit() {
this.webSocketService.connect(); this.webSocketService.connect();
this.configService.init();
this.subscribeResizeOnLayoutChange();
this.initCommandHandling(); this.initCommandHandling();
this.initDeploymentNotifications(); this.initDeploymentNotifications();
this.sendReadyIfNeeded(); this.sendReady();
}
subscribeResizeOnLayoutChange() {
this.configService.layout.subscribe(() => this.emitResizeEvent());
} }
initCommandHandling() { initCommandHandling() {
@ -61,7 +63,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
this.deploymentNotificationSubscription = this.deploymentNotificationService.deploying.subscribe( this.deploymentNotificationSubscription = this.deploymentNotificationService.deploying.subscribe(
(deploying) => { (deploying) => {
this.deploying = deploying; this.deploying = deploying;
if (!deploying && config.ide.reloadIframeOnDeploy) { if (!deploying && this.configService.reloadIframeOnDeploy.value) {
if (this.polling) { if (this.polling) {
this.iframeReloadSubscription.unsubscribe(); this.iframeReloadSubscription.unsubscribe();
} }
@ -70,26 +72,8 @@ export class DashboardComponent implements OnInit, OnDestroy {
}); });
} }
sendReadyIfNeeded() { sendReady() {
if (config.dashboard.sendReadyAfterPageReload) { setTimeout(() => this.webSocketService.send({'key': 'frontend.ready'}));
setTimeout(() => this.webSocketService.send({'key': 'frontend.ready'}));
}
}
layoutHandler(message: LayoutMessage) {
if (config.dashboard.enabledLayouts.includes(message.value)) {
this.setLayout(message.value);
} else {
console.log('Invalid ide layout "' + message.value + '" received!');
}
}
hideMessagesHandler(message: HideMessagesMessage) {
this.hideMessages = message.value;
}
terminalMenuItemHandler(message: TerminalMenuItemMessage) {
this.selectTerminalMenuItem(message.value);
} }
reloadFrontendHandlder(message: WebSocketMessage) { reloadFrontendHandlder(message: WebSocketMessage) {
@ -101,7 +85,11 @@ export class DashboardComponent implements OnInit, OnDestroy {
} }
setLayout(layout: string) { setLayout(layout: string) {
this.layout = layout; this.layout.next(layout);
this.emitResizeEvent();
}
emitResizeEvent() {
// We need to trigger a 'resize' event manually, otherwise editor stays collapsed // We need to trigger a 'resize' event manually, otherwise editor stays collapsed
// editor 'resize' event listener requires a parameter of force=true // editor 'resize' event listener requires a parameter of force=true
setTimeout(() => window.dispatchEvent(new Event('resize', {force: true} as any)), 0); setTimeout(() => window.dispatchEvent(new Event('resize', {force: true} as any)), 0);
@ -124,7 +112,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
if (!item.match('(terminal|console)')) { if (!item.match('(terminal|console)')) {
return; return;
} }
this.selectedTerminalMenuItem = item; this.terminalMenuItem.next(item);
} }
scrollMessagesToBottom(): void { scrollMessagesToBottom(): void {

View File

@ -14,7 +14,7 @@
</div> </div>
<div class="btn-group-sm tfw-deploy-btn-group"> <div class="btn-group-sm tfw-deploy-btn-group">
<button *ngIf="showDeployButton" <button *ngIf="showDeployButton | async"
type="submit" type="submit"
class="btn tfw-deploy-btn tao-grid-top-center" class="btn tfw-deploy-btn tao-grid-top-center"
(click)="sendCodeIfDirty(); deployCode()" (click)="sendCodeIfDirty(); deployCode()"
@ -23,13 +23,13 @@
[class.deploy]="deployButtonState === DeployButtonState.DEPLOYING" [class.deploy]="deployButtonState === DeployButtonState.DEPLOYING"
[class.disabled]="deployButtonState === DeployButtonState.DEPLOYING || deployButtonState === DeployButtonState.DEPLOYED" [class.disabled]="deployButtonState === DeployButtonState.DEPLOYING || deployButtonState === DeployButtonState.DEPLOYED"
[class.failed]="deployButtonState === DeployButtonState.FAILED"> [class.failed]="deployButtonState === DeployButtonState.FAILED">
<span *ngIf="deployButtonState === DeployButtonState.TODEPLOY">{{deployButtonText['TODEPLOY']}}</span> <span *ngIf="deployButtonState === DeployButtonState.TODEPLOY">{{(deployButtonText | async)['TODEPLOY']}}</span>
<span *ngIf="deployButtonState === DeployButtonState.DEPLOYED"> <span *ngIf="deployButtonState === DeployButtonState.DEPLOYED">
<img src="images/greentick_icon.svg"/> <img src="images/greentick_icon.svg"/>
<span>{{deployButtonText['DEPLOYED']}}</span> <span>{{(deployButtonText | async)['DEPLOYED']}}</span>
</span> </span>
<span *ngIf="deployButtonState === DeployButtonState.DEPLOYING"><div class="loader"></div>{{deployButtonText['DEPLOYING']}}</span> <span *ngIf="deployButtonState === DeployButtonState.DEPLOYING"><div class="loader"></div>{{(deployButtonText | async)['DEPLOYING']}}</span>
<span *ngIf="deployButtonState === DeployButtonState.FAILED">{{deployButtonText['FAILED']}}</span> <span *ngIf="deployButtonState === DeployButtonState.FAILED">{{(deployButtonText | async)['FAILED']}}</span>
</button> </button>
</div> </div>
</div> </div>

View File

@ -4,7 +4,7 @@ import { WebSocketMessage } from '../message-types/websocket-message';
import { IDEMessage } from '../message-types/ide-message'; import { IDEMessage } from '../message-types/ide-message';
import { DeployMessage } from '../message-types/deploy-message'; import { DeployMessage } from '../message-types/deploy-message';
import { DeploymentNotificationService } from '../services/deployment-notification.service'; import { DeploymentNotificationService } from '../services/deployment-notification.service';
import { config } from '../config'; import { IdeConfigService } from '../services/config.service';
import { LanguageMap } from './language-map'; import { LanguageMap } from './language-map';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
@ -31,19 +31,19 @@ export class IdeComponent implements OnInit {
files: string[]; files: string[];
filename = ''; filename = '';
code: string = config.ide.defaultCode; code = 'Loading your file...';
codeState = CodeState.SAVED; codeState = CodeState.SAVED;
deployButtonState = DeployButtonState.DEPLOYED; deployButtonState = DeployButtonState.DEPLOYED;
deployButtonText = config.ide.deployButtonText; deployButtonText = this.configService.deployButtonText;
showDeployButton: boolean = config.ide.showDeployButton; showDeployButton = this.configService.showDeployButton;
autosave = null; autosave = null;
editorOptions = { editorOptions = {
theme: 'vs-dark', theme: 'vs-dark',
language: config.ide.defaultLanguage, language: 'text',
glyphMargin: false, glyphMargin: false,
minimap: {enabled: config.ide.showMiniMap} minimap: {enabled: false}
}; };
command_handlers = { command_handlers = {
@ -54,10 +54,12 @@ export class IdeComponent implements OnInit {
constructor(private webSocketService: WebSocketService, constructor(private webSocketService: WebSocketService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private deploymentNotificationService: DeploymentNotificationService) { } private deploymentNotificationService: DeploymentNotificationService,
private configService: IdeConfigService) {}
ngOnInit() { ngOnInit() {
this.webSocketService.connect(); this.webSocketService.connect();
this.configService.init();
this.subscribeWS(); this.subscribeWS();
this.subscribeFirstLanguageDetection(); this.subscribeFirstLanguageDetection();
this.requestCode(); this.requestCode();
@ -77,7 +79,7 @@ export class IdeComponent implements OnInit {
subscribeFirstLanguageDetection() { subscribeFirstLanguageDetection() {
this.webSocketService.observeKey<IDEMessage>('ide.read').pipe(first()).subscribe( this.webSocketService.observeKey<IDEMessage>('ide.read').pipe(first()).subscribe(
() => this.autoDetectEditorLanguageIfEnabled(this.filename) () => this.autoDetectEditorLanguage(this.filename)
); );
} }
@ -108,10 +110,7 @@ export class IdeComponent implements OnInit {
this.deploymentNotificationService.deploying.next(false); this.deploymentNotificationService.deploying.next(false);
} }
autoDetectEditorLanguageIfEnabled(filename: string) { autoDetectEditorLanguage(filename: string) {
if (!config.ide.autoDetectFileLanguage) {
return;
}
const extension = filename.substr(filename.lastIndexOf('.') + 1); const extension = filename.substr(filename.lastIndexOf('.') + 1);
let language = LanguageMap[extension]; let language = LanguageMap[extension];
language = (language === undefined) ? 'text' : language; language = (language === undefined) ? 'text' : language;
@ -132,7 +131,7 @@ export class IdeComponent implements OnInit {
} }
this.autosave = setInterval( this.autosave = setInterval(
() => { this.sendCodeIfDirty(); }, () => { this.sendCodeIfDirty(); },
config.ide.autoSaveInterval this.configService.autoSaveInterval.value
); );
} }
@ -142,7 +141,7 @@ export class IdeComponent implements OnInit {
} }
this.filename = file; this.filename = file;
this.requestCode(); this.requestCode();
this.autoDetectEditorLanguageIfEnabled(this.filename); this.autoDetectEditorLanguage(this.filename);
} }
editorWriteHandler() { editorWriteHandler() {

View File

@ -2,9 +2,7 @@ import { ChangeDetectorRef, Component, OnInit, EventEmitter, Output } from '@ang
import { MessageData, Message } from '../message-types/bot-messages'; import { MessageData, Message } from '../message-types/bot-messages';
import { MarkdownService } from '../services/markdown.service'; import { MarkdownService } from '../services/markdown.service';
import { WebSocketService } from '../services/websocket.service'; import { WebSocketService } from '../services/websocket.service';
import { Subject } from 'rxjs';
import { config } from '../config';
import { Subject, Observer, BehaviorSubject } from 'rxjs';
@Component({ @Component({
selector: 'app-messages', selector: 'app-messages',

View File

@ -1,38 +1,83 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { WebSocketService } from './websocket.service'; import { WebSocketService } from './websocket.service';
import { BehaviorSubject } from 'rxjs'; import { Subject, BehaviorSubject, forkJoin } from 'rxjs';
import { ConfigServiceBase } from './config.service.base';
@Injectable() @Injectable()
abstract class ConfigService { export class DashboardConfigService extends ConfigServiceBase {
keys: Array<string> = new Array<string>(); keys = ['frontend.dashboard'];
protected mau = 'cica';
constructor(private webSocketService: WebSocketService) {} layout = new BehaviorSubject<string>('terminal-ide-web');
hideMessages = new BehaviorSubject<boolean>(false);
iframeUrl = new BehaviorSubject<string>('/webservice');
showUrlBar = new BehaviorSubject<boolean>(false);
terminalMenuItem = new BehaviorSubject<string>('terminal');
reloadIframeOnDeploy = new BehaviorSubject<boolean>(false);
enabledLayouts = new BehaviorSubject<Array<string>>([
'terminal-ide-web',
'terminal-ide-vertical',
'terminal-web',
'ide-web-vertical',
'terminal-ide-horizontal',
'terminal-only',
'ide-only',
'web-only'
]);
}
@Injectable()
export class IdeConfigService extends ConfigServiceBase {
keys = ['frontend.ide'];
autoSaveInterval = new BehaviorSubject<number>(444);
showDeployButton = new BehaviorSubject<boolean>(true);
deployButtonText = new BehaviorSubject<any>({
'TODEPLOY': 'Deploy',
'DEPLOYED': 'Deployed',
'DEPLOYING': 'Reloading app...',
'FAILED': 'Deployment failed'
});
}
@Injectable()
export class SiteConfigService extends ConfigServiceBase {
keys = ['frontend.site'];
askReloadSite = new BehaviorSubject<boolean>(false);
documentTitle = new BehaviorSubject<string>('Avatao Tutorials');
}
@Injectable()
export class ConfigReadyService {
configDone = new Subject<void>();
readyNotifiers: Array<any>;
constructor(
private webSocketService: WebSocketService,
private ide: IdeConfigService,
private dashboard: DashboardConfigService,
private site: SiteConfigService
) {
this.readyNotifiers = [
this.ide.configDone,
this.dashboard.configDone,
this.site.configDone
];
}
init() { init() {
this.webSocketService.connect(); this.webSocketService.connect();
this.keys.forEach(key => {
key = 'frontend.config.' + key;
this.webSocketService.observeKey<any>(key).subscribe(this.handleConfig.bind(this));
});
}
handleConfig(message: any) { const recvdConfigDone = new Subject<void>();
Object.keys(message).filter(key => key !== 'key').forEach(key => { this.readyNotifiers.push(recvdConfigDone);
if (this[key] === undefined) { this.webSocketService.observeKey<any>('frontend.config.done').subscribe(() => {
console.log(`Invalid ${this.keys} config key "${key}"!`); recvdConfigDone.next();
} else { recvdConfigDone.complete();
this[key].next(message[key]); });
}
forkJoin(this.readyNotifiers).subscribe(undefined, undefined, () => {
this.configDone.next();
this.configDone.complete();
}); });
} }
} }
@Injectable()
export class IdeConfigService extends ConfigService {
keys = ['ide'];
showDeployButton = new BehaviorSubject<boolean>(true);
}

View File

@ -1,5 +1,4 @@
<div class="tfw-ide-pin" *ngFor="let layoutIter of enabledLayouts"> <div class="tfw-ide-pin" *ngFor="let layoutIter of (enabledLayouts | async)">
<div> <div>
<button [class.active]="layout === layoutIter" <button [class.active]="layout === layoutIter"
(active)="layout === layoutIter" (active)="layout === layoutIter"
@ -7,5 +6,4 @@
(click)="setLayout(layoutIter)" (click)="setLayout(layoutIter)"
class="btn"><img src="images/{{layoutIter}}.svg"></button> class="btn"><img src="images/{{layoutIter}}.svg"></button>
</div> </div>
</div> </div>

View File

@ -1,18 +1,17 @@
import { Component, Input, Output, EventEmitter } from '@angular/core'; import { Component, Input, Output, EventEmitter } from '@angular/core';
import { config } from '../config'; import { DashboardConfigService } from '../services/config.service';
@Component({ @Component({
selector: 'app-sidebar', selector: 'app-sidebar',
templateUrl: './sidebar.component.html', templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.scss'] styleUrls: ['./sidebar.component.scss']
}) })
export class SidebarComponent { export class SidebarComponent {
@Input() layout: string; @Input() layout: string;
@Output() layoutChanged = new EventEmitter<string>(); @Output() layoutChanged = new EventEmitter<string>();
enabledLayouts: string[] = config.dashboard.enabledLayouts; enabledLayouts = this.configService.enabledLayouts;
constructor() {} constructor(private configService: DashboardConfigService) {}
setLayout(layout: string) { setLayout(layout: string) {
this.layout = layout; this.layout = layout;

View File

@ -1,2 +0,0 @@
<h2>WebComponent is here!</h2>
<p>Edit me if you want to write your own component in Angular!</p>

View File

@ -1,11 +0,0 @@
// Edit me if you want to write your own component in Angular!
import { Component } from '@angular/core';
@Component({
selector: 'app-web',
templateUrl: './web.component.html',
styleUrls: ['./web.component.scss']
})
export class WebComponent {
constructor() {}
}