Skip to content

Commit f81924c

Browse files
Davide NegrettiFrancescoMolinaro
authored andcommitted
Merged in task/dspace-cris-2023_02_x/DSC-1650-davide (pull request DSpace#1929)
Task/dspace cris 2023 02 x/DSC-1650 davide Approved-by: Francesco Molinaro
2 parents 0ff731c + 44e69cf commit f81924c

20 files changed

Lines changed: 266 additions & 158 deletions

config/config.example.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ browseBy:
279279
showLabels: true
280280
# If true, thumbnail images for items will be added to BOTH search and browse result lists.
281281
showThumbnails: true
282+
# Whether to add item thumbnail images to BOTH browse and search result lists.
283+
showMetrics: false
282284
# The number of entries in a paginated browse results list.
283285
# Rounded to the nearest size in the list of selectable sizes on the
284286
# settings menu.

src/app/core/layout/models/section.model.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export interface TopSection extends SectionComponent {
6060
componentType: 'top';
6161
numberOfItems: number;
6262
showThumbnails: boolean;
63+
template: TopSectionTemplateType;
6364
}
6465

6566
export interface SearchSection extends SectionComponent {
@@ -96,3 +97,10 @@ export interface TopSectionColumn {
9697
metadataField: string;
9798
titleKey: string;
9899
}
100+
101+
/**
102+
* Represents the type of template to use for the section
103+
*/
104+
export enum TopSectionTemplateType {
105+
DEFAULT = 'default', // CRIS default template
106+
}

src/app/explore-page/explore-page.component.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
1010
import { Observable, of } from 'rxjs';
1111
import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock';
1212
import { RemoteData } from '../core/data/remote-data';
13-
import { BrowseSection, FacetSection, SearchSection, Section, TopSection } from '../core/layout/models/section.model';
13+
import { BrowseSection, FacetSection, SearchSection, Section, TopSectionTemplateType, TopSection } from '../core/layout/models/section.model';
1414
import { SectionDataService } from '../core/layout/section-data.service';
1515
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
1616
import { ExplorePageComponent } from './explore-page.component';
@@ -37,6 +37,7 @@ describe('ExploreComponent', () => {
3737
numberOfItems: 5,
3838
titleKey: 'lastPublications',
3939
showThumbnails: false,
40+
template: TopSectionTemplateType.DEFAULT
4041
};
4142

4243
const searchComponent: SearchSection = {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { followLink } from '../utils/follow-link-config.model';
2+
import { CollectionElementLinkType } from '../object-collection/collection-element-link.type';
3+
import { TopSection } from '../../core/layout/models/section.model';
4+
import { Component, Input, OnChanges, OnInit, PLATFORM_ID, inject } from '@angular/core';
5+
import { isPlatformServer } from '@angular/common';
6+
7+
import { SearchService } from '../../core/shared/search/search.service';
8+
import { PaginatedSearchOptions } from '../search/models/paginated-search-options.model';
9+
import { DSpaceObject } from '../../core/shared/dspace-object.model';
10+
import { SearchResult } from '../search/models/search-result.model';
11+
import { Context } from '../../core/shared/context.model';
12+
import { RemoteData } from '../../core/data/remote-data';
13+
import { PaginatedList } from '../../core/data/paginated-list.model';
14+
import {
15+
getAllCompletedRemoteData,
16+
getPaginatedListPayload,
17+
getRemoteDataPayload,
18+
toDSpaceObjectListRD,
19+
} from '../../core/shared/operators';
20+
import { APP_CONFIG } from '../../../config/app-config.interface';
21+
import { BehaviorSubject, Observable, mergeMap } from 'rxjs';
22+
import { Item } from '../../core/shared/item.model';
23+
import { getItemPageRoute } from '../../item-page/item-page-routing-paths';
24+
25+
@Component({
26+
template: ''
27+
})
28+
export abstract class AbstractBrowseElementsComponent implements OnInit, OnChanges {
29+
30+
protected readonly appConfig = inject(APP_CONFIG);
31+
protected readonly platformId = inject(PLATFORM_ID);
32+
protected readonly searchService = inject(SearchService);
33+
34+
protected followThumbnailLink: boolean; // to be overridden
35+
36+
@Input() paginatedSearchOptions: PaginatedSearchOptions;
37+
38+
@Input() context: Context;
39+
40+
@Input() topSection: TopSection;
41+
42+
public collectionElementLinkTypeEnum = CollectionElementLinkType;
43+
44+
paginatedSearchOptionsBS: BehaviorSubject<PaginatedSearchOptions>;
45+
46+
searchResults$: Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>>;
47+
48+
searchResultArray$: Observable<DSpaceObject[]>;
49+
50+
ngOnChanges() {
51+
if (isPlatformServer(this.platformId)) {
52+
return;
53+
}
54+
this.paginatedSearchOptionsBS?.next(this.paginatedSearchOptions);
55+
}
56+
57+
ngOnInit() {
58+
const followLinks = this.followThumbnailLink ? [followLink('thumbnail')] : [];
59+
this.paginatedSearchOptionsBS = new BehaviorSubject<PaginatedSearchOptions>(this.paginatedSearchOptions);
60+
this.searchResults$ = this.paginatedSearchOptionsBS.asObservable().pipe(
61+
mergeMap((paginatedSearchOptions) =>
62+
this.searchService.search(paginatedSearchOptions, null, true, true, ...followLinks),
63+
),
64+
getAllCompletedRemoteData(),
65+
);
66+
67+
this.searchResultArray$ = this.searchResults$.pipe(
68+
toDSpaceObjectListRD(),
69+
getRemoteDataPayload(),
70+
getPaginatedListPayload(),
71+
);
72+
}
73+
74+
getItemPageRoute(item: DSpaceObject | Item) {
75+
return getItemPageRoute(item as Item);
76+
}
77+
}
Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
1-
<div class="m-2">
2-
<ng-container *ngIf="searchResults">
3-
<ul *ngIf="searchResults?.hasSucceeded" class="list-unstyled">
4-
<li *ngFor="let object of searchResults?.payload?.page; let i = index; let last = last" class="mt-4 mb-4 d-flex" [class.border-bottom]="true && !last">
5-
<ds-listable-object-component-loader [context]="context"
6-
[index]="i"
7-
[listID]="paginatedSearchOptions.configuration"
8-
[object]="object"
9-
[showLabel]="showLabel"
10-
[showMetrics]="showMetrics"
11-
[showThumbnails]="showThumbnails"
12-
[viewMode]="paginatedSearchOptions.view"></ds-listable-object-component-loader>
13-
</li>
14-
</ul>
15-
<div *ngIf="searchResults?.hasFailed">
16-
{{ 'remote.error' | translate }}
17-
</div>
18-
</ng-container>
19-
<ds-loading *ngIf="!searchResults"></ds-loading>
1+
<div [ngSwitch]="(sectionTemplateType | lowercase)">
2+
3+
<ng-container *ngSwitchDefault>
4+
<ds-themed-default-browse-elements
5+
[showMetrics]="showMetrics"
6+
[showThumbnails]="topSection.showThumbnails"
7+
[topSection]="topSection"
8+
[showLabel]="showLabel"
9+
[paginatedSearchOptions]="paginatedSearchOptionsBS.asObservable() | async"
10+
[context]="context"
11+
></ds-themed-default-browse-elements>
12+
</ng-container>
13+
2014
</div>
Lines changed: 35 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
11
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2-
import { ChangeDetectorRef, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
3-
4-
import { of } from 'rxjs';
2+
import { NO_ERRORS_SCHEMA } from '@angular/core';
53

64
import { BrowseMostElementsComponent } from './browse-most-elements.component';
7-
import { SearchManager } from '../../core/browse/search-manager';
8-
import { PaginatedSearchOptions } from '../search/models/paginated-search-options.model';
9-
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
10-
import { PaginationComponentOptions } from '../pagination/pagination-component-options.model';
11-
import { APP_CONFIG } from '../../../config/app-config.interface';
5+
import { TopSectionTemplateType } from '../../core/layout/models/section.model';
6+
import { By } from '@angular/platform-browser';
127
import { ItemSearchResult } from '../object-collection/shared/item-search-result.model';
138
import { Item } from '../../core/shared/item.model';
14-
import { createSuccessfulRemoteDataObject } from '../remote-data.utils';
15-
import { buildPaginatedList } from '../../core/data/paginated-list.model';
16-
import { PageInfo } from '../../core/shared/page-info.model';
17-
import { followLink } from '../utils/follow-link-config.model';
9+
import { of } from 'rxjs';
1810

1911
describe('BrowseMostElementsComponent', () => {
2012
let component: BrowseMostElementsComponent;
@@ -53,91 +45,60 @@ describe('BrowseMostElementsComponent', () => {
5345
]
5446
}
5547
});
56-
const mockResponse = createSuccessfulRemoteDataObject(buildPaginatedList(new PageInfo(), [mockResultObject]));
57-
58-
const mockSearchService = {
59-
search: jasmine.createSpy('search').and.returnValue(of(mockResponse)), // Replace with your desired response
60-
};
61-
62-
const mockConfig = {
63-
browseBy: {
64-
showThumbnails: true
65-
}
66-
};
6748

6849
beforeEach(waitForAsync(() => {
6950
TestBed.configureTestingModule({
7051
declarations: [BrowseMostElementsComponent],
7152
providers: [
72-
{ provide: APP_CONFIG, useValue: mockConfig },
73-
{ provide: SearchManager, useValue: mockSearchService },
74-
{ provide: ChangeDetectorRef, useValue: {} },
7553
],
76-
schemas: [CUSTOM_ELEMENTS_SCHEMA], // Ignore unknown Angular elements
54+
schemas: [NO_ERRORS_SCHEMA],
7755
}).compileComponents();
7856
}));
7957

8058
beforeEach(() => {
8159
fixture = TestBed.createComponent(BrowseMostElementsComponent);
8260
component = fixture.componentInstance;
83-
component.paginatedSearchOptions = new PaginatedSearchOptions({
84-
configuration: 'test',
85-
pagination: Object.assign(new PaginationComponentOptions(), {
86-
id: 'search-object-pagination',
87-
pageSize: 5,
88-
currentPage: 1
89-
}),
90-
sort: new SortOptions('dc.title', SortDirection.ASC)
91-
});
61+
component.topSection = {
62+
template: TopSectionTemplateType.DEFAULT
63+
} as any;
64+
fixture.detectChanges();
9265
});
9366

9467
it('should create', () => {
95-
component.showThumbnails = true;
9668
fixture.detectChanges();
9769
expect(component).toBeTruthy();
9870
});
9971

100-
it('should call searchService.search on ngOnInit with followLinks', () => {
101-
component.showThumbnails = true;
102-
fixture.detectChanges();
103-
104-
expect(mockSearchService.search).toHaveBeenCalledWith(
105-
component.paginatedSearchOptions,
106-
null,
107-
true,
108-
true,
109-
followLink('thumbnail')
110-
);
111-
});
112-
113-
it('should call searchService.search on ngOnInit with followLinks', () => {
114-
component.showThumbnails = undefined;
115-
fixture.detectChanges();
116-
117-
expect(mockSearchService.search).toHaveBeenCalledWith(
118-
component.paginatedSearchOptions,
119-
null,
120-
true,
121-
true,
122-
followLink('thumbnail')
123-
);
124-
});
72+
describe('when the templateType is DEFAULT', () => {
73+
beforeEach(() => {
74+
component.topSection = {
75+
template: TopSectionTemplateType.DEFAULT
76+
} as any;
77+
fixture.detectChanges();
78+
});
12579

126-
it('should call searchService.search on ngOnInit without followLinks', () => {
127-
component.showThumbnails = false;
128-
fixture.detectChanges();
80+
it('should display ds-themed-default-browse-elements', () => {
81+
const defaultElement = fixture.debugElement.query(By.css('ds-themed-default-browse-elements'));
82+
expect(defaultElement).toBeTruthy();
83+
});
12984

130-
expect(mockSearchService.search).toHaveBeenCalledWith(
131-
component.paginatedSearchOptions,
132-
null,
133-
true,
134-
true
135-
);
85+
it('should not display ds-themed-images-browse-elements', () => {
86+
const imageElement = fixture.debugElement.query(By.css('ds-themed-images-browse-elements'));
87+
expect(imageElement).toBeNull();
88+
});
13689
});
13790

138-
it('should update searchResults after searchService response', () => {
139-
component.ngOnInit();
91+
describe('when the templateType is not recognized', () => {
92+
beforeEach(() => {
93+
component.topSection = {
94+
template: 'not recognized' as any
95+
} as any;
96+
fixture.detectChanges();
97+
});
14098

141-
expect(component.searchResults).toEqual(mockResponse);
99+
it('should display ds-themed-default-browse-elements', () => {
100+
const defaultElement = fixture.debugElement.query(By.css('ds-themed-default-browse-elements'));
101+
expect(defaultElement).toBeTruthy();
102+
});
142103
});
143104
});
Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,38 @@
1-
import { ChangeDetectorRef, Component, Inject, Input, OnInit, PLATFORM_ID } from '@angular/core';
2-
import { isPlatformServer } from '@angular/common';
3-
4-
import { SearchManager } from '../../core/browse/search-manager';
1+
import { TopSection, TopSectionTemplateType } from './../../core/layout/models/section.model';
2+
import { Component, Input, OnChanges, OnInit } from '@angular/core';
53
import { PaginatedSearchOptions } from '../search/models/paginated-search-options.model';
6-
import { DSpaceObject } from '../../core/shared/dspace-object.model';
7-
import { SearchResult } from '../search/models/search-result.model';
84
import { Context } from '../../core/shared/context.model';
9-
import { RemoteData } from '../../core/data/remote-data';
10-
import { PaginatedList } from '../../core/data/paginated-list.model';
11-
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
12-
import { followLink } from '../utils/follow-link-config.model';
13-
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
5+
import { BehaviorSubject } from 'rxjs';
146

157
@Component({
168
selector: 'ds-browse-most-elements',
179
styleUrls: ['./browse-most-elements.component.scss'],
1810
templateUrl: './browse-most-elements.component.html'
1911
})
2012

21-
export class BrowseMostElementsComponent implements OnInit {
13+
export class BrowseMostElementsComponent implements OnInit, OnChanges {
2214

2315
@Input() paginatedSearchOptions: PaginatedSearchOptions;
2416

2517
@Input() context: Context;
26-
/**
27-
* Whether to show the badge label or not
28-
*/
29-
@Input() showLabel: boolean;
3018

31-
/**
32-
* Whether to show the metrics badges
33-
*/
34-
@Input() showMetrics: boolean;
19+
showLabel: boolean;
3520

36-
/**
37-
* Whether to show the thumbnail preview
38-
*/
39-
@Input() showThumbnails: boolean;
21+
showMetrics = true;
4022

41-
searchResults: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>;
23+
@Input() topSection: TopSection;
4224

43-
constructor(
44-
@Inject(APP_CONFIG) protected appConfig: AppConfig,
45-
@Inject(PLATFORM_ID) private platformId: Object,
46-
private searchService: SearchManager,
47-
private cdr: ChangeDetectorRef) {
25+
paginatedSearchOptionsBS = new BehaviorSubject<PaginatedSearchOptions>(null);
4826

49-
}
27+
templateTypeEnum = TopSectionTemplateType;
5028

51-
ngOnInit() {
52-
if (isPlatformServer(this.platformId)) {
53-
return;
54-
}
29+
sectionTemplateType: TopSectionTemplateType;
5530

56-
const showThumbnails = this.showThumbnails ?? this.appConfig.browseBy.showThumbnails;
57-
const followLinks = showThumbnails ? [followLink('thumbnail')] : [];
58-
this.searchService.search(this.paginatedSearchOptions, null, true, true, ...followLinks).pipe(
59-
getFirstCompletedRemoteData(),
60-
).subscribe((response: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>) => {
61-
this.searchResults = response as any;
62-
this.cdr.detectChanges();
63-
});
31+
ngOnInit(): void {
32+
this.sectionTemplateType = this.topSection?.template ?? TopSectionTemplateType.DEFAULT;
6433
}
6534

35+
ngOnChanges() { // trigger change detection on child components
36+
this.paginatedSearchOptionsBS.next(this.paginatedSearchOptions);
37+
}
6638
}

0 commit comments

Comments
 (0)