Skip to content

Commit

Permalink
added main link method on item component to use in thumbnails.
Browse files Browse the repository at this point in the history
  • Loading branch information
oscar-escire committed Jan 30, 2025
1 parent d70fa8f commit cc5ac75
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 7 deletions.
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();
});
});
});

});
38 changes: 35 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,9 +4,11 @@ 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';

Expand All @@ -20,6 +22,9 @@ import {
isIiifEnabled,
isIiifSearchEnabled,
} from './item-iiif-utils';
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';

@Component({
selector: 'ds-item',
Expand Down Expand Up @@ -75,8 +80,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 +102,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 +114,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 => {
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

0 comments on commit cc5ac75

Please sign in to comment.