build: Add build process for newgui (#7351)

This commit is contained in:
Jakob Borg
2021-02-15 14:52:28 +01:00
parent d117b4b570
commit b2c9e7b07b
123 changed files with 38050 additions and 17178 deletions
@@ -0,0 +1,4 @@
<div fxLayout="row" fxLayoutAlign="space-between start" [ngClass]="(_selected)?'item selected':'item'">
<div><a href="#">{{state}}</a>: &nbsp;</div>
<div>{{count}}</div>
</div>
@@ -0,0 +1,27 @@
@mixin chart-item-theme($theme) {
.item {
cursor: pointer;
padding: 3px 7px 3px 7px;
border-radius: 4px;
}
.selected {
background-color: #DDDDDD;
color: #303030;
}
.selected a {
color: #303030;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
.selected {
background-color: map_get($mat-grey, 900);
color: white;
}
.selected a {
color: white;
}
}
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChartItemComponent } from './chart-item.component';
describe('ChartItemComponent', () => {
let component: ChartItemComponent;
let fixture: ComponentFixture<ChartItemComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ChartItemComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ChartItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,19 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-chart-item',
templateUrl: './chart-item.component.html',
styleUrls: ['./chart-item.component.scss']
})
export class ChartItemComponent {
@Input() state: string;
@Input() count: number;
@Input('selected')
set selected(s: boolean) {
this._selected = s;
}
_selected: boolean = true;
constructor() { }
}
@@ -0,0 +1,14 @@
<app-card>
<app-card-title>{{title | uppercase}}</app-card-title>
<app-card-content>
<div fxLayout="row" fxLayoutAlign="space-between stretch">
<app-donut-chart [elementID]="chartID" fxFlex="30" [title]="title" (stateEvent)="onItemSelect($event)">
</app-donut-chart>
<div class=" items" fxLayout="column" fxLayoutAlign="start end" fxFlex="70">
<app-chart-item *ngFor="let state of states" (click)="onItemSelect(state)" [state]="state.label"
[count]="state.count" [selected]="state.selected">
</app-chart-item>
</div>
</div>
</app-card-content>
</app-card>
@@ -0,0 +1,26 @@
import { async, TestBed } from '@angular/core/testing';
import { ChartComponent } from './chart.component';
import { HttpClientModule } from '@angular/common/http';
class MockService {
getEach() {
// unimplemented
}
};
describe('ChartComponent', () => {
let component: ChartComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule],
providers: [ChartComponent]
}).compileComponents();
component = TestBed.inject(ChartComponent);
}));
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,115 @@
import { Component, OnInit, ViewChild, Input, Type } from '@angular/core';
import Folder from '../../folder'
import { FolderService } from 'src/app/services/folder.service';
import { DonutChartComponent } from '../donut-chart/donut-chart.component';
import { DeviceService } from 'src/app/services/device.service';
import Device from 'src/app/device';
import { StType } from '../../type';
import { FilterService } from 'src/app/services/filter.service';
import { Observable } from 'rxjs';
export interface ChartItemState {
label: string,
count: number,
color: string,
selected: boolean,
}
@Component({
selector: 'app-chart',
templateUrl: './chart.component.html',
styleUrls: ['./chart.component.scss']
})
export class ChartComponent implements OnInit {
@ViewChild(DonutChartComponent) donutChart: DonutChartComponent;
@Input() type: StType;
title: string;
chartID: string;
states: ChartItemState[] = [];
private observer: Observable<any>;
private activeChartState: ChartItemState;
constructor(
private folderService: FolderService,
private deviceService: DeviceService,
private filterService: FilterService,
) { }
onItemSelect(s: ChartItemState) {
// Send chart item state to filter
this.filterService.changeFilter({ type: this.type, text: s.label });
// Deselect all other items
this.states.forEach(s => {
s.selected = false;
});
// Select item only
if (s !== this.activeChartState) {
s.selected = true;
this.activeChartState = s;
} else {
this.activeChartState = null;
this.filterService.changeFilter({ type: this.type, text: "" })
}
}
ngOnInit(): void {
switch (this.type) {
case StType.Folder:
this.title = "Folders";
this.chartID = 'foldersChart';
this.observer = this.folderService.folderAdded$;
break;
case StType.Device:
this.title = "Devices";
this.chartID = 'devicesChart';
this.observer = this.deviceService.deviceAdded$;
break;
}
}
ngAfterViewInit() {
let totalCount: number = 0;
this.observer.subscribe(
t => {
// Count the number of folders and set chart
totalCount++;
this.donutChart.count = totalCount;
// Get StateType and convert to string
const stateType = t.stateType;
const state = t.state;
let color;
switch (this.type) {
case StType.Folder:
color = Folder.stateTypeToColor(t.stateType);
break;
case StType.Device:
color = Device.stateTypeToColor(stateType);
break;
}
// Check if state exists
let found: boolean = false;
this.states.forEach(s => {
if (s.label === state) {
s.count = s.count + 1;
found = true;
}
});
if (!found) {
this.states.push({ label: state, count: 1, color: color, selected: false });
}
this.donutChart.updateData(this.states);
},
err => console.error('Observer got an error: ' + err),
() => {
}
);
}
}
@@ -0,0 +1,7 @@
<div class="chart-container">
<canvas id={{elementID}} width="100px" height="100px"></canvas>
<div class="center" fxLayout="column" fxLayoutAlign="center center">
<div class="{{_countClass}}">{{_count}}</div>
<div class="title">{{title}}</div>
</div>
</div>
@@ -0,0 +1,48 @@
.chart-container {
position: relative;
width: 100%;
height: 100%;
}
.center {
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
width: 50%;
height: 50%;
overflow: auto;
margin: auto;
}
.title {
font-size: calc(0.5rem + 0.625vw);
display:none;
}
.count-total {
font-size: calc(1rem + 0.625vw);
}
.large-count-total {
font-size: calc(0.5rem + 0.625vw);
}
@media (max-width: 800px) {
.count-total {
font-size: calc(1.00rem + 0.625vw);
}
}
@media (min-width: 800px) and (max-width: 1000px) {
.title {
font-size: calc(0.35rem + 0.625vw);
}
.count-total {
font-size: calc(1.35rem + 0.625vw);
}
}
@media (min-width:1000px) {
.title {
display: inline;
}
}
@@ -0,0 +1,21 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DonutChartComponent } from './donut-chart.component';
import { HttpClientModule } from '@angular/common/http';
describe('DonutChartComponent', () => {
let component: DonutChartComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [DonutChartComponent],
providers: [DonutChartComponent]
}).compileComponents();
component = TestBed.inject(DonutChartComponent);
}));
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,87 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Chart } from 'chart.js'
import { tooltip } from '../tooltip'
import { FilterService } from 'src/app/services/filter.service';
import { ChartItemState } from '../chart/chart.component';
@Component({
selector: 'app-donut-chart',
templateUrl: './donut-chart.component.html',
styleUrls: ['./donut-chart.component.scss']
})
export class DonutChartComponent {
@Input() elementID: string;
@Input() title: number;
@Output() stateEvent = new EventEmitter<ChartItemState>();;
_count: number;
_countClass = "count-total";
set count(n: number) {
if (n >= 1000) { // use a smaller font
this._countClass = "large-count-total"
}
this._count = n;
}
private canvas: any;
private ctx: any;
private chart: Chart;
private states: ChartItemState[];
constructor(private filterService: FilterService) { }
updateData(states: ChartItemState[]): void {
this.states = states;
// Using object destructuring
for (let i = 0; i < states.length; i++) {
let s = states[i];
this.chart.data.labels[i] = s.label;
this.chart.data.datasets[0].data[i] = s.count;
this.chart.data.datasets[0].backgroundColor[i] = s.color;
}
this.chart.update();
}
removeAllData(withAnimation: boolean): void {
this.chart.data.labels.pop();
this.chart.data.datasets.forEach((dataset) => {
dataset.data = [];
});
this.chart.update(withAnimation);
}
ngAfterViewInit(): void {
this.canvas = document.getElementById(this.elementID);
this.ctx = this.canvas.getContext('2d');
this.chart = new Chart(this.ctx, {
type: 'doughnut',
data: {
datasets: [{
data: [],
backgroundColor: [],
borderWidth: 1
}]
},
options: {
cutoutPercentage: 77,
responsive: true,
onClick: (e) => {
var activePoints = this.chart.getElementsAtEvent(e);
if (activePoints.length > 0) {
const index = activePoints[0]["_index"];
this.stateEvent.emit(this.states[index]);
}
},
legend: {
display: false
},
tooltips: {
// Disable the on-canvas tooltip
enabled: false,
custom: tooltip(),
},
animation: false
}
});
}
}
+62
View File
@@ -0,0 +1,62 @@
// Adapted from https://www.chartjs.org/samples/latest/tooltips/custom-pie.html
export let tooltip: () => (tooltip: any) => void =
function (): (tooltip: any) => void {
return function (tooltip: any): void {
// Tooltip Element
const tooltipEl = document.getElementById('chartjs-tooltip');
// Hide if no tooltip
if (tooltip.opacity === 0) {
tooltipEl.style.opacity = '0';
return;
}
// Set caret Position
tooltipEl.classList.remove('above', 'below', 'no-transform');
if (tooltip.yAlign) {
tooltipEl.classList.add(tooltip.yAlign);
} else {
tooltipEl.classList.add('no-transform');
}
function getBody(bodyItem) {
return bodyItem.lines;
}
// Set Text
if (tooltip.body) {
let titleLines = tooltip.title || [];
const bodyLines = tooltip.body.map(getBody);
let innerHtml = '<thead>';
titleLines.forEach(function (title) {
innerHtml += '<tr><th>' + title + '</th></tr>';
});
innerHtml += '</thead><tbody>';
bodyLines.forEach(function (body, i) {
let colors = tooltip.labelColors[i];
let style = 'background:' + colors.backgroundColor;
style += '; border-color:' + colors.borderColor;
style += '; border-width: 2px';
let span = '<span class="chartjs-tooltip-key" style="' + style + '"></span>';
innerHtml += '<tr><td>' + span + body + '</td></tr>';
});
innerHtml += '</tbody>';
let tableRoot = tooltipEl.querySelector('table');
tableRoot.innerHTML = innerHtml;
}
var position = this._chart.canvas.getBoundingClientRect();
// Display, position, and set styles for font
tooltipEl.style.opacity = '1';
tooltipEl.style.position = 'absolute';
tooltipEl.style.left = position.left + window.pageXOffset + tooltip.caretX + 'px';
tooltipEl.style.top = position.top + window.pageYOffset + tooltip.caretY + 'px';
tooltipEl.style.padding = tooltip.yPadding + 'px ' + tooltip.xPadding + 'px';
tooltipEl.style.pointerEvents = 'none';
}
};