mirror of
https://github.com/bec-project/bec_atlas.git
synced 2025-07-13 22:51:49 +02:00
feat: add column settings dialog, no changing column order
This commit is contained in:
@ -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;
|
||||||
}
|
}
|
||||||
|
|
7
frontend/bec_atlas/src/app/core/model/scan-user-data.ts
Normal file
7
frontend/bec_atlas/src/app/core/model/scan-user-data.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface ScanUserData {
|
||||||
|
name: string;
|
||||||
|
user_rating: number;
|
||||||
|
system_rating: number;
|
||||||
|
user_comments: string;
|
||||||
|
system_comments: string;
|
||||||
|
}
|
@ -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(),
|
||||||
},
|
},
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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) {}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user