diff --git a/config/config.example.yml b/config/config.example.yml index c1d7f967a4..0a039a40ad 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -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 @@ -84,7 +92,7 @@ cache: anonymousCache: # Maximum number of pages to cache. Default is zero (0) which means anonymous user cache is disabled. # As all pages are cached in server memory, increasing this value will increase memory needs. - # Individual cached pages are usually small (<100KB), so a value of max=1000 would only require ~100MB of memory. + # Individual cached pages are usually small (<100KB), so a value of max=1000 would only require ~100MB of memory. max: 0 # Amount of time after which cached pages are considered stale (in ms). After becoming stale, the cached # copy is automatically refreshed on the next request. @@ -394,7 +402,7 @@ vocabularies: vocabulary: 'srsc' enabled: true -# Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query. +# Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query. comcolSelectionSort: sortField: 'dc.title' sortDirection: 'ASC' diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index e18841f9ca..7f14f45b2c 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -23,6 +23,7 @@ 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'; @@ -147,7 +148,8 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { currentPage: 1, pageSize: this.appConfig.browseBy.pageSize, }); - } + this.renderOnServerSide = environment.ssr.enableBrowseComponent; + } ngOnInit(): void { diff --git a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts index 67e8906bb5..6f88d3924a 100644 --- a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts @@ -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(); + /** * True when the filter is 100% collapsed in the UI */ @@ -99,6 +101,9 @@ export class SearchFilterComponent implements OnInit { this.filterService.expand(this.filter.name); } }); + this.active$.pipe(take(1)).subscribe(() => { + this.isVisibilityComputed.emit(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 97f8c75234..45875ad7ae 100644 --- a/src/app/shared/search/search-filters/search-filters.component.html +++ b/src/app/shared/search/search-filters/search-filters.component.html @@ -1,11 +1,12 @@

{{"search.filters.head" | translate}}

-
+
- +
- + + - + {{"search.filters.reset" | translate}} 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 c960dfda56..28f351cb6c 100644 --- a/src/app/shared/search/search-filters/search-filters.component.ts +++ b/src/app/shared/search/search-filters/search-filters.component.ts @@ -61,6 +61,11 @@ export class SearchFiltersComponent implements OnInit, OnDestroy { */ searchLink: string; + /** + * Filters for which visibility has been computed + */ + filtersWithComputedVisibility = 0; + subs = []; defaultFilterCount: number; @@ -114,4 +119,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 index 4bc70d1263..84a67f4358 100644 --- 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 @@ -1,24 +1,36 @@
-
+
-@for (result of loadingResults; track result) { -
- @if(showThumbnails) { -
- +@if((viewMode$ | async) === ViewMode.ListElement) { + @for (result of loadingResults; track result) { +
+ @if(showThumbnails) { +
+ +
+ } +
+
+ +
+
+ +
+
+
+ } +} @else if ((viewMode$ | async) === ViewMode.GridElement) { +
+ @for (result of loadingResults; track result) { +
+
+ +
} -
-
- -
-
- -
-
} 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 index 1a111f9dd9..e05e4be6d8 100644 --- 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 @@ -1,4 +1,6 @@ :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); @@ -27,13 +29,31 @@ } } + .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); + } + } } .result-row { margin-right: 0; margin-left: 0; } + 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 index fef1529460..d2896460e1 100644 --- 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 @@ -1,10 +1,17 @@ +import { + AsyncPipe, + NgForOf, +} from '@angular/common'; import { Component, Input, OnInit, } from '@angular/core'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; +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({ @@ -12,6 +19,8 @@ import { hasValue } from '../../../empty.util'; standalone: true, imports: [ NgxSkeletonLoaderModule, + AsyncPipe, + NgForOf, ], templateUrl: './search-results-skeleton.component.html', styleUrl: './search-results-skeleton.component.scss', @@ -26,8 +35,16 @@ export class SearchResultsSkeletonComponent implements OnInit { @Input() textLineCount = 2; + public viewMode$: Observable; + public loadingResults: number[]; + protected readonly ViewMode = ViewMode; + + constructor(private searchService: SearchService) { + this.viewMode$ = searchService.getViewMode(); + } + ngOnInit() { this.loadingResults = Array.from({ length: this.numberOfResults }, (_, i) => i + 1); 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 643cf5eb4b..71839d009b 100644 --- a/src/app/shared/search/search-results/search-results.component.html +++ b/src/app/shared/search/search-results/search-results.component.html @@ -1,12 +1,10 @@ -@if ((filters$ | async).length > 0 && isLoading()) { +@if ((activeFilters$ | async).length > 0 && (appliedFilters$ | async).length === 0) {
-
- @for (filter of (filters$| async); track filter.key) { -
- +
+
+
- }
diff --git a/src/app/shared/search/search-results/search-results.component.scss b/src/app/shared/search/search-results/search-results.component.scss index 4bf3f8325e..6e369c729b 100644 --- a/src/app/shared/search/search-results/search-results.component.scss +++ b/src/app/shared/search/search-results/search-results.component.scss @@ -4,7 +4,14 @@ background-color: var(--bs-light); box-shadow: none; width: var(--ds-search-skeleton-filter-badge-width); - height: var(--ds-search-skeleton-text-height); + 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.ts b/src/app/shared/search/search-results/search-results.component.ts index 296d73f9d8..1faf8dd778 100644 --- a/src/app/shared/search/search-results/search-results.component.ts +++ b/src/app/shared/search/search-results/search-results.component.ts @@ -14,6 +14,7 @@ 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; @@ -110,7 +111,10 @@ export class SearchResultsComponent { @Output() selectObject: EventEmitter = new EventEmitter(); - constructor(private searchConfigService: SearchConfigurationService) { + constructor( + protected searchConfigService: SearchConfigurationService, + protected searchService: SearchService, + ) { this.filters$ = this.searchConfigService.getCurrentFilters(); } diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index 362257be0e..1e8811aa86 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -49,6 +49,7 @@ import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-ro 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', @@ -304,6 +305,7 @@ export class SearchComponent implements OnDestroy, OnInit { @Inject(PLATFORM_ID) public platformId: any, ) { this.isXsOrSm$ = this.windowService.isXsOrSm(); + this.renderOnServerSide = environment.universal.enableSearchComponent; } /** 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..ea49f5eb10 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. 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 d64f1c6acd..d47f5d2dfa 100644 --- a/src/styles/_custom_variables.scss +++ b/src/styles/_custom_variables.scss @@ -140,6 +140,7 @@ --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-height: 125px; --ds-search-skeleton-thumbnail-width: 90px; --ds-search-skeleton-thumbnail-padding: 1em; @@ -148,6 +149,7 @@ --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;