mirror of
				https://github.com/avatao-content/frontend-tutorial-framework
				synced 2025-11-04 05:32:55 +00:00 
			
		
		
		
	Merge branch 'dynamic_configger' into chausie
This commit is contained in:
		@@ -7,16 +7,15 @@ import { TerminalComponent } from './terminal/terminal.component';
 | 
				
			|||||||
import { MessagesComponent } from './messages/messages.component';
 | 
					import { MessagesComponent } from './messages/messages.component';
 | 
				
			||||||
import { ConsoleComponent } from './console/console.component';
 | 
					import { ConsoleComponent } from './console/console.component';
 | 
				
			||||||
import { TestmessengerComponent } from './testmessenger/testmessenger.component';
 | 
					import { TestmessengerComponent } from './testmessenger/testmessenger.component';
 | 
				
			||||||
import { config } from './config';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const routes: Routes = [
 | 
					const routes: Routes = [
 | 
				
			||||||
  { path: '', redirectTo: '/dashboard', pathMatch: 'full'},
 | 
					  { path: '', redirectTo: '/dashboard', pathMatch: 'full'},
 | 
				
			||||||
  { path: config.dashboard.route, component: DashboardComponent},
 | 
					  { path: 'dashboard', component: DashboardComponent},
 | 
				
			||||||
  { path: config.ide.route, component: IdeComponent },
 | 
					  { path: 'ide', component: IdeComponent },
 | 
				
			||||||
  { path: config.terminal.route, component: TerminalComponent },
 | 
					  { path: 'terminal', component: TerminalComponent },
 | 
				
			||||||
  { path: config.messages.route, component: MessagesComponent },
 | 
					  { path: 'messages', component: MessagesComponent },
 | 
				
			||||||
  { path: config.console.route, component: ConsoleComponent },
 | 
					  { path: 'console', component: ConsoleComponent },
 | 
				
			||||||
  { path: config.testmessenger.route, component: TestmessengerComponent }
 | 
					  { path: 'testmessenger', component: TestmessengerComponent }
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@NgModule({
 | 
					@NgModule({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 { 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) => {
 | 
					
 | 
				
			||||||
 | 
					    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?';
 | 
					    const confirmationMessage = 'Refreshing this page may mess up your challenge/tutorial state. Are you sure?';
 | 
				
			||||||
    event.returnValue = confirmationMessage;
 | 
					    event.returnValue = confirmationMessage;
 | 
				
			||||||
    return confirmationMessage;
 | 
					    return confirmationMessage;
 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,6 +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 {
 | 
				
			||||||
 | 
					  ConfigReadyService,
 | 
				
			||||||
 | 
					  IdeConfigService,
 | 
				
			||||||
 | 
					  DashboardConfigService,
 | 
				
			||||||
 | 
					  SiteConfigService
 | 
				
			||||||
 | 
					} from './services/config.service';
 | 
				
			||||||
 | 
					import { LoaderComponent } from './loader/loader.component';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@NgModule({
 | 
					@NgModule({
 | 
				
			||||||
@@ -28,14 +34,14 @@ import { MonacoEditorModule } from 'ngx-monaco-editor';
 | 
				
			|||||||
    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,
 | 
				
			||||||
@@ -50,6 +56,10 @@ import { MonacoEditorModule } from 'ngx-monaco-editor';
 | 
				
			|||||||
    WebSocketService,
 | 
					    WebSocketService,
 | 
				
			||||||
    TerminadoService,
 | 
					    TerminadoService,
 | 
				
			||||||
    DeploymentNotificationService,
 | 
					    DeploymentNotificationService,
 | 
				
			||||||
 | 
					    ConfigReadyService,
 | 
				
			||||||
 | 
					    IdeConfigService,
 | 
				
			||||||
 | 
					    DashboardConfigService,
 | 
				
			||||||
 | 
					    SiteConfigService
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  bootstrap: [
 | 
					  bootstrap: [
 | 
				
			||||||
    AppComponent
 | 
					    AppComponent
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 { 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>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,15 @@
 | 
				
			|||||||
<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 *ngIf="iframeUrl | async" 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()">↻</button>
 | 
					            <button class="refresh btn btn-sm rounded-circle" (click)="reloadIframe()">↻</button>
 | 
				
			||||||
            <input type="text"
 | 
					            <input type="text"
 | 
				
			||||||
                   #urlbar
 | 
					                   #urlbar
 | 
				
			||||||
@@ -18,13 +18,12 @@
 | 
				
			|||||||
                   (keyup.enter)="changeIframeURL()">
 | 
					                   (keyup.enter)="changeIframeURL()">
 | 
				
			||||||
            <button class="go btn btn-sm rounded-circle" (click)="changeIframeURL()">⇨</button>
 | 
					            <button class="go btn btn-sm rounded-circle" (click)="changeIframeURL()">⇨</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 | safe">
 | 
					                  [src]="iframeUrl | async | safe">
 | 
				
			||||||
          </iframe>
 | 
					          </iframe>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@@ -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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -66,6 +66,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  .tfw-header {
 | 
					  .tfw-header {
 | 
				
			||||||
    padding: $small;
 | 
					    padding: $small;
 | 
				
			||||||
 | 
					    padding-top: $tiny;
 | 
				
			||||||
    background-color: $tao-gray-50;
 | 
					    background-color: $tao-gray-50;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,27 +72,9 @@ 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) {
 | 
				
			||||||
    setTimeout(() => window.location.reload(), 2000);
 | 
					    setTimeout(() => window.location.reload(), 2000);
 | 
				
			||||||
@@ -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,18 +112,18 @@ 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() {
 | 
				
			||||||
    const element = this.messages.nativeElement;
 | 
					    const element = this.messages.nativeElement;
 | 
				
			||||||
    // This must be done in the Angular event loop to avoid messing up
 | 
					    // This must be done in the Angular event loop to avoid messing up
 | 
				
			||||||
    // change detection (not in the template like ConsoleComponent does)
 | 
					    // change detection (not in the template like ConsoleComponent does)
 | 
				
			||||||
    element.scrollTop = element.scrollHeight;
 | 
					    element.scrollTop = element.scrollHeight;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  iframeLoad(): void {
 | 
					  iframeLoad() {
 | 
				
			||||||
    if (this.webiframe) {
 | 
					    if (this.webiframe && this.iframeUrl.value) {
 | 
				
			||||||
      const href = this.webiframe.nativeElement.contentWindow.frames.location.href;
 | 
					      const href = this.webiframe.nativeElement.contentWindow.frames.location.href;
 | 
				
			||||||
      const niceURL = href.match(/.*?\/\/.*?(\/.*)/)[1];
 | 
					      const niceURL = href.match(/.*?\/\/.*?(\/.*)/)[1];
 | 
				
			||||||
      this.actualIframeUrl = niceURL;
 | 
					      this.actualIframeUrl = niceURL;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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() {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								src/app/loader/loader.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/app/loader/loader.component.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					<div *ngIf="loading" class="loading-container">
 | 
				
			||||||
 | 
					  <div class=" loader">
 | 
				
			||||||
 | 
					    <div class="outer"></div>
 | 
				
			||||||
 | 
					    <div class="middle"></div>
 | 
				
			||||||
 | 
					    <div class="inner"></div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
							
								
								
									
										62
									
								
								src/app/loader/loader.component.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/app/loader/loader.component.scss
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										29
									
								
								src/app/loader/loader.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/app/loader/loader.component.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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',
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										52
									
								
								src/app/services/config.service.base.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/app/services/config.service.base.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<string>();
 | 
				
			||||||
 | 
					  mandatoryConfigs = new Array<any>();
 | 
				
			||||||
 | 
					  configDone = new Subject<void>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(private webSocketService: WebSocketService) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  init() {
 | 
				
			||||||
 | 
					    if (!this.initDone) {
 | 
				
			||||||
 | 
					      this.waitForMandatoryConfigs();
 | 
				
			||||||
 | 
					      this.subscribeConfigKeys();
 | 
				
			||||||
 | 
					      this.initDone = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  waitForMandatoryConfigs() {
 | 
				
			||||||
 | 
					    const firstConfigs = new Array<any>();
 | 
				
			||||||
 | 
					    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<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]);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										83
									
								
								src/app/services/config.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/app/services/config.service.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const recvdConfigDone = new Subject<void>();
 | 
				
			||||||
 | 
					    this.readyNotifiers.push(recvdConfigDone);
 | 
				
			||||||
 | 
					    this.webSocketService.observeKey<any>('frontend.ready').subscribe(() => {
 | 
				
			||||||
 | 
					      recvdConfigDone.next();
 | 
				
			||||||
 | 
					      recvdConfigDone.complete();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    merge(...this.readyNotifiers).subscribe(undefined, undefined, () => {
 | 
				
			||||||
 | 
					      this.configDone.next();
 | 
				
			||||||
 | 
					      this.configDone.complete();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -7,6 +7,7 @@ import * as terminado from 'xterm/lib/addons/terminado/terminado';
 | 
				
			|||||||
export class TerminadoService {
 | 
					export class TerminadoService {
 | 
				
			||||||
  xterm: Terminal;
 | 
					  xterm: Terminal;
 | 
				
			||||||
  ws: WebSocket;
 | 
					  ws: WebSocket;
 | 
				
			||||||
 | 
					  attached = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor() {
 | 
					  constructor() {
 | 
				
			||||||
    Terminal.applyAddon(fit);
 | 
					    Terminal.applyAddon(fit);
 | 
				
			||||||
@@ -47,6 +48,7 @@ export class TerminadoService {
 | 
				
			|||||||
      this.xterm.open(element);
 | 
					      this.xterm.open(element);
 | 
				
			||||||
      this.fit();
 | 
					      this.fit();
 | 
				
			||||||
      this.xterm.blur();
 | 
					      this.xterm.blur();
 | 
				
			||||||
 | 
					      this.attached = true;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -54,9 +56,12 @@ export class TerminadoService {
 | 
				
			|||||||
    (<any>this.xterm).terminadoDetach(this.ws);
 | 
					    (<any>this.xterm).terminadoDetach(this.ws);
 | 
				
			||||||
    this.xterm.destroy();
 | 
					    this.xterm.destroy();
 | 
				
			||||||
    this.ws.close();
 | 
					    this.ws.close();
 | 
				
			||||||
 | 
					    this.attached = false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fit() {
 | 
					  fit() {
 | 
				
			||||||
 | 
					    if (this.attached) {
 | 
				
			||||||
      (<any>this.xterm).fit();
 | 
					      (<any>this.xterm).fit();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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() {}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -2,8 +2,8 @@ $grid-columns-count: 100;
 | 
				
			|||||||
$grid-rows-count: 30;
 | 
					$grid-rows-count: 30;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$terminal-ide-web-layout: (
 | 
					$terminal-ide-web-layout: (
 | 
				
			||||||
  'header': (0, 20, 0, 4),
 | 
					  'header': (0, 20, 0, 8),
 | 
				
			||||||
  'messages': (0, 20, 4, 60),
 | 
					  'messages': (0, 20, 8, 60),
 | 
				
			||||||
  'ide': (56, 96, 0, 100),
 | 
					  'ide': (56, 96, 0, 100),
 | 
				
			||||||
  'terminal': (0, 56, 60, 100),
 | 
					  'terminal': (0, 56, 60, 100),
 | 
				
			||||||
  'web': (20, 56, 0, 60),
 | 
					  'web': (20, 56, 0, 60),
 | 
				
			||||||
@@ -11,8 +11,8 @@ $terminal-ide-web-layout: (
 | 
				
			|||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$terminal-web-layout: (
 | 
					$terminal-web-layout: (
 | 
				
			||||||
  'header': (0, 20, 0, 4),
 | 
					  'header': (0, 20, 0, 8),
 | 
				
			||||||
  'messages': (0, 20, 2, 100),
 | 
					  'messages': (0, 20, 8, 100),
 | 
				
			||||||
  'ide': (),
 | 
					  'ide': (),
 | 
				
			||||||
  'terminal': (56, 96, 0, 100),
 | 
					  'terminal': (56, 96, 0, 100),
 | 
				
			||||||
  'web': (20, 56, 0, 100),
 | 
					  'web': (20, 56, 0, 100),
 | 
				
			||||||
@@ -20,8 +20,8 @@ $terminal-web-layout: (
 | 
				
			|||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$terminal-ide-vertical-layout: (
 | 
					$terminal-ide-vertical-layout: (
 | 
				
			||||||
  'header': (0, 20, 0, 4),
 | 
					  'header': (0, 20, 0, 8),
 | 
				
			||||||
  'messages': (0, 20, 2, 100),
 | 
					  'messages': (0, 20, 8, 100),
 | 
				
			||||||
  'ide': (56, 96, 0, 100),
 | 
					  'ide': (56, 96, 0, 100),
 | 
				
			||||||
  'terminal': (20, 56, 0, 100),
 | 
					  'terminal': (20, 56, 0, 100),
 | 
				
			||||||
  'web': (),
 | 
					  'web': (),
 | 
				
			||||||
@@ -29,8 +29,8 @@ $terminal-ide-vertical-layout: (
 | 
				
			|||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$terminal-ide-horizontal-layout: (
 | 
					$terminal-ide-horizontal-layout: (
 | 
				
			||||||
  'header': (0, 20, 0, 4),
 | 
					  'header': (0, 20, 0, 8),
 | 
				
			||||||
  'messages': (0, 20, 2, 100),
 | 
					  'messages': (0, 20, 8, 100),
 | 
				
			||||||
  'ide': (20, 96, 0, 60),
 | 
					  'ide': (20, 96, 0, 60),
 | 
				
			||||||
  'terminal': (20, 96, 60, 100),
 | 
					  'terminal': (20, 96, 60, 100),
 | 
				
			||||||
  'web': (),
 | 
					  'web': (),
 | 
				
			||||||
@@ -38,8 +38,8 @@ $terminal-ide-horizontal-layout: (
 | 
				
			|||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$ide-web-vertical-layout: (
 | 
					$ide-web-vertical-layout: (
 | 
				
			||||||
  'header': (0, 20, 0, 4),
 | 
					  'header': (0, 20, 0, 8),
 | 
				
			||||||
  'messages': (0, 20, 4, 100),
 | 
					  'messages': (0, 20, 8, 100),
 | 
				
			||||||
  'ide': (56, 96, 0, 100),
 | 
					  'ide': (56, 96, 0, 100),
 | 
				
			||||||
  'terminal': (),
 | 
					  'terminal': (),
 | 
				
			||||||
  'web': (20, 56, 0, 100),
 | 
					  'web': (20, 56, 0, 100),
 | 
				
			||||||
@@ -47,8 +47,8 @@ $ide-web-vertical-layout: (
 | 
				
			|||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$terminal-only-layout: (
 | 
					$terminal-only-layout: (
 | 
				
			||||||
  'header': (0, 20, 0, 4),
 | 
					  'header': (0, 20, 0, 8),
 | 
				
			||||||
  'messages': (0, 20, 4, 100),
 | 
					  'messages': (0, 20, 8, 100),
 | 
				
			||||||
  'ide': (),
 | 
					  'ide': (),
 | 
				
			||||||
  'terminal': (20, 96, 0, 100),
 | 
					  'terminal': (20, 96, 0, 100),
 | 
				
			||||||
  'web': (),
 | 
					  'web': (),
 | 
				
			||||||
@@ -56,8 +56,8 @@ $terminal-only-layout: (
 | 
				
			|||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$ide-only-layout: (
 | 
					$ide-only-layout: (
 | 
				
			||||||
  'header': (0, 20, 0, 4),
 | 
					  'header': (0, 20, 0, 8),
 | 
				
			||||||
  'messages': (0, 20, 4, 100),
 | 
					  'messages': (0, 20, 8, 100),
 | 
				
			||||||
  'ide': (20, 96, 0, 100),
 | 
					  'ide': (20, 96, 0, 100),
 | 
				
			||||||
  'terminal': (),
 | 
					  'terminal': (),
 | 
				
			||||||
  'web': (),
 | 
					  'web': (),
 | 
				
			||||||
@@ -65,8 +65,8 @@ $ide-only-layout: (
 | 
				
			|||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$web-only-layout: (
 | 
					$web-only-layout: (
 | 
				
			||||||
  'header': (0, 20, 0, 4),
 | 
					  'header': (0, 20, 0, 8),
 | 
				
			||||||
  'messages': (0, 20, 4, 100),
 | 
					  'messages': (0, 20, 8, 100),
 | 
				
			||||||
  'ide': (),
 | 
					  'ide': (),
 | 
				
			||||||
  'terminal': (),
 | 
					  'terminal': (),
 | 
				
			||||||
  'web': (20, 96, 0, 100),
 | 
					  'web': (20, 96, 0, 100),
 | 
				
			||||||
@@ -142,7 +142,7 @@ $layouts: (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @if (length($tfw-component) > 0) {
 | 
					  @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);
 | 
					    $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-width: #{$columns-count / $grid-columns-count * 100}vw;
 | 
				
			||||||
    min-height: #{$rows-count / $grid-rows-count * 100}vh;
 | 
					    min-height: #{$rows-count / $grid-rows-count * 100}vh;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
@mixin set-scrollbar-style($style, $selctor) {
 | 
					@mixin set-scrollbar-style($style, $selctor) {
 | 
				
			||||||
  ::-webkit-scrollbar {
 | 
					  ::-webkit-scrollbar {
 | 
				
			||||||
    width: 3px !important;
 | 
					    width: 3px !important;
 | 
				
			||||||
 | 
					    height: 3px !important;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  #{$selctor}::-webkit-scrollbar-track {
 | 
					  #{$selctor}::-webkit-scrollbar-track {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user