diff --git a/client/src/app/instance/instance-routing.module.ts b/client/src/app/instance/instance-routing.module.ts index 03c0e21f5b879295d917d7152162fc4e94e73988..53ae1296d4a2648f295956697e42626368d31be5 100644 --- a/client/src/app/instance/instance-routing.module.ts +++ b/client/src/app/instance/instance-routing.module.ts @@ -18,6 +18,7 @@ const routes: Routes = [ { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: 'home', loadChildren: () => import('./home/home.module').then(m => m.HomeModule) }, { path: 'search', loadChildren: () => import('./search/search.module').then(m => m.SearchModule) }, + { path: 'search-multiple', loadChildren: () => import('./search-multiple/search-multiple.module').then(m => m.SearchMultipleModule) }, { path: 'documentation', loadChildren: () => import('./documentation/documentation.module').then(m => m.DocumentationModule) } ] } diff --git a/client/src/app/instance/instance.reducer.ts b/client/src/app/instance/instance.reducer.ts index 76e651beb8b286a870569e434a9c274b4ba1369a..d14c08ae52efefef8b043088ae0b74cddbb1a44d 100644 --- a/client/src/app/instance/instance.reducer.ts +++ b/client/src/app/instance/instance.reducer.ts @@ -11,12 +11,14 @@ import { combineReducers, createFeatureSelector } from '@ngrx/store'; import { RouterReducerState } from 'src/app/custom-route-serializer'; import * as search from './store/reducers/search.reducer'; +import * as searchMultiple from './store/reducers/search-multiple.reducer'; import * as samp from './store/reducers/samp.reducer'; import * as coneSearch from './store/reducers/cone-search.reducer'; import * as detail from './store/reducers/detail.reducer'; export interface State { search: search.State, + searchMultiple: searchMultiple.State, samp: samp.State, coneSearch: coneSearch.State detail: detail.State @@ -24,6 +26,7 @@ export interface State { const reducers = { search: search.searchReducer, + searchMultiple: searchMultiple.searchMultipleReducer, samp: samp.sampReducer, coneSearch: coneSearch.coneSearchReducer, detail: detail.detailReducer 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 0000000000000000000000000000000000000000..4395a45c9d7adad1d8ebe8fa6598b3c3f90f6fee --- /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 0000000000000000000000000000000000000000..f01569abfd679097f564b04a1563c52806e6957e --- /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 0000000000000000000000000000000000000000..c0ab965e6bdb4d3cabde7446155f1f95ae62490d --- /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 0000000000000000000000000000000000000000..14afb86a712e88c6595f1578648cb2b5c8040dbb --- /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 0000000000000000000000000000000000000000..8a1a260c5e72f0f86b46ee6910c9e76f1bfe5976 --- /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 0000000000000000000000000000000000000000..730e87c6c191b0313c5f71920538f3374fe5245f --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..53355e402864869e82505c5ac93da6e6c7da61ec --- /dev/null +++ b/client/src/app/instance/search-multiple/components/index.ts @@ -0,0 +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, + datasetsComponents, + resultComponents +]; diff --git a/client/src/app/instance/search-multiple/components/progress-bar-multiple.component.html b/client/src/app/instance/search-multiple/components/progress-bar-multiple.component.html new file mode 100644 index 0000000000000000000000000000000000000000..4e9fec69aa928951984caba5d04fe6a3dc02d8b1 --- /dev/null +++ b/client/src/app/instance/search-multiple/components/progress-bar-multiple.component.html @@ -0,0 +1,51 @@ +<div class="row text-center"> + <div class="col"> + <h1>Search around a position in multiple datasets</h1> + <p class="text-muted">Fill RA & DEC position, select datasets and display the result.</p> + </div> +</div> +<div class="progress-navigation"> + <div class="progress progress-with-circle"> + <div class="progress-bar" role="progressbar" aria-valuenow="1" aria-valuemin="1" aria-valuemax="4" [ngClass]="getStepClass()"></div> + </div> + <ul class="nav nav-pills"> + <li class="nav-item checked" [ngClass]="{'active': currentStep === 'position'}"> + <a class="nav-link" routerLink="/instance/{{ instance.name }}/search-multiple/position" [queryParams]="queryParams" data-toggle="tab"> + <div class="icon-circle"> + <span class="fas fa-drafting-compass"></span> + </div> + Position + </a> + </li> + + <li class="nav-item" [ngClass]="{'active': currentStep === 'datasets', 'checked': datasetsStepChecked}"> + <a *ngIf="coneSearch" class="nav-link" routerLink="/instance/{{ instance.name }}/search-multiple/datasets" [queryParams]="queryParams" data-toggle="tab"> + <div class="icon-circle"> + <span class="fas fa-book"></span> + </div> + Datasets + </a> + <a *ngIf="!coneSearch" class="nav-link disabled" data-toggle="tab"> + <div class="icon-circle"> + <span class="fas fa-book"></span> + </div> + Datasets + </a> + </li> + + <li class="nav-item" [ngClass]="{'active': currentStep === 'result', 'checked': resultStepChecked}"> + <a *ngIf="coneSearch && selectedDatasets.length > 0" class="nav-link" routerLink="/instance/{{ instance.name }}/search-multiple/result" [queryParams]="queryParams" data-toggle="tab"> + <div class="icon-circle"> + <span class="fas fa-table"></span> + </div> + Result + </a> + <a *ngIf="!coneSearch || selectedDatasets.length < 1" class="nav-link disabled" data-toggle="tab"> + <div class="icon-circle"> + <span class="fas fa-table"></span> + </div> + Result + </a> + </li> + </ul> +</div> diff --git a/client/src/app/instance/search-multiple/components/progress-bar-multiple.component.scss b/client/src/app/instance/search-multiple/components/progress-bar-multiple.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..0a42a748bbc5a7bef5571be4cbd2b9011a183dd3 --- /dev/null +++ b/client/src/app/instance/search-multiple/components/progress-bar-multiple.component.scss @@ -0,0 +1,120 @@ +/** + * 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. + */ + +.progress-navigation { + position: relative; + height: 125px; + margin-top: 15px; +} + +.progress-with-circle { + position: relative; + top: 40px; + z-index: 50; + height: 4px; +} + +.progress-bar { + box-shadow: none; + -webkit-transition: width .3s ease; + -o-transition: width .3s ease; + transition: width .3s ease; + background-color: #7AC29A; +} + +.nav-pills { + background-color: #F3F2EE; + position: absolute; + width: 100%; + height: 4px; + top: 40px; + text-align: center; +} + +.nav-pills li a { + padding: 0; + max-width: 78px; + margin: 0 auto; + color: rgba(0, 0, 0, 0.2); + border-radius: 50%; + position: relative; + top: -32px; + z-index: 100; +} + +.icon-circle { + font-size: 20px; + border: 3px solid #E9ECEF; + text-align: center; + border-radius: 50%; + color: rgba(0, 0, 0, 0.2); + font-weight: 600; + width: 70px; + height: 70px; + background-color: #FFFFFF; + margin: 0 auto; + position: relative; + top: -2px; +} + +.nav-item { + width: 33%; +} + +.nav-item.checked .icon-circle { + border-color: #7AC29A; + color: #7AC29A; +} + +.nav-item.checked a { + color: #7AC29A !important; +} + +.nav-item.active a { + color: #7AC29A !important; + background-color: transparent; +} + +.nav-item.active .icon-circle { + color: white !important; + border-color: #7AC29A; + background-color: #7AC29A; +} + +.nav-link.disabled { + cursor: not-allowed; +} + +.icon-circle svg { + position: absolute; + z-index: 1; + left: 22px; + right: 0; + top: 23px; +} + +.positionStep { + width: 15%; +} + +.datasetsStep { + width: 48%; +} + +.resultStep { + width: 100%; +} + +.btn-clear-form span { + display: none; +} + +.btn-clear-form:hover span { + display: inline; +} diff --git a/client/src/app/instance/search-multiple/components/progress-bar-multiple.component.ts b/client/src/app/instance/search-multiple/components/progress-bar-multiple.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..d3c3d5105ad47a2aa59d2a81a710813478ee128a --- /dev/null +++ b/client/src/app/instance/search-multiple/components/progress-bar-multiple.component.ts @@ -0,0 +1,52 @@ +/** + * 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, ChangeDetectionStrategy } from '@angular/core'; + +import { Instance } from 'src/app/metamodel/models'; +import { ConeSearch, SearchMultipleQueryParams } from 'src/app/instance/store/models'; + +@Component({ + selector: 'app-progress-bar-multiple', + templateUrl: 'progress-bar-multiple.component.html', + styleUrls: ['progress-bar-multiple.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +/** + * @class + * @classdesc Search multiple progress bar component. + */ +export class ProgressBarMultipleComponent { + @Input() instance: Instance; + @Input() currentStep: string; + @Input() positionStepChecked: boolean; + @Input() datasetsStepChecked: boolean; + @Input() resultStepChecked: boolean; + @Input() coneSearch: ConeSearch; + @Input() selectedDatasets: string[]; + @Input() queryParams: SearchMultipleQueryParams; + + /** + * Returns step class that match to the current step. + * + * @return string + */ + getStepClass(): string { + switch (this.currentStep) { + case 'position': + return 'positionStep'; + case 'datasets': + return 'datasetsStep'; + case 'result': + return 'resultStep'; + default: + return 'positionStep'; + } + } +} 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 0000000000000000000000000000000000000000..73ec101031c08b3bdd8c58d04fed9afcb762ddac --- /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 0000000000000000000000000000000000000000..91b90040c64e0fa910f1e502ff2d7c33d34a5be1 --- /dev/null +++ b/client/src/app/instance/search-multiple/components/result/overview.component.html @@ -0,0 +1,75 @@ +<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="/instance/{{ instanceSelected }}/search-multiple/position" 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> + <hr *ngIf="getCountByDataset(dataset.name) > 0" class="my-4"> + <div *ngIf="getCountByDataset(dataset.name) > 0" class="text-center"> + <a routerLink="/instance/{{ instanceSelected }}/search/result/{{ dataset.name }}" [queryParams]="getCsQueryParams()" class="btn btn-outline-primary"> + <span class="fas fa-forward"></span> Go to result + </a> + </div> + </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 0000000000000000000000000000000000000000..e85af4d2feed7f641e352c6f5a998a7a596899c8 --- /dev/null +++ b/client/src/app/instance/search-multiple/components/result/overview.component.ts @@ -0,0 +1,94 @@ +/** + * 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() instanceSelected: string; + @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; + } + + getCsQueryParams() { + return { + cs: `${this.coneSearch.ra}:${this.coneSearch.dec}:${this.coneSearch.radius}` + } + } +} diff --git a/client/src/app/instance/search-multiple/components/summary-multiple.component.html b/client/src/app/instance/search-multiple/components/summary-multiple.component.html new file mode 100644 index 0000000000000000000000000000000000000000..3d60496597bb4fde521dab330da270201431178a --- /dev/null +++ b/client/src/app/instance/search-multiple/components/summary-multiple.component.html @@ -0,0 +1,59 @@ +<div class="border rounded"> + <p class="lead text-center border-bottom bg-light py-3">Search summary</p> + + <!-- Position --> + <p class="text-center font-italic"> + Position + </p> + <span *ngIf="coneSearch" class="pl-5"> + Cone search: + <ul class="ml-3 pl-5 list-unstyled"> + <li>RA = {{ coneSearch.ra }}°</li> + <li>DEC = {{ coneSearch.dec }}°</li> + <li>radius = {{ coneSearch.radius }} arcsecond</li> + </ul> + </span> + <p *ngIf="!coneSearch" class="pl-5 text-danger font-weight-bold"> + Not valid position! + </p> + <hr> + + <!-- Dataset List --> + <p class="text-center font-italic"> + Datasets + </p> + <div> + <p *ngIf="selectedDatasets.length < 1" class="pl-5 text-danger font-weight-bold"> + At least 1 dataset required! + </p> + <div *ngIf="selectedDatasets.length > 0"> + <!-- Accordion Dataset families --> + <accordion [isAnimated]="true"> + <accordion-group *ngFor="let datasetFamily of getDatasetFamilyList()" #ag panelClass="abstract-accordion" [isOpen]="accordionFamilyIsOpen" class="pl-5"> + <button class="btn btn-link btn-block clearfix pb-1 text-primary" accordion-heading> + <div class="pull-left float-left"> + {{ datasetFamily.label }} + + <span *ngIf="ag.isOpen"> + <span class="fas fa-chevron-up"></span> + </span> + <span *ngIf="!ag.isOpen"> + <span class="fas fa-chevron-down"></span> + </span> + </div> + </button> + + <!-- Selected Datasets --> + <ul *ngIf="getSelectedDatasetsByFamily(datasetFamily.id).length > 0; else noDataset" class="mb-0 pl-4 list-unstyled"> + <li *ngFor="let dataset of getSelectedDatasetsByFamily(datasetFamily.id)" class="pb-1"> + {{ dataset.label }} + </li> + </ul> + <ng-template #noDataset> + <p class="mb-1 pl-4">No selected dataset</p> + </ng-template> + </accordion-group> + </accordion> + </div> + </div> +</div> diff --git a/client/src/app/instance/search-multiple/components/summary-multiple.component.scss b/client/src/app/instance/search-multiple/components/summary-multiple.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..c837d14e1ce994aeb0857803139aaad3fd7f8d6d --- /dev/null +++ b/client/src/app/instance/search-multiple/components/summary-multiple.component.scss @@ -0,0 +1,12 @@ +/** + * 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. + */ + +li>span { + cursor: pointer; +} diff --git a/client/src/app/instance/search-multiple/components/summary-multiple.component.ts b/client/src/app/instance/search-multiple/components/summary-multiple.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..790fcd5c75c8862f0dab3bef3ff867edc4c7a361 --- /dev/null +++ b/client/src/app/instance/search-multiple/components/summary-multiple.component.ts @@ -0,0 +1,64 @@ +/** + * 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 { ChangeDetectionStrategy, Component, Input } from '@angular/core'; + +import { SearchMultipleQueryParams, ConeSearch } from 'src/app/instance/store/models'; +import { Dataset, DatasetFamily } from 'src/app/metamodel/models'; + +@Component({ + selector: 'app-summary-multiple', + templateUrl: 'summary-multiple.component.html', + styleUrls: ['summary-multiple.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +/** + * @class + * @classdesc Search multiple summary component. + */ +export class SummaryMultipleComponent { + @Input() currentStep: string; + @Input() coneSearch: ConeSearch; + @Input() selectedDatasets: string[]; + @Input() queryParams: SearchMultipleQueryParams; + @Input() datasetFamilyList: DatasetFamily[]; + @Input() datasetList: Dataset[]; + + accordionFamilyIsOpen = true; + + /** + * Returns dataset families sorted by display, that contains datasets with cone search enabled. + * + * @return Family[] + */ + getDatasetFamilyList(): DatasetFamily[] { + const familiesId: number[] = []; + this.datasetList.forEach(d => { + if (!familiesId.includes(d.id_dataset_family)) { + familiesId.push(d.id_dataset_family); + } + }); + return this.datasetFamilyList + .filter(f => familiesId.includes(f.id)) + //.sort(sortByDisplay); + } + + /** + * Returns 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)); + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..d8bbadd476b404c8b20a43d728284d0423c3a67f --- /dev/null +++ b/client/src/app/instance/search-multiple/containers/abstract-search-multiple.component.ts @@ -0,0 +1,58 @@ +import { Directive, OnDestroy, OnInit } from '@angular/core'; + +import { Store } from '@ngrx/store'; +import { Observable, Subscription } from 'rxjs'; + +import { Dataset, DatasetFamily } from 'src/app/metamodel/models'; +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 implements OnInit, OnDestroy { + public pristine: Observable<boolean>; + public instanceSelected: Observable<string>; + public datasetFamilyListIsLoading: Observable<boolean>; + public datasetFamilyListIsLoaded: Observable<boolean>; + public datasetFamilyList: Observable<DatasetFamily[]>; + public datasetListIsLoading: Observable<boolean>; + public datasetListIsLoaded: Observable<boolean>; + public datasetList: Observable<Dataset[]>; + public currentStep: Observable<string>; + public selectedDatasets: Observable<string[]>; + public coneSearch: Observable<ConeSearch>; + public queryParams: Observable<SearchMultipleQueryParams>; + + private datasetListSubscription: Subscription; + + constructor(protected store: Store<{ }>) { + this.pristine = this.store.select(searchMultipleSelector.selectPristine); + this.instanceSelected = this.store.select(instanceSelector.selectInstanceNameByRoute); + this.datasetFamilyListIsLoading = store.select(datasetFamilySelector.selectDatasetFamilyListIsLoading); + this.datasetFamilyListIsLoaded = store.select(datasetFamilySelector.selectDatasetFamilyListIsLoaded); + this.datasetFamilyList = store.select(datasetFamilySelector.selectAllDatasetFamilies); + this.datasetListIsLoading = this.store.select(datasetSelector.selectDatasetListIsLoading); + this.datasetListIsLoaded = this.store.select(datasetSelector.selectDatasetListIsLoaded); + this.datasetList = this.store.select(datasetSelector.selectAllConeSearchDatasets); + 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() { + this.datasetListSubscription = this.datasetListIsLoaded.subscribe(datasetListIsLoaded => { + if (datasetListIsLoaded) { + Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.initSearch())); + } + }) + } + + ngOnDestroy() { + this.datasetListSubscription.unsubscribe(); + } +} diff --git a/client/src/app/instance/search-multiple/containers/datasets.component.html b/client/src/app/instance/search-multiple/containers/datasets.component.html new file mode 100644 index 0000000000000000000000000000000000000000..6c030e4a4f8ca0b8ec8d5ce94b654778d7baf1f0 --- /dev/null +++ b/client/src/app/instance/search-multiple/containers/datasets.component.html @@ -0,0 +1,44 @@ +<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"> + <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" + [coneSearch]="coneSearch | async" + [selectedDatasets]="selectedDatasets | 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"> + <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 *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 new file mode 100644 index 0000000000000000000000000000000000000000..5b990465122fc275552cdd57367d0299df39bccc --- /dev/null +++ b/client/src/app/instance/search-multiple/containers/datasets.component.ts @@ -0,0 +1,45 @@ +/** + * 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 } from '@angular/core'; + +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; + +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' +}) +export class DatasetsComponent extends AbstractSearchMultipleComponent { + public surveyListIsLoading: Observable<boolean>; + public surveyListIsLoaded: Observable<boolean>; + public surveyList: Observable<Survey[]>; + + 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() { + Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.changeStep({ step: 'datasets' }))); + Promise.resolve(null).then(() => this.store.dispatch(searchMultipleActions.checkDatasets())); + super.ngOnInit(); + } + + 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 new file mode 100644 index 0000000000000000000000000000000000000000..c12f7eee58be2fcc472afa3b853db897c874d433 --- /dev/null +++ b/client/src/app/instance/search-multiple/containers/position.component.html @@ -0,0 +1,47 @@ +<app-spinner *ngIf="(pristine | async) || (datasetFamilyListIsLoading | async) || (datasetListIsLoading | async)"> +</app-spinner> + +<div *ngIf="!(pristine | async) && (datasetFamilyListIsLoaded | async) && (datasetListIsLoaded | 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">Cone Search</p> + <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> + <div class="col-12 col-md-4 col-lg-3 pt-2"> + <app-summary-multiple + [currentStep]="currentStep | async" + [coneSearch]="coneSearch | async" + [selectedDatasets]="selectedDatasets | async" + [queryParams]="queryParams | async" + [datasetFamilyList]="datasetFamilyList | async" + [datasetList]="datasetList | async"> + </app-summary-multiple> + </div> +</div> + +<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" + class="btn btn-outline-primary">Datasets <span class="fas fa-arrow-right"></span> + </a> + </div> +</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 new file mode 100644 index 0000000000000000000000000000000000000000..dea597950d3f0c527909d7059537152eda0c00f8 --- /dev/null +++ b/client/src/app/instance/search-multiple/containers/position.component.ts @@ -0,0 +1,53 @@ +/** + * 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 } 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' +}) +export class PositionComponent extends AbstractSearchMultipleComponent { + public resolver: Observable<Resolver>; + public resolverIsLoading: Observable<boolean>; + public resolverIsLoaded: Observable<boolean>; + + constructor(protected store: Store<{ }>) { + super(store); + 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 new file mode 100644 index 0000000000000000000000000000000000000000..258f1567d2d3f55ae3fba8ef57b014824078f4a8 --- /dev/null +++ b/client/src/app/instance/search-multiple/containers/result-multiple.component.html @@ -0,0 +1,41 @@ +<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 + [instanceSelected]="instanceSelected | async" + [datasetFamilyList]="datasetFamilyList | async" + [datasetList]="datasetList | async" + [coneSearch]="coneSearch | async" + [selectedDatasets]="selectedDatasets | async" + [dataLength]="dataLength | async" + [queryParams]="queryParams | async"> + </app-overview> + <!-- + <app-datasets-result + [datasetsCountIsLoaded]="datasetsCountIsLoaded | async" + [datasetFamilyList]="datasetFamilyList | async" + [datasetList]="datasetList | async" + [coneSearch]="coneSearch | async" + [selectedDatasets]="selectedDatasets | async" + [datasetsCount]="datasetsCount | async" + [datasetsWithAttributeList]="datasetsWithAttributeList | async" + [allAttributeList]="allAttributeList | async" + [datasetsWithData]="datasetsWithData | async" + [allData]="allData | async" + [selectedData]="selectedData | async" + (retrieveMeta)="retrieveMeta($event)" + (retrieveData)="retrieveData($event)" + (updateSelectedData)="updateSelectedData($event)"> + </app-datasets-result> --> + </div> +</div> +<div class="row mt-5 justify-content-between"> + <div class="col"> + <a routerLink="/instance/{{ instanceSelected | async }}/search-multiple/datasets" [queryParams]="queryParams | async" + class="btn btn-outline-secondary"> + <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 new file mode 100644 index 0000000000000000000000000000000000000000..3e4d66b40fbfcad6aabcededb30c6f628554f20d --- /dev/null +++ b/client/src/app/instance/search-multiple/containers/result-multiple.component.ts @@ -0,0 +1,60 @@ +/** + * 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 } from '@angular/core'; + +import { Observable, Subscription } from 'rxjs'; +import { Store } from '@ngrx/store'; + +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' +}) +export class ResultMultipleComponent extends AbstractSearchMultipleComponent { + 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.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() { + 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())); + } + }); + } + + ngOnDestroy() { + 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 4438a68664b0e8e931aabab371af2a8841fc845f..a3590eb28c7d96bbd48addb36fa15a9d00c3ff0b 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 @@ -10,10 +10,20 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -//import { SearchMultipleComponent } from './search-multiple.component'; +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 } + { + path: '', component: SearchMultipleComponent, children: [ + { path: '', redirectTo: 'position', pathMatch: 'full' }, + { path: 'position', component: PositionComponent }, + { path: 'datasets', component: DatasetsComponent }, + { path: 'result', component: ResultMultipleComponent } + ] + } ]; @NgModule({ @@ -23,5 +33,8 @@ const routes: Routes = [ export class SearchMultipleRoutingModule { } export const routedComponents = [ - //SearchMultipleComponent + SearchMultipleComponent, + PositionComponent, + DatasetsComponent, + ResultMultipleComponent ]; diff --git a/client/src/app/instance/search-multiple/search-multiple.component.html b/client/src/app/instance/search-multiple/search-multiple.component.html new file mode 100644 index 0000000000000000000000000000000000000000..7f7342be55b65395e810bc751799628776dea30e --- /dev/null +++ b/client/src/app/instance/search-multiple/search-multiple.component.html @@ -0,0 +1,13 @@ +<div class="mx-1 mx-sm-5 px-1 px-sm-5"> + <app-progress-bar-multiple + [instance]="instance | async" + [currentStep]="currentStep | async" + [positionStepChecked]="positionStepChecked | async" + [datasetsStepChecked]="datasetsStepChecked | async" + [resultStepChecked]="resultStepChecked | async" + [coneSearch]="coneSearch | async" + [selectedDatasets]="selectedDatasets | async" + [queryParams]="queryParams | async"> + </app-progress-bar-multiple> + <router-outlet></router-outlet> +</div> diff --git a/client/src/app/instance/search-multiple/search-multiple.component.ts b/client/src/app/instance/search-multiple/search-multiple.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..b0d2b7b88a44d5099b04b2455761c63191ecc8da --- /dev/null +++ b/client/src/app/instance/search-multiple/search-multiple.component.ts @@ -0,0 +1,49 @@ +/** + * 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, } from '@angular/core'; + +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; + +import { ConeSearch, SearchMultipleQueryParams } from '../store/models'; +import { Instance } from 'src/app/metamodel/models'; +import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector'; +import * as searchMultipleSelector from '../store/selectors/search-multiple.selector'; +import * as coneSearchSelector from '../store/selectors/cone-search.selector'; + +@Component({ + selector: 'app-search-multiple', + templateUrl: 'search-multiple.component.html' +}) +/** + * @class + * @classdesc Search multiple container. + */ +export class SearchMultipleComponent { + public instance: Observable<Instance>; + public currentStep: Observable<string>; + public positionStepChecked: Observable<boolean>; + public datasetsStepChecked: Observable<boolean>; + public resultStepChecked: Observable<boolean>; + public coneSearch: Observable<ConeSearch>; + public selectedDatasets: Observable<string[]>; + public queryParams: Observable<SearchMultipleQueryParams>; + + constructor(private store: Store<{ }>) { + this.instance = this.store.select(instanceSelector.selectInstanceByRouteName); + this.currentStep = this.store.select(searchMultipleSelector.selectCurrentStep); + this.positionStepChecked = this.store.select(searchMultipleSelector.selectPositionStepChecked); + this.datasetsStepChecked = this.store.select(searchMultipleSelector.selectDatasetsStepChecked); + this.resultStepChecked = this.store.select(searchMultipleSelector.selectResultStepChecked); + this.coneSearch = this.store.select(coneSearchSelector.selectConeSearch); + this.selectedDatasets = this.store.select(searchMultipleSelector.selectSelectedDatasets); + this.queryParams = this.store.select(searchMultipleSelector.selectQueryParams); + } +} diff --git a/client/src/app/instance/search-multiple/search-multiple.module.ts b/client/src/app/instance/search-multiple/search-multiple.module.ts index 6368aaf5ac0eed7e0f4be27e5839c470efd21e25..f309487d7a04e1e064c428c6a1303379f093d57d 100644 --- a/client/src/app/instance/search-multiple/search-multiple.module.ts +++ b/client/src/app/instance/search-multiple/search-multiple.module.ts @@ -10,13 +10,19 @@ import { NgModule } from '@angular/core'; import { SharedModule } from 'src/app/shared/shared.module'; +import { SharedSearchModule } from '../shared-search/shared-search.module'; import { SearchMultipleRoutingModule, routedComponents } from './search-multiple-routing.module'; +import { dummiesComponents } from './components'; @NgModule({ imports: [ SharedModule, + SharedSearchModule, SearchMultipleRoutingModule ], - declarations: [routedComponents] + declarations: [ + routedComponents, + dummiesComponents + ] }) export class SearchMultipleModule { } 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 ea951c32bd3106f1c745ad5e18876c73d592bad5..f3ec7c1649ba70a4fb2d5a3c4ae111451c49fd15 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 82f65348ed3b1cd56878cb116aa9eaae9053fec9..f62ca538ed8b10b2a94be5d2b5e3778e92907a22 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 59de49ada64fd52e4fcd09720480a1bb8d2daf7d..0bd90e12c4ef706ddd60ccb4bf6a151e8ea2eb6e 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 new file mode 100644 index 0000000000000000000000000000000000000000..87d20074517eeb51f87b1895a8c81f6d9a5161a8 --- /dev/null +++ b/client/src/app/instance/store/actions/search-multiple.actions.ts @@ -0,0 +1,26 @@ +/** + * 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 { 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 markAsDirty = createAction('[Search Multiple] Mark As Dirty'); +export const changeStep = createAction('[Search Multiple] Change Step', props<{ step: string }>()); +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<{ 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: SearchMultipleDatasetData[] }>()); +export const retrieveDataFail = createAction('[Search Multiple] Retrieve Data Fail'); diff --git a/client/src/app/instance/store/effects/index.ts b/client/src/app/instance/store/effects/index.ts index 132bb177b9768dd206195f68ed2d2eef1ca6a8fd..582dd51102f0f5b76cdf2db4ff4fd0e3e36307b6 100644 --- a/client/src/app/instance/store/effects/index.ts +++ b/client/src/app/instance/store/effects/index.ts @@ -1,11 +1,13 @@ import { SampEffects } from './samp.effects'; import { SearchEffects } from './search.effects'; +import { SearchMultipleEffects } from './search-multiple.effects'; import { ConeSearchEffects } from './cone-search.effects'; import { DetailEffects } from './detail.effects'; export const instanceEffects = [ SampEffects, SearchEffects, + SearchMultipleEffects, ConeSearchEffects, DetailEffects ]; diff --git a/client/src/app/instance/store/effects/search-multiple.effects.ts b/client/src/app/instance/store/effects/search-multiple.effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..84382d44732bfb1c719f7d0cbaa96e4326c693a3 --- /dev/null +++ b/client/src/app/instance/store/effects/search-multiple.effects.ts @@ -0,0 +1,132 @@ +/** + * 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 { Injectable } from '@angular/core'; + +import { Actions, createEffect, ofType, concatLatestFrom } from '@ngrx/effects'; +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 { SearchMultipleDatasetLength } from '../models'; +import { SearchService } from '../services/search.service'; +import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector'; +import * as datasetSelector from 'src/app/metamodel/selectors/dataset.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), + this.store.select(instanceSelector.selectInstanceByRouteName), + this.store.select(datasetSelector.selectAllConeSearchDatasets) + ]), + mergeMap(([action, pristine, coneSearchByRoute, selectedDatasetsByRoute, instance, datasetList]) => { + if (!pristine && !coneSearchByRoute) { + // Restart search + return [ + coneSearchActions.deleteConeSearch(), + searchMultipleActions.restartSearch() + ]; + } + + 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() + ); + } else if (instance.config.search.search_multiple_all_datasets_selected) { + const selectedDatasets = datasetList.map(dataset => dataset.name); + actions.push( + searchMultipleActions.updateSelectedDatasets({ selectedDatasets }) + ); + } + + // Returns actions and mark the form as dirty + return actions; + }) + ) + ); + + restartSearch$ = createEffect(() => + this.actions$.pipe( + ofType(searchMultipleActions.restartSearch), + map(() => searchMultipleActions.initSearch()) + ) + ); + + 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, + private store: Store<{ }>, + private toastr: ToastrService + ) {} +} diff --git a/client/src/app/instance/store/models/index.ts b/client/src/app/instance/store/models/index.ts index c471d6929ad3c505f8a5a9386d3d6f716d49e750..962eede1ad19a550541f007ee3155525a696c59c 100644 --- a/client/src/app/instance/store/models/index.ts +++ b/client/src/app/instance/store/models/index.ts @@ -1,6 +1,9 @@ export * from './criterion.model'; export * from './search-query-params.model'; +export * from './search-multiple-query-params.model'; 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/store/models/search-multiple-dataset-data.ts b/client/src/app/instance/store/models/search-multiple-dataset-data.ts new file mode 100644 index 0000000000000000000000000000000000000000..fa25ed88176093d7840cdd984c476aed52b25ed3 --- /dev/null +++ b/client/src/app/instance/store/models/search-multiple-dataset-data.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 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 0000000000000000000000000000000000000000..75961a0d5c68acd8878099d32715c9a4c9143242 --- /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/models/search-multiple-query-params.model.ts b/client/src/app/instance/store/models/search-multiple-query-params.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..9e4cd67b1047b0288bf8fcfabe5c895f41a1713a --- /dev/null +++ b/client/src/app/instance/store/models/search-multiple-query-params.model.ts @@ -0,0 +1,18 @@ +/** + * 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. + */ + +/** + * Interface for search multiple query parameters. + * + * @interface SearchMultipleQueryParams + */ +export interface SearchMultipleQueryParams { + cs?: string; + d?: string; +} diff --git a/client/src/app/instance/store/reducers/search-multiple.reducer.ts b/client/src/app/instance/store/reducers/search-multiple.reducer.ts new file mode 100644 index 0000000000000000000000000000000000000000..bd73fbba1df15c2853d55d21157e8a7f30fb1dbb --- /dev/null +++ b/client/src/app/instance/store/reducers/search-multiple.reducer.ts @@ -0,0 +1,114 @@ +/** + * 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 { createReducer, on } from '@ngrx/store'; + +import { SearchMultipleDatasetLength, SearchMultipleDatasetData } from '../models'; +import * as searchMultipleActions from '../actions/search-multiple.actions'; + +export interface State { + pristine: boolean; + currentStep: string; + positionStepChecked: boolean; + datasetsStepChecked: boolean; + resultStepChecked: boolean; + selectedDatasets: string[]; + dataLengthIsLoading: boolean; + dataLengthIsLoaded: boolean; + dataLength: SearchMultipleDatasetLength[]; + dataIsLoading: boolean; + dataIsLoaded: boolean; + data: SearchMultipleDatasetData[]; +} + +export const initialState: State = { + pristine: true, + currentStep: null, + positionStepChecked: false, + datasetsStepChecked: false, + resultStepChecked: false, + 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 + })), + on(searchMultipleActions.markAsDirty, state => ({ + ...state, + pristine: false + })), + on(searchMultipleActions.checkDatasets, state => ({ + ...state, + datasetsStepChecked: true + })), + on(searchMultipleActions.checkResult, state => ({ + ...state, + resultStepChecked: true + })), + 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 + })) +); + +export const selectPristine = (state: State) => state.pristine; +export const selectCurrentStep = (state: State) => state.currentStep; +export const selectPositionStepChecked = (state: State) => state.positionStepChecked; +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 new file mode 100644 index 0000000000000000000000000000000000000000..61ae2e99501760bef036054717ac11ad5408ad42 --- /dev/null +++ b/client/src/app/instance/store/selectors/search-multiple.selector.ts @@ -0,0 +1,108 @@ +/** + * 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 { createSelector } from '@ngrx/store'; + +import { SearchMultipleQueryParams, ConeSearch } from '../models'; +import * as reducer from '../../instance.reducer'; +import * as fromSearchMultiple from '../reducers/search-multiple.reducer'; +import * as coneSearchSelector from './cone-search.selector'; + +export const selectSearchMultipleState = createSelector( + reducer.getInstanceState, + (state: reducer.State) => state.searchMultiple +); + +export const selectPristine = createSelector( + selectSearchMultipleState, + fromSearchMultiple.selectPristine +); + +export const selectCurrentStep = createSelector( + selectSearchMultipleState, + fromSearchMultiple.selectCurrentStep +); + +export const selectPositionStepChecked = createSelector( + selectSearchMultipleState, + fromSearchMultiple.selectPositionStepChecked +); + +export const selectDatasetsStepChecked = createSelector( + selectSearchMultipleState, + fromSearchMultiple.selectDatasetsStepChecked +); + +export const selectResultStepChecked = createSelector( + selectSearchMultipleState, + fromSearchMultiple.selectResultStepChecked +); + +export const selectSelectedDatasets = createSelector( + selectSearchMultipleState, + 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, + ( + coneSearch: ConeSearch, + selectedDatasets: string[]) => { + let queryParams: SearchMultipleQueryParams = { }; + if (coneSearch) { + queryParams = { + ...queryParams, + cs: coneSearch.ra + ':' + coneSearch.dec + ':' + coneSearch.radius + }; + } + if (selectedDatasets.length > 0) { + queryParams = { + ...queryParams, + d: selectedDatasets.join(';') + }; + } + return queryParams; + } +); + +export const selectSelectedDatasetsByRoute = createSelector( + reducer.selectRouterState, + router => router.state.queryParams.d as string +); diff --git a/client/src/app/metamodel/selectors/dataset.selector.ts b/client/src/app/metamodel/selectors/dataset.selector.ts index 71294e9175bcb756fcbef983db7288edc3a18da3..d456b9e12640f2340cb11dd66d80c6edea60fb35 100644 --- a/client/src/app/metamodel/selectors/dataset.selector.ts +++ b/client/src/app/metamodel/selectors/dataset.selector.ts @@ -57,3 +57,8 @@ export const selectDatasetNameByRoute = createSelector( reducer.selectRouterState, router => router.state.params.dname as string ); + +export const selectAllConeSearchDatasets = createSelector( + selectAllDatasets, + datasetList => datasetList.filter(dataset => dataset.config.cone_search.cone_search_enabled) +); \ No newline at end of file