diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index acc3389..d7adb2a 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -7,16 +7,15 @@ import { TerminalComponent } from './terminal/terminal.component'; import { MessagesComponent } from './messages/messages.component'; import { ConsoleComponent } from './console/console.component'; import { TestmessengerComponent } from './testmessenger/testmessenger.component'; -import { config } from './config'; const routes: Routes = [ { path: '', redirectTo: '/dashboard', pathMatch: 'full'}, - { path: config.dashboard.route, component: DashboardComponent}, - { path: config.ide.route, component: IdeComponent }, - { path: config.terminal.route, component: TerminalComponent }, - { path: config.messages.route, component: MessagesComponent }, - { path: config.console.route, component: ConsoleComponent }, - { path: config.testmessenger.route, component: TestmessengerComponent } + { path: 'dashboard', component: DashboardComponent}, + { path: 'ide', component: IdeComponent }, + { path: 'terminal', component: TerminalComponent }, + { path: 'messages', component: MessagesComponent }, + { path: 'console', component: ConsoleComponent }, + { path: 'testmessenger', component: TestmessengerComponent } ]; @NgModule({ 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 da2ed9b..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,6 +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 { + ConfigReadyService, + IdeConfigService, + DashboardConfigService, + SiteConfigService +} from './services/config.service'; +import { LoaderComponent } from './loader/loader.component'; @NgModule({ @@ -28,14 +34,14 @@ import { MonacoEditorModule } from 'ngx-monaco-editor'; AppComponent, HeaderComponent, SidebarComponent, - WebComponent, IdeComponent, MessagesComponent, TerminalComponent, DashboardComponent, TestmessengerComponent, SafePipe, - ConsoleComponent + ConsoleComponent, + LoaderComponent ], imports: [ BrowserModule, @@ -50,9 +56,13 @@ import { MonacoEditorModule } from 'ngx-monaco-editor'; WebSocketService, TerminadoService, DeploymentNotificationService, + 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..21f7dc4 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.scss b/src/app/dashboard/dashboard.component.scss index 1d25e72..8552c99 100644 --- a/src/app/dashboard/dashboard.component.scss +++ b/src/app/dashboard/dashboard.component.scss @@ -66,6 +66,7 @@ .tfw-header { padding: $small; + padding-top: $tiny; background-color: $tao-gray-50; } diff --git a/src/app/dashboard/dashboard.component.ts b/src/app/dashboard/dashboard.component.ts index d4b8194..66b0aca 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,18 +112,18 @@ export class DashboardComponent implements OnInit, OnDestroy { if (!item.match('(terminal|console)')) { return; } - this.selectedTerminalMenuItem = item; + this.terminalMenuItem.next(item); } - scrollMessagesToBottom(): void { + 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(): void { - if (this.webiframe) { + iframeLoad() { + if (this.webiframe && this.iframeUrl.value) { const href = this.webiframe.nativeElement.contentWindow.frames.location.href; const niceURL = href.match(/.*?\/\/.*?(\/.*)/)[1]; this.actualIframeUrl = niceURL; 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/loader/loader.component.html b/src/app/loader/loader.component.html new file mode 100644 index 0000000..45d8204 --- /dev/null +++ b/src/app/loader/loader.component.html @@ -0,0 +1,7 @@ +
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/app/loader/loader.component.scss b/src/app/loader/loader.component.scss new file mode 100644 index 0000000..efc0a00 --- /dev/null +++ b/src/app/loader/loader.component.scss @@ -0,0 +1,62 @@ +.loading-container { + position: absolute; + top: 0; + left: 0; + margin: 0; + padding: 0; + display: flex; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.2); + z-index: 99999; + filter: blur(0px)!important; +} + +.loader { + position: relative; + filter: blur(0px) !important; +} + +.outer, +.middle, +.inner { + border: 3px solid transparent; + border-top-color: #277EEC; + border-right-color: #277EEC; + border-radius: 50%; + position: absolute; + top: 50%; + left: 50%; +} + +.outer { + width: 3.5em; + height: 3.5em; + margin-left: -1.75em; + margin-top: -1.75em; + animation: spin 2s linear infinite; +} + +.middle { + width: 2.1em; + height: 2.1em; + margin-left: -1.05em; + margin-top: -1.05em; + animation: spin 1.75s linear reverse infinite; +} + +.inner { + width: 0.8em; + height: 0.8em; + margin-left: -0.4em; + margin-top: -0.4em; + animation: spin 1.5s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} diff --git a/src/app/loader/loader.component.ts b/src/app/loader/loader.component.ts new file mode 100644 index 0000000..b70f5e1 --- /dev/null +++ b/src/app/loader/loader.component.ts @@ -0,0 +1,29 @@ +import { Component, Input, AfterViewInit, OnInit } from '@angular/core'; +import { ConfigReadyService } from '../services/config.service'; + + +@Component({ + selector: 'app-loader', + templateUrl: './loader.component.html', + styleUrls: ['./loader.component.scss'] +}) +export class LoaderComponent implements OnInit, AfterViewInit { + @Input() toBlur: HTMLElement; + loading = true; + + constructor(private configReadyService: ConfigReadyService) {} + + ngOnInit() { + this.configReadyService.init(); + } + + ngAfterViewInit() { + this.toBlur.classList.add('blur'); + this.configReadyService.configDone.subscribe( + () => { + this.loading = false; + this.toBlur.classList.remove('blur'); + } + ); + } +} 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.base.ts b/src/app/services/config.service.base.ts new file mode 100644 index 0000000..74c1b80 --- /dev/null +++ b/src/app/services/config.service.base.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@angular/core'; +import { WebSocketService } from './websocket.service'; +import { Subject, BehaviorSubject, merge } from 'rxjs'; +import { take } from 'rxjs/operators'; + +@Injectable() +export abstract class ConfigServiceBase { + initDone = false; + keys = new Array(); + mandatoryConfigs = new Array(); + configDone = new Subject(); + + constructor(private webSocketService: WebSocketService) {} + + init() { + if (!this.initDone) { + this.waitForMandatoryConfigs(); + this.subscribeConfigKeys(); + this.initDone = true; + } + } + + waitForMandatoryConfigs() { + const firstConfigs = new Array(); + this.mandatoryConfigs.forEach((config) => { + const requiredEmitCount = config instanceof BehaviorSubject ? 2 : 1; + firstConfigs.push(config.pipe(take(requiredEmitCount))); + }); + + merge(...firstConfigs).subscribe(undefined, undefined, () => { + this.configDone.next(); + this.configDone.complete(); + }); + } + + subscribeConfigKeys() { + this.webSocketService.connect(); + this.keys.forEach(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]); + } + }); + } +} diff --git a/src/app/services/config.service.ts b/src/app/services/config.service.ts new file mode 100644 index 0000000..561e798 --- /dev/null +++ b/src/app/services/config.service.ts @@ -0,0 +1,83 @@ +import { Injectable } from '@angular/core'; +import { WebSocketService } from './websocket.service'; +import { Subject, BehaviorSubject, merge } from 'rxjs'; +import { ConfigServiceBase } from './config.service.base'; + +@Injectable() +export class DashboardConfigService extends ConfigServiceBase { + keys = ['frontend.dashboard']; + + 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(); + + const recvdConfigDone = new Subject(); + this.readyNotifiers.push(recvdConfigDone); + this.webSocketService.observeKey('frontend.ready').subscribe(() => { + recvdConfigDone.next(); + recvdConfigDone.complete(); + }); + + merge(...this.readyNotifiers).subscribe(undefined, undefined, () => { + this.configDone.next(); + this.configDone.complete(); + }); + } +} diff --git a/src/app/services/terminado.service.ts b/src/app/services/terminado.service.ts index ed65e34..eb523a8 100644 --- a/src/app/services/terminado.service.ts +++ b/src/app/services/terminado.service.ts @@ -7,6 +7,7 @@ import * as terminado from 'xterm/lib/addons/terminado/terminado'; export class TerminadoService { xterm: Terminal; ws: WebSocket; + attached = false; constructor() { Terminal.applyAddon(fit); @@ -47,6 +48,7 @@ export class TerminadoService { this.xterm.open(element); this.fit(); this.xterm.blur(); + this.attached = true; }; } @@ -54,9 +56,12 @@ export class TerminadoService { (this.xterm).terminadoDetach(this.ws); this.xterm.destroy(); this.ws.close(); + this.attached = false; } fit() { - (this.xterm).fit(); + if (this.attached) { + (this.xterm).fit(); + } } } 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() {} -} diff --git a/src/assets/scss/mixins/_layout.scss b/src/assets/scss/mixins/_layout.scss index cddfa5d..dc6d682 100644 --- a/src/assets/scss/mixins/_layout.scss +++ b/src/assets/scss/mixins/_layout.scss @@ -2,8 +2,8 @@ $grid-columns-count: 100; $grid-rows-count: 30; $terminal-ide-web-layout: ( - 'header': (0, 20, 0, 4), - 'messages': (0, 20, 4, 60), + 'header': (0, 20, 0, 8), + 'messages': (0, 20, 8, 60), 'ide': (56, 96, 0, 100), 'terminal': (0, 56, 60, 100), 'web': (20, 56, 0, 60), @@ -11,8 +11,8 @@ $terminal-ide-web-layout: ( ); $terminal-web-layout: ( - 'header': (0, 20, 0, 4), - 'messages': (0, 20, 2, 100), + 'header': (0, 20, 0, 8), + 'messages': (0, 20, 8, 100), 'ide': (), 'terminal': (56, 96, 0, 100), 'web': (20, 56, 0, 100), @@ -20,8 +20,8 @@ $terminal-web-layout: ( ); $terminal-ide-vertical-layout: ( - 'header': (0, 20, 0, 4), - 'messages': (0, 20, 2, 100), + 'header': (0, 20, 0, 8), + 'messages': (0, 20, 8, 100), 'ide': (56, 96, 0, 100), 'terminal': (20, 56, 0, 100), 'web': (), @@ -29,8 +29,8 @@ $terminal-ide-vertical-layout: ( ); $terminal-ide-horizontal-layout: ( - 'header': (0, 20, 0, 4), - 'messages': (0, 20, 2, 100), + 'header': (0, 20, 0, 8), + 'messages': (0, 20, 8, 100), 'ide': (20, 96, 0, 60), 'terminal': (20, 96, 60, 100), 'web': (), @@ -38,8 +38,8 @@ $terminal-ide-horizontal-layout: ( ); $ide-web-vertical-layout: ( - 'header': (0, 20, 0, 4), - 'messages': (0, 20, 4, 100), + 'header': (0, 20, 0, 8), + 'messages': (0, 20, 8, 100), 'ide': (56, 96, 0, 100), 'terminal': (), 'web': (20, 56, 0, 100), @@ -47,8 +47,8 @@ $ide-web-vertical-layout: ( ); $terminal-only-layout: ( - 'header': (0, 20, 0, 4), - 'messages': (0, 20, 4, 100), + 'header': (0, 20, 0, 8), + 'messages': (0, 20, 8, 100), 'ide': (), 'terminal': (20, 96, 0, 100), 'web': (), @@ -56,8 +56,8 @@ $terminal-only-layout: ( ); $ide-only-layout: ( - 'header': (0, 20, 0, 4), - 'messages': (0, 20, 4, 100), + 'header': (0, 20, 0, 8), + 'messages': (0, 20, 8, 100), 'ide': (20, 96, 0, 100), 'terminal': (), 'web': (), @@ -65,8 +65,8 @@ $ide-only-layout: ( ); $web-only-layout: ( - 'header': (0, 20, 0, 4), - 'messages': (0, 20, 4, 100), + 'header': (0, 20, 0, 8), + 'messages': (0, 20, 8, 100), 'ide': (), 'terminal': (), 'web': (20, 96, 0, 100), @@ -142,7 +142,7 @@ $layouts: ( @if (length($tfw-component) > 0) { $columns-count: convert-ratio(nth($tfw-component,2), $grid-columns-count) - convert-ratio(nth($tfw-component,1), $grid-columns-count); - $rows-count: convert-ratio(nth($tfw-component,4), $grid-columns-count) - convert-ratio(nth($tfw-component,3), $grid-columns-count); + $rows-count: convert-ratio(nth($tfw-component,4), $grid-rows-count) - convert-ratio(nth($tfw-component,3), $grid-rows-count); min-width: #{$columns-count / $grid-columns-count * 100}vw; min-height: #{$rows-count / $grid-rows-count * 100}vh; diff --git a/src/assets/scss/mixins/_scrollbar.scss b/src/assets/scss/mixins/_scrollbar.scss index 9a4c3fd..41bae4d 100644 --- a/src/assets/scss/mixins/_scrollbar.scss +++ b/src/assets/scss/mixins/_scrollbar.scss @@ -1,6 +1,7 @@ @mixin set-scrollbar-style($style, $selctor) { ::-webkit-scrollbar { width: 3px !important; + height: 3px !important; } #{$selctor}::-webkit-scrollbar-track {