adding dashbord

This commit is contained in:
2023-02-26 08:18:53 +01:00
parent eb9904a30b
commit a2945d2d9b
44 changed files with 928 additions and 8 deletions

View File

@@ -4,11 +4,15 @@ import { VisualizationPageComponent } from './visualization-page/visualization-p
import { MainPageComponent } from './main-page/main-page.component';
import { UploadPageComponent } from './upload-page/upload-page.component';
import { HelpPageComponent } from './help-page/help-page.component';
import { DashboardPageComponent } from './dashboard-page/dashboard-page.component';
import { DashboardComponent } from './dashboard-page/dashboard/dashboard.component';
const routes: Routes = [
{ path: '', component: MainPageComponent},
{ path: 'vis', component: VisualizationPageComponent },
{ path: 'dashboard', component: DashboardPageComponent},
{ path: 'dashboard/:id', component: DashboardComponent},
{ path: 'upload', component: UploadPageComponent },
{ path: 'help', component: HelpPageComponent },
// { path: '**', component: PageNotFoundComponent }

View File

@@ -1,8 +1,9 @@
<div id="main-toolbar">
<a href="/" title="open main page"><img src="assets/img/home.svg" class="icon-small" aria-hidden="false" aria-label="go to main page" /></a>
<a href="/vis" title="open visualization page"><img src="assets/img/scatter-chart2.svg" class="icon-small" aria-hidden="false" aria-label="go to visualization page" /></a>
<a href="/" title="Home"><img src="assets/img/home.svg" class="icon-small" aria-hidden="false" aria-label="go to home page" /></a>
<a href="/vis" title="Visualization"><img src="assets/img/scatter-chart2.svg" class="icon-small" aria-hidden="false" aria-label="go to visualization page" /></a>
<a href="/dashboard" title="Dashboards"><img src="assets/img/dashboard-line.svg" class="icon-small" aria-hidden="false" aria-label="go to dashboard page" /></a>
</div>
<router-outlet></router-outlet>

View File

@@ -29,6 +29,14 @@ import { PlotDetailsComponent } from './plot-details/plot-details.component';
import { PlotViewComponent } from './plot-view/plot-view.component';
import { GalleryViewComponent, GalleryItemView, GalleryFilterView } from './gallery-view/gallery-view.component';
import { ImageToggleComponent } from './image-toggle/image-toggle.component';
import { DashboardPageComponent } from './dashboard-page/dashboard-page.component';
import { NewDashboardComponent } from './dashboard-page/new-dashboard/new-dashboard.component';
import { MatDialogModule, MAT_DIALOG_DEFAULT_OPTIONS } from '@angular/material/dialog';
import {MatTableModule} from '@angular/material/table';
import { DashboardComponent } from './dashboard-page/dashboard/dashboard.component';
import { AddTextDialogComponent } from './dashboard-page/dashboard/add-text-dialog/add-text-dialog.component';
import { TextWidgetComponent } from './dashboard-page/dashboard/text-widget/text-widget.component';
import { AddPlotDialogComponent } from './dashboard-page/dashboard/add-plot-dialog/add-plot-dialog.component';
@NgModule({
declarations: [
@@ -45,7 +53,13 @@ import { ImageToggleComponent } from './image-toggle/image-toggle.component';
GalleryViewComponent,
GalleryItemView,
GalleryFilterView,
ImageToggleComponent
ImageToggleComponent,
DashboardPageComponent,
NewDashboardComponent,
DashboardComponent,
AddTextDialogComponent,
TextWidgetComponent,
AddPlotDialogComponent
],
imports: [
BrowserModule,
@@ -55,6 +69,7 @@ import { ImageToggleComponent } from './image-toggle/image-toggle.component';
MatAutocompleteModule,
MatButtonModule,
MatCheckboxModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatRadioModule,
@@ -62,11 +77,12 @@ import { ImageToggleComponent } from './image-toggle/image-toggle.component';
MatProgressSpinnerModule,
MatSelectModule,
MatSnackBarModule,
MatTableModule,
MatTooltipModule,
BrowserAnimationsModule,
HttpClientModule
],
providers: [],
providers: [{provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: true}}],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@@ -0,0 +1,26 @@
<div class="toolbar">
<button mat-stroked-button (click)="createNewDashboard()">New</button>
</div>
<div *ngIf="loading">
loading
</div>
<div *ngIf="!loading">
<table mat-table [dataSource]="dataSource" >
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let element"><a [routerLink]="['/dashboard', element.id]">{{element.name}}</a></td>
</ng-container>
<!-- Description Column -->
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>Description</th>
<td mat-cell *matCellDef="let element">{{element.description}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>

View File

@@ -0,0 +1,8 @@
:host {
height: calc(100% - 29px);
position: absolute;
width: 100%;
padding: 0.5em;
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DashboardPageComponent } from './dashboard-page.component';
describe('DashboardPageComponent', () => {
let component: DashboardPageComponent;
let fixture: ComponentFixture<DashboardPageComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DashboardPageComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(DashboardPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,49 @@
import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Dashboard, DashboardCreationData, DashboardList, DashboardService } from '../dashboard.service';
import { NewDashboardComponent } from './new-dashboard/new-dashboard.component';
@Component({
selector: 'app-dashboard-page',
templateUrl: './dashboard-page.component.html',
styleUrls: ['./dashboard-page.component.scss']
})
export class DashboardPageComponent implements OnInit {
displayedColumns: string[] = ['name', 'description'];
dataSource: Dashboard[] = [];
loading = true;
constructor(public dialog: MatDialog, private dashboardService: DashboardService){
}
ngOnInit(): void {
this.refreshTable();
}
refreshTable(){
this.loading = true;
this.dashboardService.getDashboards().subscribe((dashboardList: DashboardList) => {
this.dataSource = dashboardList.dashboards;
this.loading = false;
});
}
createNewDashboard() {
const dialogRef = this.dialog.open(NewDashboardComponent, {
data: {name: "", description: ""},
hasBackdrop: true
});
dialogRef.afterClosed().subscribe((result: DashboardCreationData) => {
console.log('The dialog was closed with result ', JSON.stringify(result));
this.dashboardService.createDashboard(result).subscribe(result => {
console.log(result);
this.refreshTable();
});
});
}
}

View File

@@ -0,0 +1,22 @@
<style>
:host {
display: flex;
flex-direction: column;
}
pdb-visualization-page {
position: relative;
width: 100%;
height: calc(100% - 150px);
flex-grow: 1;
}
div[mat-dialog-actions] {
}
</style>
<h1 mat-dialog-title>Add Plot</h1>
<pdb-visualization-page mat-dialog-content></pdb-visualization-page>
<div mat-dialog-actions align="end">
<button mat-button mat-dialog-close>Cancel</button>
<button mat-button mat-dialog-close (click)="onSaveClick()" cdkFocusInitial>Save</button>
</div>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AddPlotDialogComponent } from './add-plot-dialog.component';
describe('AddPlotDialogComponent', () => {
let component: AddPlotDialogComponent;
let fixture: ComponentFixture<AddPlotDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ AddPlotDialogComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(AddPlotDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,18 @@
import { Component } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'app-add-plot-dialog',
templateUrl: './add-plot-dialog.component.html',
styleUrls: ['./add-plot-dialog.component.scss']
})
export class AddPlotDialogComponent {
constructor(public dialogRef: MatDialogRef<string>){
}
onSaveClick(): void {
this.dialogRef.close("todo");
}
}

View File

@@ -0,0 +1,11 @@
<h1 mat-dialog-title>Add Text</h1>
<div mat-dialog-content>
<mat-form-field appearance="fill">
<mat-label>Text</mat-label>
<textarea matInput [(ngModel)]="text"></textarea>
</mat-form-field>
</div>
<div mat-dialog-actions align="end">
<button mat-button mat-dialog-close>Cancel</button>
<button mat-button mat-dialog-close (click)="onSaveClick()" cdkFocusInitial>Save</button>
</div>

View File

@@ -0,0 +1,3 @@
textarea {
height: 5em;
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AddTextDialogComponent } from './add-text-dialog.component';
describe('AddTextDialogComponent', () => {
let component: AddTextDialogComponent;
let fixture: ComponentFixture<AddTextDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ AddTextDialogComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(AddTextDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,18 @@
import { Component } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'app-add-text-dialog',
templateUrl: './add-text-dialog.component.html',
styleUrls: ['./add-text-dialog.component.scss']
})
export class AddTextDialogComponent {
text = "";
constructor(public dialogRef: MatDialogRef<string>){
}
onSaveClick(): void {
this.dialogRef.close(this.text);
}
}

View File

@@ -0,0 +1,14 @@
<div *ngIf="dashboard === undefined" class="spinner">
<mat-spinner></mat-spinner>
</div>
<div *ngIf="dashboard !== undefined" class="content">
<div class="toolbar">
<button mat-stroked-button (click)="addText()">Add Text</button>
<button mat-stroked-button (click)="addPlot()">Add Plot</button>
<button mat-stroked-button (click)="save()">Save Dashboard</button>
</div>
<h1>{{dashboard.name}}</h1>
<p>{{dashboard.description}}</p>
<app-text-widget *ngFor="let textWidget of dashboard.texts" [text]="textWidget.text"></app-text-widget>
</div>

View File

@@ -0,0 +1,15 @@
:host {
width: 100%;
height: 100%;
}
.spinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.content {
padding: 0.5em;
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DashboardComponent } from './dashboard.component';
describe('DashboardComponent', () => {
let component: DashboardComponent;
let fixture: ComponentFixture<DashboardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DashboardComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(DashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,53 @@
import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar';
import { ActivatedRoute } from '@angular/router';
import { Dashboard, DashboardService, TextWidget } from 'src/app/dashboard.service';
import { AddPlotDialogComponent } from './add-plot-dialog/add-plot-dialog.component';
import { AddTextDialogComponent } from './add-text-dialog/add-text-dialog.component';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {
dashboard?: Dashboard = undefined;
constructor(private route: ActivatedRoute, private service: DashboardService, private dialog: MatDialog, private snackBar: MatSnackBar) {}
ngOnInit(): void {
this.service.getDashboard(<string>this.route.snapshot.paramMap.get("id")).subscribe((dashboard: Dashboard) => {
this.dashboard = dashboard;
});
}
addText() {
this.dialog.open(AddTextDialogComponent,{
width: '600px'
}).afterClosed().subscribe((text: string) => {
this.dashboard!.texts.push(new TextWidget('MEDIUM', text));
});
}
addPlot() {
this.dialog.open(AddPlotDialogComponent,{
width: 'calc(100% - 1em)',
//height: 'calc(100% - 1em)'
}).afterClosed().subscribe((text: string) => {
//this.dashboard!.texts.push(new TextWidget('MEDIUM', text));
});
}
save() {
this.service.saveDashboard(this.dashboard!).subscribe({
'complete': () => {
this.snackBar.open("saved dashboard","", {
duration: 5000,
verticalPosition: 'top'
});
}
});
}
}

View File

@@ -0,0 +1,7 @@
<style>
:host {
display: block;
margin-top: 1em;
}
</style>
<p *ngFor="let line of lines()">{{line}}</p>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TextWidgetComponent } from './text-widget.component';
describe('TextWidgetComponent', () => {
let component: TextWidgetComponent;
let fixture: ComponentFixture<TextWidgetComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ TextWidgetComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(TextWidgetComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,14 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-text-widget',
templateUrl: './text-widget.component.html'
})
export class TextWidgetComponent {
@Input()
text = "";
lines(): string[]{
return this.text.split(/\r?\n/);
}
}

View File

@@ -0,0 +1,15 @@
<h1 mat-dialog-title>Create a new dashboard</h1>
<div mat-dialog-content>
<mat-form-field appearance="fill">
<mat-label>Name</mat-label>
<input matInput [(ngModel)]="data.name">
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Description</mat-label>
<textarea matInput [(ngModel)]="data.description"></textarea>
</mat-form-field>
</div>
<div mat-dialog-actions align="end">
<button mat-button mat-dialog-close>Cancel</button>
<button mat-button mat-dialog-close (click)="onSaveClick()" cdkFocusInitial>Save</button>
</div>

View File

@@ -0,0 +1,4 @@
div[mat-dialog-content] {
overflow: hidden;
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NewDashboardComponent } from './new-dashboard.component';
describe('NewDashboardComponent', () => {
let component: NewDashboardComponent;
let fixture: ComponentFixture<NewDashboardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ NewDashboardComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(NewDashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,18 @@
import { Component, Inject } from '@angular/core';
import {MatDialog, MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import { DashboardCreationData } from 'src/app/dashboard.service';
@Component({
selector: 'app-new-dashboard',
templateUrl: './new-dashboard.component.html',
styleUrls: ['./new-dashboard.component.scss']
})
export class NewDashboardComponent {
constructor(public dialogRef: MatDialogRef<NewDashboardComponent>,
@Inject(MAT_DIALOG_DATA) public data: DashboardCreationData,){
}
onSaveClick(): void {
this.dialogRef.close(this.data);
}
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { DashboardService } from './dashboard.service';
describe('DashboardService', () => {
let service: DashboardService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(DashboardService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,55 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DashboardService {
constructor(private http: HttpClient) { }
createDashboard(data: DashboardCreationData): Observable<Dashboard>{
return this.http.post<Dashboard>('//'+window.location.hostname+':'+window.location.port+'/api/dashboards/', data);
}
getDashboards(): Observable<DashboardList>{
return this.http.get<DashboardList>('//'+window.location.hostname+':'+window.location.port+'/api/dashboards/');
}
getDashboard(id: string): Observable<Dashboard>{
return this.http.get<Dashboard>('//'+window.location.hostname+':'+window.location.port+'/api/dashboards/'+id);
}
saveDashboard(dashboard: Dashboard): Observable<Dashboard>{
return this.http.put<Dashboard>('//'+window.location.hostname+':'+window.location.port+'/api/dashboards/'+dashboard.id, dashboard);
}
}
export class DashboardCreationData{
constructor(public name: string, public description: string){}
}
export class Dashboard{
constructor(public id: string, public name: string, public description: string, public texts: [TextWidget]){}
}
export class DashboardList{
constructor(public dashboards: [Dashboard]){}
}
export abstract class BaseWidget {
constructor(public type: 'TEXT'|'PLOT', public size: 'SMALL'|'MEDIUM'|'LARGE') {}
}
export class TextWidget extends BaseWidget {
constructor(override size: 'SMALL'|'MEDIUM'|'LARGE', public text: string) {
super('TEXT', size);
}
}
export class PlotWidget extends BaseWidget {
constructor(override size: 'SMALL'|'MEDIUM'|'LARGE') {
super('PLOT', size);
}
}

View File

@@ -12,7 +12,8 @@
</style>
<div id="main-page-links">
<a href="/vis" class="button-large" title="open visualization page"><img src="assets/img/scatter-chart2.svg" class="icon-large" aria-hidden="false" aria-label="go to visualization page" /></a>
<a [routerLink]="['/vis']" class="button-large" title="Visualization"><img src="assets/img/scatter-chart2.svg" class="icon-large" aria-hidden="false" aria-label="go to visualization page" /></a>
<a [routerLink]="['/dashboard']" class="button-large" title="Dashboards"><img src="assets/img/dashboard-line.svg" class="icon-large" aria-hidden="false" aria-label="go to dashboard page" /></a>
<!--a href="/upload" class="button-large" title="upload data"><img src="assets/img/upload.svg" class="icon-large" aria-hidden="false" aria-label="upload data" /></a-->
</div>

View File

@@ -70,6 +70,9 @@ export class VisualizationPageComponent implements OnInit {
ngOnInit() {
const that = this;
(<any>window).initDatePicker();
this.plotTypes = this.plotService.getPlotTypes();
this.selectedPlotType.push(this.plotTypes[0]);
@@ -86,7 +89,7 @@ export class VisualizationPageComponent implements OnInit {
that.splitBy = that.tagFields.find(val => filterDefaults.splitBy == val.name);
});
}
changePlotType(selectedPlotTypes: Array<PlotType>) {
const compatiblePlotTypes = this.plotTypes.filter(pt => pt.compatible(selectedPlotTypes));
this.plotTypes.forEach(pt => pt.active=false);