Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
263701c499 |
+1
-2
@@ -5,8 +5,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve --proxy-config proxy.conf.json",
|
"start": "ng serve --proxy-config proxy.conf.json",
|
||||||
"build": "ng build --prod --aot --build-optimizer",
|
"build": "ng build --prod --aot --build-optimizer"
|
||||||
"lint": "ng lint --fix"
|
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { TerminalComponent } from './terminal/terminal.component';
|
|||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { TestmessengerComponent } from './testmessenger/testmessenger.component';
|
import { TestmessengerComponent } from './testmessenger/testmessenger.component';
|
||||||
import { DeploymentNotificationService } from './services/deployment-notification.service';
|
import { DeploymentNotificationService } from './services/deployment-notification.service';
|
||||||
import { SafePipe, SafeHtmlPipe } 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 {
|
import {
|
||||||
@@ -28,7 +28,6 @@ import {
|
|||||||
} from './services/config.service';
|
} from './services/config.service';
|
||||||
import { FSMUpdateService } from './services/fsmupdate.service';
|
import { FSMUpdateService } from './services/fsmupdate.service';
|
||||||
import { LoaderComponent } from './loader/loader.component';
|
import { LoaderComponent } from './loader/loader.component';
|
||||||
import {CapitalizePipe} from './pipes/capitalize.pipe';
|
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -42,8 +41,6 @@ import {CapitalizePipe} from './pipes/capitalize.pipe';
|
|||||||
DashboardComponent,
|
DashboardComponent,
|
||||||
TestmessengerComponent,
|
TestmessengerComponent,
|
||||||
SafePipe,
|
SafePipe,
|
||||||
SafeHtmlPipe,
|
|
||||||
CapitalizePipe,
|
|
||||||
ConsoleComponent,
|
ConsoleComponent,
|
||||||
LoaderComponent
|
LoaderComponent
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<div [attr.class]="layout | async">
|
<div [attr.class]="layout | async">
|
||||||
<div class="tfw-grid-main-components">
|
<div class="tfw-grid-main-components">
|
||||||
<div [ngClass]="{'hide-attribute': hideMessages | async}" class="tfw-messages">
|
|
||||||
<app-messages></app-messages>
|
|
||||||
</div>
|
|
||||||
<div class="tfw-header"><app-header></app-header></div>
|
<div class="tfw-header"><app-header></app-header></div>
|
||||||
|
<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"
|
<div class="tfw-web tao-grid-top-left"
|
||||||
[ngClass]="{'deploy-blur': deploying || (iframeReloadPoller.isPolling | async)}">
|
[ngClass]="{'deploy-blur': deploying || (polling | async)}">
|
||||||
<div *ngIf="iframeUrl | async" class="iframe-container">
|
<div *ngIf="iframeUrl | async" class="iframe-container">
|
||||||
<div *ngIf="showUrlBar | async" 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>
|
||||||
@@ -18,7 +20,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<iframe class="iframe"
|
<iframe class="iframe"
|
||||||
#webiframe
|
#webiframe
|
||||||
name="webiframe"
|
|
||||||
scrolling="yes"
|
scrolling="yes"
|
||||||
frameborder="0"
|
frameborder="0"
|
||||||
(load)="iframeLoad()"
|
(load)="iframeLoad()"
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
.tfw-messages {
|
.tfw-messages {
|
||||||
// Check whether the layout contains a web component
|
// Check whether the layout contains a web component
|
||||||
div[class*="web"] & {
|
div[class*="web"] & {
|
||||||
border: 2px solid $tao-base-color;
|
border: 2px solid $tao-gray-100;
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
border-left: 0;
|
border-left: 0;
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
@@ -65,16 +65,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tfw-header {
|
.tfw-header {
|
||||||
padding: $tiny;
|
padding: $small;
|
||||||
padding-top: $hair;
|
padding-top: $tiny;
|
||||||
background-color: $tao-base-color;
|
background-color: $tao-gray-50;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include set-scrollbar-style('dark', '.tfw-messages');
|
@include set-scrollbar-style('dark', '.tfw-messages');
|
||||||
|
|
||||||
.tfw-messages {
|
.tfw-messages {
|
||||||
padding: $tiny;
|
padding: $space;
|
||||||
background-color: $tao-base-color;
|
padding-top: $hair;
|
||||||
|
background-color: $tao-gray-50;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
max-height: 95vmin;
|
max-height: 95vmin;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { Component, OnDestroy, OnInit, ChangeDetectorRef, ElementRef, ViewChild } from '@angular/core';
|
import { Component, OnDestroy, OnInit, ChangeDetectorRef, ElementRef, ViewChild } from '@angular/core';
|
||||||
import { DeploymentNotificationService } from '../services/deployment-notification.service';
|
import { DeploymentNotificationService } from '../services/deployment-notification.service';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription, BehaviorSubject } 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 { DashboardConfigService, ConfigReadyService } from '../services/config.service';
|
import { DashboardConfigService } from '../services/config.service';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { delay, retryWhen, tap } from 'rxjs/operators';
|
||||||
import { FSMUpdateService } from '../services/fsmupdate.service';
|
import { FSMUpdateService } from '../services/fsmupdate.service';
|
||||||
import { MessagesComponent } from '../messages/messages.component';
|
|
||||||
import { StatusCodePoller } from './statuscodepoller';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
@@ -16,9 +15,10 @@ import { StatusCodePoller } from './statuscodepoller';
|
|||||||
})
|
})
|
||||||
export class DashboardComponent implements OnInit, OnDestroy {
|
export class DashboardComponent implements OnInit, OnDestroy {
|
||||||
deploying = false;
|
deploying = false;
|
||||||
|
polling = new BehaviorSubject<boolean>(false);
|
||||||
deploymentNotificationSubscription: Subscription;
|
deploymentNotificationSubscription: Subscription;
|
||||||
@ViewChild('webiframe', {static: false}) webiframe: ElementRef;
|
@ViewChild('webiframe', {static: false}) webiframe: ElementRef;
|
||||||
@ViewChild(MessagesComponent, {static: false}) messages: MessagesComponent;
|
@ViewChild('tfwmessages', {static: false}) messages: ElementRef;
|
||||||
@ViewChild('urlbar', {static: false}) urlbar: ElementRef;
|
@ViewChild('urlbar', {static: false}) urlbar: ElementRef;
|
||||||
|
|
||||||
layout = this.configService.layout;
|
layout = this.configService.layout;
|
||||||
@@ -27,7 +27,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
iframeUrl = this.configService.iframeUrl;
|
iframeUrl = this.configService.iframeUrl;
|
||||||
actualIframeUrl = this.iframeUrl.value;
|
actualIframeUrl = this.iframeUrl.value;
|
||||||
terminalMenuItem = this.configService.terminalMenuItem;
|
terminalMenuItem = this.configService.terminalMenuItem;
|
||||||
iframeReloadPoller: StatusCodePoller;
|
iframeReloadSubscription: Subscription;
|
||||||
|
|
||||||
command_handlers = {
|
command_handlers = {
|
||||||
'dashboard.reloadFrontend': this.reloadFrontendHandlder.bind(this),
|
'dashboard.reloadFrontend': this.reloadFrontendHandlder.bind(this),
|
||||||
@@ -38,7 +38,6 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
private webSocketService: WebSocketService,
|
private webSocketService: WebSocketService,
|
||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
private http: HttpClient,
|
private http: HttpClient,
|
||||||
private configReadyService: ConfigReadyService,
|
|
||||||
private configService: DashboardConfigService,
|
private configService: DashboardConfigService,
|
||||||
private fsmUpdateService: FSMUpdateService) {}
|
private fsmUpdateService: FSMUpdateService) {}
|
||||||
|
|
||||||
@@ -46,7 +45,6 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
this.webSocketService.connect();
|
this.webSocketService.connect();
|
||||||
this.configService.init();
|
this.configService.init();
|
||||||
this.subscribeCheckSolution();
|
this.subscribeCheckSolution();
|
||||||
this.iframeReloadPoller = new StatusCodePoller(this.iframeUrl, this.http);
|
|
||||||
this.hideIframeUntilResponseOk();
|
this.hideIframeUntilResponseOk();
|
||||||
this.subscribeResizeOnLayoutChange();
|
this.subscribeResizeOnLayoutChange();
|
||||||
this.initCommandHandling();
|
this.initCommandHandling();
|
||||||
@@ -64,16 +62,14 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hideIframeUntilResponseOk() {
|
hideIframeUntilResponseOk() {
|
||||||
this.iframeReloadPoller.ok.subscribe(() => this.reloadIframe());
|
// TODO: hide iframe and show it after this whole deal...
|
||||||
this.configReadyService.configDone.subscribe(() =>
|
this.reloadIframeWhenResponseOk();
|
||||||
this.iframeReloadPoller.start()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeResizeOnLayoutChange() {
|
subscribeResizeOnLayoutChange() {
|
||||||
this.configService.layout.subscribe(() => {
|
this.configService.layout.subscribe(() => {
|
||||||
this.emitResizeEvent();
|
this.emitResizeEvent();
|
||||||
setTimeout(() => this.messages.scrollToBottom(), 0);
|
setTimeout(() => this.scrollMessagesToBottom(), 0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +85,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
(deploying) => {
|
(deploying) => {
|
||||||
this.deploying = deploying;
|
this.deploying = deploying;
|
||||||
if (!deploying && this.configService.reloadIframeOnDeploy.value) {
|
if (!deploying && this.configService.reloadIframeOnDeploy.value) {
|
||||||
this.iframeReloadPoller.start();
|
this.reloadIframeWhenResponseOk();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -103,8 +99,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reloadIframeHandler(message: WebSocketMessage) {
|
reloadIframeHandler(message: WebSocketMessage) {
|
||||||
this.iframeReloadPoller.ok.subscribe(() => this.reloadIframe());
|
setTimeout(() => this.reloadIframeNoSubmit(), 200);
|
||||||
this.iframeReloadPoller.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLayout(layout: string) {
|
setLayout(layout: string) {
|
||||||
@@ -119,7 +114,14 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
reloadIframe() {
|
reloadIframe() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.webiframe.nativeElement.contentWindow.frames.location.href = this.iframeUrl.value;
|
this.webiframe.nativeElement.contentWindow.location.reload(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadIframeNoSubmit() {
|
||||||
|
// Sometimes it is needed to reload the iframe without resending the previous form data
|
||||||
|
setTimeout(() => {
|
||||||
|
this.webiframe.nativeElement.contentWindow.location = this.webiframe.nativeElement.contentWindow.location.href;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,16 +132,22 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
this.terminalMenuItem.next(item);
|
this.terminalMenuItem.next(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scrollMessagesToBottom() {
|
||||||
|
const element = this.messages.nativeElement;
|
||||||
|
// This must be done in the Angular event loop to avoid messing up
|
||||||
|
// change detection (not in the template like ConsoleComponent does)
|
||||||
|
element.scrollTop = element.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
iframeLoad() {
|
iframeLoad() {
|
||||||
if (this.webiframe && this.iframeUrl.value) {
|
if (this.webiframe && this.iframeUrl.value) {
|
||||||
const href = this.webiframe.nativeElement.contentWindow.frames.location.href;
|
// This hack deals with Firefox iframes having a valid src,
|
||||||
const match = href.match(/.*?\/\/.*?(\/.*)/);
|
// but 'about:blank' contentWindow on (load) events
|
||||||
if (match !== null) {
|
setTimeout(() => {
|
||||||
// iframes on Firefox can have an about:blank
|
const href = this.webiframe.nativeElement.contentWindow.frames.location.href;
|
||||||
// contentWindow after firing a (load) event
|
const niceURL = href.match(/.*?\/\/.*?(\/.*)/)[1];
|
||||||
const niceURL = match[1];
|
|
||||||
this.actualIframeUrl = niceURL;
|
this.actualIframeUrl = niceURL;
|
||||||
}
|
}, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,13 +160,35 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.iframeUrl.next(this.urlbar.nativeElement.value);
|
this.webiframe.nativeElement.contentWindow.frames.location.href = this.urlbar.nativeElement.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadIframeWhenResponseOk() {
|
||||||
|
if (this.polling.value) {
|
||||||
|
this.iframeReloadSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
this.polling.next(true);
|
||||||
|
this.iframeReloadSubscription = this.http.get(this.actualIframeUrl, {observe: 'response'}).pipe(
|
||||||
|
retryWhen(errors =>
|
||||||
|
errors.pipe(
|
||||||
|
tap(
|
||||||
|
response => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
this.iframeReloadSubscription.unsubscribe();
|
||||||
|
this.polling.next(false);
|
||||||
|
this.reloadIframe();
|
||||||
|
}}),
|
||||||
|
delay(1000)
|
||||||
|
))).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
if (this.deploymentNotificationSubscription) {
|
if (this.deploymentNotificationSubscription) {
|
||||||
this.deploymentNotificationSubscription.unsubscribe();
|
this.deploymentNotificationSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
this.iframeReloadPoller.stop();
|
|
||||||
|
if (this.iframeReloadSubscription) {
|
||||||
|
this.iframeReloadSubscription.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { Subject, Subscription, BehaviorSubject } from 'rxjs';
|
|
||||||
import { retryWhen, delay, tap, skip } from 'rxjs/operators';
|
|
||||||
|
|
||||||
export class StatusCodePoller {
|
|
||||||
http: HttpClient;
|
|
||||||
url: BehaviorSubject<string>;
|
|
||||||
pollFreq: number;
|
|
||||||
okCode: number;
|
|
||||||
stopOnOk: boolean;
|
|
||||||
|
|
||||||
pollSubscription: Subscription;
|
|
||||||
urlSubscription: Subscription;
|
|
||||||
poll = new Subject<any>();
|
|
||||||
ok = new Subject<any>();
|
|
||||||
isPolling = new BehaviorSubject<boolean>(false);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
url: BehaviorSubject<string>, http: HttpClient,
|
|
||||||
pollFreq = 1000, okCode = 200,
|
|
||||||
stopOnOk = true
|
|
||||||
) {
|
|
||||||
this.url = url;
|
|
||||||
this.http = http;
|
|
||||||
this.pollFreq = pollFreq;
|
|
||||||
this.okCode = okCode;
|
|
||||||
this.stopOnOk = stopOnOk;
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
this.stop();
|
|
||||||
if (this.url.value === '') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.urlSubscription = this.url.pipe(skip(1)).subscribe(() => this.stop());
|
|
||||||
this.isPolling.next(true);
|
|
||||||
this.pollSubscription = this.http.get(this.url.value, {observe: 'response'}).pipe(
|
|
||||||
retryWhen(errors =>
|
|
||||||
errors.pipe(
|
|
||||||
tap(
|
|
||||||
response => {
|
|
||||||
this.poll.next(response);
|
|
||||||
if (response.status === this.okCode) {
|
|
||||||
if (this.stopOnOk) {
|
|
||||||
this.stop();
|
|
||||||
}
|
|
||||||
this.ok.next(response);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
delay(this.pollFreq)
|
|
||||||
))).subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
this.isPolling.next(false);
|
|
||||||
if (this.pollSubscription) {
|
|
||||||
this.pollSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
if (this.urlSubscription) {
|
|
||||||
this.urlSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
<div>
|
<div class="tfw-grid-navbar tao-grid-center-center">
|
||||||
<img src="images/avatao-tutorial-framework-logo.svg" routerLink="/" class="tao-grid-center-left tfw-company-logo" alt="">
|
<img src="images/avatao_logo.svg" routerLink="/" class="tao-grid-center-left tfw-company-logo" alt="">
|
||||||
|
<span class="tfw-header-title">Tutorials</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,19 @@
|
|||||||
@import "../../assets/scss/variables.scss";
|
@import "../../assets/scss/variables.scss";
|
||||||
|
|
||||||
|
.tfw-header-title {
|
||||||
|
color: $tao-blue-500;
|
||||||
|
font-size: $font-size-h3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tfw-grid-navbar {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: $company-logo-width 1fr;
|
||||||
|
grid-column-gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.tfw-company-logo {
|
.tfw-company-logo {
|
||||||
display: block;
|
display: block;
|
||||||
padding: $tiny;
|
width: $company-logo-width;
|
||||||
width: 80%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,8 +75,7 @@
|
|||||||
|
|
||||||
.loader {
|
.loader {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 2px solid $tao-warm-yellow-600;
|
border: 2px solid;
|
||||||
border-top: 2px solid $tao-warm-yellow-200;
|
|
||||||
width: 15px;
|
width: 15px;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
animation: spin 2s linear infinite;
|
animation: spin 2s linear infinite;
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ export class IdeComponent implements OnInit {
|
|||||||
files: string[];
|
files: string[];
|
||||||
filename = '';
|
filename = '';
|
||||||
lastFilename = '';
|
lastFilename = '';
|
||||||
reloadFile = false;
|
|
||||||
code = 'Loading your file...';
|
code = 'Loading your file...';
|
||||||
|
|
||||||
codeState = CodeState.SAVED;
|
codeState = CodeState.SAVED;
|
||||||
@@ -86,18 +85,10 @@ export class IdeComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reloadHandler(message: WebSocketMessage) {
|
reloadHandler(message: WebSocketMessage) {
|
||||||
if (!this.reloadFile) {
|
this.requestCode();
|
||||||
this.reloadFile = true;
|
|
||||||
this.requestCode();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readHandler(message: IDEMessage) {
|
readHandler(message: IDEMessage) {
|
||||||
if (!this.reloadFile && message.reload) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.reloadFile = false;
|
|
||||||
|
|
||||||
if (this.codeState === CodeState.SAVED) {
|
if (this.codeState === CodeState.SAVED) {
|
||||||
if (this.filename !== message.filename) {
|
if (this.filename !== message.filename) {
|
||||||
this.filename = message.filename;
|
this.filename = message.filename;
|
||||||
@@ -198,8 +189,7 @@ export class IdeComponent implements OnInit {
|
|||||||
requestCode() {
|
requestCode() {
|
||||||
this.webSocketService.send({
|
this.webSocketService.send({
|
||||||
'key': 'ide.read',
|
'key': 'ide.read',
|
||||||
'filename': this.filename,
|
'filename': this.filename
|
||||||
'reload': this.reloadFile
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,5 @@ export const LanguageMap: { [extension: string]: string; } = {
|
|||||||
css: 'css',
|
css: 'css',
|
||||||
less: 'less',
|
less: 'less',
|
||||||
scss: 'scss',
|
scss: 'scss',
|
||||||
sh: 'shell',
|
sh: 'shell'
|
||||||
kt: 'kotlin',
|
|
||||||
kts: 'kotlin'
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { WebSocketMessage } from './websocket-message';
|
import { WebSocketMessage } from './websocket-message';
|
||||||
|
|
||||||
export type MessageButton = 'yes' | 'no' | 'fix' | 'hint' | 'solution' | 'next';
|
export interface MessageConfig extends WebSocketMessage {
|
||||||
export type MessageButtonMap = Record<MessageButton, {caption: string}>;
|
originator?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface MessageData {
|
export interface MessageData {
|
||||||
originator?: string;
|
originator?: string;
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
typing?: boolean;
|
typing?: boolean;
|
||||||
buttons?: MessageButton[];
|
|
||||||
command?: any;
|
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,5 +4,4 @@ export interface IDEMessage extends WebSocketMessage {
|
|||||||
filename: string;
|
filename: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
files?: string[];
|
files?: string[];
|
||||||
reload?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,17 @@
|
|||||||
<div class="tfw-messages-main">
|
<div class="tfw-messages-main">
|
||||||
<div class="tfw-grid-message"
|
<div class="tfw-grid-message"
|
||||||
*ngFor="let message of messages.slice(); let last = last"
|
*ngFor="let message of messages.slice(); let last = last"
|
||||||
[class.highlighted-message]="last"
|
[class.highlighted-message]="last">
|
||||||
(click)="sendMessageCommand(message)">
|
|
||||||
<div class="tfw-grid-message-header">
|
<div class="tfw-grid-message-header">
|
||||||
|
<img class="tao-grid-center-left" src="images/avataobot.svg"/>
|
||||||
<div class="tao-grid-center-left originator">{{message.originator}}</div>
|
<div class="tao-grid-center-left originator">{{message.originator}}</div>
|
||||||
<div class="timestamp tao-grid-center-right">{{message.timestamp | date:'HH:mm:ss'}}</div>
|
<div class="timestamp tao-grid-center-right">{{message.timestamp | date:'HH:mm:ss'}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tfw-grid-message-body" [innerHtml]="message.message | safeHtml"></div>
|
<div class="tfw-grid-message-body" [innerHtml]="message.message"></div>
|
||||||
<div *ngIf="message.buttons !== undefined">
|
</div>
|
||||||
|
<div *ngIf="showTypingIndicator" class="tfw-grid-message jumping-circle-container">
|
||||||
<div class="tfw-message-btn-divider"></div>
|
<div class="jumping-circle" id="jc1"></div>
|
||||||
<div class="tfw-message-btn">
|
<div class="jumping-circle" id="jc2"></div>
|
||||||
<button *ngFor="let messageButton of message.buttons"
|
<div class="jumping-circle" id="jc3"></div>
|
||||||
class="{{messageButton}}"
|
|
||||||
(click)="sendButtonCommand(messageButton)">
|
|
||||||
{{buttonMap[messageButton].caption}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div *ngIf="showTypingIndicator"
|
|
||||||
class="tfw-grid-message jumping-circle-container"
|
|
||||||
(click)="drainMessageQueue()">
|
|
||||||
<div class="jumping-circle" id="jc1"></div>
|
|
||||||
<div class="jumping-circle" id="jc2"></div>
|
|
||||||
<div class="jumping-circle" id="jc3"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,67 +1,25 @@
|
|||||||
@import "../../assets/scss/variables.scss";
|
@import "../../assets/scss/variables.scss";
|
||||||
@import "../../assets/scss/mixins/layout";
|
@import "../../assets/scss/mixins/layout";
|
||||||
@import "../../assets/scss/mixins/button";
|
|
||||||
|
.tfw-next-button {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.tfw-grid-message {
|
.tfw-grid-message {
|
||||||
font-family: "Roboto";
|
|
||||||
font-style: normal;
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 1fr auto;
|
grid-template-rows: 1fr auto;
|
||||||
grid-row-gap: $nano;
|
grid-row-gap: $hair;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: $hair;
|
|
||||||
|
background-color: $tao-gray-100;
|
||||||
border-radius: $tao-panel-border-radius-sm;
|
border-radius: $tao-panel-border-radius-sm;
|
||||||
|
padding: $tiny;
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-base;
|
||||||
margin-bottom: $hair;
|
margin-bottom: $hair;
|
||||||
|
|
||||||
animation-name: inflate;
|
animation-name: inflate;
|
||||||
animation-duration: 0.5s;
|
animation-duration: 0.5s;
|
||||||
animation-timing-function: cubic-bezier(0.01, 0.1, 0, 1);
|
animation-timing-function: cubic-bezier(0.01, 0.1, 0, 1);
|
||||||
color: white;
|
|
||||||
background-color: $tao-card-color;
|
|
||||||
|
|
||||||
.tfw-message-btn-divider {
|
|
||||||
opacity: 0.25;
|
|
||||||
border: 1px solid $tao-gray-color;
|
|
||||||
}
|
|
||||||
.tfw-message-btn {
|
|
||||||
color: $tao-btn-font-color;
|
|
||||||
padding-top: $tiny;
|
|
||||||
padding-bottom: $hair;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.no {
|
|
||||||
@include set-button-style($tao-red-color);
|
|
||||||
width: $xlarge;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yes {
|
|
||||||
@include set-button-style($tao-green-color);
|
|
||||||
width: $xlarge;
|
|
||||||
}
|
|
||||||
|
|
||||||
.solution {
|
|
||||||
@include set-button-style($tao-blue-color);
|
|
||||||
width: $xlarge + $medium + $tiny;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hint {
|
|
||||||
@include set-button-style($tao-yellow-color);
|
|
||||||
width: $xlarge + $medium + $tiny;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fix {
|
|
||||||
@include set-button-style($tao-yellow-color);
|
|
||||||
width: $xlarge + $medium + $tiny;
|
|
||||||
}
|
|
||||||
|
|
||||||
.next {
|
|
||||||
@include set-button-style($tao-blue-color);
|
|
||||||
width: $xlarge;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlighted-message {
|
.highlighted-message {
|
||||||
@@ -86,7 +44,7 @@
|
|||||||
background-color: gray;
|
background-color: gray;
|
||||||
margin-top: 0.3em;
|
margin-top: 0.3em;
|
||||||
margin-left: 0.3em;
|
margin-left: 0.3em;
|
||||||
|
|
||||||
animation-name: float;
|
animation-name: float;
|
||||||
animation-duration: 1.7s;
|
animation-duration: 1.7s;
|
||||||
animation-timing-function: ease-in-out;
|
animation-timing-function: ease-in-out;
|
||||||
@@ -107,33 +65,24 @@
|
|||||||
|
|
||||||
.tfw-grid-message-header {
|
.tfw-grid-message-header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 5fr 8fr;
|
grid-template-columns: 1fr 5fr 8fr;
|
||||||
grid-column-gap: 4px;
|
grid-column-gap: 4px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.originator {
|
img {
|
||||||
font-style: normal;
|
width: 12px;
|
||||||
font-weight: bold;
|
}
|
||||||
font-size: $font-size-base;
|
|
||||||
line-height: 26px;
|
|
||||||
|
|
||||||
display: flex;
|
.originator {
|
||||||
align-items: center;
|
font-weight: 500;
|
||||||
color: $tao-message-originator-color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.timestamp {
|
.timestamp {
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-tiny;
|
||||||
color: $tao-gray-color;
|
|
||||||
opacity: 0.37;
|
opacity: 0.37;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tfw-grid-message-body {
|
.tfw-grid-message-body {
|
||||||
@include word-break();
|
@include word-break()
|
||||||
line-height: 26px;
|
|
||||||
|
|
||||||
& p {
|
|
||||||
margin-bottom: $tiny !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ChangeDetectorRef, Component, OnInit, EventEmitter, Output, ElementRef } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnInit, EventEmitter, Output } from '@angular/core';
|
||||||
import {MessageData, Message, MessageButton, MessageButtonMap} 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 { Subject } from 'rxjs';
|
||||||
@@ -13,21 +13,13 @@ export class MessagesComponent implements OnInit {
|
|||||||
@Output() newMessageEvent: EventEmitter<any> = new EventEmitter();
|
@Output() newMessageEvent: EventEmitter<any> = new EventEmitter();
|
||||||
newMessage: Subject<MessageData> = new Subject<MessageData>();
|
newMessage: Subject<MessageData> = new Subject<MessageData>();
|
||||||
showTypingIndicator = false;
|
showTypingIndicator = false;
|
||||||
|
|
||||||
messages: MessageData[] = [];
|
messages: MessageData[] = [];
|
||||||
buttonMap: MessageButtonMap = {
|
|
||||||
fix: {caption: 'Ready to fix'},
|
|
||||||
solution: {caption: 'Show solution'},
|
|
||||||
hint: {caption: 'Hint'},
|
|
||||||
next: {caption: 'Next'},
|
|
||||||
yes: {caption: 'Yes'},
|
|
||||||
no: {caption: 'No'},
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private markdownService: MarkdownService,
|
private markdownService: MarkdownService,
|
||||||
private websocketService: WebSocketService,
|
private websocketService: WebSocketService,
|
||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef
|
||||||
private ref: ElementRef
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -35,7 +27,6 @@ export class MessagesComponent implements OnInit {
|
|||||||
message => {
|
message => {
|
||||||
this.writeMessage(message);
|
this.writeMessage(message);
|
||||||
this.newMessageEvent.emit();
|
this.newMessageEvent.emit();
|
||||||
this.scrollToBottom();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.websocketService.connect();
|
this.websocketService.connect();
|
||||||
@@ -50,11 +41,6 @@ export class MessagesComponent implements OnInit {
|
|||||||
this.changeDetectorRef.detectChanges();
|
this.changeDetectorRef.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToBottom() {
|
|
||||||
const element = this.ref.nativeElement.parentElement;
|
|
||||||
element.scrollTop = element.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
transformMessage(message: MessageData) {
|
transformMessage(message: MessageData) {
|
||||||
message.message = this.convertMarkdownToHTML(message.message);
|
message.message = this.convertMarkdownToHTML(message.message);
|
||||||
if (!message.timestamp) {
|
if (!message.timestamp) {
|
||||||
@@ -66,23 +52,4 @@ export class MessagesComponent implements OnInit {
|
|||||||
convertMarkdownToHTML(text: string) {
|
convertMarkdownToHTML(text: string) {
|
||||||
return this.markdownService.convertToHtml(text);
|
return this.markdownService.convertToHtml(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
drainMessageQueue() {
|
|
||||||
this.websocketService.send({
|
|
||||||
'key': 'message.queue.drain'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
sendMessageCommand(message: MessageData) {
|
|
||||||
if ('command' in message) {
|
|
||||||
this.websocketService.send(message.command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendButtonCommand(messageButton: MessageButton) {
|
|
||||||
this.websocketService.send({
|
|
||||||
'key': 'message.button.click',
|
|
||||||
'value': messageButton
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core';
|
|
||||||
|
|
||||||
@Pipe({
|
|
||||||
name: 'capitalize',
|
|
||||||
})
|
|
||||||
export class CapitalizePipe implements PipeTransform {
|
|
||||||
transform(value: string): string {
|
|
||||||
if (!value) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return value[0].toLocaleUpperCase() + value.toLocaleLowerCase().slice(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,14 +11,3 @@ export class SafePipe implements PipeTransform {
|
|||||||
return this.sanitizer.bypassSecurityTrustResourceUrl(value);
|
return this.sanitizer.bypassSecurityTrustResourceUrl(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Pipe({
|
|
||||||
name: 'safeHtml'
|
|
||||||
})
|
|
||||||
export class SafeHtmlPipe implements PipeTransform {
|
|
||||||
constructor(private sanitized: DomSanitizer) {}
|
|
||||||
|
|
||||||
transform(value) {
|
|
||||||
return this.sanitized.bypassSecurityTrustHtml(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class DashboardConfigService extends ConfigServiceBase {
|
|||||||
|
|
||||||
layout = new BehaviorSubject<string>('terminal-ide-web');
|
layout = new BehaviorSubject<string>('terminal-ide-web');
|
||||||
hideMessages = new BehaviorSubject<boolean>(false);
|
hideMessages = new BehaviorSubject<boolean>(false);
|
||||||
iframeUrl = new BehaviorSubject<string>('');
|
iframeUrl = new BehaviorSubject<string>('/webservice');
|
||||||
showUrlBar = new BehaviorSubject<boolean>(false);
|
showUrlBar = new BehaviorSubject<boolean>(false);
|
||||||
terminalMenuItem = new BehaviorSubject<string>('terminal');
|
terminalMenuItem = new BehaviorSubject<string>('terminal');
|
||||||
reloadIframeOnDeploy = new BehaviorSubject<boolean>(false);
|
reloadIframeOnDeploy = new BehaviorSubject<boolean>(false);
|
||||||
|
|||||||
@@ -8,16 +8,11 @@ export class TerminadoService {
|
|||||||
xterm: Terminal;
|
xterm: Terminal;
|
||||||
ws: WebSocket;
|
ws: WebSocket;
|
||||||
attached = false;
|
attached = false;
|
||||||
private dataListener: any;
|
|
||||||
private resizeListener: any;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
Terminal.applyAddon(fit);
|
Terminal.applyAddon(fit);
|
||||||
this.xterm = this.createTerminal();
|
Terminal.applyAddon(terminado);
|
||||||
}
|
this.xterm = new Terminal({
|
||||||
|
|
||||||
createTerminal() {
|
|
||||||
return new Terminal({
|
|
||||||
theme: {
|
theme: {
|
||||||
foreground: '#ffffff',
|
foreground: '#ffffff',
|
||||||
background: '#0C0C0C', // $tao-gray-800
|
background: '#0C0C0C', // $tao-gray-800
|
||||||
@@ -42,52 +37,26 @@ export class TerminadoService {
|
|||||||
},
|
},
|
||||||
fontSize: 14
|
fontSize: 14
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
attach(element: HTMLElement) {
|
|
||||||
if (this.attached) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wsproto = (location.protocol === 'https:') ? 'wss://' : 'ws://';
|
const wsproto = (location.protocol === 'https:') ? 'wss://' : 'ws://';
|
||||||
this.ws = new WebSocket(wsproto + window.location.host + '/terminal');
|
this.ws = new WebSocket(wsproto + window.location.host + '/terminal');
|
||||||
|
}
|
||||||
|
|
||||||
|
attach(element: HTMLElement) {
|
||||||
this.ws.onopen = () => {
|
this.ws.onopen = () => {
|
||||||
this.attached = true;
|
(<any>this.xterm).terminadoAttach(this.ws);
|
||||||
this.xterm = this.createTerminal();
|
|
||||||
this.xterm.open(element);
|
this.xterm.open(element);
|
||||||
this.fit();
|
this.fit();
|
||||||
// In order to reset the terminal state after a broken socket, we need to register the listeners manually.
|
this.xterm.blur();
|
||||||
(<any>this.xterm)._core.register(this.dataListener = this.xterm.onData(data => {
|
this.attached = true;
|
||||||
this.ws.send(JSON.stringify(['stdin', data]));
|
|
||||||
}));
|
|
||||||
(<any>this.xterm)._core.register(this.resizeListener = this.xterm.onResize((size: { rows: number, cols: number }) => {
|
|
||||||
this.ws.send(JSON.stringify(['set_size', size.rows, size.cols]));
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onclose = () => {
|
|
||||||
this.detach();
|
|
||||||
this.xterm.destroy();
|
|
||||||
this.attach(element);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onmessage = msg => {
|
|
||||||
const data = JSON.parse(msg.data);
|
|
||||||
if (data[0] === 'stdout') {
|
|
||||||
this.xterm.write(data[1]);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
detach() {
|
detach() {
|
||||||
if (!this.attached) {
|
(<any>this.xterm).terminadoDetach(this.ws);
|
||||||
return;
|
this.xterm.destroy();
|
||||||
}
|
this.ws.close();
|
||||||
|
|
||||||
this.attached = false;
|
this.attached = false;
|
||||||
this.dataListener.dispose();
|
|
||||||
this.resizeListener.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fit() {
|
fit() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable, Subject, Subscription } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
|
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
|
||||||
import { filter, map } from 'rxjs/operators';
|
import { filter, map } from 'rxjs/operators';
|
||||||
import { WebSocketMessage } from '../message-types/websocket-message';
|
import { WebSocketMessage } from '../message-types/websocket-message';
|
||||||
@@ -17,28 +17,14 @@ export enum Intent {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class WebSocketService {
|
export class WebSocketService {
|
||||||
private ws: WebSocketSubject<WebSocketMessage>;
|
private ws: WebSocketSubject<WebSocketMessage>;
|
||||||
private subject: Subject<WebSocketMessage> = new Subject<WebSocketMessage>();
|
|
||||||
private subscription: Subscription;
|
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
public connect() {
|
public connect() {
|
||||||
if (!this.ws) {
|
if (!this.ws) {
|
||||||
if (this.subscription) {
|
|
||||||
this.subscription.unsubscribe();
|
|
||||||
}
|
|
||||||
const wsproto = (location.protocol === 'https:') ? 'wss://' : 'ws://';
|
const wsproto = (location.protocol === 'https:') ? 'wss://' : 'ws://';
|
||||||
const connAddr = wsproto + window.location.host + '/ws';
|
const connAddr = wsproto + window.location.host + '/ws';
|
||||||
this.ws = webSocket<WebSocketMessage>({
|
this.ws = webSocket<WebSocketMessage>(connAddr);
|
||||||
url: connAddr,
|
|
||||||
closeObserver: {
|
|
||||||
next: closeEvent => {
|
|
||||||
this.ws = null;
|
|
||||||
this.connect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.subscription = this.ws.subscribe(msg => this.subject.next(msg));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +35,7 @@ export class WebSocketService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public observeAll<T extends WebSocketMessage>(key: string): Observable<T> {
|
public observeAll<T extends WebSocketMessage>(key: string): Observable<T> {
|
||||||
return this.subject.pipe(
|
return this.ws.pipe(
|
||||||
filter(message => message.key.startsWith(key)),
|
filter(message => message.key.startsWith(key)),
|
||||||
map(message => <T> message)
|
map(message => <T> message)
|
||||||
);
|
);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 17 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.0 KiB |
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 11.98 16.98"><defs><style>.cls-1{fill:none;stroke:#277eec;stroke-linecap:round;stroke-linejoin:round;stroke-width:2px;}</style></defs><title>avataobot</title><rect class="cls-1" x="2.46" y="7.47" width="7.05" height="7.05" transform="translate(9.53 -1.01) rotate(45)"/><polyline class="cls-1" points="10.98 1 5.99 5.99 1 1"/></svg>
|
||||||
|
After Width: | Height: | Size: 417 B |
@@ -3,7 +3,6 @@
|
|||||||
'grid';
|
'grid';
|
||||||
|
|
||||||
@import
|
@import
|
||||||
'mixins/button',
|
|
||||||
'mixins/scrollbar';
|
'mixins/scrollbar';
|
||||||
|
|
||||||
@import
|
@import
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Spaces
|
// Spaces
|
||||||
//
|
//
|
||||||
|
|
||||||
$space: 32px;
|
$space: 24px;
|
||||||
|
|
||||||
$sxlarge: 9 * $space;
|
$sxlarge: 9 * $space;
|
||||||
$xxxlarge: 6 * $space;
|
$xxxlarge: 6 * $space;
|
||||||
@@ -30,6 +30,30 @@ $tao-blue-700: #195684;
|
|||||||
$tao-blue-800: #103B5B;
|
$tao-blue-800: #103B5B;
|
||||||
$tao-blue-900: #081A2B;
|
$tao-blue-900: #081A2B;
|
||||||
|
|
||||||
|
// Tao sky palette
|
||||||
|
$tao-sky-50: #F1FAFD;
|
||||||
|
$tao-sky-100: #C5E9F5;
|
||||||
|
$tao-sky-200: #9AD8EE;
|
||||||
|
$tao-sky-300: #6EC7E6;
|
||||||
|
$tao-sky-400: #42B7DF;
|
||||||
|
$tao-sky-500: #19A7D8;
|
||||||
|
$tao-sky-600: #0E8BA8;
|
||||||
|
$tao-sky-700: #04647A;
|
||||||
|
$tao-sky-800: #004251;
|
||||||
|
$tao-sky-900: #002028;
|
||||||
|
|
||||||
|
// Tao phtalo palette
|
||||||
|
$tao-phtalo-50: #F2FBFC;
|
||||||
|
$tao-phtalo-100: #C8EDF1;
|
||||||
|
$tao-phtalo-200: #9FDFE6;
|
||||||
|
$tao-phtalo-300: #75D1DB;
|
||||||
|
$tao-phtalo-400: #4CC3D0;
|
||||||
|
$tao-phtalo-500: #24B6C6;
|
||||||
|
$tao-phtalo-600: #16989E;
|
||||||
|
$tao-phtalo-700: #0C7575;
|
||||||
|
$tao-phtalo-800: #034C4F;
|
||||||
|
$tao-phtalo-900: #002426;
|
||||||
|
|
||||||
// Tao turqoise palette
|
// Tao turqoise palette
|
||||||
$tao-turqoise-50: #F1FBFB;
|
$tao-turqoise-50: #F1FBFB;
|
||||||
$tao-turqoise-100: #C3EFEF;
|
$tao-turqoise-100: #C3EFEF;
|
||||||
@@ -66,6 +90,29 @@ $tao-warm-yellow-700: #CC8B36;
|
|||||||
$tao-warm-yellow-800: #B2762F;
|
$tao-warm-yellow-800: #B2762F;
|
||||||
$tao-warm-yellow-900: #996526;
|
$tao-warm-yellow-900: #996526;
|
||||||
|
|
||||||
|
// Tao plum palette
|
||||||
|
$tao-plum-50: #F6F8FD;
|
||||||
|
$tao-plum-100: #DADFF8;
|
||||||
|
$tao-plum-200: #BEC7F3;
|
||||||
|
$tao-plum-300: #A2AFED;
|
||||||
|
$tao-plum-400: #8797E8;
|
||||||
|
$tao-plum-500: #6C80E3;
|
||||||
|
$tao-plum-600: #5E77BF;
|
||||||
|
$tao-plum-700: #4B5E99;
|
||||||
|
$tao-plum-800: #384672;
|
||||||
|
$tao-plum-900: #272F4C;
|
||||||
|
|
||||||
|
// Tao pink palette
|
||||||
|
$tao-pink-50: #FFF0F7;
|
||||||
|
$tao-pink-100: #FFBFDF;
|
||||||
|
$tao-pink-200: #FF8FC6;
|
||||||
|
$tao-pink-300: #FF5EAE;
|
||||||
|
$tao-pink-400: #FF2E95;
|
||||||
|
$tao-pink-500: #FF007E;
|
||||||
|
$tao-pink-600: #D8007C;
|
||||||
|
$tao-pink-700: #B20066;
|
||||||
|
$tao-pink-800: #8C0050;
|
||||||
|
$tao-pink-900: #66003A;
|
||||||
|
|
||||||
// Tao red palette
|
// Tao red palette
|
||||||
$tao-red-50: #FFF5F5;
|
$tao-red-50: #FFF5F5;
|
||||||
@@ -91,19 +138,6 @@ $tao-gray-700: #232323;
|
|||||||
$tao-gray-800: #0C0C0C;
|
$tao-gray-800: #0C0C0C;
|
||||||
$tao-gray-900: #000000;
|
$tao-gray-900: #000000;
|
||||||
|
|
||||||
// Tao messages
|
|
||||||
$tao-base-color: #2A324C;
|
|
||||||
$tao-btn-font-color: #1F2836;
|
|
||||||
$tao-card-color: #363E56;
|
|
||||||
$tao-message-originator-color: #6697FF;
|
|
||||||
$tao-copy-color: #FFFFFF;
|
|
||||||
$tao-timestamp-color: #94A6B9;
|
|
||||||
$tao-gray-color: #94A6B9; // @25% opacity
|
|
||||||
$tao-yellow-color: #FFD68F;
|
|
||||||
$tao-blue-color: #6697FF;
|
|
||||||
$tao-green-color: #48BEB0;
|
|
||||||
$tao-red-color: #FF4583;
|
|
||||||
|
|
||||||
|
|
||||||
// Panel border radius
|
// Panel border radius
|
||||||
$tao-panel-border-radius: 14px;
|
$tao-panel-border-radius: 14px;
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
@import "../variables.scss";
|
|
||||||
|
|
||||||
@mixin set-button-style($color) {
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: $color;
|
|
||||||
border: 1.5px solid $color;
|
|
||||||
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 26px;
|
|
||||||
height: $space;
|
|
||||||
margin-right: $tiny;
|
|
||||||
color: $tao-btn-font-color;
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: $tao-btn-font-color;
|
|
||||||
border: $tao-btn-font-color;
|
|
||||||
color: $color;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -2,8 +2,8 @@ $grid-columns-count: 100;
|
|||||||
$grid-rows-count: 30;
|
$grid-rows-count: 30;
|
||||||
|
|
||||||
$terminal-ide-web-layout: (
|
$terminal-ide-web-layout: (
|
||||||
'messages': (0, 20, 0, 52),
|
'header': (0, 20, 0, 8),
|
||||||
'header': (0, 20, 52, 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: (
|
||||||
'messages': (0, 20, 0, 92),
|
'header': (0, 20, 0, 8),
|
||||||
'header': (0, 20, 92, 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: (
|
||||||
'messages': (0, 20, 0, 92),
|
'header': (0, 20, 0, 8),
|
||||||
'header': (0, 20, 92, 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: (
|
||||||
'messages': (0, 20, 0, 92),
|
'header': (0, 20, 0, 8),
|
||||||
'header': (0, 20, 92, 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: (
|
||||||
'messages': (0, 20, 0, 92),
|
'header': (0, 20, 0, 8),
|
||||||
'header': (0, 20, 92, 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: (
|
||||||
'messages': (0, 20, 0, 92),
|
'header': (0, 20, 0, 8),
|
||||||
'header': (0, 20, 92, 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: (
|
||||||
'messages': (0, 20, 0, 92),
|
'header': (0, 20, 0, 8),
|
||||||
'header': (0, 20, 92, 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: (
|
||||||
'messages': (0, 20, 0, 92),
|
'header': (0, 20, 0, 8),
|
||||||
'header': (0, 20, 92, 100),
|
'messages': (0, 20, 8, 100),
|
||||||
'ide': (),
|
'ide': (),
|
||||||
'terminal': (),
|
'terminal': (),
|
||||||
'web': (20, 96, 0, 100),
|
'web': (20, 96, 0, 100),
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
@mixin set-scrollbar-style($style, $selector) {
|
@mixin set-scrollbar-style($style, $selctor) {
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 3px !important;
|
width: 3px !important;
|
||||||
height: 3px !important;
|
height: 3px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#{$selector}::-webkit-scrollbar-track {
|
#{$selctor}::-webkit-scrollbar-track {
|
||||||
border-radius: 10px !important;
|
border-radius: 10px !important;
|
||||||
width: 3px !important;
|
width: 3px !important;
|
||||||
@if ($style == 'light') {
|
@if ($style == 'light') {
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#{$selector}::-webkit-scrollbar-thumb {
|
#{$selctor}::-webkit-scrollbar-thumb {
|
||||||
@if ($style == 'dark') {
|
@if ($style == 'dark') {
|
||||||
background: #8a8a8a !important;
|
background: #8a8a8a !important;
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
border-radius: 10px !important;
|
border-radius: 10px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#{$selector}::-webkit-scrollbar-thumb:hover {
|
#{$selctor}::-webkit-scrollbar-thumb:hover {
|
||||||
@if ($style == 'dark') {
|
@if ($style == 'dark') {
|
||||||
background: #424242 !important;
|
background: #424242 !important;
|
||||||
|
|
||||||
@@ -35,4 +35,3 @@
|
|||||||
cursor: pointer !important;
|
cursor: pointer !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
<base href="/">
|
<base href="/">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/x-icon" href="images/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="images/favicon.ico">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700&display=swap" rel="stylesheet" type="text/css">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
|
|||||||
@@ -8,6 +8,5 @@ body, html {
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
@include set-scrollbar-style('light', '');
|
@include set-scrollbar-style('light', '');
|
||||||
}
|
}
|
||||||
|
|||||||
-6796
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user