diff --git a/src/app/app.component.html b/src/app/app.component.html index 0680b43..c75bd18 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1 +1,2 @@ - +
+ diff --git a/src/app/app.component.scss b/src/app/app.component.scss index e69de29..0a52e91 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -0,0 +1,3 @@ +.blur { + filter: blur(3px); +} \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 0c7a486..b6d8ab7 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { Title } from '@angular/platform-browser'; -import { config } from './config'; +import { SiteConfigService } from './services/config.service'; @Component({ selector: 'app-root', @@ -8,18 +8,27 @@ import { config } from './config'; styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit { - documentTitle: string = config.documentTitle; - - constructor(private titleService: Title) {} + constructor( + private titleService: Title, + private configService: SiteConfigService + ) {} ngOnInit() { - this.titleService.setTitle(this.documentTitle); - if (config.dashboard.askReloadSite) { - window.addEventListener('beforeunload', (event) => { - const confirmationMessage = 'Refreshing this page may mess up your challenge/tutorial state. Are you sure?'; - event.returnValue = confirmationMessage; - return confirmationMessage; - }); - } + this.configService.init(); + this.configService.documentTitle.subscribe(title => this.titleService.setTitle(title)); + + this.configService.askReloadSite.subscribe(askReloadSite => { + if (askReloadSite) { + 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; } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 41f2f9b..01984a5 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -8,7 +8,6 @@ import { AppComponent } from './app.component'; import { DashboardComponent } from './dashboard/dashboard.component'; import { HeaderComponent } from './header/header.component'; import { SidebarComponent } from './sidebar/sidebar.component'; -import { WebComponent } from './web/web.component'; import { MarkdownService } from './services/markdown.service'; import { TerminadoService } from './services/terminado.service'; import { IdeComponent } from './ide/ide.component'; @@ -21,7 +20,13 @@ import { DeploymentNotificationService } from './services/deployment-notificatio import { SafePipe } from './pipes/safe.pipe'; import { ConsoleComponent } from './console/console.component'; 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({ @@ -29,14 +34,14 @@ import { IdeConfigService } from './services/config.service'; AppComponent, HeaderComponent, SidebarComponent, - WebComponent, IdeComponent, MessagesComponent, TerminalComponent, DashboardComponent, TestmessengerComponent, SafePipe, - ConsoleComponent + ConsoleComponent, + LoaderComponent ], imports: [ BrowserModule, @@ -51,10 +56,13 @@ import { IdeConfigService } from './services/config.service'; WebSocketService, TerminadoService, DeploymentNotificationService, - IdeConfigService + ConfigReadyService, + IdeConfigService, + DashboardConfigService, + SiteConfigService ], bootstrap: [ AppComponent ] }) -export class AppModule { } +export class AppModule {} diff --git a/src/app/config.ts b/src/app/config.ts deleted file mode 100644 index 6d7f08e..0000000 --- a/src/app/config.ts +++ /dev/null @@ -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' - } -}; diff --git a/src/app/console/console.component.ts b/src/app/console/console.component.ts index 5ad4f38..ba4f4b6 100644 --- a/src/app/console/console.component.ts +++ b/src/app/console/console.component.ts @@ -2,7 +2,6 @@ import { Component, OnInit, Output, EventEmitter } from '@angular/core'; import { WebSocketService } from '../services/websocket.service'; import { WebSocketMessage } from '../message-types/websocket-message'; import { ConsoleReadMessage, ConsoleWriteMessage } from '../message-types/console-messages'; -import { config } from '../config'; @Component({ selector: 'app-console', @@ -10,7 +9,7 @@ import { config } from '../config'; styleUrls: ['./console.component.scss'] }) export class ConsoleComponent implements OnInit { - console_content: string = config.console.defaultContent; + console_content = ''; @Output() contentChanged = new EventEmitter(); diff --git a/src/app/dashboard/dashboard.component.html b/src/app/dashboard/dashboard.component.html index 272496b..e77bb70 100644 --- a/src/app/dashboard/dashboard.component.html +++ b/src/app/dashboard/dashboard.component.html @@ -1,31 +1,30 @@ -
+
-
- -
- - - -
-
- +
+
+ + + +
+
@@ -35,17 +34,17 @@
+ [class.selected]="(terminalMenuItem | async) === 'terminal'">TERMINAL + [class.selected]="(terminalMenuItem | async) === 'console'">CONSOLE

- - + +
- +
diff --git a/src/app/dashboard/dashboard.component.ts b/src/app/dashboard/dashboard.component.ts index d4b8194..41272e0 100644 --- a/src/app/dashboard/dashboard.component.ts +++ b/src/app/dashboard/dashboard.component.ts @@ -3,9 +3,7 @@ import { DeploymentNotificationService } from '../services/deployment-notificati import { Subscription } from 'rxjs'; import { WebSocketService } from '../services/websocket.service'; import { WebSocketMessage } from '../message-types/websocket-message'; -import { HideMessagesMessage, LayoutMessage, TerminalMenuItemMessage } from '../message-types/dashboard-messages'; -import { config } from '../config'; -import { LogMessage } from '../message-types/log-message'; +import { DashboardConfigService } from '../services/config.service'; import { HttpClient } from '@angular/common/http'; import { delay, retryWhen, tap } from 'rxjs/operators'; @@ -18,22 +16,19 @@ export class DashboardComponent implements OnInit, OnDestroy { deploying = false; polling = false; deploymentNotificationSubscription: Subscription; - @ViewChild('webiframe', {static: true}) webiframe: ElementRef; - @ViewChild('tfwmessages', {static: true}) messages: ElementRef; - @ViewChild('urlbar', {static: true}) urlbar: ElementRef; + @ViewChild('webiframe', {static: false}) webiframe: ElementRef; + @ViewChild('tfwmessages', {static: false}) messages: ElementRef; + @ViewChild('urlbar', {static: false}) urlbar: ElementRef; - layout: string = config.dashboard.currentLayout; - hideMessages: boolean = config.dashboard.hideMessages; - iframeUrl: string = config.dashboard.iframeUrl; - showUrlBar = config.dashboard.showUrlBar; - actualIframeUrl: string = this.iframeUrl; - selectedTerminalMenuItem: string = config.dashboard.terminalOrConsole; + 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.layout': this.layoutHandler.bind(this), - 'dashboard.hideMessages': this.hideMessagesHandler.bind(this), - 'dashboard.terminalMenuItem': this.terminalMenuItemHandler.bind(this), 'dashboard.reloadFrontend': this.reloadFrontendHandlder.bind(this), 'dashboard.reloadIframe': this.reloadIframeHandler.bind(this) }; @@ -41,13 +36,20 @@ export class DashboardComponent implements OnInit, OnDestroy { constructor(private deploymentNotificationService: DeploymentNotificationService, private webSocketService: WebSocketService, private changeDetectorRef: ChangeDetectorRef, - private http: HttpClient) {} + private http: HttpClient, + private configService: DashboardConfigService) {} ngOnInit() { this.webSocketService.connect(); + this.configService.init(); + this.subscribeResizeOnLayoutChange(); this.initCommandHandling(); this.initDeploymentNotifications(); - this.sendReadyIfNeeded(); + this.sendReady(); + } + + subscribeResizeOnLayoutChange() { + this.configService.layout.subscribe(() => this.emitResizeEvent()); } initCommandHandling() { @@ -61,7 +63,7 @@ export class DashboardComponent implements OnInit, OnDestroy { this.deploymentNotificationSubscription = this.deploymentNotificationService.deploying.subscribe( (deploying) => { this.deploying = deploying; - if (!deploying && config.ide.reloadIframeOnDeploy) { + if (!deploying && this.configService.reloadIframeOnDeploy.value) { if (this.polling) { this.iframeReloadSubscription.unsubscribe(); } @@ -70,26 +72,8 @@ export class DashboardComponent implements OnInit, OnDestroy { }); } - sendReadyIfNeeded() { - if (config.dashboard.sendReadyAfterPageReload) { - 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); + sendReady() { + setTimeout(() => this.webSocketService.send({'key': 'frontend.ready'})); } reloadFrontendHandlder(message: WebSocketMessage) { @@ -101,7 +85,11 @@ export class DashboardComponent implements OnInit, OnDestroy { } 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 // editor 'resize' event listener requires a parameter of force=true 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)')) { return; } - this.selectedTerminalMenuItem = item; + this.terminalMenuItem.next(item); } scrollMessagesToBottom(): void { diff --git a/src/app/ide/ide.component.html b/src/app/ide/ide.component.html index 17dad4b..aa018a6 100644 --- a/src/app/ide/ide.component.html +++ b/src/app/ide/ide.component.html @@ -14,7 +14,7 @@
-
diff --git a/src/app/ide/ide.component.ts b/src/app/ide/ide.component.ts index 252b2ec..7b8ad82 100644 --- a/src/app/ide/ide.component.ts +++ b/src/app/ide/ide.component.ts @@ -4,7 +4,7 @@ import { WebSocketMessage } from '../message-types/websocket-message'; import { IDEMessage } from '../message-types/ide-message'; import { DeployMessage } from '../message-types/deploy-message'; import { DeploymentNotificationService } from '../services/deployment-notification.service'; -import { config } from '../config'; +import { IdeConfigService } from '../services/config.service'; import { LanguageMap } from './language-map'; import { first } from 'rxjs/operators'; @@ -31,19 +31,19 @@ export class IdeComponent implements OnInit { files: string[]; filename = ''; - code: string = config.ide.defaultCode; + code = 'Loading your file...'; codeState = CodeState.SAVED; deployButtonState = DeployButtonState.DEPLOYED; - deployButtonText = config.ide.deployButtonText; - showDeployButton: boolean = config.ide.showDeployButton; + deployButtonText = this.configService.deployButtonText; + showDeployButton = this.configService.showDeployButton; autosave = null; editorOptions = { theme: 'vs-dark', - language: config.ide.defaultLanguage, + language: 'text', glyphMargin: false, - minimap: {enabled: config.ide.showMiniMap} + minimap: {enabled: false} }; command_handlers = { @@ -54,10 +54,12 @@ export class IdeComponent implements OnInit { constructor(private webSocketService: WebSocketService, private changeDetectorRef: ChangeDetectorRef, - private deploymentNotificationService: DeploymentNotificationService) { } + private deploymentNotificationService: DeploymentNotificationService, + private configService: IdeConfigService) {} ngOnInit() { this.webSocketService.connect(); + this.configService.init(); this.subscribeWS(); this.subscribeFirstLanguageDetection(); this.requestCode(); @@ -77,7 +79,7 @@ export class IdeComponent implements OnInit { subscribeFirstLanguageDetection() { this.webSocketService.observeKey('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); } - autoDetectEditorLanguageIfEnabled(filename: string) { - if (!config.ide.autoDetectFileLanguage) { - return; - } + autoDetectEditorLanguage(filename: string) { const extension = filename.substr(filename.lastIndexOf('.') + 1); let language = LanguageMap[extension]; language = (language === undefined) ? 'text' : language; @@ -132,7 +131,7 @@ export class IdeComponent implements OnInit { } this.autosave = setInterval( () => { this.sendCodeIfDirty(); }, - config.ide.autoSaveInterval + this.configService.autoSaveInterval.value ); } @@ -142,7 +141,7 @@ export class IdeComponent implements OnInit { } this.filename = file; this.requestCode(); - this.autoDetectEditorLanguageIfEnabled(this.filename); + this.autoDetectEditorLanguage(this.filename); } editorWriteHandler() { diff --git a/src/app/messages/messages.component.ts b/src/app/messages/messages.component.ts index 7f87535..ecb7866 100644 --- a/src/app/messages/messages.component.ts +++ b/src/app/messages/messages.component.ts @@ -2,9 +2,7 @@ import { ChangeDetectorRef, Component, OnInit, EventEmitter, Output } from '@ang import { MessageData, Message } from '../message-types/bot-messages'; import { MarkdownService } from '../services/markdown.service'; import { WebSocketService } from '../services/websocket.service'; - -import { config } from '../config'; -import { Subject, Observer, BehaviorSubject } from 'rxjs'; +import { Subject } from 'rxjs'; @Component({ selector: 'app-messages', diff --git a/src/app/services/config.service.ts b/src/app/services/config.service.ts index 425a733..a26a801 100644 --- a/src/app/services/config.service.ts +++ b/src/app/services/config.service.ts @@ -1,38 +1,83 @@ import { Injectable } from '@angular/core'; import { WebSocketService } from './websocket.service'; -import { BehaviorSubject } from 'rxjs'; - +import { Subject, BehaviorSubject, forkJoin } from 'rxjs'; +import { ConfigServiceBase } from './config.service.base'; @Injectable() -abstract class ConfigService { - keys: Array = new Array(); - protected mau = 'cica'; +export class DashboardConfigService extends ConfigServiceBase { + keys = ['frontend.dashboard']; - constructor(private webSocketService: WebSocketService) {} + layout = new BehaviorSubject('terminal-ide-web'); + hideMessages = new BehaviorSubject(false); + iframeUrl = new BehaviorSubject('/webservice'); + showUrlBar = new BehaviorSubject(false); + terminalMenuItem = new BehaviorSubject('terminal'); + reloadIframeOnDeploy = new BehaviorSubject(false); + enabledLayouts = new BehaviorSubject>([ + '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(444); + showDeployButton = new BehaviorSubject(true); + deployButtonText = new BehaviorSubject({ + 'TODEPLOY': 'Deploy', + 'DEPLOYED': 'Deployed', + 'DEPLOYING': 'Reloading app...', + 'FAILED': 'Deployment failed' + }); +} + +@Injectable() +export class SiteConfigService extends ConfigServiceBase { + keys = ['frontend.site']; + + askReloadSite = new BehaviorSubject(false); + documentTitle = new BehaviorSubject('Avatao Tutorials'); +} + +@Injectable() +export class ConfigReadyService { + configDone = new Subject(); + readyNotifiers: Array; + + 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() { this.webSocketService.connect(); - this.keys.forEach(key => { - key = 'frontend.config.' + key; - this.webSocketService.observeKey(key).subscribe(this.handleConfig.bind(this)); - }); - } - handleConfig(message: any) { - Object.keys(message).filter(key => key !== 'key').forEach(key => { - if (this[key] === undefined) { - console.log(`Invalid ${this.keys} config key "${key}"!`); - } else { - this[key].next(message[key]); - } + const recvdConfigDone = new Subject(); + this.readyNotifiers.push(recvdConfigDone); + this.webSocketService.observeKey('frontend.config.done').subscribe(() => { + recvdConfigDone.next(); + recvdConfigDone.complete(); + }); + + forkJoin(this.readyNotifiers).subscribe(undefined, undefined, () => { + this.configDone.next(); + this.configDone.complete(); }); } } - - -@Injectable() -export class IdeConfigService extends ConfigService { - keys = ['ide']; - - showDeployButton = new BehaviorSubject(true); -} diff --git a/src/app/sidebar/sidebar.component.html b/src/app/sidebar/sidebar.component.html index 3dc9779..8402989 100644 --- a/src/app/sidebar/sidebar.component.html +++ b/src/app/sidebar/sidebar.component.html @@ -1,5 +1,4 @@ -
- +
-
diff --git a/src/app/sidebar/sidebar.component.ts b/src/app/sidebar/sidebar.component.ts index 3b69301..b81238f 100644 --- a/src/app/sidebar/sidebar.component.ts +++ b/src/app/sidebar/sidebar.component.ts @@ -1,18 +1,17 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'; -import { config } from '../config'; +import { DashboardConfigService } from '../services/config.service'; @Component({ selector: 'app-sidebar', templateUrl: './sidebar.component.html', styleUrls: ['./sidebar.component.scss'] }) - export class SidebarComponent { @Input() layout: string; @Output() layoutChanged = new EventEmitter(); - enabledLayouts: string[] = config.dashboard.enabledLayouts; + enabledLayouts = this.configService.enabledLayouts; - constructor() {} + constructor(private configService: DashboardConfigService) {} setLayout(layout: string) { this.layout = layout; diff --git a/src/app/web/web.component.html b/src/app/web/web.component.html deleted file mode 100644 index 63f9bf4..0000000 --- a/src/app/web/web.component.html +++ /dev/null @@ -1,2 +0,0 @@ -

WebComponent is here!

-

Edit me if you want to write your own component in Angular!

diff --git a/src/app/web/web.component.scss b/src/app/web/web.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/web/web.component.ts b/src/app/web/web.component.ts deleted file mode 100644 index ff36dd3..0000000 --- a/src/app/web/web.component.ts +++ /dev/null @@ -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() {} -}