Compare commits
10 Commits
b56b4c231e
...
1ca4f18e3d
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ca4f18e3d | |||
| add30a5ee9 | |||
| e42c00cc08 | |||
| c9758adbef | |||
| dafe6813ed | |||
| bc0ae23de5 | |||
| 02c748c286 | |||
| 44aed2883d | |||
| 96ed788793 | |||
| b5028e03be |
@@ -4,7 +4,7 @@ import org.apache.tools.ant.filters.ReplaceTokens
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'eclipse'
|
||||
id 'com.github.ben-manes.versions' version "0.45.0" // check for dependency updates run: gradlew dependenyUpdates
|
||||
id 'com.github.ben-manes.versions' version "0.46.0" // check for dependency updates run: gradlew dependenyUpdates
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ ext {
|
||||
javaVersion=17
|
||||
|
||||
version_log4j2= '2.19.0' // keep in sync with spring-boot-starter-log4j2
|
||||
version_spring = '3.0.2'
|
||||
version_spring = '3.0.4'
|
||||
version_junit = '5.9.2'
|
||||
version_junit_platform = '1.9.2'
|
||||
version_nodejs = '16.17.1' // keep in sync with npm
|
||||
@@ -136,5 +136,5 @@ subprojects {
|
||||
}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = '8.0.1'
|
||||
gradleVersion = '8.0.2'
|
||||
}
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
2
liquibase.properties
Normal file
2
liquibase.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
liquibase.hub.mode=off
|
||||
2
pdb-js/.vscode/settings.json
vendored
Normal file
2
pdb-js/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
@@ -33,6 +33,8 @@ 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 {MatGridListModule} from '@angular/material/grid-list';
|
||||
import {MatCardModule} from '@angular/material/card';
|
||||
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';
|
||||
@@ -83,9 +85,11 @@ import { FocusDirective } from './focus.directive';
|
||||
MatAutocompleteModule,
|
||||
MatBadgeModule,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatCheckboxModule,
|
||||
MatDialogModule,
|
||||
MatFormFieldModule,
|
||||
MatGridListModule,
|
||||
MatInputModule,
|
||||
MatRadioModule,
|
||||
MatProgressBarModule,
|
||||
|
||||
@@ -1,121 +1,110 @@
|
||||
<style>
|
||||
.customizable-grid {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.draggable {
|
||||
flex-shrink: 0;
|
||||
padding: 0.5rem;
|
||||
border: solid 1px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.draggable-small {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
|
||||
background-color: rgb(158, 240, 189);
|
||||
}
|
||||
|
||||
.draggable-middle {
|
||||
width: 600px;
|
||||
height: 300px;
|
||||
background-color: darksalmon;
|
||||
}
|
||||
|
||||
.example-container {
|
||||
width: 400px;
|
||||
max-width: 100%;
|
||||
margin: 0 25px 25px 0;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
.example-list {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.example-list {
|
||||
border: solid 1px #ccc;
|
||||
min-height: 60px;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
.example-list li {
|
||||
display: table-cell;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.example-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
min-width: 600px;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.example-box {
|
||||
padding: 20px 10px;
|
||||
border-bottom: solid 1px #ccc;
|
||||
width: var(--box-width);
|
||||
height: var(--box-height);
|
||||
border: solid 1px #ccc;
|
||||
font-size: 30pt;
|
||||
font-weight: bold;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
cursor: grab;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
cursor: move;
|
||||
background: white;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
|
||||
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),
|
||||
0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.example-box--wide {
|
||||
width: 400px;
|
||||
height: var(--box-height);
|
||||
}
|
||||
|
||||
.example-box:active {
|
||||
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
|
||||
0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.cdk-drop-list {
|
||||
display: flex;
|
||||
padding-right: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.cdk-drag-preview {
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
|
||||
0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
||||
0 3px 14px 2px rgba(0, 0, 0, 0.12);
|
||||
0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.cdk-drag-placeholder {
|
||||
opacity: 0;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.cdk-drag-animating {
|
||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.example-box:last-child {
|
||||
border: none;
|
||||
.cdk-drop-list-dragging {
|
||||
cursor: grabbing !important;
|
||||
}
|
||||
|
||||
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
|
||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||
button {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
<!--
|
||||
<div class="customizable-grid">
|
||||
<div class="draggable draggable-small"></div>
|
||||
<div class="draggable draggable-middle"></div>
|
||||
<div class="draggable draggable-small"></div>
|
||||
<div class="draggable draggable-middle"></div>
|
||||
</div>
|
||||
-->
|
||||
<h1>Drag&Drop with a flex-wrap</h1>
|
||||
|
||||
<div class="example-container">
|
||||
<h2>To do</h2>
|
||||
<button (click)="add()">Add</button>
|
||||
<button (click)="shuffle()">Shuffle</button>
|
||||
|
||||
<br />
|
||||
|
||||
<ul class="example-list">
|
||||
<li *ngFor="let item of items">{{ item }}</li>
|
||||
</ul>
|
||||
|
||||
<div
|
||||
class="example-container"
|
||||
cdkDropListGroup
|
||||
[ngStyle]="{ '--box-width': boxWidth, '--box-height': boxHeight }"
|
||||
>
|
||||
<div
|
||||
cdkDropList
|
||||
#todoList="cdkDropList"
|
||||
[cdkDropListData]="todo"
|
||||
[cdkDropListConnectedTo]="[doneList]"
|
||||
class="example-list"
|
||||
(cdkDropListDropped)="drop($event)">
|
||||
<div class="example-box" *ngFor="let item of todo" cdkDrag>{{item}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="example-container">
|
||||
<h2>Done</h2>
|
||||
|
||||
(cdkDropListEntered)="onDropListEntered($event)"
|
||||
(cdkDropListDropped)="onDropListDropped()"
|
||||
></div>
|
||||
<div
|
||||
cdkDropList
|
||||
#doneList="cdkDropList"
|
||||
[cdkDropListData]="done"
|
||||
[cdkDropListConnectedTo]="[todoList]"
|
||||
class="example-list"
|
||||
(cdkDropListDropped)="drop($event)">
|
||||
<div class="example-box" *ngFor="let item of done" cdkDrag>{{item}}</div>
|
||||
(cdkDropListEntered)="onDropListEntered($event)"
|
||||
(cdkDropListDropped)="onDropListDropped()"
|
||||
*ngFor="let item of items"
|
||||
>
|
||||
<div cdkDrag class="example-box" [ngClass]="{'example-box--wide': item%2==1}">{{ item }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -1,24 +1,119 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag-drop';
|
||||
import { CdkDragEnter, CdkDropList, moveItemInArray, DragRef} from '@angular/cdk/drag-drop';
|
||||
import { AfterViewInit } from '@angular/core';
|
||||
import { ViewChild } from '@angular/core';
|
||||
|
||||
|
||||
@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'];
|
||||
export class CustomizableGridComponent implements AfterViewInit {
|
||||
@ViewChild(CdkDropList) placeholder!: CdkDropList;
|
||||
|
||||
drop(event: CdkDragDrop<string[]>) {
|
||||
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,
|
||||
);
|
||||
private target: CdkDropList|null = null;
|
||||
private targetIndex: number = 0;
|
||||
private source: CdkDropList|null = null;
|
||||
private sourceIndex: number = 0;
|
||||
private dragRef: DragRef|null = null;
|
||||
|
||||
items: Array<number> = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
|
||||
boxWidth = '200px';
|
||||
boxHeight = '200px';
|
||||
|
||||
ngAfterViewInit() {
|
||||
const placeholderElement = this.placeholder.element.nativeElement;
|
||||
|
||||
placeholderElement.style.display = 'none';
|
||||
placeholderElement.parentNode!.removeChild(placeholderElement);
|
||||
}
|
||||
|
||||
add() {
|
||||
this.items.push(this.items.length + 1);
|
||||
}
|
||||
|
||||
shuffle() {
|
||||
this.items.sort(function () {
|
||||
return 0.5 - Math.random();
|
||||
});
|
||||
}
|
||||
|
||||
onDropListDropped() {
|
||||
if (!this.target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const placeholderElement: HTMLElement =
|
||||
this.placeholder.element.nativeElement;
|
||||
const placeholderParentElement: HTMLElement =
|
||||
placeholderElement.parentElement!;
|
||||
|
||||
placeholderElement.style.display = 'none';
|
||||
|
||||
placeholderParentElement.removeChild(placeholderElement);
|
||||
placeholderParentElement.appendChild(placeholderElement);
|
||||
placeholderParentElement.insertBefore(
|
||||
this.source!.element.nativeElement,
|
||||
placeholderParentElement.children[this.sourceIndex]
|
||||
);
|
||||
|
||||
if (this.placeholder._dropListRef.isDragging() && this.dragRef != null) {
|
||||
this.placeholder._dropListRef.exit(this.dragRef);
|
||||
}
|
||||
|
||||
this.target = null;
|
||||
this.source = null;
|
||||
this.dragRef = null;
|
||||
|
||||
if (this.sourceIndex !== this.targetIndex) {
|
||||
moveItemInArray(this.items, this.sourceIndex, this.targetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
onDropListEntered({ item, container }: CdkDragEnter) {
|
||||
if (container == this.placeholder) {
|
||||
return;
|
||||
}
|
||||
|
||||
const placeholderElement: HTMLElement =
|
||||
this.placeholder.element.nativeElement;
|
||||
const sourceElement: HTMLElement = item.dropContainer.element.nativeElement;
|
||||
const dropElement: HTMLElement = container.element.nativeElement;
|
||||
const dragIndex: number = Array.prototype.indexOf.call(
|
||||
dropElement.parentElement!.children,
|
||||
this.source ? placeholderElement : sourceElement
|
||||
);
|
||||
const dropIndex: number = Array.prototype.indexOf.call(
|
||||
dropElement.parentElement!.children,
|
||||
dropElement
|
||||
);
|
||||
|
||||
if (!this.source) {
|
||||
this.sourceIndex = dragIndex;
|
||||
this.source = item.dropContainer;
|
||||
|
||||
placeholderElement.style.width = this.boxWidth + 'px';
|
||||
placeholderElement.style.height = this.boxHeight + 40 + 'px';
|
||||
|
||||
sourceElement.parentElement!.removeChild(sourceElement);
|
||||
}
|
||||
|
||||
this.targetIndex = dropIndex;
|
||||
this.target = container;
|
||||
this.dragRef = item._dragRef;
|
||||
|
||||
placeholderElement.style.display = '';
|
||||
|
||||
dropElement.parentElement!.insertBefore(
|
||||
placeholderElement,
|
||||
dropIndex > dragIndex ? dropElement.nextSibling : dropElement
|
||||
);
|
||||
|
||||
this.placeholder._dropListRef.enter(
|
||||
item._dragRef,
|
||||
item.element.nativeElement.offsetLeft,
|
||||
item.element.nativeElement.offsetTop
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
<style>
|
||||
:host {
|
||||
/*
|
||||
height: calc(100% - 29px);
|
||||
*/
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
|
||||
.center {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@@ -23,12 +17,15 @@
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
.no-break {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div *ngIf="loading" class="center">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
<div *ngIf="error" class="center is-error center-content ">
|
||||
<div *ngIf="error" class="center is-error center-content">
|
||||
{{error}}
|
||||
</div>
|
||||
<div *ngIf="!loading && !error">
|
||||
@@ -55,11 +52,14 @@
|
||||
<th mat-header-cell *matHeaderCellDef>Description</th>
|
||||
<td mat-cell *matCellDef="let element">{{element.description}}</td>
|
||||
</ng-container>
|
||||
<!-- Delete Column -->
|
||||
<ng-container matColumnDef="delete">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button mat-icon-button (click)="delete(element)">
|
||||
<!-- actions Column -->
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
||||
<td mat-cell *matCellDef="let element" class="no-break">
|
||||
<button mat-icon-button (click)="edit(element)" aria-label="edit dashboard" title="edit dashboard">
|
||||
<img src="assets/img/edit-outline.svg" class="icon-small" title="delete" />
|
||||
</button>
|
||||
<button mat-icon-button (click)="delete(element)" aria-label="delete dashboard" title="delete dashboard">
|
||||
<img src="assets/img/recycle-bin-line.svg" class="icon-small" title="delete" />
|
||||
</button>
|
||||
</td>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||
import { Dashboard, DashboardCreationData, DashboardList, DashboardService } from '../dashboard.service';
|
||||
import { NewDashboardComponent } from './new-dashboard/new-dashboard.component';
|
||||
@@ -11,14 +12,15 @@ import { NewDashboardComponent } from './new-dashboard/new-dashboard.component';
|
||||
})
|
||||
export class DashboardPageComponent implements OnInit {
|
||||
|
||||
displayedColumns: string[] = [/*'icon',*/ 'name', 'description','delete'];
|
||||
displayedColumns: string[] = [/*'icon',*/ 'name', 'description','actions'];
|
||||
dataSource: Dashboard[] = [];
|
||||
loading = true;
|
||||
error = "";
|
||||
|
||||
constructor(public dialog: MatDialog, private dashboardService: DashboardService){
|
||||
|
||||
}
|
||||
constructor(
|
||||
public dialog: MatDialog,
|
||||
private dashboardService: DashboardService,
|
||||
private snackBar: MatSnackBar){}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.refreshTable();
|
||||
@@ -44,39 +46,57 @@ export class DashboardPageComponent implements OnInit {
|
||||
|
||||
createNewDashboard() {
|
||||
const dialogRef = this.dialog.open(NewDashboardComponent, {
|
||||
data: {name: "", description: ""},
|
||||
hasBackdrop: true
|
||||
data: {name: "", description: ""}
|
||||
});
|
||||
|
||||
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.dashboardService.createDashboard(result).subscribe(result => this.refreshTable());
|
||||
});
|
||||
}
|
||||
|
||||
this.refreshTable();
|
||||
});
|
||||
edit(dashboard: Dashboard) {
|
||||
const dialogRef = this.dialog.open(NewDashboardComponent, {
|
||||
data: {name: dashboard.name, description: dashboard.description},
|
||||
hasBackdrop: true
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((result?: DashboardCreationData) => {
|
||||
|
||||
if (result) {
|
||||
dashboard.name = result.name;
|
||||
dashboard.description = result.description;
|
||||
this.dashboardService.saveDashboard(dashboard).subscribe({
|
||||
'error': () => {
|
||||
this.snackBar.open("server made a boo boo", "", {
|
||||
duration:5000
|
||||
})
|
||||
},
|
||||
complete: () => {
|
||||
this.refreshTable()
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
delete(dashboard: Dashboard){
|
||||
|
||||
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
|
||||
data: {title: "", text: "Delete dashboard '"+dashboard.name+"'?", btnOkLabel: "Delete", btnCancelLabel: "Cancel"},
|
||||
hasBackdrop: true
|
||||
data: {title: "", text: "Delete dashboard '"+dashboard.name+"'?", btnOkLabel: "Delete", btnCancelLabel: "Cancel"}
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((result: boolean) => {
|
||||
if (result === true) {
|
||||
this.dashboardService.deleteDashboard(dashboard.id).subscribe({
|
||||
'error': (error) => {
|
||||
|
||||
this.snackBar.open("failed to delete dashboard","", {
|
||||
duration: 5000,
|
||||
verticalPosition: 'top'
|
||||
});
|
||||
},
|
||||
'complete': () => this.refreshTable()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
max-height: unset;
|
||||
}
|
||||
</style>
|
||||
<h1 mat-dialog-title>Add Plot</h1>
|
||||
<h1 mat-dialog-title>{{data.title}}</h1>
|
||||
|
||||
<pdb-visualization-page mat-dialog-content #plot></pdb-visualization-page>
|
||||
<pdb-visualization-page mat-dialog-content #plot [defaultConfig]="data.config" [galleryEnabled]="false"></pdb-visualization-page>
|
||||
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-button mat-dialog-close>Cancel</button>
|
||||
<button mat-button mat-dialog-close >Cancel</button>
|
||||
<button class="save-button" mat-button mat-dialog-close (click)="onSaveClick()">Save</button>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { Component, ElementRef, ViewChild } from '@angular/core';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { AfterViewInit, Component, ElementRef, Inject, ViewChild } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { PlotConfig } from 'src/app/plot.service';
|
||||
import { VisualizationPageComponent } from 'src/app/visualization-page/visualization-page.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-plot-dialog',
|
||||
templateUrl: './add-plot-dialog.component.html',
|
||||
styleUrls: ['./add-plot-dialog.component.scss']
|
||||
templateUrl: './add-plot-dialog.component.html'
|
||||
})
|
||||
export class AddPlotDialogComponent {
|
||||
|
||||
@ViewChild("plot") plotElement! :VisualizationPageComponent;
|
||||
|
||||
constructor(public dialogRef: MatDialogRef<string>){
|
||||
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<PlotConfig | undefined>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: {config: PlotConfig, title: string}
|
||||
){
|
||||
}
|
||||
|
||||
onSaveClick(): void {
|
||||
|
||||
@@ -18,7 +18,6 @@ export class AddTextDialogComponent {
|
||||
this.dialogRef.close(undefined);
|
||||
}
|
||||
|
||||
|
||||
onSaveClick(): void {
|
||||
this.dialogRef.close(this.text);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
:host {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
.center {
|
||||
position: absolute;
|
||||
@@ -26,19 +25,19 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.editable .editable-hovered {
|
||||
visibility: hidden;
|
||||
}
|
||||
.editable:hover .editable-hovered {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.handle {
|
||||
display: block;
|
||||
height: 1.5em;
|
||||
visibility: hidden;
|
||||
width: fit-content;
|
||||
}
|
||||
[cdkDrag] {
|
||||
position: relative;
|
||||
}
|
||||
[cdkDrag]:hover .handle, .cdk-drop-list-dragging .handle {
|
||||
background-color: #f3f3f3;
|
||||
border-radius: 3px;
|
||||
cursor: grab;
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -53,7 +52,7 @@
|
||||
<div class="toolbar">
|
||||
<button mat-button (click)="addText()">Add Text</button>
|
||||
<button mat-button (click)="addPlot()">Add Plot</button>
|
||||
<button class="save-button" mat-button (click)="save()">Save</button>
|
||||
<button class="save-button" mat-button (click)="save()" [disabled]="!isDirty()">Save</button>
|
||||
</div>
|
||||
<div class="editable">
|
||||
<h1>{{dashboard.name}}<button mat-icon-button (click)="editNameAndDescription()" class="editable-hovered"><img src="/assets/img/edit-outline.svg"/></button></h1>
|
||||
@@ -61,7 +60,6 @@
|
||||
</div>
|
||||
|
||||
<div cdkDropListGroup>
|
||||
<!-- All lists in here will be connected. -->
|
||||
<div
|
||||
cdkDropList
|
||||
class="dashboard-column"
|
||||
@@ -72,13 +70,13 @@
|
||||
cdkDrag
|
||||
*ngFor="let id of column"
|
||||
[attr.widget-id]="id">
|
||||
<div cdkDragHandle class="handle"></div>
|
||||
<div cdkDragHandle class="handle"><img src="/assets/img/drag_handle.svg" class="icon-small"/></div>
|
||||
<app-text-widget
|
||||
*ngIf="isTextWidget(id)"
|
||||
[data]="getTextWidget(id)!"></app-text-widget>
|
||||
[data]="getTextWidget(id)!" (deleted)="delete($event)"></app-text-widget>
|
||||
<app-plot-widget
|
||||
*ngIf="isPlotWidget(id)"
|
||||
[data]="getPlotWidget(id)!"></app-plot-widget>
|
||||
[data]="getPlotWidget(id)!" (deleted)="delete($event)"></app-plot-widget>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,8 +4,8 @@ import { Component, ElementRef, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { BaseWidget, Dashboard, DashboardCreationData, DashboardService, PlotSize, PlotWidget, PlotWidgetRenderData, TextWidget } from 'src/app/dashboard.service';
|
||||
import { PlotConfig, PlotRequest, PlotResponse, PlotService, RenderOptions } from 'src/app/plot.service';
|
||||
import { Dashboard, DashboardCreationData, DashboardService, PlotWidget, PlotWidgetRenderData, TextWidget } from 'src/app/dashboard.service';
|
||||
import { PlotConfig, PlotResponse, PlotService } from 'src/app/plot.service';
|
||||
import { NewDashboardComponent } from '../new-dashboard/new-dashboard.component';
|
||||
import { AddPlotDialogComponent } from './add-plot-dialog/add-plot-dialog.component';
|
||||
import { AddTextDialogComponent } from './add-text-dialog/add-text-dialog.component';
|
||||
@@ -18,6 +18,8 @@ export class DashboardComponent implements OnInit {
|
||||
|
||||
dashboard?: Dashboard = undefined;
|
||||
|
||||
pristineDashboardJSON?: string = undefined;
|
||||
|
||||
error = "";
|
||||
|
||||
plotWidgetRenderData: PlotWidgetRenderData[] = [];
|
||||
@@ -34,6 +36,7 @@ export class DashboardComponent implements OnInit {
|
||||
this.service.getDashboard(<string>this.route.snapshot.paramMap.get("id")).subscribe({
|
||||
'next':(dashboard: Dashboard) => {
|
||||
this.dashboard = dashboard;
|
||||
this.pristineDashboardJSON = JSON.stringify(dashboard);
|
||||
this.repairArrangement();
|
||||
|
||||
dashboard.plots.forEach(p => {
|
||||
@@ -54,61 +57,24 @@ export class DashboardComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
isDirty() {
|
||||
return this.pristineDashboardJSON !== JSON.stringify(this.dashboard);
|
||||
}
|
||||
|
||||
loadImages(index: number, plotWidgetQueue: PlotWidgetRenderData[]) {
|
||||
|
||||
if (index < plotWidgetQueue.length){
|
||||
const plot = plotWidgetQueue[index];
|
||||
const request = this.createPlotRequest(plot.widget)
|
||||
this.plotService.sendPlotRequest(request).subscribe({
|
||||
next: (response: PlotResponse)=> {
|
||||
plot.plotResponse= response;
|
||||
},
|
||||
error: (error:any)=> {},
|
||||
complete: () => {
|
||||
this.loadImages(index +1 , plotWidgetQueue);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
createPlotRequest(plotWidget: PlotWidget): PlotRequest {
|
||||
|
||||
const height = this.height(plotWidget.size);
|
||||
const width = this.width(plotWidget.size);
|
||||
|
||||
const fullWidth = window.innerWidth-30;
|
||||
const fullHeight = window.innerHeight-30;
|
||||
|
||||
const request = new PlotRequest(
|
||||
(<any>window).submitterId+crypto.randomUUID(),
|
||||
plotWidget.config,
|
||||
{
|
||||
'main': new RenderOptions(height,width, false, true),
|
||||
'fullScreen': new RenderOptions(fullHeight,fullWidth, false, true)
|
||||
}
|
||||
);
|
||||
return request;
|
||||
}
|
||||
|
||||
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;
|
||||
const request = PlotWidget.createPlotRequest(plot.widget);
|
||||
this.plotService.sendPlotRequest(request).subscribe({
|
||||
next: (response: PlotResponse)=> {
|
||||
plot.plotResponse= response;
|
||||
},
|
||||
error: (error:any)=> {},
|
||||
complete: () => {
|
||||
this.loadImages(index +1 , plotWidgetQueue);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +119,7 @@ export class DashboardComponent implements OnInit {
|
||||
|
||||
addPlot() {
|
||||
this.dialog.open(AddPlotDialogComponent,{
|
||||
data: {title: "Add Plot"},
|
||||
width: 'calc(100% - 1em)',
|
||||
height: 'calc(100% - 1em)'
|
||||
}).afterClosed().subscribe((config: PlotConfig | "") => {
|
||||
@@ -201,6 +168,7 @@ export class DashboardComponent implements OnInit {
|
||||
duration: 5000,
|
||||
verticalPosition: 'top'
|
||||
});
|
||||
this.pristineDashboardJSON = JSON.stringify(this.dashboard);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -218,7 +186,6 @@ export class DashboardComponent implements OnInit {
|
||||
this.dashboard!.description = result.description;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
isTextWidget(id: string): boolean {
|
||||
@@ -249,4 +216,10 @@ export class DashboardComponent implements OnInit {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
delete(dashboardId: string) {
|
||||
this.dashboard!.arrangement[0] = this.dashboard!.arrangement[0].filter(a => a != dashboardId);
|
||||
this.dashboard!.plots = this.dashboard!.plots.filter(p => p.id != dashboardId);
|
||||
this.dashboard!.texts = this.dashboard!.texts.filter(t => t.id != dashboardId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
float: left;
|
||||
}
|
||||
.dashboard-card {
|
||||
border: solid 1px red;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -19,8 +19,31 @@
|
||||
img {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
.top-left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.dashboard-card .editable-hovered {
|
||||
visibility: hidden;
|
||||
}
|
||||
.dashboard-card:hover .editable-hovered {
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
||||
<div class="dashboard-card" [ngClass]="{'size-medium' : true}">
|
||||
<div class="editable-hovered top-right">
|
||||
<button mat-icon-button (click)="edit()" ><img src="/assets/img/edit-outline.svg"/></button>
|
||||
<button mat-icon-button (click)="delete()"><img src="/assets/img/recycle-bin-line.svg"/></button>
|
||||
</div>
|
||||
<mat-spinner *ngIf="!hasRender('main') && !isError"></mat-spinner>
|
||||
<img *ngIf="hasRender('main')" [src]="getImageUrl('main')" (click)="showFullScreenImage()" />
|
||||
<div *ngIf="isError">
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ConfirmationDialogComponent } from 'src/app/confirmation-dialog/confirmation-dialog.component';
|
||||
import { PlotWidget, PlotWidgetRenderData } from 'src/app/dashboard.service';
|
||||
import { PlotViewComponent } from 'src/app/plot-view/plot-view.component';
|
||||
import { PlotRequest, PlotResponse, PlotService, RenderOptions } from 'src/app/plot.service';
|
||||
import { PlotConfig, PlotResponse, PlotService } from 'src/app/plot.service';
|
||||
import { AddPlotDialogComponent } from '../add-plot-dialog/add-plot-dialog.component';
|
||||
import { FullScreenPlotDialogComponent } from '../full-screen-plot-dialog/full-screen-plot-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-plot-widget',
|
||||
templateUrl: './plot-widget.component.html'
|
||||
})
|
||||
export class PlotWidgetComponent implements AfterViewInit {
|
||||
export class PlotWidgetComponent {
|
||||
@Input("data")
|
||||
data!: PlotWidgetRenderData;
|
||||
|
||||
@@ -19,29 +21,65 @@ export class PlotWidgetComponent implements AfterViewInit {
|
||||
|
||||
@ViewChild("plotView") plotView!: PlotViewComponent;
|
||||
|
||||
constructor(private dialog: MatDialog, ){}
|
||||
@Output()
|
||||
deleted : EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
constructor(private dialog: MatDialog, private service: PlotService){}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
|
||||
}
|
||||
|
||||
hasRender(name: string): boolean{
|
||||
const hasRender = this.data !== undefined && this.data.plotResponse !== undefined && this.data.plotResponse?.rendered[name] !== undefined;
|
||||
return hasRender;
|
||||
return this.data !== undefined && this.data.plotResponse !== undefined && this.data.plotResponse?.rendered[name] !== undefined;
|
||||
}
|
||||
|
||||
getImageUrl(name: string ): string | undefined {
|
||||
return this.data?.plotResponse?.rendered[name];
|
||||
}
|
||||
|
||||
showFullScreenImage(){
|
||||
|
||||
showFullScreenImage() {
|
||||
this.dialog.open(FullScreenPlotDialogComponent,{
|
||||
width: 'calc(100% - 15px)',
|
||||
height: 'calc(100% - 15px)',
|
||||
'data': {'imageUrl': this.getImageUrl('fullScreen')}
|
||||
data: {'imageUrl': this.getImageUrl('fullScreen')}
|
||||
}).afterClosed().subscribe(() => {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.dialog
|
||||
.open(ConfirmationDialogComponent, {
|
||||
data: {title: "", text: "Delete plot?", btnOkLabel: "Delete", btnCancelLabel: "Cancel"}
|
||||
})
|
||||
.afterClosed()
|
||||
.subscribe((result: boolean) => {
|
||||
if (result === true) {
|
||||
this.deleted.emit(this.data.widget.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
edit() {
|
||||
this.dialog.open(AddPlotDialogComponent, {
|
||||
data: {config: this.data.widget.config, title:"Edit Plot"},
|
||||
width: 'calc(100% - 15px)',
|
||||
height: 'calc(100% - 15px)',
|
||||
}).afterClosed().subscribe((config?: PlotConfig) => {
|
||||
if (config !== undefined && config.query.length > 0) {
|
||||
this.data.widget.config = config;
|
||||
|
||||
this.isError = false;
|
||||
this.data.plotResponse = undefined;
|
||||
|
||||
const request = PlotWidget.createPlotRequest(this.data.widget);
|
||||
this.service.sendPlotRequest(request).subscribe({
|
||||
next: (response: PlotResponse)=> {
|
||||
this.data.plotResponse = response;
|
||||
},
|
||||
error: (error:any)=> {
|
||||
this.isError = true;
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
}
|
||||
.text-widget {
|
||||
position: relative;
|
||||
padding: 1em 0;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
.text-widget:hover {
|
||||
/*outline: solid 1px black;/**/
|
||||
@@ -25,6 +25,9 @@
|
||||
}
|
||||
</style>
|
||||
<div class="text-widget">
|
||||
<button mat-icon-button (click)="edit()" class="editable-hovered"><img src="/assets/img/edit-outline.svg"/></button>
|
||||
<div class="editable-hovered">
|
||||
<button mat-icon-button (click)="edit()"><img src="/assets/img/edit-outline.svg"/></button>
|
||||
<button mat-icon-button (click)="delete()"><img src="/assets/img/recycle-bin-line.svg"/></button>
|
||||
</div>
|
||||
<p *ngFor="let line of lines()">{{line}}</p>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ConfirmationDialogComponent } from 'src/app/confirmation-dialog/confirmation-dialog.component';
|
||||
import { TextWidget } from 'src/app/dashboard.service';
|
||||
import { AddTextDialogComponent } from '../add-text-dialog/add-text-dialog.component';
|
||||
|
||||
@@ -11,11 +12,26 @@ export class TextWidgetComponent {
|
||||
@Input()
|
||||
data! : TextWidget;
|
||||
|
||||
@Output()
|
||||
deleted : EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
constructor(private dialog: MatDialog){}
|
||||
|
||||
lines(): string[]{
|
||||
return typeof this.data.text == 'string' ? this.data.text.split(/\r?\n/) : [];
|
||||
}
|
||||
delete() {
|
||||
this.dialog
|
||||
.open(ConfirmationDialogComponent, {
|
||||
data: {title: "", text: "Delete text?", btnOkLabel: "Delete", btnCancelLabel: "Cancel"}
|
||||
})
|
||||
.afterClosed()
|
||||
.subscribe((result: boolean) => {
|
||||
if (result === true) {
|
||||
this.deleted.emit(this.data.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
edit() {
|
||||
this.dialog.open(AddTextDialogComponent,{
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
<style>
|
||||
div[mat-dialog-content] {
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
<h1 mat-dialog-title>Create a new dashboard</h1>
|
||||
<div mat-dialog-content>
|
||||
<mat-form-field class="pdb-form-full-width">
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
div[mat-dialog-content] {
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
|
||||
import {MatDialog, MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
|
||||
import {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']
|
||||
templateUrl: './new-dashboard.component.html'
|
||||
})
|
||||
export class NewDashboardComponent implements OnInit {
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { PlotConfig, PlotResponse } from './plot.service';
|
||||
import { PlotConfig, PlotRequest, PlotResponse, RenderOptions } from './plot.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -67,6 +67,47 @@ export class PlotWidget extends BaseWidget {
|
||||
constructor(override id: string, override size: 'SMALL'|'MEDIUM'|'LARGE', public config: PlotConfig) {
|
||||
super(id, 'PLOT', size);
|
||||
}
|
||||
|
||||
public static createPlotRequest(widget: PlotWidget): PlotRequest {
|
||||
|
||||
const height = this.height(widget.size);
|
||||
const width = this.width(widget.size);
|
||||
|
||||
const fullWidth = window.innerWidth-30;
|
||||
const fullHeight = window.innerHeight-30;
|
||||
|
||||
const request = new PlotRequest(
|
||||
(<any>window).submitterId+crypto.randomUUID(),
|
||||
widget.config,
|
||||
{
|
||||
'main': new RenderOptions(height,width, false, true),
|
||||
'fullScreen': new RenderOptions(fullHeight,fullWidth, false, true)
|
||||
}
|
||||
);
|
||||
return request;
|
||||
}
|
||||
|
||||
|
||||
static height(size: PlotSize): number{
|
||||
switch (size) {
|
||||
case 'SMALL':
|
||||
return 300;
|
||||
case 'MEDIUM':
|
||||
return 400;
|
||||
case 'LARGE':
|
||||
return 600;
|
||||
}
|
||||
}
|
||||
static width(size: PlotSize): number{
|
||||
switch (size) {
|
||||
case 'SMALL':
|
||||
return 400;
|
||||
case 'MEDIUM':
|
||||
return 600;
|
||||
case 'LARGE':
|
||||
return 900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type PlotSize = 'SMALL'|'MEDIUM'|'LARGE';
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
<mat-form-field class="pdb-form-number"
|
||||
*ngIf="limitBy !== 'NO_LIMIT'">
|
||||
<mat-label><!--empty label needed for layout reasons--></mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="number"
|
||||
|
||||
@@ -93,20 +93,15 @@ export class PlotService {
|
||||
|
||||
|
||||
export class PlotType {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string
|
||||
active: boolean;
|
||||
xAxis: DataType;
|
||||
yAxis: DataType;
|
||||
|
||||
constructor(id: string, name: string, icon: string, active: boolean, xAxis: DataType, yAxis: DataType) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
this.active = active;
|
||||
this.xAxis = xAxis;
|
||||
this.yAxis = yAxis;
|
||||
constructor(
|
||||
public id: string,
|
||||
public name: string,
|
||||
public icon: string,
|
||||
public active: boolean,
|
||||
public xAxis: DataType,
|
||||
public yAxis: DataType) {
|
||||
|
||||
}
|
||||
|
||||
compatible(others: Array<PlotType>) : boolean {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, Input, ViewChild } from '@angular/core';
|
||||
import {UntypedFormControl} from '@angular/forms';
|
||||
import { Component, OnInit, Input, ViewChild, AfterViewInit } from '@angular/core';
|
||||
import {FormControl, UntypedFormControl} from '@angular/forms';
|
||||
import {Observable} from 'rxjs';
|
||||
import {startWith, map} from 'rxjs/operators';
|
||||
import {MatAutocompleteTrigger } from '@angular/material/autocomplete';
|
||||
@@ -12,41 +12,48 @@ import { PlotService, PlotType, AutocompleteResult, Suggestion, ResultMode } fro
|
||||
})
|
||||
export class QueryAutocompleteComponent implements OnInit {
|
||||
|
||||
queryField = new UntypedFormControl('');
|
||||
queryField = new FormControl<Suggestion>(new Suggestion("","",0));
|
||||
|
||||
suggestions = new UntypedFormControl();
|
||||
|
||||
filteredSuggestions!: Observable<Suggestion[]>;
|
||||
|
||||
query : string = "";
|
||||
|
||||
suggestionFetcherEnabled = true;
|
||||
|
||||
@ViewChild(MatAutocompleteTrigger)
|
||||
autocomplete!: MatAutocompleteTrigger;
|
||||
|
||||
|
||||
constructor(private plotService: PlotService) {}
|
||||
constructor(private plotService: PlotService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const that = this;
|
||||
this.query = "";
|
||||
this.queryField.valueChanges.subscribe(function(value){
|
||||
if (typeof value == "string") {
|
||||
that.query = value;
|
||||
}else{
|
||||
that.query = value.newQuery;
|
||||
this.queryField.valueChanges.subscribe((value) =>{
|
||||
if (value != null) {
|
||||
if (typeof value == "string") {
|
||||
this.query = value;
|
||||
}else{
|
||||
this.query = value.newQuery;
|
||||
|
||||
const el : HTMLInputElement = <HTMLInputElement>document.getElementById('query-autocomplete-input');
|
||||
el.selectionStart=value.newCaretPosition;
|
||||
el.selectionEnd=value.newCaretPosition;
|
||||
}
|
||||
|
||||
var el : HTMLInputElement = <HTMLInputElement>document.getElementById('query-autocomplete-input');
|
||||
el.selectionStart=value.newCaretPosition;
|
||||
el.selectionEnd=value.newCaretPosition;
|
||||
|
||||
that.fetchSuggestions(value.newCaretPosition);
|
||||
if (this.suggestionFetcherEnabled) {
|
||||
this.fetchSuggestions(value.newCaretPosition);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.filteredSuggestions = this.suggestions.valueChanges.pipe(
|
||||
map(value => value)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
onKey(event: any) {
|
||||
//console.log(event);
|
||||
if (event.key == "ArrowDown" || event.key == "ArrowUp"){
|
||||
@@ -61,23 +68,21 @@ export class QueryAutocompleteComponent implements OnInit {
|
||||
const that = this;
|
||||
const query = typeof this.queryField.value == "string"
|
||||
? this.queryField.value
|
||||
: this.queryField.value.newQuery;
|
||||
: this.queryField.value!.newQuery;
|
||||
|
||||
this.plotService
|
||||
.autocomplete(query, caretIndex, ResultMode.CUT_AT_DOT)
|
||||
.subscribe(
|
||||
(data: AutocompleteResult) => {// success path
|
||||
//console.log(JSON.stringify(data.proposals));
|
||||
.subscribe({
|
||||
next: (data: AutocompleteResult) => {
|
||||
that.suggestions.setValue(data.proposals);
|
||||
|
||||
that.autocomplete.openPanel();
|
||||
},
|
||||
(error:any) => console.log(error)
|
||||
error: (error:any) => console.log(error)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
displaySuggestion(suggestion?: Suggestion): string {
|
||||
//console.log("suggestion: "+JSON.stringify(suggestion));
|
||||
return suggestion ? suggestion.newQuery : '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<pdb-y-axis-definition #y1AxisDefinitionComponent yIndex="1"></pdb-y-axis-definition>
|
||||
<pdb-y-axis-definition #y2AxisDefinitionComponent yIndex="2" [hidden]="!y2AxisAvailable"></pdb-y-axis-definition>
|
||||
|
||||
<mat-checkbox [(ngModel)]="enableGallery">Gallery</mat-checkbox>
|
||||
<mat-checkbox *ngIf="galleryEnabled" [(ngModel)]="enableGallery">Gallery</mat-checkbox>
|
||||
|
||||
<mat-form-field *ngIf="enableGallery" class="pdb-form-full-width">
|
||||
<mat-label>Split By:</mat-label>
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
grid:
|
||||
"query-box query-box date-box" auto
|
||||
"filters results results" 1fr
|
||||
/ 25.5em 3fr 24em;
|
||||
/ 25.5em 3fr 23.5em;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
@media screen and (max-width: 900px) {
|
||||
#visualization {
|
||||
display: grid;
|
||||
margin: 0;
|
||||
@@ -27,7 +27,7 @@
|
||||
grid:
|
||||
"query-box" auto
|
||||
"date-box" auto
|
||||
"filters" auto
|
||||
"filters" min-content
|
||||
"results" 1fr
|
||||
/ 1fr;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { PlotService, PlotType, PlotRequest, PlotResponse, TagField, FilterDefaults, DataType, YAxisDefinition, AxesTypes, PlotConfig, RenderOptions, RenderOptionsMap } from '../plot.service';
|
||||
import { UntypedFormControl, Validators } from '@angular/forms';
|
||||
import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { PlotService, PlotType, PlotRequest, TagField, FilterDefaults, DataType, AxesTypes, PlotConfig, RenderOptions, RenderOptionsMap, Suggestion } from '../plot.service';
|
||||
import { UntypedFormControl, } from '@angular/forms';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { LimitByComponent } from '../limit-by/limit-by.component';
|
||||
import { YAxisDefinitionComponent } from '../y-axis-definition/y-axis-definition.component';
|
||||
import { QueryAutocompleteComponent } from '../query-autocomplete/query-autocomplete.component';
|
||||
import { PlotViewComponent, SelectionRange, DateAnchor, LoadingEvent } from '../plot-view/plot-view.component';
|
||||
import { PlotViewComponent, LoadingEvent } from '../plot-view/plot-view.component';
|
||||
import { GalleryViewComponent } from '../gallery-view/gallery-view.component';
|
||||
import * as moment from 'moment';
|
||||
import { WidgetDimensions } from '../dashboard.service';
|
||||
|
||||
@Component({
|
||||
@@ -15,14 +14,20 @@ import { WidgetDimensions } from '../dashboard.service';
|
||||
templateUrl: './visualization-page.component.html',
|
||||
styleUrls: ['./visualization-page.component.scss']
|
||||
})
|
||||
export class VisualizationPageComponent implements OnInit {
|
||||
export class VisualizationPageComponent implements OnInit, AfterViewInit {
|
||||
|
||||
readonly DATE_PATTERN = "YYYY-MM-DD HH:mm:ss"; // for moment-JS
|
||||
|
||||
@Input()
|
||||
defaultConfig?: PlotConfig;
|
||||
|
||||
@Input()
|
||||
galleryEnabled = true;
|
||||
|
||||
dateRange = new UntypedFormControl('2019-10-05 00:00:00 - 2019-10-11 23:59:59');
|
||||
|
||||
selectedPlotType = new Array<PlotType>();
|
||||
plotTypes: Array<any> = [];
|
||||
plotTypes: PlotType[] = [];
|
||||
|
||||
tagFields: Array<TagField> = new Array<TagField>();
|
||||
|
||||
@@ -68,33 +73,68 @@ export class VisualizationPageComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const that = this;
|
||||
|
||||
(<any>window).initDatePicker();
|
||||
|
||||
this.plotTypes = this.plotService.getPlotTypes();
|
||||
this.selectedPlotType.push(this.plotTypes[0]);
|
||||
|
||||
that.plotService.getFilterDefaults().subscribe(function(filterDefaults: FilterDefaults) {
|
||||
this.plotService.getFilterDefaults().subscribe((filterDefaults: FilterDefaults) => {
|
||||
|
||||
filterDefaults.fields.forEach(function(name:string) {
|
||||
that.tagFields.push(new TagField(name));
|
||||
filterDefaults.fields.forEach((name:string) => {
|
||||
this.tagFields.push(new TagField(name));
|
||||
},
|
||||
(error: any) => {
|
||||
that.showError(error.error.message);
|
||||
this.showError(error.error.message);
|
||||
});
|
||||
|
||||
that.groupBy = that.tagFields.filter(val => filterDefaults.groupBy.includes(val.name));
|
||||
that.splitBy = that.tagFields.find(val => filterDefaults.splitBy == val.name);
|
||||
});
|
||||
const groupByDefaults = this.defaultConfig ? this.defaultConfig.groupBy : filterDefaults.groupBy;
|
||||
this.groupBy = this.tagFields.filter(val => groupByDefaults.includes(val.name));
|
||||
this.splitBy = this.tagFields.find(val => filterDefaults.splitBy == val.name);
|
||||
|
||||
if (this.defaultConfig) {
|
||||
this.plot();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
if (this.defaultConfig) {
|
||||
const c = this.defaultConfig;
|
||||
this.query.suggestionFetcherEnabled = false;
|
||||
this.query.queryField.setValue(new Suggestion(c.query, c.query, c.query.length));
|
||||
this.query.suggestionFetcherEnabled = true;
|
||||
|
||||
this.selectedPlotType = this.plotTypes.filter(pt => c.aggregates.includes(pt.id));
|
||||
this.changePlotType(this.selectedPlotType);
|
||||
this.updateDateRange(c.dateRange, false);
|
||||
this.limitbycomponent.limitBy = c.limitBy;
|
||||
this.limitbycomponent.limit = c.limit;
|
||||
this.y1AxisDefinitionComponent.yAxisScale = c.y1.axisScale;
|
||||
this.y1AxisDefinitionComponent.minYValue = c.y1.rangeMin;
|
||||
this.y1AxisDefinitionComponent.maxYValue = c.y1.rangeMax;
|
||||
this.y1AxisDefinitionComponent.yAxisUnit = c.y1.rangeUnit;
|
||||
|
||||
if (c.y2) {
|
||||
this.y2AxisDefinitionComponent.yAxisScale = c.y2.axisScale;
|
||||
this.y2AxisDefinitionComponent.minYValue = c.y2.rangeMin;
|
||||
this.y2AxisDefinitionComponent.maxYValue = c.y2.rangeMax;
|
||||
this.y2AxisDefinitionComponent.yAxisUnit = c.y2.rangeUnit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loading(event: LoadingEvent) {
|
||||
this.plotJobActive = event.loading;
|
||||
}
|
||||
|
||||
updateDateRange(newDateRange: string) {
|
||||
updateDateRange(newDateRange: string, updatePlot=true) {
|
||||
(<HTMLInputElement>document.getElementById("search-date-range")).value = newDateRange;
|
||||
this.plot();
|
||||
if (updatePlot){
|
||||
this.plot();
|
||||
}
|
||||
}
|
||||
|
||||
changePlotType(selectedPlotTypes: Array<PlotType>) {
|
||||
|
||||
21
pdb-js/src/assets/img/drag_handle.svg
Normal file
21
pdb-js/src/assets/img/drag_handle.svg
Normal file
@@ -0,0 +1,21 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 32 32">
|
||||
<g transform="translate(0.5,0.5)" style="fill:black">
|
||||
<circle cx="4" cy="9" r="2"/>
|
||||
<circle cx="12" cy="9" r="2"/>
|
||||
<circle cx="20" cy="9" r="2"/>
|
||||
<circle cx="28" cy="9" r="2"/>
|
||||
|
||||
<circle cx="4" cy="15" r="2"/>
|
||||
<circle cx="12" cy="15" r="2"/>
|
||||
<circle cx="20" cy="15" r="2"/>
|
||||
<circle cx="28" cy="15" r="2"/>
|
||||
|
||||
<circle cx="4" cy="21" r="2"/>
|
||||
<circle cx="12" cy="21" r="2"/>
|
||||
<circle cx="20" cy="21" r="2"/>
|
||||
<circle cx="28" cy="21" r="2"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 534 B |
@@ -48,9 +48,6 @@
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
//initDatePicker();
|
||||
|
||||
|
||||
initInvaders('results');
|
||||
document.addEventListener("invadersPause", function(event) {
|
||||
pauseInvaders();
|
||||
|
||||
@@ -110,23 +110,25 @@ button[disabled] .icon-inline {
|
||||
button.save-button {
|
||||
background-color: #ff9900;
|
||||
}
|
||||
button.save-button:disabled {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.icon-tiny {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.icon-small {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
}
|
||||
.icon-small:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.icon-middle {
|
||||
width: 2.5em;
|
||||
height: 2.5em;
|
||||
margin: 0.2em;
|
||||
}
|
||||
.icon-middle:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.icon-large {
|
||||
width: 8em;
|
||||
@@ -145,8 +147,6 @@ button.save-button {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
|
||||
|
||||
a ,a:visited {
|
||||
color: blue;
|
||||
text-decoration: none;
|
||||
|
||||
@@ -90,7 +90,7 @@ public class TcpIngestor implements Ingestor, AutoCloseable, DisposableBean {
|
||||
port = serverSocket.getLocalPort();
|
||||
LOGGER.info("listening on port " + serverSocket.getLocalPort());
|
||||
|
||||
serverSocket.setSoTimeout((int) TimeUnit.MILLISECONDS.toMillis(2));
|
||||
serverSocket.setSoTimeout((int) TimeUnit.MILLISECONDS.toMillis(100));
|
||||
started.countDown(); // notify caller that the socket is now listening
|
||||
|
||||
while (acceptNewConnections.get()) {
|
||||
|
||||
Reference in New Issue
Block a user