From c67698a9a2cbe07912b3882aeffbe9bde8a37272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr> Date: Fri, 3 Sep 2021 16:56:01 +0200 Subject: [PATCH] Search multiple overivew + count => done --- .../datasets/dataset-list.component.html | 13 ++ .../datasets/dataset-list.component.ts | 82 ++++++++++ .../datasets-by-family.component.html | 43 +++++ .../datasets-by-family.component.scss | 27 +++ .../datasets/datasets-by-family.component.ts | 96 +++++++++++ .../components/datasets/index.ts | 7 + .../search-multiple/components/index.ts | 6 +- .../components/result/index.ts | 5 + .../components/result/overview.component.html | 69 ++++++++ .../components/result/overview.component.ts | 87 ++++++++++ .../abstract-search-multiple.component.ts | 14 +- .../containers/datasets.component.html | 59 +++---- .../containers/datasets.component.ts | 89 ++-------- .../containers/position.component.html | 25 ++- .../containers/position.component.ts | 29 +++- .../containers/result-multiple.component.html | 23 +-- .../containers/result-multiple.component.ts | 154 +++++------------- .../search-multiple-routing.module.ts | 10 +- .../criteria/cone-search-tab.component.html | 2 - .../search/containers/result.component.ts | 2 +- .../cone-search/cone-search.component.ts | 2 - .../store/actions/search-multiple.actions.ts | 11 +- .../store/effects/search-multiple.effects.ts | 91 +++++++++-- client/src/app/instance/store/models/index.ts | 2 + .../models/search-multiple-dataset-data.ts} | 5 +- .../models/search-multiple-dataset-length.ts | 13 ++ .../store/reducers/search-multiple.reducer.ts | 59 ++++++- .../selectors/search-multiple.selector.ts | 35 ++++ 28 files changed, 770 insertions(+), 290 deletions(-) create mode 100644 client/src/app/instance/search-multiple/components/datasets/dataset-list.component.html create mode 100644 client/src/app/instance/search-multiple/components/datasets/dataset-list.component.ts create mode 100644 client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.html create mode 100644 client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.scss create mode 100644 client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.ts create mode 100644 client/src/app/instance/search-multiple/components/datasets/index.ts create mode 100644 client/src/app/instance/search-multiple/components/result/index.ts create mode 100644 client/src/app/instance/search-multiple/components/result/overview.component.html create mode 100644 client/src/app/instance/search-multiple/components/result/overview.component.ts rename client/src/app/instance/{search-multiple/containers/position.component.scss => store/models/search-multiple-dataset-data.ts} (73%) create mode 100644 client/src/app/instance/store/models/search-multiple-dataset-length.ts diff --git a/client/src/app/instance/search-multiple/components/datasets/dataset-list.component.html b/client/src/app/instance/search-multiple/components/datasets/dataset-list.component.html new file mode 100644 index 00000000..4395a45c --- /dev/null +++ b/client/src/app/instance/search-multiple/components/datasets/dataset-list.component.html @@ -0,0 +1,13 @@ +<div class="row"> + <div *ngFor="let datasetFamily of getDatasetFamilyList()" class="col-12 col-lg-6 col-xl-4 my-3 text-center"> + <app-datasets-by-family + [datasetFamily]="datasetFamily" + [datasetList]="getDatasetsByFamily(datasetFamily.id)" + [surveyList]="surveyList" + [selectedDatasets]="selectedDatasets" + [isAllSelected]="getIsAllSelected(datasetFamily.id)" + [isAllUnselected]="getIsAllUnselected(datasetFamily.id)" + (updateSelectedDatasets)="updateSelectedDatasets.emit($event)"> + </app-datasets-by-family> + </div> +</div> diff --git a/client/src/app/instance/search-multiple/components/datasets/dataset-list.component.ts b/client/src/app/instance/search-multiple/components/datasets/dataset-list.component.ts new file mode 100644 index 00000000..f01569ab --- /dev/null +++ b/client/src/app/instance/search-multiple/components/datasets/dataset-list.component.ts @@ -0,0 +1,82 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; + +import { Dataset, DatasetFamily, Survey } from 'src/app/metamodel/models'; + +@Component({ + selector: 'app-dataset-list', + templateUrl: 'dataset-list.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +/** + * @class + * @classdesc Search multiple dataset list component. + */ +export class DatasetListComponent { + @Input() datasetFamilyList: DatasetFamily[]; + @Input() datasetList: Dataset[]; + @Input() surveyList: Survey[]; + @Input() selectedDatasets: string[]; + @Output() updateSelectedDatasets: EventEmitter<string[]> = new EventEmitter(); + + /** + * Returns dataset family list sorted by display, that contains datasets with cone search enabled. + * + * @return Family[] + */ + getDatasetFamilyList(): DatasetFamily[] { + const familyId: number[] = []; + this.datasetList.forEach(d => { + if (!familyId.includes(d.id_dataset_family)) { + familyId.push(d.id_dataset_family); + } + }); + return this.datasetFamilyList + .filter(f => familyId.includes(f.id)); + } + + /** + * Returns dataset list that belongs to the given ID family. + * + * @param {number} familyId - The dataset family ID. + * + * @return Dataset[] + */ + getDatasetsByFamily(familyId: number): Dataset[] { + return this.datasetList.filter(d => d.id_dataset_family === familyId); + } + + /** + * Checks if all datasets that belongs to the given dataset family ID are selected. + * + * @param {number} familyId - The dataset family ID. + * + * @return boolean + */ + getIsAllSelected(familyId: number): boolean { + const datasetListName = this.getDatasetsByFamily(familyId).map(d => d.name); + const filteredSelectedDatasets = this.selectedDatasets.filter(name => datasetListName.indexOf(name) > -1); + return datasetListName.length === filteredSelectedDatasets.length; + } + + /** + * Checks if none of datasets that belongs to the given dataset family ID are selected. + * + * @param {number} familyId - The dataset family ID. + * + * @return boolean + */ + getIsAllUnselected(familyId: number): boolean { + const datasetListName = this.getDatasetsByFamily(familyId).map(d => d.name); + const filteredSelectedDatasets = this.selectedDatasets.filter(name => datasetListName.indexOf(name) > -1); + return filteredSelectedDatasets.length === 0; + } +} diff --git a/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.html b/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.html new file mode 100644 index 00000000..c0ab965e --- /dev/null +++ b/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.html @@ -0,0 +1,43 @@ +<p class="mb-3"><em>{{ datasetFamily.label }}</em></p> +<div class="row mb-1"> + <div class="col pr-1"> + <button (click)="selectAll()" [disabled]="isAllSelected" + class="btn btn-sm btn-block btn-outline-secondary letter-spacing"> + Select All + </button> + </div> + <div class="col pl-1"> + <button (click)="unselectAll()" [disabled]="isAllUnselected" + class="btn btn-sm btn-block btn-outline-secondary letter-spacing"> + Unselect All + </button> + </div> +</div> +<div class="selectbox p-0"> + <div *ngFor="let dataset of datasetList"> + <div *ngIf="isSelected(dataset.name)"> + <button class="btn btn-block text-left py-1 m-0 rounded-0" (click)="toggleSelection(dataset.name)"> + <span class="fas fa-fw fa-check-square theme-color"></span> + {{ dataset.label }} + <span [tooltip]="datasetInfo" placement="right" containerClass="custom-tooltip right-tooltip"> + <span class="far fa-question-circle fa-xs"></span> + </span> + </button> + </div> + <div *ngIf="!isSelected(dataset.name)"> + <button class="btn btn-block text-left py-1 m-0 rounded-0" (click)="toggleSelection(dataset.name)"> + <span class="far fa-fw fa-square text-secondary"></span> + {{ dataset.label }} + <span [tooltip]="datasetInfo" placement="right" containerClass="custom-tooltip right-tooltip"> + <span class="far fa-question-circle fa-xs"></span> + </span> + </button> + </div> + + <ng-template #datasetInfo class="text-left"> + {{ dataset.description }} + <br><br> + {{ getSurveyDescription(dataset.survey_name) }} + </ng-template> + </div> +</div> diff --git a/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.scss b/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.scss new file mode 100644 index 00000000..14afb86a --- /dev/null +++ b/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.scss @@ -0,0 +1,27 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +.selectbox { + height: 200px; + overflow-y: auto; + border: 1px solid #ced4da; + border-radius: .25rem; +} + +.letter-spacing { + letter-spacing: 2px; +} + +.selectbox button:hover { + background-color: #ECECEC; +} + +.selectbox button:focus { + box-shadow: none; +} diff --git a/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.ts b/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.ts new file mode 100644 index 00000000..8a1a260c --- /dev/null +++ b/client/src/app/instance/search-multiple/components/datasets/datasets-by-family.component.ts @@ -0,0 +1,96 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core'; + +import { Dataset, DatasetFamily, Survey } from 'src/app/metamodel/models'; + +@Component({ + selector: 'app-datasets-by-family', + templateUrl: 'datasets-by-family.component.html', + styleUrls: ['datasets-by-family.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None +}) +/** + * @class + * @classdesc Search multiple datasets by family component. + */ +export class DatasetsByFamilyComponent { + @Input() datasetFamily: DatasetFamily; + @Input() datasetList: Dataset[]; + @Input() surveyList: Survey[]; + @Input() selectedDatasets: string[]; + @Input() isAllSelected: boolean; + @Input() isAllUnselected: boolean; + @Output() updateSelectedDatasets: EventEmitter<string[]> = new EventEmitter(); + + /** + * Checks if dataset is selected fot the given dataset name. + * + * @param {string} dname - The dataset name. + * + * @return boolean + */ + isSelected(dname: string): boolean { + return this.selectedDatasets.filter(i => i === dname).length > 0; + } + + /** + * Emits event to update dataset list selection with the given updated selected dataset name list. + * + * @param {string} dname - The dataset name. + */ + toggleSelection(dname: string): void { + const clonedSelectedDatasets = [...this.selectedDatasets]; + const index = clonedSelectedDatasets.indexOf(dname); + if (index > -1) { + clonedSelectedDatasets.splice(index, 1); + } else { + clonedSelectedDatasets.push(dname); + } + this.updateSelectedDatasets.emit(clonedSelectedDatasets); + } + + /** + * Emits event to update dataset list selection with all datasets names. + */ + selectAll(): void { + const clonedSelectedDatasets = [...this.selectedDatasets]; + const datasetListName = this.datasetList.map(d => d.name); + datasetListName.filter(name => clonedSelectedDatasets.indexOf(name) === -1).forEach(name => { + clonedSelectedDatasets.push(name); + }); + this.updateSelectedDatasets.emit(clonedSelectedDatasets); + } + + /** + * Emits event to update dataset list selection with no datasets names. + */ + unselectAll(): void { + const clonedSelectedDatasets = [...this.selectedDatasets]; + const datasetListName = this.datasetList.map(d => d.name); + datasetListName.filter(name => clonedSelectedDatasets.indexOf(name) > -1).forEach(name => { + const index = clonedSelectedDatasets.indexOf(name); + clonedSelectedDatasets.splice(index, 1); + }); + this.updateSelectedDatasets.emit(clonedSelectedDatasets); + } + + /** + * Returns survey description of the given survey name. + * + * @param {string} surveyName - The survey name. + * + * @return string + */ + getSurveyDescription(surveyName: string): string { + return this.surveyList.find(p => p.name === surveyName).description; + } +} diff --git a/client/src/app/instance/search-multiple/components/datasets/index.ts b/client/src/app/instance/search-multiple/components/datasets/index.ts new file mode 100644 index 00000000..730e87c6 --- /dev/null +++ b/client/src/app/instance/search-multiple/components/datasets/index.ts @@ -0,0 +1,7 @@ +import { DatasetListComponent } from './dataset-list.component'; +import { DatasetsByFamilyComponent } from './datasets-by-family.component'; + +export const datasetsComponents = [ + DatasetListComponent, + DatasetsByFamilyComponent +]; diff --git a/client/src/app/instance/search-multiple/components/index.ts b/client/src/app/instance/search-multiple/components/index.ts index d3d2236e..53355e40 100644 --- a/client/src/app/instance/search-multiple/components/index.ts +++ b/client/src/app/instance/search-multiple/components/index.ts @@ -1,7 +1,11 @@ import { ProgressBarMultipleComponent } from './progress-bar-multiple.component'; import { SummaryMultipleComponent } from './summary-multiple.component'; +import { datasetsComponents } from './datasets'; +import { resultComponents } from './result'; export const dummiesComponents = [ ProgressBarMultipleComponent, - SummaryMultipleComponent + SummaryMultipleComponent, + datasetsComponents, + resultComponents ]; diff --git a/client/src/app/instance/search-multiple/components/result/index.ts b/client/src/app/instance/search-multiple/components/result/index.ts new file mode 100644 index 00000000..73ec1010 --- /dev/null +++ b/client/src/app/instance/search-multiple/components/result/index.ts @@ -0,0 +1,5 @@ +import { OverviewComponent } from './overview.component'; + +export const resultComponents = [ + OverviewComponent +]; diff --git a/client/src/app/instance/search-multiple/components/result/overview.component.html b/client/src/app/instance/search-multiple/components/result/overview.component.html new file mode 100644 index 00000000..d1414ccf --- /dev/null +++ b/client/src/app/instance/search-multiple/components/result/overview.component.html @@ -0,0 +1,69 @@ +<div class="jumbotron mb-5 py-4"> + <div *ngIf="getTotalObject() === 0"> + <div class=""> + <h2 class="font-weight-bold">No results.</h2> + </div> + <hr class="my-4"> + <div class="row justify-content-around"> + <div class="col-auto border-right"> + <span class="title">Cone search</span> + <ul class="list-unstyled pl-3"> + <li>RA = {{ coneSearch.ra }}°</li> + <li>DEC = {{ coneSearch.dec }}°</li> + <li>radius = {{ coneSearch.radius }} arcsecond</li> + </ul> + </div> + <div *ngFor="let datasetFamily of getSortedDatasetFamilyList()" class="col-auto"> + <span class="title">{{ datasetFamily.label }}</span> + <ul class="list-unstyled pl-3"> + <li *ngFor="let dataset of getSelectedDatasetsByFamily(datasetFamily.id)" > + {{ dataset.label }} <span class="badge badge-pill badge-light text-danger">0</span> + </li> + </ul> + </div> + </div> + <hr class="my-4"> + <div class="text-center"> + <a routerLink="/search-multiple/position" [queryParams]="queryParams" class="btn btn-lg btn-outline-primary"> + <span class="fas fa-undo"></span> Try something else + </a> + </div> + </div> + + <div *ngIf="getTotalObject() > 0"> + <div class="lead"> + Found + <span class="font-weight-bold">{{ getTotalObject() }}</span> + <span *ngIf="getTotalObject() > 1; else object"> objects</span> + <ng-template #object> object</ng-template> + in + <span class="font-weight-bold">{{ getTotalDatasets() }}</span> + <span *ngIf="getTotalDatasets() > 1; else dataset"> datasets</span> + <ng-template #dataset> dataset</ng-template>. + </div> + <hr class="my-4"> + <div class="row justify-content-around"> + <div class="col-auto border-right"> + <span class="title">Cone search</span> + <ul class="list-unstyled pl-3"> + <li>RA = {{ coneSearch.ra }}°</li> + <li>DEC = {{ coneSearch.dec }}°</li> + <li>radius = {{ coneSearch.radius }} arcsecond</li> + </ul> + </div> + <div *ngFor="let datasetFamily of getSortedDatasetFamilyList()" class="col-auto"> + <span class="title">{{ datasetFamily.label }}</span> + <ul class="list-unstyled pl-3"> + <li *ngFor="let dataset of getSelectedDatasetsByFamily(datasetFamily.id)" > + {{ dataset.label }} + <span class="badge badge-pill badge-light" [ngClass]="{'text-primary': getCountByDataset(dataset.name) !== 0}"> + {{ getCountByDataset(dataset.name) }} + </span> + <span *ngIf="getCountByDataset(dataset.name) < 2"> object found</span> + <span *ngIf="getCountByDataset(dataset.name) > 1"> objects found</span> + </li> + </ul> + </div> + </div> + </div> +</div> diff --git a/client/src/app/instance/search-multiple/components/result/overview.component.ts b/client/src/app/instance/search-multiple/components/result/overview.component.ts new file mode 100644 index 00000000..a2f64652 --- /dev/null +++ b/client/src/app/instance/search-multiple/components/result/overview.component.ts @@ -0,0 +1,87 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, Input } from '@angular/core'; + +import { DatasetFamily, Dataset } from 'src/app/metamodel/models'; +import { ConeSearch, SearchMultipleDatasetLength, SearchMultipleQueryParams } from 'src/app/instance/store/models'; + +@Component({ + selector: 'app-overview', + templateUrl: 'overview.component.html' +}) +export class OverviewComponent { + @Input() datasetFamilyList: DatasetFamily[]; + @Input() datasetList: Dataset[]; + @Input() coneSearch: ConeSearch; + @Input() selectedDatasets: string[]; + @Input() dataLength: SearchMultipleDatasetLength[]; + @Input() queryParams: SearchMultipleQueryParams; + + /** + * Returns total amount of results for all datasets. + * + * @return number + */ + getTotalObject(): number { + return this.dataLength + .filter(datasetLength => datasetLength.length > 0) + .reduce((sum, datasetLength) => sum + datasetLength.length, 0); + } + + /** + * Returns total number of datasets with results. + * + * @return number + */ + getTotalDatasets(): number { + return this.dataLength.filter(datasetLength => datasetLength.length > 0).length; + } + + /** + * Returns dataset families sorted by display, that contains selected datasets. + * + * @return Family[] + */ + getSortedDatasetFamilyList(): DatasetFamily[] { + let datasetFamiliesWithSelectedDataset: DatasetFamily[] = []; + this.selectedDatasets.forEach(dname => { + const dataset: Dataset = this.datasetList.find(d => d.name === dname); + const datasetFamily: DatasetFamily = this.datasetFamilyList.find(f => f.id === dataset.id_dataset_family); + if (!datasetFamiliesWithSelectedDataset.includes(datasetFamily)) { + datasetFamiliesWithSelectedDataset.push(datasetFamily); + } + }); + return datasetFamiliesWithSelectedDataset; + } + + /** + * Returns selected dataset list for the given dataset family ID. + * + * @param {number} familyId - The family ID. + * + * @return Dataset[] + */ + getSelectedDatasetsByFamily(familyId: number): Dataset[] { + return this.datasetList + .filter(d => d.id_dataset_family === familyId) + .filter(d => this.selectedDatasets.includes(d.name)); + } + + /** + * Returns the result number for the given dataset name. + * + * @param {string} dname - The dataset name. + * + * @return number + */ + getCountByDataset(dname: string): number { + return this.dataLength.find(datasetLength => datasetLength.datasetName === dname).length; + } +} diff --git a/client/src/app/instance/search-multiple/containers/abstract-search-multiple.component.ts b/client/src/app/instance/search-multiple/containers/abstract-search-multiple.component.ts index f83b809a..31963675 100644 --- a/client/src/app/instance/search-multiple/containers/abstract-search-multiple.component.ts +++ b/client/src/app/instance/search-multiple/containers/abstract-search-multiple.component.ts @@ -1,4 +1,4 @@ -import { Directive } from '@angular/core'; +import { Directive, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; @@ -8,11 +8,12 @@ import { ConeSearch, SearchMultipleQueryParams } from '../../store/models'; import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector'; import * as datasetFamilySelector from 'src/app/metamodel/selectors/dataset-family.selector'; import * as datasetSelector from 'src/app/metamodel/selectors/dataset.selector'; +import * as searchMultipleActions from '../../store/actions/search-multiple.actions'; import * as searchMultipleSelector from 'src/app/instance/store/selectors/search-multiple.selector'; import * as coneSearchSelector from 'src/app/instance/store/selectors/cone-search.selector'; @Directive() -export abstract class AbstractSearchMultipleComponent { +export abstract class AbstractSearchMultipleComponent implements OnInit, OnDestroy { public instanceSelected: Observable<string>; public datasetFamilyListIsLoading: Observable<boolean>; public datasetFamilyListIsLoaded: Observable<boolean>; @@ -20,7 +21,6 @@ export abstract class AbstractSearchMultipleComponent { public datasetListIsLoading: Observable<boolean>; public datasetListIsLoaded: Observable<boolean>; public datasetList: Observable<Dataset[]>; - public pristine: Observable<boolean>; public currentStep: Observable<string>; public selectedDatasets: Observable<string[]>; public coneSearch: Observable<ConeSearch>; @@ -34,13 +34,15 @@ export abstract class AbstractSearchMultipleComponent { this.datasetListIsLoading = this.store.select(datasetSelector.selectDatasetListIsLoading); this.datasetListIsLoaded = this.store.select(datasetSelector.selectDatasetListIsLoaded); this.datasetList = this.store.select(datasetSelector.selectAllDatasets); - this.pristine = this.store.select(searchMultipleSelector.selectPristine); this.currentStep = this.store.select(searchMultipleSelector.selectCurrentStep) this.selectedDatasets = this.store.select(searchMultipleSelector.selectSelectedDatasets); this.coneSearch = this.store.select(coneSearchSelector.selectConeSearch); this.queryParams = this.store.select(searchMultipleSelector.selectQueryParams); } - + ngOnInit() { + Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.initSearch())); } -} \ No newline at end of file + + ngOnDestroy() { } +} diff --git a/client/src/app/instance/search-multiple/containers/datasets.component.html b/client/src/app/instance/search-multiple/containers/datasets.component.html index 4f05f1ef..6c030e4a 100644 --- a/client/src/app/instance/search-multiple/containers/datasets.component.html +++ b/client/src/app/instance/search-multiple/containers/datasets.component.html @@ -1,61 +1,44 @@ -<div class="row mt-4"> +<app-spinner *ngIf="(datasetFamilyListIsLoading | async) || (datasetListIsLoading | async) || (surveyListIsLoading | async)"> +</app-spinner> + +<div *ngIf="(datasetFamilyListIsLoaded | async) && (datasetListIsLoaded | async) && (surveyListIsLoaded | async)" class="row mt-4"> <div class="col-12 col-md-8 col-lg-9"> <div class="border rounded my-2"> <p class="border-bottom bg-light text-primary mb-0 py-4 pl-4">Datasets</p> <div class="px-3"> - <div *ngIf="datasetSearchMetaIsLoading | async" class="text-center my-5"> - <span class="fas fa-circle-notch fa-spin fa-3x"></span> - <span class="sr-only">Loading...</span> - </div> - <app-dataset-list *ngIf="datasetSearchMetaIsLoaded | async" - [surveyList]="surveyList | async" - [datasetFamilyList]="datasetFamilyList | async" - [datasetList]="datasetList | async" - [selectedDatasets]="selectedDatasets | async" - (updateSelectedDatasets)="updateSelectedDatasets($event)"> - </app-dataset-list> + <app-dataset-list + [surveyList]="surveyList | async" + [datasetFamilyList]="datasetFamilyList | async" + [datasetList]="datasetList | async" + [selectedDatasets]="selectedDatasets | async" + (updateSelectedDatasets)="updateSelectedDatasets($event)"> + </app-dataset-list> </div> </div> </div> <div class="col-12 col-md-4 col-lg-3 pt-2"> <app-summary-multiple [currentStep]="currentStep | async" - [isValidConeSearch]="isValidConeSearch | async" [coneSearch]="coneSearch | async" - [datasetSearchMetaIsLoading]="datasetSearchMetaIsLoading | async" - [datasetSearchMetaIsLoaded]="datasetSearchMetaIsLoaded | async" - [datasetFamilyList]="datasetFamilyList | async" - [datasetList]="datasetList | async" [selectedDatasets]="selectedDatasets | async" - [noSelectedDatasets]="noSelectedDatasets | async" - [queryParams]="queryParams | async"> + [queryParams]="queryParams | async" + [datasetFamilyList]="datasetFamilyList | async" + [datasetList]="datasetList | async"> </app-summary-multiple> </div> </div> <div class="row mt-5 justify-content-between"> <div class="col"> - <button *ngIf="noSelectedDatasets | async" class="btn btn-outline-secondary disabled not-allowed" title="At least one dataset required!"> - <span class="fas fa-arrow-left"></span> Previous - </button> - <a *ngIf="!(noSelectedDatasets | async)" - routerLink="/search-multiple/position" - [queryParams]="queryParams | async" - (click)="checkStep()" - class="btn btn-outline-secondary"> - <span class="fas fa-arrow-left"></span> Previous + <a routerLink="/instance/{{ instanceSelected | async }}/search-multiple/position" [queryParams]="queryParams | async" + class="btn btn-outline-secondary"> + <span class="fas fa-arrow-left"></span> Position </a> </div> - <div class="col col-auto"> - <button *ngIf="noSelectedDatasets | async" class="btn btn-outline-primary disabled not-allowed" title="At least one dataset required!"> - Next <span class="fas fa-arrow-right"></span> - </button> - <a *ngIf="!(noSelectedDatasets | async)" - routerLink="/search-multiple/result" - [queryParams]="queryParams | async" - (click)="checkStep()" - class="btn btn-outline-primary"> - Next <span class="fas fa-arrow-right"></span> + <div *ngIf="(selectedDatasets | async).length > 0" class="col col-auto"> + <a routerLink="/instance/{{ instanceSelected | async }}/search-multiple/result" [queryParams]="queryParams | async" + class="btn btn-outline-primary"> + Result <span class="fas fa-arrow-right"></span> </a> </div> </div> diff --git a/client/src/app/instance/search-multiple/containers/datasets.component.ts b/client/src/app/instance/search-multiple/containers/datasets.component.ts index 4ffa2ea0..5b990465 100644 --- a/client/src/app/instance/search-multiple/containers/datasets.component.ts +++ b/client/src/app/instance/search-multiple/containers/datasets.component.ts @@ -7,92 +7,39 @@ * file that was distributed with this source code. */ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; -import * as fromSearchMultiple from '../store/search-multiple.reducer'; -import * as searchMultipleActions from '../store/search-multiple.action'; -import * as searchMultipleSelector from '../store/search-multiple.selector'; -import { SearchMultipleQueryParams } from '../store/model'; -import * as fromMetamodel from '../../metamodel/reducers'; -import * as datasetActions from '../../metamodel/action/dataset.action'; -import { Dataset, Family, Survey } from '../../metamodel/model'; -import * as metamodelSelector from '../../metamodel/selectors'; -import * as coneSearchSelector from '../../shared/cone-search/store/cone-search.selector'; -import { ConeSearch } from '../../shared/cone-search/store/model'; -import { ScrollTopService } from '../../shared/service/sroll-top.service'; - -/** - * Interface for store state. - * - * @interface StoreState - */ -interface StoreState { - searchMultiple: fromSearchMultiple.State; - metamodel: fromMetamodel.State; -} +import { Survey } from 'src/app/metamodel/models'; +import { AbstractSearchMultipleComponent } from './abstract-search-multiple.component'; +import * as searchMultipleActions from '../../store/actions/search-multiple.actions'; +import * as surveySelector from 'src/app/metamodel/selectors/survey.selector'; @Component({ selector: 'app-datasets', templateUrl: 'datasets.component.html' }) -/** - * @class - * @classdesc Search multiple datasets container. - * - * @implements OnInit - */ -export class DatasetsComponent implements OnInit { - public datasetSearchMetaIsLoading: Observable<boolean>; - public datasetSearchMetaIsLoaded: Observable<boolean>; - public currentStep: Observable<string>; +export class DatasetsComponent extends AbstractSearchMultipleComponent { + public surveyListIsLoading: Observable<boolean>; + public surveyListIsLoaded: Observable<boolean>; public surveyList: Observable<Survey[]>; - public datasetFamilyList: Observable<Family[]>; - public datasetList: Observable<Dataset[]>; - public isValidConeSearch: Observable<boolean>; - public coneSearch: Observable<ConeSearch>; - public selectedDatasets: Observable<string[]>; - public noSelectedDatasets: Observable<boolean>; - public queryParams: Observable<SearchMultipleQueryParams>; - constructor(private store: Store<StoreState>, private scrollTopService: ScrollTopService) { - this.datasetSearchMetaIsLoading = store.select(metamodelSelector.getDatasetSearchMetaIsLoading); - this.datasetSearchMetaIsLoaded = store.select(metamodelSelector.getDatasetSearchMetaIsLoaded); - this.currentStep = store.select(searchMultipleSelector.getCurrentStep); - this.surveyList = store.select(metamodelSelector.getSurveyList); - this.datasetFamilyList = store.select(metamodelSelector.getDatasetFamilyList); - this.datasetList = store.select(metamodelSelector.getDatasetWithConeSearchList); - this.isValidConeSearch = this.store.select(coneSearchSelector.getIsValidConeSearch); - this.selectedDatasets = this.store.select(searchMultipleSelector.getSelectedDatasets); - this.noSelectedDatasets = this.store.select(searchMultipleSelector.getNoSelectedDatasets); - this.coneSearch = this.store.select(coneSearchSelector.getConeSearch); - this.queryParams = this.store.select(searchMultipleSelector.getQueryParams); + constructor(protected store: Store<{ }>) { + super(store); + this.surveyListIsLoading = store.select(surveySelector.selectSurveyListIsLoading); + this.surveyListIsLoaded = store.select(surveySelector.selectSurveyListIsLoaded); + this.surveyList = store.select(surveySelector.selectAllSurveys); } ngOnInit() { - // Create a micro task that is processed after the current synchronous code - // This micro task prevent the expression has changed after view init error - Promise.resolve(null).then(() => this.store.dispatch(new searchMultipleActions.InitSearchByUrlAction())); - Promise.resolve(null).then(() => this.store.dispatch(new searchMultipleActions.ChangeStepAction('datasets'))); - this.store.dispatch(new datasetActions.LoadDatasetSearchMetaAction()); - this.scrollTopService.setScrollTop(); - } - - /** - * Dispatches action to check datasets step. - */ - checkStep(): void { - this.store.dispatch(new searchMultipleActions.DatasetsCheckedAction()); + Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.changeStep({ step: 'datasets' }))); + Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.checkDatasets())); + super.ngOnInit(); } - /** - * Dispatches action to update dataset list selection with the given updated selected dataset name list. - * - * @param {string[]} selectedDatasets - The updated dataset name list. - */ - updateSelectedDatasets(selectedDatasets: string[]): void { - this.store.dispatch(new searchMultipleActions.UpdateSelectedDatasetsAction(selectedDatasets)); + updateSelectedDatasets(selectedDatasets: string[]) { + this.store.dispatch(searchMultipleActions.updateSelectedDatasets({ selectedDatasets })); } } diff --git a/client/src/app/instance/search-multiple/containers/position.component.html b/client/src/app/instance/search-multiple/containers/position.component.html index e7c2138b..cb9ef95a 100644 --- a/client/src/app/instance/search-multiple/containers/position.component.html +++ b/client/src/app/instance/search-multiple/containers/position.component.html @@ -5,11 +5,24 @@ <div class="col-12 col-md-8 col-lg-9"> <div class="border rounded my-2"> <p class="border-bottom bg-light text-primary mb-0 py-4 pl-4">Cone Search</p> - <div class="p-4"> - <!-- <app-cone-search></app-cone-search> - <div class=" pt-3 text-center"> - <button class="btn btn-outline-secondary" (click)="resetConeSearch()">Reset cone search</button> - </div> --> + <div class="row p-4"> + <div class="col"> + <app-cone-search + [coneSearch]="coneSearch | async" + [resolver]="resolver | async" + [resolverIsLoading]="resolverIsLoading | async" + [resolverIsLoaded]="resolverIsLoaded | async" + (retrieveCoordinates)="retrieveCoordinates($event)" #cs> + </app-cone-search> + </div> + <div class="col-2 text-center align-self-end"> + <button class="btn btn-outline-success" *ngIf="!(coneSearch | async)" [hidden]="cs.form.invalid" (click)="addConeSearch(cs.getConeSearch())"> + <span class="fas fa-plus fa-fw"></span> + </button> + <button class="btn btn-outline-danger" *ngIf="coneSearch | async" (click)="deleteConeSearch()"> + <span class="fa fa-times fa-fw"></span> + </button> + </div> </div> </div> </div> @@ -27,7 +40,7 @@ <div *ngIf="coneSearch | async" class="row mt-5 justify-content-end"> <div class="col col-auto"> - <a routerLink="/instance/{{ instanceSelected | async}}/search-multiple/datasets}" [queryParams]="queryParams | async" + <a routerLink="/instance/{{ instanceSelected | async }}/search-multiple/datasets" [queryParams]="queryParams | async" class="btn btn-outline-primary">Datasets <span class="fas fa-arrow-right"></span> </a> </div> diff --git a/client/src/app/instance/search-multiple/containers/position.component.ts b/client/src/app/instance/search-multiple/containers/position.component.ts index a7aa89f9..dea59795 100644 --- a/client/src/app/instance/search-multiple/containers/position.component.ts +++ b/client/src/app/instance/search-multiple/containers/position.component.ts @@ -10,23 +10,44 @@ import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; import { AbstractSearchMultipleComponent } from './abstract-search-multiple.component'; +import { Resolver, ConeSearch } from '../../store/models'; import * as searchMultipleActions from '../../store/actions/search-multiple.actions'; +import * as coneSearchActions from '../../store/actions/cone-search.actions'; +import * as coneSearchSelector from '../../store/selectors/cone-search.selector'; @Component({ selector: 'app-position', - templateUrl: 'position.component.html', - styleUrls: ['position.component.scss'] + templateUrl: 'position.component.html' }) export class PositionComponent extends AbstractSearchMultipleComponent { + public resolver: Observable<Resolver>; + public resolverIsLoading: Observable<boolean>; + public resolverIsLoaded: Observable<boolean>; constructor(protected store: Store<{ }>) { super(store); - Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.changeStep({ step: 'position' }))); + this.resolver = this.store.select(coneSearchSelector.selectResolver); + this.resolverIsLoading = this.store.select(coneSearchSelector.selectResolverIsLoading); + this.resolverIsLoaded = this.store.select(coneSearchSelector.selectResolverIsLoaded); } ngOnInit() { - + Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.changeStep({ step: 'position' }))); + super.ngOnInit(); + } + + addConeSearch(coneSearch: ConeSearch): void { + this.store.dispatch(coneSearchActions.addConeSearch({ coneSearch })); + } + + deleteConeSearch(): void { + this.store.dispatch(coneSearchActions.deleteConeSearch()); + } + + retrieveCoordinates(name: string): void { + this.store.dispatch(coneSearchActions.retrieveCoordinates({ name })); } } diff --git a/client/src/app/instance/search-multiple/containers/result-multiple.component.html b/client/src/app/instance/search-multiple/containers/result-multiple.component.html index f1d2d75d..84f8d63b 100644 --- a/client/src/app/instance/search-multiple/containers/result-multiple.component.html +++ b/client/src/app/instance/search-multiple/containers/result-multiple.component.html @@ -1,16 +1,17 @@ -<div class="row mt-4"> +<app-spinner *ngIf="(datasetFamilyListIsLoading | async) || (datasetListIsLoading | async) || (dataLengthIsLoading | async)"> +</app-spinner> + +<div *ngIf="(datasetFamilyListIsLoaded | async) && (datasetListIsLoaded | async) || (dataLengthIsLoaded | async)" class="row mt-4"> <div class="col-12"> <app-overview - [datasetSearchMetaIsLoaded]="datasetSearchMetaIsLoaded | async" - [coneSearch]="coneSearch | async" [datasetFamilyList]="datasetFamilyList | async" [datasetList]="datasetList | async" + [coneSearch]="coneSearch | async" [selectedDatasets]="selectedDatasets | async" - [queryParams]="queryParams | async" - [datasetsCountIsLoaded]="datasetsCountIsLoaded | async" - [datasetsCount]="datasetsCount | async" - (getDatasetCount)="getDatasetsCount()"> + [dataLength]="dataLength | async" + [queryParams]="queryParams | async"> </app-overview> + <!-- <app-datasets-result [datasetsCountIsLoaded]="datasetsCountIsLoaded | async" [datasetFamilyList]="datasetFamilyList | async" @@ -26,14 +27,14 @@ (retrieveMeta)="retrieveMeta($event)" (retrieveData)="retrieveData($event)" (updateSelectedData)="updateSelectedData($event)"> - </app-datasets-result> + </app-datasets-result> --> </div> </div> -<div *ngIf="(datasetSearchMetaIsLoaded | async) && (datasetsCountIsLoaded | async)" class="row mt-5 justify-content-between"> +<div class="row mt-5 justify-content-between"> <div class="col"> - <a routerLink="/search-multiple/datasets" [queryParams]="queryParams | async" + <a routerLink="/instance/{{ instanceSelected | async }}/search-multiple/datasets" [queryParams]="queryParams | async" class="btn btn-outline-secondary"> - <span class="fas fa-arrow-left"></span> Previous + <span class="fas fa-arrow-left"></span> Datasets </a> </div> </div> diff --git a/client/src/app/instance/search-multiple/containers/result-multiple.component.ts b/client/src/app/instance/search-multiple/containers/result-multiple.component.ts index 64b5ed34..d6118271 100644 --- a/client/src/app/instance/search-multiple/containers/result-multiple.component.ts +++ b/client/src/app/instance/search-multiple/containers/result-multiple.component.ts @@ -7,136 +7,56 @@ * file that was distributed with this source code. */ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component } from '@angular/core'; +import { Observable, Subscription } from 'rxjs'; import { Store } from '@ngrx/store'; -import { Dictionary } from '@ngrx/entity'; -import { Observable } from 'rxjs'; -import * as fromSearchMultiple from '../store/search-multiple.reducer'; -import * as searchMultipleActions from '../store/search-multiple.action'; -import * as searchMultipleSelector from '../store/search-multiple.selector'; -import { DataByDataset, DatasetCount, SearchMultipleQueryParams } from '../store/model'; -import * as fromMetamodel from '../../metamodel/reducers'; -import * as datasetActions from '../../metamodel/action/dataset.action'; -import * as attributeActions from '../../metamodel/action/attribute.action'; -import * as metamodelSelector from '../../metamodel/selectors'; -import { AttributesByDataset, Dataset, Family } from '../../metamodel/model'; -import * as coneSearchSelector from '../../shared/cone-search/store/cone-search.selector'; -import { ConeSearch } from '../../shared/cone-search/store/model'; -import { Pagination } from '../../shared/datatable/model'; -import { ScrollTopService } from '../../shared/service/sroll-top.service'; - -/** - * Interface for store state. - * - * @interface StoreState - */ -interface StoreState { - searchMultiple: fromSearchMultiple.State; - metamodel: fromMetamodel.State; -} +import { SearchMultipleDatasetLength, SearchMultipleDatasetData } from '../../store/models'; +import { AbstractSearchMultipleComponent } from './abstract-search-multiple.component'; +import * as searchMultipleActions from '../../store/actions/search-multiple.actions'; +import * as searchMultipleSelector from 'src/app/instance/store/selectors/search-multiple.selector'; @Component({ selector: 'app-result-multiple', templateUrl: 'result-multiple.component.html' }) -/** - * @class - * @classdesc Search multiple result container. - * - * @implements OnInit - * @implements OnDestroy - */ -export class ResultMultipleComponent implements OnInit, OnDestroy { - public datasetSearchMetaIsLoading: Observable<boolean>; - public datasetSearchMetaIsLoaded: Observable<boolean>; - public datasetFamilyList: Observable<Family[]>; - public datasetList: Observable<Dataset[]>; - public currentStep: Observable<string>; - public coneSearch: Observable<ConeSearch>; - public selectedDatasets: Observable<string[]>; - public queryParams: Observable<SearchMultipleQueryParams>; - public datasetsCountIsLoading: Observable<boolean>; - public datasetsCountIsLoaded: Observable<boolean>; - public datasetsCount: Observable<DatasetCount[]>; - public datasetsWithAttributeList: Observable<string[] | number[]>; - public allAttributeList: Observable<Dictionary<AttributesByDataset>>; - public data: Observable<{ dname: string, data: any[]}[]>; - public datasetsWithData: Observable<string[] | number[]>; - public allData: Observable<Dictionary<DataByDataset>>; - public selectedData: Observable<{ dname: string, data: (number | string)[] }[]>; - - constructor(private store: Store<StoreState>, private scrollTopService: ScrollTopService) { - this.datasetSearchMetaIsLoading = store.select(metamodelSelector.getDatasetSearchMetaIsLoading); - this.datasetSearchMetaIsLoaded = store.select(metamodelSelector.getDatasetSearchMetaIsLoaded); - this.currentStep = store.select(searchMultipleSelector.getCurrentStep); - this.datasetFamilyList = store.select(metamodelSelector.getDatasetFamilyList); - this.datasetList = store.select(metamodelSelector.getDatasetList); - this.coneSearch = this.store.select(coneSearchSelector.getConeSearch); - this.selectedDatasets = this.store.select(searchMultipleSelector.getSelectedDatasets); - this.queryParams = this.store.select(searchMultipleSelector.getQueryParams); - this.datasetsCountIsLoading = this.store.select(searchMultipleSelector.getDatasetsCountIsLoading); - this.datasetsCountIsLoaded = this.store.select(searchMultipleSelector.getDatasetsCountIsLoaded); - this.datasetsCount = this.store.select(searchMultipleSelector.getDatasetsCount); - this.datasetsWithAttributeList = this.store.select(metamodelSelector.getDatasetsWithAttributeList); - this.allAttributeList = this.store.select(metamodelSelector.getAllAttributeList); - this.datasetsWithData = this.store.select(searchMultipleSelector.getDatasetsWithData); - this.allData = this.store.select(searchMultipleSelector.getAllData); - this.selectedData = this.store.select(searchMultipleSelector.getSelectedData); +export class ResultMultipleComponent extends AbstractSearchMultipleComponent { + public pristine: Observable<boolean>; + public dataLength: Observable<SearchMultipleDatasetLength[]>; + public dataLengthIsLoading: Observable<boolean>; + public dataLengthIsLoaded: Observable<boolean>; + public data: Observable<SearchMultipleDatasetData[]>; + public dataIsLoading: Observable<boolean>; + public dataIsLoaded: Observable<boolean>; + + private pristineSubscription: Subscription; + + constructor(protected store: Store<{ }>) { + super(store); + this.pristine = this.store.select(searchMultipleSelector.selectPristine); + this.dataLength = this.store.select(searchMultipleSelector.selectDataLength); + this.dataLengthIsLoading = this.store.select(searchMultipleSelector.selectDataLengthIsLoading); + this.dataLengthIsLoaded = this.store.select(searchMultipleSelector.selectDataLengthIsLoaded); + this.data = this.store.select(searchMultipleSelector.selectData); + this.dataIsLoading = this.store.select(searchMultipleSelector.selectDataIsLoading); + this.dataIsLoaded = this.store.select(searchMultipleSelector.selectDataIsLoaded); } ngOnInit() { - // Create a micro task that is processed after the current synchronous code - // This micro task prevent the expression has changed after view init error - Promise.resolve(null).then(() => this.store.dispatch(new searchMultipleActions.ChangeStepAction('result'))); - Promise.resolve(null).then(() => this.store.dispatch(new searchMultipleActions.InitSearchByUrlAction())); - this.store.dispatch(new datasetActions.LoadDatasetSearchMetaAction()); - this.scrollTopService.setScrollTop(); + Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.changeStep({ step: 'result' }))); + Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.checkDatasets())); + Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.checkResult())); + super.ngOnInit(); + this.pristineSubscription = this.pristine.subscribe(pristine => { + if (!pristine) { + Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.retrieveDataLength())); + } + }); } - /** - * Dispatches action to retrieve dataset result number. - */ - getDatasetsCount(): void { - this.store.dispatch(new searchMultipleActions.RetrieveDatasetsCountAction()); - } - - /** - * Dispatches action to retrieve metadata of a given dataset name. - * - * @param {string} dname - The dataset name. - */ - retrieveMeta(dname: string): void { - this.store.dispatch(new attributeActions.LoadAttributeListAction(dname)); - } - - /** - * Dispatches action to retrieve data with the given pagination parameters. - * - * @param {Pagination} params - The pagination parameters. - */ - retrieveData(params: Pagination): void { - this.store.dispatch(new searchMultipleActions.RetrieveDataAction(params)); - } - - /** - * Dispatches action to update data selection with the given updated selected data. - * - * @param {{ dname: string, data: (string | number)[] }[]} data - The updated selected data. - */ - updateSelectedData(data: { dname: string, data: (string | number)[] }[]): void { - this.store.dispatch(new searchMultipleActions.UpdateSelectedDataAction(data)); - } - - // executeProcess(typeProcess: string): void { - // this.store.dispatch(new searchActions.ExecuteProcessAction(typeProcess)); - // } - - /** - * Dispatches action to destroy search multiple results. - */ ngOnDestroy() { - this.store.dispatch(new searchMultipleActions.DestroyResultsAction()); + this.pristineSubscription.unsubscribe(); + super.ngOnDestroy(); } } diff --git a/client/src/app/instance/search-multiple/search-multiple-routing.module.ts b/client/src/app/instance/search-multiple/search-multiple-routing.module.ts index ca77e734..a3590eb2 100644 --- a/client/src/app/instance/search-multiple/search-multiple-routing.module.ts +++ b/client/src/app/instance/search-multiple/search-multiple-routing.module.ts @@ -12,12 +12,16 @@ import { Routes, RouterModule } from '@angular/router'; import { SearchMultipleComponent } from './search-multiple.component'; import { PositionComponent } from './containers/position.component'; +import { DatasetsComponent } from './containers/datasets.component'; +import { ResultMultipleComponent } from './containers/result-multiple.component'; const routes: Routes = [ { path: '', component: SearchMultipleComponent, children: [ { path: '', redirectTo: 'position', pathMatch: 'full' }, - { path: 'position', component: PositionComponent } + { path: 'position', component: PositionComponent }, + { path: 'datasets', component: DatasetsComponent }, + { path: 'result', component: ResultMultipleComponent } ] } ]; @@ -30,5 +34,7 @@ export class SearchMultipleRoutingModule { } export const routedComponents = [ SearchMultipleComponent, - PositionComponent + PositionComponent, + DatasetsComponent, + ResultMultipleComponent ]; diff --git a/client/src/app/instance/search/components/criteria/cone-search-tab.component.html b/client/src/app/instance/search/components/criteria/cone-search-tab.component.html index ea951c32..f3ec7c16 100644 --- a/client/src/app/instance/search/components/criteria/cone-search-tab.component.html +++ b/client/src/app/instance/search/components/criteria/cone-search-tab.component.html @@ -19,8 +19,6 @@ [resolver]="resolver" [resolverIsLoading]="resolverIsLoading" [resolverIsLoaded]="resolverIsLoaded" - (addConeSearch)="addConeSearch.emit($event)" - (deleteConeSearch)="deleteConeSearch.emit()" (retrieveCoordinates)="retrieveCoordinates.emit($event)" #cs> </app-cone-search> </div> diff --git a/client/src/app/instance/search/containers/result.component.ts b/client/src/app/instance/search/containers/result.component.ts index 82f65348..f62ca538 100644 --- a/client/src/app/instance/search/containers/result.component.ts +++ b/client/src/app/instance/search/containers/result.component.ts @@ -68,7 +68,7 @@ export class ResultComponent extends AbstractSearchComponent { if (!pristine) { Promise.resolve(null).then(() => this.store.dispatch(searchActions.retrieveDataLength())); } - }) + }); } sampRegister() { diff --git a/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts b/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts index 59de49ad..0bd90e12 100644 --- a/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts +++ b/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts @@ -26,8 +26,6 @@ export class ConeSearchComponent implements OnChanges { @Input() resolver: Resolver; @Input() resolverIsLoading: boolean; @Input() resolverIsLoaded: boolean; - @Output() addConeSearch: EventEmitter<ConeSearch> = new EventEmitter(); - @Output() deleteConeSearch: EventEmitter<{ }> = new EventEmitter(); @Output() retrieveCoordinates: EventEmitter<string> = new EventEmitter(); public form = new FormGroup({ diff --git a/client/src/app/instance/store/actions/search-multiple.actions.ts b/client/src/app/instance/store/actions/search-multiple.actions.ts index 8559feb4..173a56ec 100644 --- a/client/src/app/instance/store/actions/search-multiple.actions.ts +++ b/client/src/app/instance/store/actions/search-multiple.actions.ts @@ -9,20 +9,17 @@ import { createAction, props } from '@ngrx/store'; +import { SearchMultipleDatasetLength, SearchMultipleDatasetData } from '../models'; + export const initSearch = createAction('[Search Multiple] Init Search'); -export const restartSearch = createAction('[Search Multiple] Restart Search'); -export const resetSearch = createAction('[Search Multiple] Reset Search'); -export const destroyResults = createAction('[Search Multiple] Destroy Results'); -export const loadDefaultFormParameters = createAction('[Search Multiple] Load Default Form Parameters'); export const markAsDirty = createAction('[Search Multiple] Mark As Dirty'); export const changeStep = createAction('[Search Multiple] Change Step', props<{ step: string }>()); -export const checkPosition = createAction('[Search Multiple] Check Position'); export const checkDatasets = createAction('[Search Multiple] Check Datasets'); export const checkResult = createAction('[Search Multiple] Check Result'); export const updateSelectedDatasets = createAction('[Search Multiple] Update Selected Datasets', props<{ selectedDatasets: string[] }>()); export const retrieveDataLength = createAction('[Search Multiple] Retrieve Data Length'); -export const retrieveDataLengthSuccess = createAction('[Search Multiple] Retrieve Data Length Success', props<{ length: number }>()); +export const retrieveDataLengthSuccess = createAction('[Search Multiple] Retrieve Data Length Success', props<{ dataLength: SearchMultipleDatasetLength[] }>()); export const retrieveDataLengthFail = createAction('[Search Multiple] Retrieve Data Length Fail'); export const retrieveData = createAction('[Search Multiple] Retrieve Data'); -export const retrieveDataSuccess = createAction('[Search Multiple] Retrieve Data Success', props<{ data: any[] }>()); +export const retrieveDataSuccess = createAction('[Search Multiple] Retrieve Data Success', props<{ data: SearchMultipleDatasetData[] }>()); export const retrieveDataFail = createAction('[Search Multiple] Retrieve Data Fail'); diff --git a/client/src/app/instance/store/effects/search-multiple.effects.ts b/client/src/app/instance/store/effects/search-multiple.effects.ts index e6c26c9c..27078901 100644 --- a/client/src/app/instance/store/effects/search-multiple.effects.ts +++ b/client/src/app/instance/store/effects/search-multiple.effects.ts @@ -10,26 +10,95 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType, concatLatestFrom } from '@ngrx/effects'; -import { Store, Action } from '@ngrx/store'; -import { of } from 'rxjs'; +import { Store, Action } from '@ngrx/store'; +import { forkJoin, of } from 'rxjs'; import { map, tap, mergeMap, catchError } from 'rxjs/operators'; import { ToastrService } from 'ngx-toastr'; -import { ConeSearch, criterionToString, stringToCriterion } from '../models'; +import { SearchMultipleDatasetLength } from '../models'; import { SearchService } from '../services/search.service'; -import * as searchActions from '../actions/search.actions'; -import * as attributeActions from 'src/app/metamodel/actions/attribute.actions'; -import * as attributeSelector from 'src/app/metamodel/selectors/attribute.selector'; -import * as criteriaFamilyActions from 'src/app/metamodel/actions/criteria-family.actions'; -import * as outputFamilyActions from 'src/app/metamodel/actions/output-family.actions'; -import * as outputCategoryActions from 'src/app/metamodel/actions/output-category.actions'; -import * as datasetSelector from 'src/app/metamodel/selectors/dataset.selector'; -import * as searchSelector from '../selectors/search.selector'; +import * as searchMultipleActions from '../actions/search-multiple.actions'; +import * as searchMultipleSelector from '../selectors/search-multiple.selector'; import * as coneSearchActions from '../actions/cone-search.actions'; import * as coneSearchSelector from '../selectors/cone-search.selector'; @Injectable() export class SearchMultipleEffects { + initSearch$ = createEffect(() => + this.actions$.pipe( + ofType(searchMultipleActions.initSearch), + concatLatestFrom(() => [ + this.store.select(searchMultipleSelector.selectPristine), + this.store.select(coneSearchSelector.selectConeSearchByRoute), + this.store.select(searchMultipleSelector.selectSelectedDatasetsByRoute) + ]), + mergeMap(([action, pristine, coneSearchByRoute, selectedDatasetsByRoute]) => { + if (!pristine) { + // Default form parameters already loaded or no dataset selected + return of({ type: '[No Action] Load Default Form Parameters' }); + } + + let actions: Action[] = [ + searchMultipleActions.markAsDirty() + ]; + + // Update cone search + if (coneSearchByRoute) { + const params = coneSearchByRoute.split(':'); + const coneSearch = { + ra: +params[0], + dec: +params[1], + radius: +params[2] + }; + actions.push(coneSearchActions.addConeSearch({ coneSearch })); + } + + // Update selected datasets + if (selectedDatasetsByRoute) { + // Build output list with the URL query parameters (a) + const selectedDatasets = selectedDatasetsByRoute.split(';'); + actions.push( + searchMultipleActions.updateSelectedDatasets({ selectedDatasets }), + searchMultipleActions.checkDatasets() + ); + } + + // Returns actions and mark the form as dirty + return actions; + }) + ) + ); + + retrieveDataLength$ = createEffect(() => + this.actions$.pipe( + ofType(searchMultipleActions.retrieveDataLength), + concatLatestFrom(() => [ + this.store.select(searchMultipleSelector.selectSelectedDatasets), + this.store.select(coneSearchSelector.selectConeSearch) + ]), + mergeMap(([action, selectedDatasets, coneSearch]) => { + const queries = selectedDatasets.map(datasetName => this.searchService.retrieveDataLength( + `${datasetName}?a=count&cs=${coneSearch.ra}:${coneSearch.dec}:${coneSearch.radius}` + ).pipe( + map((response: { nb: number }[]) => ({ datasetName, length: response[0].nb })) + )); + + return forkJoin(queries) + .pipe( + map((response: SearchMultipleDatasetLength[]) => searchMultipleActions.retrieveDataLengthSuccess({ dataLength: response })), + catchError(() => of(searchMultipleActions.retrieveDataLengthFail())) + ) + }) + ) + ); + + retrieveDataLengthFail$ = createEffect(() => + this.actions$.pipe( + ofType(searchMultipleActions.retrieveDataLengthFail), + tap(() => this.toastr.error('Loading Failed', 'The search multiple data length loading failed')) + ), { dispatch: false} + ); + constructor( private actions$: Actions, private searchService: SearchService, diff --git a/client/src/app/instance/store/models/index.ts b/client/src/app/instance/store/models/index.ts index 0f5c77df..962eede1 100644 --- a/client/src/app/instance/store/models/index.ts +++ b/client/src/app/instance/store/models/index.ts @@ -5,3 +5,5 @@ export * from './criterion'; export * from './pagination.model'; export * from './cone-search.model'; export * from './resolver.model'; +export * from './search-multiple-dataset-length'; +export * from './search-multiple-dataset-data'; diff --git a/client/src/app/instance/search-multiple/containers/position.component.scss b/client/src/app/instance/store/models/search-multiple-dataset-data.ts similarity index 73% rename from client/src/app/instance/search-multiple/containers/position.component.scss rename to client/src/app/instance/store/models/search-multiple-dataset-data.ts index b2357fc4..fa25ed88 100644 --- a/client/src/app/instance/search-multiple/containers/position.component.scss +++ b/client/src/app/instance/store/models/search-multiple-dataset-data.ts @@ -7,6 +7,7 @@ * file that was distributed with this source code. */ -.disabled { - cursor: not-allowed; +export interface SearchMultipleDatasetData { + datasetName: string; + data: any[]; } diff --git a/client/src/app/instance/store/models/search-multiple-dataset-length.ts b/client/src/app/instance/store/models/search-multiple-dataset-length.ts new file mode 100644 index 00000000..75961a0d --- /dev/null +++ b/client/src/app/instance/store/models/search-multiple-dataset-length.ts @@ -0,0 +1,13 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +export interface SearchMultipleDatasetLength { + datasetName: string; + length: number; +} diff --git a/client/src/app/instance/store/reducers/search-multiple.reducer.ts b/client/src/app/instance/store/reducers/search-multiple.reducer.ts index 25dcaf67..af05dbda 100644 --- a/client/src/app/instance/store/reducers/search-multiple.reducer.ts +++ b/client/src/app/instance/store/reducers/search-multiple.reducer.ts @@ -9,6 +9,7 @@ import { createReducer, on } from '@ngrx/store'; +import { SearchMultipleDatasetLength, SearchMultipleDatasetData } from '../models'; import * as searchMultipleActions from '../actions/search-multiple.actions'; export interface State { @@ -18,6 +19,12 @@ export interface State { datasetsStepChecked: boolean; resultStepChecked: boolean; selectedDatasets: string[]; + dataLengthIsLoading: boolean; + dataLengthIsLoaded: boolean; + dataLength: SearchMultipleDatasetLength[]; + dataIsLoading: boolean; + dataIsLoaded: boolean; + data: SearchMultipleDatasetData[]; } export const initialState: State = { @@ -26,15 +33,17 @@ export const initialState: State = { positionStepChecked: false, datasetsStepChecked: false, resultStepChecked: false, - selectedDatasets: [] + selectedDatasets: [], + dataLengthIsLoading: false, + dataLengthIsLoaded: false, + dataLength: [], + dataIsLoading: false, + dataIsLoaded: false, + data: [] }; export const searchMultipleReducer = createReducer( initialState, - on(searchMultipleActions.restartSearch, () => ({ - ...initialState, - currentStep: 'position' - })), on(searchMultipleActions.changeStep, (state, { step }) => ({ ...state, currentStep: step @@ -43,10 +52,6 @@ export const searchMultipleReducer = createReducer( ...state, pristine: false })), - on(searchMultipleActions.checkPosition, state => ({ - ...state, - positionStepChecked: true - })), on(searchMultipleActions.checkDatasets, state => ({ ...state, datasetsStepChecked: true @@ -58,6 +63,36 @@ export const searchMultipleReducer = createReducer( on(searchMultipleActions.updateSelectedDatasets, (state, { selectedDatasets }) => ({ ...state, selectedDatasets + })), + on(searchMultipleActions.retrieveDataLength, state => ({ + ...state, + dataLengthIsLoading: true, + dataLengthIsLoaded: false + })), + on(searchMultipleActions.retrieveDataLengthSuccess, (state, { dataLength }) => ({ + ...state, + dataLength, + dataLengthIsLoading: false, + dataLengthIsLoaded: true + })), + on(searchMultipleActions.retrieveDataLengthFail, state => ({ + ...state, + dataLengthIsLoading: false + })), + on(searchMultipleActions.retrieveData, state => ({ + ...state, + dataIsLoading: true, + dataIsLoaded: false + })), + on(searchMultipleActions.retrieveDataSuccess, (state, { data }) => ({ + ...state, + data, + dataIsLoading: false, + dataIsLoaded: true + })), + on(searchMultipleActions.retrieveDataFail, state => ({ + ...state, + dataIsLoading: false })) ); @@ -67,3 +102,9 @@ export const selectPositionStepChecked = (state: State) => state.positionStepChe export const selectDatasetsStepChecked = (state: State) => state.datasetsStepChecked; export const selectResultStepChecked = (state: State) => state.resultStepChecked; export const selectSelectedDatasets = (state: State) => state.selectedDatasets; +export const selectDataLengthIsLoading = (state: State) => state.dataLengthIsLoading; +export const selectDataLengthIsLoaded = (state: State) => state.dataLengthIsLoaded; +export const selectDataLength = (state: State) => state.dataLength; +export const selectDataIsLoading = (state: State) => state.dataIsLoading; +export const selectDataIsLoaded = (state: State) => state.dataIsLoaded; +export const selectData = (state: State) => state.data; diff --git a/client/src/app/instance/store/selectors/search-multiple.selector.ts b/client/src/app/instance/store/selectors/search-multiple.selector.ts index 9e88a69b..61ae2e99 100644 --- a/client/src/app/instance/store/selectors/search-multiple.selector.ts +++ b/client/src/app/instance/store/selectors/search-multiple.selector.ts @@ -49,6 +49,36 @@ export const selectSelectedDatasets = createSelector( fromSearchMultiple.selectSelectedDatasets ); +export const selectDataLengthIsLoading = createSelector( + selectSearchMultipleState, + fromSearchMultiple.selectDataLengthIsLoading +); + +export const selectDataLengthIsLoaded = createSelector( + selectSearchMultipleState, + fromSearchMultiple.selectDataLengthIsLoaded +); + +export const selectDataLength = createSelector( + selectSearchMultipleState, + fromSearchMultiple.selectDataLength +); + +export const selectDataIsLoading = createSelector( + selectSearchMultipleState, + fromSearchMultiple.selectDataIsLoading +); + +export const selectDataIsLoaded = createSelector( + selectSearchMultipleState, + fromSearchMultiple.selectDataIsLoaded +); + +export const selectData = createSelector( + selectSearchMultipleState, + fromSearchMultiple.selectData +); + export const selectQueryParams = createSelector( coneSearchSelector.selectConeSearch, selectSelectedDatasets, @@ -71,3 +101,8 @@ export const selectQueryParams = createSelector( return queryParams; } ); + +export const selectSelectedDatasetsByRoute = createSelector( + reducer.selectRouterState, + router => router.state.queryParams.d as string +); -- GitLab