-
Notifications
You must be signed in to change notification settings - Fork 567
Expand file tree
/
Copy paththumbnail.component.ts
More file actions
242 lines (220 loc) · 7.17 KB
/
Copy paththumbnail.component.ts
File metadata and controls
242 lines (220 loc) · 7.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
import { isPlatformBrowser } from '@angular/common';
import {
Component,
Inject,
Input,
OnChanges,
OnInit,
PLATFORM_ID,
signal,
SimpleChanges,
WritableSignal,
} from '@angular/core';
import { AuthService } from '@dspace/core/auth/auth.service';
import { AuthorizationDataService } from '@dspace/core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '@dspace/core/data/feature-authorization/feature-id';
import { RemoteData } from '@dspace/core/data/remote-data';
import { Bitstream } from '@dspace/core/shared/bitstream.model';
import { FileService } from '@dspace/core/shared/file.service';
import {
hasNoValue,
hasValue,
} from '@dspace/shared/utils/empty.util';
import { TranslatePipe } from '@ngx-translate/core';
import { of } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { ThemedLoadingComponent } from '../shared/loading/themed-loading.component';
import { SafeUrlPipe } from '../shared/utils/safe-url-pipe';
/**
* This component renders a given Bitstream as a thumbnail.
* One input parameter of type Bitstream is expected.
* If no Bitstream is provided, an HTML placeholder will be rendered instead.
*/
@Component({
selector: 'ds-base-thumbnail',
styleUrls: ['./thumbnail.component.scss'],
templateUrl: './thumbnail.component.html',
imports: [
SafeUrlPipe,
ThemedLoadingComponent,
TranslatePipe,
],
})
export class ThumbnailComponent implements OnInit, OnChanges {
/**
* The thumbnail Bitstream
*/
@Input() thumbnail: Bitstream | RemoteData<Bitstream>;
/**
* Variable that listens to the thumbnail value
*/
listenThumbnail: RemoteData<Bitstream>;
/**
* 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.
*/
@Input() defaultImage? = null;
/**
* The src attribute used in the template to render the image.
*/
src: WritableSignal<string> = signal(undefined);
retriedWithToken = false;
/**
* i18n key of thumbnail alt text
*/
@Input() alt? = 'thumbnail.default.alt';
/**
* Custom thumbnail description for alt text
*/
customDescription: string;
/**
* i18n key of HTML placeholder text
*/
@Input() placeholder? = 'thumbnail.default.placeholder';
/**
* Limit thumbnail width to --ds-thumbnail-max-width
*/
@Input() limitWidth? = true;
/**
* Whether the thumbnail is currently loading
* Start out as true to avoid flashing the alt text while a thumbnail is being loaded.
*/
isLoading: WritableSignal<boolean> = signal(true);
constructor(
@Inject(PLATFORM_ID) private platformID: any,
protected auth: AuthService,
protected authorizationService: AuthorizationDataService,
protected fileService: FileService,
) {
}
/**
* Getting the description from the thumbnail file
* when rendering the screen.
*/
ngOnInit(): void{
if (this.thumbnail){
if ('payload' in this.thumbnail) {
this.listenThumbnail = this.thumbnail;
const bitstream = this.listenThumbnail.payload;
if (bitstream && bitstream.hasMetadata('dc.description')) {
this.customDescription = bitstream.firstMetadataValue('dc.description');
}
}
}
}
/**
* Resolve the thumbnail.
* Use a default image if no actual image is available.
*/
ngOnChanges(changes: SimpleChanges): void {
if (isPlatformBrowser(this.platformID)) {
if (hasNoValue(this.thumbnail)) {
this.setSrc(this.defaultImage);
return;
}
const src = this.contentHref;
if (hasValue(src)) {
this.setSrc(src);
} else {
this.setSrc(this.defaultImage);
}
}
}
/**
* The current thumbnail Bitstream
* @private
*/
private get bitstream(): Bitstream {
if (this.thumbnail instanceof Bitstream) {
return this.thumbnail as Bitstream;
} else if (this.thumbnail instanceof RemoteData) {
return (this.thumbnail as RemoteData<Bitstream>).payload;
}
}
private get contentHref(): string | undefined {
if (this.thumbnail instanceof Bitstream) {
return this.thumbnail?._links?.content?.href;
} else if (this.thumbnail instanceof RemoteData) {
return this.thumbnail?.payload?._links?.content?.href;
}
}
/**
* Handle image download errors.
* If the image can't be loaded, try re-requesting it with an authorization token in case it's a restricted Bitstream
* Otherwise, fall back to the default image or a HTML placeholder
*/
errorHandler() {
const src = this.src();
const thumbnail = this.bitstream;
const thumbnailSrc = thumbnail?._links?.content?.href;
if (!this.retriedWithToken && hasValue(thumbnailSrc) && src === thumbnailSrc) {
// the thumbnail may have failed to load because it's restricted
// → retry with an authorization token
// only do this once; fall back to the default if it still fails
this.retriedWithToken = true;
this.auth.isAuthenticated().pipe(
switchMap((isLoggedIn) => {
if (isLoggedIn) {
return this.authorizationService.isAuthorized(FeatureID.CanDownload, thumbnail.self);
} else {
return of(false);
}
}),
switchMap((isAuthorized) => {
if (isAuthorized) {
return this.fileService.retrieveFileDownloadLink(thumbnailSrc);
} else {
return of(null);
}
}),
).subscribe((url: string) => {
if (hasValue(url)) {
// If we got a URL, try to load it
// (if it still fails this method will be called again, and we'll fall back to the default)
// Otherwise, fall back to the default image right now
this.setSrc(url);
} else {
this.setSrc(this.defaultImage);
}
});
} else {
if (src !== this.defaultImage) {
// we failed to get thumbnail (possibly retried with a token but failed again)
this.setSrc(this.defaultImage);
} else {
// we have failed to retrieve the default image, fall back to the placeholder
this.setSrc(null);
}
}
}
/**
* Set the thumbnail.
* Stop the loading animation if setting to null.
* @param src
*/
setSrc(src: string): void {
// only update the src if it has changed (the parent component may fire the same one multiple times
if (this.src() !== src) {
// every time the src changes we need to start the loading animation again, as it's possible
// that it is first set to null when the parent component initializes and then set to
// the actual value
//
// isLoading$ will be set to false by the error or success handler afterwards, except in the
// case where src is null, then we have to set it manually here (because those handlers won't
// trigger)
if (src !== null && this.isLoading() === false) {
this.isLoading.set(true);
}
this.src.set(src);
if (src === null && this.isLoading() === true) {
this.isLoading.set(false);
}
}
}
/**
* Stop the loading animation once the thumbnail is successfully loaded
*/
successHandler() {
this.isLoading.set(false);
}
}