Fix manual and instumented layout changes (force event 'resize')

This commit is contained in:
Kristóf Tóth
2018-04-20 11:09:47 +02:00
parent 5e3e619423
commit 26df194a17
21 changed files with 26 additions and 335 deletions

View File

@ -1,13 +1,16 @@
export const config = {
dashboard: {
route: 'dashboard',
currentLayout: 'terminal-web',
currentLayout: 'terminal-ide-web',
enabledLayouts: new Set([
'terminal-ide-web',
'terminal-web',
'terminal-ide-vertical',
'terminal-ide-horizontal',
'terminal-only',
'web-only',
'ide-only'
'terminal-web',
'ide-web-vertical',
'ide-only',
'web-only'
]),
allLayouts: new Set([
'terminal-ide-web',

View File

@ -17,7 +17,7 @@
<app-terminal></app-terminal>
</div>
<div class="tfw-sidebar">
<app-sidebar [layout]="layout"></app-sidebar>
<app-sidebar (layoutChanged)="setLayout($event)" [layout]="layout"></app-sidebar>
</div>
<div class="tfw-terminal-footer"></div>

View File

@ -1,4 +1,4 @@
import { Component, OnDestroy, OnInit, ChangeDetectorRef, ViewChild, AfterViewInit } from '@angular/core';
import { Component, OnDestroy, OnInit, ChangeDetectorRef } from '@angular/core';
import { DeploymentNotificationService } from '../services/deployment-notification.service';
import { Subscription } from 'rxjs/Subscription';
import { WebSocketService } from '../services/websocket.service';
@ -11,16 +11,13 @@ import { SidebarComponent } from '../sidebar/sidebar.component';
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
export class DashboardComponent implements OnInit, OnDestroy {
deploying = false;
deploymentNotificationSubscription: Subscription;
enabledLayouts: Set<string> = config.dashboard.enabledLayouts;
layout: string = config.dashboard.currentLayout ;
layout: string = config.dashboard.currentLayout;
command_handlers = {'layout': this.layoutHandler.bind(this)};
@ViewChild(SidebarComponent)
private sidebarComponent: SidebarComponent;
constructor(private deploymentNotificationService: DeploymentNotificationService,
private webSocketService: WebSocketService,
private changeDetectorRef: ChangeDetectorRef) {}
@ -36,19 +33,22 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
);
}
ngAfterViewInit() {
this.layout = this.sidebarComponent.layout;
}
layoutHandler(data: LayoutCommand) {
if (this.enabledLayouts.has(data.layout)) {
this.layout = data.layout;
this.setLayout(data.layout);
}
else {
console.log('Invalid ide layout "' + data.layout + '" received!');
}
}
setLayout(layout: string) {
this.layout = layout;
// We need to trigger a 'resize' event manually, otherwise ace editor stays collapsed
// Ace editors 'resize' event listener requires a parameter of force=true
setTimeout(() => window.dispatchEvent(new Event('resize', {force: true} as any)), 0);
}
ngOnDestroy() {
if (this.deploymentNotificationSubscription) {
this.deploymentNotificationSubscription.unsubscribe();

View File

@ -17,6 +17,7 @@ import { WebSocketService } from '../services/websocket.service';
import { ProcessManagerService } from '../services/processmanager.service';
import { DeploymentNotificationService } from '../services/deployment-notification.service';
import { config } from '../config';
import { element } from 'protractor';
const modelist = brace.acequire('ace/ext/modelist');

View File

@ -1,4 +1,4 @@
import { Component, Input, Output, OnInit } from '@angular/core';
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { config } from '../config';
@Component({
@ -7,14 +7,15 @@ import { config } from '../config';
styleUrls: ['./sidebar.component.scss']
})
export class SidebarComponent implements OnInit {
@Input() @Output() layout: string;
export class SidebarComponent {
@Input() layout: string;
@Output() layoutChanged = new EventEmitter<string>();
enabledLayouts: Set<string> = config.dashboard.enabledLayouts;
constructor() {}
ngOnInit() {
}
setLayout(layout: string) {
this.layout = layout;
this.layoutChanged.emit(this.layout);
}
}

View File

@ -1,7 +0,0 @@
export interface SourceCode {
filename: string;
content?: string;
files: string[];
directory: string;
command: string;
}

View File

@ -1,44 +0,0 @@
<div class="tfw-grid-webide-statusbar">
<div class="btn-group btn-group-sm flex-wrap tao-grid-center-left">
<button *ngFor="let file of files"
class="btn tfw-tab-btn"
(click)="tabSwitchButtonHandler(file)"
[class.active]="filename === file"
[class.disabled]="filename === file"
[disabled]="filename === file"
[class.tao-tab-btn-saved]="filename === file && codeState === 'SAVED'">
<span *ngIf="filename !== file">{{file}}</span>
<span *ngIf="filename === file"
[class.underline]="codeState === 'DIRTY'">{{file}}</span>
</button>
</div>
<div class="btn-group-sm tfw-deploy-btn-group">
<button *ngIf="showDeployButton"
type="submit"
class="btn tfw-deploy-btn tao-grid-top-center"
(click)="sendCodeIfDirty(); deployCode()"
[disabled]="deployButtonState === 'DEPLOYING' || deployButtonState === 'DEPLOYED'"
[class.deployed]="deployButtonState === 'DEPLOYED'"
[class.deploy]="deployButtonState === 'DEPLOYING'"
[class.disabled]="deployButtonState === 'DEPLOYING' || deployButtonState === 'DEPLOYED'"
[class.failed]="deployButtonState === 'FAILED'"
>
<span *ngIf="deployButtonState === 'TODEPLOY'">Deploy</span>
<span *ngIf="deployButtonState === 'DEPLOYED'">
<img src="images/greentick_icon.svg"/>
<span>Deployed</span>
</span>
<span *ngIf="deployButtonState === 'DEPLOYING'"><div class="loader"></div>Reloading app...</span>
<span *ngIf="deployButtonState === 'FAILED'">Deployment failed. Retry</span></button>
</div>
</div>
<div (keyup)="setCodeState('DIRTY'); setDeployButtonState('TODEPLOY'); resetAutoSaveCountdown()"
ace-editor
[(text)]="code"
[mode]="language"
[theme]="theme"
class="tfw-ace-editor"
>
</div>

View File

@ -1,95 +0,0 @@
@import "../../assets/scss/variables.scss";
.tfw-grid-webide-statusbar {
display: grid;
height: $tao-navbar-height;
grid-template-columns: 8fr 1fr;
}
.tfw-ace-editor {
height: calc(100% - 67px);
width: 100%;
}
.btn-group {
padding-left: 34px;
}
.underline {
text-decoration: underline;
}
.tfw-tab-btn {
background-color: white;
border: 1px solid $tao-plum-900;
border-left: 0;
border-right: 0;
border-radius: 100px;
padding: 5px 19px;
z-index: 200;
.tfw-tab-btn-saved,
.active,
.disabled,
&:disabled {
background-color: $tao-plum-200;
font-weight: 500;
font-style: italic;
color: black;
border: 0;
}
}
.tfw-deploy-btn-group {
margin: auto $tiny;
.tfw-deploy-btn {
background: $tao-bright-green-200;
border-radius: 100px;
padding: 6px 19px;
img {
position: relative;
bottom: 1px;
height: $small;
}
&.failed {
background-color: $tao-red-500;
color:white;
}
&:disabled,
&.disabled,
&.deployed,
&.deploy
{
background-color: $tao-bright-green-100;
color: black;
}
&.deploy {
background-color: $tao-warm-yellow-200;
}
.loader {
border: 2px solid $tao-warm-yellow-600;
border-radius: 50%;
border-top: 2px solid $tao-warm-yellow-200;
width: 15px;
height: 15px;
animation: spin 2s linear infinite;
display: inline-block;
margin-right: 5px;
position: relative;
top: 2px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
}
}

View File

@ -1,154 +0,0 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import * as brace from 'brace';
import 'brace/ext/modelist';
import 'brace/mode/c_cpp';
import 'brace/mode/csharp';
import 'brace/mode/java';
import 'brace/mode/javascript';
import 'brace/mode/json';
import 'brace/mode/python';
import 'brace/mode/sql';
import 'brace/theme/cobalt';
import { SourceCode } from './source-code';
import { WebSocketService } from '../services/websocket.service';
import { ProcessManagerService } from '../services/processmanager.service';
import { DeploymentNotificationService } from '../services/deployment-notification.service';
import { config } from '../config';
const modelist = brace.acequire('ace/ext/modelist');
@Component({
selector: 'app-webide',
templateUrl: './webide.component.html',
styleUrls: ['./webide.component.scss']
})
export class WebideComponent implements OnInit {
key_id = 'webide';
filename = '';
code: string = config.webide.defaultCode;
language: string = config.webide.defaultLanguage;
theme = 'cobalt';
directory = '';
files: string[];
codeState = 'SAVED';
deployButtonState = 'DEPLOYED';
showDeployButton: boolean = config.webide.showDeployButton;
autosave = null;
command_handlers = {'reload': this.reloadHandler.bind(this),
'read': this.readHandler.bind(this),
'select': this.selectHandler.bind(this),
'write': this.writeHandler.bind(this),
'selectdir': this.selectdirHandler.bind(this)};
constructor(private webSocketService: WebSocketService,
private changeDetectorRef: ChangeDetectorRef,
private processManagerService: ProcessManagerService,
private deploymentNotificationService: DeploymentNotificationService) { }
ngOnInit() {
this.webSocketService.connect();
this.subscribeWS();
this.requestCode();
this.processManagerService.init();
this.processManagerService.subscribeCallback(config.webide.deployProcessName, (event) => { this.setDeployButtonState('DEPLOYED'); });
this.processManagerService.subscribeErrorCallback(config.webide.deployProcessName, (event) => { this.setDeployButtonState('FAILED'); });
this.resetAutoSaveCountdown();
}
subscribeWS() {
this.webSocketService.observeKey<SourceCode>(this.key_id).subscribe((event) => {
this.command_handlers[event.data.command](event.data);
this.changeDetectorRef.detectChanges();
});
}
updateFileData(data: SourceCode) {
this.filename = data.filename;
this.directory = data.directory;
this.code = (data.content != null) ? data.content : this.code;
this.language = modelist.getModeForPath(this.filename).name;
this.files = data.files;
}
selectHandler(data: SourceCode) {
this.updateFileData(data);
}
reloadHandler(data: SourceCode) {
this.requestCode();
}
readHandler(data: SourceCode) {
if (this.codeState === 'SAVED') {
this.updateFileData(data);
}
}
writeHandler() {
this.setCodeState('SAVED');
}
selectdirHandler(data: SourceCode) {
this.updateFileData(data);
}
resetAutoSaveCountdown() {
if (this.autosave) {
clearInterval(this.autosave);
}
this.autosave = setInterval(() => { this.sendCodeIfDirty(); }, config.webide.autoSaveInterval);
}
tabSwitchButtonHandler(file) {
if (this.codeState === 'DIRTY') {
this.sendCodeContents();
}
this.selectCode(file);
this.requestCode();
}
setCodeState(state: string) {
if (state.match('SAVED|DIRTY')) {
this.codeState = state;
}
}
setDeployButtonState(state: string) {
this.deployButtonState = state;
this.deploymentNotificationService.deploying.next(state === 'DEPLOYING' ? true : false);
}
deployCode() {
this.processManagerService.restartProcess('login');
this.setDeployButtonState('DEPLOYING');
}
sendCodeIfDirty() {
if (this.codeState === 'DIRTY') {
this.sendCodeContents();
}
}
sendCodeContents() {
this.webSocketService.send(this.key_id, {
'command': 'write',
'content': this.code
});
}
requestCode() {
this.webSocketService.send(this.key_id, {
'command': 'read'
});
}
selectCode(filename: string) {
this.webSocketService.send(this.key_id, {
'command': 'select',
'filename': filename
});
}
}