From 10c982c8bcc9549549641ada9c28ebae6b220ee9 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Sun, 5 Mar 2023 16:16:54 +0100 Subject: [PATCH] move dashboard elements and store the order --- pdb-js/src/app/app-routing.module.ts | 2 + pdb-js/src/app/app.module.ts | 9 +- .../customizable-grid.component.html | 121 +++++++++++++ .../customizable-grid.component.spec.ts | 23 +++ .../customizable-grid.component.ts | 24 +++ .../dashboard-page.component.html | 48 +++--- .../dashboard-page.component.ts | 2 +- .../dashboard/dashboard.component.html | 27 ++- .../dashboard/dashboard.component.ts | 161 +++++++++++++----- .../text-widget/text-widget.component.html | 1 + pdb-js/src/app/dashboard.service.ts | 24 ++- pdb-js/src/assets/img/dashboard-outline.svg | 1 + pdb-js/src/styles.scss | 6 + .../pdbui/dashboard/BaseDashboardWidget.java | 12 ++ .../lucares/pdbui/dashboard/Dashboard.java | 10 ++ 15 files changed, 399 insertions(+), 72 deletions(-) create mode 100644 pdb-js/src/app/customizable-grid/customizable-grid.component.html create mode 100644 pdb-js/src/app/customizable-grid/customizable-grid.component.spec.ts create mode 100644 pdb-js/src/app/customizable-grid/customizable-grid.component.ts create mode 100644 pdb-js/src/assets/img/dashboard-outline.svg diff --git a/pdb-js/src/app/app-routing.module.ts b/pdb-js/src/app/app-routing.module.ts index 3b81758..a011b96 100644 --- a/pdb-js/src/app/app-routing.module.ts +++ b/pdb-js/src/app/app-routing.module.ts @@ -6,6 +6,7 @@ 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'; +import { CustomizableGridComponent } from './customizable-grid/customizable-grid.component'; const routes: Routes = [ @@ -14,6 +15,7 @@ const routes: Routes = [ { path: 'dashboard', component: DashboardPageComponent}, { path: 'dashboard/:id', component: DashboardComponent}, { path: 'upload', component: UploadPageComponent }, + { path: 'grid', component: CustomizableGridComponent }, { path: 'help', component: HelpPageComponent }, // { path: '**', component: PageNotFoundComponent } ]; diff --git a/pdb-js/src/app/app.module.ts b/pdb-js/src/app/app.module.ts index 5d5c494..ad24a88 100644 --- a/pdb-js/src/app/app.module.ts +++ b/pdb-js/src/app/app.module.ts @@ -33,12 +33,16 @@ import { DashboardPageComponent } from './dashboard-page/dashboard-page.componen 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 {MatBadgeModule} from '@angular/material/badge'; 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'; import { PlotWidgetComponent } from './dashboard-page/dashboard/plot-widget/plot-widget.component'; import { FullScreenPlotDialogComponent } from './dashboard-page/dashboard/full-screen-plot-dialog/full-screen-plot-dialog.component'; +import { CustomizableGridComponent } from './customizable-grid/customizable-grid.component'; + +import {DragDropModule} from '@angular/cdk/drag-drop'; @NgModule({ declarations: [ @@ -63,14 +67,17 @@ import { FullScreenPlotDialogComponent } from './dashboard-page/dashboard/full-s TextWidgetComponent, AddPlotDialogComponent, PlotWidgetComponent, - FullScreenPlotDialogComponent + FullScreenPlotDialogComponent, + CustomizableGridComponent ], imports: [ BrowserModule, AppRoutingModule, FormsModule, ReactiveFormsModule, + DragDropModule, MatAutocompleteModule, + MatBadgeModule, MatButtonModule, MatCheckboxModule, MatDialogModule, diff --git a/pdb-js/src/app/customizable-grid/customizable-grid.component.html b/pdb-js/src/app/customizable-grid/customizable-grid.component.html new file mode 100644 index 0000000..54126b4 --- /dev/null +++ b/pdb-js/src/app/customizable-grid/customizable-grid.component.html @@ -0,0 +1,121 @@ + + + +
+

To do

+ +
+
{{item}}
+
+
+ +
+

Done

+ +
+
{{item}}
+
+
+ + diff --git a/pdb-js/src/app/customizable-grid/customizable-grid.component.spec.ts b/pdb-js/src/app/customizable-grid/customizable-grid.component.spec.ts new file mode 100644 index 0000000..8e0cda2 --- /dev/null +++ b/pdb-js/src/app/customizable-grid/customizable-grid.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CustomizableGridComponent } from './customizable-grid.component'; + +describe('CustomizableGridComponent', () => { + let component: CustomizableGridComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CustomizableGridComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CustomizableGridComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/pdb-js/src/app/customizable-grid/customizable-grid.component.ts b/pdb-js/src/app/customizable-grid/customizable-grid.component.ts new file mode 100644 index 0000000..3639956 --- /dev/null +++ b/pdb-js/src/app/customizable-grid/customizable-grid.component.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core'; +import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag-drop'; + +@Component({ + selector: 'app-customizable-grid', + templateUrl: './customizable-grid.component.html' +}) +export class CustomizableGridComponent { + todo = ['Get to work', 'Pick up groceries', 'Go home', 'Fall asleep']; + done = ['Get up', 'Brush teeth', 'Take a shower', 'Check e-mail', 'Walk dog']; + + drop(event: CdkDragDrop) { + if (event.previousContainer === event.container) { + moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + } else { + transferArrayItem( + event.previousContainer.data, + event.container.data, + event.previousIndex, + event.currentIndex, + ); + } + } +} diff --git a/pdb-js/src/app/dashboard-page/dashboard-page.component.html b/pdb-js/src/app/dashboard-page/dashboard-page.component.html index 9517a7f..cdd06ae 100644 --- a/pdb-js/src/app/dashboard-page/dashboard-page.component.html +++ b/pdb-js/src/app/dashboard-page/dashboard-page.component.html @@ -1,27 +1,28 @@
@@ -39,6 +40,11 @@
+ + + + + diff --git a/pdb-js/src/app/dashboard-page/dashboard-page.component.ts b/pdb-js/src/app/dashboard-page/dashboard-page.component.ts index 65e0165..05e78fd 100644 --- a/pdb-js/src/app/dashboard-page/dashboard-page.component.ts +++ b/pdb-js/src/app/dashboard-page/dashboard-page.component.ts @@ -10,7 +10,7 @@ import { NewDashboardComponent } from './new-dashboard/new-dashboard.component'; }) export class DashboardPageComponent implements OnInit { - displayedColumns: string[] = ['name', 'description']; + displayedColumns: string[] = [/*'icon',*/ 'name', 'description']; dataSource: Dashboard[] = []; loading = true; error = ""; diff --git a/pdb-js/src/app/dashboard-page/dashboard/dashboard.component.html b/pdb-js/src/app/dashboard-page/dashboard/dashboard.component.html index bc36cd7..446c272 100644 --- a/pdb-js/src/app/dashboard-page/dashboard/dashboard.component.html +++ b/pdb-js/src/app/dashboard-page/dashboard/dashboard.component.html @@ -22,6 +22,10 @@ .content { padding: 0.5em; } + .dashboard-column { + display: flex; + flex-direction: column; + }
@@ -40,6 +44,25 @@

{{dashboard.name}}

{{dashboard.description}}

- - +
+ +
+
+ + +
+
+
diff --git a/pdb-js/src/app/dashboard-page/dashboard/dashboard.component.ts b/pdb-js/src/app/dashboard-page/dashboard/dashboard.component.ts index 872c093..d55558b 100644 --- a/pdb-js/src/app/dashboard-page/dashboard/dashboard.component.ts +++ b/pdb-js/src/app/dashboard-page/dashboard/dashboard.component.ts @@ -1,9 +1,10 @@ +import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; import { HttpErrorResponse } from '@angular/common/http'; -import { Component, OnInit } from '@angular/core'; +import { Component, ElementRef, 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, PlotSize, PlotWidget, PlotWidgetRenderData, TextWidget } from 'src/app/dashboard.service'; +import { BaseWidget, Dashboard, DashboardService, PlotSize, PlotWidget, PlotWidgetRenderData, TextWidget } from 'src/app/dashboard.service'; import { PlotConfig, PlotRequest, PlotResponse, PlotService, RenderOptions } from 'src/app/plot.service'; import { AddPlotDialogComponent } from './add-plot-dialog/add-plot-dialog.component'; import { AddTextDialogComponent } from './add-text-dialog/add-text-dialog.component'; @@ -25,29 +26,31 @@ export class DashboardComponent implements OnInit { private service: DashboardService, private dialog: MatDialog, private snackBar: MatSnackBar, - private plotService: PlotService) {} + private plotService: PlotService, + private element: ElementRef) {} ngOnInit(): void { this.service.getDashboard(this.route.snapshot.paramMap.get("id")).subscribe({ 'next':(dashboard: Dashboard) => { - this.dashboard = dashboard; + this.dashboard = dashboard; + this.repairArrangement(); - dashboard.plots.forEach(p => { - this.plotWidgetRenderData.push(new PlotWidgetRenderData(p)); - }); + dashboard.plots.forEach(p => { + this.plotWidgetRenderData.push(new PlotWidgetRenderData(p)); + }); - this.loadImages(0, this.plotWidgetRenderData); - }, - 'error': (error: HttpErrorResponse) =>{ - if (error.status == 404) { - this.error = "Not Found"; - }else if (error.status == 504) { // gateway timeout - this.error = "Server Unreachable"; - }else{ - this.error = "Failed to load dashboard"; + this.loadImages(0, this.plotWidgetRenderData); + }, + 'error': (error: HttpErrorResponse) =>{ + if (error.status == 404) { + this.error = "Not Found"; + }else if (error.status == 504) { // gateway timeout + this.error = "Server Unreachable"; + }else{ + this.error = "Failed to load dashboard"; + } } - } - }); + }); } loadImages(index: number, plotWidgetQueue: PlotWidgetRenderData[]) { @@ -84,35 +87,63 @@ export class DashboardComponent implements OnInit { 'fullScreen': new RenderOptions(fullHeight,fullWidth, false, true) } ); - return request; - } + return request; + } - height(size: PlotSize): number{ - switch (size) { - case 'SMALL': - return 300; - case 'MEDIUM': - return 400; - case 'LARGE': - return 600; + height(size: PlotSize): number{ + switch (size) { + case 'SMALL': + return 300; + case 'MEDIUM': + return 400; + case 'LARGE': + return 600; + } } - } - width(size: PlotSize): number{ - switch (size) { - case 'SMALL': - return 400; - case 'MEDIUM': - return 600; - case 'LARGE': - return 900; + width(size: PlotSize): number{ + switch (size) { + case 'SMALL': + return 400; + case 'MEDIUM': + return 600; + case 'LARGE': + return 900; + } + } + + private repairArrangement(){ + const arrangement = this.dashboard!.arrangement || []; + if (arrangement.length == 0){ + arrangement[0] = []; + } + this.dashboard?.texts.forEach(t => { + if (!this.arrangmentContainsId(arrangement, t.id)){ + arrangement[0].push(t.id); + } + }); + this.dashboard?.plots.forEach(t => { + if (!this.arrangmentContainsId(arrangement, t.id)){ + arrangement[0].push(t.id); + } + }); + this.dashboard!.arrangement = arrangement; + } + + private arrangmentContainsId(arrangement: string[][], id: string): boolean{ + + for ( let i = 0; i < arrangement.length; i++){ + if (arrangement[i].includes(id)) { + return true; + } + } + return false; } - } addText() { this.dialog.open(AddTextDialogComponent,{ width: '600px' }).afterClosed().subscribe((text: string) => { - this.dashboard!.texts.push(new TextWidget('MEDIUM', text)); + this.dashboard!.texts.push(new TextWidget(crypto.randomUUID(),'MEDIUM', text)); }); } @@ -121,11 +152,28 @@ export class DashboardComponent implements OnInit { width: 'calc(100% - 1em)', height: 'calc(100% - 1em)' }).afterClosed().subscribe((config: PlotConfig) => { - this.dashboard!.plots.push(new PlotWidget('MEDIUM', config)); + this.dashboard!.plots.push(new PlotWidget(crypto.randomUUID(), 'MEDIUM', config)); }); } save() { + const arrangement = []; + const dashboardColumns = (this.element.nativeElement).querySelectorAll('.dashboard-column'); + for(let i =0; i < dashboardColumns.length; i++){ + const ids = []; + const column = dashboardColumns.item(i); + for(let c = 0; c column.children.item(c) + const id = element!.getAttribute("widget-id"); + if (id !== null) { + ids.push(id); + } + } + arrangement.push(ids); + } + + this.dashboard!.arrangement = arrangement; + this.service.saveDashboard(this.dashboard!).subscribe({ 'complete': () => { this.snackBar.open("saved dashboard","", { @@ -135,4 +183,37 @@ export class DashboardComponent implements OnInit { } }); } + + isTextWidget(id: string): boolean { + return this.getTextWidget(id) !== undefined; + } + + isPlotWidget(id: string): boolean { + return this.dashboard?.plots.find( x => x.id == id) !== undefined; + } + + getTextWidget(id: string): TextWidget | undefined { + return this.dashboard?.texts.find( x => x.id == id); + } + + getPlotWidget(id: string): PlotWidgetRenderData | undefined { + return this.plotWidgetRenderData.find( x => x.widget.id == id); + } + + drop(event: CdkDragDrop) { + if (event.previousContainer === event.container) { + moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + } else { + transferArrayItem( + event.previousContainer.data, + event.container.data, + event.previousIndex, + event.currentIndex, + ); + } + } + + s(a: any){ + return JSON.stringify(a); + } } diff --git a/pdb-js/src/app/dashboard-page/dashboard/text-widget/text-widget.component.html b/pdb-js/src/app/dashboard-page/dashboard/text-widget/text-widget.component.html index 1cbcf0b..0bfee8a 100644 --- a/pdb-js/src/app/dashboard-page/dashboard/text-widget/text-widget.component.html +++ b/pdb-js/src/app/dashboard-page/dashboard/text-widget/text-widget.component.html @@ -2,6 +2,7 @@ :host { display: block; margin-top: 1em; + background-color: aliceblue; }

{{line}}

diff --git a/pdb-js/src/app/dashboard.service.ts b/pdb-js/src/app/dashboard.service.ts index cef4c3e..3eb0881 100644 --- a/pdb-js/src/app/dashboard.service.ts +++ b/pdb-js/src/app/dashboard.service.ts @@ -32,26 +32,36 @@ export class DashboardCreationData{ constructor(public name: string, public description: string){} } +export interface HasId { + id: string; +} + export class Dashboard{ - constructor(public id: string, public name: string, public description: string, public texts: TextWidget[], public plots: PlotWidget[]){} + constructor( + public id: string, + public name: string, + public description: string, + public texts: TextWidget[], + public plots: PlotWidget[], + public arrangement: string[][]){} } export class DashboardList{ constructor(public dashboards: [Dashboard]){} } -export abstract class BaseWidget { - constructor(public type: PlotType, public size: PlotSize) {} +export abstract class BaseWidget implements HasId { + constructor(public id: string, public type: PlotType, public size: PlotSize) {} } export class TextWidget extends BaseWidget { - constructor(override size: PlotSize, public text: string) { - super('TEXT', size); + constructor(override id: string, override size: PlotSize, public text: string) { + super(id, 'TEXT', size); } } export class PlotWidget extends BaseWidget { - constructor(override size: PlotSize, public config: PlotConfig) { - super('PLOT', size); + constructor(override id: string, override size: PlotSize, public config: PlotConfig) { + super(id, 'PLOT', size); } } diff --git a/pdb-js/src/assets/img/dashboard-outline.svg b/pdb-js/src/assets/img/dashboard-outline.svg new file mode 100644 index 0000000..073fc3e --- /dev/null +++ b/pdb-js/src/assets/img/dashboard-outline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pdb-js/src/styles.scss b/pdb-js/src/styles.scss index 3946345..9ea1147 100644 --- a/pdb-js/src/styles.scss +++ b/pdb-js/src/styles.scss @@ -141,6 +141,12 @@ mat-option.mat-option.mat-active { background-color: #ccc; } + +a ,a:visited { + color: blue; + text-decoration: none; +} + a.external-link:after { background: transparent url('/assets/img/external-link.svg') no-repeat center bottom; background-size: 0.8em; diff --git a/pdb-ui/src/main/java/org/lucares/pdbui/dashboard/BaseDashboardWidget.java b/pdb-ui/src/main/java/org/lucares/pdbui/dashboard/BaseDashboardWidget.java index 87732f8..1496d56 100644 --- a/pdb-ui/src/main/java/org/lucares/pdbui/dashboard/BaseDashboardWidget.java +++ b/pdb-ui/src/main/java/org/lucares/pdbui/dashboard/BaseDashboardWidget.java @@ -1,7 +1,11 @@ package org.lucares.pdbui.dashboard; +import java.util.UUID; + public abstract class BaseDashboardWidget { + private String id = UUID.randomUUID().toString(); + private DashboardWidgetType type; private DashboardWidgetSize size; @@ -15,6 +19,14 @@ public abstract class BaseDashboardWidget { this.size = size; } + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + public DashboardWidgetType getType() { return type; } diff --git a/pdb-ui/src/main/java/org/lucares/pdbui/dashboard/Dashboard.java b/pdb-ui/src/main/java/org/lucares/pdbui/dashboard/Dashboard.java index 6c1cb5a..559deac 100644 --- a/pdb-ui/src/main/java/org/lucares/pdbui/dashboard/Dashboard.java +++ b/pdb-ui/src/main/java/org/lucares/pdbui/dashboard/Dashboard.java @@ -24,6 +24,8 @@ public class Dashboard { private List plots = new ArrayList<>(); + private List> arrangement = new ArrayList<>(); + public Dashboard() { super(); @@ -77,4 +79,12 @@ public class Dashboard { public List getPlots() { return plots; } + + public List> getArrangement() { + return arrangement; + } + + public void setArrangement(final List> arrangement) { + this.arrangement = arrangement; + } }
Name