fix issue where thumnails of embargoed bitstreams wouldn't show up for users with access rights

This commit is contained in:
Art Lowel
2025-04-25 14:55:52 +02:00
parent d31e17894c
commit 11f251755b
3 changed files with 47 additions and 36 deletions

View File

@@ -1,15 +1,15 @@
<div class="thumbnail" [class.limit-width]="limitWidth">
<div *ngIf="isLoading" class="thumbnail-content outer">
<div *ngIf="(isLoading$ | async)" class="thumbnail-content outer">
<div class="inner">
<div class="centered">
<ds-themed-loading [spinner]="true"></ds-themed-loading>
</div>
</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}"
[src]="src | dsSafeUrl" [alt]="alt | translate" (error)="errorHandler()" (load)="successHandler()">
<div *ngIf="src === null && !isLoading" class="thumbnail-content outer">
<!-- don't use *ngIf="!(isLoading$ | async)" so the thumbnail can load in while the animation is playing -->
<img *ngIf="(src$ | async) !== null" class="thumbnail-content img-fluid" [ngClass]="{'d-none': (isLoading$ | async)}"
[src]="(src$ | async) | dsSafeUrl" [alt]="alt | translate" (error)="errorHandler()" (load)="successHandler()">
<div *ngIf="(src$ | async) === null && !(isLoading$ | async)" class="thumbnail-content outer">
<div class="inner">
<div class="thumbnail-placeholder centered lead">
{{ placeholder | translate }}

View File

@@ -67,31 +67,31 @@ describe('ThumbnailComponent', () => {
describe('loading', () => {
it('should start out with isLoading$ true', () => {
expect(comp.isLoading).toBeTrue();
expect(comp.isLoading$.getValue()).toBeTrue();
});
it('should set isLoading$ to false once an image is successfully loaded', () => {
comp.setSrc('http://bit.stream');
fixture.debugElement.query(By.css('img.thumbnail-content')).triggerEventHandler('load', new Event('load'));
expect(comp.isLoading).toBeFalse();
expect(comp.isLoading$.getValue()).toBeFalse();
});
it('should set isLoading$ to false once the src is set to null', () => {
comp.setSrc(null);
expect(comp.isLoading).toBeFalse();
expect(comp.isLoading$.getValue()).toBeFalse();
});
it('should show a loading animation while isLoading$ is true', () => {
expect(de.query(By.css('ds-themed-loading'))).toBeTruthy();
comp.isLoading = false;
comp.isLoading$.next(false);
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('ds-themed-loading'))).toBeFalsy();
});
describe('with a thumbnail image', () => {
beforeEach(() => {
comp.src = 'https://bit.stream';
comp.src$.next('https://bit.stream');
fixture.detectChanges();
});
@@ -100,7 +100,7 @@ describe('ThumbnailComponent', () => {
expect(img).toBeTruthy();
expect(img.classes['d-none']).toBeTrue();
comp.isLoading = false;
comp.isLoading$.next(false);
fixture.detectChanges();
img = fixture.debugElement.query(By.css('img.thumbnail-content'));
expect(img).toBeTruthy();
@@ -111,14 +111,14 @@ describe('ThumbnailComponent', () => {
describe('without a thumbnail image', () => {
beforeEach(() => {
comp.src = null;
comp.src$.next(null);
fixture.detectChanges();
});
it('should only show the HTML placeholder once done loading', () => {
expect(fixture.debugElement.query(By.css('div.thumbnail-placeholder'))).toBeFalsy();
comp.isLoading = false;
comp.isLoading$.next(false);
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('div.thumbnail-placeholder'))).toBeTruthy();
});
@@ -214,14 +214,14 @@ describe('ThumbnailComponent', () => {
describe('fallback', () => {
describe('if there is a default image', () => {
it('should display the default image', () => {
comp.src = 'http://bit.stream';
comp.src$.next('http://bit.stream');
comp.defaultImage = 'http://default.img';
comp.errorHandler();
expect(comp.src).toBe(comp.defaultImage);
expect(comp.src$.getValue()).toBe(comp.defaultImage);
});
it('should include the alt text', () => {
comp.src = 'http://bit.stream';
comp.src$.next('http://bit.stream');
comp.defaultImage = 'http://default.img';
comp.errorHandler();
@@ -233,10 +233,10 @@ describe('ThumbnailComponent', () => {
describe('if there is no default image', () => {
it('should display the HTML placeholder', () => {
comp.src = 'http://default.img';
comp.src$.next('http://default.img');
comp.defaultImage = null;
comp.errorHandler();
expect(comp.src).toBe(null);
expect(comp.src$.getValue()).toBe(null);
fixture.detectChanges();
const placeholder = fixture.debugElement.query(By.css('div.thumbnail-placeholder')).nativeElement;
@@ -328,7 +328,7 @@ describe('ThumbnailComponent', () => {
it('should show the default image', () => {
comp.defaultImage = 'default/image.jpg';
comp.ngOnChanges({});
expect(comp.src).toBe('default/image.jpg');
expect(comp.src$.getValue()).toBe('default/image.jpg');
});
});
});

View File

@@ -1,13 +1,14 @@
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Component, Inject, Input, OnChanges, PLATFORM_ID, SimpleChanges } from '@angular/core';
import { Bitstream } from '../core/shared/bitstream.model';
import { hasNoValue, hasValue } from '../shared/empty.util';
import { RemoteData } from '../core/data/remote-data';
import { of as observableOf } from 'rxjs';
import { of as observableOf, BehaviorSubject } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { FeatureID } from '../core/data/feature-authorization/feature-id';
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
import { AuthService } from '../core/auth/auth.service';
import { FileService } from '../core/shared/file.service';
import { isPlatformBrowser } from '@angular/common';
/**
* This component renders a given Bitstream as a thumbnail.
@@ -34,7 +35,7 @@ export class ThumbnailComponent implements OnChanges {
/**
* The src attribute used in the template to render the image.
*/
src: string = undefined;
src$: BehaviorSubject<string> = new BehaviorSubject<string>(undefined);
retriedWithToken = false;
@@ -57,9 +58,10 @@ export class ThumbnailComponent implements OnChanges {
* Whether the thumbnail is currently loading
* Start out as true to avoid flashing the alt text while a thumbnail is being loaded.
*/
isLoading = true;
isLoading$: BehaviorSubject<boolean> = new BehaviorSubject(true);
constructor(
@Inject(PLATFORM_ID) private platformID: any,
protected auth: AuthService,
protected authorizationService: AuthorizationDataService,
protected fileService: FileService,
@@ -71,6 +73,14 @@ export class ThumbnailComponent implements OnChanges {
* Use a default image if no actual image is available.
*/
ngOnChanges(changes: SimpleChanges): void {
if (isPlatformBrowser(this.platformID)) {
// every time the inputs change we need to start the loading animation again, as it's possible
// that thumbnail is first set to null when the parent component initializes and then set to
// the actual value
if (this.isLoading$.getValue() === false) {
this.isLoading$.next(true);
}
if (hasNoValue(this.thumbnail)) {
this.setSrc(this.defaultImage);
return;
@@ -83,6 +93,7 @@ export class ThumbnailComponent implements OnChanges {
this.setSrc(this.defaultImage);
}
}
}
/**
* The current thumbnail Bitstream
@@ -110,7 +121,7 @@ export class ThumbnailComponent implements OnChanges {
* Otherwise, fall back to the default image or a HTML placeholder
*/
errorHandler() {
const src = this.src;
const src = this.src$.getValue();
const thumbnail = this.bitstream;
const thumbnailSrc = thumbnail?._links?.content?.href;
@@ -162,9 +173,9 @@ export class ThumbnailComponent implements OnChanges {
* @param src
*/
setSrc(src: string): void {
this.src = src;
this.src$.next(src);
if (src === null) {
this.isLoading = false;
this.isLoading$.next(false);
}
}
@@ -172,6 +183,6 @@ export class ThumbnailComponent implements OnChanges {
* Stop the loading animation once the thumbnail is successfully loaded
*/
successHandler() {
this.isLoading = false;
this.isLoading$.next(false);
}
}