Refreshing UI (#1253)

* Update font-fixer.ts

* Update interface-status.ts

* Updating project map UI

* Updating templates menu

* Fixing tests

* Update karma.conf.js
This commit is contained in:
piotrpekala7 2022-02-08 23:48:45 +01:00 committed by GitHub
parent 2b834768c6
commit 8874e7efbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 194 additions and 138 deletions

View File

@ -16,8 +16,8 @@ describe('FontFixer', () => {
}; };
expect(fixer.fix(font)).toEqual({ expect(fixer.fix(font)).toEqual({
font_family: 'Noto Sans', font_family: 'Arial',
font_size: 11, font_size: 12,
font_weight: 'bold', font_weight: 'bold',
}); });
}); });
@ -39,12 +39,12 @@ describe('FontFixer', () => {
it('should fix TypeWriter font and 10px size in styles', () => { it('should fix TypeWriter font and 10px size in styles', () => {
const styles = 'font-family: TypeWriter; font-size: 10px; font-weight: bold'; const styles = 'font-family: TypeWriter; font-size: 10px; font-weight: bold';
expect(fixer.fixStyles(styles)).toEqual('font-family:Noto Sans;font-size:11px;font-weight:bold'); expect(fixer.fixStyles(styles)).toEqual('font-family:Arial;font-size:12px;font-weight:bold');
}); });
it('should fix TypeWriter font and 10px size in styles with quotes', () => { it('should fix TypeWriter font and 10px size in styles with quotes', () => {
const styles = 'font-family: "TypeWriter"; font-size: 10px; font-weight: bold'; const styles = 'font-family: "TypeWriter"; font-size: 10px; font-weight: bold';
expect(fixer.fixStyles(styles)).toEqual('font-family:Noto Sans;font-size:11px;font-weight:bold'); expect(fixer.fixStyles(styles)).toEqual('font-family:Arial;font-size:12px;font-weight:bold');
}); });
}); });

View File

@ -9,8 +9,8 @@ import { Font } from '../models/font';
export class FontFixer { export class FontFixer {
static DEFAULT_FONT = 'TypeWriter'; static DEFAULT_FONT = 'TypeWriter';
static DEFAULT_SIZE = 10; static DEFAULT_SIZE = 10;
static REPLACE_BY_FONT = 'Noto Sans'; static REPLACE_BY_FONT = 'Arial';
static REPLACE_BY_SIZE = 11; static REPLACE_BY_SIZE = 12;
public fix(font: Font): Font { public fix(font: Font): Font {
if (font.font_family === FontFixer.DEFAULT_FONT && font.font_size === FontFixer.DEFAULT_SIZE) { if (font.font_family === FontFixer.DEFAULT_FONT && font.font_size === FontFixer.DEFAULT_SIZE) {

View File

@ -42,7 +42,7 @@ describe('TextDrawingWidget', () => {
const text_element = drew.nodes()[0]; const text_element = drew.nodes()[0];
expect(text_element.innerHTML).toEqual('<tspan xml:space="preserve" x="0" dy="0em">THIS IS TEXT</tspan>'); expect(text_element.innerHTML).toEqual('<tspan xml:space="preserve" x="0" dy="0em">THIS IS TEXT</tspan>');
expect(text_element.getAttribute('fill')).toEqual('#000000'); expect(text_element.getAttribute('fill')).toEqual('#000000');
expect(text_element.getAttribute('style')).toEqual('font-family: "Noto Sans"; font-size: 11pt; font-weight: bold'); expect(text_element.getAttribute('style')).toEqual('font-family: "Arial"; font-size: 12pt; font-weight: bold');
expect(text_element.getAttribute('text-decoration')).toEqual('line-through'); expect(text_element.getAttribute('text-decoration')).toEqual('line-through');
}); });

View File

@ -97,9 +97,9 @@ export class InterfaceStatusWidget implements Widget {
.attr('y', (ls: LinkStatus) => ls.y - 10) .attr('y', (ls: LinkStatus) => ls.y - 10)
.attr('rx', 8) .attr('rx', 8)
.attr('ry', 8) .attr('ry', 8)
.style('fill', 'white') .style('fill', '#c7ffdf')
.attr('stroke', '#2ecc71') .attr('stroke', '#2ecc71')
.attr('stroke-width', 3); .attr('stroke-width', 2);
status_started.exit().remove(); status_started.exit().remove();
const status_started_label = link_group const status_started_label = link_group
.selectAll<SVGTextElement, LinkStatus>('text.status_started_label') .selectAll<SVGTextElement, LinkStatus>('text.status_started_label')
@ -111,7 +111,7 @@ export class InterfaceStatusWidget implements Widget {
.text((ls: LinkStatus) => ls.port) .text((ls: LinkStatus) => ls.port)
.attr('x', (ls: LinkStatus) => ls.x - 25) .attr('x', (ls: LinkStatus) => ls.x - 25)
.attr('y', (ls: LinkStatus) => ls.y + 5) .attr('y', (ls: LinkStatus) => ls.y + 5)
.attr('fill', `black`); .attr('fill', `#0e9647`);
status_started_label.exit().remove(); status_started_label.exit().remove();
const status_stopped = link_group const status_stopped = link_group
@ -129,9 +129,9 @@ export class InterfaceStatusWidget implements Widget {
.attr('y', (ls: LinkStatus) => ls.y - 10) .attr('y', (ls: LinkStatus) => ls.y - 10)
.attr('rx', 8) .attr('rx', 8)
.attr('ry', 8) .attr('ry', 8)
.style('fill', 'white') .style('fill', '#ffe3e3')
.attr('stroke', 'red') .attr('stroke', 'red')
.attr('stroke-width', 3); .attr('stroke-width', 2);
status_stopped.exit().remove(); status_stopped.exit().remove();
const status_stopped_label = link_group const status_stopped_label = link_group
.selectAll<SVGTextElement, LinkStatus>('text.status_stopped_label') .selectAll<SVGTextElement, LinkStatus>('text.status_stopped_label')
@ -143,7 +143,7 @@ export class InterfaceStatusWidget implements Widget {
.text((ls: LinkStatus) => ls.port) .text((ls: LinkStatus) => ls.port)
.attr('x', (ls: LinkStatus) => ls.x - 25) .attr('x', (ls: LinkStatus) => ls.x - 25)
.attr('y', (ls: LinkStatus) => ls.y + 5) .attr('y', (ls: LinkStatus) => ls.y + 5)
.attr('fill', `black`); .attr('fill', `red`);
status_stopped_label.exit().remove(); status_stopped_label.exit().remove();
const status_suspended = link_group const status_suspended = link_group
@ -162,8 +162,8 @@ export class InterfaceStatusWidget implements Widget {
.attr('rx', 8) .attr('rx', 8)
.attr('ry', 8) .attr('ry', 8)
.style('fill', 'white') .style('fill', 'white')
.attr('stroke', '#FFFF00') .attr('stroke', '#fffbc3')
.attr('stroke-width', 3); .attr('stroke-width', 2);
status_suspended.exit().remove(); status_suspended.exit().remove();
const status_suspended_label = link_group const status_suspended_label = link_group
.selectAll<SVGTextElement, LinkStatus>('text.status_suspended_label') .selectAll<SVGTextElement, LinkStatus>('text.status_suspended_label')
@ -175,7 +175,7 @@ export class InterfaceStatusWidget implements Widget {
.text((ls: LinkStatus) => ls.port) .text((ls: LinkStatus) => ls.port)
.attr('x', (ls: LinkStatus) => ls.x - 25) .attr('x', (ls: LinkStatus) => ls.x - 25)
.attr('y', (ls: LinkStatus) => ls.y + 5) .attr('y', (ls: LinkStatus) => ls.y + 5)
.attr('fill', `black`); .attr('fill', `#6b5633`);
status_suspended_label.exit().remove(); status_suspended_label.exit().remove();
} else { } else {
const status_started = link_group const status_started = link_group

View File

@ -67,6 +67,14 @@
</div> </div>
</div> </div>
<div> <div>
<button
class="map-settings-button"
matTooltip="Project Map Settings"
matTooltipClass="custom-tooltip"
mat-icon-button
[matMenuTriggerFor]="viewMenu">
<mat-icon>view_module</mat-icon>
</button>
<button <button
matTooltip="Toggle topology/servers summary" matTooltip="Toggle topology/servers summary"
matTooltipClass="custom-tooltip" matTooltipClass="custom-tooltip"
@ -83,39 +91,27 @@
<!-- GNS3 menu --> <!-- GNS3 menu -->
<mat-menu #mainMenu="matMenu" [overlapTrigger]="false"> <mat-menu #mainMenu="matMenu" [overlapTrigger]="false">
<button mat-menu-item [matMenuTriggerFor]="projectMenu">
<mat-icon>insert_drive_file</mat-icon>
<span>Project settings</span>
</button>
<button mat-menu-item [routerLink]="['/server', server.id, 'projects']"> <button mat-menu-item [routerLink]="['/server', server.id, 'projects']">
<mat-icon>work</mat-icon> <mat-icon>work</mat-icon>
<span>Go to projects</span> <span>Projects</span>
</button> </button>
<button mat-menu-item [routerLink]="['/servers']"> <button mat-menu-item [routerLink]="['/servers']">
<mat-icon>developer_board</mat-icon> <mat-icon>developer_board</mat-icon>
<span>Go to servers</span> <span>Servers</span>
</button>
<button mat-menu-item routerLink="/server/{{ server.id }}/preferences">
<mat-icon>settings_applications</mat-icon>
<span>Go to preferences</span>
</button> </button>
<button mat-menu-item routerLink="/server/{{ server.id }}/systemstatus"> <button mat-menu-item routerLink="/server/{{ server.id }}/systemstatus">
<mat-icon>info</mat-icon> <mat-icon>data_usage</mat-icon>
<span>Go to system status</span> <span>System Status</span>
</button> </button>
<button mat-menu-item routerLink="/settings"> <button mat-menu-item routerLink="/settings">
<mat-icon>settings</mat-icon> <mat-icon>settings</mat-icon>
<span>Go to settings</span> <span>Settings</span>
</button>
<button mat-menu-item (click)="addNewTemplate()">
<mat-icon>control_point</mat-icon>
<span>New template</span>
</button> </button>
<app-import-appliance [server]="server" [project]="project"></app-import-appliance> <app-import-appliance [server]="server" [project]="project"></app-import-appliance>
<button mat-menu-item [matMenuTriggerFor]="projectMenu">
<mat-icon>settings</mat-icon>
<span>Project settings</span>
</button>
<button mat-menu-item [matMenuTriggerFor]="viewMenu">
<mat-icon>view_module</mat-icon>
<span>Map settings</span>
</button>
</mat-menu> </mat-menu>
<!-- Project Settings sub-menu --> <!-- Project Settings sub-menu -->

View File

@ -73,6 +73,10 @@ g.node:hover {
font-size: 28px !important; font-size: 28px !important;
} }
.map-settings-button mat-icon {
font-size: 22px !important;
}
.selected { .selected {
background: rgba(0, 151, 167, 0.1); background: rgba(0, 151, 167, 0.1);

View File

@ -1,5 +1,5 @@
<div class="title-container"> <div class="title-container">
<h1 mat-dialog-title>Add a node</h1> <h1 mat-dialog-title>Insert New Node</h1>
<button <button
mat-button mat-button
class="top-button" class="top-button"

View File

@ -10,63 +10,95 @@
</button> </button>
<mat-menu #mainMenu="matMenu"> <mat-menu #mainMenu="matMenu">
<button mat-menu-item (click)="openDialog()"> <div class="templateMenu">
<mat-icon>add_to_queue</mat-icon> <div class="templateMenuHeader">
<span>Open dialog to configure</span> <button mat-menu-item (click)="openDialog()">
</button> <mat-icon>add_to_queue</mat-icon>
<span>
Insert New Node...
</span>
</button>
<button mat-menu-item (click)="addNewTemplate()">
<mat-icon>library_add</mat-icon>
<span>
New Template...
</span>
</button>
</div>
<mat-form-field (click)="$event.stopPropagation()" class="form-field" floatPlaceholder="never"> <div class="templateFilterBar">
<input <mat-form-field (click)="$event.stopPropagation()" class="form-field" floatPlaceholder="never">
matInput <input
placeholder="Search by name" matInput
(keyup)="filterTemplates($event)" placeholder="Filter by name"
[(ngModel)]="searchText" search="search"
[ngModelOptions]="{ standalone: true }" (keyup)="filterTemplates($event)"
/> [(ngModel)]="searchText"
</mat-form-field> [ngModelOptions]="{ standalone: true }"
<mat-form-field (click)="$event.stopPropagation()" class="form-field"> />
<mat-select <mat-icon class="searchIcon" matSuffix>search</mat-icon>
[ngModelOptions]="{ standalone: true }" </mat-form-field>
placeholder="Filter templates by type" <mat-form-field (click)="$event.stopPropagation()" class="form-field">
(selectionChange)="filterTemplates($event)" <mat-select
[(ngModel)]="selectedType" [ngModelOptions]="{ standalone: true }"
> placeholder="Filter by type"
<mat-option *ngFor="let type of templateTypes" [value]="type"> (selectionChange)="filterTemplates($event)"
{{ type }} [(ngModel)]="selectedType"
</mat-option> >
</mat-select> <mat-option *ngFor="let type of templateTypes" [value]="type">
</mat-form-field> {{ type }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="menu"> <div class="menu">
<div class="templateList"> <div class="templateList">
<mat-list-item *ngFor="let template of filteredTemplates; let i = index"> <mat-list-item *ngFor="let template of filteredTemplates; let i = index">
<span *ngIf="i % 4 === 0" class="templateRow"> <span *ngIf="i % 4 === 0" class="templateRow">
<span class="templateIcon"> <span class="templateIcon">
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i])"> <div
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i])" /> mwlDraggable
</div> (dragStart)="dragStart($event)"
<div [ngClass]="{ templateText: !isLightThemeEnabled, lightTemplateText: isLightThemeEnabled }">{{ filteredTemplates[i].name }}</div> (dragEnd)="dragEnd($event, filteredTemplates[i])"
</span> class="iconContainer">
<span *ngIf="filteredTemplates[i + 1]" class="templateIcon"> <img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i])" />
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i + 1])"> </div>
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 1])" /> <div class="templateText">{{ filteredTemplates[i].name }}</div>
</div> </span>
<div [ngClass]="{ templateText: !isLightThemeEnabled, lightTemplateText: isLightThemeEnabled }">{{ filteredTemplates[i + 1].name }}</div> <span *ngIf="filteredTemplates[i + 1]" class="templateIcon">
</span> <div
<span *ngIf="filteredTemplates[i + 2]" class="templateIcon"> mwlDraggable
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i + 2])"> (dragStart)="dragStart($event)"
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 2])" /> (dragEnd)="dragEnd($event, filteredTemplates[i + 1])"
</div> class="iconContainer">
<div [ngClass]="{ templateText: !isLightThemeEnabled, lightTemplateText: isLightThemeEnabled }">{{ filteredTemplates[i + 2].name }}</div> <img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 1])" />
</span> </div>
<span *ngIf="filteredTemplates[i + 3]" class="templateIcon"> <div class="templateText">{{ filteredTemplates[i + 1].name }}</div>
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i + 3])"> </span>
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 3])" /> <span *ngIf="filteredTemplates[i + 2]" class="templateIcon">
</div> <div
<div [ngClass]="{ templateText: !isLightThemeEnabled, lightTemplateText: isLightThemeEnabled }">{{ filteredTemplates[i + 3].name }}</div> mwlDraggable
</span> (dragStart)="dragStart($event)"
</span> (dragEnd)="dragEnd($event, filteredTemplates[i + 2])"
</mat-list-item> class="iconContainer">
</div> <img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 2])" />
</div> </div>
<div class="templateText">{{ filteredTemplates[i + 2].name }}</div>
</span>
<span *ngIf="filteredTemplates[i + 3]" class="templateIcon">
<div
mwlDraggable
(dragStart)="dragStart($event)"
(dragEnd)="dragEnd($event, filteredTemplates[i + 3])"
class="iconContainer">
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 3])" />
</div>
<div class="templateText">{{ filteredTemplates[i + 3].name }}</div>
</span>
</span>
</mat-list-item>
</div>
</div>
</div>
</mat-menu> </mat-menu>

View File

@ -1,6 +1,7 @@
::ng-deep .mat-menu-panel { ::ng-deep .mat-menu-panel {
max-width: 400px; max-width: 400px;
max-height: 500px; max-height: 640px;
border-radius: 8px;
} }
.menu { .menu {
@ -8,6 +9,26 @@
overflow-y: scroll; overflow-y: scroll;
scrollbar-color: darkgrey #263238; scrollbar-color: darkgrey #263238;
scrollbar-width: thin; scrollbar-width: thin;
font-size: 12px;
}
.templateMenuHeader {
border-bottom: 1px solid rgba(255,255,255,0.05);
}
.templateFilterBar {
padding: 10px 2%;
background-color: rgba(0,0,0,0.2);
margin-bottom: 10px;
}
.templateFilterBar > .form-field {
font-size: 12px;
}
.templateFilterBar .searchIcon {
position: relative;
top: 5px;
} }
::-webkit-scrollbar { ::-webkit-scrollbar {
@ -24,20 +45,22 @@
} }
.form-field { .form-field {
width: 90%; width: 44%;
margin-left: 5%; margin-left: 3%;
margin-right: 5%; margin-right: 3%;
} }
.image { .image {
width: 65px; display: inline-block;
height: 65px; width: 55px;
height: 55px;
filter: invert(0); filter: invert(0);
--webkit-filter: invert(0) !important; --webkit-filter: invert(0) !important;
} }
.templateList { .templateList {
width: 100%; width: 100%;
padding: 10px;
} }
.templateRow { .templateRow {
@ -49,12 +72,24 @@
word-wrap: break-word; word-wrap: break-word;
} }
.lightTemplateText { .templateIcon {
word-wrap: break-word; width: 90px !important;
color: black; padding: 2px 5px;
text-align: center;
margin: 3px;
} }
.templateIcon { .templateIcon > .iconContainer {
width: 80px !important; display: inline-flex;
padding: 10px; align-items: center;
justify-content: center;
width: 70px;
height: 70px;
margin-bottom: 10px;
border-radius: 50%;
cursor: move;
}
.templateIcon > .iconContainer:hover {
background-color: rgba(237, 246, 231, 0.08);
} }

View File

@ -1,7 +1,5 @@
import { OverlayContainer } from '@angular/cdk/overlay';
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ThemeService } from '../../services/theme.service';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { Project } from '../../models/project'; import { Project } from '../../models/project';
import { Server } from '../../models/server'; import { Server } from '../../models/server';
@ -10,6 +8,7 @@ import { MapScaleService } from '../../services/mapScale.service';
import { SymbolService } from '../../services/symbol.service'; import { SymbolService } from '../../services/symbol.service';
import { TemplateService } from '../../services/template.service'; import { TemplateService } from '../../services/template.service';
import { NodeAddedEvent, TemplateListDialogComponent } from './template-list-dialog/template-list-dialog.component'; import { NodeAddedEvent, TemplateListDialogComponent } from './template-list-dialog/template-list-dialog.component';
import { NewTemplateDialogComponent } from '../project-map/new-template-dialog/new-template-dialog.component';
@Component({ @Component({
selector: 'app-template', selector: 'app-template',
@ -20,7 +19,7 @@ export class TemplateComponent implements OnInit, OnDestroy {
@Input() server: Server; @Input() server: Server;
@Input() project: Project; @Input() project: Project;
@Output() onNodeCreation = new EventEmitter<any>(); @Output() onNodeCreation = new EventEmitter<any>();
overlay;
templates: Template[] = []; templates: Template[] = [];
filteredTemplates: Template[] = []; filteredTemplates: Template[] = [];
searchText: string = ''; searchText: string = '';
@ -47,19 +46,13 @@ export class TemplateComponent implements OnInit, OnDestroy {
startY: number; startY: number;
private subscription: Subscription; private subscription: Subscription;
private themeSubscription: Subscription;
private isLightThemeEnabled: boolean = false;
constructor( constructor(
private dialog: MatDialog, private dialog: MatDialog,
private templateService: TemplateService, private templateService: TemplateService,
private scaleService: MapScaleService, private scaleService: MapScaleService,
private symbolService: SymbolService, private symbolService: SymbolService
private themeService: ThemeService, ) {}
private overlayContainer: OverlayContainer,
) {
this.overlay = overlayContainer.getContainerElement();
}
ngOnInit() { ngOnInit() {
this.subscription = this.templateService.newTemplateCreated.subscribe((template: Template) => { this.subscription = this.templateService.newTemplateCreated.subscribe((template: Template) => {
@ -72,23 +65,6 @@ export class TemplateComponent implements OnInit, OnDestroy {
this.templates = listOfTemplates; this.templates = listOfTemplates;
}); });
this.symbolService.list(this.server); this.symbolService.list(this.server);
if (this.themeService.getActualTheme() === 'light') this.isLightThemeEnabled = true;
this.themeSubscription = this.themeService.themeChanged.subscribe((value: string) => {
if (value === 'light-theme') this.isLightThemeEnabled = true;
this.toggleTheme();
});
}
toggleTheme(): void {
if (this.overlay.classList.contains("dark-theme")) {
this.overlay.classList.remove("dark-theme");
this.overlay.classList.add("light-theme");
} else if (this.overlay.classList.contains("light-theme")) {
this.overlay.classList.remove("light-theme");
this.overlay.classList.add("dark-theme");
} else {
this.overlay.classList.add("light-theme");
}
} }
sortTemplates() { sortTemplates() {
@ -152,6 +128,19 @@ export class TemplateComponent implements OnInit, OnDestroy {
}); });
} }
addNewTemplate() {
const dialogRef = this.dialog.open(NewTemplateDialogComponent, {
width: '1000px',
maxHeight: '700px',
autoFocus: false,
disableClose: true,
});
let instance = dialogRef.componentInstance;
instance.server = this.server;
instance.project = this.project;
}
getImageSourceForTemplate(template: Template) { getImageSourceForTemplate(template: Template) {
return `${this.server.protocol}//${this.server.host}:${this.server.port}/v2/symbols/${template.symbol}/raw`; return `${this.server.protocol}//${this.server.host}:${this.server.port}/v2/symbols/${template.symbol}/raw`;
} }