feat: add column settings dialog, no changing column order

This commit is contained in:
2025-01-30 15:52:31 +01:00
parent 030b1348e6
commit 5918401f77
9 changed files with 228 additions and 39 deletions

View File

@ -1,24 +1,42 @@
export interface ScanDataResponse { export interface ScanDataResponse {
scan_id?: string; scan_id?: string;
scan_number?: number; scan_number?: number;
status?: "open" | "paused" | "aborted" | "halted" | "closed"; status?: 'open' | 'paused' | 'aborted' | 'halted' | 'closed';
session_id?: string; session_id?: string;
num_points?: number; num_points?: number;
scan_name?: string; scan_name?: string;
scan_type?: "step" | "fly"; scan_type?: 'step' | 'fly';
dataset_number?: number; dataset_number?: number;
scan_report_devices?: string[]; scan_report_devices?: string[];
user_metadata?: { [key: string]: any }; user_metadata?: { [key: string]: any };
readout_priority?: { [key: "monitored" | "baseline" | "async" | "continuous" | "on_request" | string]: string[] }; readout_priority?: {
scan_parameters?: { [key: "exp_time" | "frames_per_trigger" | "settling_time" | "readout_time" | string]: any }; [
request_inputs?: { [key: "arg_bundle" | "inputs" | "kwargs" | string]: any }; key:
info?: { [key: string]: any }; | 'monitored'
timestamp?: number; | 'baseline'
user_data?: { [key: string]: any }; | 'async'
name?: string; | 'continuous'
user_rating?: number; | 'on_request'
system_rating?: number; | string
user_comments?: string; ]: string[];
system_comments?: string; };
scan_parameters?: {
[
key:
| 'exp_time'
| 'frames_per_trigger'
| 'settling_time'
| 'readout_time'
| string
]: any;
};
request_inputs?: { [key: 'arg_bundle' | 'inputs' | 'kwargs' | string]: any };
info?: { [key: string]: any };
timestamp?: number;
user_data?: { [key: string]: any };
name?: string;
user_rating?: number;
system_rating?: number;
user_comments?: string;
system_comments?: string;
} }

View File

@ -0,0 +1,7 @@
export interface ScanUserData {
name: string;
user_rating: number;
system_rating: number;
user_comments: string;
system_comments: string;
}

View File

@ -1,10 +1,11 @@
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Inject, inject, Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ServerSettingsService } from '../server-settings.service'; import { ServerSettingsService } from '../server-settings.service';
import { ScanDataResponse } from './model/scan-data'; import { ScanDataResponse } from './model/scan-data';
import { Realm } from './model/realm'; import { Realm } from './model/realm';
import { Deployment } from './model/deployment'; import { Deployment } from './model/deployment';
import { ScanCountResponse } from './model/scan-count'; import { ScanCountResponse } from './model/scan-count';
import { ScanUserData } from './model/scan-user-data';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -42,7 +43,7 @@ export class RemoteDataService {
*/ */
protected get<T>( protected get<T>(
path: string, path: string,
params: { [key: string]: string | number }, params: { [key: string]: string | number | Array<string> },
headers: HttpHeaders headers: HttpHeaders
) { ) {
return this.httpClient.get<T>( return this.httpClient.get<T>(
@ -110,7 +111,7 @@ export class ScanDataService extends RemoteDataService {
session_id: sessionId, session_id: sessionId,
offset: offset.toString(), offset: offset.toString(),
limit: limit.toString(), limit: limit.toString(),
fields: fields ? fields.join(',') : '', fields: fields ? fields : '',
sort: sort ? JSON.stringify(sort) : '', sort: sort ? JSON.stringify(sort) : '',
includeUserData: includeUserData.toString(), includeUserData: includeUserData.toString(),
}, },

View File

@ -0,0 +1,17 @@
<h2 mat-dialog-title>Select Columns</h2>
<mat-dialog-content class="column-selection-dialog">
@for (column of columns; track column; let i = $index) {
<mat-checkbox class="checkbox" (change)="handleCheckboxChecked($event, i)" [checked]="column.selected" [name]="column.name" >{{ column.name }}</mat-checkbox><br>
}
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-icon-button (click)="onCancelClick()">
<mat-icon>cancel</mat-icon>
</button>
<button mat-icon-button (click)="onApplyClick()">
<mat-icon>check</mat-icon>
</button>
</mat-dialog-actions>

View File

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

View File

@ -0,0 +1,41 @@
import { Component, inject } from '@angular/core';
import { MatCheckbox, MatCheckboxChange } from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import {
MatDialogModule,
MatDialogRef,
MAT_DIALOG_DATA,
} from '@angular/material/dialog';
// Models
export interface ColumnModel {
name: string;
selected: boolean;
}
@Component({
selector: 'app-column-selection-dialog',
imports: [MatCheckbox, MatDialogModule, MatIconModule, MatButtonModule],
templateUrl: './column-selection-dialog.component.html',
styleUrl: './column-selection-dialog.component.scss',
})
export class ColumnSelectionDialogComponent {
columns = inject<ColumnModel[]>(MAT_DIALOG_DATA);
initialSelection: string[] = [];
readonly dialogRef = inject(MatDialogRef<ColumnSelectionDialogComponent>);
handleCheckboxChecked(event: MatCheckboxChange, index: number): void {
this.columns[index].selected = event.checked;
}
onCancelClick(): void {
this.dialogRef.close(null);
}
onApplyClick(): void {
let data = this.columns
.filter((column) => column.selected)
.map((column) => column.name);
this.dialogRef.close(data);
}
}

View File

@ -6,18 +6,24 @@
<button mat-icon-button (click)="ngOnInit()"> <button mat-icon-button (click)="ngOnInit()">
<mat-icon>search</mat-icon> <mat-icon>search</mat-icon>
</button> </button>
<button mat-icon-button (click)="ngOnInit()"> <button mat-icon-button (click)="handleRefresh()">
<mat-icon>refresh</mat-icon> <mat-icon>refresh</mat-icon>
</button> </button>
<button mat-icon-button (click)="ngOnInit()"> <button mat-icon-button (click)="openDialog()">
<mat-icon>settings</mat-icon> <mat-icon>settings</mat-icon>
</button> </button>
<!--
<mat-menu #menu="matMenu">
@for (column of availableColumns(); track column) {
<mat-checkbox class="checkbox" (change)="handleColumnSelection(column)">{{ column }}</mat-checkbox>
}
</mat-menu> -->
</mat-toolbar> </mat-toolbar>
<table mat-table *ngIf="tableData() as data" [dataSource]="data" class="mat-elevation-z8"> <table mat-table *ngIf="tableData() as data" [dataSource]="data" class="mat-elevation-z8">
<!-- Create table data dynamically --> <!-- Create table data dynamically -->
<ng-container *ngFor="let column of displayedColumns" [matColumnDef]="column"> <ng-container *ngFor="let column of displayedColumns()" [matColumnDef]="column">
<th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th> <th mat-header-cell *matHeaderCellDef mat-sort-header> {{ column | titlecase }} </th>
<td mat-cell *matCellDef="let element"> <td mat-cell *matCellDef="let element">
@if (column === 'timestamp') { @if (column === 'timestamp') {
{{ element[column] * 1000 | date :'HH:mm:ss'}} {{ element[column] * 1000 | date :'HH:mm:ss'}}
@ -33,9 +39,9 @@
</ng-container> </ng-container>
<!-- Header Row --> <!-- Header Row -->
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr> <tr mat-header-row *matHeaderRowDef="displayedColumns(); sticky: true"></tr>
<!-- Data Rows --> <!-- Data Rows -->
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns()"></tr>
</table> </table>
</mat-card> </mat-card>
</div> </div>

View File

@ -1,12 +1,11 @@
import { import {
ChangeDetectionStrategy,
Component, Component,
WritableSignal,
signal, signal,
ViewChild, ViewChild,
computed, computed,
resource, resource,
Signal, Signal,
inject,
} from '@angular/core'; } from '@angular/core';
import { ScanCountService, ScanDataService } from '../core/remote-data.service'; import { ScanCountService, ScanDataService } from '../core/remote-data.service';
import { ScanDataResponse } from '../core/model/scan-data'; import { ScanDataResponse } from '../core/model/scan-data';
@ -26,6 +25,10 @@ import { StarRatingModule } from 'angular-star-rating';
import { MatSort } from '@angular/material/sort'; import { MatSort } from '@angular/material/sort';
import { firstValueFrom, Observable } from 'rxjs'; import { firstValueFrom, Observable } from 'rxjs';
import { ScanCountResponse } from '../core/model/scan-count'; import { ScanCountResponse } from '../core/model/scan-count';
import { MatMenuModule } from '@angular/material/menu';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialog } from '@angular/material/dialog';
import { ColumnSelectionDialogComponent } from './column-selection-dialog/column-selection-dialog.component';
export interface ResourceStatus { export interface ResourceStatus {
status: any; status: any;
@ -50,10 +53,11 @@ export interface ResourceLoaderParams {
MatPaginatorModule, MatPaginatorModule,
StarRatingModule, StarRatingModule,
MatProgressSpinnerModule, MatProgressSpinnerModule,
MatMenuModule,
MatCheckboxModule,
], ],
templateUrl: './scan-table.component.html', templateUrl: './scan-table.component.html',
styleUrl: './scan-table.component.scss', styleUrl: './scan-table.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class ScanTableComponent { export class ScanTableComponent {
tableData: Signal<ScanDataResponse[]>; tableData: Signal<ScanDataResponse[]>;
@ -61,11 +65,10 @@ export class ScanTableComponent {
limit = signal<number>(10); limit = signal<number>(10);
offset = signal<number>(0); offset = signal<number>(0);
sessionId = signal<string>(''); sessionId = signal<string>('');
dialog = inject(MatDialog);
// page: number = this.offset / this.limit;
pageEvent: PageEvent = new PageEvent(); pageEvent: PageEvent = new PageEvent();
sorting: number = -1; sorting: number = -1;
displayedColumns: string[] = [ displayedColumns = signal<string[]>([
'scan_number', 'scan_number',
'status', 'status',
'num_points', 'num_points',
@ -74,6 +77,29 @@ export class ScanTableComponent {
'dataset_number', 'dataset_number',
'timestamp', 'timestamp',
'user_rating', 'user_rating',
]);
allColumns: string[] = [
'scan_id',
'scan_number',
'status',
'session_id',
'num_points',
'scan_name',
'scan_type',
'dataset_number',
'scan_report_devices',
'user_metadata',
'readout_priority',
'scan_parameters',
'request_inputs',
'info',
'timestamp',
'user_data',
'name',
'user_rating',
'system_rating',
'user_comments',
'system_comments',
]; ];
ignoredEntries: string[] = [ ignoredEntries: string[] = [
'scan_report_devices', 'scan_report_devices',
@ -84,21 +110,38 @@ export class ScanTableComponent {
'info', 'info',
]; ];
availableColumns = computed(() =>
this.allColumns.filter((element) => !this.ignoredEntries.includes(element))
);
reloadCriteria = computed(() => ({ reloadCriteria = computed(() => ({
sessionId: this.sessionId(), sessionId: this.sessionId(),
offset: this.offset(), offset: this.offset(),
limit: this.limit(), limit: this.limit(),
column: this.displayedColumns(),
})); }));
loadScanDataResource = resource({ loadScanDataResource = resource({
request: () => this.reloadCriteria(), request: () => this.reloadCriteria(),
loader: ({ request, abortSignal }): Promise<ScanDataResponse[]> => { loader: ({ request, abortSignal }): Promise<ScanDataResponse[]> => {
let columns = request.column.map((element) =>
[
'name',
'user_rating',
'system_rating',
'user_comments',
'system_comments',
].includes(element)
? 'user_data'
: element
);
console.log('Columns', columns);
return firstValueFrom( return firstValueFrom(
this.scanData.getScanData( this.scanData.getScanData(
request.sessionId, request.sessionId,
request.offset, request.offset,
request.limit, request.limit,
this.displayedColumns, columns,
false, false,
{ scan_number: this.sorting } { scan_number: this.sorting }
) )
@ -115,8 +158,18 @@ export class ScanTableComponent {
handleScanData(data: ScanDataResponse[] | []) { handleScanData(data: ScanDataResponse[] | []) {
for (const entry of data) { for (const entry of data) {
if (entry.user_data && entry.user_data['user_rating']) { if (entry?.user_data !== undefined) {
entry.name = entry.user_data['name'];
entry.system_rating = entry.user_data['system_rating'];
entry.user_rating = entry.user_data['user_rating']; entry.user_rating = entry.user_data['user_rating'];
entry.user_comments = entry.user_data['user_comments'];
entry.system_comments = entry.user_data['system_comments'];
} else {
entry.name = '';
entry.system_rating = 0;
entry.user_rating = 0;
entry.user_comments = '';
entry.system_comments = '';
} }
} }
return data; return data;
@ -154,4 +207,27 @@ export class ScanTableComponent {
this.offset.set(event.pageIndex * event.pageSize); this.offset.set(event.pageIndex * event.pageSize);
this.limit.set(event.pageSize); this.limit.set(event.pageSize);
} }
handleRefresh() {
this.loadScanCountResource.reload();
this.loadScanDataResource.reload();
}
openDialog(): void {
const dialogRef = this.dialog.open(ColumnSelectionDialogComponent, {
data: this.availableColumns().map((element) => ({
name: element,
selected: this.displayedColumns().includes(element),
})),
disableClose: true,
});
dialogRef.afterClosed().subscribe((result: string[] | null) => {
if (result !== null) {
this.displayedColumns.set(result);
// this.handleRefresh();
}
});
}
handleColumnSelection(event: any) {}
} }