mirror of
				https://github.com/avatao-content/frontend-tutorial-framework
				synced 2025-11-04 12:12:55 +00:00 
			
		
		
		
	Make whole frontend dynamically configurable to avoid rebuilds
This commit is contained in:
		@@ -1 +1,2 @@
 | 
			
		||||
<router-outlet></router-outlet>
 | 
			
		||||
<div #router><router-outlet></router-outlet></div>
 | 
			
		||||
<app-loader [toBlur]="router"></app-loader>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,3 @@
 | 
			
		||||
.blur {
 | 
			
		||||
  filter: blur(3px);
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {}
 | 
			
		||||
 
 | 
			
		||||
@@ -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'
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@@ -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<string>();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,31 +1,30 @@
 | 
			
		||||
<div [attr.class]="layout">
 | 
			
		||||
<div [attr.class]="layout | async">
 | 
			
		||||
  <div class="tfw-grid-main-components">
 | 
			
		||||
    <div class="tfw-header"><app-header></app-header></div>
 | 
			
		||||
    <div [ngClass]="{'hide-attribute': hideMessages}"
 | 
			
		||||
    <div [ngClass]="{'hide-attribute': hideMessages | async}"
 | 
			
		||||
         class="tfw-messages"
 | 
			
		||||
         #tfwmessages>
 | 
			
		||||
      <app-messages (newMessageEvent)="scrollMessagesToBottom()"></app-messages>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="tfw-web tao-grid-top-left"
 | 
			
		||||
         [ngClass]="{'deploy-blur': deploying || polling}">
 | 
			
		||||
         <app-web *ngIf="!iframeUrl"></app-web>
 | 
			
		||||
         <div *ngIf="showUrlBar" class="urlbar-container">
 | 
			
		||||
           <button class="refresh btn btn-sm rounded-circle" (click)="reloadIframe()">↻</button>
 | 
			
		||||
           <input type="text"
 | 
			
		||||
                  #urlbar
 | 
			
		||||
                  class="urlbar form-control"
 | 
			
		||||
                  value="{{actualIframeUrl}}"
 | 
			
		||||
                  (keyup.enter)="changeIframeURL()">
 | 
			
		||||
           <button class="go btn btn-sm rounded-circle" (click)="changeIframeURL()">⇨</button>
 | 
			
		||||
         </div>
 | 
			
		||||
         <div *ngIf="iframeUrl" class="iframe-container">
 | 
			
		||||
            <iframe class="iframe"
 | 
			
		||||
                    #webiframe
 | 
			
		||||
                    scrolling="yes"
 | 
			
		||||
                    frameborder="0"
 | 
			
		||||
                    (load)="iframeLoad()"
 | 
			
		||||
                    [src]="iframeUrl | safe">
 | 
			
		||||
            </iframe>
 | 
			
		||||
        <div class="iframe-container">
 | 
			
		||||
          <div *ngIf="showUrlBar | async" class="urlbar-container">
 | 
			
		||||
            <button class="refresh btn btn-sm rounded-circle" (click)="reloadIframe()">↻</button>
 | 
			
		||||
            <input type="text"
 | 
			
		||||
                   #urlbar
 | 
			
		||||
                   class="urlbar form-control"
 | 
			
		||||
                   value="{{actualIframeUrl}}"
 | 
			
		||||
                   (keyup.enter)="changeIframeURL()">
 | 
			
		||||
            <button class="go btn btn-sm rounded-circle" (click)="changeIframeURL()">⇨</button>
 | 
			
		||||
          </div>
 | 
			
		||||
          <iframe class="iframe"
 | 
			
		||||
                  #webiframe
 | 
			
		||||
                  scrolling="yes"
 | 
			
		||||
                  frameborder="0"
 | 
			
		||||
                  (load)="iframeLoad()"
 | 
			
		||||
                  [src]="iframeUrl | async | safe">
 | 
			
		||||
          </iframe>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <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">
 | 
			
		||||
        <button class="tfw-console-terminal-menu-button"
 | 
			
		||||
                (click)="selectTerminalMenuItem('terminal')"
 | 
			
		||||
                [class.selected]="selectedTerminalMenuItem === 'terminal'">TERMINAL</button>
 | 
			
		||||
                [class.selected]="(terminalMenuItem | async) === 'terminal'">TERMINAL</button>
 | 
			
		||||
        <button class="tfw-console-terminal-menu-button"
 | 
			
		||||
                (click)="selectTerminalMenuItem('console')"
 | 
			
		||||
                [class.selected]="selectedTerminalMenuItem === 'console'">CONSOLE</button>
 | 
			
		||||
                [class.selected]="(terminalMenuItem | async) === 'console'">CONSOLE</button>
 | 
			
		||||
      </div>
 | 
			
		||||
      <hr>
 | 
			
		||||
      <app-terminal [class.hidden]="selectedTerminalMenuItem !== 'terminal'"></app-terminal>
 | 
			
		||||
      <app-console [class.hidden]="selectedTerminalMenuItem !== 'console'"></app-console>
 | 
			
		||||
      <app-terminal [class.hidden]="(terminalMenuItem | async) !== 'terminal'"></app-terminal>
 | 
			
		||||
      <app-console [class.hidden]="(terminalMenuItem | async) !== 'console'"></app-console>
 | 
			
		||||
    </div>
 | 
			
		||||
    <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 class="tfw-terminal-footer"></div>
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div class="btn-group-sm tfw-deploy-btn-group">
 | 
			
		||||
    <button *ngIf="showDeployButton"
 | 
			
		||||
    <button *ngIf="showDeployButton | async"
 | 
			
		||||
            type="submit"
 | 
			
		||||
            class="btn tfw-deploy-btn tao-grid-top-center"
 | 
			
		||||
            (click)="sendCodeIfDirty(); deployCode()"
 | 
			
		||||
@@ -23,13 +23,13 @@
 | 
			
		||||
            [class.deploy]="deployButtonState === DeployButtonState.DEPLOYING"
 | 
			
		||||
            [class.disabled]="deployButtonState === DeployButtonState.DEPLOYING || deployButtonState === DeployButtonState.DEPLOYED"
 | 
			
		||||
            [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">
 | 
			
		||||
        <img src="images/greentick_icon.svg"/>
 | 
			
		||||
        <span>{{deployButtonText['DEPLOYED']}}</span>
 | 
			
		||||
        <span>{{(deployButtonText | async)['DEPLOYED']}}</span>
 | 
			
		||||
      </span>
 | 
			
		||||
      <span *ngIf="deployButtonState === DeployButtonState.DEPLOYING"><div class="loader"></div>{{deployButtonText['DEPLOYING']}}</span>
 | 
			
		||||
      <span *ngIf="deployButtonState === DeployButtonState.FAILED">{{deployButtonText['FAILED']}}</span>
 | 
			
		||||
      <span *ngIf="deployButtonState === DeployButtonState.DEPLOYING"><div class="loader"></div>{{(deployButtonText | async)['DEPLOYING']}}</span>
 | 
			
		||||
      <span *ngIf="deployButtonState === DeployButtonState.FAILED">{{(deployButtonText | async)['FAILED']}}</span>
 | 
			
		||||
    </button>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -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<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);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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() {
 | 
			
		||||
 
 | 
			
		||||
@@ -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',
 | 
			
		||||
 
 | 
			
		||||
@@ -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<string> = new Array<string>();
 | 
			
		||||
  protected mau = 'cica';
 | 
			
		||||
export class DashboardConfigService extends ConfigServiceBase {
 | 
			
		||||
  keys = ['frontend.dashboard'];
 | 
			
		||||
 | 
			
		||||
  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() {
 | 
			
		||||
    this.webSocketService.connect();
 | 
			
		||||
    this.keys.forEach(key => {
 | 
			
		||||
        key = 'frontend.config.' + key;
 | 
			
		||||
        this.webSocketService.observeKey<any>(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<void>();
 | 
			
		||||
    this.readyNotifiers.push(recvdConfigDone);
 | 
			
		||||
    this.webSocketService.observeKey<any>('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<boolean>(true);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
    <button [class.active]="layout === layoutIter"
 | 
			
		||||
            (active)="layout === layoutIter"
 | 
			
		||||
@@ -7,5 +6,4 @@
 | 
			
		||||
            (click)="setLayout(layoutIter)"
 | 
			
		||||
            class="btn"><img src="images/{{layoutIter}}.svg"></button>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -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<string>();
 | 
			
		||||
  enabledLayouts: string[] = config.dashboard.enabledLayouts;
 | 
			
		||||
  enabledLayouts = this.configService.enabledLayouts;
 | 
			
		||||
 | 
			
		||||
  constructor() {}
 | 
			
		||||
  constructor(private configService: DashboardConfigService) {}
 | 
			
		||||
 | 
			
		||||
  setLayout(layout: string) {
 | 
			
		||||
    this.layout = layout;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +0,0 @@
 | 
			
		||||
<h2>WebComponent is here!</h2>
 | 
			
		||||
<p>Edit me if you want to write your own component in Angular!</p>
 | 
			
		||||
@@ -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() {}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user