diff --git a/src/app/datasets/datafiles/datafiles.component.html b/src/app/datasets/datafiles/datafiles.component.html index 50ec74edd9..bfdb646436 100644 --- a/src/app/datasets/datafiles/datafiles.component.html +++ b/src/app/datasets/datafiles/datafiles.component.html @@ -13,7 +13,7 @@ - +
Selected: {{ selectedFileSize | filesize @@ -38,17 +38,17 @@

No files associated to this dataset

>
- - + diff --git a/src/app/datasets/datafiles/datafiles.component.scss b/src/app/datasets/datafiles/datafiles.component.scss index f0663d1d2d..859458675a 100644 --- a/src/app/datasets/datafiles/datafiles.component.scss +++ b/src/app/datasets/datafiles/datafiles.component.scss @@ -8,7 +8,7 @@ mat-icon { .datafiles-header { margin-top: 1em; - height: 40px; + min-height: 40px; .nbr-of-files { font-size: larger; diff --git a/src/app/datasets/datafiles/datafiles.component.spec.ts b/src/app/datasets/datafiles/datafiles.component.spec.ts index c1c843e6d1..499fc909ef 100644 --- a/src/app/datasets/datafiles/datafiles.component.spec.ts +++ b/src/app/datasets/datafiles/datafiles.component.spec.ts @@ -6,14 +6,12 @@ import { MatTableModule } from "@angular/material/table"; import { PipesModule } from "shared/pipes/pipes.module"; import { RouterModule } from "@angular/router"; import { StoreModule } from "@ngrx/store"; -import { CheckboxEvent } from "shared/modules/table/table.component"; import { MockAuthService, MockDatafilesActionsComponent, MockMatDialogRef, MockUserApi, } from "shared/MockStubs"; -import { MatCheckboxChange } from "@angular/material/checkbox"; import { MatIconModule } from "@angular/material/icon"; import { MatButtonModule } from "@angular/material/button"; import { AppConfigService } from "app-config.service"; @@ -22,6 +20,7 @@ import { ConfigurableActionsComponent } from "shared/modules/configurable-action import { UsersService } from "@scicatproject/scicat-sdk-ts-angular"; import { AuthService } from "shared/services/auth/auth.service"; import { FileSizePipe } from "shared/pipes/filesize.pipe"; +import { RowEventType } from "shared/modules/dynamic-material-table/models/table-row.model"; describe("DatafilesComponent", () => { let component: DatafilesComponent; @@ -92,6 +91,7 @@ describe("DatafilesComponent", () => { ], declarations: [DatafilesComponent], }); + TestBed.overrideComponent(DatafilesComponent, { set: { providers: [ @@ -107,20 +107,14 @@ describe("DatafilesComponent", () => { ], }, }); + TestBed.compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(DatafilesComponent); component = fixture.componentInstance; - fixture.detectChanges(); - }); - afterEach(() => { - fixture.destroy(); - }); - - beforeEach(() => { component.files = [ { path: "test1", @@ -145,182 +139,93 @@ describe("DatafilesComponent", () => { hash: "", }, ]; - component.tableData = component.files; + component.sourceFolder = "/test/"; fixture.detectChanges(); }); - it("should create", () => { - expect(component).toBeTruthy(); - }); - - describe("#getAreAllSelected()", () => { - it("should return 'false' if no file is selected", () => { - const areAllSelected = component.getAreAllSelected(); - - expect(areAllSelected).toEqual(false); - }); - - it("should return 'false' if only some files are selected", () => { - component.tableData[0].selected = true; - const areAllSelected = component.getAreAllSelected(); - - expect(areAllSelected).toEqual(false); - }); - - it("should return 'true' if all files are selected", () => { - component.tableData.forEach((file) => { - file.selected = true; - }); - const areAllSelected = component.getAreAllSelected(); - - expect(areAllSelected).toEqual(true); - }); + afterEach(() => { + fixture.destroy(); }); - describe("#getIsNoneSelected()", () => { - it("should return 'true' if no file is selected", () => { - const isNoneSelected = component.getIsNoneSelected(); - - expect(isNoneSelected).toEqual(true); - }); - - it("should return 'false' if some files are selected", () => { - component.tableData[0].selected = true; - const isNoneSelected = component.getIsNoneSelected(); - - expect(isNoneSelected).toEqual(false); - }); - - it("should return 'false' if all files are selected", () => { - component.tableData.forEach((file) => { - file.selected = true; - }); - const isNoneSelected = component.getIsNoneSelected(); - - expect(isNoneSelected).toEqual(false); - }); + it("should create", () => { + expect(component).toBeTruthy(); }); describe("#getAllFiles()", () => { - it("should return an array of file paths of files in table", () => { + it("should return an array of file paths from files", () => { const files = component.getAllFiles(); expect(Array.isArray(files)).toEqual(true); - expect(files.includes("test1")).toEqual(true); - expect(files.includes("test2")).toEqual(true); + expect(files).toEqual(["test1", "test2"]); }); }); describe("#getSelectedFiles()", () => { - it("should return an array of file paths from selected files in table", () => { - component.tableData[0].selected = true; + it("should return selected file paths", () => { + component.files[0].selected = true; const files = component.getSelectedFiles(); - expect(Array.isArray(files)).toEqual(true); - expect(files.includes("test1")).toEqual(true); + expect(files).toEqual(["test1"]); }); }); - describe("#updateSelectionStatus()", () => { - it("should set 'areAllSelected' to false and 'isNoneSelected' to true if no file is selected", () => { - component.updateSelectionStatus(); - - expect(component.areAllSelected).toEqual(false); - expect(component.isNoneSelected).toEqual(true); - }); - - it("should set both 'areAllSelected' and 'isNoneSelected' to false if some files are selected", () => { - component.tableData[0].selected = true; - component.updateSelectionStatus(); - - expect(component.areAllSelected).toEqual(false); - expect(component.isNoneSelected).toEqual(false); - }); + describe("#onRowEvent()", () => { + it("should select one row and update selectedFileSize", () => { + const row = component.files[0]; - it("should set 'areAllSelected' to true and 'isNoneSelected' to false if all files are selected", () => { - component.tableData.forEach((file) => { - file.selected = true; - }); - component.updateSelectionStatus(); + component.onRowEvent({ + event: RowEventType.RowSelectionChange, + sender: { row, checked: true }, + } as any); - expect(component.areAllSelected).toEqual(true); - expect(component.isNoneSelected).toEqual(false); + expect(component.files[0].selected).toEqual(true); + expect(component.selectedFileSize).toEqual(5000); }); - }); - describe("#onSelectOne()", () => { - it("should set 'selected' to true and add the size of the file to 'selectedFileSize'", () => { - const file = component.tableData[0]; - const event = new MatCheckboxChange(); - event.checked = true; - const checkboxEvent: CheckboxEvent = { event, row: file }; - component.onSelectOne(checkboxEvent); + it("should unselect one row and update selectedFileSize", () => { + const row = component.files[0]; + row.selected = true; + component.selectedFileSize = 5000; - expect(component.tableData[0].selected).toEqual(true); - expect(component.selectedFileSize).toEqual(file.size); - }); + component.onRowEvent({ + event: RowEventType.RowSelectionChange, + sender: { row, checked: false }, + } as any); - it("should set 'selected' of the provided file to false and subtract the size of the file from 'selectedFileSize'", () => { - const firstFile = component.tableData[0]; - const event = new MatCheckboxChange(); - event.checked = true; - const firstCheckboxEvent: CheckboxEvent = { event, row: firstFile }; - component.onSelectOne(firstCheckboxEvent); - - expect(component.tableData[0].selected).toEqual(true); - expect(component.selectedFileSize).toEqual(firstFile.size); - - const event2 = new MatCheckboxChange(); - event2.checked = false; - const secondCheckboxEvent: CheckboxEvent = { - event: event2, - row: firstFile, - }; - component.onSelectOne(secondCheckboxEvent); - - expect(component.tableData[0].selected).toEqual(false); + expect(component.files[0].selected).toEqual(false); expect(component.selectedFileSize).toEqual(0); }); - }); - describe("#onSelectAll()", () => { - it("should set 'selected' of all files to true if previously set to false and add the size of the files to 'selectedFileSize'", () => { - const event = { - checked: true, - } as MatCheckboxChange; - component.onSelectAll(event); + it("should apply master selection from selectionModel", () => { + const selectionModel = { + isSelected: (file) => file.path === "test1", + }; - component.tableData.forEach((file) => { - expect(file.selected).toEqual(true); - }); + component.onRowEvent({ + event: RowEventType.MasterSelectionChange, + sender: { selectionModel }, + } as any); - expect(component.selectedFileSize).toEqual(15000); + expect(component.files[0].selected).toEqual(true); + expect(component.files[1].selected).toEqual(false); + expect(component.selectedFileSize).toEqual(5000); }); - it("should set 'selected' of all files to false and subtract the size of the files from 'selectedFileSize'", () => { - const firstEvent = { - checked: true, - } as MatCheckboxChange; - component.onSelectAll(firstEvent); + it("should select all rows in master selection", () => { + const selectionModel = { + isSelected: () => true, + }; - component.tableData.forEach((file) => { - expect(file.selected).toEqual(true); - }); + component.onRowEvent({ + event: RowEventType.MasterSelectionChange, + sender: { selectionModel }, + } as any); + expect(component.files[0].selected).toEqual(true); + expect(component.files[1].selected).toEqual(true); expect(component.selectedFileSize).toEqual(15000); - - const secondEvent = { - checked: false, - } as MatCheckboxChange; - component.onSelectAll(secondEvent); - - component.tableData.forEach((file) => { - expect(file.selected).toEqual(false); - }); - - expect(component.selectedFileSize).toEqual(0); }); }); diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index 8766755f17..260d6f3fff 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -8,17 +8,12 @@ import { ViewChild, ElementRef, } from "@angular/core"; -import { Subscription } from "rxjs"; +import { BehaviorSubject, Subscription } from "rxjs"; import { Store } from "@ngrx/store"; import { selectCurrentOrigDatablocks, selectCurrentDataset, } from "state-management/selectors/datasets.selectors"; -import { - TableColumn, - PageChangeEvent, - CheckboxEvent, -} from "shared/modules/table/table.component"; import { selectIsLoading, selectIsLoggedIn, @@ -29,7 +24,6 @@ import { CreateJobDtoV3, } from "@scicatproject/scicat-sdk-ts-angular"; import { FileSizePipe } from "shared/pipes/filesize.pipe"; -import { MatCheckboxChange } from "@angular/material/checkbox"; import { MatDialog } from "@angular/material/dialog"; import { PublicDownloadDialogComponent } from "datasets/public-download-dialog/public-download-dialog.component"; import { submitJobAction } from "state-management/actions/jobs.actions"; @@ -41,6 +35,18 @@ import { ActionItems, } from "shared/modules/configurable-actions/configurable-action.interfaces"; import { AuthService } from "shared/services/auth/auth.service"; +import { TableField } from "shared/modules/dynamic-material-table/models/table-field.model"; +import { + TablePagination, + TablePaginationMode, +} from "shared/modules/dynamic-material-table/models/table-pagination.model"; +import { + IRowEvent, + RowEventType, + TableSelectionMode, +} from "shared/modules/dynamic-material-table/models/table-row.model"; +import { ITableSetting } from "shared/modules/dynamic-material-table/models/table-setting.model"; +import { actionMenu } from "shared/modules/dynamic-material-table/utilizes/default-table-settings"; @Component({ selector: "datafiles", @@ -64,9 +70,6 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { totalFileSize = 0; selectedFileSize = 0; - areAllSelected = false; - isNoneSelected = true; - subscriptions: Subscription[] = []; files: Array = []; @@ -76,8 +79,6 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { }; count = 0; - pageSize = 25; - currentPage = 0; fileDownloadEnabled: boolean = this.appConfig.fileDownloadEnabled; multipleDownloadEnabled: boolean = this.appConfig.multipleDownloadEnabled; fileserverBaseURL: string | undefined = this.appConfig.fileserverBaseURL; @@ -94,29 +95,60 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { jwt: CreateUserJWT; auth_token: string; - tableColumns: TableColumn[] = [ + tableColumns: TableField[] = [ { name: "path", - icon: "save", - sort: false, - inList: true, + header: "Path", }, { name: "size", - icon: "save", - sort: false, - inList: true, - pipe: FileSizePipe, + header: "Size", + customRender: (_column, row: DataFiles_File) => + this.fileSizePipe.transform(row.size), }, { name: "time", - icon: "access_time", - sort: false, - inList: true, - dateFormat: "yyyy-MM-dd HH:mm", + header: "Time", + type: "date", + format: "yyyy-MM-dd HH:mm", }, ]; - tableData: DataFiles_File[] = []; + + setting: ITableSetting = {}; + + tableDefaultSettingsConfig: ITableSetting = { + visibleActionMenu: actionMenu, + saveSettingMode: "none", + settingList: [ + { + visibleActionMenu: actionMenu, + saveSettingMode: "none", + isDefaultSetting: true, + isCurrentSetting: true, + columnSetting: [], + }, + ], + rowStyle: { + "border-bottom": "1px solid #d2d2d2", + }, + }; + + dataSource: BehaviorSubject = new BehaviorSubject< + DataFiles_File[] + >([]); + + paginationMode: TablePaginationMode = "client-side"; + + pagination: TablePagination = { + pageSizeOptions: [5, 10, 25, 50, 100], + pageIndex: 0, + pageSize: 25, + length: 0, + }; + + rowSelectionMode: TableSelectionMode = this.fileDownloadEnabled + ? "multi" + : "none"; constructor( public appConfigService: AppConfigService, @@ -128,80 +160,28 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { private fileSizePipe: FileSizePipe, ) {} - onPageChange(event: PageChangeEvent) { - const { pageIndex, pageSize } = event; - this.currentPage = pageIndex; - this.pageSize = pageSize; - const skip = this.currentPage * this.pageSize; - const end = skip + this.pageSize; - this.tableData = this.files.slice(skip, end); - } - - getAreAllSelected() { - return this.tableData.reduce((accum, curr) => accum && curr.selected, true); - } - - getIsNoneSelected() { - return this.tableData.reduce( - (accum, curr) => accum && !curr.selected, - true, - ); - } - getAllFiles() { - if (!this.tableData) { - return []; - } - return this.tableData.map((file) => file.path); + return this.files.map((file) => file.path); } getSelectedFiles() { - if (!this.tableData) { - return []; - } - return this.tableData - .filter((file) => file.selected) - .map((file) => file.path); + return this.files.filter((file) => file.selected).map((file) => file.path); } - updateSelectionStatus() { - this.areAllSelected = this.getAreAllSelected(); - this.isNoneSelected = this.getIsNoneSelected(); - this.updateSelectedInFiles(); - } - - updateSelectedInFiles() { - const selected = this.tableData - .filter((item) => item.selected) - .map((item) => item.path); - const files = this.files.map((item) => { - item.selected = selected.includes(item.path); - return item; - }); - this.files = [...files]; - } + onRowEvent({ event, sender }: IRowEvent) { + if (event === RowEventType.RowSelectionChange && sender.row) { + sender.row.selected = sender.checked; + } - onSelectOne(checkboxEvent: CheckboxEvent) { - const { event, row } = checkboxEvent; - row.selected = event.checked; - if (event.checked) { - this.selectedFileSize += row.size; - } else { - this.selectedFileSize -= row.size; + if (event === RowEventType.MasterSelectionChange && sender.selectionModel) { + this.files.forEach((file) => { + file.selected = sender.selectionModel.isSelected(file); + }); } - this.updateSelectionStatus(); - } - onSelectAll(event: MatCheckboxChange) { - this.tableData.forEach((file) => { - file.selected = event.checked; - if (event.checked) { - this.selectedFileSize += file.size; - } else { - this.selectedFileSize = 0; - } - }); - this.updateSelectionStatus(); + this.selectedFileSize = this.files + .filter((file) => file.selected) + .reduce((sum, file) => sum + file.size, 0); } hasTooLargeFiles(files: any[]) { @@ -249,6 +229,7 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { } //ngAfterViewInit() { ngOnInit() { + this.setting = this.tableDefaultSettingsConfig; this.subscriptions.push( this.dataset$.subscribe((dataset) => { if (dataset) { @@ -259,6 +240,7 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { this.subscriptions.push( this.datablocks$.subscribe((datablocks) => { if (datablocks) { + this.totalFileSize = 0; const files: DataFiles_File[] = []; datablocks.forEach((block) => { block.dataFileList.map((file: DataFiles_File) => { @@ -268,10 +250,20 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { }); }); this.count = files.length; - this.tableData = files.slice(0, this.pageSize); this.files = files; + + this.pagination = { + ...this.pagination, + pageIndex: 0, + length: files.length, + pageSize: this.pagination.pageSize || 25, + }; + + this.applyPagination(); this.tooLargeFile = this.hasTooLargeFiles(this.files); - this.actionItems.datasets[0].files = files; + if (this.actionItems.datasets.length > 0) { + this.actionItems.datasets[0].files = files; + } } }), ); @@ -299,6 +291,22 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { } } + applyPagination() { + const start = this.pagination.pageIndex * this.pagination.pageSize; + const end = start + this.pagination.pageSize; + this.dataSource.next(this.files.slice(start, end)); + } + + onPaginationChange({ pageIndex, pageSize }: TablePagination) { + this.pagination = { + ...this.pagination, + pageIndex, + pageSize, + length: this.files.length, + }; + this.applyPagination(); + } + ngOnDestroy() { this.subscriptions.forEach((subscription) => subscription.unsubscribe()); } diff --git a/src/app/datasets/related-datasets/related-datasets.component.html b/src/app/datasets/related-datasets/related-datasets.component.html index fe0d5f231f..fd90610da2 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.html +++ b/src/app/datasets/related-datasets/related-datasets.component.html @@ -1,19 +1,15 @@
- - - - +
diff --git a/src/app/datasets/related-datasets/related-datasets.component.spec.ts b/src/app/datasets/related-datasets/related-datasets.component.spec.ts index 71c4e47887..7e79a003aa 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.spec.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.spec.ts @@ -1,20 +1,35 @@ import { DatePipe } from "@angular/common"; import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { Router } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; import { Store } from "@ngrx/store"; import { provideMockStore } from "@ngrx/store/testing"; -import { PageChangeEvent } from "shared/modules/table/table.component"; import { changeRelatedDatasetsPageAction, fetchRelatedDatasetsAction, } from "state-management/actions/datasets.actions"; -import { selectRelatedDatasetsPageViewModel } from "state-management/selectors/datasets.selectors"; +import { + selectRelatedDatasetsCurrentPage, + selectRelatedDatasetsPageViewModel, + selectRelatedDatasetsPerPage, +} from "state-management/selectors/datasets.selectors"; import { RelatedDatasetsComponent } from "./related-datasets.component"; -import { TableModule } from "shared/modules/table/table.module"; -import { createMock } from "shared/MockStubs"; +import { MockActivatedRoute, createMock } from "shared/MockStubs"; import { DatasetClass } from "@scicatproject/scicat-sdk-ts-angular"; -import { EmptyContentModule } from "shared/modules/generic-empty-content/empty-content.module"; +import { RowEventType } from "shared/modules/dynamic-material-table/models/table-row.model"; +import { DynamicMatTableModule } from "shared/modules/dynamic-material-table/table/dynamic-mat-table.module"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { TranslateService } from "@ngx-translate/core"; +import { SharedScicatFrontendModule } from "shared/shared.module"; +import { TablePagination } from "shared/modules/dynamic-material-table/models/table-pagination.model"; +import { + provideHttpClient, + withInterceptorsFromDi, +} from "@angular/common/http"; +import { provideHttpClientTesting } from "@angular/common/http/testing"; +import { FileSizePipe } from "shared/pipes/filesize.pipe"; +import { selectColumnsWithHasFetchedSettings } from "state-management/selectors/user.selectors"; +import { JsonHeadPipe } from "shared/pipes/json-head.pipe"; describe("RelatedDatasetsComponent", () => { let component: RelatedDatasetsComponent; @@ -22,16 +37,23 @@ describe("RelatedDatasetsComponent", () => { const router = { navigateByUrl: jasmine.createSpy("navigateByUrl"), + navigate: jasmine.createSpy("navigate"), }; let store: Store; - let dispatchSpy; + let dispatchSpy: jasmine.Spy; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [RelatedDatasetsComponent], - imports: [TableModule, EmptyContentModule], + imports: [ + BrowserAnimationsModule, + DynamicMatTableModule.forRoot({}), + SharedScicatFrontendModule, + ], providers: [ DatePipe, + FileSizePipe, + JsonHeadPipe, provideMockStore({ selectors: [ { @@ -39,16 +61,27 @@ describe("RelatedDatasetsComponent", () => { value: { relatedDatasets: [], relatedDatasetsCount: 0, - relatedDatasetsFilters: { - skip: 0, - limit: 25, - sortField: "creationTime:desc", - }, }, }, + { + selector: selectRelatedDatasetsCurrentPage, + value: 0, + }, + { + selector: selectRelatedDatasetsPerPage, + value: 25, + }, + { + selector: selectColumnsWithHasFetchedSettings, + value: { columns: [], hasFetchedSettings: true }, + }, ], }), { provide: Router, useValue: router }, + { provide: ActivatedRoute, useClass: MockActivatedRoute }, + { provide: TranslateService, useValue: { instant: (k: string) => k } }, + provideHttpClient(withInterceptorsFromDi()), + provideHttpClientTesting(), ], }).compileComponents(); @@ -65,17 +98,17 @@ describe("RelatedDatasetsComponent", () => { expect(component).toBeTruthy(); }); - describe("#onPageChange", () => { - it("should dispatch a changeRelatedDatasetsPageAction and a fetchRelatedDatasetsAction", () => { + describe("#onPaginationChange()", () => { + it("should dispatch changeRelatedDatasetsPageAction and fetchRelatedDatasetsAction", () => { dispatchSpy = spyOn(store, "dispatch"); - const event: PageChangeEvent = { + const event: TablePagination = { pageIndex: 0, pageSize: 25, length: 25, }; - component.onPageChange(event); + component.onPaginationChange(event); expect(dispatchSpy).toHaveBeenCalledTimes(2); expect(dispatchSpy).toHaveBeenCalledWith( @@ -88,11 +121,14 @@ describe("RelatedDatasetsComponent", () => { }); }); - describe("#onRowClick()", () => { + describe("#onRowEvent()", () => { it("should navigate to a dataset", () => { - const dataset = createMock({}); + const dataset = createMock({ pid: "PID-123" }); - component.onRowClick(dataset); + component.onRowEvent({ + event: RowEventType.RowClick, + sender: { row: dataset }, + } as any); expect(router.navigateByUrl).toHaveBeenCalledOnceWith( "/datasets/" + encodeURIComponent(dataset.pid), diff --git a/src/app/datasets/related-datasets/related-datasets.component.ts b/src/app/datasets/related-datasets/related-datasets.component.ts index 750e0f5d09..2d90e7d185 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.ts @@ -1,16 +1,9 @@ import { DatePipe } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, OnInit, OnDestroy } from "@angular/core"; import { Router } from "@angular/router"; import { Store } from "@ngrx/store"; import { map } from "rxjs/operators"; -import { - PageChangeEvent, - TableColumn, -} from "shared/modules/table/table.component"; -import { - DatasetClass, - OutputDatasetObsoleteDto, -} from "@scicatproject/scicat-sdk-ts-angular"; +import { OutputDatasetObsoleteDto } from "@scicatproject/scicat-sdk-ts-angular"; import { changeRelatedDatasetsPageAction, fetchRelatedDatasetsAction, @@ -20,6 +13,24 @@ import { selectRelatedDatasetsPageViewModel, selectRelatedDatasetsPerPage, } from "state-management/selectors/datasets.selectors"; +import { TableField } from "shared/modules/dynamic-material-table/models/table-field.model"; +import { BehaviorSubject, Subscription, take, combineLatest } from "rxjs"; +import { + TablePagination, + TablePaginationMode, +} from "shared/modules/dynamic-material-table/models/table-pagination.model"; +import { + IRowEvent, + RowEventType, + TableSelectionMode, +} from "shared/modules/dynamic-material-table/models/table-row.model"; +import { ITableSetting } from "shared/modules/dynamic-material-table/models/table-setting.model"; +import { AppConfigService } from "app-config.service"; +import { TableConfigService } from "shared/services/table-config.service"; +import { DatasetsListService } from "shared/services/datasets-list.service"; +import { TableColumn } from "state-management/models"; +import { actionMenu } from "shared/modules/dynamic-material-table/utilizes/default-table-settings"; +import { selectColumnsWithHasFetchedSettings } from "state-management/selectors/user.selectors"; @Component({ selector: "app-related-datasets", @@ -27,7 +38,7 @@ import { styleUrls: ["./related-datasets.component.scss"], standalone: false, }) -export class RelatedDatasetsComponent { +export class RelatedDatasetsComponent implements OnInit, OnDestroy { vm$ = this.store.select(selectRelatedDatasetsPageViewModel).pipe( map((vm) => ({ ...vm, @@ -37,60 +48,163 @@ export class RelatedDatasetsComponent { currentPage$ = this.store.select(selectRelatedDatasetsCurrentPage); datasetsPerPage$ = this.store.select(selectRelatedDatasetsPerPage); - tablePaginate = true; - tableColumns: TableColumn[] = [ + subscription: Subscription; + + relatedDatasets$ = this.store.select(selectRelatedDatasetsPageViewModel); + + appConfig = this.appConfigService.getConfig(); + + selectColumnsWithFetchedSettings$ = this.store.select( + selectColumnsWithHasFetchedSettings, + ); + + tableName = "relatedDatasetsTable"; + + columns: TableField[]; + + pending = true; + + setting: ITableSetting = {}; + + tableColumns: TableField[] = [ { name: "name", - icon: "portrait", - sort: true, - inList: true, + header: "Name", }, { name: "sourceFolder", - icon: "explore", - sort: true, - inList: true, + header: "Source Folder", }, { name: "size", - icon: "save", - sort: true, - inList: true, + header: "Size", }, { name: "type", - icon: "bubble_chart", - sort: true, - inList: true, + header: "Type", }, { name: "creationTime", - icon: "calendar_today", - sort: true, - inList: true, + header: "Creation Time", }, { name: "owner", - icon: "face", - sort: true, - inList: true, + header: "Owner", }, ]; + tableDefaultSettingsConfig: ITableSetting = { + visibleActionMenu: actionMenu, + saveSettingMode: "none", + settingList: [ + { + visibleActionMenu: actionMenu, + saveSettingMode: "none", + isDefaultSetting: true, + isCurrentSetting: true, + columnSetting: [], + }, + ], + rowStyle: { + "border-bottom": "1px solid #d2d2d2", + }, + }; + + dataSource: BehaviorSubject = new BehaviorSubject< + OutputDatasetObsoleteDto[] + >([]); + + paginationMode: TablePaginationMode = "server-side"; + + pagination: TablePagination = { + pageSizeOptions: [5, 10, 25, 50, 100], + pageIndex: 0, + pageSize: 10, + length: 0, + }; + + rowSelectionMode: TableSelectionMode = "none"; + constructor( private datePipe: DatePipe, private router: Router, private store: Store, + private appConfigService: AppConfigService, + private tableConfigService: TableConfigService, + private datasetsListService: DatasetsListService, ) {} + ngOnInit(): void { + this.store.dispatch(fetchRelatedDatasetsAction()); + + this.subscription = combineLatest([ + this.vm$, + this.selectColumnsWithFetchedSettings$.pipe(take(1)), + this.currentPage$, + this.datasetsPerPage$, + ]).subscribe(([vm, defaultTableColumns, currentPage, datasetsPerPage]) => { + this.dataSource.next(vm.relatedDatasets); + this.pending = false; + + const defaultConfigColumns = + this.appConfig?.defaultDatasetsListSettings?.columns || []; + + const userTableConfigColumns = + this.datasetsListService.convertSavedDatasetColumns( + defaultTableColumns.columns, + ); + + this.tableDefaultSettingsConfig.settingList[0].columnSetting = + this.datasetsListService.convertSavedDatasetColumns( + defaultConfigColumns as TableColumn[], + ); + + const tableSettingsConfig = + this.tableConfigService.getTableSettingsConfig( + this.tableName, + this.tableDefaultSettingsConfig, + userTableConfigColumns, + ); + + const paginationConfig = { + pageSizeOptions: [5, 10, 25, 100], + pageIndex: currentPage || 0, + pageSize: datasetsPerPage || 10, + length: vm.relatedDatasetsCount || 0, + }; + + if (tableSettingsConfig?.settingList.length) { + this.initTable(tableSettingsConfig, paginationConfig); + } + }); + } + + initTable( + settingConfig: ITableSetting, + paginationConfig: TablePagination, + ): void { + const currentColumnSetting = settingConfig.settingList.find( + (s) => s.isCurrentSetting, + )?.columnSetting; + + this.columns = currentColumnSetting; + this.setting = settingConfig; + this.pagination = paginationConfig; + } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } + formatTableData( datasets: OutputDatasetObsoleteDto[], - ): Record[] { + ): OutputDatasetObsoleteDto[] { if (!datasets) { return []; } return datasets.map((dataset) => ({ + ...dataset, pid: dataset.pid, name: dataset.datasetName, sourceFolder: dataset.sourceFolder, @@ -104,18 +218,20 @@ export class RelatedDatasetsComponent { })); } - onPageChange(event: PageChangeEvent): void { + onPaginationChange({ pageIndex, pageSize }: TablePagination): void { this.store.dispatch( changeRelatedDatasetsPageAction({ - page: event.pageIndex, - limit: event.pageSize, + page: pageIndex, + limit: pageSize, }), ); this.store.dispatch(fetchRelatedDatasetsAction()); } - onRowClick(dataset: DatasetClass): void { - const pid = encodeURIComponent(dataset.pid); - this.router.navigateByUrl("/datasets/" + pid); + onRowEvent({ event, sender }: IRowEvent): void { + if (event === RowEventType.RowClick) { + const pid = encodeURIComponent(sender.row.pid); + this.router.navigateByUrl("/datasets/" + pid); + } } } diff --git a/src/app/files/files-dashboard/files-dashboard.component.ts b/src/app/files/files-dashboard/files-dashboard.component.ts index c739f50fb7..7141e008d6 100644 --- a/src/app/files/files-dashboard/files-dashboard.component.ts +++ b/src/app/files/files-dashboard/files-dashboard.component.ts @@ -73,7 +73,6 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { columnSetting: [ { name: "dataFileList.path", - icon: "text_snippet", header: "Filename", customRender(column, row) { return get(row, column.name); @@ -81,7 +80,6 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { }, { name: "dataFileList.size", - icon: "save", header: "Size", customRender(column, row) { return get(row, column.name); @@ -89,7 +87,6 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { }, { name: "dataFileList.time", - icon: "access_time", header: "Created at", customRender: (column, row) => { return this.datePipe.transform(get(row, column.name)); @@ -97,7 +94,6 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { }, { name: "dataFileList.uid", - icon: "person", header: "UID", customRender: (column, row) => { return get(row, column.name); @@ -105,7 +101,6 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { }, { name: "dataFileList.gid", - icon: "group", header: "GID", customRender: (column, row) => { return get(row, column.name); @@ -113,12 +108,10 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { }, { name: "ownerGroup", - icon: "group", header: "Owner Group", }, { name: "datasetId", - icon: "list", header: "Dataset PID", customRender: (column, row) => `${row[column.name]}`, diff --git a/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts b/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts index 1bb5085ef9..ea0752f431 100644 --- a/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts +++ b/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts @@ -37,12 +37,10 @@ const tableDefaultSettingsConfig: ITableSetting = { { name: "uniqueName", header: "Unique Name", - icon: "scanner", }, { name: "name", header: "Name", - icon: "fingerprint", }, ], }, diff --git a/src/app/proposals/related-proposals/related-proposals.component.ts b/src/app/proposals/related-proposals/related-proposals.component.ts index 638c566e78..05667ca7d5 100644 --- a/src/app/proposals/related-proposals/related-proposals.component.ts +++ b/src/app/proposals/related-proposals/related-proposals.component.ts @@ -39,27 +39,21 @@ const tableDefaultSettingsConfig: ITableSetting = { { name: "proposalId", header: "Proposal ID", - icon: "perm_device_information", }, { name: "relation", - icon: "compare_arrows", }, { name: "title", - icon: "description", }, { name: "abstract", - icon: "description", }, { name: "email", - icon: "badge", }, { name: "type", - icon: "text_format", }, ], }, diff --git a/src/app/samples/sample-dashboard/sample-dashboard.component.html b/src/app/samples/sample-dashboard/sample-dashboard.component.html index ba479e0cd2..818e1f7d7c 100644 --- a/src/app/samples/sample-dashboard/sample-dashboard.component.html +++ b/src/app/samples/sample-dashboard/sample-dashboard.component.html @@ -75,7 +75,7 @@
-
+
{ return this.datePipe.transform(row[column.name]); }, @@ -90,7 +86,6 @@ export class SampleDashboardComponent implements OnInit, OnDestroy { { name: "ownerGroup", header: "Owner group", - icon: "group", }, ], }, diff --git a/src/app/shared/modules/configurable-actions/configurable-actions.component.scss b/src/app/shared/modules/configurable-actions/configurable-actions.component.scss index a0d3feb528..6f42afefb1 100644 --- a/src/app/shared/modules/configurable-actions/configurable-actions.component.scss +++ b/src/app/shared/modules/configurable-actions/configurable-actions.component.scss @@ -1,4 +1,4 @@ .configurable-actions { - float: right; - //margin: 1em; + display: flex; + justify-content: flex-end; }