Skip to content

Commit 73bd0d6

Browse files
authored
Merge pull request #4249 from atmire/fix-embargoed-thumbnails_contribute-9.0
Show restricted thumbnails to users with access rights
2 parents dec05e5 + bec91c5 commit 73bd0d6

File tree

6 files changed

+82
-49
lines changed

6 files changed

+82
-49
lines changed

cypress/e2e/homepage.cy.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ describe('Homepage', () => {
2626
// Wait for homepage tag to appear
2727
cy.get('ds-home-page').should('be.visible');
2828

29+
// Wait for at least one loading component to show up
30+
cy.get('ds-loading').should('exist');
31+
32+
// Wait until all loading components have disappeared
33+
cy.get('ds-loading').should('not.exist');
34+
2935
// Analyze <ds-home-page> for accessibility issues
3036
testA11y('ds-home-page');
3137
});

src/app/item-page/media-viewer/media-viewer.component.html

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[showMessage]="false"
66
></ds-loading>
77
}
8-
@if (!isLoading) {
8+
@else {
99
<div class="media-viewer">
1010
@if (mediaList.length > 0) {
1111
<ng-container *ngVar="mediaOptions.video && ['audio', 'video'].includes(mediaList[0]?.format) as showVideo">
@@ -33,17 +33,9 @@
3333
</ng-container>
3434
</ng-container>
3535
} @else {
36-
@if (mediaOptions.image && mediaOptions.video) {
37-
<ds-media-viewer-image
38-
[image]="(thumbnailsRD$ | async)?.payload?.page[0]?._links.content.href || thumbnailPlaceholder"
39-
[preview]="false"
40-
></ds-media-viewer-image>
41-
}
42-
@if (!(mediaOptions.image && mediaOptions.video)) {
43-
<ds-thumbnail
44-
[thumbnail]="(thumbnailsRD$ | async)?.payload?.page[0]">
45-
</ds-thumbnail>
46-
}
36+
<ds-thumbnail
37+
[thumbnail]="(thumbnailsRD$ | async)?.payload?.page[0]">
38+
</ds-thumbnail>
4739
}
4840
</div>
4941
}

src/app/item-page/media-viewer/media-viewer.component.spec.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { NO_ERRORS_SCHEMA } from '@angular/core';
1+
import {
2+
NO_ERRORS_SCHEMA,
3+
PLATFORM_ID,
4+
} from '@angular/core';
25
import {
36
ComponentFixture,
47
TestBed,
@@ -15,7 +18,9 @@ import { of as observableOf } from 'rxjs';
1518

1619
import { AuthService } from '../../core/auth/auth.service';
1720
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
21+
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
1822
import { Bitstream } from '../../core/shared/bitstream.model';
23+
import { FileService } from '../../core/shared/file.service';
1924
import { MediaViewerItem } from '../../core/shared/media-viewer-item.model';
2025
import { MetadataFieldWrapperComponent } from '../../shared/metadata-field-wrapper/metadata-field-wrapper.component';
2126
import { AuthServiceMock } from '../../shared/mocks/auth.service.mock';
@@ -33,6 +38,9 @@ import { MediaViewerComponent } from './media-viewer.component';
3338
describe('MediaViewerComponent', () => {
3439
let comp: MediaViewerComponent;
3540
let fixture: ComponentFixture<MediaViewerComponent>;
41+
let authService;
42+
let authorizationService;
43+
let fileService;
3644

3745
const mockBitstream: Bitstream = Object.assign(new Bitstream(), {
3846
sizeBytes: 10201,
@@ -57,7 +65,7 @@ describe('MediaViewerComponent', () => {
5765
'dc.title': [
5866
{
5967
language: null,
60-
value: 'test_word.docx',
68+
value: 'test_image.jpg',
6169
},
6270
],
6371
},
@@ -75,6 +83,15 @@ describe('MediaViewerComponent', () => {
7583
);
7684

7785
beforeEach(waitForAsync(() => {
86+
authService = jasmine.createSpyObj('AuthService', {
87+
isAuthenticated: observableOf(true),
88+
});
89+
authorizationService = jasmine.createSpyObj('AuthorizationService', {
90+
isAuthorized: observableOf(true),
91+
});
92+
fileService = jasmine.createSpyObj('FileService', {
93+
retrieveFileDownloadLink: null,
94+
});
7895
return TestBed.configureTestingModule({
7996
imports: [
8097
TranslateModule.forRoot({
@@ -90,6 +107,10 @@ describe('MediaViewerComponent', () => {
90107
MetadataFieldWrapperComponent,
91108
],
92109
providers: [
110+
{ provide: AuthService, useValue: authService },
111+
{ provide: AuthorizationDataService, useValue: authorizationService },
112+
{ provide: FileService, useValue: fileService },
113+
{ provide: PLATFORM_ID, useValue: 'browser' },
93114
{ provide: BitstreamDataService, useValue: bitstreamDataService },
94115
{ provide: ThemeService, useValue: getMockThemeService() },
95116
{ provide: AuthService, useValue: new AuthServiceMock() },
@@ -153,9 +174,9 @@ describe('MediaViewerComponent', () => {
153174
expect(mediaItem.thumbnail).toBe(null);
154175
});
155176

156-
it('should display a default, thumbnail', () => {
177+
it('should display a default thumbnail', () => {
157178
const defaultThumbnail = fixture.debugElement.query(
158-
By.css('ds-media-viewer-image'),
179+
By.css('ds-thumbnail'),
159180
);
160181
expect(defaultThumbnail.nativeElement).toBeDefined();
161182
});

src/app/thumbnail/thumbnail.component.html

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="thumbnail" [class.limit-width]="limitWidth">
2-
@if (isLoading) {
2+
@if (isLoading()) {
33
<div class="thumbnail-content outer">
44
<div class="inner">
55
<div class="centered">
@@ -9,16 +9,16 @@
99
</div>
1010
}
1111
<!-- don't use *ngIf="!isLoading" so the thumbnail can load in while the animation is playing -->
12-
@if (src !== null) {
12+
@if (src() !== null) {
1313
<img
1414
class="thumbnail-content img-fluid"
15-
[ngClass]="{ 'd-none': isLoading }"
16-
[src]="src | dsSafeUrl"
17-
[alt]="alt | translate"
18-
(error)="errorHandler()"
19-
(load)="successHandler()"
15+
[ngClass]="{ 'd-none': isLoading ()}"
16+
[src]="src() | dsSafeUrl"
17+
[alt]="alt | translate"
18+
(error)="errorHandler()"
19+
(load)="successHandler()"
2020
/>
21-
} @if (src === null && !isLoading) {
21+
} @if (src() === null && isLoading() === false) {
2222
<div class="thumbnail-content outer">
2323
<div class="inner">
2424
<div class="thumbnail-placeholder centered">

src/app/thumbnail/thumbnail.component.spec.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -99,32 +99,32 @@ describe('ThumbnailComponent', () => {
9999
});
100100

101101
describe('loading', () => {
102-
it('should start out with isLoading$ true', () => {
103-
expect(comp.isLoading).toBeTrue();
102+
it('should start out with isLoading true', () => {
103+
expect(comp.isLoading()).toBeTrue();
104104
});
105105

106106
it('should set isLoading$ to false once an image is successfully loaded', () => {
107107
comp.setSrc('http://bit.stream');
108108
fixture.debugElement.query(By.css('img.thumbnail-content')).triggerEventHandler('load', new Event('load'));
109-
expect(comp.isLoading).toBeFalse();
109+
expect(comp.isLoading()).toBeFalse();
110110
});
111111

112112
it('should set isLoading$ to false once the src is set to null', () => {
113113
comp.setSrc(null);
114-
expect(comp.isLoading).toBeFalse();
114+
expect(comp.isLoading()).toBeFalse();
115115
});
116116

117117
it('should show a loading animation while isLoading$ is true', () => {
118118
expect(de.query(By.css('ds-loading'))).toBeTruthy();
119119

120-
comp.isLoading = false;
120+
comp.isLoading.set(false);
121121
fixture.detectChanges();
122122
expect(fixture.debugElement.query(By.css('ds-loading'))).toBeFalsy();
123123
});
124124

125125
describe('with a thumbnail image', () => {
126126
beforeEach(() => {
127-
comp.src = 'https://bit.stream';
127+
comp.src.set('https://bit.stream');
128128
fixture.detectChanges();
129129
});
130130

@@ -133,7 +133,7 @@ describe('ThumbnailComponent', () => {
133133
expect(img).toBeTruthy();
134134
expect(img.classes['d-none']).toBeTrue();
135135

136-
comp.isLoading = false;
136+
comp.isLoading.set(false);
137137
fixture.detectChanges();
138138
img = fixture.debugElement.query(By.css('img.thumbnail-content'));
139139
expect(img).toBeTruthy();
@@ -144,14 +144,14 @@ describe('ThumbnailComponent', () => {
144144

145145
describe('without a thumbnail image', () => {
146146
beforeEach(() => {
147-
comp.src = null;
147+
comp.src.set(null);
148148
fixture.detectChanges();
149149
});
150150

151151
it('should only show the HTML placeholder once done loading', () => {
152152
expect(fixture.debugElement.query(By.css('div.thumbnail-placeholder'))).toBeFalsy();
153153

154-
comp.isLoading = false;
154+
comp.isLoading.set(false);
155155
fixture.detectChanges();
156156
expect(fixture.debugElement.query(By.css('div.thumbnail-placeholder'))).toBeTruthy();
157157
});
@@ -247,14 +247,14 @@ describe('ThumbnailComponent', () => {
247247
describe('fallback', () => {
248248
describe('if there is a default image', () => {
249249
it('should display the default image', () => {
250-
comp.src = 'http://bit.stream';
250+
comp.src.set('http://bit.stream');
251251
comp.defaultImage = 'http://default.img';
252252
comp.errorHandler();
253-
expect(comp.src).toBe(comp.defaultImage);
253+
expect(comp.src()).toBe(comp.defaultImage);
254254
});
255255

256256
it('should include the alt text', () => {
257-
comp.src = 'http://bit.stream';
257+
comp.src.set('http://bit.stream');
258258
comp.defaultImage = 'http://default.img';
259259
comp.errorHandler();
260260

@@ -266,10 +266,10 @@ describe('ThumbnailComponent', () => {
266266

267267
describe('if there is no default image', () => {
268268
it('should display the HTML placeholder', () => {
269-
comp.src = 'http://default.img';
269+
comp.src.set('http://default.img');
270270
comp.defaultImage = null;
271271
comp.errorHandler();
272-
expect(comp.src).toBe(null);
272+
expect(comp.src()).toBe(null);
273273

274274
fixture.detectChanges();
275275
const placeholder = fixture.debugElement.query(By.css('div.thumbnail-placeholder')).nativeElement;
@@ -363,7 +363,7 @@ describe('ThumbnailComponent', () => {
363363
it('should show the default image', () => {
364364
comp.defaultImage = 'default/image.jpg';
365365
comp.ngOnChanges({});
366-
expect(comp.src).toBe('default/image.jpg');
366+
expect(comp.src()).toBe('default/image.jpg');
367367
});
368368
});
369369
});
@@ -419,7 +419,7 @@ describe('ThumbnailComponent', () => {
419419
});
420420

421421
it('should start out with isLoading$ true', () => {
422-
expect(comp.isLoading).toBeTrue();
422+
expect(comp.isLoading()).toBeTrue();
423423
expect(de.query(By.css('ds-loading'))).toBeTruthy();
424424
});
425425

src/app/thumbnail/thumbnail.component.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import {
88
Input,
99
OnChanges,
1010
PLATFORM_ID,
11+
signal,
1112
SimpleChanges,
13+
WritableSignal,
1214
} from '@angular/core';
1315
import { TranslateModule } from '@ngx-translate/core';
1416
import { of as observableOf } from 'rxjs';
@@ -26,7 +28,6 @@ import {
2628
} from '../shared/empty.util';
2729
import { ThemedLoadingComponent } from '../shared/loading/themed-loading.component';
2830
import { SafeUrlPipe } from '../shared/utils/safe-url-pipe';
29-
import { VarDirective } from '../shared/utils/var.directive';
3031

3132
/**
3233
* This component renders a given Bitstream as a thumbnail.
@@ -38,7 +39,7 @@ import { VarDirective } from '../shared/utils/var.directive';
3839
styleUrls: ['./thumbnail.component.scss'],
3940
templateUrl: './thumbnail.component.html',
4041
standalone: true,
41-
imports: [VarDirective, CommonModule, ThemedLoadingComponent, TranslateModule, SafeUrlPipe],
42+
imports: [CommonModule, ThemedLoadingComponent, TranslateModule, SafeUrlPipe],
4243
})
4344
export class ThumbnailComponent implements OnChanges {
4445
/**
@@ -55,7 +56,7 @@ export class ThumbnailComponent implements OnChanges {
5556
/**
5657
* The src attribute used in the template to render the image.
5758
*/
58-
src: string = undefined;
59+
src: WritableSignal<string> = signal(undefined);
5960

6061
retriedWithToken = false;
6162

@@ -78,7 +79,7 @@ export class ThumbnailComponent implements OnChanges {
7879
* Whether the thumbnail is currently loading
7980
* Start out as true to avoid flashing the alt text while a thumbnail is being loaded.
8081
*/
81-
isLoading = true;
82+
isLoading: WritableSignal<boolean> = signal(true);
8283

8384
constructor(
8485
@Inject(PLATFORM_ID) private platformID: any,
@@ -134,7 +135,7 @@ export class ThumbnailComponent implements OnChanges {
134135
* Otherwise, fall back to the default image or a HTML placeholder
135136
*/
136137
errorHandler() {
137-
const src = this.src;
138+
const src = this.src();
138139
const thumbnail = this.bitstream;
139140
const thumbnailSrc = thumbnail?._links?.content?.href;
140141

@@ -186,16 +187,29 @@ export class ThumbnailComponent implements OnChanges {
186187
* @param src
187188
*/
188189
setSrc(src: string): void {
189-
this.src = src;
190-
if (src === null) {
191-
this.isLoading = false;
190+
// only update the src if it has changed (the parent component may fire the same one multiple times
191+
if (this.src() !== src) {
192+
// every time the src changes we need to start the loading animation again, as it's possible
193+
// that it is first set to null when the parent component initializes and then set to
194+
// the actual value
195+
//
196+
// isLoading$ will be set to false by the error or success handler afterwards, except in the
197+
// case where src is null, then we have to set it manually here (because those handlers won't
198+
// trigger)
199+
if (src !== null && this.isLoading() === false) {
200+
this.isLoading.set(true);
201+
}
202+
this.src.set(src);
203+
if (src === null && this.isLoading() === true) {
204+
this.isLoading.set(false);
205+
}
192206
}
193207
}
194208

195209
/**
196210
* Stop the loading animation once the thumbnail is successfully loaded
197211
*/
198212
successHandler() {
199-
this.isLoading = false;
213+
this.isLoading.set(false);
200214
}
201215
}

0 commit comments

Comments
 (0)