();
+
/**
* 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));
+ })
+ );
}
}
diff --git a/src/app/shared/search/search-filters/search-filters.component.html b/src/app/shared/search/search-filters/search-filters.component.html
index c006d80c44..e1e54c683d 100644
--- a/src/app/shared/search/search-filters/search-filters.component.html
+++ b/src/app/shared/search/search-filters/search-filters.component.html
@@ -1,7 +1,13 @@
{{"search.filters.head" | translate}}
-
+
+
+
+
+
{{"search.filters.reset" | translate}}
diff --git a/src/app/shared/search/search-filters/search-filters.component.scss b/src/app/shared/search/search-filters/search-filters.component.scss
index b5b2816e89..6170b9281c 100644
--- a/src/app/shared/search/search-filters/search-filters.component.scss
+++ b/src/app/shared/search/search-filters/search-filters.component.scss
@@ -1,2 +1,12 @@
@import '../../../../styles/variables';
-@import '../../../../styles/mixins';
\ No newline at end of file
+@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;
+ }
+}
diff --git a/src/app/shared/search/search-filters/search-filters.component.spec.ts b/src/app/shared/search/search-filters/search-filters.component.spec.ts
index 522459b603..864d7b583e 100644
--- a/src/app/shared/search/search-filters/search-filters.component.spec.ts
+++ b/src/app/shared/search/search-filters/search-filters.component.spec.ts
@@ -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, {
diff --git a/src/app/shared/search/search-filters/search-filters.component.ts b/src/app/shared/search/search-filters/search-filters.component.ts
index 766939226d..b491f21177 100644
--- a/src/app/shared/search/search-filters/search-filters.component.ts
+++ b/src/app/shared/search/search-filters/search-filters.component.ts
@@ -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;
+ }
+ }
}
diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html
new file mode 100644
index 0000000000..c318eea2e1
--- /dev/null
+++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss
new file mode 100644
index 0000000000..9345f1ab43
--- /dev/null
+++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss
@@ -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);
+ }
+ }
+}
+
+
diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.spec.ts b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.spec.ts
new file mode 100644
index 0000000000..86277181fe
--- /dev/null
+++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.spec.ts
@@ -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
;
+
+ 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();
+ });
+});
diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts
new file mode 100644
index 0000000000..179b26f1fd
--- /dev/null
+++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts
@@ -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;
+ /**
+ * 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;
+ }
+ }
+}
diff --git a/src/app/shared/search/search-results/search-results.component.html b/src/app/shared/search/search-results/search-results.component.html
index 1f1e58ea10..a2bc91195c 100644
--- a/src/app/shared/search/search-results/search-results.component.html
+++ b/src/app/shared/search/search-results/search-results.component.html
@@ -19,7 +19,13 @@
(selectObject)="selectObject.emit($event)">
-
+
+
+
diff --git a/src/app/shared/search/search-results/search-results.component.scss b/src/app/shared/search/search-results/search-results.component.scss
new file mode 100644
index 0000000000..6e369c729b
--- /dev/null
+++ b/src/app/shared/search/search-results/search-results.component.scss
@@ -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);
+ }
+}
diff --git a/src/app/shared/search/search-results/search-results.component.spec.ts b/src/app/shared/search/search-results/search-results.component.spec.ts
index 4cc4f84f65..1ef596a4ed 100644
--- a/src/app/shared/search/search-results/search-results.component.spec.ts
+++ b/src/app/shared/search/search-results/search-results.component.spec.ts
@@ -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));
diff --git a/src/app/shared/search/search-results/search-results.component.ts b/src/app/shared/search/search-results/search-results.component.ts
index 15d2cc0f00..9050ce9cd0 100644
--- a/src/app/shared/search/search-results/search-results.component.ts
+++ b/src/app/shared/search/search-results/search-results.component.ts
@@ -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;
+
/**
* The link type of the listed search results
*/
@@ -104,6 +111,13 @@ export class SearchResultsComponent {
@Output() selectObject: EventEmitter = new EventEmitter();
+ constructor(
+ protected searchConfigService: SearchConfigurationService,
+ protected searchService: SearchService,
+ ) {
+ this.filters$ = this.searchConfigService.getCurrentFilters();
+ }
+
/**
* Check if search results are loading
*/
diff --git a/src/app/shared/search/search.component.spec.ts b/src/app/shared/search/search.component.spec.ts
index 8ffd832009..05d4fc6b85 100644
--- a/src/app/shared/search/search.component.spec.ts
+++ b/src/app/shared/search/search.component.spec.ts
@@ -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();
+ }));
+ });
});
});
diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts
index 5a848c9786..527d2d2719 100644
--- a/src/app/shared/search/search.component.ts
+++ b/src/app/shared/search/search.component.ts
@@ -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);
diff --git a/src/app/shared/search/search.module.ts b/src/app/shared/search/search.module.ts
index 69500999a8..e2075a5f56 100644
--- a/src/app/shared/search/search.module.ts
+++ b/src/app/shared/search/search.module.ts
@@ -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
diff --git a/src/app/shared/testing/search-configuration-service.stub.ts b/src/app/shared/testing/search-configuration-service.stub.ts
index 78b358f0d4..ef72e40041 100644
--- a/src/app/shared/testing/search-configuration-service.stub.ts
+++ b/src/app/shared/testing/search-configuration-service.stub.ts
@@ -13,6 +13,10 @@ export class SearchConfigurationServiceStub {
return observableOf([]);
}
+ getCurrentFilters() {
+ return observableOf([]);
+ }
+
getCurrentScope(a) {
return observableOf('test-id');
}
diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts
index a2bf0cb876..af78d4ab88 100644
--- a/src/config/app-config.interface.ts
+++ b/src/config/app-config.interface.ts
@@ -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
}
/**
diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts
index a3a490538c..de4f3bd56e 100644
--- a/src/config/default-app-config.ts
+++ b/src/config/default-app-config.ts
@@ -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
+ };
}
diff --git a/src/config/search-page-config.interface.ts b/src/config/search-page-config.interface.ts
new file mode 100644
index 0000000000..410876cde2
--- /dev/null
+++ b/src/config/search-page-config.interface.ts
@@ -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;
+
+}
diff --git a/src/config/universal-config.interface.ts b/src/config/universal-config.interface.ts
index e54168823f..e3f7f399a9 100644
--- a/src/config/universal-config.interface.ts
+++ b/src/config/universal-config.interface.ts
@@ -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;
+ /**
+ * Whether to enable rendering of search component on SSR
+ */
+ enableSearchComponent: boolean;
+
+ /**
+ * Whether to enable rendering of browse component on SSR
+ */
+ enableBrowseComponent: boolean;
}
diff --git a/src/environments/environment.production.ts b/src/environments/environment.production.ts
index 46a93519df..c3cb74651b 100644
--- a/src/environments/environment.production.ts
+++ b/src/environments/environment.production.ts
@@ -10,5 +10,7 @@ export const environment: Partial = {
time: false,
inlineCriticalCss: false,
paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ],
- }
+ enableSearchComponent: false,
+ enableBrowseComponent: false,
+ },
};
diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts
index e872285f61..e6ffe85df6 100644
--- a/src/environments/environment.test.ts
+++ b/src/environments/environment.test.ts
@@ -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
+ }
};
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
index 25af371e47..419238f264 100644
--- a/src/environments/environment.ts
+++ b/src/environments/environment.ts
@@ -15,7 +15,9 @@ export const environment: Partial = {
time: false,
inlineCriticalCss: false,
paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ],
- }
+ enableSearchComponent: false,
+ enableBrowseComponent: false,
+ },
};
/*
diff --git a/src/styles/_custom_variables.scss b/src/styles/_custom_variables.scss
index aa67acac1c..3c3244549e 100644
--- a/src/styles/_custom_variables.scss
+++ b/src/styles/_custom_variables.scss
@@ -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;
}
diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts
index 73400e7880..546d2dccbf 100644
--- a/src/themes/custom/lazy-theme.module.ts
+++ b/src/themes/custom/lazy-theme.module.ts
@@ -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: [
diff --git a/src/themes/dspace/styles/_theme_css_variable_overrides.scss b/src/themes/dspace/styles/_theme_css_variable_overrides.scss
index 8b95cd0033..46d73997eb 100644
--- a/src/themes/dspace/styles/_theme_css_variable_overrides.scss
+++ b/src/themes/dspace/styles/_theme_css_variable_overrides.scss
@@ -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;
diff --git a/yarn.lock b/yarn.lock
index 58d8f59fbc..86a07c555a 100644
--- a/yarn.lock
+++ b/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"