mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-10 11:33:04 +00:00
Merge branch 'w2p-49440_Date-widget' into w2p-49440_date-widget-for-search-features
Conflicts: package.json src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts src/app/+search-page/search-filters/search-filter/search-filter.component.html src/app/+search-page/search-page.component.ts src/app/+search-page/search-page.module.ts src/app/shared/shared.module.ts
This commit is contained in:
@@ -87,6 +87,7 @@
|
|||||||
"@ngx-translate/core": "9.1.1",
|
"@ngx-translate/core": "9.1.1",
|
||||||
"@ngx-translate/http-loader": "2.0.1",
|
"@ngx-translate/http-loader": "2.0.1",
|
||||||
"angular-idle-preload": "2.0.4",
|
"angular-idle-preload": "2.0.4",
|
||||||
|
"angular2-moment": "^1.9.0",
|
||||||
"angulartics2": "^5.2.0",
|
"angulartics2": "^5.2.0",
|
||||||
"body-parser": "1.18.2",
|
"body-parser": "1.18.2",
|
||||||
"bootstrap": "^4.0.0",
|
"bootstrap": "^4.0.0",
|
||||||
@@ -104,8 +105,11 @@
|
|||||||
"jsonschema": "1.2.2",
|
"jsonschema": "1.2.2",
|
||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
"methods": "1.1.2",
|
"methods": "1.1.2",
|
||||||
|
"moment": "^2.22.1",
|
||||||
"morgan": "1.9.0",
|
"morgan": "1.9.0",
|
||||||
|
"ng2-nouislider": "^1.7.11",
|
||||||
"ngx-pagination": "3.0.3",
|
"ngx-pagination": "3.0.3",
|
||||||
|
"nouislider": "^11.0.0",
|
||||||
"pem": "1.12.3",
|
"pem": "1.12.3",
|
||||||
"reflect-metadata": "0.1.12",
|
"reflect-metadata": "0.1.12",
|
||||||
"rxjs": "5.5.6",
|
"rxjs": "5.5.6",
|
||||||
|
@@ -125,7 +125,12 @@
|
|||||||
"head": "Subject"
|
"head": "Subject"
|
||||||
},
|
},
|
||||||
"dateIssued": {
|
"dateIssued": {
|
||||||
"placeholder": "Date",
|
"max": {
|
||||||
|
"placeholder": "Minimum Date"
|
||||||
|
},
|
||||||
|
"min": {
|
||||||
|
"placeholder": "Maximum Date"
|
||||||
|
},
|
||||||
"head": "Date"
|
"head": "Date"
|
||||||
},
|
},
|
||||||
"has_content_in_original_bundle": {
|
"has_content_in_original_bundle": {
|
||||||
|
@@ -0,0 +1,22 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { FilterType } from '../../../search-service/filter-type.model';
|
||||||
|
import { renderFacetFor } from '../search-filter-type-decorator';
|
||||||
|
import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component renders a simple item page.
|
||||||
|
* The route parameter 'id' is used to request the item it represents.
|
||||||
|
* All fields of the item that should be displayed, are defined in its template.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-search-boolean-filter',
|
||||||
|
styleUrls: ['./search-boolean-filter.component.scss'],
|
||||||
|
templateUrl: './search-boolean-filter.component.html',
|
||||||
|
})
|
||||||
|
|
||||||
|
@renderFacetFor(FilterType.boolean)
|
||||||
|
export class SearchBooleanFilterComponent extends SearchFacetFilterComponent implements OnInit {
|
||||||
|
currentPage: Observable<number>;
|
||||||
|
}
|
@@ -0,0 +1 @@
|
|||||||
|
<ng-container *ngComponentOutlet="getSearchFilter(); injector: objectInjector;"></ng-container>
|
@@ -0,0 +1,36 @@
|
|||||||
|
import { Component, Injector, Input, OnInit } from '@angular/core';
|
||||||
|
import { renderFilterType } from '../search-filter-type-decorator';
|
||||||
|
import { FilterType } from '../../../search-service/filter-type.model';
|
||||||
|
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
||||||
|
import { FILTER_CONFIG, SELECTED_VALUES } from '../search-filter.service';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-search-facet-filter-wrapper',
|
||||||
|
templateUrl: './search-facet-filter-wrapper.component.html'
|
||||||
|
})
|
||||||
|
export class SearchFacetFilterWrapperComponent implements OnInit {
|
||||||
|
@Input() filterConfig: SearchFilterConfig;
|
||||||
|
@Input() selectedValues: Observable<string[]>;
|
||||||
|
objectInjector: Injector;
|
||||||
|
|
||||||
|
constructor(private injector: Injector) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.selectedValues.subscribe((values) => {
|
||||||
|
this.objectInjector = Injector.create({
|
||||||
|
providers: [
|
||||||
|
{ provide: FILTER_CONFIG, useFactory: () => (this.filterConfig), deps: [] },
|
||||||
|
{ provide: SELECTED_VALUES, useFactory: () => (values), deps: [] }],
|
||||||
|
|
||||||
|
parent: this.injector
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getSearchFilter(): string {
|
||||||
|
const type: FilterType = this.filterConfig.type;
|
||||||
|
return renderFilterType(type);
|
||||||
|
}
|
||||||
|
}
|
@@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { SearchFacetFilterComponent } from './search-facet-filter.component';
|
import { FILTER_CONFIG, SearchFilterService, SELECTED_VALUES } from '../search-filter.service';
|
||||||
import { SearchFilterService } from '../search-filter.service';
|
|
||||||
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
||||||
import { FilterType } from '../../../search-service/filter-type.model';
|
import { FilterType } from '../../../search-service/filter-type.model';
|
||||||
import { FacetValue } from '../../../search-service/facet-value.model';
|
import { FacetValue } from '../../../search-service/facet-value.model';
|
||||||
@@ -18,6 +17,7 @@ import { RouterStub } from '../../../../shared/testing/router-stub';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
|
import { SearchFacetFilterComponent } from './search-facet-filter.component';
|
||||||
|
|
||||||
describe('SearchFacetFilterComponent', () => {
|
describe('SearchFacetFilterComponent', () => {
|
||||||
let comp: SearchFacetFilterComponent;
|
let comp: SearchFacetFilterComponent;
|
||||||
@@ -64,6 +64,8 @@ describe('SearchFacetFilterComponent', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: SearchService, useValue: new SearchServiceStub(searchLink) },
|
{ provide: SearchService, useValue: new SearchServiceStub(searchLink) },
|
||||||
{ provide: Router, useValue: new RouterStub() },
|
{ provide: Router, useValue: new RouterStub() },
|
||||||
|
{ provide: FILTER_CONFIG, useValue: new SearchFilterConfig()},
|
||||||
|
{ provide: SELECTED_VALUES, useValue: {} },
|
||||||
{
|
{
|
||||||
provide: SearchFilterService, useValue: {
|
provide: SearchFilterService, useValue: {
|
||||||
isFilterActiveWithValue: (paramName: string, filterValue: string) => true,
|
isFilterActiveWithValue: (paramName: string, filterValue: string) => true,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
Input,
|
Inject,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit, QueryList,
|
OnInit, QueryList,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
@@ -11,7 +11,7 @@ import { FacetValue } from '../../../search-service/facet-value.model';
|
|||||||
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { SearchFilterService } from '../search-filter.service';
|
import { FILTER_CONFIG, SearchFilterService, SELECTED_VALUES } from '../search-filter.service';
|
||||||
import { hasNoValue, hasValue, isNotEmpty } from '../../../../shared/empty.util';
|
import { hasNoValue, hasValue, isNotEmpty } from '../../../../shared/empty.util';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
@@ -29,13 +29,10 @@ import { EmphasizePipe } from '../../../../shared/utils/emphasize.pipe';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-facet-filter',
|
selector: 'ds-search-facet-filter',
|
||||||
styleUrls: ['./search-facet-filter.component.scss'],
|
template: ``,
|
||||||
templateUrl: './search-facet-filter.component.html'
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
||||||
@Input() filterConfig: SearchFilterConfig;
|
|
||||||
@Input() selectedValues: string[];
|
|
||||||
filterValues: Array<Observable<RemoteData<PaginatedList<FacetValue>>>> = [];
|
filterValues: Array<Observable<RemoteData<PaginatedList<FacetValue>>>> = [];
|
||||||
filterValues$: BehaviorSubject<any> = new BehaviorSubject(this.filterValues);
|
filterValues$: BehaviorSubject<any> = new BehaviorSubject(this.filterValues);
|
||||||
currentPage: Observable<number>;
|
currentPage: Observable<number>;
|
||||||
@@ -45,7 +42,11 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
sub: Subscription;
|
sub: Subscription;
|
||||||
filterSearchResults: Observable<any[]> = Observable.of([]);
|
filterSearchResults: Observable<any[]> = Observable.of([]);
|
||||||
|
|
||||||
constructor(private searchService: SearchService, private filterService: SearchFilterService, private router: Router) {
|
constructor(protected searchService: SearchService,
|
||||||
|
protected filterService: SearchFilterService,
|
||||||
|
protected router: Router,
|
||||||
|
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig,
|
||||||
|
@Inject(SELECTED_VALUES) public selectedValues: string[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
import { FilterType } from '../../search-service/filter-type.model';
|
||||||
|
|
||||||
|
const filterTypeMap = new Map();
|
||||||
|
|
||||||
|
export function renderFacetFor(type: FilterType) {
|
||||||
|
return function decorator(objectElement: any) {
|
||||||
|
if (!objectElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
filterTypeMap.set(type, objectElement);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderFilterType(type: FilterType) {
|
||||||
|
return filterTypeMap.get(type);
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div (click)="toggle()" class="filter-name"><h5 class="d-inline-block mb-0">{{'search.filters.filter.' + filter.name + '.head'| translate}}</h5> <span class="filter-toggle fa float-right"
|
<div (click)="toggle()" class="filter-name"><h5 class="d-inline-block mb-0">{{'search.filters.filter.' + filter.name + '.head'| translate}}</h5> <span class="filter-toggle fa float-right"
|
||||||
[ngClass]="(isCollapsed() | async) ? 'fa-plus' : 'fa-minus'"></span></div>
|
[ngClass]="(isCollapsed() | async) ? 'fa-plus' : 'fa-minus'"></span></div>
|
||||||
<div [@slide]="(isCollapsed() | async) ? 'collapsed' : 'expanded'" (@slide.start)="startSlide($event)" (@slide.done)="finishSlide($event)" class="search-filter-wrapper" [ngClass]="{closed: collapsed}">
|
<div [@slide]="(isCollapsed() | async) ? 'collapsed' : 'expanded'" (@slide.start)="startSlide($event)" (@slide.done)="finishSlide($event)" class="search-filter-wrapper" [ngClass]="{'closed' : collapsed}">
|
||||||
<ds-search-facet-filter [filterConfig]="filter" [selectedValues]="getSelectedValues() | async"></ds-search-facet-filter>
|
<ds-search-facet-filter-wrapper [filterConfig]="filter" [selectedValues]="getSelectedValues() | async"></ds-search-facet-filter-wrapper>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable, InjectionToken } from '@angular/core';
|
||||||
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';
|
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';
|
||||||
import { SearchFiltersState, SearchFilterState } from './search-filter.reducer';
|
import { SearchFiltersState, SearchFilterState } from './search-filter.reducer';
|
||||||
import { createSelector, MemoizedSelector, Store } from '@ngrx/store';
|
import { createSelector, MemoizedSelector, Store } from '@ngrx/store';
|
||||||
@@ -23,6 +23,9 @@ import { PaginatedSearchOptions } from '../../paginated-search-options.model';
|
|||||||
|
|
||||||
const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
|
const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
|
||||||
|
|
||||||
|
export const FILTER_CONFIG: InjectionToken<SearchFilterConfig> = new InjectionToken<SearchFilterConfig>('filterConfig');
|
||||||
|
export const SELECTED_VALUES: InjectionToken<string[]> = new InjectionToken<string[]>('selectedValues');
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchFilterService {
|
export class SearchFilterService {
|
||||||
|
|
||||||
@@ -68,10 +71,31 @@ export class SearchFilterService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentFilters() {
|
getCurrentFilters(): Observable<any> {
|
||||||
return this.routeService.getQueryParamsWithPrefix('f.');
|
return this.routeService.getQueryParamsWithPrefix('f.').map((filterParams) => {
|
||||||
|
if (isNotEmpty(filterParams)) {
|
||||||
|
const params = {};
|
||||||
|
Object.keys(filterParams).forEach((key) => {
|
||||||
|
if (key.endsWith('.min') || key.endsWith('.max')) {
|
||||||
|
const realKey = key.slice(0, -4);
|
||||||
|
if (isEmpty(params[realKey])) {
|
||||||
|
const min = filterParams[realKey + '.min'][0] || '*';
|
||||||
|
const max = filterParams[realKey + '.max'][0] || '*';
|
||||||
|
params[realKey] = ['[' + min + ' TO ' + max + ']'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
params[key] = filterParams[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
return filterParams;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCurrentFrontendFilters(): Observable<any> {
|
||||||
|
return this.routeService.getQueryParamsWithPrefix('f.');
|
||||||
|
}
|
||||||
getCurrentView() {
|
getCurrentView() {
|
||||||
return this.routeService.getQueryParameterValue('view');
|
return this.routeService.getQueryParameterValue('view');
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,38 @@
|
|||||||
|
<div>
|
||||||
|
<div class="filters">
|
||||||
|
<a *ngFor="let value of selectedValues" class="d-block"
|
||||||
|
[routerLink]="[getSearchLink()]"
|
||||||
|
[queryParams]="getRemoveParams(value)" queryParamsHandling="merge">
|
||||||
|
<input type="checkbox" [checked]="true"/>
|
||||||
|
<span class="filter-value">{{value}}</span>
|
||||||
|
</a>
|
||||||
|
<ng-container *ngFor="let page of (filterValues$ | async)">
|
||||||
|
<ng-container *ngFor="let value of (page | async)?.payload.page; let i=index">
|
||||||
|
<a *ngIf="!selectedValues.includes(value.value)" class="d-block clearfix"
|
||||||
|
[routerLink]="[getSearchLink()]"
|
||||||
|
[queryParams]="getAddParams(value.value)" queryParamsHandling="merge" >
|
||||||
|
<input type="checkbox" [checked]="false"/>
|
||||||
|
<span class="filter-value">{{value.value}}</span>
|
||||||
|
<span class="float-right filter-value-count">
|
||||||
|
<span class="badge badge-secondary badge-pill">{{value.count}}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
<div class="clearfix toggle-more-filters">
|
||||||
|
<a class="float-left" *ngIf="!(isLastPage$ | async)"
|
||||||
|
(click)="showMore()">{{"search.filters.filter.show-more"
|
||||||
|
| translate}}</a>
|
||||||
|
<a class="float-right" *ngIf="(currentPage | async) > 1"
|
||||||
|
(click)="showFirstPageOnly()">{{"search.filters.filter.show-less"
|
||||||
|
| translate}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)" class="add-filter"
|
||||||
|
[action]="getCurrentUrl()">
|
||||||
|
<input type="text" [(ngModel)]="filter" [name]="filterConfig.paramName" class="form-control"
|
||||||
|
aria-label="New filter input"
|
||||||
|
[placeholder]="'search.filters.filter.' + filterConfig.name + '.placeholder'| translate" [ngModelOptions]="{standalone: true}"/>
|
||||||
|
<input type="submit" class="d-none"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
@@ -0,0 +1,18 @@
|
|||||||
|
@import '../../../../../styles/variables.scss';
|
||||||
|
@import '../../../../../styles/mixins.scss';
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
margin-top: $spacer/2;
|
||||||
|
margin-bottom: $spacer/2;
|
||||||
|
a {
|
||||||
|
color: $body-color;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toggle-more-filters a {
|
||||||
|
color: $link-color;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,23 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FacetValue } from '../../../search-service/facet-value.model';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { FilterType } from '../../../search-service/filter-type.model';
|
||||||
|
import { renderFacetFor } from '../search-filter-type-decorator';
|
||||||
|
import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component renders a simple item page.
|
||||||
|
* The route parameter 'id' is used to request the item it represents.
|
||||||
|
* All fields of the item that should be displayed, are defined in its template.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-search-hierarchy-filter',
|
||||||
|
styleUrls: ['./search-hierarchy-filter.component.scss'],
|
||||||
|
templateUrl: './search-hierarchy-filter.component.html',
|
||||||
|
})
|
||||||
|
|
||||||
|
@renderFacetFor(FilterType.hierarchy)
|
||||||
|
export class SearchHierarchyFilterComponent extends SearchFacetFilterComponent implements OnInit {
|
||||||
|
currentPage: Observable<number>;
|
||||||
|
}
|
@@ -0,0 +1,38 @@
|
|||||||
|
<div>
|
||||||
|
<div class="filters">
|
||||||
|
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)" class="add-filter row"
|
||||||
|
[action]="getCurrentUrl()">
|
||||||
|
<div class="col-6">
|
||||||
|
<input type="text" [ngModel]="range[0]" [name]="filterConfig.paramName + '.min'"
|
||||||
|
class="form-control" (blur)="onSubmit(form.value)"
|
||||||
|
aria-label="Mininum value"
|
||||||
|
[placeholder]="'search.filters.filter.' + filterConfig.name + '.min.placeholder'| translate"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<input type="text" [ngModel]="range[1]" [name]="filterConfig.paramName + '.max'"
|
||||||
|
class="form-control" (blur)="onSubmit(form.value)"
|
||||||
|
aria-label="Maximum value"
|
||||||
|
[placeholder]="'search.filters.filter.' + filterConfig.name + '.max.placeholder'| translate"/>
|
||||||
|
</div>
|
||||||
|
<input type="submit" class="d-none"/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ng-container *ngIf="shouldShowSlider()">
|
||||||
|
<nouislider [connect]="true" [min]="min" [max]="max" [step]="1"
|
||||||
|
[(ngModel)]="range" (mouseup)="onSubmit(form.value)" ngDefaultControl></nouislider>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngFor="let page of (filterValues$ | async)">
|
||||||
|
<ng-container *ngFor="let value of (page | async)?.payload.page">
|
||||||
|
<a *ngIf="!selectedValues.includes(value.value)" class="d-block clearfix"
|
||||||
|
[routerLink]="[getSearchLink()]"
|
||||||
|
[queryParams]="getAddParams(value.value)" queryParamsHandling="merge">
|
||||||
|
<span class="filter-value">{{value.value}}</span>
|
||||||
|
<span class="float-right filter-value-count">
|
||||||
|
<span class="badge badge-secondary badge-pill">{{value.count}}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,41 @@
|
|||||||
|
@import '../../../../../styles/variables.scss';
|
||||||
|
@import '../../../../../styles/mixins.scss';
|
||||||
|
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
margin-top: $spacer/2;
|
||||||
|
margin-bottom: $spacer/2;
|
||||||
|
a {
|
||||||
|
color: $link-color;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: $link-hover-color;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toggle-more-filters a {
|
||||||
|
color: $link-color;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$slider-handle-width: 18px;
|
||||||
|
::ng-deep
|
||||||
|
{
|
||||||
|
html:not([dir=rtl]) .noUi-horizontal .noUi-handle {
|
||||||
|
right: -$slider-handle-width/2;
|
||||||
|
}
|
||||||
|
.noUi-horizontal .noUi-handle {
|
||||||
|
width: $slider-handle-width;
|
||||||
|
&:before {
|
||||||
|
left: ($slider-handle-width - 2)/2 - 2;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
left: ($slider-handle-width - 2)/2 + 2;
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,139 @@
|
|||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { FILTER_CONFIG, SearchFilterService, SELECTED_VALUES } from '../search-filter.service';
|
||||||
|
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
||||||
|
import { FilterType } from '../../../search-service/filter-type.model';
|
||||||
|
import { FacetValue } from '../../../search-service/facet-value.model';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { SearchService } from '../../../search-service/search.service';
|
||||||
|
import { SearchServiceStub } from '../../../../shared/testing/search-service-stub';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
import { RouterStub } from '../../../../shared/testing/router-stub';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
|
import { SearchRangeFilterComponent } from './search-range-filter.component';
|
||||||
|
import { MockActivatedRoute } from '../../../../shared/mocks/mock-active-router';
|
||||||
|
|
||||||
|
describe('SearchFacetFilterComponent', () => {
|
||||||
|
let comp: SearchRangeFilterComponent;
|
||||||
|
let fixture: ComponentFixture<SearchRangeFilterComponent>;
|
||||||
|
const minSuffix = '.min';
|
||||||
|
const maxSuffix = '.max';
|
||||||
|
const dateFormats = ['YYYY', 'YYYY-MM', 'YYYY-MM-DD'];
|
||||||
|
const filterName1 = 'test name';
|
||||||
|
const value1 = '2000 - 2012';
|
||||||
|
const value2 = '1992 - 2000';
|
||||||
|
const value3 = '1990 - 1992';
|
||||||
|
const mockFilterConfig: SearchFilterConfig = Object.assign(new SearchFilterConfig(), {
|
||||||
|
name: filterName1,
|
||||||
|
type: FilterType.range,
|
||||||
|
hasFacets: false,
|
||||||
|
isOpenByDefault: false,
|
||||||
|
pageSize: 2,
|
||||||
|
minValue: 200,
|
||||||
|
maxValue: 3000,
|
||||||
|
});
|
||||||
|
const values: FacetValue[] = [
|
||||||
|
{
|
||||||
|
value: value1,
|
||||||
|
count: 52,
|
||||||
|
search: ''
|
||||||
|
}, {
|
||||||
|
value: value2,
|
||||||
|
count: 20,
|
||||||
|
search: ''
|
||||||
|
}, {
|
||||||
|
value: value3,
|
||||||
|
count: 5,
|
||||||
|
search: ''
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const searchLink = '/search';
|
||||||
|
const selectedValues = [value1];
|
||||||
|
let filterService;
|
||||||
|
let searchService;
|
||||||
|
let router;
|
||||||
|
const page = Observable.of(0);
|
||||||
|
const activatedRouteStub = new MockActivatedRoute();
|
||||||
|
|
||||||
|
const mockValues = Observable.of(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), values)));
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
|
||||||
|
declarations: [SearchRangeFilterComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: SearchService, useValue: new SearchServiceStub(searchLink) },
|
||||||
|
{ provide: Router, useValue: new RouterStub() },
|
||||||
|
{ provide: FILTER_CONFIG, useValue: mockFilterConfig},
|
||||||
|
{ provide: SELECTED_VALUES, useValue: selectedValues },
|
||||||
|
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||||
|
{
|
||||||
|
provide: SearchFilterService, useValue: {
|
||||||
|
isFilterActiveWithValue: (paramName: string, filterValue: string) => true,
|
||||||
|
getPage: (paramName: string) => page,
|
||||||
|
/* tslint:disable:no-empty */
|
||||||
|
incrementPage: (filterName: string) => {
|
||||||
|
},
|
||||||
|
resetPage: (filterName: string) => {
|
||||||
|
},
|
||||||
|
getSearchOptions: () => Observable.of({}),
|
||||||
|
/* tslint:enable:no-empty */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(SearchRangeFilterComponent, {
|
||||||
|
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SearchRangeFilterComponent);
|
||||||
|
comp = fixture.componentInstance; // SearchPageComponent test instance
|
||||||
|
comp.filterValues = [mockValues];
|
||||||
|
comp.filterValues$ = new BehaviorSubject(comp.filterValues);
|
||||||
|
filterService = (comp as any).filterService;
|
||||||
|
searchService = (comp as any).searchService;
|
||||||
|
spyOn(searchService, 'getFacetValuesFor').and.returnValue(mockValues);
|
||||||
|
router = (comp as any).router;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the getAddParams method is called wih a value', () => {
|
||||||
|
it('should return the selectedValue list with the new parameter value', () => {
|
||||||
|
const result = comp.getAddParams(value3);
|
||||||
|
expect(result[mockFilterConfig.paramName + minSuffix]).toEqual(['1990']);
|
||||||
|
expect(result[mockFilterConfig.paramName + maxSuffix]).toEqual(['1992']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the getRemoveParams method is called wih a value', () => {
|
||||||
|
it('should return the selectedValue list with the parameter value left out', () => {
|
||||||
|
const result = comp.getRemoveParams(value1);
|
||||||
|
expect(result[mockFilterConfig.paramName + minSuffix]).toBeNull();
|
||||||
|
expect(result[mockFilterConfig.paramName + maxSuffix]).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the onSubmit method is called with data', () => {
|
||||||
|
const searchUrl = '/search/path';
|
||||||
|
const data = { [mockFilterConfig.paramName + minSuffix]: '1900', [mockFilterConfig.paramName + maxSuffix]: '1950' };
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(comp, 'getSearchLink').and.returnValue(searchUrl);
|
||||||
|
comp.onSubmit(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call navigate on the router with the right searchlink and parameters', () => {
|
||||||
|
expect(router.navigate).toHaveBeenCalledWith([searchUrl], {
|
||||||
|
queryParams: { [mockFilterConfig.paramName + minSuffix]: ['1900'], [mockFilterConfig.paramName + maxSuffix]: ['1950']},
|
||||||
|
queryParamsHandling: 'merge'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,95 @@
|
|||||||
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
|
import { Component, Inject, OnInit, PLATFORM_ID } from '@angular/core';
|
||||||
|
import { FilterType } from '../../../search-service/filter-type.model';
|
||||||
|
import { renderFacetFor } from '../search-filter-type-decorator';
|
||||||
|
import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component';
|
||||||
|
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||||
|
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
||||||
|
import { FILTER_CONFIG, SearchFilterService, SELECTED_VALUES } from '../search-filter.service';
|
||||||
|
import { SearchService } from '../../../search-service/search.service';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component renders a simple item page.
|
||||||
|
* The route parameter 'id' is used to request the item it represents.
|
||||||
|
* All fields of the item that should be displayed, are defined in its template.
|
||||||
|
*/
|
||||||
|
const minSuffix = '.min';
|
||||||
|
const maxSuffix = '.max';
|
||||||
|
const dateFormats = ['YYYY', 'YYYY-MM', 'YYYY-MM-DD'];
|
||||||
|
const rangeDelimiter = '-';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-search-range-filter',
|
||||||
|
styleUrls: ['./search-range-filter.component.scss'],
|
||||||
|
templateUrl: './search-range-filter.component.html',
|
||||||
|
})
|
||||||
|
|
||||||
|
@renderFacetFor(FilterType.range)
|
||||||
|
export class SearchRangeFilterComponent extends SearchFacetFilterComponent implements OnInit {
|
||||||
|
min = 1950;
|
||||||
|
max = 2018;
|
||||||
|
range;
|
||||||
|
|
||||||
|
constructor(protected searchService: SearchService,
|
||||||
|
protected filterService: SearchFilterService,
|
||||||
|
protected router: Router,
|
||||||
|
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig,
|
||||||
|
@Inject(SELECTED_VALUES) public selectedValues: string[],
|
||||||
|
@Inject(PLATFORM_ID) private platformId: any,
|
||||||
|
private route: ActivatedRoute) {
|
||||||
|
super(searchService, filterService, router, filterConfig, selectedValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
this.min = moment(this.filterConfig.minValue, dateFormats).year() || this.min;
|
||||||
|
this.max = moment(this.filterConfig.maxValue, dateFormats).year() || this.max;
|
||||||
|
const iniMin = this.route.snapshot.queryParams[this.filterConfig.paramName + minSuffix] || this.min;
|
||||||
|
const iniMax = this.route.snapshot.queryParams[this.filterConfig.paramName + maxSuffix] || this.max;
|
||||||
|
this.range = [iniMin, iniMax];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
getAddParams(value: string) {
|
||||||
|
const parts = value.split(rangeDelimiter);
|
||||||
|
const min = parts.length > 1 ? parts[0].trim() : value;
|
||||||
|
const max = parts.length > 1 ? parts[1].trim() : value;
|
||||||
|
return {
|
||||||
|
[this.filterConfig.paramName + minSuffix]: [min],
|
||||||
|
[this.filterConfig.paramName + maxSuffix]: [max],
|
||||||
|
page: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getRemoveParams(value: string) {
|
||||||
|
return {
|
||||||
|
[this.filterConfig.paramName + minSuffix]: null,
|
||||||
|
[this.filterConfig.paramName + maxSuffix]: null,
|
||||||
|
page: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(data: any) {
|
||||||
|
if (isNotEmpty(data)) {
|
||||||
|
this.router.navigate([this.getSearchLink()], {
|
||||||
|
queryParams:
|
||||||
|
{
|
||||||
|
[this.filterConfig.paramName + minSuffix]: [data[this.filterConfig.paramName + minSuffix]],
|
||||||
|
[this.filterConfig.paramName + maxSuffix]: [data[this.filterConfig.paramName + maxSuffix]]
|
||||||
|
},
|
||||||
|
queryParamsHandling: 'merge'
|
||||||
|
});
|
||||||
|
this.filter = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO when upgrading nouislider, verify that this check is still needed.
|
||||||
|
*/
|
||||||
|
shouldShowSlider(): boolean {
|
||||||
|
return isPlatformBrowser(this.platformId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,38 @@
|
|||||||
|
<div>
|
||||||
|
<div class="filters">
|
||||||
|
<a *ngFor="let value of selectedValues" class="d-block"
|
||||||
|
[routerLink]="[getSearchLink()]"
|
||||||
|
[queryParams]="getRemoveParams(value)" queryParamsHandling="merge">
|
||||||
|
<input type="checkbox" [checked]="true"/>
|
||||||
|
<span class="filter-value">{{value}}</span>
|
||||||
|
</a>
|
||||||
|
<ng-container *ngFor="let page of (filterValues$ | async)">
|
||||||
|
<ng-container *ngFor="let value of (page | async)?.payload.page; let i=index">
|
||||||
|
<a *ngIf="!selectedValues.includes(value.value)" class="d-block clearfix"
|
||||||
|
[routerLink]="[getSearchLink()]"
|
||||||
|
[queryParams]="getAddParams(value.value)" queryParamsHandling="merge" >
|
||||||
|
<input type="checkbox" [checked]="false"/>
|
||||||
|
<span class="filter-value">{{value.value}}</span>
|
||||||
|
<span class="float-right filter-value-count">
|
||||||
|
<span class="badge badge-secondary badge-pill">{{value.count}}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
<div class="clearfix toggle-more-filters">
|
||||||
|
<a class="float-left" *ngIf="!(isLastPage$ | async)"
|
||||||
|
(click)="showMore()">{{"search.filters.filter.show-more"
|
||||||
|
| translate}}</a>
|
||||||
|
<a class="float-right" *ngIf="(currentPage | async) > 1"
|
||||||
|
(click)="showFirstPageOnly()">{{"search.filters.filter.show-less"
|
||||||
|
| translate}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)" class="add-filter"
|
||||||
|
[action]="getCurrentUrl()">
|
||||||
|
<input type="text" [(ngModel)]="filter" [name]="filterConfig.paramName" class="form-control"
|
||||||
|
aria-label="New filter input"
|
||||||
|
[placeholder]="'search.filters.filter.' + filterConfig.name + '.placeholder'| translate" [ngModelOptions]="{standalone: true}"/>
|
||||||
|
<input type="submit" class="d-none"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
@@ -0,0 +1,18 @@
|
|||||||
|
@import '../../../../../styles/variables.scss';
|
||||||
|
@import '../../../../../styles/mixins.scss';
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
margin-top: $spacer/2;
|
||||||
|
margin-bottom: $spacer/2;
|
||||||
|
a {
|
||||||
|
color: $body-color;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toggle-more-filters a {
|
||||||
|
color: $link-color;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,23 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FacetValue } from '../../../search-service/facet-value.model';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { FilterType } from '../../../search-service/filter-type.model';
|
||||||
|
import { renderFacetFor } from '../search-filter-type-decorator';
|
||||||
|
import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component renders a simple item page.
|
||||||
|
* The route parameter 'id' is used to request the item it represents.
|
||||||
|
* All fields of the item that should be displayed, are defined in its template.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-search-text-filter',
|
||||||
|
styleUrls: ['./search-text-filter.component.scss'],
|
||||||
|
templateUrl: './search-text-filter.component.html',
|
||||||
|
})
|
||||||
|
|
||||||
|
@renderFacetFor(FilterType.text)
|
||||||
|
export class SearchTextFilterComponent extends SearchFacetFilterComponent implements OnInit {
|
||||||
|
currentPage: Observable<number>;
|
||||||
|
}
|
@@ -24,7 +24,7 @@ describe('SearchFiltersComponent', () => {
|
|||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
};
|
};
|
||||||
const searchFilterServiceStub = jasmine.createSpyObj('SearchFilterService', {
|
const searchFilterServiceStub = jasmine.createSpyObj('SearchFilterService', {
|
||||||
getCurrentFilters: Observable.of({})
|
getCurrentFrontendFilters: Observable.of({})
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
@@ -22,7 +22,7 @@ export class SearchFiltersComponent {
|
|||||||
clearParams;
|
clearParams;
|
||||||
constructor(private searchService: SearchService, private filterService: SearchFilterService) {
|
constructor(private searchService: SearchService, private filterService: SearchFilterService) {
|
||||||
this.filters = searchService.getConfig();
|
this.filters = searchService.getConfig();
|
||||||
this.clearParams = filterService.getCurrentFilters().map((filters) => {Object.keys(filters).forEach((f) => filters[f] = null); return filters;});
|
this.clearParams = filterService.getCurrentFrontendFilters().map((filters) => {Object.keys(filters).forEach((f) => filters[f] = null); return filters;});
|
||||||
}
|
}
|
||||||
|
|
||||||
getSearchLink() {
|
getSearchLink() {
|
||||||
|
@@ -22,6 +22,11 @@ import { SearchFilterComponent } from './search-filters/search-filter/search-fil
|
|||||||
import { SearchFacetFilterComponent } from './search-filters/search-filter/search-facet-filter/search-facet-filter.component';
|
import { SearchFacetFilterComponent } from './search-filters/search-filter/search-facet-filter/search-facet-filter.component';
|
||||||
import { SearchFilterService } from './search-filters/search-filter/search-filter.service';
|
import { SearchFilterService } from './search-filters/search-filter/search-filter.service';
|
||||||
import { SearchLabelsComponent } from './search-labels/search-labels.component';
|
import { SearchLabelsComponent } from './search-labels/search-labels.component';
|
||||||
|
import { SearchRangeFilterComponent } from './search-filters/search-filter/search-range-filter/search-range-filter.component';
|
||||||
|
import { SearchTextFilterComponent } from './search-filters/search-filter/search-text-filter/search-text-filter.component';
|
||||||
|
import { SearchFacetFilterWrapperComponent } from './search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component';
|
||||||
|
import { SearchBooleanFilterComponent } from './search-filters/search-filter/search-boolean-filter/search-boolean-filter.component';
|
||||||
|
import { SearchHierarchyFilterComponent } from './search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component';
|
||||||
|
|
||||||
const effects = [
|
const effects = [
|
||||||
SearchSidebarEffects
|
SearchSidebarEffects
|
||||||
@@ -50,7 +55,13 @@ const effects = [
|
|||||||
SearchFiltersComponent,
|
SearchFiltersComponent,
|
||||||
SearchFilterComponent,
|
SearchFilterComponent,
|
||||||
SearchFacetFilterComponent,
|
SearchFacetFilterComponent,
|
||||||
SearchLabelsComponent
|
SearchLabelsComponent,
|
||||||
|
SearchFacetFilterComponent,
|
||||||
|
SearchFacetFilterWrapperComponent,
|
||||||
|
SearchRangeFilterComponent,
|
||||||
|
SearchTextFilterComponent,
|
||||||
|
SearchHierarchyFilterComponent,
|
||||||
|
SearchBooleanFilterComponent,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SearchService,
|
SearchService,
|
||||||
@@ -64,6 +75,11 @@ const effects = [
|
|||||||
ItemSearchResultGridElementComponent,
|
ItemSearchResultGridElementComponent,
|
||||||
CollectionSearchResultGridElementComponent,
|
CollectionSearchResultGridElementComponent,
|
||||||
CommunitySearchResultGridElementComponent,
|
CommunitySearchResultGridElementComponent,
|
||||||
|
SearchFacetFilterComponent,
|
||||||
|
SearchRangeFilterComponent,
|
||||||
|
SearchTextFilterComponent,
|
||||||
|
SearchHierarchyFilterComponent,
|
||||||
|
SearchBooleanFilterComponent,
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class SearchPageModule {
|
export class SearchPageModule {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
export enum FilterType {
|
export enum FilterType {
|
||||||
text,
|
text = 'text',
|
||||||
date,
|
range = 'date',
|
||||||
hierarchical,
|
hierarchy = 'hierarchical',
|
||||||
standard
|
boolean = 'standard'
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,12 @@
|
|||||||
|
|
||||||
@autoserialize
|
@autoserialize
|
||||||
isOpenByDefault: boolean;
|
isOpenByDefault: boolean;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
maxValue: string;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
minValue: string;
|
||||||
/**
|
/**
|
||||||
* Name of this configuration that can be used in a url
|
* Name of this configuration that can be used in a url
|
||||||
* @returns Parameter name
|
* @returns Parameter name
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
@import '../styles/variables.scss';
|
@import '../styles/variables.scss';
|
||||||
@import '../../node_modules/bootstrap/scss/bootstrap.scss';
|
@import '../../node_modules/bootstrap/scss/bootstrap.scss';
|
||||||
|
@import '../../node_modules/nouislider/distribute/nouislider.min.css';
|
||||||
@import "../../node_modules/font-awesome/scss/font-awesome.scss";
|
@import "../../node_modules/font-awesome/scss/font-awesome.scss";
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
@@ -26,7 +26,7 @@ import { Metadatum } from '../shared/metadatum.model';
|
|||||||
|
|
||||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||||
import { BitstreamFormat } from '../shared/bitstream-format.model';
|
import { BitstreamFormat } from '../shared/bitstream-format.model';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MetadataService {
|
export class MetadataService {
|
||||||
@@ -269,11 +269,9 @@ export class MetadataService {
|
|||||||
private setCitationPdfUrlTag(): void {
|
private setCitationPdfUrlTag(): void {
|
||||||
if (this.currentObject.value instanceof Item) {
|
if (this.currentObject.value instanceof Item) {
|
||||||
const item = this.currentObject.value as Item;
|
const item = this.currentObject.value as Item;
|
||||||
// NOTE: Observable resolves many times with same data
|
item.getFiles().filter((files) => isNotEmpty(files)).first().subscribe((bitstreams: Bitstream[]) => {
|
||||||
// taking only two, fist one is empty array
|
|
||||||
item.getFiles().take(2).subscribe((bitstreams: Bitstream[]) => {
|
|
||||||
for (const bitstream of bitstreams) {
|
for (const bitstream of bitstreams) {
|
||||||
bitstream.format.take(1)
|
bitstream.format.first()
|
||||||
.map((rd: RemoteData<BitstreamFormat>) => rd.payload)
|
.map((rd: RemoteData<BitstreamFormat>) => rd.payload)
|
||||||
.filter((format: BitstreamFormat) => hasValue(format))
|
.filter((format: BitstreamFormat) => hasValue(format))
|
||||||
.subscribe((format: BitstreamFormat) => {
|
.subscribe((format: BitstreamFormat) => {
|
||||||
|
@@ -29,6 +29,6 @@ export class MockActivatedRoute {
|
|||||||
|
|
||||||
// ActivatedRoute.snapshot.params
|
// ActivatedRoute.snapshot.params
|
||||||
get snapshot() {
|
get snapshot() {
|
||||||
return { params: this.testParams };
|
return { params: this.testParams, queryParams: this.testParams };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { NouisliderModule } from 'ng2-nouislider';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@@ -54,6 +54,7 @@ import { ClickOutsideDirective } from './utils/click-outside.directive';
|
|||||||
import { EmphasizePipe } from './utils/emphasize.pipe';
|
import { EmphasizePipe } from './utils/emphasize.pipe';
|
||||||
import { InputSuggestionsComponent } from './input-suggestions/input-suggestions.component';
|
import { InputSuggestionsComponent } from './input-suggestions/input-suggestions.component';
|
||||||
import { CapitalizePipe } from './utils/capitalize.pipe';
|
import { CapitalizePipe } from './utils/capitalize.pipe';
|
||||||
|
import { MomentModule } from 'angular2-moment';
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -63,7 +64,9 @@ const MODULES = [
|
|||||||
NgxPaginationModule,
|
NgxPaginationModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
TranslateModule
|
TranslateModule,
|
||||||
|
NouisliderModule,
|
||||||
|
MomentModule
|
||||||
];
|
];
|
||||||
|
|
||||||
const PIPES = [
|
const PIPES = [
|
||||||
|
18
yarn.lock
18
yarn.lock
@@ -425,6 +425,12 @@ angular-idle-preload@2.0.4:
|
|||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/angular-idle-preload/-/angular-idle-preload-2.0.4.tgz#7b177c0f52918c090e5c345480b922297cd59a0d"
|
resolved "https://registry.yarnpkg.com/angular-idle-preload/-/angular-idle-preload-2.0.4.tgz#7b177c0f52918c090e5c345480b922297cd59a0d"
|
||||||
|
|
||||||
|
angular2-moment@^1.9.0:
|
||||||
|
version "1.9.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/angular2-moment/-/angular2-moment-1.9.0.tgz#d198a4d9bc825f61de19106ac7ea07a78569f5a1"
|
||||||
|
dependencies:
|
||||||
|
moment "^2.19.3"
|
||||||
|
|
||||||
angular2-template-loader@0.6.2:
|
angular2-template-loader@0.6.2:
|
||||||
version "0.6.2"
|
version "0.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/angular2-template-loader/-/angular2-template-loader-0.6.2.tgz#c0d44e90fff0fac95e8b23f043acda7fd1c51d7c"
|
resolved "https://registry.yarnpkg.com/angular2-template-loader/-/angular2-template-loader-0.6.2.tgz#c0d44e90fff0fac95e8b23f043acda7fd1c51d7c"
|
||||||
@@ -5503,6 +5509,10 @@ module-deps@^4.0.8:
|
|||||||
through2 "^2.0.0"
|
through2 "^2.0.0"
|
||||||
xtend "^4.0.0"
|
xtend "^4.0.0"
|
||||||
|
|
||||||
|
moment@^2.19.3, moment@^2.22.1:
|
||||||
|
version "2.22.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad"
|
||||||
|
|
||||||
morgan@1.9.0:
|
morgan@1.9.0:
|
||||||
version "1.9.0"
|
version "1.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.0.tgz#d01fa6c65859b76fcf31b3cb53a3821a311d8051"
|
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.0.tgz#d01fa6c65859b76fcf31b3cb53a3821a311d8051"
|
||||||
@@ -5599,6 +5609,10 @@ netmask@~1.0.4:
|
|||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35"
|
resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35"
|
||||||
|
|
||||||
|
ng2-nouislider@^1.7.11:
|
||||||
|
version "1.7.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/ng2-nouislider/-/ng2-nouislider-1.7.11.tgz#b8ba5e3d2ffc23e1e32dfe54dd1726e2b4be316b"
|
||||||
|
|
||||||
ngrx-store-freeze@^0.2.1:
|
ngrx-store-freeze@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/ngrx-store-freeze/-/ngrx-store-freeze-0.2.1.tgz#04fb29db33cafda0f2d6ea32adeaac4891b1b27b"
|
resolved "https://registry.yarnpkg.com/ngrx-store-freeze/-/ngrx-store-freeze-0.2.1.tgz#04fb29db33cafda0f2d6ea32adeaac4891b1b27b"
|
||||||
@@ -5818,6 +5832,10 @@ normalize-url@^1.4.0:
|
|||||||
query-string "^4.1.0"
|
query-string "^4.1.0"
|
||||||
sort-keys "^1.0.0"
|
sort-keys "^1.0.0"
|
||||||
|
|
||||||
|
nouislider@^11.0.0:
|
||||||
|
version "11.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/nouislider/-/nouislider-11.1.0.tgz#1768eb5b854917325d41b96f2dc4eb3757d73381"
|
||||||
|
|
||||||
npm-run-all@4.1.2:
|
npm-run-all@4.1.2:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.2.tgz#90d62d078792d20669139e718621186656cea056"
|
resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.2.tgz#90d62d078792d20669139e718621186656cea056"
|
||||||
|
Reference in New Issue
Block a user