Merge pull request #1780 from mspalti/collection-thumbnail-embed

Show thumbnails in result lists
This commit is contained in:
Tim Donohue
2022-09-23 10:11:53 -05:00
committed by GitHub
102 changed files with 1632 additions and 254 deletions

View File

@@ -174,6 +174,8 @@ browseBy:
fiveYearLimit: 30 fiveYearLimit: 30
# The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items) # The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items)
defaultLowerLimit: 1900 defaultLowerLimit: 1900
# If true, thumbnail images for items will be added to BOTH search and browse result lists.
showThumbnails: true
# The number of entries in a paginated browse results list. # The number of entries in a paginated browse results list.
# Rounded to the nearest size in the list of selectable sizes on the # Rounded to the nearest size in the list of selectable sizes on the
# settings menu. # settings menu.

View File

@@ -13,6 +13,8 @@ import { RouterTestingModule } from '@angular/router/testing';
import { getCollectionEditRoute } from '../../../../../collection-page/collection-page-routing-paths'; import { getCollectionEditRoute } from '../../../../../collection-page/collection-page-routing-paths';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
import { environment } from '../../../../../../environments/environment';
describe('CollectionAdminSearchResultListElementComponent', () => { describe('CollectionAdminSearchResultListElementComponent', () => {
let component: CollectionAdminSearchResultListElementComponent; let component: CollectionAdminSearchResultListElementComponent;
@@ -36,7 +38,8 @@ describe('CollectionAdminSearchResultListElementComponent', () => {
], ],
declarations: [CollectionAdminSearchResultListElementComponent], declarations: [CollectionAdminSearchResultListElementComponent],
providers: [{ provide: TruncatableService, useValue: {} }, providers: [{ provide: TruncatableService, useValue: {} },
{ provide: DSONameService, useClass: DSONameServiceMock }], { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environment }],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}) })
.compileComponents(); .compileComponents();

View File

@@ -13,6 +13,8 @@ import { Community } from '../../../../../core/shared/community.model';
import { getCommunityEditRoute } from '../../../../../community-page/community-page-routing-paths'; import { getCommunityEditRoute } from '../../../../../community-page/community-page-routing-paths';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
import { environment } from '../../../../../../environments/environment';
describe('CommunityAdminSearchResultListElementComponent', () => { describe('CommunityAdminSearchResultListElementComponent', () => {
let component: CommunityAdminSearchResultListElementComponent; let component: CommunityAdminSearchResultListElementComponent;
@@ -36,7 +38,8 @@ describe('CommunityAdminSearchResultListElementComponent', () => {
], ],
declarations: [CommunityAdminSearchResultListElementComponent], declarations: [CommunityAdminSearchResultListElementComponent],
providers: [{ provide: TruncatableService, useValue: {} }, providers: [{ provide: TruncatableService, useValue: {} },
{ provide: DSONameService, useClass: DSONameServiceMock }], { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environment }],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}) })
.compileComponents(); .compileComponents();

View File

@@ -10,6 +10,8 @@ import { ItemAdminSearchResultListElementComponent } from './item-admin-search-r
import { Item } from '../../../../../core/shared/item.model'; import { Item } from '../../../../../core/shared/item.model';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
import { environment } from '../../../../../../environments/environment';
describe('ItemAdminSearchResultListElementComponent', () => { describe('ItemAdminSearchResultListElementComponent', () => {
let component: ItemAdminSearchResultListElementComponent; let component: ItemAdminSearchResultListElementComponent;
@@ -33,7 +35,8 @@ describe('ItemAdminSearchResultListElementComponent', () => {
], ],
declarations: [ItemAdminSearchResultListElementComponent], declarations: [ItemAdminSearchResultListElementComponent],
providers: [{ provide: TruncatableService, useValue: {} }, providers: [{ provide: TruncatableService, useValue: {} },
{ provide: DSONameService, useClass: DSONameServiceMock }], { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environment }],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}) })
.compileComponents(); .compileComponents();

View File

@@ -18,6 +18,8 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-
import { getMockLinkService } from '../../../../../shared/mocks/link-service.mock'; import { getMockLinkService } from '../../../../../shared/mocks/link-service.mock';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
import { environment } from '../../../../../../environments/environment';
describe('WorkflowItemAdminWorkflowListElementComponent', () => { describe('WorkflowItemAdminWorkflowListElementComponent', () => {
let component: WorkflowItemSearchResultAdminWorkflowListElementComponent; let component: WorkflowItemSearchResultAdminWorkflowListElementComponent;
@@ -51,7 +53,8 @@ describe('WorkflowItemAdminWorkflowListElementComponent', () => {
providers: [ providers: [
{ provide: TruncatableService, useValue: mockTruncatableService }, { provide: TruncatableService, useValue: mockTruncatableService },
{ provide: LinkService, useValue: linkService }, { provide: LinkService, useValue: linkService },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environment }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}) })

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, Inject, OnInit } from '@angular/core';
import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { Context } from '../../../../../core/shared/context.model'; import { Context } from '../../../../../core/shared/context.model';
@@ -13,6 +13,7 @@ import { SearchResultListElementComponent } from '../../../../../shared/object-l
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model'; import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.interface';
@listableObjectComponent(WorkflowItemSearchResult, ViewMode.ListElement, Context.AdminWorkflowSearch) @listableObjectComponent(WorkflowItemSearchResult, ViewMode.ListElement, Context.AdminWorkflowSearch)
@Component({ @Component({
@@ -32,9 +33,10 @@ export class WorkflowItemSearchResultAdminWorkflowListElementComponent extends S
constructor(private linkService: LinkService, constructor(private linkService: LinkService,
protected truncatableService: TruncatableService, protected truncatableService: TruncatableService,
protected dsoNameService: DSONameService protected dsoNameService: DSONameService,
@Inject(APP_CONFIG) protected appConfig: AppConfig
) { ) {
super(truncatableService, dsoNameService); super(truncatableService, dsoNameService, appConfig);
} }
/** /**

View File

@@ -18,13 +18,10 @@ import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-
import { toRemoteData } from '../browse-by-metadata-page/browse-by-metadata-page.component.spec'; import { toRemoteData } from '../browse-by-metadata-page/browse-by-metadata-page.component.spec';
import { VarDirective } from '../../shared/utils/var.directive'; import { VarDirective } from '../../shared/utils/var.directive';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { PaginationService } from '../../core/pagination/pagination.service'; import { PaginationService } from '../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
import { FindListOptions } from '../../core/data/find-list-options.model'; import { APP_CONFIG } from '../../../config/app-config.interface';
import { APP_CONFIG } from 'src/config/app-config.interface'; import { environment } from '../../../environments/environment';
import { environment } from 'src/environments/environment';
describe('BrowseByDatePageComponent', () => { describe('BrowseByDatePageComponent', () => {
let comp: BrowseByDatePageComponent; let comp: BrowseByDatePageComponent;

View File

@@ -1,9 +1,8 @@
import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { ChangeDetectorRef, Component, Inject } from '@angular/core';
import { import {
BrowseByMetadataPageComponent, BrowseByMetadataPageComponent,
browseParamsToOptions browseParamsToOptions, getBrowseSearchOptions
} from '../browse-by-metadata-page/browse-by-metadata-page.component'; } from '../browse-by-metadata-page/browse-by-metadata-page.component';
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
import { combineLatest as observableCombineLatest } from 'rxjs'; import { combineLatest as observableCombineLatest } from 'rxjs';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { Item } from '../../core/shared/item.model'; import { Item } from '../../core/shared/item.model';
@@ -13,7 +12,6 @@ import { BrowseService } from '../../core/browse/browse.service';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
import { environment } from '../../../environments/environment';
import { PaginationService } from '../../core/pagination/pagination.service'; import { PaginationService } from '../../core/pagination/pagination.service';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
@@ -45,14 +43,15 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
protected router: Router, protected router: Router,
protected paginationService: PaginationService, protected paginationService: PaginationService,
protected cdRef: ChangeDetectorRef, protected cdRef: ChangeDetectorRef,
@Inject(APP_CONFIG) protected appConfig: AppConfig) { @Inject(APP_CONFIG) public appConfig: AppConfig) {
super(route, browseService, dsoService, paginationService, router, appConfig); super(route, browseService, dsoService, paginationService, router, appConfig);
} }
ngOnInit(): void { ngOnInit(): void {
const sortConfig = new SortOptions('default', SortDirection.ASC); const sortConfig = new SortOptions('default', SortDirection.ASC);
this.startsWithType = StartsWithType.date; this.startsWithType = StartsWithType.date;
this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig)); // include the thumbnail configuration in browse search options
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig, this.fetchThumbnails));
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig); this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
this.subs.push( this.subs.push(
@@ -65,7 +64,7 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
const metadataKeys = params.browseDefinition ? params.browseDefinition.metadataKeys : this.defaultMetadataKeys; const metadataKeys = params.browseDefinition ? params.browseDefinition.metadataKeys : this.defaultMetadataKeys;
this.browseId = params.id || this.defaultBrowseId; this.browseId = params.id || this.defaultBrowseId;
this.startsWith = +params.startsWith || params.startsWith; this.startsWith = +params.startsWith || params.startsWith;
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId); const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails);
this.updatePageWithItems(searchOptions, this.value, undefined); this.updatePageWithItems(searchOptions, this.value, undefined);
this.updateParent(params.scope); this.updateParent(params.scope);
this.updateStartsWithOptions(this.browseId, metadataKeys, params.scope); this.updateStartsWithOptions(this.browseId, metadataKeys, params.scope);
@@ -85,7 +84,7 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) { updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) {
this.subs.push( this.subs.push(
this.browseService.getFirstItemFor(definition, scope).subscribe((firstItemRD: RemoteData<Item>) => { this.browseService.getFirstItemFor(definition, scope).subscribe((firstItemRD: RemoteData<Item>) => {
let lowerLimit = environment.browseBy.defaultLowerLimit; let lowerLimit = this.appConfig.browseBy.defaultLowerLimit;
if (hasValue(firstItemRD.payload)) { if (hasValue(firstItemRD.payload)) {
const date = firstItemRD.payload.firstMetadataValue(metadataKeys); const date = firstItemRD.payload.firstMetadataValue(metadataKeys);
if (isNotEmpty(date) && isValidDate(date)) { if (isNotEmpty(date) && isValidDate(date)) {
@@ -96,8 +95,8 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
} }
const options = []; const options = [];
const currentYear = new Date().getUTCFullYear(); const currentYear = new Date().getUTCFullYear();
const oneYearBreak = Math.floor((currentYear - environment.browseBy.oneYearLimit) / 5) * 5; const oneYearBreak = Math.floor((currentYear - this.appConfig.browseBy.oneYearLimit) / 5) * 5;
const fiveYearBreak = Math.floor((currentYear - environment.browseBy.fiveYearLimit) / 10) * 10; const fiveYearBreak = Math.floor((currentYear - this.appConfig.browseBy.fiveYearLimit) / 10) * 10;
if (lowerLimit <= fiveYearBreak) { if (lowerLimit <= fiveYearBreak) {
lowerLimit -= 10; lowerLimit -= 10;
} else if (lowerLimit <= oneYearBreak) { } else if (lowerLimit <= oneYearBreak) {

View File

@@ -1,4 +1,8 @@
import { BrowseByMetadataPageComponent, browseParamsToOptions } from './browse-by-metadata-page.component'; import {
BrowseByMetadataPageComponent,
browseParamsToOptions,
getBrowseSearchOptions
} from './browse-by-metadata-page.component';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { BrowseService } from '../../core/browse/browse.service'; import { BrowseService } from '../../core/browse/browse.service';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
@@ -14,7 +18,7 @@ import { RemoteData } from '../../core/data/remote-data';
import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model'; import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model';
import { PageInfo } from '../../core/shared/page-info.model'; import { PageInfo } from '../../core/shared/page-info.model';
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model'; import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { SortDirection } from '../../core/cache/models/sort-options.model';
import { Item } from '../../core/shared/item.model'; import { Item } from '../../core/shared/item.model';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { Community } from '../../core/shared/community.model'; import { Community } from '../../core/shared/community.model';
@@ -26,7 +30,6 @@ import { PaginationService } from '../../core/pagination/pagination.service';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
import { APP_CONFIG } from '../../../config/app-config.interface'; import { APP_CONFIG } from '../../../config/app-config.interface';
import { environment } from '../../../environments/environment';
describe('BrowseByMetadataPageComponent', () => { describe('BrowseByMetadataPageComponent', () => {
let comp: BrowseByMetadataPageComponent; let comp: BrowseByMetadataPageComponent;
@@ -45,6 +48,13 @@ describe('BrowseByMetadataPageComponent', () => {
] ]
}); });
const environmentMock = {
browseBy: {
showThumbnails: true,
pageSize: 10
}
};
const mockEntries = [ const mockEntries = [
{ {
type: BrowseEntry.type, type: BrowseEntry.type,
@@ -100,7 +110,7 @@ describe('BrowseByMetadataPageComponent', () => {
{ provide: DSpaceObjectDataService, useValue: mockDsoService }, { provide: DSpaceObjectDataService, useValue: mockDsoService },
{ provide: PaginationService, useValue: paginationService }, { provide: PaginationService, useValue: paginationService },
{ provide: Router, useValue: new RouterMock() }, { provide: Router, useValue: new RouterMock() },
{ provide: APP_CONFIG, useValue: environment } { provide: APP_CONFIG, useValue: environmentMock }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).compileComponents(); }).compileComponents();
@@ -121,6 +131,10 @@ describe('BrowseByMetadataPageComponent', () => {
expect(comp.items$).toBeUndefined(); expect(comp.items$).toBeUndefined();
}); });
it('should set embed thumbnail property to true', () => {
expect(comp.fetchThumbnails).toBeTrue();
});
describe('when a value is provided', () => { describe('when a value is provided', () => {
beforeEach(() => { beforeEach(() => {
const paramsWithValue = { const paramsWithValue = {
@@ -148,14 +162,14 @@ describe('BrowseByMetadataPageComponent', () => {
}; };
const paginationOptions = Object.assign(new PaginationComponentOptions(), { const paginationOptions = Object.assign(new PaginationComponentOptions(), {
currentPage: 5, currentPage: 5,
pageSize: 10, pageSize: comp.appConfig.browseBy.pageSize,
}); });
const sortOptions = { const sortOptions = {
direction: SortDirection.ASC, direction: SortDirection.ASC,
field: 'fake-field', field: 'fake-field',
}; };
result = browseParamsToOptions(paramsScope, paginationOptions, sortOptions, 'author'); result = browseParamsToOptions(paramsScope, paginationOptions, sortOptions, 'author', comp.fetchThumbnails);
}); });
it('should return BrowseEntrySearchOptions with the correct properties', () => { it('should return BrowseEntrySearchOptions with the correct properties', () => {
@@ -166,6 +180,36 @@ describe('BrowseByMetadataPageComponent', () => {
expect(result.sort.direction).toEqual(SortDirection.ASC); expect(result.sort.direction).toEqual(SortDirection.ASC);
expect(result.sort.field).toEqual('fake-field'); expect(result.sort.field).toEqual('fake-field');
expect(result.scope).toEqual('fake-scope'); expect(result.scope).toEqual('fake-scope');
expect(result.fetchThumbnail).toBeTrue();
});
});
describe('calling getBrowseSearchOptions', () => {
let result: BrowseEntrySearchOptions;
beforeEach(() => {
const paramsScope = {
scope: 'fake-scope'
};
const paginationOptions = Object.assign(new PaginationComponentOptions(), {
currentPage: 5,
pageSize: comp.appConfig.browseBy.pageSize,
});
const sortOptions = {
direction: SortDirection.ASC,
field: 'fake-field',
};
result = getBrowseSearchOptions('title', paginationOptions, sortOptions, comp.fetchThumbnails);
});
it('should return BrowseEntrySearchOptions with the correct properties', () => {
expect(result.metadataDefinition).toEqual('title');
expect(result.pagination.currentPage).toEqual(5);
expect(result.pagination.pageSize).toEqual(10);
expect(result.sort.direction).toEqual(SortDirection.ASC);
expect(result.sort.field).toEqual('fake-field');
expect(result.fetchThumbnail).toBeTrue();
}); });
}); });
}); });

View File

@@ -1,6 +1,5 @@
import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs'; import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
import { Component, Inject, OnInit } from '@angular/core'; import { Component, Inject, OnInit } from '@angular/core';
import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { PaginatedList } from '../../core/data/paginated-list.model'; import { PaginatedList } from '../../core/data/paginated-list.model';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
@@ -18,6 +17,7 @@ import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
import { PaginationService } from '../../core/pagination/pagination.service'; import { PaginationService } from '../../core/pagination/pagination.service';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
export const BBM_PAGINATION_ID = 'bbm'; export const BBM_PAGINATION_ID = 'bbm';
@@ -109,12 +109,19 @@ export class BrowseByMetadataPageComponent implements OnInit {
*/ */
startsWith: string; startsWith: string;
/**
* Determines whether to request embedded thumbnail.
*/
fetchThumbnails: boolean;
public constructor(protected route: ActivatedRoute, public constructor(protected route: ActivatedRoute,
protected browseService: BrowseService, protected browseService: BrowseService,
protected dsoService: DSpaceObjectDataService, protected dsoService: DSpaceObjectDataService,
protected paginationService: PaginationService, protected paginationService: PaginationService,
protected router: Router, protected router: Router,
@Inject(APP_CONFIG) protected appConfig: AppConfig) { @Inject(APP_CONFIG) public appConfig: AppConfig) {
this.fetchThumbnails = this.appConfig.browseBy.showThumbnails;
this.paginationConfig = Object.assign(new PaginationComponentOptions(), { this.paginationConfig = Object.assign(new PaginationComponentOptions(), {
id: BBM_PAGINATION_ID, id: BBM_PAGINATION_ID,
currentPage: 1, currentPage: 1,
@@ -122,9 +129,11 @@ export class BrowseByMetadataPageComponent implements OnInit {
}); });
} }
ngOnInit(): void { ngOnInit(): void {
const sortConfig = new SortOptions('default', SortDirection.ASC); const sortConfig = new SortOptions('default', SortDirection.ASC);
this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig)); this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig));
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig); this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
this.subs.push( this.subs.push(
@@ -137,15 +146,16 @@ export class BrowseByMetadataPageComponent implements OnInit {
this.authority = params.authority; this.authority = params.authority;
this.value = +params.value || params.value || ''; this.value = +params.value || params.value || '';
this.startsWith = +params.startsWith || params.startsWith; this.startsWith = +params.startsWith || params.startsWith;
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId);
if (isNotEmpty(this.value)) { if (isNotEmpty(this.value)) {
this.updatePageWithItems(searchOptions, this.value, this.authority); this.updatePageWithItems(
browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails), this.value, this.authority);
} else { } else {
this.updatePage(searchOptions); this.updatePage(browseParamsToOptions(params, currentPage, currentSort, this.browseId, false));
} }
this.updateParent(params.scope); this.updateParent(params.scope);
})); }));
this.updateStartsWithTextOptions(); this.updateStartsWithTextOptions();
} }
/** /**
@@ -232,22 +242,44 @@ export class BrowseByMetadataPageComponent implements OnInit {
} }
/**
* Creates browse entry search options.
* @param defaultBrowseId the metadata definition to fetch entries or items for
* @param paginationConfig the required pagination configuration
* @param sortConfig the required sort configuration
* @param fetchThumbnails optional boolean for fetching thumbnails
* @returns BrowseEntrySearchOptions instance
*/
export function getBrowseSearchOptions(defaultBrowseId: string,
paginationConfig: PaginationComponentOptions,
sortConfig: SortOptions,
fetchThumbnails?: boolean) {
if (!hasValue(fetchThumbnails)) {
fetchThumbnails = false;
}
return new BrowseEntrySearchOptions(defaultBrowseId, paginationConfig, sortConfig, null,
null, fetchThumbnails);
}
/** /**
* Function to transform query and url parameters into searchOptions used to fetch browse entries or items * Function to transform query and url parameters into searchOptions used to fetch browse entries or items
* @param params URL and query parameters * @param params URL and query parameters
* @param paginationConfig Pagination configuration * @param paginationConfig Pagination configuration
* @param sortConfig Sorting configuration * @param sortConfig Sorting configuration
* @param metadata Optional metadata definition to fetch browse entries/items for * @param metadata Optional metadata definition to fetch browse entries/items for
* @param fetchThumbnail Optional parameter for requesting thumbnail images
*/ */
export function browseParamsToOptions(params: any, export function browseParamsToOptions(params: any,
paginationConfig: PaginationComponentOptions, paginationConfig: PaginationComponentOptions,
sortConfig: SortOptions, sortConfig: SortOptions,
metadata?: string): BrowseEntrySearchOptions { metadata?: string,
fetchThumbnail?: boolean): BrowseEntrySearchOptions {
return new BrowseEntrySearchOptions( return new BrowseEntrySearchOptions(
metadata, metadata,
paginationConfig, paginationConfig,
sortConfig, sortConfig,
+params.startsWith || params.startsWith, +params.startsWith || params.startsWith,
params.scope params.scope,
fetchThumbnail
); );
} }

View File

@@ -18,13 +18,11 @@ import { BrowseService } from '../../core/browse/browse.service';
import { RouterMock } from '../../shared/mocks/router.mock'; import { RouterMock } from '../../shared/mocks/router.mock';
import { VarDirective } from '../../shared/utils/var.directive'; import { VarDirective } from '../../shared/utils/var.directive';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { PaginationService } from '../../core/pagination/pagination.service'; import { PaginationService } from '../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
import { FindListOptions } from '../../core/data/find-list-options.model'; import { APP_CONFIG } from '../../../config/app-config.interface';
import { APP_CONFIG } from 'src/config/app-config.interface'; import { environment } from '../../../environments/environment';
import { environment } from 'src/environments/environment';
describe('BrowseByTitlePageComponent', () => { describe('BrowseByTitlePageComponent', () => {
let comp: BrowseByTitlePageComponent; let comp: BrowseByTitlePageComponent;

View File

@@ -4,9 +4,8 @@ import { ActivatedRoute, Params, Router } from '@angular/router';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
import { import {
BrowseByMetadataPageComponent, BrowseByMetadataPageComponent,
browseParamsToOptions browseParamsToOptions, getBrowseSearchOptions
} from '../browse-by-metadata-page/browse-by-metadata-page.component'; } from '../browse-by-metadata-page/browse-by-metadata-page.component';
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { BrowseService } from '../../core/browse/browse.service'; import { BrowseService } from '../../core/browse/browse.service';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
@@ -32,13 +31,14 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
protected dsoService: DSpaceObjectDataService, protected dsoService: DSpaceObjectDataService,
protected paginationService: PaginationService, protected paginationService: PaginationService,
protected router: Router, protected router: Router,
@Inject(APP_CONFIG) protected appConfig: AppConfig) { @Inject(APP_CONFIG) public appConfig: AppConfig) {
super(route, browseService, dsoService, paginationService, router, appConfig); super(route, browseService, dsoService, paginationService, router, appConfig);
} }
ngOnInit(): void { ngOnInit(): void {
const sortConfig = new SortOptions('dc.title', SortDirection.ASC); const sortConfig = new SortOptions('dc.title', SortDirection.ASC);
this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig)); // include the thumbnail configuration in browse search options
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig, this.fetchThumbnails));
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig); this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
this.subs.push( this.subs.push(
@@ -49,7 +49,7 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { ).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
this.startsWith = +params.startsWith || params.startsWith; this.startsWith = +params.startsWith || params.startsWith;
this.browseId = params.id || this.defaultBrowseId; this.browseId = params.id || this.defaultBrowseId;
this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId), undefined, undefined); this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails), undefined, undefined);
this.updateParent(params.scope); this.updateParent(params.scope);
})); }));
this.updateStartsWithTextOptions(); this.updateStartsWithTextOptions();

View File

@@ -28,6 +28,7 @@ import { AuthorizationDataService } from '../core/data/feature-authorization/aut
import { FeatureID } from '../core/data/feature-authorization/feature-id'; import { FeatureID } from '../core/data/feature-authorization/feature-id';
import { getCollectionPageRoute } from './collection-page-routing-paths'; import { getCollectionPageRoute } from './collection-page-routing-paths';
import { redirectOn4xx } from '../core/shared/authorized.operators'; import { redirectOn4xx } from '../core/shared/authorized.operators';
import { BROWSE_LINKS_TO_FOLLOW } from '../core/browse/browse.service';
@Component({ @Component({
selector: 'ds-collection-page', selector: 'ds-collection-page',
@@ -74,6 +75,7 @@ export class CollectionPageComponent implements OnInit {
this.paginationConfig.pageSize = 5; this.paginationConfig.pageSize = 5;
this.paginationConfig.currentPage = 1; this.paginationConfig.currentPage = 1;
this.sortConfig = new SortOptions('dc.date.accessioned', SortDirection.DESC); this.sortConfig = new SortOptions('dc.date.accessioned', SortDirection.DESC);
} }
ngOnInit(): void { ngOnInit(): void {
@@ -102,13 +104,14 @@ export class CollectionPageComponent implements OnInit {
getFirstSucceededRemoteData(), getFirstSucceededRemoteData(),
map((rd) => rd.payload.id), map((rd) => rd.payload.id),
switchMap((id: string) => { switchMap((id: string) => {
return this.searchService.search( return this.searchService.search<Item>(
new PaginatedSearchOptions({ new PaginatedSearchOptions({
scope: id, scope: id,
pagination: currentPagination, pagination: currentPagination,
sort: currentSort, sort: currentSort,
dsoTypes: [DSpaceObjectType.ITEM] dsoTypes: [DSpaceObjectType.ITEM]
})).pipe(toDSpaceObjectListRD()) as Observable<RemoteData<PaginatedList<Item>>>; }), null, true, true, ...BROWSE_LINKS_TO_FOLLOW)
.pipe(toDSpaceObjectListRD()) as Observable<RemoteData<PaginatedList<Item>>>;
}), }),
startWith(undefined) // Make sure switching pages shows loading component startWith(undefined) // Make sure switching pages shows loading component
) )

View File

@@ -17,7 +17,7 @@ export const COLLECTION_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Collection>[] = [
followLink('parentCommunity', {}, followLink('parentCommunity', {},
followLink('parentCommunity') followLink('parentCommunity')
), ),
followLink('logo') followLink('logo'),
]; ];
/** /**

View File

@@ -6,13 +6,16 @@ import { SortOptions } from '../cache/models/sort-options.model';
* - metadataDefinition: The metadata definition to fetch entries or items for * - metadataDefinition: The metadata definition to fetch entries or items for
* - pagination: Optional pagination options to use * - pagination: Optional pagination options to use
* - sort: Optional sorting options to use * - sort: Optional sorting options to use
* - startsWith An optional value to use to filter the browse results
* - scope: An optional scope to limit the results within a specific collection or community * - scope: An optional scope to limit the results within a specific collection or community
* - fetchThumbnail An optional boolean to request thumbnail for items
*/ */
export class BrowseEntrySearchOptions { export class BrowseEntrySearchOptions {
constructor(public metadataDefinition: string, constructor(public metadataDefinition: string,
public pagination?: PaginationComponentOptions, public pagination?: PaginationComponentOptions,
public sort?: SortOptions, public sort?: SortOptions,
public startsWith?: string, public startsWith?: string,
public scope?: string) { public scope?: string,
public fetchThumbnail?: boolean) {
} }
} }

View File

@@ -21,6 +21,12 @@ import { URLCombiner } from '../url-combiner/url-combiner';
import { BrowseEntrySearchOptions } from './browse-entry-search-options.model'; import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
import { BrowseDefinitionDataService } from './browse-definition-data.service'; import { BrowseDefinitionDataService } from './browse-definition-data.service';
import { HrefOnlyDataService } from '../data/href-only-data.service'; import { HrefOnlyDataService } from '../data/href-only-data.service';
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
export const BROWSE_LINKS_TO_FOLLOW: FollowLinkConfig<BrowseEntry | Item>[] = [
followLink('thumbnail')
];
/** /**
* The service handling all browse requests * The service handling all browse requests
@@ -96,6 +102,9 @@ export class BrowseService {
return href; return href;
}) })
); );
if (options.fetchThumbnail ) {
return this.hrefOnlyDataService.findListByHref<BrowseEntry>(href$, {}, null, null, ...BROWSE_LINKS_TO_FOLLOW);
}
return this.hrefOnlyDataService.findListByHref<BrowseEntry>(href$); return this.hrefOnlyDataService.findListByHref<BrowseEntry>(href$);
} }
@@ -141,6 +150,9 @@ export class BrowseService {
return href; return href;
}), }),
); );
if (options.fetchThumbnail) {
return this.hrefOnlyDataService.findListByHref<Item>(href$, {}, null, null, ...BROWSE_LINKS_TO_FOLLOW);
}
return this.hrefOnlyDataService.findListByHref<Item>(href$); return this.hrefOnlyDataService.findListByHref<Item>(href$);
} }

View File

@@ -11,4 +11,5 @@ export class FindListOptions {
sort?: SortOptions; sort?: SortOptions;
searchParams?: RequestParam[]; searchParams?: RequestParam[];
startsWith?: string; startsWith?: string;
fetchThumbnail?: boolean;
} }

View File

@@ -202,6 +202,7 @@ describe('RelationshipDataService', () => {
}); });
it('should call getItemRelationshipsByLabel with the correct params', (done) => { it('should call getItemRelationshipsByLabel with the correct params', (done) => {
mockOptions = Object.assign(mockOptions, { fetchThumbnail: true });
service.getRelatedItemsByLabel( service.getRelatedItemsByLabel(
mockItem, mockItem,
mockLabel, mockLabel,
@@ -213,8 +214,8 @@ describe('RelationshipDataService', () => {
mockOptions, mockOptions,
true, true,
true, true,
followLink('leftItem'), followLink('leftItem',{}, followLink('thumbnail')),
followLink('rightItem'), followLink('rightItem',{}, followLink('thumbnail')),
followLink('relationshipType') followLink('relationshipType')
); );
done(); done();

View File

@@ -45,6 +45,7 @@ import { SearchData, SearchDataImpl } from './base/search-data';
import { PutData, PutDataImpl } from './base/put-data'; import { PutData, PutDataImpl } from './base/put-data';
import { IdentifiableDataService } from './base/identifiable-data.service'; import { IdentifiableDataService } from './base/identifiable-data.service';
import { dataService } from './base/data-service.decorator'; import { dataService } from './base/data-service.decorator';
import { itemLinksToFollow } from '../../shared/utils/relation-query.utils';
const relationshipListsStateSelector = (state: AppState) => state.relationshipLists; const relationshipListsStateSelector = (state: AppState) => state.relationshipLists;
@@ -185,7 +186,7 @@ export class RelationshipDataService extends IdentifiableDataService<Relationshi
]).pipe( ]).pipe(
filter(([existsInOC, existsInRC]) => !existsInOC && !existsInRC), filter(([existsInOC, existsInRC]) => !existsInOC && !existsInRC),
take(1), take(1),
).subscribe(() => this.itemService.findByHref(item._links.self.href, false)); ).subscribe(() => this.itemService.findByHref(item._links.self.href));
} }
/** /**
@@ -258,7 +259,10 @@ export class RelationshipDataService extends IdentifiableDataService<Relationshi
* @param options * @param options
*/ */
getRelatedItemsByLabel(item: Item, label: string, options?: FindListOptions): Observable<RemoteData<PaginatedList<Item>>> { getRelatedItemsByLabel(item: Item, label: string, options?: FindListOptions): Observable<RemoteData<PaginatedList<Item>>> {
return this.getItemRelationshipsByLabel(item, label, options, true, true, followLink('leftItem'), followLink('rightItem'), followLink('relationshipType')).pipe(this.paginatedRelationsToItems(item.uuid)); let linksToFollow: FollowLinkConfig<Relationship>[] = itemLinksToFollow(options.fetchThumbnail);
linksToFollow.push(followLink('relationshipType'));
return this.getItemRelationshipsByLabel(item, label, options, true, true, ...linksToFollow).pipe(this.paginatedRelationsToItems(item.uuid));
} }
/** /**
@@ -516,14 +520,14 @@ export class RelationshipDataService extends IdentifiableDataService<Relationshi
{ {
fieldName: 'relatedItem', fieldName: 'relatedItem',
fieldValue: itemId, fieldValue: itemId,
}, }
); );
}); });
return this.searchBy( return this.searchBy(
'byItemsAndType', 'byItemsAndType',
{ {
searchParams: searchParams, searchParams: searchParams
}, },
) as Observable<RemoteData<PaginatedList<Relationship>>>; ) as Observable<RemoteData<PaginatedList<Relationship>>>;

View File

@@ -40,6 +40,12 @@ export class BrowseEntry extends ListableObject implements TypedObject {
@autoserializeAs('valueLang') @autoserializeAs('valueLang')
language: string; language: string;
/**
* Thumbnail link used when browsing items with showThumbs config enabled.
*/
@autoserializeAs('thumbnail')
thumbnail: string;
/** /**
* The count of this browse entry * The count of this browse entry
*/ */
@@ -51,6 +57,7 @@ export class BrowseEntry extends ListableObject implements TypedObject {
_links: { _links: {
self: HALLink; self: HALLink;
entries: HALLink; entries: HALLink;
thumbnail: HALLink;
}; };
/** /**

View File

@@ -1,6 +1,17 @@
<div class="row">
<div *ngIf="showThumbnails" class="col-3 col-md-2">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="true">
</ds-thumbnail>
</a>
</div>
<div [ngClass]="showThumbnails ? 'col-9' : 'col-md-12'">
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge> <ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" <a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out" [routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
[innerHTML]="dsoTitle"></a> [innerHTML]="dsoTitle"></a>
<span *ngIf="linkType == linkTypes.None" <span *ngIf="linkType == linkTypes.None"
@@ -23,3 +34,5 @@
</ds-truncatable-part> </ds-truncatable-part>
</span> </span>
</ds-truncatable> </ds-truncatable>
</div>
</div>

View File

@@ -9,6 +9,7 @@ import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
let journalIssueListElementComponent: JournalIssueSearchResultListElementComponent; let journalIssueListElementComponent: JournalIssueSearchResultListElementComponent;
let fixture: ComponentFixture<JournalIssueSearchResultListElementComponent>; let fixture: ComponentFixture<JournalIssueSearchResultListElementComponent>;
@@ -57,13 +58,26 @@ const mockItemWithoutMetadata: ItemSearchResult = Object.assign(
}) })
}); });
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
const enviromentNoThumbs = {
browseBy: {
showThumbnails: false
}
};
describe('JournalIssueSearchResultListElementComponent', () => { describe('JournalIssueSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [JournalIssueSearchResultListElementComponent, TruncatePipe], declarations: [JournalIssueSearchResultListElementComponent, TruncatePipe],
providers: [ providers: [
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
@@ -78,6 +92,22 @@ describe('JournalIssueSearchResultListElementComponent', () => {
})); }));
describe('with environment.browseBy.showThumbnails set to true', () => {
beforeEach(() => {
journalIssueListElementComponent.object = mockItemWithMetadata;
fixture.detectChanges();
});
it('should set showThumbnails to true', () => {
expect(journalIssueListElementComponent.showThumbnails).toBeTrue();
});
it('should add thumbnail element', () => {
const thumbnailElement = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnailElement).toBeTruthy();
});
});
describe('When the item has a journal identifier', () => { describe('When the item has a journal identifier', () => {
beforeEach(() => { beforeEach(() => {
journalIssueListElementComponent.object = mockItemWithMetadata; journalIssueListElementComponent.object = mockItemWithMetadata;
@@ -126,3 +156,39 @@ describe('JournalIssueSearchResultListElementComponent', () => {
}); });
}); });
}); });
describe('JournalIssueSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [JournalIssueSearchResultListElementComponent, TruncatePipe],
providers: [
{provide: TruncatableService, useValue: {}},
{provide: DSONameService, useClass: DSONameServiceMock},
{ provide: APP_CONFIG, useValue: enviromentNoThumbs }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(JournalIssueSearchResultListElementComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(JournalIssueSearchResultListElementComponent);
journalIssueListElementComponent = fixture.componentInstance;
}));
describe('with environment.browseBy.showThumbnails set to false', () => {
beforeEach(() => {
journalIssueListElementComponent.object = mockItemWithMetadata;
fixture.detectChanges();
});
it('should not add thumbnail element', () => {
const thumbnailElement = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnailElement).toBeFalsy();
});
});
});

View File

@@ -13,4 +13,15 @@ import { ItemSearchResultListElementComponent } from '../../../../../shared/obje
* The component for displaying a list element for an item search result of the type Journal Issue * The component for displaying a list element for an item search result of the type Journal Issue
*/ */
export class JournalIssueSearchResultListElementComponent extends ItemSearchResultListElementComponent { export class JournalIssueSearchResultListElementComponent extends ItemSearchResultListElementComponent {
/**
* Display thumbnails if required by configuration
*/
showThumbnails: boolean;
ngOnInit(): void {
super.ngOnInit();
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
}
} }

View File

@@ -1,6 +1,17 @@
<div class="row">
<div *ngIf="showThumbnails" class="col-3 col-md-2">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="true">
</ds-thumbnail>
</a>
</div>
<div [ngClass]="showThumbnails ? 'col-9' : 'col-md-12'">
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge> <ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" <a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out" [routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
[innerHTML]="dsoTitle"></a> [innerHTML]="dsoTitle"></a>
<span *ngIf="linkType == linkTypes.None" <span *ngIf="linkType == linkTypes.None"
@@ -23,3 +34,5 @@
</ds-truncatable-part> </ds-truncatable-part>
</span> </span>
</ds-truncatable> </ds-truncatable>
</div>
</div>

View File

@@ -9,6 +9,7 @@ import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
let journalVolumeListElementComponent: JournalVolumeSearchResultListElementComponent; let journalVolumeListElementComponent: JournalVolumeSearchResultListElementComponent;
let fixture: ComponentFixture<JournalVolumeSearchResultListElementComponent>; let fixture: ComponentFixture<JournalVolumeSearchResultListElementComponent>;
@@ -56,6 +57,18 @@ const mockItemWithoutMetadata: ItemSearchResult = Object.assign(
}) })
}); });
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
const enviromentNoThumbs = {
browseBy: {
showThumbnails: false
}
};
describe('JournalVolumeSearchResultListElementComponent', () => { describe('JournalVolumeSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -63,6 +76,7 @@ describe('JournalVolumeSearchResultListElementComponent', () => {
providers: [ providers: [
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },
{ provide: DSONameService, useClass: DSONameServiceMock }, { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
@@ -77,6 +91,21 @@ describe('JournalVolumeSearchResultListElementComponent', () => {
})); }));
describe('with environment.browseBy.showThumbnails set to true', () => {
beforeEach(() => {
journalVolumeListElementComponent.object = mockItemWithMetadata;
fixture.detectChanges();
});
it('should set showThumbnails to true', () => {
expect(journalVolumeListElementComponent.showThumbnails).toBeTrue();
});
it('should add thumbnail element', () => {
const thumbnailElement = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnailElement).toBeTruthy();
});
});
describe('When the item has a journal title', () => { describe('When the item has a journal title', () => {
beforeEach(() => { beforeEach(() => {
journalVolumeListElementComponent.object = mockItemWithMetadata; journalVolumeListElementComponent.object = mockItemWithMetadata;
@@ -125,3 +154,38 @@ describe('JournalVolumeSearchResultListElementComponent', () => {
}); });
}); });
}); });
describe('JournalVolumeSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [JournalVolumeSearchResultListElementComponent, TruncatePipe],
providers: [
{provide: TruncatableService, useValue: {}},
{provide: DSONameService, useClass: DSONameServiceMock},
{ provide: APP_CONFIG, useValue: enviromentNoThumbs }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(JournalVolumeSearchResultListElementComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(JournalVolumeSearchResultListElementComponent);
journalVolumeListElementComponent = fixture.componentInstance;
}));
describe('with environment.browseBy.showThumbnails set to false', () => {
beforeEach(() => {
journalVolumeListElementComponent.object = mockItemWithMetadata;
fixture.detectChanges();
});
it('should not add thumbnail element', () => {
const thumbnailElement = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnailElement).toBeFalsy();
});
});
});

View File

@@ -13,4 +13,15 @@ import { ItemSearchResultListElementComponent } from '../../../../../shared/obje
* The component for displaying a list element for an item search result of the type Journal Volume * The component for displaying a list element for an item search result of the type Journal Volume
*/ */
export class JournalVolumeSearchResultListElementComponent extends ItemSearchResultListElementComponent { export class JournalVolumeSearchResultListElementComponent extends ItemSearchResultListElementComponent {
/**
* Display thumbnails if required by configuration
*/
showThumbnails: boolean;
ngOnInit(): void {
super.ngOnInit();
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
}
} }

View File

@@ -1,3 +1,12 @@
<div class="row">
<div *ngIf="showThumbnails" class="col-3 col-md-2">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="true">
</ds-thumbnail>
</a>
</div>
<div [ngClass]="showThumbnails ? 'col-9' : 'col-md-12'">
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge> <ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" <a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
@@ -17,3 +26,5 @@
</ds-truncatable-part> </ds-truncatable-part>
</span> </span>
</ds-truncatable> </ds-truncatable>
</div>
</div>

View File

@@ -9,6 +9,7 @@ import { TruncatableService } from '../../../../../shared/truncatable/truncatabl
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
let journalListElementComponent: JournalSearchResultListElementComponent; let journalListElementComponent: JournalSearchResultListElementComponent;
let fixture: ComponentFixture<JournalSearchResultListElementComponent>; let fixture: ComponentFixture<JournalSearchResultListElementComponent>;
@@ -52,6 +53,18 @@ const mockItemWithoutMetadata: ItemSearchResult = Object.assign(
} }
); );
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
const enviromentNoThumbs = {
browseBy: {
showThumbnails: false
}
};
describe('JournalSearchResultListElementComponent', () => { describe('JournalSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -59,6 +72,7 @@ describe('JournalSearchResultListElementComponent', () => {
providers: [ providers: [
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },
{ provide: DSONameService, useClass: DSONameServiceMock }, { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
@@ -73,6 +87,21 @@ describe('JournalSearchResultListElementComponent', () => {
})); }));
describe('with environment.browseBy.showThumbnails set to true', () => {
beforeEach(() => {
journalListElementComponent.object = mockItemWithMetadata;
fixture.detectChanges();
});
it('should set showThumbnails to true', () => {
expect(journalListElementComponent.showThumbnails).toBeTrue();
});
it('should add thumbnail element', () => {
const thumbnailElement = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnailElement).toBeTruthy();
});
});
describe('When the item has an issn', () => { describe('When the item has an issn', () => {
beforeEach(() => { beforeEach(() => {
journalListElementComponent.object = mockItemWithMetadata; journalListElementComponent.object = mockItemWithMetadata;
@@ -97,3 +126,39 @@ describe('JournalSearchResultListElementComponent', () => {
}); });
}); });
}); });
describe('JournalSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [JournalSearchResultListElementComponent, TruncatePipe],
providers: [
{provide: TruncatableService, useValue: {}},
{provide: DSONameService, useClass: DSONameServiceMock},
{ provide: APP_CONFIG, useValue: enviromentNoThumbs }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(JournalSearchResultListElementComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(JournalSearchResultListElementComponent);
journalListElementComponent = fixture.componentInstance;
}));
describe('with environment.browseBy.showThumbnails set to false', () => {
beforeEach(() => {
journalListElementComponent.object = mockItemWithMetadata;
fixture.detectChanges();
});
it('should not add thumbnail element', () => {
const thumbnailElement = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnailElement).toBeFalsy();
});
});
});

View File

@@ -13,4 +13,15 @@ import { ItemSearchResultListElementComponent } from '../../../../../shared/obje
* The component for displaying a list element for an item search result of the type Journal * The component for displaying a list element for an item search result of the type Journal
*/ */
export class JournalSearchResultListElementComponent extends ItemSearchResultListElementComponent { export class JournalSearchResultListElementComponent extends ItemSearchResultListElementComponent {
/**
* Display thumbnails if required by configuration
*/
showThumbnails: boolean;
ngOnInit(): void {
super.ngOnInit();
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
}
} }

View File

@@ -1,6 +1,20 @@
<div class="row">
<div *ngIf="showThumbnails" class="col-3 col-md-2">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="dont-break-out">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async"
[defaultImage]="'assets/images/orgunit-placeholder.svg'"
[alt]="'thumbnail.orgunit.alt'"
[placeholder]="'thumbnail.orgunit.placeholder'">
</ds-thumbnail>
</a>
</div>
<div [ngClass]="showThumbnails ? 'col-9' : 'col-md-12'">
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge> <ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" <a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="lead" [routerLink]="[itemPageRoute]" class="lead"
[innerHTML]="firstMetadataValue('organization.legalName')"></a> [innerHTML]="firstMetadataValue('organization.legalName')"></a>
<span *ngIf="linkType == linkTypes.None" <span *ngIf="linkType == linkTypes.None"
@@ -15,3 +29,5 @@
</span> </span>
</span> </span>
</ds-truncatable> </ds-truncatable>
</div>
</div>

View File

@@ -9,6 +9,7 @@ import { TruncatableService } from '../../../../../shared/truncatable/truncatabl
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
let orgUnitListElementComponent: OrgUnitSearchResultListElementComponent; let orgUnitListElementComponent: OrgUnitSearchResultListElementComponent;
let fixture: ComponentFixture<OrgUnitSearchResultListElementComponent>; let fixture: ComponentFixture<OrgUnitSearchResultListElementComponent>;
@@ -50,13 +51,26 @@ const mockItemWithoutMetadata: ItemSearchResult = Object.assign(
}) })
}); });
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
const enviromentNoThumbs = {
browseBy: {
showThumbnails: false
}
};
describe('OrgUnitSearchResultListElementComponent', () => { describe('OrgUnitSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ OrgUnitSearchResultListElementComponent , TruncatePipe], declarations: [ OrgUnitSearchResultListElementComponent , TruncatePipe],
providers: [ providers: [
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], ],
schemas: [ NO_ERRORS_SCHEMA ] schemas: [ NO_ERRORS_SCHEMA ]
@@ -71,6 +85,21 @@ describe('OrgUnitSearchResultListElementComponent', () => {
})); }));
describe('with environment.browseBy.showThumbnails set to true', () => {
beforeEach(() => {
orgUnitListElementComponent.object = mockItemWithMetadata;
fixture.detectChanges();
});
it('should set showThumbnails to true', () => {
expect(orgUnitListElementComponent.showThumbnails).toBeTrue();
});
it('should add thumbnail element', () => {
const thumbnailElement = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnailElement).toBeTruthy();
});
});
describe('When the item has an org unit description', () => { describe('When the item has an org unit description', () => {
beforeEach(() => { beforeEach(() => {
orgUnitListElementComponent.object = mockItemWithMetadata; orgUnitListElementComponent.object = mockItemWithMetadata;
@@ -95,3 +124,39 @@ describe('OrgUnitSearchResultListElementComponent', () => {
}); });
}); });
}); });
describe('OrgUnitSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [OrgUnitSearchResultListElementComponent, TruncatePipe],
providers: [
{provide: TruncatableService, useValue: {}},
{provide: DSONameService, useClass: DSONameServiceMock},
{ provide: APP_CONFIG, useValue: enviromentNoThumbs }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(OrgUnitSearchResultListElementComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(OrgUnitSearchResultListElementComponent);
orgUnitListElementComponent = fixture.componentInstance;
}));
describe('with environment.browseBy.showThumbnails set to false', () => {
beforeEach(() => {
orgUnitListElementComponent.object = mockItemWithMetadata;
fixture.detectChanges();
});
it('should not add thumbnail element', () => {
const thumbnailElement = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnailElement).toBeNull();
});
});
});

View File

@@ -13,4 +13,15 @@ import { ItemSearchResultListElementComponent } from '../../../../../shared/obje
* The component for displaying a list element for an item search result of the type Organisation Unit * The component for displaying a list element for an item search result of the type Organisation Unit
*/ */
export class OrgUnitSearchResultListElementComponent extends ItemSearchResultListElementComponent { export class OrgUnitSearchResultListElementComponent extends ItemSearchResultListElementComponent {
/**
* Display thumbnail if required by configuration
*/
showThumbnails: boolean;
ngOnInit(): void {
super.ngOnInit();
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
}
} }

View File

@@ -1,6 +1,20 @@
<div class="row">
<div *ngIf="showThumbnails" class="col-3 col-md-2">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="dont-break-out">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async"
[defaultImage]="'assets/images/person-placeholder.svg'"
[alt]="'thumbnail.person.alt'"
[placeholder]="'thumbnail.person.placeholder'">
</ds-thumbnail>
</a>
</div>
<div [ngClass]="showThumbnails ? 'col-9 col-md-10' : 'col-12'">
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge> <ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" <a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="lead" [routerLink]="[itemPageRoute]" class="lead"
[innerHTML]="name"></a> [innerHTML]="name"></a>
<span *ngIf="linkType == linkTypes.None" <span *ngIf="linkType == linkTypes.None"
@@ -17,3 +31,6 @@
</ds-truncatable-part> </ds-truncatable-part>
</span> </span>
</ds-truncatable> </ds-truncatable>
</div>
</div>

View File

@@ -9,6 +9,7 @@ import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
let personListElementComponent: PersonSearchResultListElementComponent; let personListElementComponent: PersonSearchResultListElementComponent;
let fixture: ComponentFixture<PersonSearchResultListElementComponent>; let fixture: ComponentFixture<PersonSearchResultListElementComponent>;
@@ -50,13 +51,26 @@ const mockItemWithoutMetadata: ItemSearchResult = Object.assign(
}) })
}); });
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
const enviromentNoThumbs = {
browseBy: {
showThumbnails: false
}
};
describe('PersonSearchResultListElementComponent', () => { describe('PersonSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [PersonSearchResultListElementComponent, TruncatePipe], declarations: [PersonSearchResultListElementComponent, TruncatePipe],
providers: [ providers: [
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
@@ -71,6 +85,21 @@ describe('PersonSearchResultListElementComponent', () => {
})); }));
describe('with environment.browseBy.showThumbnails set to true', () => {
beforeEach(() => {
personListElementComponent.object = mockItemWithMetadata;
fixture.detectChanges();
});
it('should set showThumbnails to true', () => {
expect(personListElementComponent.showThumbnails).toBeTrue();
});
it('should add thumbnail element', () => {
const thumbnailElement = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnailElement).toBeTruthy();
});
});
describe('When the item has a job title', () => { describe('When the item has a job title', () => {
beforeEach(() => { beforeEach(() => {
personListElementComponent.object = mockItemWithMetadata; personListElementComponent.object = mockItemWithMetadata;
@@ -95,3 +124,39 @@ describe('PersonSearchResultListElementComponent', () => {
}); });
}); });
}); });
describe('PersonSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [PersonSearchResultListElementComponent, TruncatePipe],
providers: [
{provide: TruncatableService, useValue: {}},
{provide: DSONameService, useClass: DSONameServiceMock},
{ provide: APP_CONFIG, useValue: enviromentNoThumbs }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(PersonSearchResultListElementComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(PersonSearchResultListElementComponent);
personListElementComponent = fixture.componentInstance;
}));
describe('with environment.browseBy.showThumbnails set to false', () => {
beforeEach(() => {
personListElementComponent.object = mockItemWithMetadata;
fixture.detectChanges();
});
it('should not add thumbnail element', () => {
const thumbnailElement = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnailElement).toBeFalsy();
});
});
});

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { import {
listableObjectComponent listableObjectComponent
} from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
@@ -8,6 +8,7 @@ import {
} from '../../../../../shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component'; } from '../../../../../shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component';
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.interface';
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement) @listableObjectComponent('PersonSearchResult', ViewMode.ListElement)
@Component({ @Component({
@@ -20,8 +21,20 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service
*/ */
export class PersonSearchResultListElementComponent extends ItemSearchResultListElementComponent { export class PersonSearchResultListElementComponent extends ItemSearchResultListElementComponent {
public constructor(protected truncatableService: TruncatableService, protected dsoNameService: DSONameService) { public constructor(protected truncatableService: TruncatableService,
super(truncatableService, dsoNameService); protected dsoNameService: DSONameService,
@Inject(APP_CONFIG) protected appConfig: AppConfig ) {
super(truncatableService, dsoNameService, appConfig);
}
/**
* Display thumbnail if required by configuration
*/
showThumbnails: boolean;
ngOnInit(): void {
super.ngOnInit();
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
} }
/** /**

View File

@@ -1,6 +1,20 @@
<div class="row">
<div *ngIf="showThumbnails" class="col-3 col-md-2">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="dont-break-out">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async"
[defaultImage]="'assets/images/project-placeholder.svg'"
[alt]="'thumbnail.project.alt'"
[placeholder]="'thumbnail.project.placeholder'">
</ds-thumbnail>
</a>
</div>
<div [ngClass]="showThumbnails ? 'col-9' : 'col-md-12'">
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge> <ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" <a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out" [routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
[innerHTML]="dsoTitle"></a> [innerHTML]="dsoTitle"></a>
<span *ngIf="linkType == linkTypes.None" <span *ngIf="linkType == linkTypes.None"
@@ -17,3 +31,5 @@
<!--</ds-truncatable-part>--> <!--</ds-truncatable-part>-->
<!--</span>--> <!--</span>-->
</ds-truncatable> </ds-truncatable>
</div>
</div>

View File

@@ -8,6 +8,8 @@ import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
import { By } from '@angular/platform-browser';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
let projectListElementComponent: ProjectSearchResultListElementComponent; let projectListElementComponent: ProjectSearchResultListElementComponent;
let fixture: ComponentFixture<ProjectSearchResultListElementComponent>; let fixture: ComponentFixture<ProjectSearchResultListElementComponent>;
@@ -50,13 +52,26 @@ const mockItemWithoutMetadata: ItemSearchResult = Object.assign(
}) })
}); });
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
const enviromentNoThumbs = {
browseBy: {
showThumbnails: false
}
};
describe('ProjectSearchResultListElementComponent', () => { describe('ProjectSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ProjectSearchResultListElementComponent, TruncatePipe], declarations: [ProjectSearchResultListElementComponent, TruncatePipe],
providers: [ providers: [
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
@@ -71,6 +86,21 @@ describe('ProjectSearchResultListElementComponent', () => {
})); }));
describe('with environment.browseBy.showThumbnails set to true', () => {
beforeEach(() => {
projectListElementComponent.object = mockItemWithMetadata;
fixture.detectChanges();
});
it('should set showThumbnails to true', () => {
expect(projectListElementComponent.showThumbnails).toBeTrue();
});
it('should add thumbnail element', () => {
const thumbnailElement = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnailElement).toBeTruthy();
});
});
// describe('When the item has a status', () => { // describe('When the item has a status', () => {
// beforeEach(() => { // beforeEach(() => {
// projectListElementComponent.item = mockItemWithMetadata; // projectListElementComponent.item = mockItemWithMetadata;
@@ -95,3 +125,40 @@ describe('ProjectSearchResultListElementComponent', () => {
// }); // });
// }); // });
}); });
describe('ProjectSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ProjectSearchResultListElementComponent, TruncatePipe],
providers: [
{provide: TruncatableService, useValue: {}},
{provide: DSONameService, useClass: DSONameServiceMock},
{ provide: APP_CONFIG, useValue: enviromentNoThumbs }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ProjectSearchResultListElementComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(ProjectSearchResultListElementComponent);
projectListElementComponent = fixture.componentInstance;
}));
describe('with environment.browseBy.showThumbnails set to false', () => {
beforeEach(() => {
projectListElementComponent.object = mockItemWithMetadata;
fixture.detectChanges();
});
it('should not add thumbnail element', () => {
const thumbnailElement = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnailElement).toBeFalsy();
});
});
});

View File

@@ -13,4 +13,15 @@ import { ItemSearchResultListElementComponent } from '../../../../../shared/obje
* The component for displaying a list element for an item search result of the type Project * The component for displaying a list element for an item search result of the type Project
*/ */
export class ProjectSearchResultListElementComponent extends ItemSearchResultListElementComponent { export class ProjectSearchResultListElementComponent extends ItemSearchResultListElementComponent {
/**
* Display thumbnail if required by configuration
*/
showThumbnails: boolean;
ngOnInit(): void {
super.ngOnInit();
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
}
} }

View File

@@ -36,7 +36,7 @@
[label]="'orgunit.page.id'"> [label]="'orgunit.page.id'">
</ds-generic-item-page-field> </ds-generic-item-page-field>
</div> </div>
<div class="col-xs-12 col-md-6"> <div class="col-xs-12 col-md-7">
<ds-related-items <ds-related-items
[parentItem]="object" [parentItem]="object"
[relationType]="'isPublicationOfOrgUnit'" [relationType]="'isPublicationOfOrgUnit'"

View File

@@ -29,7 +29,7 @@
[label]="'person.page.birthdate'"> [label]="'person.page.birthdate'">
</ds-generic-item-page-field> </ds-generic-item-page-field>
</div> </div>
<div class="col-xs-12 col-md-6"> <div class="col-xs-12 col-md-7">
<ds-related-items <ds-related-items
[parentItem]="object" [parentItem]="object"
[relationType]="'isProjectOfPerson'" [relationType]="'isProjectOfPerson'"

View File

@@ -42,7 +42,7 @@
<!--[label]="'project.page.expectedcompletion'">--> <!--[label]="'project.page.expectedcompletion'">-->
<!--</ds-generic-item-page-field>--> <!--</ds-generic-item-page-field>-->
</div> </div>
<div class="col-xs-12 col-md-6"> <div class="col-xs-12 col-md-7">
<ds-related-items <ds-related-items
[parentItem]="object" [parentItem]="object"
[relationType]="'isPersonOfProject'" [relationType]="'isPersonOfProject'"

View File

@@ -29,6 +29,8 @@ import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
import { OrgUnitSearchResultListSubmissionElementComponent } from './org-unit-search-result-list-submission-element.component'; import { OrgUnitSearchResultListSubmissionElementComponent } from './org-unit-search-result-list-submission-element.component';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
import { environment } from '../../../../../../environments/environment';
let personListElementComponent: OrgUnitSearchResultListSubmissionElementComponent; let personListElementComponent: OrgUnitSearchResultListSubmissionElementComponent;
let fixture: ComponentFixture<OrgUnitSearchResultListSubmissionElementComponent>; let fixture: ComponentFixture<OrgUnitSearchResultListSubmissionElementComponent>;
@@ -117,7 +119,8 @@ describe('OrgUnitSearchResultListSubmissionElementComponent', () => {
{ provide: DSOChangeAnalyzer, useValue: {} }, { provide: DSOChangeAnalyzer, useValue: {} },
{ provide: DefaultChangeAnalyzer, useValue: {} }, { provide: DefaultChangeAnalyzer, useValue: {} },
{ provide: BitstreamDataService, useValue: mockBitstreamDataService }, { provide: BitstreamDataService, useValue: mockBitstreamDataService },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environment }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
@@ -179,4 +182,6 @@ describe('OrgUnitSearchResultListSubmissionElementComponent', () => {
expect(jobTitleField).toBeNull(); expect(jobTitleField).toBeNull();
}); });
}); });
}); });

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, Inject, OnInit } from '@angular/core';
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component'; import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
@@ -17,6 +17,7 @@ import { ItemDataService } from '../../../../../core/data/item-data.service';
import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service'; import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service';
import { NameVariantModalComponent } from '../../name-variant-modal/name-variant-modal.component'; import { NameVariantModalComponent } from '../../name-variant-modal/name-variant-modal.component';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.interface';
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.EntitySearchModal) @listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.EntitySearchModal)
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.EntitySearchModalWithNameVariants) @listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.EntitySearchModalWithNameVariants)
@@ -35,6 +36,11 @@ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchRes
alternativeField = 'dc.title.alternative'; alternativeField = 'dc.title.alternative';
useNameVariants = false; useNameVariants = false;
/**
* Display thumbnail if required by configuration
*/
showThumbnails: boolean;
constructor(protected truncatableService: TruncatableService, constructor(protected truncatableService: TruncatableService,
private relationshipService: RelationshipDataService, private relationshipService: RelationshipDataService,
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
@@ -43,9 +49,10 @@ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchRes
private itemDataService: ItemDataService, private itemDataService: ItemDataService,
private bitstreamDataService: BitstreamDataService, private bitstreamDataService: BitstreamDataService,
private selectableListService: SelectableListService, private selectableListService: SelectableListService,
protected dsoNameService: DSONameService protected dsoNameService: DSONameService,
@Inject(APP_CONFIG) protected appConfig: AppConfig
) { ) {
super(truncatableService, dsoNameService); super(truncatableService, dsoNameService, appConfig);
} }
ngOnInit() { ngOnInit() {
@@ -65,6 +72,7 @@ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchRes
} }
); );
} }
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
} }
select(value) { select(value) {

View File

@@ -1,6 +1,20 @@
<div class="row">
<div *ngIf="showThumbnails" class="col-3 col-md-2">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer" class="dont-break-out">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async"
[defaultImage]="'assets/images/person-placeholder.svg'"
[alt]="'thumbnail.person.alt'"
[placeholder]="'thumbnail.person.placeholder'">
</ds-thumbnail>
</a>
</div>
<div [ngClass]="showThumbnails ? 'col-9' : 'col-md-12'">
<div class="d-flex"> <div class="d-flex">
<div class="flex-grow-1"> <div class="flex-grow-1">
<ds-person-input-suggestions [suggestions]="allSuggestions" [(ngModel)]="selectedName" (clickSuggestion)="select($event)" (submitSuggestion)="selectCustom($event)"></ds-person-input-suggestions> <ds-person-input-suggestions [suggestions]="allSuggestions" [(ngModel)]="selectedName"
(clickSuggestion)="select($event)"
(submitSuggestion)="selectCustom($event)"></ds-person-input-suggestions>
<span class="text-muted"> <span class="text-muted">
<span *ngIf="dso.allMetadata(['person.jobTitle']).length > 0" <span *ngIf="dso.allMetadata(['person.jobTitle']).length > 0"
class="item-list-job-title"> class="item-list-job-title">
@@ -11,3 +25,5 @@
</span> </span>
</div> </div>
</div> </div>
</div>
</div>

View File

@@ -27,6 +27,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe'; import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
import { PersonSearchResultListSubmissionElementComponent } from './person-search-result-list-submission-element.component'; import { PersonSearchResultListSubmissionElementComponent } from './person-search-result-list-submission-element.component';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
let personListElementComponent: PersonSearchResultListSubmissionElementComponent; let personListElementComponent: PersonSearchResultListSubmissionElementComponent;
let fixture: ComponentFixture<PersonSearchResultListSubmissionElementComponent>; let fixture: ComponentFixture<PersonSearchResultListSubmissionElementComponent>;
@@ -37,6 +38,18 @@ let mockItemWithoutMetadata: ItemSearchResult;
let nameVariant; let nameVariant;
let mockRelationshipService; let mockRelationshipService;
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
const enviromentNoThumbs = {
browseBy: {
showThumbnails: false
}
};
function init() { function init() {
mockItemWithMetadata = Object.assign( mockItemWithMetadata = Object.assign(
new ItemSearchResult(), new ItemSearchResult(),
@@ -109,6 +122,7 @@ describe('PersonSearchResultListElementSubmissionComponent', () => {
{ provide: DSOChangeAnalyzer, useValue: {} }, { provide: DSOChangeAnalyzer, useValue: {} },
{ provide: DefaultChangeAnalyzer, useValue: {} }, { provide: DefaultChangeAnalyzer, useValue: {} },
{ provide: BitstreamDataService, useValue: mockBitstreamDataService }, { provide: BitstreamDataService, useValue: mockBitstreamDataService },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
@@ -146,4 +160,72 @@ describe('PersonSearchResultListElementSubmissionComponent', () => {
expect(jobTitleField).toBeNull(); expect(jobTitleField).toBeNull();
}); });
}); });
describe('When the environment is set to show thumbnails', () => {
beforeEach(() => {
personListElementComponent.object = mockItemWithoutMetadata;
fixture.detectChanges();
});
it('should add the ds-thumbnail element', () => {
const thumbnail = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnail).toBeTruthy();
});
});
});
describe('PersonSearchResultListElementSubmissionComponent', () => {
const mockBitstreamDataService = {
getThumbnailFor(item: Item): Observable<RemoteData<Bitstream>> {
return createSuccessfulRemoteDataObject$(new Bitstream());
}
};
beforeEach(waitForAsync(() => {
init();
TestBed.configureTestingModule({
declarations: [PersonSearchResultListSubmissionElementComponent, TruncatePipe],
providers: [
{ provide: TruncatableService, useValue: {} },
{ provide: RelationshipDataService, useValue: mockRelationshipService },
{ provide: NotificationsService, useValue: {} },
{ provide: TranslateService, useValue: {} },
{ provide: NgbModal, useValue: {} },
{ provide: ItemDataService, useValue: {} },
{ provide: SelectableListService, useValue: {} },
{ provide: Store, useValue: {}},
{ provide: ObjectCacheService, useValue: {} },
{ provide: UUIDService, useValue: {} },
{ provide: RemoteDataBuildService, useValue: {} },
{ provide: CommunityDataService, useValue: {} },
{ provide: HALEndpointService, useValue: {} },
{ provide: HttpClient, useValue: {} },
{ provide: DSOChangeAnalyzer, useValue: {} },
{ provide: DefaultChangeAnalyzer, useValue: {} },
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
{ provide: APP_CONFIG, useValue: enviromentNoThumbs }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(PersonSearchResultListSubmissionElementComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(PersonSearchResultListSubmissionElementComponent);
personListElementComponent = fixture.componentInstance;
}));
describe('When the environment is not set to show thumbnails', () => {
beforeEach(() => {
personListElementComponent.object = mockItemWithoutMetadata;
fixture.detectChanges();
});
it('should not add the ds-thumbnail element', () => {
const thumbnail = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnail).toBeNull();
});
});
}); });

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, Inject, OnInit } from '@angular/core';
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component'; import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
@@ -17,6 +17,7 @@ import { MetadataValue } from '../../../../../core/shared/metadata.models';
import { ItemDataService } from '../../../../../core/data/item-data.service'; import { ItemDataService } from '../../../../../core/data/item-data.service';
import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service'; import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.interface';
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.EntitySearchModalWithNameVariants) @listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.EntitySearchModalWithNameVariants)
@Component({ @Component({
@@ -33,6 +34,11 @@ export class PersonSearchResultListSubmissionElementComponent extends SearchResu
selectedName: string; selectedName: string;
alternativeField = 'dc.title.alternative'; alternativeField = 'dc.title.alternative';
/**
* Display thumbnail if required by configuration
*/
showThumbnails: boolean;
constructor(protected truncatableService: TruncatableService, constructor(protected truncatableService: TruncatableService,
private relationshipService: RelationshipDataService, private relationshipService: RelationshipDataService,
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
@@ -41,9 +47,10 @@ export class PersonSearchResultListSubmissionElementComponent extends SearchResu
private itemDataService: ItemDataService, private itemDataService: ItemDataService,
private bitstreamDataService: BitstreamDataService, private bitstreamDataService: BitstreamDataService,
private selectableListService: SelectableListService, private selectableListService: SelectableListService,
protected dsoNameService: DSONameService protected dsoNameService: DSONameService,
@Inject(APP_CONFIG) protected appConfig: AppConfig
) { ) {
super(truncatableService, dsoNameService); super(truncatableService, dsoNameService, appConfig);
} }
ngOnInit() { ngOnInit() {
@@ -58,6 +65,7 @@ export class PersonSearchResultListSubmissionElementComponent extends SearchResu
this.selectedName = nameVariant || defaultValue; this.selectedName = nameVariant || defaultValue;
} }
); );
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
} }
select(value) { select(value) {

View File

@@ -31,6 +31,7 @@ import { SearchConfigurationServiceStub } from '../../../../shared/testing/searc
import { ConfigurationProperty } from '../../../../core/shared/configuration-property.model'; import { ConfigurationProperty } from '../../../../core/shared/configuration-property.model';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { RouterMock } from '../../../../shared/mocks/router.mock'; import { RouterMock } from '../../../../shared/mocks/router.mock';
import { APP_CONFIG } from '../../../../../config/app-config.interface';
let comp: EditRelationshipListComponent; let comp: EditRelationshipListComponent;
let fixture: ComponentFixture<EditRelationshipListComponent>; let fixture: ComponentFixture<EditRelationshipListComponent>;
@@ -201,6 +202,12 @@ describe('EditRelationshipListComponent', () => {
})) }))
}); });
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [SharedModule, TranslateModule.forRoot()], imports: [SharedModule, TranslateModule.forRoot()],
declarations: [EditRelationshipListComponent], declarations: [EditRelationshipListComponent],
@@ -217,6 +224,7 @@ describe('EditRelationshipListComponent', () => {
{ provide: LinkHeadService, useValue: linkHeadService }, { provide: LinkHeadService, useValue: linkHeadService },
{ provide: ConfigurationDataService, useValue: configurationDataService }, { provide: ConfigurationDataService, useValue: configurationDataService },
{ provide: SearchConfigurationService, useValue: new SearchConfigurationServiceStub() }, { provide: SearchConfigurationService, useValue: new SearchConfigurationServiceStub() },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], schemas: [ ], schemas: [
NO_ERRORS_SCHEMA NO_ERRORS_SCHEMA
] ]
@@ -259,9 +267,11 @@ describe('EditRelationshipListComponent', () => {
const callArgs = relationshipService.getItemRelationshipsByLabel.calls.mostRecent().args; const callArgs = relationshipService.getItemRelationshipsByLabel.calls.mostRecent().args;
const findListOptions = callArgs[2]; const findListOptions = callArgs[2];
const linksToFollow = callArgs[5];
expect(findListOptions.elementsPerPage).toEqual(paginationOptions.pageSize); expect(findListOptions.elementsPerPage).toEqual(paginationOptions.pageSize);
expect(findListOptions.currentPage).toEqual(paginationOptions.currentPage); expect(findListOptions.currentPage).toEqual(paginationOptions.currentPage);
expect(linksToFollow.linksToFollow[0].name).toEqual('thumbnail');
}); });
describe('when the publication is on the left side of the relationship', () => { describe('when the publication is on the left side of the relationship', () => {

View File

@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Inject, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { LinkService } from '../../../../core/cache/builders/link.service'; import { LinkService } from '../../../../core/cache/builders/link.service';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
@@ -29,7 +29,7 @@ import { DsDynamicLookupRelationModalComponent } from '../../../../shared/form/b
import { RelationshipOptions } from '../../../../shared/form/builder/models/relationship-options.model'; import { RelationshipOptions } from '../../../../shared/form/builder/models/relationship-options.model';
import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service'; import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service';
import { SearchResult } from '../../../../shared/search/models/search-result.model'; import { SearchResult } from '../../../../shared/search/models/search-result.model';
import { followLink } from '../../../../shared/utils/follow-link-config.model'; import { FollowLinkConfig } from '../../../../shared/utils/follow-link-config.model';
import { PaginatedList } from '../../../../core/data/paginated-list.model'; import { PaginatedList } from '../../../../core/data/paginated-list.model';
import { RemoteData } from '../../../../core/data/remote-data'; import { RemoteData } from '../../../../core/data/remote-data';
import { Collection } from '../../../../core/shared/collection.model'; import { Collection } from '../../../../core/shared/collection.model';
@@ -39,6 +39,8 @@ import { RelationshipTypeDataService } from '../../../../core/data/relationship-
import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model'; import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model';
import { FieldUpdates } from '../../../../core/data/object-updates/field-updates.model'; import { FieldUpdates } from '../../../../core/data/object-updates/field-updates.model';
import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model'; import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model';
import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface';
import { itemLinksToFollow } from '../../../../shared/utils/relation-query.utils';
@Component({ @Component({
selector: 'ds-edit-relationship-list', selector: 'ds-edit-relationship-list',
@@ -138,6 +140,10 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
*/ */
modalRef: NgbModalRef; modalRef: NgbModalRef;
/**
* Determines whether to ask for the embedded item thumbnail.
*/
fetchThumbnail: boolean;
constructor( constructor(
protected objectUpdatesService: ObjectUpdatesService, protected objectUpdatesService: ObjectUpdatesService,
@@ -147,7 +153,9 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
protected modalService: NgbModal, protected modalService: NgbModal,
protected paginationService: PaginationService, protected paginationService: PaginationService,
protected selectableListService: SelectableListService, protected selectableListService: SelectableListService,
@Inject(APP_CONFIG) protected appConfig: AppConfig
) { ) {
this.fetchThumbnail = this.appConfig.browseBy.showThumbnails;
} }
/** /**
@@ -484,6 +492,9 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
tap(() => this.loading$.next(true)) tap(() => this.loading$.next(true))
); );
// this adds thumbnail images when required by configuration
let linksToFollow: FollowLinkConfig<Relationship>[] = itemLinksToFollow(this.fetchThumbnail);
this.subs.push( this.subs.push(
observableCombineLatest([ observableCombineLatest([
currentPagination$, currentPagination$,
@@ -496,12 +507,11 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
currentItemIsLeftItem ? this.relationshipType.leftwardType : this.relationshipType.rightwardType, currentItemIsLeftItem ? this.relationshipType.leftwardType : this.relationshipType.rightwardType,
{ {
elementsPerPage: currentPagination.pageSize, elementsPerPage: currentPagination.pageSize,
currentPage: currentPagination.currentPage, currentPage: currentPagination.currentPage
}, },
false, false,
true, true,
followLink('leftItem'), ...linksToFollow
followLink('rightItem'),
)), )),
).subscribe((rd: RemoteData<PaginatedList<Relationship>>) => { ).subscribe((rd: RemoteData<PaginatedList<Relationship>>) => {
this.relationshipsRd$.next(rd); this.relationshipsRd$.next(rd);

View File

@@ -10,6 +10,7 @@ import { hasValue, isNotEmpty } from '../../../shared/empty.util';
* An abstract component for displaying an incremental list of objects * An abstract component for displaying an incremental list of objects
*/ */
export class AbstractIncrementalListComponent<T> implements OnInit, OnDestroy { export class AbstractIncrementalListComponent<T> implements OnInit, OnDestroy {
/** /**
* The amount to increment the list by * The amount to increment the list by
* Define this amount in the child component overriding this component * Define this amount in the child component overriding this component
@@ -21,6 +22,11 @@ export class AbstractIncrementalListComponent<T> implements OnInit, OnDestroy {
*/ */
objects: T[]; objects: T[];
/**
* Placeholder css class (defined in global-styles)
*/
placeholderFontClass: string;
/** /**
* A list of open subscriptions * A list of open subscriptions
*/ */

View File

@@ -53,7 +53,7 @@
[label]="'publication.page.publisher'"> [label]="'publication.page.publisher'">
</ds-generic-item-page-field> </ds-generic-item-page-field>
</div> </div>
<div class="col-xs-12 col-md-6"> <div class="col-xs-12 col-md-7">
<ds-related-items <ds-related-items
[parentItem]="object" [parentItem]="object"
[relationType]="'isProjectOfPublication'" [relationType]="'isProjectOfPublication'"

View File

@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core'; import { Component, ElementRef, Inject, Input, PLATFORM_ID } from '@angular/core';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
@@ -7,6 +7,9 @@ import { ViewMode } from '../../../core/shared/view-mode.model';
import { RelationshipDataService } from '../../../core/data/relationship-data.service'; import { RelationshipDataService } from '../../../core/data/relationship-data.service';
import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component'; import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component';
import { FindListOptions } from '../../../core/data/find-list-options.model'; import { FindListOptions } from '../../../core/data/find-list-options.model';
import { setPlaceHolderAttributes } from '../../../shared/utils/object-list-utils';
import { APP_CONFIG, AppConfig } from '../../../../config/app-config.interface';
import { isPlatformBrowser } from '@angular/common';
@Component({ @Component({
selector: 'ds-related-items', selector: 'ds-related-items',
@@ -18,6 +21,7 @@ import { FindListOptions } from '../../../core/data/find-list-options.model';
* It expects a parent item and relationship type, as well as a label to display on top * It expects a parent item and relationship type, as well as a label to display on top
*/ */
export class RelatedItemsComponent extends AbstractIncrementalListComponent<Observable<RemoteData<PaginatedList<Item>>>> { export class RelatedItemsComponent extends AbstractIncrementalListComponent<Observable<RemoteData<PaginatedList<Item>>>> {
/** /**
* The parent of the list of related items to display * The parent of the list of related items to display
*/ */
@@ -53,8 +57,28 @@ export class RelatedItemsComponent extends AbstractIncrementalListComponent<Obse
*/ */
viewMode = ViewMode.ListElement; viewMode = ViewMode.ListElement;
constructor(public relationshipService: RelationshipDataService) { /**
* Determines whether to request embedded thumbnail.
*/
fetchThumbnail: boolean;
constructor(public relationshipService: RelationshipDataService,
protected elementRef: ElementRef,
@Inject(APP_CONFIG) protected appConfig: AppConfig,
@Inject(PLATFORM_ID) private platformId: Object
) {
super(); super();
this.fetchThumbnail = this.appConfig.browseBy.showThumbnails;
}
ngOnInit(): void {
if (isPlatformBrowser(this.platformId)) {
const width = this.elementRef.nativeElement.offsetWidth;
this.placeholderFontClass = setPlaceHolderAttributes(width);
} else {
this.placeholderFontClass = 'hide-placeholder-text';
}
super.ngOnInit();
} }
/** /**
@@ -62,6 +86,7 @@ export class RelatedItemsComponent extends AbstractIncrementalListComponent<Obse
* @param page The page to fetch * @param page The page to fetch
*/ */
getPage(page: number): Observable<RemoteData<PaginatedList<Item>>> { getPage(page: number): Observable<RemoteData<PaginatedList<Item>>> {
return this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, Object.assign(this.options, { elementsPerPage: this.incrementBy, currentPage: page })); return this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, Object.assign(this.options,
{ elementsPerPage: this.incrementBy, currentPage: page, fetchThumbnail: this.fetchThumbnail }));
} }
} }

View File

@@ -1,4 +1,4 @@
<ds-metadata-field-wrapper [label]="label"> <ds-metadata-field-wrapper [label]="label" [ngClass]="placeholderFontClass">
<ng-container *ngFor="let objectPage of objects; let i = index"> <ng-container *ngFor="let objectPage of objects; let i = index">
<ng-container *ngVar="(objectPage | async) as itemsRD"> <ng-container *ngVar="(objectPage | async) as itemsRD">
<ds-listable-object-component-loader *ngFor="let item of itemsRD?.payload?.page" <ds-listable-object-component-loader *ngFor="let item of itemsRD?.payload?.page"

View File

@@ -0,0 +1,8 @@
:host {
display: block;
}
ds-listable-object-component-loader {
margin-bottom: 10px;
display: block;
}

View File

@@ -10,6 +10,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { VarDirective } from '../../../shared/utils/var.directive'; import { VarDirective } from '../../../shared/utils/var.directive';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { createPaginatedList } from '../../../shared/testing/utils.test'; import { createPaginatedList } from '../../../shared/testing/utils.test';
import { APP_CONFIG } from '../../../../config/app-config.interface';
const parentItem: Item = Object.assign(new Item(), { const parentItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
@@ -30,6 +31,18 @@ const mockItems = [mockItem1, mockItem2];
const relationType = 'isItemOfItem'; const relationType = 'isItemOfItem';
let relationshipService: RelationshipDataService; let relationshipService: RelationshipDataService;
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
const enviromentNoThumbs = {
browseBy: {
showThumbnails: false
}
};
describe('RelatedItemsComponent', () => { describe('RelatedItemsComponent', () => {
let comp: RelatedItemsComponent; let comp: RelatedItemsComponent;
let fixture: ComponentFixture<RelatedItemsComponent>; let fixture: ComponentFixture<RelatedItemsComponent>;
@@ -45,7 +58,8 @@ describe('RelatedItemsComponent', () => {
imports: [TranslateModule.forRoot()], imports: [TranslateModule.forRoot()],
declarations: [RelatedItemsComponent, VarDirective], declarations: [RelatedItemsComponent, VarDirective],
providers: [ providers: [
{ provide: RelationshipDataService, useValue: relationshipService } { provide: RelationshipDataService, useValue: relationshipService },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(RelatedItemsComponent, { }).overrideComponent(RelatedItemsComponent, {
@@ -82,9 +96,11 @@ describe('RelatedItemsComponent', () => {
it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments (second page)', () => { it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments (second page)', () => {
expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, Object.assign(comp.options, { expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, Object.assign(comp.options, {
elementsPerPage: comp.incrementBy, elementsPerPage: comp.incrementBy,
currentPage: 2 currentPage: 2,
fetchThumbnail: true
})); }));
}); });
}); });
describe('when decrease is called', () => { describe('when decrease is called', () => {
@@ -100,3 +116,42 @@ describe('RelatedItemsComponent', () => {
}); });
}); });
describe('RelatedItemsComponent', () => {
let comp: RelatedItemsComponent;
let fixture: ComponentFixture<RelatedItemsComponent>;
beforeEach(waitForAsync(() => {
relationshipService = jasmine.createSpyObj('relationshipService',
{
getRelatedItemsByLabel: createSuccessfulRemoteDataObject$(createPaginatedList(mockItems)),
}
);
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [RelatedItemsComponent, VarDirective],
providers: [
{provide: RelationshipDataService, useValue: relationshipService},
{provide: APP_CONFIG, useValue: enviromentNoThumbs}
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(RelatedItemsComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(RelatedItemsComponent);
comp = fixture.componentInstance;
comp.parentItem = parentItem;
comp.relationType = relationType;
fixture.detectChanges();
}));
it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments (second page)', () => {
expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, Object.assign(comp.options, {
elementsPerPage: comp.incrementBy,
currentPage: 2,
fetchThumbnail: false
}));
});
});

View File

@@ -1,4 +1,4 @@
<ds-object-list [config]="config" <ds-object-list [ngClass]="placeholderFontClass" [config]="config"
[sortConfig]="sortConfig" [sortConfig]="sortConfig"
[objects]="objects" [objects]="objects"
[hasBorder]="hasBorder" [hasBorder]="hasBorder"

View File

@@ -0,0 +1,5 @@
:host {
display:block;
}

View File

@@ -48,5 +48,9 @@ describe('ObjectCollectionComponent', () => {
expect(fixture.debugElement.query(By.css('ds-object-list'))).toBeDefined(); expect(fixture.debugElement.query(By.css('ds-object-list'))).toBeDefined();
expect(fixture.debugElement.query(By.css('ds-object-grid'))).toBeNull(); expect(fixture.debugElement.query(By.css('ds-object-grid'))).toBeNull();
}); });
it('should set fallback placeholder font size during test', () => {
objectCollectionComponent.currentMode$ = observableOf(ViewMode.ListElement);
expect(fixture.debugElement.query(By.css('thumb-font-3'))).toBeDefined();
}); });
});

View File

@@ -1,4 +1,11 @@
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import {
ChangeDetectorRef,
Component, ElementRef,
EventEmitter, Inject,
Input,
OnInit,
Output, PLATFORM_ID,
} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -14,6 +21,8 @@ import { ViewMode } from '../../core/shared/view-mode.model';
import { CollectionElementLinkType } from './collection-element-link.type'; import { CollectionElementLinkType } from './collection-element-link.type';
import { PaginatedList } from '../../core/data/paginated-list.model'; import { PaginatedList } from '../../core/data/paginated-list.model';
import { Context } from '../../core/shared/context.model'; import { Context } from '../../core/shared/context.model';
import { setPlaceHolderAttributes } from '../utils/object-list-utils';
import { isPlatformBrowser } from '@angular/common';
/** /**
* Component that can render a list of listable objects in different view modes * Component that can render a list of listable objects in different view modes
@@ -147,14 +156,13 @@ export class ObjectCollectionComponent implements OnInit {
*/ */
viewModeEnum = ViewMode; viewModeEnum = ViewMode;
ngOnInit(): void { /**
this.currentMode$ = this.route * Placeholder class (defined in global-styles)
.queryParams */
.pipe( placeholderFontClass: string;
map((params) => isEmpty(params?.view) ? ViewMode.ListElement : params.view),
distinctUntilChanged()
);
}
/** /**
* @param cdRef * @param cdRef
@@ -163,11 +171,30 @@ export class ObjectCollectionComponent implements OnInit {
* Route is a singleton service provided by Angular. * Route is a singleton service provided by Angular.
* @param router * @param router
* Router is a singleton service provided by Angular. * Router is a singleton service provided by Angular.
* @param elementRef
* Used only to read DOM for the element width
*/ */
constructor( constructor(
private cdRef: ChangeDetectorRef, private cdRef: ChangeDetectorRef,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router) { private router: Router,
private elementRef: ElementRef,
@Inject(PLATFORM_ID) private platformId: Object) {
}
ngOnInit(): void {
this.currentMode$ = this.route
.queryParams
.pipe(
map((params) => isEmpty(params?.view) ? ViewMode.ListElement : params.view),
distinctUntilChanged()
);
if (isPlatformBrowser(this.platformId)) {
const width = this.elementRef.nativeElement.offsetWidth;
this.placeholderFontClass = setPlaceHolderAttributes(width);
} else {
this.placeholderFontClass = 'hide-placeholder-text';
}
} }
/** /**

View File

@@ -17,6 +17,8 @@ import { ClaimedApprovedTaskSearchResult } from '../../../../object-collection/s
import { ClaimedApprovedSearchResultListElementComponent } from './claimed-approved-search-result-list-element.component'; import { ClaimedApprovedSearchResultListElementComponent } from './claimed-approved-search-result-list-element.component';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../../mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../../mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
import { environment } from '../../../../../../environments/environment';
let component: ClaimedApprovedSearchResultListElementComponent; let component: ClaimedApprovedSearchResultListElementComponent;
let fixture: ComponentFixture<ClaimedApprovedSearchResultListElementComponent>; let fixture: ComponentFixture<ClaimedApprovedSearchResultListElementComponent>;
@@ -67,7 +69,8 @@ describe('ClaimedApprovedSearchResultListElementComponent', () => {
providers: [ providers: [
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },
{ provide: LinkService, useValue: linkService }, { provide: LinkService, useValue: linkService },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environment }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ClaimedApprovedSearchResultListElementComponent, { }).overrideComponent(ClaimedApprovedSearchResultListElementComponent, {

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { ClaimedApprovedTaskSearchResult } from '../../../../object-collection/shared/claimed-approved-task-search-result.model'; import { ClaimedApprovedTaskSearchResult } from '../../../../object-collection/shared/claimed-approved-task-search-result.model';
import { listableObjectComponent } from '../../../../object-collection/shared/listable-object/listable-object.decorator'; import { listableObjectComponent } from '../../../../object-collection/shared/listable-object/listable-object.decorator';
@@ -13,6 +13,7 @@ import { SearchResultListElementComponent } from '../../../search-result-list-el
import { ClaimedTaskSearchResult } from '../../../../object-collection/shared/claimed-task-search-result.model'; import { ClaimedTaskSearchResult } from '../../../../object-collection/shared/claimed-task-search-result.model';
import { ClaimedTask } from '../../../../../core/tasks/models/claimed-task-object.model'; import { ClaimedTask } from '../../../../../core/tasks/models/claimed-task-object.model';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.interface';
/** /**
* This component renders claimed task approved object for the search result in the list view. * This component renders claimed task approved object for the search result in the list view.
@@ -43,9 +44,10 @@ export class ClaimedApprovedSearchResultListElementComponent extends SearchResul
public constructor( public constructor(
protected linkService: LinkService, protected linkService: LinkService,
protected truncatableService: TruncatableService, protected truncatableService: TruncatableService,
protected dsoNameService: DSONameService protected dsoNameService: DSONameService,
@Inject(APP_CONFIG) protected appConfig: AppConfig
) { ) {
super(truncatableService, dsoNameService); super(truncatableService, dsoNameService, appConfig);
} }
/** /**

View File

@@ -17,6 +17,8 @@ import { LinkService } from '../../../../../core/cache/builders/link.service';
import { MyDspaceItemStatusType } from '../../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import { MyDspaceItemStatusType } from '../../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../../mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../../mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
import { environment } from '../../../../../../environments/environment';
let component: ClaimedDeclinedSearchResultListElementComponent; let component: ClaimedDeclinedSearchResultListElementComponent;
let fixture: ComponentFixture<ClaimedDeclinedSearchResultListElementComponent>; let fixture: ComponentFixture<ClaimedDeclinedSearchResultListElementComponent>;
@@ -67,7 +69,8 @@ describe('ClaimedDeclinedSearchResultListElementComponent', () => {
providers: [ providers: [
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },
{ provide: LinkService, useValue: linkService }, { provide: LinkService, useValue: linkService },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environment }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ClaimedDeclinedSearchResultListElementComponent, { }).overrideComponent(ClaimedDeclinedSearchResultListElementComponent, {

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { listableObjectComponent } from '../../../../object-collection/shared/listable-object/listable-object.decorator'; import { listableObjectComponent } from '../../../../object-collection/shared/listable-object/listable-object.decorator';
import { ClaimedDeclinedTaskSearchResult } from '../../../../object-collection/shared/claimed-declined-task-search-result.model'; import { ClaimedDeclinedTaskSearchResult } from '../../../../object-collection/shared/claimed-declined-task-search-result.model';
@@ -14,6 +14,7 @@ import { SearchResultListElementComponent } from '../../../search-result-list-el
import { ClaimedTaskSearchResult } from '../../../../object-collection/shared/claimed-task-search-result.model'; import { ClaimedTaskSearchResult } from '../../../../object-collection/shared/claimed-task-search-result.model';
import { ClaimedTask } from '../../../../../core/tasks/models/claimed-task-object.model'; import { ClaimedTask } from '../../../../../core/tasks/models/claimed-task-object.model';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.interface';
/** /**
* This component renders claimed task declined object for the search result in the list view. * This component renders claimed task declined object for the search result in the list view.
@@ -44,9 +45,10 @@ export class ClaimedDeclinedSearchResultListElementComponent extends SearchResul
public constructor( public constructor(
protected linkService: LinkService, protected linkService: LinkService,
protected truncatableService: TruncatableService, protected truncatableService: TruncatableService,
protected dsoNameService: DSONameService protected dsoNameService: DSONameService,
@Inject(APP_CONFIG) protected appConfig: AppConfig
) { ) {
super(truncatableService, dsoNameService); super(truncatableService, dsoNameService, appConfig);
} }
/** /**

View File

@@ -18,6 +18,8 @@ import { getMockLinkService } from '../../../mocks/link-service.mock';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../config/app-config.interface';
import { environment } from '../../../../../environments/environment';
let component: ClaimedSearchResultListElementComponent; let component: ClaimedSearchResultListElementComponent;
let fixture: ComponentFixture<ClaimedSearchResultListElementComponent>; let fixture: ComponentFixture<ClaimedSearchResultListElementComponent>;
@@ -68,7 +70,8 @@ describe('ClaimedSearchResultListElementComponent', () => {
providers: [ providers: [
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },
{ provide: LinkService, useValue: linkService }, { provide: LinkService, useValue: linkService },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environment }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ClaimedSearchResultListElementComponent, { }).overrideComponent(ClaimedSearchResultListElementComponent, {

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { ViewMode } from '../../../../core/shared/view-mode.model'; import { ViewMode } from '../../../../core/shared/view-mode.model';
import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator'; import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator';
@@ -13,6 +13,7 @@ import { followLink } from '../../../utils/follow-link-config.model';
import { SearchResultListElementComponent } from '../../search-result-list-element/search-result-list-element.component'; import { SearchResultListElementComponent } from '../../search-result-list-element/search-result-list-element.component';
import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface';
@Component({ @Component({
selector: 'ds-claimed-search-result-list-element', selector: 'ds-claimed-search-result-list-element',
@@ -40,9 +41,10 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle
public constructor( public constructor(
protected linkService: LinkService, protected linkService: LinkService,
protected truncatableService: TruncatableService, protected truncatableService: TruncatableService,
protected dsoNameService: DSONameService protected dsoNameService: DSONameService,
@Inject(APP_CONFIG) protected appConfig: AppConfig
) { ) {
super(truncatableService, dsoNameService); super(truncatableService, dsoNameService, appConfig);
} }
/** /**

View File

@@ -1,3 +1,9 @@
<div class="row">
<div *ngIf="showThumbnails" class="col-3 col-md-2">
<ds-thumbnail [thumbnail]="item?.thumbnail | async" [limitWidth]="true">
</ds-thumbnail>
</div>
<div [ngClass]="showThumbnails ? 'col-9' : 'col-md-12'">
<div *ngIf="item" @fadeInOut> <div *ngIf="item" @fadeInOut>
<ng-container *ngIf="status"> <ng-container *ngIf="status">
<ds-mydspace-item-status [status]="status"></ds-mydspace-item-status> <ds-mydspace-item-status [status]="status"></ds-mydspace-item-status>
@@ -7,17 +13,21 @@
<ds-access-status-badge [item]="item" class="pl-1"></ds-access-status-badge> <ds-access-status-badge [item]="item" class="pl-1"></ds-access-status-badge>
</div> </div>
<ds-truncatable [id]="item.id"> <ds-truncatable [id]="item.id">
<h3 [innerHTML]="item.firstMetadataValue('dc.title') || ('mydspace.results.no-title' | translate)" [ngClass]="{'lead': true,'text-muted': !item.firstMetadataValue('dc.title')}"></h3> <h3 [innerHTML]="item.firstMetadataValue('dc.title') || ('mydspace.results.no-title' | translate)"
[ngClass]="{'lead': true,'text-muted': !item.firstMetadataValue('dc.title')}"></h3>
<div> <div>
<span class="text-muted"> <span class="text-muted">
<ds-truncatable-part [id]="item.id" [minLines]="1"> <ds-truncatable-part [id]="item.id" [minLines]="1">
(<span *ngIf="item.hasMetadata('dc.publisher')" class="item-list-publisher" (<span *ngIf="item.hasMetadata('dc.publisher')" class="item-list-publisher"
[innerHTML]="item.firstMetadataValue('dc.publisher') + ', '"></span> [innerHTML]="item.firstMetadataValue('dc.publisher') + ', '"></span>
<span class="item-list-date" [innerHTML]="item.firstMetadataValue('dc.date.issued') || ('mydspace.results.no-date' | translate)"></span>) <span class="item-list-date"
[innerHTML]="item.firstMetadataValue('dc.date.issued') || ('mydspace.results.no-date' | translate)"></span>)
<span *ngIf="item.hasMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);" <span *ngIf="item.hasMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);"
class="item-list-authors"> class="item-list-authors">
<span *ngIf="item.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length === 0">{{'mydspace.results.no-authors' | translate}}</span> <span
<span *ngFor="let author of item.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;"> *ngIf="item.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length === 0">{{'mydspace.results.no-authors' | translate}}</span>
<span
*ngFor="let author of item.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">
<span [innerHTML]="author"><span [innerHTML]="author"></span></span> <span [innerHTML]="author"><span [innerHTML]="author"></span></span>
<span *ngIf="!last">; </span> <span *ngIf="!last">; </span>
</span> </span>
@@ -35,3 +45,5 @@
</ds-truncatable> </ds-truncatable>
<ds-item-submitter *ngIf="showSubmitter" [object]="object.indexableObject"></ds-item-submitter> <ds-item-submitter *ngIf="showSubmitter" [object]="object.indexableObject"></ds-item-submitter>
</div> </div>
</div>
</div>

View File

@@ -10,6 +10,7 @@ import { ItemListPreviewComponent } from './item-list-preview.component';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock';
import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { APP_CONFIG } from '../../../../../config/app-config.interface';
let component: ItemListPreviewComponent; let component: ItemListPreviewComponent;
let fixture: ComponentFixture<ItemListPreviewComponent>; let fixture: ComponentFixture<ItemListPreviewComponent>;
@@ -66,6 +67,18 @@ const mockItemWithEntityType: Item = Object.assign(new Item(), {
} }
}); });
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
const enviromentNoThumbs = {
browseBy: {
showThumbnails: false
}
};
describe('ItemListPreviewComponent', () => { describe('ItemListPreviewComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -80,8 +93,8 @@ describe('ItemListPreviewComponent', () => {
], ],
declarations: [ItemListPreviewComponent, TruncatePipe], declarations: [ItemListPreviewComponent, TruncatePipe],
providers: [ providers: [
{ provide: 'objectElementProvider', useValue: { mockItemWithAuthorAndDate } } { provide: 'objectElementProvider', useValue: { mockItemWithAuthorAndDate }},
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
@@ -100,6 +113,17 @@ describe('ItemListPreviewComponent', () => {
component.object = { hitHighlights: {} } as any; component.object = { hitHighlights: {} } as any;
}); });
describe('When showThumbnails is true', () => {
beforeEach(() => {
component.item = mockItemWithAuthorAndDate;
fixture.detectChanges();
});
it('should add the ds-thumbnail element', () => {
const thumbnail = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnail).toBeTruthy();
});
});
describe('When the item has an author', () => { describe('When the item has an author', () => {
beforeEach(() => { beforeEach(() => {
component.item = mockItemWithAuthorAndDate; component.item = mockItemWithAuthorAndDate;
@@ -160,3 +184,48 @@ describe('ItemListPreviewComponent', () => {
}); });
}); });
}); });
describe('ItemListPreviewComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderMock
}
}),
NoopAnimationsModule
],
declarations: [ItemListPreviewComponent, TruncatePipe],
providers: [
{provide: 'objectElementProvider', useValue: {mockItemWithAuthorAndDate}},
{provide: APP_CONFIG, useValue: enviromentNoThumbs}
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ItemListPreviewComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(ItemListPreviewComponent);
component = fixture.componentInstance;
}));
beforeEach(() => {
component.object = { hitHighlights: {} } as any;
});
describe('When showThumbnails is true', () => {
beforeEach(() => {
component.item = mockItemWithAuthorAndDate;
fixture.detectChanges();
});
it('should add the ds-thumbnail element', () => {
const thumbnail = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnail).toBeFalsy();
});
});
});

View File

@@ -1,9 +1,10 @@
import { Component, Input } from '@angular/core'; import { Component, Inject, Input, OnInit } from '@angular/core';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
import { fadeInOut } from '../../../animations/fade'; import { fadeInOut } from '../../../animations/fade';
import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type';
import { SearchResult } from '../../../search/models/search-result.model'; import { SearchResult } from '../../../search/models/search-result.model';
import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface';
/** /**
* This component show metadata for the given item object in the list view. * This component show metadata for the given item object in the list view.
@@ -14,7 +15,7 @@ import { SearchResult } from '../../../search/models/search-result.model';
templateUrl: 'item-list-preview.component.html', templateUrl: 'item-list-preview.component.html',
animations: [fadeInOut] animations: [fadeInOut]
}) })
export class ItemListPreviewComponent { export class ItemListPreviewComponent implements OnInit{
/** /**
* The item to display * The item to display
@@ -35,4 +36,17 @@ export class ItemListPreviewComponent {
* A boolean representing if to show submitter information * A boolean representing if to show submitter information
*/ */
@Input() showSubmitter = false; @Input() showSubmitter = false;
/**
* Display thumbnails if required by configuration
*/
showThumbnails: boolean;
constructor(@Inject(APP_CONFIG) protected appConfig: AppConfig) {
}
ngOnInit(): void {
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
}
} }

View File

@@ -24,6 +24,7 @@ export class ThemedItemListPreviewComponent extends ThemedComponent<ItemListPrev
@Input() showSubmitter = false; @Input() showSubmitter = false;
protected getComponentName(): string { protected getComponentName(): string {
return 'ItemListPreviewComponent'; return 'ItemListPreviewComponent';
} }

View File

@@ -2,4 +2,9 @@
[object]="object" [object]="object"
[status]="status"></ds-themed-item-list-preview> [status]="status"></ds-themed-item-list-preview>
<div class="row">
<div [ngClass]="showThumbnails ? 'offset-3 offset-md-2 pl-3' : ''">
<ds-item-actions [object]="dso" (processCompleted)="reloadedObject.emit($event.reloadedObject)"></ds-item-actions> <ds-item-actions [object]="dso" (processCompleted)="reloadedObject.emit($event.reloadedObject)"></ds-item-actions>
</div>
</div>

View File

@@ -12,6 +12,8 @@ import { TruncatableService } from '../../../truncatable/truncatable.service';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../config/app-config.interface';
import { environment } from '../../../../../environments/environment';
let component: ItemSearchResultListElementSubmissionComponent; let component: ItemSearchResultListElementSubmissionComponent;
let fixture: ComponentFixture<ItemSearchResultListElementSubmissionComponent>; let fixture: ComponentFixture<ItemSearchResultListElementSubmissionComponent>;
@@ -56,7 +58,8 @@ describe('ItemMyDSpaceResultListElementComponent', () => {
declarations: [ItemSearchResultListElementSubmissionComponent], declarations: [ItemSearchResultListElementSubmissionComponent],
providers: [ providers: [
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environment }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ItemSearchResultListElementSubmissionComponent, { }).overrideComponent(ItemSearchResultListElementSubmissionComponent, {

View File

@@ -25,7 +25,13 @@ export class ItemSearchResultListElementSubmissionComponent extends SearchResult
*/ */
public status = MyDspaceItemStatusType.ARCHIVED; public status = MyDspaceItemStatusType.ARCHIVED;
/**
* Display thumbnails if required by configuration
*/
showThumbnails: boolean;
ngOnInit() { ngOnInit() {
super.ngOnInit(); super.ngOnInit();
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
} }
} }

View File

@@ -4,5 +4,10 @@
[object]="object" [object]="object"
[showSubmitter]="showSubmitter" [showSubmitter]="showSubmitter"
[status]="status"></ds-themed-item-list-preview> [status]="status"></ds-themed-item-list-preview>
<ds-pool-task-actions id="actions" *ngIf="workflowitem" [object]="dso" (processCompleted)="this.reloadedObject.emit($event.reloadedObject)"></ds-pool-task-actions> <div class="row">
<div [ngClass]="showThumbnails ? 'offset-3 offset-md-2 pl-3' : ''">
<ds-pool-task-actions id="actions" *ngIf="workflowitem" [object]="dso"
(processCompleted)="this.reloadedObject.emit($event.reloadedObject)"></ds-pool-task-actions>
</div>
</div>
</ng-container> </ng-container>

View File

@@ -18,6 +18,7 @@ import { getMockLinkService } from '../../../mocks/link-service.mock';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../config/app-config.interface';
let component: PoolSearchResultListElementComponent; let component: PoolSearchResultListElementComponent;
let fixture: ComponentFixture<PoolSearchResultListElementComponent>; let fixture: ComponentFixture<PoolSearchResultListElementComponent>;
@@ -54,6 +55,13 @@ const item = Object.assign(new Item(), {
] ]
} }
}); });
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
const rdItem = createSuccessfulRemoteDataObject(item); const rdItem = createSuccessfulRemoteDataObject(item);
const workflowitem = Object.assign(new WorkflowItem(), { item: observableOf(rdItem) }); const workflowitem = Object.assign(new WorkflowItem(), { item: observableOf(rdItem) });
const rdWorkflowitem = createSuccessfulRemoteDataObject(workflowitem); const rdWorkflowitem = createSuccessfulRemoteDataObject(workflowitem);
@@ -68,7 +76,8 @@ describe('PoolSearchResultListElementComponent', () => {
providers: [ providers: [
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },
{ provide: LinkService, useValue: linkService }, { provide: LinkService, useValue: linkService },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(PoolSearchResultListElementComponent, { }).overrideComponent(PoolSearchResultListElementComponent, {
@@ -112,4 +121,9 @@ describe('PoolSearchResultListElementComponent', () => {
expect(component.reloadedObject.emit).toHaveBeenCalledWith(actionPayload.reloadedObject); expect(component.reloadedObject.emit).toHaveBeenCalledWith(actionPayload.reloadedObject);
})); }));
it('should add an offset to the actions element', () => {
const thumbnail = fixture.debugElement.query(By.css('.offset-3'));
expect(thumbnail).toBeTruthy();
});
}); });

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, Inject, OnInit } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -14,6 +14,7 @@ import { TruncatableService } from '../../../truncatable/truncatable.service';
import { followLink } from '../../../utils/follow-link-config.model'; import { followLink } from '../../../utils/follow-link-config.model';
import { LinkService } from '../../../../core/cache/builders/link.service'; import { LinkService } from '../../../../core/cache/builders/link.service';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface';
/** /**
* This component renders pool task object for the search result in the list view. * This component renders pool task object for the search result in the list view.
@@ -47,12 +48,18 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen
*/ */
public index: number; public index: number;
/**
* Display thumbnails if required by configuration
*/
showThumbnails: boolean;
constructor( constructor(
protected linkService: LinkService, protected linkService: LinkService,
protected truncatableService: TruncatableService, protected truncatableService: TruncatableService,
protected dsoNameService: DSONameService protected dsoNameService: DSONameService,
@Inject(APP_CONFIG) protected appConfig: AppConfig
) { ) {
super(truncatableService, dsoNameService); super(truncatableService, dsoNameService, appConfig);
} }
/** /**
@@ -64,6 +71,7 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen
followLink('item'), followLink('submitter') followLink('item'), followLink('submitter')
), followLink('action')); ), followLink('action'));
this.workflowitemRD$ = this.dso.workflowitem as Observable<RemoteData<WorkflowItem>>; this.workflowitemRD$ = this.dso.workflowitem as Observable<RemoteData<WorkflowItem>>;
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
} }
} }

View File

@@ -3,9 +3,13 @@
[item]="item$ | async" [item]="item$ | async"
[object]="object" [object]="object"
[status]="status"></ds-themed-item-list-preview> [status]="status"></ds-themed-item-list-preview>
<div class="row">
<div [ngClass]="showThumbnails ? 'offset-3 offset-md-2 pl-3' : ''">
<ds-workflowitem-actions [object]="dso" (processCompleted)="reloadedObject.emit($event.reloadedObject)"></ds-workflowitem-actions> <ds-workflowitem-actions [object]="dso" (processCompleted)="reloadedObject.emit($event.reloadedObject)"></ds-workflowitem-actions>
</div>
</div>
</ng-container> </ng-container>
<ds-themed-loading <ds-themed-loading
*ngIf="!(item$ | async)" *ngIf="!(item$ | async)"
[showMessage]="false"></ds-themed-loading> [showMessage]="false"></ds-themed-loading>

View File

@@ -18,6 +18,7 @@ import { WorkflowItemSearchResultListElementComponent } from './workflow-item-se
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../config/app-config.interface';
let component: WorkflowItemSearchResultListElementComponent; let component: WorkflowItemSearchResultListElementComponent;
let fixture: ComponentFixture<WorkflowItemSearchResultListElementComponent>; let fixture: ComponentFixture<WorkflowItemSearchResultListElementComponent>;
@@ -54,6 +55,13 @@ const item = Object.assign(new Item(), {
] ]
} }
}); });
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
const rd = createSuccessfulRemoteDataObject(item); const rd = createSuccessfulRemoteDataObject(item);
mockResultObject.indexableObject = Object.assign(new WorkflowItem(), { item: observableOf(rd) }); mockResultObject.indexableObject = Object.assign(new WorkflowItem(), { item: observableOf(rd) });
@@ -69,7 +77,8 @@ describe('WorkflowItemSearchResultListElementComponent', () => {
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },
{ provide: ItemDataService, useValue: {} }, { provide: ItemDataService, useValue: {} },
{ provide: LinkService, useValue: linkService }, { provide: LinkService, useValue: linkService },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(WorkflowItemSearchResultListElementComponent, { }).overrideComponent(WorkflowItemSearchResultListElementComponent, {
@@ -110,4 +119,10 @@ describe('WorkflowItemSearchResultListElementComponent', () => {
expect(component.reloadedObject.emit).toHaveBeenCalledWith(actionPayload.reloadedObject); expect(component.reloadedObject.emit).toHaveBeenCalledWith(actionPayload.reloadedObject);
})); }));
it('should add an offset to the actions element', () => {
const thumbnail = fixture.debugElement.query(By.css('.offset-3'));
expect(thumbnail).toBeTruthy();
});
}); });

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { find, map } from 'rxjs/operators'; import { find, map } from 'rxjs/operators';
@@ -16,6 +16,7 @@ import { TruncatableService } from '../../../truncatable/truncatable.service';
import { followLink } from '../../../utils/follow-link-config.model'; import { followLink } from '../../../utils/follow-link-config.model';
import { SearchResultListElementComponent } from '../../search-result-list-element/search-result-list-element.component'; import { SearchResultListElementComponent } from '../../search-result-list-element/search-result-list-element.component';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface';
/** /**
* This component renders workflowitem object for the search result in the list view. * This component renders workflowitem object for the search result in the list view.
@@ -39,12 +40,18 @@ export class WorkflowItemSearchResultListElementComponent extends SearchResultLi
*/ */
public status = MyDspaceItemStatusType.WORKFLOW; public status = MyDspaceItemStatusType.WORKFLOW;
/**
* Display thumbnails if required by configuration
*/
showThumbnails: boolean;
constructor( constructor(
protected truncatableService: TruncatableService, protected truncatableService: TruncatableService,
protected linkService: LinkService, protected linkService: LinkService,
protected dsoNameService: DSONameService protected dsoNameService: DSONameService,
@Inject(APP_CONFIG) protected appConfig: AppConfig
) { ) {
super(truncatableService, dsoNameService); super(truncatableService, dsoNameService, appConfig);
} }
/** /**
@@ -54,6 +61,7 @@ export class WorkflowItemSearchResultListElementComponent extends SearchResultLi
super.ngOnInit(); super.ngOnInit();
this.linkService.resolveLink(this.dso, followLink('item')); this.linkService.resolveLink(this.dso, followLink('item'));
this.initItem(this.dso.item as Observable<RemoteData<Item>> ); this.initItem(this.dso.item as Observable<RemoteData<Item>> );
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
} }
/** /**

View File

@@ -4,7 +4,11 @@
[object]="object" [object]="object"
[status]="status"></ds-themed-item-list-preview> [status]="status"></ds-themed-item-list-preview>
<div class="row">
<div [ngClass]="showThumbnails ? 'offset-3 offset-md-2 pl-3' : ''">
<ds-workspaceitem-actions [object]="dso" (processCompleted)="reloadedObject.emit($event.reloadedObject)"></ds-workspaceitem-actions> <ds-workspaceitem-actions [object]="dso" (processCompleted)="reloadedObject.emit($event.reloadedObject)"></ds-workspaceitem-actions>
</div>
</div>
</ng-container> </ng-container>
<ds-themed-loading <ds-themed-loading
*ngIf="!(item$ | async)" *ngIf="!(item$ | async)"

View File

@@ -18,6 +18,7 @@ import { WorkspaceItemSearchResultListElementComponent } from './workspace-item-
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../config/app-config.interface';
let component: WorkspaceItemSearchResultListElementComponent; let component: WorkspaceItemSearchResultListElementComponent;
let fixture: ComponentFixture<WorkspaceItemSearchResultListElementComponent>; let fixture: ComponentFixture<WorkspaceItemSearchResultListElementComponent>;
@@ -54,6 +55,13 @@ const item = Object.assign(new Item(), {
] ]
} }
}); });
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
const rd = createSuccessfulRemoteDataObject(item); const rd = createSuccessfulRemoteDataObject(item);
mockResultObject.indexableObject = Object.assign(new WorkspaceItem(), { item: observableOf(rd) }); mockResultObject.indexableObject = Object.assign(new WorkspaceItem(), { item: observableOf(rd) });
let linkService; let linkService;
@@ -68,7 +76,8 @@ describe('WorkspaceItemSearchResultListElementComponent', () => {
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },
{ provide: ItemDataService, useValue: {} }, { provide: ItemDataService, useValue: {} },
{ provide: LinkService, useValue: linkService }, { provide: LinkService, useValue: linkService },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(WorkspaceItemSearchResultListElementComponent, { }).overrideComponent(WorkspaceItemSearchResultListElementComponent, {
@@ -110,4 +119,10 @@ describe('WorkspaceItemSearchResultListElementComponent', () => {
expect(component.reloadedObject.emit).toHaveBeenCalledWith(actionPayload.reloadedObject); expect(component.reloadedObject.emit).toHaveBeenCalledWith(actionPayload.reloadedObject);
})); }));
it('should add an offset to the actions element', () => {
const thumbnail = fixture.debugElement.query(By.css('.offset-3'));
expect(thumbnail).toBeTruthy();
});
}); });

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { find, map } from 'rxjs/operators'; import { find, map } from 'rxjs/operators';
@@ -16,6 +16,7 @@ import { TruncatableService } from '../../../truncatable/truncatable.service';
import { followLink } from '../../../utils/follow-link-config.model'; import { followLink } from '../../../utils/follow-link-config.model';
import { SearchResultListElementComponent } from '../../search-result-list-element/search-result-list-element.component'; import { SearchResultListElementComponent } from '../../search-result-list-element/search-result-list-element.component';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface';
/** /**
* This component renders workspaceitem object for the search result in the list view. * This component renders workspaceitem object for the search result in the list view.
@@ -39,12 +40,18 @@ export class WorkspaceItemSearchResultListElementComponent extends SearchResultL
*/ */
status = MyDspaceItemStatusType.WORKSPACE; status = MyDspaceItemStatusType.WORKSPACE;
/**
* Display thumbnails if required by configuration
*/
showThumbnails: boolean;
constructor( constructor(
protected truncatableService: TruncatableService, protected truncatableService: TruncatableService,
protected linkService: LinkService, protected linkService: LinkService,
protected dsoNameService: DSONameService protected dsoNameService: DSONameService,
@Inject(APP_CONFIG) protected appConfig: AppConfig
) { ) {
super(truncatableService, dsoNameService); super(truncatableService, dsoNameService, appConfig);
} }
/** /**
@@ -54,6 +61,7 @@ export class WorkspaceItemSearchResultListElementComponent extends SearchResultL
super.ngOnInit(); super.ngOnInit();
this.linkService.resolveLink(this.dso, followLink('item')); this.linkService.resolveLink(this.dso, followLink('item'));
this.initItem(this.dso.item as Observable<RemoteData<Item>>); this.initItem(this.dso.item as Observable<RemoteData<Item>>);
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
} }
/** /**

View File

@@ -1,4 +1,9 @@
<div class="row">
<div *ngIf="showThumbnails" class="offset-3 offset-md-2 "></div>
<div [ngClass]="showThumbnails ? 'col-9' : 'col-md-12'">
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge> <ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/collections/' + dso.id]" class="lead" [innerHTML]="dsoTitle"></a> <a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/collections/' + dso.id]" class="lead" [innerHTML]="dsoTitle"></a>
<span *ngIf="linkType == linkTypes.None" class="lead" [innerHTML]="dsoTitle"></span> <span *ngIf="linkType == linkTypes.None" class="lead" [innerHTML]="dsoTitle"></span>
<div *ngIf="dso.shortDescription" class="text-muted abstract-text" [innerHTML]="firstMetadataValue('dc.description.abstract')"></div> <div *ngIf="dso.shortDescription" class="text-muted abstract-text" [innerHTML]="firstMetadataValue('dc.description.abstract')"></div>
</div>
</div>

View File

@@ -9,6 +9,7 @@ import { TruncatableService } from '../../../truncatable/truncatable.service';
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model'; import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../config/app-config.interface';
let collectionSearchResultListElementComponent: CollectionSearchResultListElementComponent; let collectionSearchResultListElementComponent: CollectionSearchResultListElementComponent;
let fixture: ComponentFixture<CollectionSearchResultListElementComponent>; let fixture: ComponentFixture<CollectionSearchResultListElementComponent>;
@@ -43,13 +44,20 @@ mockCollectionWithoutAbstract.indexableObject = Object.assign(new Collection(),
} }
}); });
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
describe('CollectionSearchResultListElementComponent', () => { describe('CollectionSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [CollectionSearchResultListElementComponent, TruncatePipe], declarations: [CollectionSearchResultListElementComponent, TruncatePipe],
providers: [ providers: [
{ provide: TruncatableService, useValue: truncatableServiceStub }, { provide: TruncatableService, useValue: truncatableServiceStub },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(CollectionSearchResultListElementComponent, { }).overrideComponent(CollectionSearchResultListElementComponent, {
@@ -87,4 +95,11 @@ describe('CollectionSearchResultListElementComponent', () => {
expect(collectionAbstractField).toBeNull(); expect(collectionAbstractField).toBeNull();
}); });
}); });
describe('when environment is set to show thumbnail images', () => {
it('should offset content', () => {
const offset = fixture.debugElement.query(By.css('offset-md-2'));
});
});
}); });

View File

@@ -14,4 +14,16 @@ import { listableObjectComponent } from '../../../object-collection/shared/lista
* Component representing a collection search result in list view * Component representing a collection search result in list view
*/ */
@listableObjectComponent(CollectionSearchResult, ViewMode.ListElement) @listableObjectComponent(CollectionSearchResult, ViewMode.ListElement)
export class CollectionSearchResultListElementComponent extends SearchResultListElementComponent<CollectionSearchResult, Collection> {} export class CollectionSearchResultListElementComponent extends SearchResultListElementComponent<CollectionSearchResult, Collection> {
/**
* Display thumbnails if required by configuration
*/
showThumbnails: boolean;
ngOnInit(): void {
super.ngOnInit();
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
}
}

View File

@@ -1,4 +1,9 @@
<div class="row">
<div *ngIf="showThumbnails" class="offset-md-2"></div>
<div [ngClass]="showThumbnails ? 'col-9' : 'col-md-12'">
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge> <ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/communities/' + dso.id]" class="lead" [innerHTML]="dsoTitle"></a> <a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/communities/' + dso.id]" class="lead" [innerHTML]="dsoTitle"></a>
<span *ngIf="linkType == linkTypes.None" class="lead" [innerHTML]="dsoTitle"></span> <span *ngIf="linkType == linkTypes.None" class="lead" [innerHTML]="dsoTitle"></span>
<div *ngIf="dso.shortDescription" class="text-muted abstract-text" [innerHTML]="firstMetadataValue('dc.description.abstract')"></div> <div *ngIf="dso.shortDescription" class="text-muted abstract-text" [innerHTML]="firstMetadataValue('dc.description.abstract')"></div>
</div>
</div>

View File

@@ -9,6 +9,7 @@ import { TruncatableService } from '../../../truncatable/truncatable.service';
import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model'; import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../config/app-config.interface';
let communitySearchResultListElementComponent: CommunitySearchResultListElementComponent; let communitySearchResultListElementComponent: CommunitySearchResultListElementComponent;
let fixture: ComponentFixture<CommunitySearchResultListElementComponent>; let fixture: ComponentFixture<CommunitySearchResultListElementComponent>;
@@ -43,13 +44,20 @@ mockCommunityWithoutAbstract.indexableObject = Object.assign(new Community(), {
} }
}); });
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
describe('CommunitySearchResultListElementComponent', () => { describe('CommunitySearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [CommunitySearchResultListElementComponent, TruncatePipe], declarations: [CommunitySearchResultListElementComponent, TruncatePipe],
providers: [ providers: [
{ provide: TruncatableService, useValue: truncatableServiceStub }, { provide: TruncatableService, useValue: truncatableServiceStub },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
@@ -88,4 +96,10 @@ describe('CommunitySearchResultListElementComponent', () => {
expect(communityAbstractField).toBeNull(); expect(communityAbstractField).toBeNull();
}); });
}); });
describe('when environment is set to show thumbnail images', () => {
it('should offset content', () => {
const offset = fixture.debugElement.query(By.css('offset-md-2'));
});
});
}); });

View File

@@ -15,5 +15,14 @@ import { listableObjectComponent } from '../../../object-collection/shared/lista
*/ */
@listableObjectComponent(CommunitySearchResult, ViewMode.ListElement) @listableObjectComponent(CommunitySearchResult, ViewMode.ListElement)
export class CommunitySearchResultListElementComponent extends SearchResultListElementComponent<CommunitySearchResult, Community> { export class CommunitySearchResultListElementComponent extends SearchResultListElementComponent<CommunitySearchResult, Community> {
/**
* Display thumbnails if required by configuration
*/
showThumbnails: boolean;
ngOnInit(): void {
super.ngOnInit();
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
}
} }

View File

@@ -1,3 +1,12 @@
<div class="row">
<div *ngIf="showThumbnails" class="col-3 col-md-2">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="dont-break-out">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="true">
</ds-thumbnail>
</a>
</div>
<div [ngClass]="showThumbnails ? 'col-9' : 'col-md-12'">
<div class="d-flex"> <div class="d-flex">
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge> <ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-access-status-badge [item]="dso" class="pl-1"></ds-access-status-badge> <ds-access-status-badge [item]="dso" class="pl-1"></ds-access-status-badge>
@@ -29,3 +38,5 @@
</ds-truncatable-part> </ds-truncatable-part>
</div> </div>
</ds-truncatable> </ds-truncatable>
</div>
</div>

View File

@@ -9,6 +9,7 @@ import { TruncatableService } from '../../../../../truncatable/truncatable.servi
import { ItemSearchResult } from '../../../../../object-collection/shared/item-search-result.model'; import { ItemSearchResult } from '../../../../../object-collection/shared/item-search-result.model';
import { DSONameService } from '../../../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock, UNDEFINED_NAME } from '../../../../../mocks/dso-name.service.mock'; import { DSONameServiceMock, UNDEFINED_NAME } from '../../../../../mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../../config/app-config.interface';
let publicationListElementComponent: ItemSearchResultListElementComponent; let publicationListElementComponent: ItemSearchResultListElementComponent;
let fixture: ComponentFixture<ItemSearchResultListElementComponent>; let fixture: ComponentFixture<ItemSearchResultListElementComponent>;
@@ -59,14 +60,26 @@ const mockItemWithoutMetadata: ItemSearchResult = Object.assign(new ItemSearchRe
}) })
}); });
const environmentUseThumbs = {
browseBy: {
showThumbnails: true
}
};
describe('ItemListElementComponent', () => { const enviromentNoThumbs = {
browseBy: {
showThumbnails: false
}
};
describe('ItemSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ItemSearchResultListElementComponent, TruncatePipe], declarations: [ItemSearchResultListElementComponent, TruncatePipe],
providers: [ providers: [
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },
{ provide: DSONameService, useClass: DSONameServiceMock } { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
@@ -81,6 +94,21 @@ describe('ItemListElementComponent', () => {
})); }));
describe('with environment.browseBy.showThumbnails set to true', () => {
beforeEach(() => {
publicationListElementComponent.object = mockItemWithMetadata;
fixture.detectChanges();
});
it('should set showThumbnails to true', () => {
expect(publicationListElementComponent.showThumbnails).toBeTrue();
});
it('should add ds-thumbnail element', () => {
const thumbnailElement = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnailElement).toBeTruthy();
});
});
describe('When the item has an author', () => { describe('When the item has an author', () => {
beforeEach(() => { beforeEach(() => {
publicationListElementComponent.object = mockItemWithMetadata; publicationListElementComponent.object = mockItemWithMetadata;
@@ -189,3 +217,39 @@ describe('ItemListElementComponent', () => {
}); });
}); });
}); });
describe('ItemSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ItemSearchResultListElementComponent, TruncatePipe],
providers: [
{provide: TruncatableService, useValue: {}},
{provide: DSONameService, useClass: DSONameServiceMock},
{ provide: APP_CONFIG, useValue: enviromentNoThumbs }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ItemSearchResultListElementComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(ItemSearchResultListElementComponent);
publicationListElementComponent = fixture.componentInstance;
}));
describe('with environment.browseBy.showThumbnails set to false', () => {
beforeEach(() => {
publicationListElementComponent.object = mockItemWithMetadata;
fixture.detectChanges();
});
it('should not add ds-thumbnail element', () => {
const thumbnailElement = fixture.debugElement.query(By.css('ds-thumbnail'));
expect(thumbnailElement).toBeFalsy();
});
});
});

View File

@@ -22,8 +22,14 @@ export class ItemSearchResultListElementComponent extends SearchResultListElemen
*/ */
itemPageRoute: string; itemPageRoute: string;
/**
* Display thumbnails if required by configuration
*/
showThumbnails: boolean;
ngOnInit(): void { ngOnInit(): void {
super.ngOnInit(); super.ngOnInit();
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
this.itemPageRoute = getItemPageRoute(this.dso); this.itemPageRoute = getItemPageRoute(this.dso);
} }
} }

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, Inject, OnInit } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { SearchResult } from '../../search/models/search-result.model'; import { SearchResult } from '../../search/models/search-result.model';
@@ -8,6 +8,7 @@ import { AbstractListableElementComponent } from '../../object-collection/shared
import { TruncatableService } from '../../truncatable/truncatable.service'; import { TruncatableService } from '../../truncatable/truncatable.service';
import { Metadata } from '../../../core/shared/metadata.utils'; import { Metadata } from '../../../core/shared/metadata.utils';
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
import { APP_CONFIG, AppConfig } from '../../../../config/app-config.interface';
@Component({ @Component({
selector: 'ds-search-result-list-element', selector: 'ds-search-result-list-element',
@@ -20,7 +21,9 @@ export class SearchResultListElementComponent<T extends SearchResult<K>, K exten
dso: K; dso: K;
dsoTitle: string; dsoTitle: string;
public constructor(protected truncatableService: TruncatableService, protected dsoNameService: DSONameService) { public constructor(protected truncatableService: TruncatableService,
protected dsoNameService: DSONameService,
@Inject(APP_CONFIG) protected appConfig?: AppConfig) {
super(); super();
} }

View File

@@ -42,7 +42,7 @@ export class SidebarSearchListElementComponent<T extends SearchResult<K>, K exte
protected linkService: LinkService, protected linkService: LinkService,
protected dsoNameService: DSONameService protected dsoNameService: DSONameService
) { ) {
super(truncatableService, dsoNameService); super(truncatableService, dsoNameService, null);
} }
/** /**

View File

@@ -0,0 +1,15 @@
/**
* Sets the class to be used for the "no thumbnail"
* placeholder font size in lists.
*/
export function setPlaceHolderAttributes(width: number): string {
if (width < 400) {
return 'thumb-font-0';
} else if (width < 750) {
return 'thumb-font-1';
} else if (width < 1000) {
return 'thumb-font-2';
} else {
return 'thumb-font-3';
}
}

View File

@@ -1,3 +1,6 @@
import { followLink, FollowLinkConfig } from './follow-link-config.model';
import { Relationship } from '../../core/shared/item-relationships/relationship.model';
/** /**
* Get the query for looking up items by relation type * Get the query for looking up items by relation type
* @param {string} relationType Relation type * @param {string} relationType Relation type
@@ -16,3 +19,21 @@ export function getQueryByRelations(relationType: string, itemUUID: string): str
export function getFilterByRelation(relationType: string, itemUUID: string): string { export function getFilterByRelation(relationType: string, itemUUID: string): string {
return `f.${relationType}=${itemUUID},equals`; return `f.${relationType}=${itemUUID},equals`;
} }
/**
* Creates links to follow for the leftItem and rightItem. Links will include
* @param showThumbnail thumbnail image configuration
* @returns followLink array
*/
export function itemLinksToFollow(showThumbnail: boolean): FollowLinkConfig<Relationship>[] {
let linksToFollow: FollowLinkConfig<Relationship>[];
if (showThumbnail) {
linksToFollow = [
followLink('leftItem',{}, followLink('thumbnail')),
followLink('rightItem',{}, followLink('thumbnail'))
];
} else {
linksToFollow = [followLink('leftItem'), followLink('rightItem')];
}
return linksToFollow;
}

View File

@@ -7,7 +7,7 @@
[src]="src | dsSafeUrl" [alt]="alt | translate" (error)="errorHandler()"> [src]="src | dsSafeUrl" [alt]="alt | translate" (error)="errorHandler()">
<div *ngIf="src === null" class="thumbnail-content outer"> <div *ngIf="src === null" class="thumbnail-content outer">
<div class="inner"> <div class="inner">
<div class="thumbnail-placeholder w-100 h-100 p-3 lead">{{ placeholder | translate }}</div> <div class="thumbnail-placeholder w-100 h-100 lead">{{ placeholder | translate }}</div>
</div> </div>
</div> </div>
</ng-template> </ng-template>

View File

@@ -14,6 +14,7 @@ import { RemoteData } from '../core/data/remote-data';
templateUrl: './thumbnail.component.html', templateUrl: './thumbnail.component.html',
}) })
export class ThumbnailComponent implements OnChanges { export class ThumbnailComponent implements OnChanges {
/** /**
* The thumbnail Bitstream * The thumbnail Bitstream
*/ */
@@ -90,4 +91,5 @@ export class ThumbnailComponent implements OnChanges {
this.src = null; this.src = null;
} }
} }
} }

View File

@@ -23,10 +23,16 @@ export interface BrowseByConfig extends Config {
*/ */
defaultLowerLimit: number; defaultLowerLimit: number;
/**
* If true, thumbnail images for items will be added to BOTH search and browse result lists.
*/
showThumbnails: boolean;
/** /**
* Number of entries in the viewport of a paginated browse-by list. * Number of entries in the viewport of a paginated browse-by list.
* Rounded to the nearest size in the list of selectable sizes on the settings * Rounded to the nearest size in the list of selectable sizes on the settings
* menu. See pageSizeOptions in 'pagination-component-options.model.ts'. * menu. See pageSizeOptions in 'pagination-component-options.model.ts'.
*/ */
pageSize: number; pageSize: number;
} }

View File

@@ -210,6 +210,8 @@ export class DefaultAppConfig implements AppConfig {
fiveYearLimit: 30, fiveYearLimit: 30,
// The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items) // The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items)
defaultLowerLimit: 1900, defaultLowerLimit: 1900,
// Whether to add item thumbnail images to BOTH browse and search result lists.
showThumbnails: true,
// The number of entries in a paginated browse results list. // The number of entries in a paginated browse results list.
// Rounded to the nearest size in the list of selectable sizes on the // Rounded to the nearest size in the list of selectable sizes on the
// settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'. // settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'.

View File

@@ -203,6 +203,8 @@ export const environment: BuildConfig = {
fiveYearLimit: 30, fiveYearLimit: 30,
// The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items) // The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items)
defaultLowerLimit: 1900, defaultLowerLimit: 1900,
// Whether to add item thumbnail images to BOTH browse and search result lists.
showThumbnails: true,
// The number of entries in a paginated browse results list. // The number of entries in a paginated browse results list.
// Rounded to the nearest size in the list of selectable sizes on the // Rounded to the nearest size in the list of selectable sizes on the
// settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'. // settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'.

View File

@@ -45,7 +45,7 @@
--ds-edit-item-metadata-field-width: 190px; --ds-edit-item-metadata-field-width: 190px;
--ds-edit-item-language-field-width: 43px; --ds-edit-item-language-field-width: 43px;
--ds-thumbnail-max-width: 175px; --ds-thumbnail-max-width: 125px;
--ds-thumbnail-placeholder-background: #{$gray-100}; --ds-thumbnail-placeholder-background: #{$gray-100};
--ds-thumbnail-placeholder-border: 1px solid #{$gray-300}; --ds-thumbnail-placeholder-border: 1px solid #{$gray-300};
--ds-thumbnail-placeholder-color: #{lighten($gray-800, 7%)}; --ds-thumbnail-placeholder-color: #{lighten($gray-800, 7%)};

View File

@@ -138,3 +138,51 @@ ds-dynamic-form-control-container.d-none {
.btn-dark { .btn-dark {
background-color: var(--ds-admin-sidebar-bg); background-color: var(--ds-admin-sidebar-bg);
} }
/* The font sizes used in "no thumbnail" placeholder */
.thumb-font-0 {
.thumbnail-placeholder {
@media screen and (max-width: map-get($grid-breakpoints, lg)) {
font-size: 0.7rem;
padding: 0.2rem;
}
@media screen and (max-width: map-get($grid-breakpoints, sm)) {
font-size: 0.6rem;
padding: 0.1rem;
}
font-size: 0.4rem;
padding: 0.1rem;
}
}
.hide-placeholder-text {
.thumbnail-placeholder {
color: transparent !important;
}
}
.thumb-font-1 {
.thumbnail-placeholder {
@media screen and (max-width: map-get($grid-breakpoints, sm)) {
font-size: 0.9rem;
padding: 0.1rem;
}
@media screen and (max-width: 950px) {
font-size: 0.4rem;
padding: 0.1rem;
}
font-size: 0.6rem;
padding: 0.125rem;
}
}
.thumb-font-2 {
.thumbnail-placeholder {
font-size: 0.9rem;
padding: 0.125rem;
}
}
.thumb-font-3 {
.thumbnail-placeholder {
font-size: 1.25rem;
padding: 0.5rem;
}
}