mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
63724: new configuration based search-page component
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<ds-filtered-search-page
|
<ds-filtered-search-page
|
||||||
[fixedFilterQuery]="fixedFilter"
|
[fixedFilterQuery]="fixedFilter"
|
||||||
[fixedFilter$]="fixedFilter$"
|
[configuration$]="configuration$"
|
||||||
[searchEnabled]="searchEnabled"
|
[searchEnabled]="searchEnabled"
|
||||||
[sideBarWidth]="sideBarWidth">
|
[sideBarWidth]="sideBarWidth">
|
||||||
</ds-filtered-search-page>
|
</ds-filtered-search-page>
|
||||||
|
@@ -47,9 +47,9 @@ describe('RelatedEntitiesSearchComponent', () => {
|
|||||||
expect(comp.fixedFilter).toEqual(mockFilter);
|
expect(comp.fixedFilter).toEqual(mockFilter);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a fixedFilter$', () => {
|
it('should create a configuration$', () => {
|
||||||
comp.fixedFilter$.subscribe((fixedFilter) => {
|
comp.configuration$.subscribe((configuration) => {
|
||||||
expect(fixedFilter).toEqual(mockRelationEntityType);
|
expect(configuration).toEqual(mockRelationEntityType);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -47,7 +47,7 @@ export class RelatedEntitiesSearchComponent implements OnInit {
|
|||||||
@Input() sideBarWidth = 4;
|
@Input() sideBarWidth = 4;
|
||||||
|
|
||||||
fixedFilter: string;
|
fixedFilter: string;
|
||||||
fixedFilter$: Observable<string>;
|
configuration$: Observable<string>;
|
||||||
|
|
||||||
constructor(private fixedFilterService: SearchFixedFilterService) {
|
constructor(private fixedFilterService: SearchFixedFilterService) {
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ export class RelatedEntitiesSearchComponent implements OnInit {
|
|||||||
this.fixedFilter = this.fixedFilterService.getFilterByRelation(this.relationType, this.item.id);
|
this.fixedFilter = this.fixedFilterService.getFilterByRelation(this.relationType, this.item.id);
|
||||||
}
|
}
|
||||||
if (isNotEmpty(this.relationEntityType)) {
|
if (isNotEmpty(this.relationEntityType)) {
|
||||||
this.fixedFilter$ = of(this.relationEntityType);
|
this.configuration$ = of(this.relationEntityType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,21 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { configureSearchComponentTestingModule } from './search-page.component.spec';
|
||||||
|
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
||||||
|
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
||||||
|
|
||||||
|
describe('ConfigurationSearchPageComponent', () => {
|
||||||
|
let comp: ConfigurationSearchPageComponent;
|
||||||
|
let fixture: ComponentFixture<ConfigurationSearchPageComponent>;
|
||||||
|
let searchConfigService: SearchConfigurationService;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
configureSearchComponentTestingModule(ConfigurationSearchPageComponent);
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ConfigurationSearchPageComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
searchConfigService = (comp as any).searchConfigService;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
});
|
71
src/app/+search-page/configuration-search-page.component.ts
Normal file
71
src/app/+search-page/configuration-search-page.component.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { HostWindowService } from '../shared/host-window.service';
|
||||||
|
import { SearchService } from './search-service/search.service';
|
||||||
|
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
||||||
|
import { SearchPageComponent } from './search-page.component';
|
||||||
|
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
|
||||||
|
import { pushInOut } from '../shared/animations/push';
|
||||||
|
import { RouteService } from '../shared/services/route.service';
|
||||||
|
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
||||||
|
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component renders a search page using a configuration as input.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-configuration-search-page',
|
||||||
|
styleUrls: ['./search-page.component.scss'],
|
||||||
|
templateUrl: './search-page.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
animations: [pushInOut],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: SEARCH_CONFIG_SERVICE,
|
||||||
|
useClass: SearchConfigurationService
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
export class ConfigurationSearchPageComponent extends SearchPageComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* The configuration to use for the search options
|
||||||
|
* If empty, the configuration will be determined by the route parameter called 'configuration'
|
||||||
|
*/
|
||||||
|
@Input() configuration: string;
|
||||||
|
|
||||||
|
constructor(protected service: SearchService,
|
||||||
|
protected sidebarService: SearchSidebarService,
|
||||||
|
protected windowService: HostWindowService,
|
||||||
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||||
|
protected routeService: RouteService) {
|
||||||
|
super(service, sidebarService, windowService, searchConfigService, routeService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listening to changes in the paginated search options
|
||||||
|
* If something changes, update the search results
|
||||||
|
*
|
||||||
|
* Listen to changes in the scope
|
||||||
|
* If something changes, update the list of scopes for the dropdown
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current paginated search options after updating the configuration using the configuration input
|
||||||
|
* This is to make sure the configuration is included in the paginated search options, as it is not part of any
|
||||||
|
* query or route parameters
|
||||||
|
* @returns {Observable<PaginatedSearchOptions>}
|
||||||
|
*/
|
||||||
|
protected getSearchOptions(): Observable<PaginatedSearchOptions> {
|
||||||
|
return this.searchConfigService.paginatedSearchOptions.pipe(
|
||||||
|
map((options: PaginatedSearchOptions) => {
|
||||||
|
const config = this.configuration || options.configuration;
|
||||||
|
return Object.assign(options, { configuration: config });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
22
src/app/+search-page/configuration-search-page.guard.ts
Normal file
22
src/app/+search-page/configuration-search-page.guard.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
/**
|
||||||
|
* Assemble the correct i18n key for the configuration search page's title depending on the current route's configuration parameter.
|
||||||
|
* The format of the key will be "{configuration}.search.title" with:
|
||||||
|
* - configuration: The current configuration stored in route.params
|
||||||
|
*/
|
||||||
|
export class ConfigurationSearchPageGuard implements CanActivate {
|
||||||
|
canActivate(
|
||||||
|
route: ActivatedRouteSnapshot,
|
||||||
|
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
|
||||||
|
const configuration = route.params.configuration;
|
||||||
|
|
||||||
|
const newTitle = configuration + '.search.title';
|
||||||
|
|
||||||
|
route.data = { title: newTitle };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,22 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
/**
|
|
||||||
* Assemble the correct i18n key for the filtered search page's title depending on the current route's filter parameter.
|
|
||||||
* The format of the key will be "{filter}.search.title" with:
|
|
||||||
* - filter: The current filter stored in route.params
|
|
||||||
*/
|
|
||||||
export class FilteredSearchPageGuard implements CanActivate {
|
|
||||||
canActivate(
|
|
||||||
route: ActivatedRouteSnapshot,
|
|
||||||
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
|
|
||||||
const filter = route.params.filter;
|
|
||||||
|
|
||||||
const newTitle = filter + '.search.title';
|
|
||||||
|
|
||||||
route.data = { title: newTitle };
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,14 +2,14 @@ import { NgModule } from '@angular/core';
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { SearchPageComponent } from './search-page.component';
|
import { SearchPageComponent } from './search-page.component';
|
||||||
import { FilteredSearchPageComponent } from './filtered-search-page.component';
|
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
||||||
import { FilteredSearchPageGuard } from './filtered-search-page.guard';
|
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild([
|
RouterModule.forChild([
|
||||||
{ path: '', component: SearchPageComponent, data: { title: 'search.title' } },
|
{ path: '', component: SearchPageComponent, data: { title: 'search.title' } },
|
||||||
{ path: ':filter', component: FilteredSearchPageComponent, canActivate: [FilteredSearchPageGuard]}
|
{ path: ':configuration', component: ConfigurationSearchPageComponent, canActivate: [ConfigurationSearchPageGuard]}
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@@ -33,7 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ds-search-results [searchResults]="resultsRD$ | async"
|
<ds-search-results [searchResults]="resultsRD$ | async"
|
||||||
[searchConfig]="searchOptions$ | async"
|
[searchConfig]="searchOptions$ | async"
|
||||||
[fixedFilter]="fixedFilter$ | async"
|
[configuration]="configuration$ | async"
|
||||||
[disableHeader]="!searchEnabled"></ds-search-results>
|
[disableHeader]="!searchEnabled"></ds-search-results>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -86,10 +86,10 @@ export class SearchPageComponent implements OnInit {
|
|||||||
sideBarWidth = 3;
|
sideBarWidth = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The currently applied filter (determines title of search)
|
* The currently applied configuration (determines title of search)
|
||||||
*/
|
*/
|
||||||
@Input()
|
@Input()
|
||||||
fixedFilter$: Observable<string>;
|
configuration$: Observable<string>;
|
||||||
|
|
||||||
constructor(protected service: SearchService,
|
constructor(protected service: SearchService,
|
||||||
protected sidebarService: SearchSidebarService,
|
protected sidebarService: SearchSidebarService,
|
||||||
@@ -116,8 +116,8 @@ export class SearchPageComponent implements OnInit {
|
|||||||
this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe(
|
this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe(
|
||||||
switchMap((scopeId) => this.service.getScopes(scopeId))
|
switchMap((scopeId) => this.service.getScopes(scopeId))
|
||||||
);
|
);
|
||||||
if (!isNotEmpty(this.fixedFilter$)) {
|
if (!isNotEmpty(this.configuration$)) {
|
||||||
this.fixedFilter$ = this.routeService.getRouteParameterValue('filter');
|
this.configuration$ = this.routeService.getRouteParameterValue('configuration');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,9 +17,7 @@ import { SearchFiltersComponent } from './search-filters/search-filters.componen
|
|||||||
import { SearchFilterComponent } from './search-filters/search-filter/search-filter.component';
|
import { SearchFilterComponent } from './search-filters/search-filter/search-filter.component';
|
||||||
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 { FilteredSearchPageComponent } from './filtered-search-page.component';
|
|
||||||
import { SearchFixedFilterService } from './search-filters/search-filter/search-fixed-filter.service';
|
import { SearchFixedFilterService } from './search-filters/search-filter/search-fixed-filter.service';
|
||||||
import { FilteredSearchPageGuard } from './filtered-search-page.guard';
|
|
||||||
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 { 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 { SearchTextFilterComponent } from './search-filters/search-filter/search-text-filter/search-text-filter.component';
|
||||||
@@ -32,6 +30,9 @@ import { SearchFacetSelectedOptionComponent } from './search-filters/search-filt
|
|||||||
import { SearchFacetRangeOptionComponent } from './search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component';
|
import { SearchFacetRangeOptionComponent } from './search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component';
|
||||||
import { SearchSwitchConfigurationComponent } from './search-switch-configuration/search-switch-configuration.component';
|
import { SearchSwitchConfigurationComponent } from './search-switch-configuration/search-switch-configuration.component';
|
||||||
import { SearchAuthorityFilterComponent } from './search-filters/search-filter/search-authority-filter/search-authority-filter.component';
|
import { SearchAuthorityFilterComponent } from './search-filters/search-filter/search-authority-filter/search-authority-filter.component';
|
||||||
|
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
||||||
|
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
||||||
|
import { FilteredSearchPageComponent } from './filtered-search-page.component';
|
||||||
|
|
||||||
const effects = [
|
const effects = [
|
||||||
SearchSidebarEffects
|
SearchSidebarEffects
|
||||||
@@ -60,7 +61,8 @@ const components = [
|
|||||||
SearchFacetRangeOptionComponent,
|
SearchFacetRangeOptionComponent,
|
||||||
SearchSwitchConfigurationComponent,
|
SearchSwitchConfigurationComponent,
|
||||||
SearchAuthorityFilterComponent,
|
SearchAuthorityFilterComponent,
|
||||||
FilteredSearchPageComponent
|
FilteredSearchPageComponent,
|
||||||
|
ConfigurationSearchPageComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -76,7 +78,7 @@ const components = [
|
|||||||
SearchSidebarService,
|
SearchSidebarService,
|
||||||
SearchFilterService,
|
SearchFilterService,
|
||||||
SearchFixedFilterService,
|
SearchFixedFilterService,
|
||||||
FilteredSearchPageGuard,
|
ConfigurationSearchPageGuard,
|
||||||
SearchFilterService,
|
SearchFilterService,
|
||||||
SearchConfigurationService
|
SearchConfigurationService
|
||||||
],
|
],
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<h2 *ngIf="!disableHeader">{{ getTitleKey() | translate }}</h2>
|
<h2 *ngIf="!disableHeader">{{ (configuration ? configuration + '.search.results.head' : 'search.results.head') | translate }}</h2>
|
||||||
<div *ngIf="searchResults?.hasSucceeded && !searchResults?.isLoading && searchResults?.payload?.page.length > 0" @fadeIn>
|
<div *ngIf="searchResults?.hasSucceeded && !searchResults?.isLoading && searchResults?.payload?.page.length > 0" @fadeIn>
|
||||||
<ds-viewable-collection
|
<ds-viewable-collection
|
||||||
[config]="searchConfig.pagination"
|
[config]="searchConfig.pagination"
|
||||||
|
@@ -45,9 +45,9 @@ export class SearchResultsComponent {
|
|||||||
@Input() viewMode: SetViewMode;
|
@Input() viewMode: SetViewMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An optional fixed filter to filter the result on one type
|
* An optional configuration to filter the result on one type
|
||||||
*/
|
*/
|
||||||
@Input() fixedFilter: string;
|
@Input() configuration: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not to hide the header of the results
|
* Whether or not to hide the header of the results
|
||||||
@@ -55,19 +55,6 @@ export class SearchResultsComponent {
|
|||||||
*/
|
*/
|
||||||
@Input() disableHeader = false;
|
@Input() disableHeader = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the i18n key for the title depending on the fixed filter
|
|
||||||
* Defaults to 'search.results.head' if there's no fixed filter found
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
getTitleKey() {
|
|
||||||
if (isNotEmpty(this.fixedFilter)) {
|
|
||||||
return this.fixedFilter + '.search.results.head'
|
|
||||||
} else {
|
|
||||||
return 'search.results.head';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to change the given string by surrounding it by quotes if not already present.
|
* Method to change the given string by surrounding it by quotes if not already present.
|
||||||
*/
|
*/
|
||||||
|
@@ -9,7 +9,7 @@ import {
|
|||||||
of as observableOf,
|
of as observableOf,
|
||||||
Subscription
|
Subscription
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { filter, flatMap, map, switchMap, tap } from 'rxjs/operators';
|
import { filter, flatMap, map, startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
import { SearchOptions } from '../search-options.model';
|
import { SearchOptions } from '../search-options.model';
|
||||||
@@ -109,9 +109,14 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
* @returns {Observable<string>} Emits the current configuration string
|
* @returns {Observable<string>} Emits the current configuration string
|
||||||
*/
|
*/
|
||||||
getCurrentConfiguration(defaultConfiguration: string) {
|
getCurrentConfiguration(defaultConfiguration: string) {
|
||||||
return this.routeService.getQueryParameterValue('configuration').pipe(map((configuration) => {
|
return observableCombineLatest(
|
||||||
return configuration || defaultConfiguration;
|
this.routeService.getQueryParameterValue('configuration').pipe(startWith(undefined)),
|
||||||
}));
|
this.routeService.getRouteParameterValue('configuration').pipe(startWith(undefined))
|
||||||
|
).pipe(
|
||||||
|
map(([queryConfig, routeConfig]) => {
|
||||||
|
return queryConfig || routeConfig || defaultConfiguration;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
|
||||||
import { isNull } from '../../shared/empty.util';
|
import { hasNoValue, isNull } from '../../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains the mapping between a search result component and a DSpaceObject
|
* Contains the mapping between a search result component and a DSpaceObject
|
||||||
@@ -34,7 +34,7 @@ export function searchResultFor(domainConstructor: GenericConstructor<ListableOb
|
|||||||
* @returns The component's constructor that matches the given DSpaceObject
|
* @returns The component's constructor that matches the given DSpaceObject
|
||||||
*/
|
*/
|
||||||
export function getSearchResultFor(domainConstructor: GenericConstructor<ListableObject>, configuration: string = null) {
|
export function getSearchResultFor(domainConstructor: GenericConstructor<ListableObject>, configuration: string = null) {
|
||||||
if (isNull(configuration) || configuration === 'default') {
|
if (isNull(configuration) || configuration === 'default' || hasNoValue(searchResultMap.get(configuration))) {
|
||||||
return searchResultMap.get(domainConstructor);
|
return searchResultMap.get(domainConstructor);
|
||||||
} else {
|
} else {
|
||||||
return searchResultMap.get(configuration).get(domainConstructor);
|
return searchResultMap.get(configuration).get(domainConstructor);
|
||||||
|
Reference in New Issue
Block a user