mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge pull request #3907 from 4Science/task/dspace-7_x/DURACOM-303
[Port dspace-7_x] Exclude search and browse from Angular SSR
This commit is contained in:
@@ -25,6 +25,14 @@ ssr:
|
||||
inlineCriticalCss: false
|
||||
# Path prefixes to enable SSR for. By default these are limited to paths of primary DSpace objects.
|
||||
paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ]
|
||||
# Whether to enable rendering of Search component on SSR.
|
||||
# If set to true the component will be included in the HTML returned from the server side rendering.
|
||||
# If set to false the component will not be included in the HTML returned from the server side rendering.
|
||||
enableSearchComponent: false,
|
||||
# Whether to enable rendering of Browse component on SSR.
|
||||
# If set to true the component will be included in the HTML returned from the server side rendering.
|
||||
# If set to false the component will not be included in the HTML returned from the server side rendering.
|
||||
enableBrowseComponent: false,
|
||||
|
||||
# The REST API server settings
|
||||
# NOTE: these settings define which (publicly available) REST API to use. They are usually
|
||||
@@ -412,3 +420,12 @@ liveRegion:
|
||||
messageTimeOutDurationMs: 30000
|
||||
# The visibility of the live region. Setting this to true is only useful for debugging purposes.
|
||||
isVisible: false
|
||||
|
||||
|
||||
# Search settings
|
||||
search:
|
||||
# Number used to render n UI elements called loading skeletons that act as placeholders.
|
||||
# These elements indicate that some content will be loaded in their stead.
|
||||
# Since we don't know how many filters will be loaded before we receive a response from the server we use this parameter for the skeletons count.
|
||||
# e.g. If we set 5 then 5 loading skeletons will be visualized before the actual filters are retrieved.
|
||||
defaultFiltersCount: 5
|
||||
|
@@ -111,6 +111,7 @@
|
||||
"ngx-infinite-scroll": "^15.0.0",
|
||||
"ngx-pagination": "6.0.3",
|
||||
"ngx-sortablejs": "^11.1.0",
|
||||
"ngx-skeleton-loader": "^7.0.0",
|
||||
"ngx-ui-switch": "^14.1.0",
|
||||
"nouislider": "^15.8.1",
|
||||
"pem": "1.14.8",
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { BrowseByDatePageComponent } from './browse-by-date-page.component';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
@@ -9,7 +9,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BrowseService } from '../../core/browse/browse.service';
|
||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||
import { RouterMock } from '../../shared/mocks/router.mock';
|
||||
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ChangeDetectorRef, NO_ERRORS_SCHEMA, PLATFORM_ID } from '@angular/core';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
@@ -24,6 +24,8 @@ import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { SortDirection } from '../../core/cache/models/sort-options.model';
|
||||
import { cold } from 'jasmine-marbles';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { BrowseEntry } from '../../core/shared/browse-entry.model';
|
||||
|
||||
describe('BrowseByDatePageComponent', () => {
|
||||
let comp: BrowseByDatePageComponent;
|
||||
@@ -95,7 +97,10 @@ describe('BrowseByDatePageComponent', () => {
|
||||
{ provide: Router, useValue: new RouterMock() },
|
||||
{ provide: PaginationService, useValue: paginationService },
|
||||
{ provide: ChangeDetectorRef, useValue: mockCdRef },
|
||||
{ provide: APP_CONFIG, useValue: environment }
|
||||
{ provide: APP_CONFIG, useValue: environment },
|
||||
{ provide: Store, useValue: {} },
|
||||
{ provide: APP_CONFIG, useValue: environment },
|
||||
{ provide: PLATFORM_ID, useValue: 'browser' },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
@@ -131,4 +136,33 @@ describe('BrowseByDatePageComponent', () => {
|
||||
//expect(comp.startsWithOptions[0]).toEqual(new Date().getUTCFullYear());
|
||||
expect(comp.startsWithOptions[0]).toEqual(1960);
|
||||
});
|
||||
|
||||
describe('when rendered in SSR', () => {
|
||||
beforeEach(() => {
|
||||
comp.platformId = 'server';
|
||||
spyOn((comp as any).browseService, 'getBrowseItemsFor');
|
||||
});
|
||||
|
||||
it('should not call getBrowseItemsFor on init', (done) => {
|
||||
comp.ngOnInit();
|
||||
expect((comp as any).browseService.getBrowseItemsFor).not.toHaveBeenCalled();
|
||||
comp.loading$.subscribe((res) => {
|
||||
expect(res).toBeFalsy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when rendered in CSR', () => {
|
||||
beforeEach(() => {
|
||||
comp.platformId = 'browser';
|
||||
spyOn((comp as any).browseService, 'getBrowseItemsFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry()));
|
||||
});
|
||||
|
||||
it('should call getBrowseItemsFor on init', fakeAsync(() => {
|
||||
comp.ngOnInit();
|
||||
tick(100);
|
||||
expect((comp as any).browseService.getBrowseItemsFor).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { ChangeDetectorRef, Component, Inject } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, Inject, PLATFORM_ID } from '@angular/core';
|
||||
import {
|
||||
BrowseByMetadataPageComponent,
|
||||
browseParamsToOptions
|
||||
@@ -11,6 +11,7 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv
|
||||
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||
import { map, take } from 'rxjs/operators';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { isValidDate } from '../../shared/date.util';
|
||||
@@ -18,6 +19,8 @@ import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||
import { isPlatformServer } from '@angular/common';
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-browse-by-date-page',
|
||||
@@ -44,11 +47,16 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
||||
protected cdRef: ChangeDetectorRef,
|
||||
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
||||
public dsoNameService: DSONameService,
|
||||
@Inject(PLATFORM_ID) public platformId: any,
|
||||
) {
|
||||
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService);
|
||||
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService, platformId);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.renderOnServerSide && !environment.universal.enableBrowseComponent && isPlatformServer(this.platformId)) {
|
||||
this.loading$ = observableOf(false);
|
||||
return;
|
||||
}
|
||||
const sortConfig = new SortOptions('default', SortDirection.ASC);
|
||||
this.startsWithType = StartsWithType.date;
|
||||
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<div class="container">
|
||||
<div class="container" *ngIf="(!ssrRenderingDisabled)">
|
||||
<ng-container *ngVar="(parent$ | async) as parent">
|
||||
<ng-container *ngIf="parent?.payload as parentContext">
|
||||
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
||||
|
@@ -3,7 +3,7 @@ import {
|
||||
browseParamsToOptions,
|
||||
getBrowseSearchOptions
|
||||
} from './browse-by-metadata-page.component';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
||||
import { BrowseService } from '../../core/browse/browse.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
@@ -13,7 +13,7 @@ import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { NO_ERRORS_SCHEMA, PLATFORM_ID } from '@angular/core';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model';
|
||||
import { PageInfo } from '../../core/shared/page-info.model';
|
||||
@@ -111,7 +111,8 @@ describe('BrowseByMetadataPageComponent', () => {
|
||||
{ provide: DSpaceObjectDataService, useValue: mockDsoService },
|
||||
{ provide: PaginationService, useValue: paginationService },
|
||||
{ provide: Router, useValue: new RouterMock() },
|
||||
{ provide: APP_CONFIG, useValue: environmentMock }
|
||||
{ provide: APP_CONFIG, useValue: environmentMock },
|
||||
{ provide: PLATFORM_ID, useValue: 'browser' },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
@@ -224,6 +225,35 @@ describe('BrowseByMetadataPageComponent', () => {
|
||||
expect(result.fetchThumbnail).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when rendered in SSR', () => {
|
||||
beforeEach(() => {
|
||||
comp.ssrRenderingDisabled = true;
|
||||
spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(null));
|
||||
});
|
||||
|
||||
it('should not call getBrowseEntriesFor on init', (done) => {
|
||||
comp.ngOnInit();
|
||||
expect((comp as any).browseService.getBrowseEntriesFor).not.toHaveBeenCalled();
|
||||
comp.loading$.subscribe((res) => {
|
||||
expect(res).toBeFalsy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when rendered in CSR', () => {
|
||||
beforeEach(() => {
|
||||
comp.ssrRenderingDisabled = false;
|
||||
spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry()));
|
||||
});
|
||||
|
||||
it('should call getBrowseEntriesFor on init', fakeAsync(() => {
|
||||
comp.ngOnInit();
|
||||
tick(100);
|
||||
expect((comp as any).browseService.getBrowseEntriesFor).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
export function toRemoteData(objects: any[]): Observable<RemoteData<PaginatedList<any>>> {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { combineLatest as observableCombineLatest, Observable, Subscription, of as observableOf } from 'rxjs';
|
||||
import { Component, Inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, Inject, OnInit, OnDestroy, Input, PLATFORM_ID } from '@angular/core';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
@@ -22,6 +22,8 @@ import { Collection } from '../../core/shared/collection.model';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
|
||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||
import { isPlatformServer } from '@angular/common';
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
||||
export const BBM_PAGINATION_ID = 'bbm';
|
||||
|
||||
@@ -37,7 +39,10 @@ export const BBM_PAGINATION_ID = 'bbm';
|
||||
* 'dc.contributor.*'
|
||||
*/
|
||||
export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
||||
|
||||
/**
|
||||
* Defines whether to fetch search results during SSR execution
|
||||
*/
|
||||
@Input() renderOnServerSide = false;
|
||||
/**
|
||||
* The list of browse-entries to display
|
||||
*/
|
||||
@@ -126,6 +131,10 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
||||
* Observable determining if the loading animation needs to be shown
|
||||
*/
|
||||
loading$ = observableOf(true);
|
||||
/**
|
||||
* Whether this component should be rendered or not in SSR
|
||||
*/
|
||||
ssrRenderingDisabled = false;
|
||||
|
||||
public constructor(protected route: ActivatedRoute,
|
||||
protected browseService: BrowseService,
|
||||
@@ -134,6 +143,7 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
||||
protected router: Router,
|
||||
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
||||
public dsoNameService: DSONameService,
|
||||
@Inject(PLATFORM_ID) public platformId: any,
|
||||
) {
|
||||
|
||||
this.fetchThumbnails = this.appConfig.browseBy.showThumbnails;
|
||||
@@ -142,11 +152,15 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
||||
currentPage: 1,
|
||||
pageSize: this.appConfig.browseBy.pageSize,
|
||||
});
|
||||
}
|
||||
this.ssrRenderingDisabled = !this.renderOnServerSide && !environment.universal.enableBrowseComponent && isPlatformServer(this.platformId);
|
||||
}
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
if (this.ssrRenderingDisabled) {
|
||||
this.loading$ = observableOf(false);
|
||||
return;
|
||||
}
|
||||
const sortConfig = new SortOptions('default', SortDirection.ASC);
|
||||
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig));
|
||||
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
|
||||
@@ -22,6 +22,7 @@ import { PaginationService } from '../../core/pagination/pagination.service';
|
||||
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
||||
import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { BrowseEntry } from '../../core/shared/browse-entry.model';
|
||||
|
||||
|
||||
describe('BrowseByTitlePageComponent', () => {
|
||||
@@ -63,7 +64,8 @@ describe('BrowseByTitlePageComponent', () => {
|
||||
|
||||
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
|
||||
params: observableOf({}),
|
||||
data: observableOf({ metadata: 'title' })
|
||||
queryParams: observableOf({}),
|
||||
data: observableOf({ metadata: 'title' }),
|
||||
});
|
||||
|
||||
const paginationService = new PaginationServiceStub();
|
||||
@@ -97,4 +99,35 @@ describe('BrowseByTitlePageComponent', () => {
|
||||
expect(result.payload.page).toEqual(mockItems);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when rendered in SSR', () => {
|
||||
beforeEach(() => {
|
||||
comp.platformId = 'server';
|
||||
spyOn((comp as any).browseService, 'getBrowseItemsFor');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not call getBrowseItemsFor on init', (done) => {
|
||||
comp.ngOnInit();
|
||||
expect((comp as any).browseService.getBrowseItemsFor).not.toHaveBeenCalled();
|
||||
comp.loading$.subscribe((res) => {
|
||||
expect(res).toBeFalsy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when rendered in CSR', () => {
|
||||
beforeEach(() => {
|
||||
comp.platformId = 'browser';
|
||||
fixture.detectChanges();
|
||||
spyOn((comp as any).browseService, 'getBrowseItemsFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry()));
|
||||
});
|
||||
|
||||
it('should call getBrowseItemsFor on init', fakeAsync(() => {
|
||||
comp.ngOnInit();
|
||||
tick(100);
|
||||
expect((comp as any).browseService.getBrowseItemsFor).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { Component, Inject, PLATFORM_ID } from '@angular/core';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import {
|
||||
@@ -11,9 +11,12 @@ import { BrowseService } from '../../core/browse/browse.service';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||
import { map, take } from 'rxjs/operators';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface';
|
||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||
import { isPlatformServer } from '@angular/common';
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-browse-by-title-page',
|
||||
@@ -32,11 +35,16 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
|
||||
protected router: Router,
|
||||
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
||||
public dsoNameService: DSONameService,
|
||||
@Inject(PLATFORM_ID) public platformId: any,
|
||||
) {
|
||||
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService);
|
||||
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService, platformId);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.renderOnServerSide && !environment.universal.enableBrowseComponent && isPlatformServer(this.platformId)) {
|
||||
this.loading$ = observableOf(false);
|
||||
return;
|
||||
}
|
||||
const sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
||||
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
||||
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { HostWindowService } from '../shared/host-window.service';
|
||||
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||
import { SearchComponent } from '../shared/search/search.component';
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Inject, PLATFORM_ID } from '@angular/core';
|
||||
import { pushInOut } from '../shared/animations/push';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../my-dspace-page/my-dspace-page.component';
|
||||
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||
@@ -35,7 +35,8 @@ export class ConfigurationSearchPageComponent extends SearchComponent {
|
||||
protected routeService: RouteService,
|
||||
protected router: Router,
|
||||
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||
@Inject(PLATFORM_ID) public platformId: any,
|
||||
) {
|
||||
super(service, sidebarService, windowService, searchConfigService, routeService, router, appConfig);
|
||||
super(service, sidebarService, windowService, searchConfigService, routeService, router, appConfig, platformId);
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Component, Inject, Input, OnInit } from '@angular/core';
|
||||
import { Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core';
|
||||
|
||||
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
|
||||
import { filter, map, startWith, switchMap, take } from 'rxjs/operators';
|
||||
@@ -43,6 +43,8 @@ export class SearchFilterComponent implements OnInit {
|
||||
*/
|
||||
@Input() scope: string;
|
||||
|
||||
@Output() isVisibilityComputed = new EventEmitter<boolean>();
|
||||
|
||||
/**
|
||||
* True when the filter is 100% collapsed in the UI
|
||||
*/
|
||||
@@ -91,7 +93,9 @@ export class SearchFilterComponent implements OnInit {
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.selectedValues$ = this.getSelectedValues();
|
||||
this.active$ = this.isActive();
|
||||
this.active$ = this.isActive().pipe(
|
||||
startWith(true)
|
||||
);
|
||||
this.collapsed$ = this.isCollapsed();
|
||||
this.initializeFilter();
|
||||
this.selectedValues$.pipe(take(1)).subscribe((selectedValues) => {
|
||||
@@ -99,6 +103,9 @@ export class SearchFilterComponent implements OnInit {
|
||||
this.filterService.expand(this.filter.name);
|
||||
}
|
||||
});
|
||||
this.isActive().pipe(take(1)).subscribe(() => {
|
||||
this.isVisibilityComputed.emit(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,7 +194,7 @@ export class SearchFilterComponent implements OnInit {
|
||||
}
|
||||
));
|
||||
}
|
||||
}),
|
||||
startWith(true));
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,13 @@
|
||||
<h3>{{"search.filters.head" | translate}}</h3>
|
||||
<div *ngIf="(filters | async)?.hasSucceeded">
|
||||
<div *ngFor="let filter of (filters | async)?.payload; trackBy: trackUpdate">
|
||||
<ds-search-filter [scope]="currentScope" [filter]="filter" [inPlaceSearch]="inPlaceSearch" [refreshFilters]="refreshFilters"></ds-search-filter>
|
||||
<div [class.visually-hidden]="filtersWithComputedVisibility !== (filters | async)?.payload?.length"
|
||||
*ngFor="let filter of (filters | async)?.payload; trackBy: trackUpdate">
|
||||
<ds-search-filter (isVisibilityComputed)="countFiltersWithComputedVisibility($event)" [scope]="currentScope" [filter]="filter" [inPlaceSearch]="inPlaceSearch" [refreshFilters]="refreshFilters"></ds-search-filter>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<ng-container *ngIf="filtersWithComputedVisibility !== (filters | async)?.payload?.length">
|
||||
<ngx-skeleton-loader [count]="defaultFilterCount"/>
|
||||
</ng-container>
|
||||
<a class="btn btn-primary" [routerLink]="[searchLink]" [queryParams]="clearParams | async" queryParamsHandling="merge" role="button"><i class="fas fa-undo"></i> {{"search.filters.reset" | translate}}</a>
|
||||
|
@@ -1,2 +1,12 @@
|
||||
@import '../../../../styles/variables';
|
||||
@import '../../../../styles/mixins';
|
||||
|
||||
:host ::ng-deep {
|
||||
ngx-skeleton-loader .skeleton-loader {
|
||||
height: var(--ds-filters-skeleton-height);
|
||||
margin-bottom: var(--ds-filters-skeleton-spacing);
|
||||
padding: var(--ds-filters-skeleton-spacing);
|
||||
background-color: var(--bs-light);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,8 @@ import { SearchFiltersComponent } from './search-filters.component';
|
||||
import { SearchService } from '../../../core/shared/search/search.service';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component';
|
||||
import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service.stub';
|
||||
import { APP_CONFIG } from '../../../../config/app-config.interface';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
|
||||
describe('SearchFiltersComponent', () => {
|
||||
let comp: SearchFiltersComponent;
|
||||
@@ -37,7 +39,7 @@ describe('SearchFiltersComponent', () => {
|
||||
{ provide: SearchService, useValue: searchServiceStub },
|
||||
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
||||
{ provide: SearchFilterService, useValue: searchFiltersStub },
|
||||
|
||||
{ provide: APP_CONFIG, useValue: environment },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).overrideComponent(SearchFiltersComponent, {
|
||||
|
@@ -2,7 +2,7 @@ import { Component, Inject, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
|
||||
import { SearchService } from '../../../core/shared/search/search.service';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
@@ -12,6 +12,7 @@ import { SearchFilterService } from '../../../core/shared/search/search-filter.s
|
||||
import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component';
|
||||
import { currentPath } from '../../utils/route.utils';
|
||||
import { hasValue } from '../../empty.util';
|
||||
import { APP_CONFIG, AppConfig } from '../../../../config/app-config.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search-filters',
|
||||
@@ -60,7 +61,13 @@ export class SearchFiltersComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
searchLink: string;
|
||||
|
||||
/**
|
||||
* Filters for which visibility has been computed
|
||||
*/
|
||||
filtersWithComputedVisibility = 0;
|
||||
|
||||
subs = [];
|
||||
defaultFilterCount: number;
|
||||
|
||||
/**
|
||||
* Initialize instance variables
|
||||
@@ -68,19 +75,26 @@ export class SearchFiltersComponent implements OnInit, OnDestroy {
|
||||
* @param {SearchFilterService} filterService
|
||||
* @param {Router} router
|
||||
* @param {SearchConfigurationService} searchConfigService
|
||||
* @param appConfig
|
||||
*/
|
||||
constructor(
|
||||
private searchService: SearchService,
|
||||
private filterService: SearchFilterService,
|
||||
private router: Router,
|
||||
@Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService) {
|
||||
@Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService,
|
||||
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||
) {
|
||||
this.defaultFilterCount = this.appConfig.search.filterPlaceholdersCount ?? 5;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.clearParams = this.searchConfigService.getCurrentFrontendFilters().pipe(map((filters) => {
|
||||
Object.keys(filters).forEach((f) => filters[f] = null);
|
||||
return filters;
|
||||
}));
|
||||
this.clearParams = this.searchConfigService.getCurrentFrontendFilters().pipe(
|
||||
tap(() => this.filtersWithComputedVisibility = 0),
|
||||
map((filters) => {
|
||||
Object.keys(filters).forEach((f) => filters[f] = null);
|
||||
return filters;
|
||||
})
|
||||
);
|
||||
this.searchLink = this.getSearchLink();
|
||||
}
|
||||
|
||||
@@ -108,4 +122,10 @@ export class SearchFiltersComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
countFiltersWithComputedVisibility(computed: boolean) {
|
||||
if (computed) {
|
||||
this.filtersWithComputedVisibility += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,38 @@
|
||||
<div class="row flex-nowrap">
|
||||
<div [class.mb-2]="(viewMode$ | async) === ViewMode.ListElement" class="info-skeleton col-12">
|
||||
<ngx-skeleton-loader/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="(viewMode$ | async) === ViewMode.ListElement; else grid">
|
||||
<ng-container *ngFor="let result of loadingResults; let first = first">
|
||||
<div [class.my-4]="!first" class="row">
|
||||
<div *ngIf="showThumbnails" class="col-3 col-md-2">
|
||||
<div class="thumbnail-skeleton position-relative">
|
||||
<ngx-skeleton-loader/>
|
||||
</div>
|
||||
</div>
|
||||
<div [class.col-9]="showThumbnails" [class.col-md-10]="showThumbnails" [class.col-md-12]="!showThumbnails">
|
||||
<div class="badge-skeleton">
|
||||
<ngx-skeleton-loader/>
|
||||
</div>
|
||||
<div class="text-skeleton">
|
||||
<ngx-skeleton-loader [count]="textLineCount"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #grid>
|
||||
<div class="card-columns row">
|
||||
<ng-container *ngFor="let result of loadingResults">
|
||||
<div class="card-column col col-sm-6 col-lg-4">
|
||||
<div class="card-skeleton">
|
||||
<ngx-skeleton-loader/>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
@@ -0,0 +1,56 @@
|
||||
:host ::ng-deep {
|
||||
--ds-wrapper-grid-spacing: calc(var(--bs-spacer) / 2);
|
||||
|
||||
.info-skeleton, .badge-skeleton, .text-skeleton{
|
||||
ngx-skeleton-loader .skeleton-loader {
|
||||
height: var(--ds-search-skeleton-text-height);
|
||||
}
|
||||
}
|
||||
|
||||
.badge-skeleton, .info-skeleton {
|
||||
ngx-skeleton-loader .skeleton-loader {
|
||||
width: var(--ds-search-skeleton-badge-width);
|
||||
}
|
||||
}
|
||||
|
||||
.info-skeleton {
|
||||
ngx-skeleton-loader .skeleton-loader {
|
||||
width: var(--ds-search-skeleton-info-width);
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnail-skeleton {
|
||||
max-width: var(--ds-thumbnail-max-width);
|
||||
height: 100%;
|
||||
|
||||
ngx-skeleton-loader .skeleton-loader {
|
||||
margin-right: var(--ds-search-skeleton-thumbnail-margin);
|
||||
border-radius: 0;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.card-skeleton {
|
||||
ngx-skeleton-loader .skeleton-loader {
|
||||
height: var(--ds-search-skeleton-card-height);
|
||||
}
|
||||
}
|
||||
|
||||
ngx-skeleton-loader .skeleton-loader {
|
||||
background-color: var(--bs-light);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.card-columns {
|
||||
margin-left: calc(-1 * var(--ds-wrapper-grid-spacing));
|
||||
margin-right: calc(-1 * var(--ds-wrapper-grid-spacing));
|
||||
column-gap: 0;
|
||||
|
||||
.card-column {
|
||||
padding-left: var(--ds-wrapper-grid-spacing);
|
||||
padding-right: var(--ds-wrapper-grid-spacing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
} from '@angular/core/testing';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
|
||||
import { SearchService } from '../../../../core/shared/search/search.service';
|
||||
import { SearchServiceStub } from '../../../testing/search-service.stub';
|
||||
import { SearchResultsSkeletonComponent } from './search-results-skeleton.component';
|
||||
|
||||
describe('SearchResultsSkeletonComponent', () => {
|
||||
let component: SearchResultsSkeletonComponent;
|
||||
let fixture: ComponentFixture<SearchResultsSkeletonComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NgxSkeletonLoaderModule],
|
||||
declarations: [SearchResultsSkeletonComponent],
|
||||
providers: [
|
||||
{ provide: SearchService, useValue: new SearchServiceStub() },
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SearchResultsSkeletonComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { SearchService } from '../../../../core/shared/search/search.service';
|
||||
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||
import { hasValue } from '../../../empty.util';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search-results-skeleton',
|
||||
templateUrl: './search-results-skeleton.component.html',
|
||||
styleUrls: ['./search-results-skeleton.component.scss'],
|
||||
})
|
||||
/**
|
||||
* Component to show placeholders for search results while loading, to give a loading feedback to the user without layout shifting.
|
||||
*/
|
||||
export class SearchResultsSkeletonComponent implements OnInit {
|
||||
/**
|
||||
* Whether the search result contains thumbnail
|
||||
*/
|
||||
@Input()
|
||||
showThumbnails: boolean;
|
||||
/**
|
||||
* The number of search result loaded in the current page
|
||||
*/
|
||||
@Input()
|
||||
numberOfResults = 0;
|
||||
/**
|
||||
* How many placeholder are displayed for the search result text
|
||||
*/
|
||||
@Input()
|
||||
textLineCount = 2;
|
||||
/**
|
||||
* The view mode of the search page
|
||||
*/
|
||||
public viewMode$: Observable<ViewMode>;
|
||||
/**
|
||||
* Array built from numberOfResults to count and iterate based on index
|
||||
*/
|
||||
public loadingResults: number[];
|
||||
|
||||
protected readonly ViewMode = ViewMode;
|
||||
|
||||
constructor(private searchService: SearchService) {
|
||||
this.viewMode$ = this.searchService.getViewMode();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadingResults = Array.from({ length: this.numberOfResults }, (_, i) => i + 1);
|
||||
|
||||
if (!hasValue(this.showThumbnails)) {
|
||||
// this is needed as the default value of show thumbnails is true but set in lower levels of the DOM.
|
||||
this.showThumbnails = true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -19,7 +19,13 @@
|
||||
(selectObject)="selectObject.emit($event)">
|
||||
</ds-viewable-collection>
|
||||
</div>
|
||||
<ds-themed-loading *ngIf="isLoading()" message="{{'loading.search-results' | translate}}"></ds-themed-loading>
|
||||
|
||||
<ds-search-results-skeleton
|
||||
*ngIf="isLoading()"
|
||||
[showThumbnails]="showThumbnails"
|
||||
[numberOfResults]="searchConfig.pagination.pageSize"
|
||||
></ds-search-results-skeleton>
|
||||
|
||||
<ds-error *ngIf="showError()"
|
||||
message="{{errorMessageLabel() | translate}}"></ds-error>
|
||||
<div *ngIf="searchResults?.payload?.page.length == 0 || searchResults?.statusCode == 400">
|
||||
|
@@ -0,0 +1,17 @@
|
||||
:host ::ng-deep {
|
||||
.filter-badge-skeleton {
|
||||
ngx-skeleton-loader .skeleton-loader {
|
||||
background-color: var(--bs-light);
|
||||
box-shadow: none;
|
||||
width: var(--ds-search-skeleton-filter-badge-width);
|
||||
height: var(--ds-search-skeleton-badge-height);
|
||||
margin-bottom: 0;
|
||||
margin-right: calc(var(--bs-spacer) / 4);
|
||||
}
|
||||
}
|
||||
|
||||
.filters-badge-skeleton-container {
|
||||
display: flex;
|
||||
max-height: var(--ds-search-skeleton-badge-height);
|
||||
}
|
||||
}
|
@@ -7,6 +7,11 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SearchResultsComponent } from './search-results.component';
|
||||
import { QueryParamsDirectiveStub } from '../../testing/query-params-directive.stub';
|
||||
import { createFailedRemoteDataObject } from '../../remote-data.utils';
|
||||
import { SearchResultsSkeletonComponent } from './search-results-skeleton/search-results-skeleton.component';
|
||||
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
||||
import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service.stub';
|
||||
import { SearchService } from '../../../core/shared/search/search.service';
|
||||
import { SearchServiceStub } from '../../testing/search-service.stub';
|
||||
|
||||
describe('SearchResultsComponent', () => {
|
||||
let comp: SearchResultsComponent;
|
||||
@@ -19,7 +24,13 @@ describe('SearchResultsComponent', () => {
|
||||
imports: [TranslateModule.forRoot(), NoopAnimationsModule],
|
||||
declarations: [
|
||||
SearchResultsComponent,
|
||||
QueryParamsDirectiveStub],
|
||||
SearchResultsSkeletonComponent,
|
||||
QueryParamsDirectiveStub
|
||||
],
|
||||
providers: [
|
||||
{ provide: SearchConfigurationService, useValue: new SearchConfigurationServiceStub() },
|
||||
{ provide: SearchService, useValue: new SearchServiceStub() },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
@@ -63,7 +74,7 @@ describe('SearchResultsComponent', () => {
|
||||
|
||||
it('should display link with new search where query is quoted if search return a error 400', () => {
|
||||
(comp as any).searchResults = createFailedRemoteDataObject('Error', 400);
|
||||
(comp as any).searchConfig = { query: 'foobar' };
|
||||
(comp as any).searchConfig = { query: 'foobar', pagination: { pageSize: 10 } };
|
||||
fixture.detectChanges();
|
||||
|
||||
const linkDes = fixture.debugElement.queryAll(By.directive(QueryParamsDirectiveStub));
|
||||
|
@@ -11,6 +11,10 @@ import { CollectionElementLinkType } from '../../object-collection/collection-el
|
||||
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||
import { Context } from '../../../core/shared/context.model';
|
||||
import { PaginatedSearchOptions } from '../models/paginated-search-options.model';
|
||||
import { SearchFilter } from '../models/search-filter.model';
|
||||
import { Observable } from 'rxjs';
|
||||
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
||||
import { SearchService } from '../../../core/shared/search/search.service';
|
||||
|
||||
export interface SelectionConfig {
|
||||
repeatable: boolean;
|
||||
@@ -19,6 +23,7 @@ export interface SelectionConfig {
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search-results',
|
||||
styleUrls: ['./search-results.component.scss'],
|
||||
templateUrl: './search-results.component.html',
|
||||
animations: [
|
||||
fadeIn,
|
||||
@@ -32,6 +37,8 @@ export interface SelectionConfig {
|
||||
export class SearchResultsComponent {
|
||||
hasNoValue = hasNoValue;
|
||||
|
||||
filters$: Observable<SearchFilter[]>;
|
||||
|
||||
/**
|
||||
* The link type of the listed search results
|
||||
*/
|
||||
@@ -104,6 +111,13 @@ export class SearchResultsComponent {
|
||||
|
||||
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||
|
||||
constructor(
|
||||
protected searchConfigService: SearchConfigurationService,
|
||||
protected searchService: SearchService,
|
||||
) {
|
||||
this.filters$ = this.searchConfigService.getCurrentFilters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if search results are loading
|
||||
*/
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA, PLATFORM_ID } from '@angular/core';
|
||||
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
@@ -216,6 +216,7 @@ export function configureSearchComponentTestingModule(compType, additionalDeclar
|
||||
useValue: searchConfigurationServiceStub
|
||||
},
|
||||
{ provide: APP_CONFIG, useValue: environment },
|
||||
{ provide: PLATFORM_ID, useValue: 'browser' },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).overrideComponent(compType, {
|
||||
@@ -374,5 +375,34 @@ describe('SearchComponent', () => {
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when rendered in SSR', () => {
|
||||
beforeEach(() => {
|
||||
comp.platformId = 'server';
|
||||
});
|
||||
|
||||
it('should not call search method on init', (done) => {
|
||||
comp.ngOnInit();
|
||||
//Check that the first method from which the search depend upon is not being called
|
||||
expect(searchConfigurationServiceStub.getCurrentConfiguration).not.toHaveBeenCalled();
|
||||
comp.initialized$.subscribe((res) => {
|
||||
expect(res).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when rendered in CSR', () => {
|
||||
beforeEach(() => {
|
||||
comp.platformId = 'browser';
|
||||
});
|
||||
|
||||
it('should call search method on init', fakeAsync(() => {
|
||||
comp.ngOnInit();
|
||||
tick(100);
|
||||
//Check that the last method from which the search depend upon is being called
|
||||
expect(searchServiceStub.search).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,4 +1,14 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Inject, Input, OnInit, Output, OnDestroy } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Inject,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
OnDestroy,
|
||||
PLATFORM_ID
|
||||
} from '@angular/core';
|
||||
import { NavigationStart, Router } from '@angular/router';
|
||||
|
||||
import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';
|
||||
@@ -38,6 +48,8 @@ import { ITEM_MODULE_PATH } from '../../item-page/item-page-routing-paths';
|
||||
import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-routing-paths';
|
||||
import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routing-paths';
|
||||
import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface';
|
||||
import { isPlatformServer } from '@angular/common';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search',
|
||||
@@ -176,6 +188,11 @@ export class SearchComponent implements OnDestroy, OnInit {
|
||||
*/
|
||||
@Input() scope: string;
|
||||
|
||||
/**
|
||||
* Defines whether to fetch search results during SSR execution
|
||||
*/
|
||||
@Input() renderOnServerSide = false;
|
||||
|
||||
/**
|
||||
* The current configuration used during the search
|
||||
*/
|
||||
@@ -285,6 +302,7 @@ export class SearchComponent implements OnDestroy, OnInit {
|
||||
protected routeService: RouteService,
|
||||
protected router: Router,
|
||||
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||
@Inject(PLATFORM_ID) public platformId: any,
|
||||
) {
|
||||
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||
}
|
||||
@@ -297,6 +315,14 @@ export class SearchComponent implements OnDestroy, OnInit {
|
||||
* If something changes, update the list of scopes for the dropdown
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
if (!this.renderOnServerSide && !environment.universal.enableSearchComponent && isPlatformServer(this.platformId)) {
|
||||
this.subs.push(this.getSearchOptions().pipe(distinctUntilChanged()).subscribe((options) => {
|
||||
this.searchOptions$.next(options);
|
||||
}));
|
||||
this.initialized$.next(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.useUniquePageId) {
|
||||
// Create an unique pagination id related to the instance of the SearchComponent
|
||||
this.paginationId = uniqueId(this.paginationId);
|
||||
|
@@ -34,6 +34,10 @@ import { ThemedSearchSettingsComponent } from './search-settings/themed-search-s
|
||||
import { NouisliderModule } from 'ng2-nouislider';
|
||||
import { ThemedSearchFiltersComponent } from './search-filters/themed-search-filters.component';
|
||||
import { ThemedSearchSidebarComponent } from './search-sidebar/themed-search-sidebar.component';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
import {
|
||||
SearchResultsSkeletonComponent
|
||||
} from './search-results/search-results-skeleton/search-results-skeleton.component';
|
||||
|
||||
const COMPONENTS = [
|
||||
SearchComponent,
|
||||
@@ -62,6 +66,7 @@ const COMPONENTS = [
|
||||
ThemedSearchSettingsComponent,
|
||||
ThemedSearchFiltersComponent,
|
||||
ThemedSearchSidebarComponent,
|
||||
SearchResultsSkeletonComponent
|
||||
];
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
@@ -74,6 +79,7 @@ const ENTRY_COMPONENTS = [
|
||||
SearchFacetSelectedOptionComponent,
|
||||
SearchFacetRangeOptionComponent,
|
||||
SearchAuthorityFilterComponent,
|
||||
SearchResultsSkeletonComponent
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -93,11 +99,12 @@ export const MODELS = [
|
||||
imports: [
|
||||
CommonModule,
|
||||
TranslateModule.forChild({
|
||||
missingTranslationHandler: { provide: MissingTranslationHandler, useClass: MissingTranslationHelper },
|
||||
missingTranslationHandler: {provide: MissingTranslationHandler, useClass: MissingTranslationHelper},
|
||||
useDefaultLang: true
|
||||
}),
|
||||
SharedModule.withEntryComponents(),
|
||||
NouisliderModule,
|
||||
NgxSkeletonLoaderModule,
|
||||
],
|
||||
exports: [
|
||||
...COMPONENTS
|
||||
|
@@ -13,6 +13,10 @@ export class SearchConfigurationServiceStub {
|
||||
return observableOf([]);
|
||||
}
|
||||
|
||||
getCurrentFilters() {
|
||||
return observableOf([]);
|
||||
}
|
||||
|
||||
getCurrentScope(a) {
|
||||
return observableOf('test-id');
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@ import { MarkdownConfig } from './markdown-config.interface';
|
||||
import { FilterVocabularyConfig } from './filter-vocabulary-config';
|
||||
import { DiscoverySortConfig } from './discovery-sort.config';
|
||||
import { LiveRegionConfig } from '../app/shared/live-region/live-region.config';
|
||||
import { SearchConfig } from './search-page-config.interface';
|
||||
|
||||
interface AppConfig extends Config {
|
||||
ui: UIServerConfig;
|
||||
@@ -50,6 +51,7 @@ interface AppConfig extends Config {
|
||||
vocabularies: FilterVocabularyConfig[];
|
||||
comcolSelectionSort: DiscoverySortConfig;
|
||||
liveRegion: LiveRegionConfig;
|
||||
search: SearchConfig
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -23,6 +23,7 @@ import { MarkdownConfig } from './markdown-config.interface';
|
||||
import { FilterVocabularyConfig } from './filter-vocabulary-config';
|
||||
import { DiscoverySortConfig } from './discovery-sort.config';
|
||||
import { LiveRegionConfig } from '../app/shared/live-region/live-region.config';
|
||||
import { SearchConfig } from './search-page-config.interface';
|
||||
|
||||
export class DefaultAppConfig implements AppConfig {
|
||||
production = false;
|
||||
@@ -442,4 +443,8 @@ export class DefaultAppConfig implements AppConfig {
|
||||
messageTimeOutDurationMs: 30000,
|
||||
isVisible: false,
|
||||
};
|
||||
|
||||
search: SearchConfig = {
|
||||
filterPlaceholdersCount: 5
|
||||
};
|
||||
}
|
||||
|
12
src/config/search-page-config.interface.ts
Normal file
12
src/config/search-page-config.interface.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Config } from './config.interface';
|
||||
|
||||
export interface SearchConfig extends Config {
|
||||
/**
|
||||
* Number used to render n UI elements called loading skeletons that act as placeholders.
|
||||
* These elements indicate that some content will be loaded in their stead.
|
||||
* Since we don't know how many filters will be loaded before we receive a response from the server we use this parameter for the skeletons count.
|
||||
* For instance if we set 5 then 5 loading skeletons will be visualized before the actual filters are retrieved.
|
||||
*/
|
||||
filterPlaceholdersCount?: number;
|
||||
|
||||
}
|
@@ -18,4 +18,13 @@ export interface UniversalConfig extends Config {
|
||||
* Paths to enable SSR for. Defaults to the home page and paths in the sitemap.
|
||||
*/
|
||||
paths: Array<string>;
|
||||
/**
|
||||
* Whether to enable rendering of search component on SSR
|
||||
*/
|
||||
enableSearchComponent: boolean;
|
||||
|
||||
/**
|
||||
* Whether to enable rendering of browse component on SSR
|
||||
*/
|
||||
enableBrowseComponent: boolean;
|
||||
}
|
||||
|
@@ -10,5 +10,7 @@ export const environment: Partial<BuildConfig> = {
|
||||
time: false,
|
||||
inlineCriticalCss: false,
|
||||
paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ],
|
||||
}
|
||||
enableSearchComponent: false,
|
||||
enableBrowseComponent: false,
|
||||
},
|
||||
};
|
||||
|
@@ -13,6 +13,8 @@ export const environment: BuildConfig = {
|
||||
time: false,
|
||||
inlineCriticalCss: false,
|
||||
paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ],
|
||||
enableSearchComponent: false,
|
||||
enableBrowseComponent: false,
|
||||
},
|
||||
|
||||
// Angular Universal server settings.
|
||||
@@ -321,4 +323,8 @@ export const environment: BuildConfig = {
|
||||
messageTimeOutDurationMs: 30000,
|
||||
isVisible: false,
|
||||
},
|
||||
|
||||
search: {
|
||||
filterPlaceholdersCount: 5
|
||||
}
|
||||
};
|
||||
|
@@ -15,7 +15,9 @@ export const environment: Partial<BuildConfig> = {
|
||||
time: false,
|
||||
inlineCriticalCss: false,
|
||||
paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ],
|
||||
}
|
||||
enableSearchComponent: false,
|
||||
enableBrowseComponent: false,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
|
@@ -138,4 +138,16 @@
|
||||
--green1: #1FB300; // This variable represents the success color for the Klaro cookie banner
|
||||
--button-text-color-cookie: #333; // This variable represents the text color for buttons in the Klaro cookie banner
|
||||
--very-dark-cyan: #215E50; // This variable represents the background color of the save cookies button
|
||||
|
||||
--ds-search-skeleton-text-height: 20px;
|
||||
--ds-search-skeleton-badge-height: 18px;
|
||||
--ds-search-skeleton-thumbnail-margin: 1em;
|
||||
--ds-search-skeleton-text-line-count: 2;
|
||||
--ds-search-skeleton-badge-width: 75px;
|
||||
--ds-search-skeleton-filter-badge-width: 200px;
|
||||
--ds-search-skeleton-info-width: 200px;
|
||||
--ds-search-skeleton-card-height: 435px;
|
||||
|
||||
--ds-filters-skeleton-height: 40px;
|
||||
--ds-filters-skeleton-spacing: 12px;
|
||||
}
|
||||
|
@@ -159,6 +159,8 @@ import { RequestCopyModule } from 'src/app/request-copy/request-copy.module';
|
||||
import {UserMenuComponent} from './app/shared/auth-nav-menu/user-menu/user-menu.component';
|
||||
import { BrowseByComponent } from './app/shared/browse-by/browse-by.component';
|
||||
import { RegisterEmailFormComponent } from './app/register-email-form/register-email-form.component';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
|
||||
|
||||
const DECLARATIONS = [
|
||||
FileSectionComponent,
|
||||
@@ -305,6 +307,7 @@ const DECLARATIONS = [
|
||||
NgxGalleryModule,
|
||||
FormModule,
|
||||
RequestCopyModule,
|
||||
NgxSkeletonLoaderModule
|
||||
],
|
||||
declarations: DECLARATIONS,
|
||||
exports: [
|
||||
|
@@ -18,6 +18,7 @@
|
||||
|
||||
/* set the next two properties as `--ds-header-navbar-border-bottom-*`
|
||||
in order to keep the bottom border of the header when navbar is expanded */
|
||||
|
||||
--ds-expandable-navbar-border-top-color: #{$white};
|
||||
--ds-expandable-navbar-border-top-height: 0;
|
||||
--ds-expandable-navbar-padding-top: 0;
|
||||
|
15
yarn.lock
15
yarn.lock
@@ -8415,6 +8415,14 @@ ngx-pagination@6.0.3:
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
ngx-skeleton-loader@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ngx-skeleton-loader/-/ngx-skeleton-loader-7.0.0.tgz#3b1325025a7208a20f3a0fdba6e578532a09cfcd"
|
||||
integrity sha512-myc6GNcNhyksZrimIFkCxeihi0kQ8JhQVZiGbtiIv4gYrnnRk5nXbs3kYitK8E8OstHG+jlsmRofqGBxuIsYTA==
|
||||
dependencies:
|
||||
perf-marks "^1.13.4"
|
||||
tslib "^2.0.0"
|
||||
|
||||
ngx-sortablejs@^11.1.0:
|
||||
version "11.1.0"
|
||||
resolved "https://registry.npmjs.org/ngx-sortablejs/-/ngx-sortablejs-11.1.0.tgz"
|
||||
@@ -9060,6 +9068,13 @@ pend@~1.2.0:
|
||||
resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz"
|
||||
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
|
||||
|
||||
perf-marks@^1.13.4:
|
||||
version "1.14.2"
|
||||
resolved "https://registry.yarnpkg.com/perf-marks/-/perf-marks-1.14.2.tgz#7511c24239b9c2071717993a33ec3057f387b8c7"
|
||||
integrity sha512-N0/bQcuTlETpgox/DsXS1voGjqaoamMoiyhncgeW3rSHy/qw8URVgmPRYfFDQns/+C6yFUHDbeSBGL7ixT6Y4A==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
performance-now@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz"
|
||||
|
Reference in New Issue
Block a user