Commit 07ae6258 authored by Jérémy Destin's avatar Jérémy Destin
Browse files

feat: Create component to manage large facets number. GNP-4309

parent 19bd3a37
......@@ -40,6 +40,7 @@ import { CardGenericDocumentComponent } from './card-generic-document/card-gener
import { MarkdownModule, MarkedOptions, MarkedRenderer } from 'ngx-markdown';
import { MarkdownPageComponent } from './markdown-page/markdown-page.component';
import { DecimalPipe } from '@angular/common';
import { LargeFacetsComponent } from './result-page/large-facets/large-facets.component';
@NgModule({
declarations: [
......@@ -64,7 +65,8 @@ import { DecimalPipe } from '@angular/common';
CardTableComponent,
XrefsComponent,
CardGenericDocumentComponent,
MarkdownPageComponent
MarkdownPageComponent,
LargeFacetsComponent
],
imports: [
BrowserModule,
......
......@@ -28,6 +28,7 @@ export class GnpisService {
static URGI_SOURCE_URI = 'https://urgi.versailles.inra.fr';
sourceByURI$ = new ReplaySubject<Record<string, DataDiscoverySource>>(1);
sources$ = new ReplaySubject<DataDiscoverySource[]>(1);
constructor(private http: HttpClient) {
// Get data sources
......@@ -39,6 +40,7 @@ export class GnpisService {
for (const dataSource of dataSources) {
sourceByURI[dataSource['@id']] = dataSource;
}
this.sources$.next(dataSources);
this.sourceByURI$.next(sourceByURI);
});
}
......
.card * {
font-size: 0.98em;
font-size: 1em;
}
.card h3 {
......
......@@ -7,9 +7,7 @@ import {
import { FormControl, FormGroup } from '@angular/forms';
import { BehaviorSubject } from 'rxjs';
import { filter } from 'rxjs/operators';
import { GnpisService } from '../../gnpis.service';
import { Params } from '@angular/router';
import { asArray } from '../../utils';
import { GermplasmSearchCriteria } from '../../models/gnpis.model';
@Component({
......@@ -32,7 +30,7 @@ export class FacetsComponent implements OnInit {
checkBoxes: FormGroup = new FormGroup({});
displayAdvanceGermplasmSearchButton: boolean;
constructor(private gnpisService: GnpisService) {
constructor() {
}
ngOnInit(): void {
......@@ -77,7 +75,6 @@ export class FacetsComponent implements OnInit {
}
if (this.germplasmSearchCriteria$) {
const field = this.facet.field;
// console.log(field);
if (field === 'holding institute') {
this.germplasmLocalCriteria = {
...this.germplasmLocalCriteria,
......@@ -133,13 +130,4 @@ export class FacetsComponent implements OnInit {
}
this.displayGermplasmResult$.next(!currentState);
}
queryParamsForGermplasmPage(criteria: DataDiscoveryCriteria) {
return {
crops: asArray(criteria.crops),
germplasmLists: asArray(criteria.germplasmLists),
accessions: asArray(criteria.accessions),
sources: asArray(criteria.sources)
};
}
}
<ng-template #resultTemplate let-facet="result" let-t="term">
<ng-container *ngIf="facet !== 'REFINE'; else refine">
<ngb-highlight [result]="displayableKey(facet.label)" [term]="t"></ngb-highlight>
<small class="ml-1 text-muted">({{ facet.count | number }})</small>
</ng-container>
<ng-template #refine>
<div class="text-muted small">Other results are available.<br/>Refine your search.</div>
</ng-template>
</ng-template>
<div class="mb-2">
<span class="badge badge-pill badge-secondary mr-1" *ngFor="let term of selectedTerms[facet.field]" tabindex="0"
(keydown.delete)="removeKey(term)"
(keydown.backspace)="removeKey(term)"> {{ displaySourceName(term) }}
<button tabindex="-1" type="button" class="btn btn-link" (click)="removeKey(term)">&times;</button>
</span>
</div>
<div class="card mb-1" *ngIf="facet.terms.length">
<div class="card-body">
<h3 class="card-title">{{ facet.field | titlecase }}</h3>
<input #typeahead class="form-control" [formControl]="criterion"
[ngbTypeahead]="search"
(selectItem)="selectKey($event)" [resultTemplate]="resultTemplate"
placeholder="Filter on {{ facet.field.toLowerCase() }}..."
(focus)="focus$.next($event.target.value)"/>
</div>
</div>
.card * {
font-size: 1em;
}
.btn-link {
border-top: 0;
border-bottom: 0;
margin-top: 0;
margin-bottom: 0;
padding: 0;
color: white;
vertical-align: baseline;
text-decoration: none;
font-size: inherit;
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LargeFacetsComponent } from './large-facets.component';
describe('LargeFacetsComponent', () => {
let component: LargeFacetsComponent;
let fixture: ComponentFixture<LargeFacetsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LargeFacetsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LargeFacetsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
import {
DataDiscoveryCriteria,
DataDiscoveryFacet,
DataDiscoverySource
} from '../../models/data-discovery.model';
import { BehaviorSubject, merge, Observable, Subject } from 'rxjs';
import { distinctUntilChanged, filter, map } from 'rxjs/operators';
import { FormControl } from '@angular/forms';
import { NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
import { GermplasmSearchCriteria } from '../../models/gnpis.model';
import { GnpisService } from '../../gnpis.service';
export type FacetTermOrRefine = {
term: string;
label: string;
count: number;
} | 'REFINE';
const maxResultsDisplayed = 8;
@Component({
selector: 'faidare-large-facets',
templateUrl: './large-facets.component.html',
styleUrls: ['./large-facets.component.scss']
})
export class LargeFacetsComponent implements OnInit {
@Input() facet: DataDiscoveryFacet;
@Input() criteria$: BehaviorSubject<DataDiscoveryCriteria>;
@Input() germplasmSearchCriteria$: BehaviorSubject<GermplasmSearchCriteria>;
@ViewChild('typeahead') typeahead: ElementRef<HTMLInputElement>;
localCriteria: DataDiscoveryCriteria;
germplasmLocalCriteria: GermplasmSearchCriteria;
focus$ = new Subject<string>();
selectedTerms: { [key: string]: string[]; } = {};
criterion = new FormControl('');
sources: DataDiscoverySource[];
constructor(private gnpisService: GnpisService) {
}
ngOnInit(): void {
this.gnpisService.sources$.subscribe(sources => {
this.sources = sources;
});
if (this.criteria$) {
this.criteria$.pipe(filter(c => c !== this.localCriteria))
.subscribe(criteria => {
this.localCriteria = { ...criteria };
this.selectedTerms[this.facet.field] = criteria[this.facet.field] || [];
});
}
if (this.germplasmSearchCriteria$) {
this.germplasmSearchCriteria$.pipe(filter(c => c !== this.germplasmLocalCriteria))
.subscribe(germplasmCriteria => {
this.germplasmLocalCriteria = germplasmCriteria;
this.selectedTerms[this.facet.field] = germplasmCriteria[this.facet.field] || [];
});
}
}
search = (text$: Observable<string>): Observable<Array<FacetTermOrRefine>> => {
const inputFocus$ = this.focus$;
const merged$ = merge(text$, inputFocus$)
.pipe(
distinctUntilChanged(),
map(searchTerm => {
const allMatchingBuckets = this.facet.terms
// returns values not already selected
.filter(terms => !this.selectedTerms[this.facet.field].includes(terms.term)
// and that contains the term, ignoring the case
&& this.displayableKey(terms.label).toLowerCase().includes(searchTerm.toString().toLowerCase()));
// return the first N results
const result: Array<FacetTermOrRefine> = allMatchingBuckets.slice(0, maxResultsDisplayed);
// if more results exist, add a fake refine bucket
if (allMatchingBuckets.length > maxResultsDisplayed) {
result.push('REFINE');
}
return result;
}));
return merged$;
};
displayableKey(key: string): string {
return key === 'NULL' ? 'None' : key;
}
displaySourceName(sourceId: string) {
for (const source of this.sources) {
if (source['@id'] === sourceId) {
return source['schema:name'];
}
}
return sourceId;
}
selectKey(event: NgbTypeaheadSelectItemEvent) {
event.preventDefault();
const selected: FacetTermOrRefine = event.item;
if (selected !== 'REFINE') {
// the item field of the event contains the facet term
// we push the selected key to our collection of keys
if (this.criteria$) {
if (this.localCriteria[this.facet.field]) {
this.localCriteria[this.facet.field].push(event.item.term);
} else {
this.localCriteria[this.facet.field] = [event.item.term];
}
}
if (this.germplasmSearchCriteria$) {
if (this.germplasmLocalCriteria[this.facet.field]) {
this.germplasmLocalCriteria[this.facet.field].push(event.item.term);
} else {
this.germplasmLocalCriteria[this.facet.field] = [event.item.term];
}
}
this.emitChanges();
}
this.typeahead.nativeElement.blur();
}
emitChanges() {
if (this.criteria$) {
this.criteria$.next(this.localCriteria);
}
if (this.germplasmSearchCriteria$) {
this.germplasmSearchCriteria$.next(this.germplasmLocalCriteria);
}
}
removeKey(key: string) {
this.selectedTerms[this.facet.field] =
this.removeFromList(this.selectedTerms[this.facet.field], key);
if (this.criteria$) {
this.localCriteria[this.facet.field] =
this.removeFromList(this.localCriteria[this.facet.field], key);
}
if (this.germplasmSearchCriteria$) {
this.germplasmLocalCriteria[this.facet.field] =
this.removeFromList(this.germplasmLocalCriteria[this.facet.field], key);
}
this.emitChanges();
}
removeFromList(list: string[], elem: string) {
const index = list.indexOf(elem);
return [
...list.slice(0, index),
...list.slice(index + 1)];
}
}
......@@ -12,7 +12,9 @@
[displayGermplasmResult$]="displayGermplasmResult$">
</faidare-facets>
</div>
<div class="row" *ngIf="germplasmfacets.length && displayGermplasmResult">
<!--<div class="row" *ngIf="germplasmfacets.length && displayGermplasmResult">
<faidare-facets
class="col-12 col-lg-12 col-sm-6"
*ngFor="let facet of germplasmfacets"
......@@ -20,8 +22,19 @@
[facet]="facet"
[displayGermplasmResult$]="displayGermplasmResult$">
</faidare-facets>
</div>-->
<div class="row" *ngIf="germplasmfacets.length && displayGermplasmResult">
<faidare-large-facets
class="col-12 col-lg-12 col-sm-6"
*ngFor="let facet of germplasmfacets"
[criteria$]="criteria$"
[facet]="facet"
[germplasmSearchCriteria$]="germplasmSearchCriteria$">
</faidare-large-facets>
</div>
</div>
<!-- Column for form and results-->
......@@ -36,12 +49,12 @@
<!-- Form -->
<faidare-form
#form
[criteria$]="criteria$"
[displayGermplasmResult$]="displayGermplasmResult$">
#form
[criteria$]="criteria$"
[displayGermplasmResult$]="displayGermplasmResult$">
</faidare-form>
<!-- Loading spinner-->
<!-- Loading spinner-->
<div class="text-center">
<faidare-loading-spinner [loading]="loading"></faidare-loading-spinner>
</div>
......
......@@ -31,7 +31,7 @@ export class ResultPageComponent implements OnInit {
facets: DataDiscoveryFacet[] = [];
germplasmSearchCriteria$ = new BehaviorSubject<GermplasmSearchCriteria>(DataDiscoveryCriteriaUtils.emptyGermplasmSearchCriteria());
germplasmfacets$ = new BehaviorSubject<DataDiscoveryFacet[]>([]);
germplasmfacets: DataDiscoveryFacet[];
germplasmfacets: DataDiscoveryFacet[] = [];
pagination = {
startResult: 1,
endResult: DEFAULT_PAGE_SIZE,
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment