From 4342ff6581850d835c4322d7fb1c1ff554f18fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr> Date: Wed, 1 Sep 2021 17:07:03 +0200 Subject: [PATCH] Search multiple => WIP --- .../search-multiple/components/index.ts | 7 + .../progress-bar-multiple.component.html | 58 +++++++ .../progress-bar-multiple.component.scss | 120 +++++++++++++++ .../progress-bar-multiple.component.ts | 50 ++++++ .../summary-multiple.component.html | 63 ++++++++ .../summary-multiple.component.scss | 12 ++ .../components/summary-multiple.component.ts | 70 +++++++++ .../containers/datasets.component.html | 61 ++++++++ .../containers/datasets.component.ts | 98 ++++++++++++ .../containers/position.component.html | 42 ++++++ .../containers/position.component.scss | 12 ++ .../containers/position.component.ts | 96 ++++++++++++ .../containers/result-multiple.component.html | 39 +++++ .../containers/result-multiple.component.ts | 142 ++++++++++++++++++ .../search-multiple-routing.module.ts | 13 +- .../search-multiple.component.html | 12 ++ .../search-multiple.component.ts | 53 +++++++ .../search-multiple/search-multiple.module.ts | 8 +- .../store/actions/search-multiple.actions.ts | 11 ++ client/src/app/instance/store/models/index.ts | 1 + .../search-multiple-query-params.model.ts | 18 +++ 21 files changed, 982 insertions(+), 4 deletions(-) create mode 100644 client/src/app/instance/search-multiple/components/index.ts create mode 100644 client/src/app/instance/search-multiple/components/progress-bar-multiple.component.html create mode 100644 client/src/app/instance/search-multiple/components/progress-bar-multiple.component.scss create mode 100644 client/src/app/instance/search-multiple/components/progress-bar-multiple.component.ts create mode 100644 client/src/app/instance/search-multiple/components/summary-multiple.component.html create mode 100644 client/src/app/instance/search-multiple/components/summary-multiple.component.scss create mode 100644 client/src/app/instance/search-multiple/components/summary-multiple.component.ts create mode 100644 client/src/app/instance/search-multiple/containers/datasets.component.html create mode 100644 client/src/app/instance/search-multiple/containers/datasets.component.ts create mode 100644 client/src/app/instance/search-multiple/containers/position.component.html create mode 100644 client/src/app/instance/search-multiple/containers/position.component.scss create mode 100644 client/src/app/instance/search-multiple/containers/position.component.ts create mode 100644 client/src/app/instance/search-multiple/containers/result-multiple.component.html create mode 100644 client/src/app/instance/search-multiple/containers/result-multiple.component.ts create mode 100644 client/src/app/instance/search-multiple/search-multiple.component.html create mode 100644 client/src/app/instance/search-multiple/search-multiple.component.ts create mode 100644 client/src/app/instance/store/actions/search-multiple.actions.ts create mode 100644 client/src/app/instance/store/models/search-multiple-query-params.model.ts 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 00000000..d3d2236e --- /dev/null +++ b/client/src/app/instance/search-multiple/components/index.ts @@ -0,0 +1,7 @@ +import { ProgressBarMultipleComponent } from './progress-bar-multiple.component'; +import { SummaryMultipleComponent } from './summary-multiple.component'; + +export const dummiesComponents = [ + ProgressBarMultipleComponent, + SummaryMultipleComponent +]; 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 00000000..0dc2981b --- /dev/null +++ b/client/src/app/instance/search-multiple/components/progress-bar-multiple.component.html @@ -0,0 +1,58 @@ +<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 *ngIf="!noSelectedDatasets" class="nav-link" routerLink="/search-multiple/position" [queryParams]="queryParams" data-toggle="tab"> + <div class="icon-circle"> + <span class="fas fa-drafting-compass"></span> + </div> + Position + </a> + <a *ngIf="noSelectedDatasets" class="nav-link disabled" 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="isValidConeSearch" class="nav-link" routerLink="/search-multiple/datasets" [queryParams]="queryParams" data-toggle="tab"> + <div class="icon-circle"> + <span class="fas fa-book"></span> + </div> + Datasets + </a> + <a *ngIf="!isValidConeSearch" 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="isValidConeSearch && !noSelectedDatasets" class="nav-link" routerLink="/search-multiple/result" [queryParams]="queryParams" data-toggle="tab"> + <div class="icon-circle"> + <span class="fas fa-table"></span> + </div> + Result + </a> + <a *ngIf="!isValidConeSearch || noSelectedDatasets" 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 00000000..0a42a748 --- /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 00000000..6060716b --- /dev/null +++ b/client/src/app/instance/search-multiple/components/progress-bar-multiple.component.ts @@ -0,0 +1,50 @@ +/** + * 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 { SearchMultipleQueryParams } from '../store/model'; + +@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() currentStep: string; + @Input() positionStepChecked: boolean; + @Input() datasetsStepChecked: boolean; + @Input() resultStepChecked: boolean; + @Input() isValidConeSearch: boolean; + @Input() noSelectedDatasets: boolean; + @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/summary-multiple.component.html b/client/src/app/instance/search-multiple/components/summary-multiple.component.html new file mode 100644 index 00000000..b59975fc --- /dev/null +++ b/client/src/app/instance/search-multiple/components/summary-multiple.component.html @@ -0,0 +1,63 @@ +<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="isValidConeSearch" 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="!isValidConeSearch" class="pl-5 text-danger font-weight-bold"> + Not valid position! + </p> + <hr> + + <!-- Dataset List --> + <p class="text-center font-italic"> + Datasets + </p> + <span *ngIf="datasetSearchMetaIsLoading" class="row justify-content-center mt-5"> + <span class="fas fa-circle-notch fa-spin fa-3x"></span> + <span class="sr-only">Loading...</span> + </span> + <div *ngIf="datasetSearchMetaIsLoaded"> + <p *ngIf="noSelectedDatasets" class="pl-5 text-danger font-weight-bold"> + At least 1 dataset required! + </p> + <div *ngIf="!noSelectedDatasets"> + <!-- 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 00000000..c837d14e --- /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 00000000..fabeefdf --- /dev/null +++ b/client/src/app/instance/search-multiple/components/summary-multiple.component.ts @@ -0,0 +1,70 @@ +/** + * 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 } from '../store/model'; +import { Dataset, Family } from '../../metamodel/model'; +import { ConeSearch } from '../../shared/cone-search/store/model'; +import { sortByDisplay } from '../../shared/utils'; + +@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() isValidConeSearch: boolean; + @Input() coneSearch: ConeSearch; + @Input() datasetSearchMetaIsLoading: boolean; + @Input() datasetSearchMetaIsLoaded: boolean; + @Input() datasetFamilyList: Family[]; + @Input() datasetList: Dataset[]; + @Input() selectedDatasets: string[]; + @Input() noSelectedDatasets: boolean; + @Input() queryParams: SearchMultipleQueryParams; + + accordionFamilyIsOpen = true; + + /** + * Returns dataset families sorted by display, that contains datasets with cone search enabled. + * + * @return Family[] + */ + getDatasetFamilyList(): Family[] { + 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/datasets.component.html b/client/src/app/instance/search-multiple/containers/datasets.component.html new file mode 100644 index 00000000..4f05f1ef --- /dev/null +++ b/client/src/app/instance/search-multiple/containers/datasets.component.html @@ -0,0 +1,61 @@ +<div 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> + </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"> + </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> + </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> + </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 00000000..4ffa2ea0 --- /dev/null +++ b/client/src/app/instance/search-multiple/containers/datasets.component.ts @@ -0,0 +1,98 @@ +/** + * 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, OnInit } 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; +} + +@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>; + 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); + } + + 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()); + } + + /** + * 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)); + } +} 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 00000000..b9124abb --- /dev/null +++ b/client/src/app/instance/search-multiple/containers/position.component.html @@ -0,0 +1,42 @@ +<div 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="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> + </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"> + </app-summary-multiple> + </div> +</div> + +<div class="row mt-5 justify-content-end"> + <div class="col col-auto"> + <button *ngIf="!(isValidConeSearch | async)" class="btn btn-outline-primary disabled not-allowed" title="Not valid position!"> + Next <span class="fas fa-arrow-right"></span> + </button> + <a *ngIf="isValidConeSearch | async" + routerLink="/search-multiple/datasets" + [queryParams]="queryParams | async" + (click)="checkStep()" + class="btn btn-outline-primary"> + Next <span class="fas fa-arrow-right"></span> + </a> + </div> +</div> diff --git a/client/src/app/instance/search-multiple/containers/position.component.scss b/client/src/app/instance/search-multiple/containers/position.component.scss new file mode 100644 index 00000000..b2357fc4 --- /dev/null +++ b/client/src/app/instance/search-multiple/containers/position.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. + */ + +.disabled { + cursor: not-allowed; +} 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 00000000..f5541c63 --- /dev/null +++ b/client/src/app/instance/search-multiple/containers/position.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, OnInit } 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 datasetActions from '../../metamodel/action/dataset.action'; +import * as fromMetamodel from '../../metamodel/reducers'; +import * as metamodelSelector from '../../metamodel/selectors'; +import { Dataset, Family } from '../../metamodel/model'; +import * as coneSearchActions from '../../shared/cone-search/store/cone-search.action'; +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; +} + +@Component({ + selector: 'app-position', + templateUrl: 'position.component.html', + styleUrls: ['position.component.scss'] +}) +/** + * @class + * @classdesc Search multiple position container. + * + * @implements OnInit + */ +export class PositionComponent implements OnInit { + public datasetSearchMetaIsLoading: Observable<boolean>; + public datasetSearchMetaIsLoaded: Observable<boolean>; + public currentStep: Observable<string>; + public isValidConeSearch: Observable<boolean>; + public coneSearch: Observable<ConeSearch>; + public datasetFamilyList: Observable<Family[]>; + public datasetList: Observable<Dataset[]>; + 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.isValidConeSearch = this.store.select(coneSearchSelector.getIsValidConeSearch); + this.coneSearch = this.store.select(coneSearchSelector.getConeSearch); + this.datasetFamilyList = store.select(metamodelSelector.getDatasetFamilyList); + this.datasetList = store.select(metamodelSelector.getDatasetWithConeSearchList); + this.selectedDatasets = store.select(searchMultipleSelector.getSelectedDatasets); + this.noSelectedDatasets = store.select(searchMultipleSelector.getNoSelectedDatasets); + this.queryParams = this.store.select(searchMultipleSelector.getQueryParams); + } + + 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('position'))); + this.store.dispatch(new datasetActions.LoadDatasetSearchMetaAction()); + this.scrollTopService.setScrollTop(); + } + + /** + * Dispatches action to check position step. + */ + checkStep(): void { + this.store.dispatch(new searchMultipleActions.PositionCheckedAction()); + } + + /** + * Dispatches action to reset cone search. + */ + resetConeSearch(): void { + this.store.dispatch(new coneSearchActions.DeleteConeSearchAction()); + } +} 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 00000000..f1d2d75d --- /dev/null +++ b/client/src/app/instance/search-multiple/containers/result-multiple.component.html @@ -0,0 +1,39 @@ +<div class="row mt-4"> + <div class="col-12"> + <app-overview + [datasetSearchMetaIsLoaded]="datasetSearchMetaIsLoaded | async" + [coneSearch]="coneSearch | async" + [datasetFamilyList]="datasetFamilyList | async" + [datasetList]="datasetList | async" + [selectedDatasets]="selectedDatasets | async" + [queryParams]="queryParams | async" + [datasetsCountIsLoaded]="datasetsCountIsLoaded | async" + [datasetsCount]="datasetsCount | async" + (getDatasetCount)="getDatasetsCount()"> + </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 *ngIf="(datasetSearchMetaIsLoaded | async) && (datasetsCountIsLoaded | async)" class="row mt-5 justify-content-between"> + <div class="col"> + <a routerLink="/search-multiple/datasets" [queryParams]="queryParams | async" + class="btn btn-outline-secondary"> + <span class="fas fa-arrow-left"></span> Previous + </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 00000000..64b5ed34 --- /dev/null +++ b/client/src/app/instance/search-multiple/containers/result-multiple.component.ts @@ -0,0 +1,142 @@ +/** + * 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, OnInit, OnDestroy } from '@angular/core'; + +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; +} + +@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); + } + + 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(); + } + + /** + * 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()); + } +} 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 4438a686..3f84f0f1 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,16 @@ 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'; const routes: Routes = [ - //{ path: '', component: SearchMultipleComponent } + { + path: '', component: SearchMultipleComponent, children: [ + { path: '', redirectTo: 'position', pathMatch: 'full' }, + { path: 'dataset', component: PositionComponent } + ] + } ]; @NgModule({ @@ -23,5 +29,6 @@ const routes: Routes = [ export class SearchMultipleRoutingModule { } export const routedComponents = [ - //SearchMultipleComponent + SearchMultipleComponent, + PositionComponent ]; 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 00000000..c3301e3f --- /dev/null +++ b/client/src/app/instance/search-multiple/search-multiple.component.html @@ -0,0 +1,12 @@ +<div class="mx-1 mx-sm-5 px-1 px-sm-5"> + <app-progress-bar-multiple + [currentStep]="currentStep | async" + [positionStepChecked]="positionStepChecked | async" + [datasetsStepChecked]="datasetsStepChecked | async" + [resultStepChecked]="resultStepChecked | async" + [isValidConeSearch]="isValidConeSearch | async" + [noSelectedDatasets]="noSelectedDatasets | 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 00000000..e5d5e1b9 --- /dev/null +++ b/client/src/app/instance/search-multiple/search-multiple.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 { SearchMultipleQueryParams } from '../store/models'; +import { Instance } from 'src/app/metamodel/models'; + +import * as searchMultipleReducer from '../store/search-multiple.reducer'; +import * as searchMultipleSelector from '../store/search-multiple.selector'; +import * as searchActions from '../../search/store/search.action'; +import * as coneSearchSelector from '../../shared/cone-search/store/cone-search.selector'; +import * as coneSearchActions from '../../shared/cone-search/store/cone-search.action'; + +@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 isValidConeSearch: Observable<boolean>; + public noSelectedDatasets: Observable<boolean>; + public queryParams: Observable<SearchMultipleQueryParams>; + + constructor(private store: Store<searchMultipleReducer.State>) { + this.currentStep = store.select(searchMultipleSelector.getCurrentStep); + this.positionStepChecked = store.select(searchMultipleSelector.getPositionStepChecked); + this.datasetsStepChecked = store.select(searchMultipleSelector.getDatasetsStepChecked); + this.resultStepChecked = store.select(searchMultipleSelector.getResultStepChecked); + this.isValidConeSearch = store.select(coneSearchSelector.getIsValidConeSearch); + this.noSelectedDatasets = store.select(searchMultipleSelector.getNoSelectedDatasets); + this.queryParams = store.select(searchMultipleSelector.getQueryParams); + this.store.dispatch(new searchActions.ResetSearchAction()); + this.store.dispatch(new coneSearchActions.DeleteConeSearchAction()); + } +} 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 6368aaf5..f309487d 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/store/actions/search-multiple.actions.ts b/client/src/app/instance/store/actions/search-multiple.actions.ts new file mode 100644 index 00000000..b28b0ac2 --- /dev/null +++ b/client/src/app/instance/store/actions/search-multiple.actions.ts @@ -0,0 +1,11 @@ +/** + * 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'; + diff --git a/client/src/app/instance/store/models/index.ts b/client/src/app/instance/store/models/index.ts index c471d692..0f5c77df 100644 --- a/client/src/app/instance/store/models/index.ts +++ b/client/src/app/instance/store/models/index.ts @@ -1,5 +1,6 @@ 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'; 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 00000000..9e4cd67b --- /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; +} -- GitLab