Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ describe('JournalComponent', () => {
getThumbnailFor(item: Item): Observable<RemoteData<Bitstream>> {
return createSuccessfulRemoteDataObject$(new Bitstream());
},
findPrimaryBitstreamByItemAndName(): Observable<RemoteData<Bitstream>> {
return createSuccessfulRemoteDataObject$(new Bitstream());
},
};
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<div class="col-xs-12 col-md-4">
<ng-container *ngIf="!(mediaViewer.image || mediaViewer.video)">
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
<ds-thumbnail [thumbnail]="object?.thumbnail | async" [link]="thumbnailLink$ | async"></ds-thumbnail>
</ds-metadata-field-wrapper>
</ng-container>
<div *ngIf="mediaViewer.image || mediaViewer.video" class="mb-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ describe('PublicationComponent', () => {
getThumbnailFor(item: Item): Observable<RemoteData<Bitstream>> {
return createSuccessfulRemoteDataObject$(new Bitstream());
},
findPrimaryBitstreamByItemAndName(): Observable<RemoteData<Bitstream>> {
return createSuccessfulRemoteDataObject$(new Bitstream());
},
};
TestBed.configureTestingModule({
imports: [
Expand Down
103 changes: 102 additions & 1 deletion src/app/item-page/simple/item-types/shared/item.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
import {
Observable,
of as observableOf,
of,
throwError,
} from 'rxjs';

import { APP_CONFIG } from '../../../../../config/app-config.interface';
Expand Down Expand Up @@ -128,6 +130,9 @@ export function getItemPageFieldsTest(mockItem: Item, component) {
getThumbnailFor(item: Item): Observable<RemoteData<Bitstream>> {
return createSuccessfulRemoteDataObject$(new Bitstream());
},
findPrimaryBitstreamByItemAndName(): Observable<RemoteData<Bitstream>> {
return createSuccessfulRemoteDataObject$(new Bitstream());
},
};

const authorizationService = jasmine.createSpyObj('authorizationService', {
Expand Down Expand Up @@ -484,6 +489,15 @@ describe('ItemComponent', () => {
const recentSubmissionsUrl = '/collections/be7b8430-77a5-4016-91c9-90863e50583a?cp.page=3';

beforeEach(waitForAsync(() => {
const mockBitstreamDataService = {
getThumbnailFor(item: Item): Observable<RemoteData<Bitstream>> {
return createSuccessfulRemoteDataObject$(new Bitstream());
},
findPrimaryBitstreamByItemAndName(): Observable<RemoteData<Bitstream>> {
return createSuccessfulRemoteDataObject$(new Bitstream());
},
};

TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot({
Expand Down Expand Up @@ -511,7 +525,7 @@ describe('ItemComponent', () => {
{ provide: VersionDataService, useValue: {} },
{ provide: NotificationsService, useValue: {} },
{ provide: DefaultChangeAnalyzer, useValue: {} },
{ provide: BitstreamDataService, useValue: {} },
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
{ provide: WorkspaceitemDataService, useValue: {} },
{ provide: SearchService, useValue: {} },
{ provide: RouteService, useValue: mockRouteService },
Expand Down Expand Up @@ -564,4 +578,91 @@ describe('ItemComponent', () => {
});
});

describe('when calling getThumbnailLink ', () => {
let component: ItemComponent;
let bitstreamDataService: jasmine.SpyObj<BitstreamDataService>;
let router: jasmine.SpyObj<Router>;
let routeService: jasmine.SpyObj<RouteService>;

beforeEach(() => {
bitstreamDataService = jasmine.createSpyObj('BitstreamDataService', [
'findPrimaryBitstreamByItemAndName',
'findAllByItemAndBundleName',
]);
router = jasmine.createSpyObj('Router', ['navigate']);
routeService = jasmine.createSpyObj('RouteService', ['getRoute']);

component = new ItemComponent(routeService, router, bitstreamDataService);
});

it('should return the primary bitstream link if available', (done) => {
const item = new Item();
const primaryBitstream = new Bitstream();
primaryBitstream._links = {
self: { href: 'self-link' },
bundle: { href: 'bundle-link' },
format: { href: 'format-link' },
content: { href: 'primary-link' },
thumbnail: { href: 'thumbnail-link' },
};
const remotePaginatedListBitstream = createSuccessfulRemoteDataObject$(createPaginatedList([primaryBitstream]));
bitstreamDataService.findPrimaryBitstreamByItemAndName.and.returnValue(of(primaryBitstream));

component.getThumbnailLink(item).subscribe(link => {
expect(link).toBe('primary-link');
done();
});
});

it('should return the first bitstream link if no primary bitstream is available', (done) => {
const item = new Item();
const primaryBitstream = new Bitstream();
primaryBitstream._links = {
self: { href: 'self-link' },
bundle: { href: 'bundle-link' },
format: { href: 'format-link' },
content: { href: 'primary-link' },
thumbnail: { href: 'thumbnail-link' },
};
const remotePaginatedListBitstream = createSuccessfulRemoteDataObject$(createPaginatedList([primaryBitstream]));
bitstreamDataService.findPrimaryBitstreamByItemAndName.and.returnValue(of(null));
bitstreamDataService.findAllByItemAndBundleName.and.returnValue(remotePaginatedListBitstream);

component.getThumbnailLink(item).subscribe(link => {
expect(link).toBe('primary-link');
done();
});
});

it('should return an empty string if no bitstream is available', (done) => {
const item = new Item();
const primaryBitstream = new Bitstream();
primaryBitstream._links = {
self: { href: 'self-link' },
bundle: { href: 'bundle-link' },
format: { href: 'format-link' },
content: { href: 'primary-link' },
thumbnail: { href: 'thumbnail-link' },
};
const remotePaginatedListBitstream = createSuccessfulRemoteDataObject$(createPaginatedList([]));
bitstreamDataService.findPrimaryBitstreamByItemAndName.and.returnValue(of(null));
bitstreamDataService.findAllByItemAndBundleName.and.returnValue(remotePaginatedListBitstream);

component.getThumbnailLink(item).subscribe(link => {
expect(link).toBe('');
done();
});
});

it('should return an empty string if an error occurs', (done) => {
const item = new Item();
bitstreamDataService.findPrimaryBitstreamByItemAndName.and.returnValue(throwError(() => new Error('Network error')));

component.getThumbnailLink(item).subscribe(link => {
expect(link).toBe('');
done();
});
});
});

});
41 changes: 38 additions & 3 deletions src/app/item-page/simple/item-types/shared/item.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@ import {
OnInit,
} from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs';
import {
Observable,
of,
} from 'rxjs';
import {
catchError,
map,
switchMap,
take,
} from 'rxjs/operators';
import { BitstreamDataService } from 'src/app/core/data/bitstream-data.service';
import { Bitstream } from 'src/app/core/shared/bitstream.model';
import { getFirstCompletedRemoteData } from 'src/app/core/shared/operators';

import { environment } from '../../../../../environments/environment';
import { RouteService } from '../../../../core/services/route.service';
Expand Down Expand Up @@ -75,8 +83,11 @@ export class ItemComponent implements OnInit {

mediaViewer;

thumbnailLink$: Observable<string>;

constructor(protected routeService: RouteService,
protected router: Router) {
protected router: Router,
private bitstreamDataService: BitstreamDataService) {
this.mediaViewer = environment.mediaViewer;
}

Expand All @@ -94,7 +105,6 @@ export class ItemComponent implements OnInit {
};

ngOnInit(): void {

this.itemPageRoute = getItemPageRoute(this.object);
// hide/show the back button
this.showBackButton$ = this.routeService.getPreviousUrl().pipe(
Expand All @@ -107,5 +117,30 @@ export class ItemComponent implements OnInit {
if (this.iiifSearchEnabled) {
this.iiifQuery$ = getDSpaceQuery(this.object, this.routeService);
}
this.thumbnailLink$ = this.getThumbnailLink(this.object);
}

/**
* Get item's primary bitstream link or item's first bitstream link if there's no primary associated
*/
getThumbnailLink(item: Item): Observable<string> {
return this.bitstreamDataService.findPrimaryBitstreamByItemAndName(item, 'ORIGINAL', true, true).pipe(
switchMap((primaryBitstream: Bitstream | null) => {
if (primaryBitstream) {
return of(primaryBitstream._links.content.href);
}
return this.bitstreamDataService.findAllByItemAndBundleName(item, 'ORIGINAL', {}, true, true).pipe(
getFirstCompletedRemoteData(),
map((bitstreams) => {
const bitstreamList = bitstreams.payload.page;
return (bitstreamList && bitstreamList.length > 0) ? bitstreamList[0]._links.content.href : '';
}),
);
}),
catchError((error: unknown) => {
console.error('Error fetching thumbnail link:', error);
return of('');
}),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<div class="col-xs-12 col-md-4">
<ng-container *ngIf="!(mediaViewer.image || mediaViewer.video)">
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
<ds-thumbnail [thumbnail]="object?.thumbnail | async" [link]="thumbnailLink$ | async"></ds-thumbnail>
</ds-metadata-field-wrapper>
</ng-container>
<div *ngIf="mediaViewer.image || mediaViewer.video" class="mb-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ describe('UntypedItemComponent', () => {
getThumbnailFor(item: Item): Observable<RemoteData<Bitstream>> {
return createSuccessfulRemoteDataObject$(new Bitstream());
},
findPrimaryBitstreamByItemAndName(): Observable<RemoteData<Bitstream>> {
return createSuccessfulRemoteDataObject$(new Bitstream());
},
};
TestBed.configureTestingModule({
imports: [
Expand Down
3 changes: 3 additions & 0 deletions src/app/thumbnail/themed-thumbnail.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export class ThemedThumbnailComponent extends ThemedComponent<ThumbnailComponent

@Input() thumbnail: Bitstream | RemoteData<Bitstream>;

@Input() link: string = undefined;

@Input() defaultImage?: string | null;

@Input() alt?: string;
Expand All @@ -29,6 +31,7 @@ export class ThemedThumbnailComponent extends ThemedComponent<ThumbnailComponent

protected inAndOutputNames: (keyof ThumbnailComponent & keyof this)[] = [
'thumbnail',
'link',
'defaultImage',
'alt',
'placeholder',
Expand Down
6 changes: 5 additions & 1 deletion src/app/thumbnail/thumbnail.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
</div>
</div>
<!-- don't use *ngIf="!isLoading" so the thumbnail can load in while the animation is playing -->
<img *ngIf="src !== null" class="thumbnail-content img-fluid" [ngClass]="{'d-none': isLoading}"
<a *ngIf="src !== null && link" target="_blank" [href]="link">
<img *ngIf="src !== null" class="thumbnail-content img-fluid" [ngClass]="{'d-none': isLoading}"
[src]="src | dsSafeUrl" [alt]="alt | translate" (error)="errorHandler()" (load)="successHandler()">
</a>
<img *ngIf="src !== null && !link" class="thumbnail-content img-fluid" [ngClass]="{'d-none': isLoading}"
[src]="src | dsSafeUrl" [alt]="alt | translate" (error)="errorHandler()" (load)="successHandler()">
<div *ngIf="src === null && !isLoading" class="thumbnail-content outer">
<div class="inner">
Expand Down
5 changes: 5 additions & 0 deletions src/app/thumbnail/thumbnail.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export class ThumbnailComponent implements OnChanges {
*/
@Input() thumbnail: Bitstream | RemoteData<Bitstream>;

/**
* A link to open when a user clicks on thumbnail image
*/
@Input() link: string = undefined;

/**
* The default image, used if the thumbnail isn't set or can't be downloaded.
* If defaultImage is null, a HTML placeholder is used instead.
Expand Down
Loading