Skip to content

Commit 0b202e8

Browse files
FrancescoMolinarovins01-4science
authored andcommitted
Merged in task/dspace-cris-2023_02_x/DSC-2309 (pull request DSpace#3136)
[DSC-2309] improve fallback mechanism for avatar Approved-by: Andrea Barbasso
2 parents 682b900 + 24d56b2 commit 0b202e8

8 files changed

Lines changed: 121 additions & 43 deletions

File tree

src/app/core/shared/image.utils.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {
2+
Observable,
3+
of,
4+
} from 'rxjs';
5+
import { map } from 'rxjs/operators';
6+
7+
export const getDefaultImageUrlByEntityType = (entityType: string): Observable<string> => {
8+
const fallbackImage = 'assets/images/file-placeholder.svg';
9+
10+
if (!entityType) {
11+
return of(fallbackImage);
12+
}
13+
14+
const defaultImage = `assets/images/${entityType.toLowerCase()}-placeholder.svg`;
15+
return checkImageExists(defaultImage).pipe(map((exists) => exists ? defaultImage : fallbackImage));
16+
};
17+
18+
const checkImageExists = (url: string): Observable<boolean> => {
19+
return new Observable<boolean>((observer) => {
20+
const img = new Image();
21+
22+
img.onload = () => {
23+
observer.next(true);
24+
observer.complete();
25+
};
26+
27+
img.onerror = () => {
28+
observer.next(false);
29+
observer.complete();
30+
};
31+
32+
img.src = url;
33+
});
34+
};
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<ds-thumbnail *ngIf="(initialized | async)" data-test="thumbnail"
22
[thumbnail]="thumbnail$ | async"
3-
[defaultImage]="default">
3+
[defaultImage]="default$ | async">
44
</ds-thumbnail>

src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/thumbnail/thumbnail.component.spec.ts

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,12 @@ describe('ThumbnailComponent', () => {
152152
expect(thumbnail).toBeTruthy();
153153
}));
154154

155-
it('should show default thumbnail', () => {
156-
expect(component.default).toBe('assets/images/file-placeholder.svg');
155+
it('should show default thumbnail', (done) => {
156+
component.default$.subscribe(image => {
157+
expect(image).toBe('assets/images/file-placeholder.svg');
158+
done();
159+
});
157160
});
158-
159161
});
160162

161163
describe('When bitstreams are only original', () => {
@@ -172,10 +174,12 @@ describe('ThumbnailComponent', () => {
172174
expect(thumbnail).toBeTruthy();
173175
}));
174176

175-
it('should show default thumbnail', () => {
176-
expect(component.default).toBe('assets/images/file-placeholder.svg');
177+
it('should show default thumbnail', (done) => {
178+
component.default$.subscribe(image => {
179+
expect(image).toBe('assets/images/file-placeholder.svg');
180+
done();
181+
});
177182
});
178-
179183
});
180184

181185
describe('When bitstreams are only thumbnail', () => {
@@ -239,10 +243,12 @@ describe('ThumbnailComponent', () => {
239243
expect(thumbnail).toBeTruthy();
240244
});
241245

242-
it('should show default thumbnail', () => {
243-
expect(component.default).toBe('assets/images/file-placeholder.svg');
246+
it('should show default thumbnail', (done) => {
247+
component.default$.subscribe(image => {
248+
expect(image).toBe('assets/images/file-placeholder.svg');
249+
done();
250+
});
244251
});
245-
246252
});
247253

248254
describe('When bitstreams are only original without the right metadata information', () => {
@@ -254,8 +260,11 @@ describe('ThumbnailComponent', () => {
254260
fixture.detectChanges();
255261
});
256262

257-
it('should not show bitstream content image src but the default image', () => {
258-
expect(component.default).toBe('assets/images/file-placeholder.svg');
263+
it('should not show bitstream content image src but the default image', (done) => {
264+
component.default$.subscribe(image => {
265+
expect(image).toBe('assets/images/file-placeholder.svg');
266+
done();
267+
});
259268
});
260269

261270
});
@@ -269,10 +278,12 @@ describe('ThumbnailComponent', () => {
269278
fixture.detectChanges();
270279
});
271280

272-
it('should not show thumbnail content image src but the default image', () => {
273-
expect(component.default).toBe('assets/images/file-placeholder.svg');
281+
it('should not show thumbnail content image src but the default image', (done) => {
282+
component.default$.subscribe(image => {
283+
expect(image).toBe('assets/images/file-placeholder.svg');
284+
done();
285+
});
274286
});
275-
276287
});
277288

278289
describe('When bitstreams are only original with the right metadata information', () => {
@@ -284,8 +295,11 @@ describe('ThumbnailComponent', () => {
284295
fixture.detectChanges();
285296
});
286297

287-
it('should not show thumbnail content image src but the default image', () => {
288-
expect(component.default).toBe('assets/images/file-placeholder.svg');
298+
it('should not show thumbnail content image src but the default image', (done) => {
299+
component.default$.subscribe(image => {
300+
expect(image).toBe('assets/images/file-placeholder.svg');
301+
done();
302+
});
289303
});
290304

291305
});

src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/thumbnail/thumbnail.component.ts

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { Component, Inject, OnInit } from '@angular/core';
22

3-
import { BehaviorSubject, of as observableOf } from 'rxjs';
3+
import { BehaviorSubject, Observable, combineLatest, of as observableOf } from 'rxjs';
44
import { map, switchMap } from 'rxjs/operators';
55
import { TranslateService } from '@ngx-translate/core';
66

77
import { FieldRenderingType, MetadataBoxFieldRendering } from '../metadata-box.decorator';
88
import { BitstreamDataService } from '../../../../../../../core/data/bitstream-data.service';
9-
import { hasValue, isEmpty, isNotEmpty } from '../../../../../../../shared/empty.util';
9+
import { isEmpty, isNotEmpty } from '../../../../../../../shared/empty.util';
1010
import { Bitstream } from '../../../../../../../core/shared/bitstream.model';
1111
import { BitstreamRenderingModelComponent } from '../bitstream-rendering-model';
1212
import { Item } from '../../../../../../../core/shared/item.model';
1313
import { LayoutField } from '../../../../../../../core/layout/models/box.model';
1414
import { getFirstCompletedRemoteData } from '../../../../../../../core/shared/operators';
1515
import { PaginatedList } from '../../../../../../../core/data/paginated-list.model';
16+
import { getDefaultImageUrlByEntityType } from '../../../../../../../core/shared/image.utils';
1617

1718
@Component({
1819
// eslint-disable-next-line @angular-eslint/component-selector
@@ -34,7 +35,7 @@ export class ThumbnailComponent extends BitstreamRenderingModelComponent impleme
3435
/**
3536
* Default image to be shown in the thumbnail
3637
*/
37-
default: string;
38+
default$: Observable<string>;
3839

3940
/**
4041
* Item rendering initialization state
@@ -61,9 +62,14 @@ export class ThumbnailComponent extends BitstreamRenderingModelComponent impleme
6162
* Get the thumbnail information from api for this item
6263
*/
6364
ngOnInit(): void {
64-
this.setDefaultImage();
65-
this.getBitstreamsByItem().pipe(
66-
map((bitstreamList: PaginatedList<Bitstream>) => bitstreamList.page),
65+
const eType = this.item.firstMetadataValue('dspace.entity.type');
66+
this.default$ = getDefaultImageUrlByEntityType(eType);
67+
68+
combineLatest([
69+
this.default$,
70+
this.getBitstreamsByItem(),
71+
]).pipe(
72+
map(([_, bitstreamList]: [string, PaginatedList<Bitstream>]) => bitstreamList.page),
6773
switchMap((filteredBitstreams: Bitstream[]) => {
6874
if (filteredBitstreams.length > 0) {
6975
if (isEmpty(filteredBitstreams[0].thumbnail)) {
@@ -91,19 +97,4 @@ export class ThumbnailComponent extends BitstreamRenderingModelComponent impleme
9197
this.initialized.next(true);
9298
});
9399
}
94-
95-
/**
96-
* Set the default image src depending on item entity type
97-
*/
98-
setDefaultImage(): void {
99-
const eType = this.item.firstMetadataValue('dspace.entity.type');
100-
this.default = 'assets/images/file-placeholder.svg';
101-
if (hasValue(eType) && eType.toUpperCase() === 'PROJECT') {
102-
this.default = 'assets/images/project-placeholder.svg';
103-
} else if (hasValue(eType) && eType.toUpperCase() === 'ORGUNIT') {
104-
this.default = 'assets/images/orgunit-placeholder.svg';
105-
} else if (hasValue(eType) && eType.toUpperCase() === 'PERSON') {
106-
this.default = 'assets/images/person-placeholder.svg';
107-
}
108-
}
109100
}

src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@
1010
<img *ngIf="src !== null" class="thumbnail-content img-fluid" [ngClass]="{'d-none': isLoading}"
1111
[src]="src | dsSafeUrl" [alt]="alt | translate" (error)="errorHandler()" (load)="successHandler()">
1212
<div *ngIf="src === null && !isLoading" class="thumbnail-content outer" #thumbnailBox>
13-
<img [src]="fallbackImage" [alt]="placeholder | translate">
13+
<img [src]="placeholderImageUrl$ | async" [alt]="placeholder | translate">
1414
</div>
1515
</div>

src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,30 @@ describe('MetadataLinkViewAvatarPopoverComponent', () => {
5757
it('should create', () => {
5858
expect(component).toBeTruthy();
5959
});
60+
61+
it('should set fallback image if no entity type', (done) => {
62+
component.ngOnInit();
63+
component.placeholderImageUrl$.subscribe((url) => {
64+
expect(url).toBe('assets/images/file-placeholder.svg');
65+
done();
66+
});
67+
});
68+
69+
it('should set correct placeholder image based on entity type if image exists', (done) => {
70+
component.entityType = 'OrgUnit';
71+
component.ngOnInit();
72+
component.placeholderImageUrl$.subscribe((url) => {
73+
expect(url).toBe('assets/images/orgunit-placeholder.svg');
74+
done();
75+
});
76+
});
77+
78+
it('should set correct fallback image if image does not exists', (done) => {
79+
component.entityType = 'missingEntityType';
80+
component.ngOnInit();
81+
component.placeholderImageUrl$.subscribe((url) => {
82+
expect(url).toBe('assets/images/file-placeholder.svg');
83+
done();
84+
});
85+
});
6086
});
Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
1-
import { Component } from '@angular/core';
1+
import { Component, Input, OnInit } from '@angular/core';
22
import { ThumbnailComponent } from 'src/app/thumbnail/thumbnail.component';
3+
import { getDefaultImageUrlByEntityType } from '../../../core/shared/image.utils';
4+
import { Observable } from 'rxjs';
35

46
@Component({
57
selector: 'ds-metadata-link-view-avatar-popover',
68
templateUrl: './metadata-link-view-avatar-popover.component.html',
79
styleUrls: ['./metadata-link-view-avatar-popover.component.scss']
810
})
9-
export class MetadataLinkViewAvatarPopoverComponent extends ThumbnailComponent {
11+
export class MetadataLinkViewAvatarPopoverComponent extends ThumbnailComponent implements OnInit {
12+
1013

1114
/**
12-
* The fallback image to use when the thumbnail is not available
15+
* Placeholder image url that changes based on entity type
1316
*/
14-
fallbackImage = 'assets/images/person-placeholder.svg';
17+
placeholderImageUrl$: Observable<string>;
18+
19+
/**
20+
* The entity type of the item which the avatar belong
21+
*/
22+
@Input() entityType: string;
23+
24+
ngOnInit() {
25+
this.placeholderImageUrl$ = getDefaultImageUrlByEntityType(this.entityType);
26+
}
1527
}

src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<ds-metadata-link-view-avatar-popover
55
*ngIf="item.thumbnail | async"
66
[thumbnail]="item.thumbnail | async"
7+
[entityType]="item.entityType"
78
></ds-metadata-link-view-avatar-popover>
89
<span class="font-weight-bold h4"> {{item.firstMetadataValue('dc.title')}} </span>
910
</div>

0 commit comments

Comments
 (0)