mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-12 20:43:08 +00:00
Merge pull request #1219 from atmire/w2p-79597_Fix-thumbnail-a11y-issues
Fix thumbnail a11y issues
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<div class="simple-view-element" [class.d-none]="content.textContent.trim().length === 0 && hasNoValue(content.querySelector('img'))">
|
||||
<div class="simple-view-element" [class.d-none]="hideIfNoTextContent && content.textContent.trim().length === 0">
|
||||
<h5 class="simple-view-element-header" *ngIf="label">{{ label }}</h5>
|
||||
<div #content class="simple-view-element-body">
|
||||
<ng-content></ng-content>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MetadataFieldWrapperComponent } from './metadata-field-wrapper.component';
|
||||
@@ -6,35 +6,34 @@ import { MetadataFieldWrapperComponent } from './metadata-field-wrapper.componen
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
@Component({
|
||||
selector: 'ds-component-without-content',
|
||||
template: '<ds-metadata-field-wrapper [label]="\'test label\'">\n' +
|
||||
template: '<ds-metadata-field-wrapper [hideIfNoTextContent]="hideIfNoTextContent" [label]="\'test label\'">\n' +
|
||||
'</ds-metadata-field-wrapper>'
|
||||
})
|
||||
class NoContentComponent {}
|
||||
class NoContentComponent {
|
||||
public hideIfNoTextContent = true;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ds-component-with-empty-spans',
|
||||
template: '<ds-metadata-field-wrapper [label]="\'test label\'">\n' +
|
||||
template: '<ds-metadata-field-wrapper [hideIfNoTextContent]="hideIfNoTextContent" [label]="\'test label\'">\n' +
|
||||
' <span></span>\n' +
|
||||
' <span></span>\n' +
|
||||
'</ds-metadata-field-wrapper>'
|
||||
})
|
||||
class SpanContentComponent {}
|
||||
class SpanContentComponent {
|
||||
@Input() hideIfNoTextContent = true;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ds-component-with-text',
|
||||
template: '<ds-metadata-field-wrapper [label]="\'test label\'">\n' +
|
||||
template: '<ds-metadata-field-wrapper [hideIfNoTextContent]="hideIfNoTextContent" [label]="\'test label\'">\n' +
|
||||
' <span>The quick brown fox jumps over the lazy dog</span>\n' +
|
||||
'</ds-metadata-field-wrapper>'
|
||||
})
|
||||
class TextContentComponent {}
|
||||
class TextContentComponent {
|
||||
@Input() hideIfNoTextContent = true;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ds-component-with-image',
|
||||
template: '<ds-metadata-field-wrapper [label]="\'test label\'">\n' +
|
||||
' <img src="https://some/image.png" alt="an alt text">\n' +
|
||||
'</ds-metadata-field-wrapper>'
|
||||
})
|
||||
class ImgContentComponent {}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
||||
describe('MetadataFieldWrapperComponent', () => {
|
||||
@@ -43,7 +42,7 @@ describe('MetadataFieldWrapperComponent', () => {
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [MetadataFieldWrapperComponent, NoContentComponent, SpanContentComponent, TextContentComponent, ImgContentComponent]
|
||||
declarations: [MetadataFieldWrapperComponent, NoContentComponent, SpanContentComponent, TextContentComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
@@ -58,38 +57,60 @@ describe('MetadataFieldWrapperComponent', () => {
|
||||
expect(component).toBeDefined();
|
||||
});
|
||||
|
||||
it('should not show the component when there is no content', () => {
|
||||
const parentFixture = TestBed.createComponent(NoContentComponent);
|
||||
parentFixture.detectChanges();
|
||||
const parentNative = parentFixture.nativeElement;
|
||||
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
||||
expect(nativeWrapper.classList.contains('d-none')).toBe(true);
|
||||
describe('with hideIfNoTextContent=true', () => {
|
||||
it('should not show the component when there is no content', () => {
|
||||
const parentFixture = TestBed.createComponent(NoContentComponent);
|
||||
parentFixture.detectChanges();
|
||||
const parentNative = parentFixture.nativeElement;
|
||||
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
||||
expect(nativeWrapper.classList.contains('d-none')).toBe(true);
|
||||
});
|
||||
|
||||
it('should not show the component when there is no text content', () => {
|
||||
const parentFixture = TestBed.createComponent(SpanContentComponent);
|
||||
parentFixture.detectChanges();
|
||||
const parentNative = parentFixture.nativeElement;
|
||||
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
||||
expect(nativeWrapper.classList.contains('d-none')).toBe(true);
|
||||
});
|
||||
|
||||
it('should show the component when there is text content', () => {
|
||||
const parentFixture = TestBed.createComponent(TextContentComponent);
|
||||
parentFixture.detectChanges();
|
||||
const parentNative = parentFixture.nativeElement;
|
||||
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
||||
parentFixture.detectChanges();
|
||||
expect(nativeWrapper.classList.contains('d-none')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not show the component when there is DOM content but not text or an image', () => {
|
||||
const parentFixture = TestBed.createComponent(SpanContentComponent);
|
||||
parentFixture.detectChanges();
|
||||
const parentNative = parentFixture.nativeElement;
|
||||
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
||||
expect(nativeWrapper.classList.contains('d-none')).toBe(true);
|
||||
});
|
||||
describe('with hideIfNoTextContent=false', () => {
|
||||
it('should show the component when there is no content', () => {
|
||||
const parentFixture = TestBed.createComponent(NoContentComponent);
|
||||
parentFixture.componentInstance.hideIfNoTextContent = false;
|
||||
parentFixture.detectChanges();
|
||||
const parentNative = parentFixture.nativeElement;
|
||||
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
||||
expect(nativeWrapper.classList.contains('d-none')).toBe(false);
|
||||
});
|
||||
|
||||
it('should show the component when there is text content', () => {
|
||||
const parentFixture = TestBed.createComponent(TextContentComponent);
|
||||
parentFixture.detectChanges();
|
||||
const parentNative = parentFixture.nativeElement;
|
||||
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
||||
parentFixture.detectChanges();
|
||||
expect(nativeWrapper.classList.contains('d-none')).toBe(false);
|
||||
});
|
||||
it('should show the component when there is no text content', () => {
|
||||
const parentFixture = TestBed.createComponent(SpanContentComponent);
|
||||
parentFixture.componentInstance.hideIfNoTextContent = false;
|
||||
parentFixture.detectChanges();
|
||||
const parentNative = parentFixture.nativeElement;
|
||||
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
||||
expect(nativeWrapper.classList.contains('d-none')).toBe(false);
|
||||
});
|
||||
|
||||
it('should show the component when there is img content', () => {
|
||||
const parentFixture = TestBed.createComponent(ImgContentComponent);
|
||||
parentFixture.detectChanges();
|
||||
const parentNative = parentFixture.nativeElement;
|
||||
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
||||
parentFixture.detectChanges();
|
||||
expect(nativeWrapper.classList.contains('d-none')).toBe(false);
|
||||
it('should show the component when there is text content', () => {
|
||||
const parentFixture = TestBed.createComponent(TextContentComponent);
|
||||
parentFixture.componentInstance.hideIfNoTextContent = false;
|
||||
parentFixture.detectChanges();
|
||||
const parentNative = parentFixture.nativeElement;
|
||||
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
||||
parentFixture.detectChanges();
|
||||
expect(nativeWrapper.classList.contains('d-none')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { hasNoValue } from '../../../shared/empty.util';
|
||||
|
||||
/**
|
||||
* This component renders any content inside this wrapper.
|
||||
@@ -17,10 +16,5 @@ export class MetadataFieldWrapperComponent {
|
||||
*/
|
||||
@Input() label: string;
|
||||
|
||||
/**
|
||||
* Make hasNoValue() available in the template
|
||||
*/
|
||||
hasNoValue(o: any): boolean {
|
||||
return hasNoValue(o);
|
||||
}
|
||||
@Input() hideIfNoTextContent = true;
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@
|
||||
[retainScrollPosition]="true">
|
||||
|
||||
|
||||
<div class="file-section row" *ngFor="let file of originals?.page;">
|
||||
<div class="file-section row mb-3" *ngFor="let file of originals?.page;">
|
||||
<div class="col-3">
|
||||
<ds-thumbnail [thumbnail]="(file.thumbnail | async)?.payload"></ds-thumbnail>
|
||||
</div>
|
||||
|
@@ -9,8 +9,8 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ng-container *ngIf="!mediaViewer.image">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
||||
<ds-thumbnail [thumbnail]="thumbnail$ | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="mediaViewer.image">
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from '../../../../../environments/environment';
|
||||
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators';
|
||||
import { takeUntilCompletedRemoteData } from '../../../../core/shared/operators';
|
||||
import { getItemPageRoute } from '../../../item-page-routing-paths';
|
||||
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item',
|
||||
@@ -17,6 +18,11 @@ import { getItemPageRoute } from '../../../item-page-routing-paths';
|
||||
export class ItemComponent implements OnInit {
|
||||
@Input() object: Item;
|
||||
|
||||
/**
|
||||
* The Item's thumbnail
|
||||
*/
|
||||
thumbnail$: BehaviorSubject<RemoteData<Bitstream>>;
|
||||
|
||||
/**
|
||||
* Route to the item page
|
||||
*/
|
||||
@@ -28,12 +34,12 @@ export class ItemComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.itemPageRoute = getItemPageRoute(this.object);
|
||||
}
|
||||
|
||||
// TODO refactor to return RemoteData, and thumbnail template to deal with loading
|
||||
getThumbnail(): Observable<Bitstream> {
|
||||
return this.bitstreamDataService.getThumbnailFor(this.object).pipe(
|
||||
getFirstSucceededRemoteDataPayload()
|
||||
);
|
||||
this.thumbnail$ = new BehaviorSubject<RemoteData<Bitstream>>(undefined);
|
||||
this.bitstreamDataService.getThumbnailFor(this.object).pipe(
|
||||
takeUntilCompletedRemoteData(),
|
||||
).subscribe((rd: RemoteData<Bitstream>) => {
|
||||
this.thumbnail$.next(rd);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -9,8 +9,8 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ng-container *ngIf="!mediaViewer.image">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
||||
<ds-thumbnail [thumbnail]="thumbnail$ | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="mediaViewer.image">
|
||||
|
@@ -8,14 +8,14 @@
|
||||
rel="noopener noreferrer" [routerLink]="[itemPageRoute]"
|
||||
class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</div>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</div>
|
||||
</span>
|
||||
<div class="card-body">
|
||||
|
@@ -8,14 +8,14 @@
|
||||
rel="noopener noreferrer" [routerLink]="[itemPageRoute]"
|
||||
class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</div>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</div>
|
||||
</span>
|
||||
<div class="card-body">
|
||||
|
@@ -8,14 +8,14 @@
|
||||
rel="noopener noreferrer" [routerLink]="[itemPageRoute]"
|
||||
class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</div>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</div>
|
||||
</span>
|
||||
<div class="card-body">
|
||||
|
@@ -8,8 +8,8 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
||||
<ds-thumbnail [thumbnail]="thumbnail$ | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<ds-generic-item-page-field [item]="object"
|
||||
[fields]="['publicationvolume.volumeNumber']"
|
||||
|
@@ -8,8 +8,8 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
||||
<ds-thumbnail [thumbnail]="thumbnail$ | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<ds-generic-item-page-field [item]="object"
|
||||
[fields]="['publicationvolume.volumeNumber']"
|
||||
|
@@ -8,8 +8,8 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
||||
<ds-thumbnail [thumbnail]="thumbnail$ | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<ds-generic-item-page-field class="item-page-fields" [item]="object"
|
||||
[fields]="['creativeworkseries.issn']"
|
||||
|
@@ -8,14 +8,14 @@
|
||||
rel="noopener noreferrer" [routerLink]="[itemPageRoute]"
|
||||
class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</div>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</div>
|
||||
</span>
|
||||
<div class="card-body">
|
||||
|
@@ -8,14 +8,14 @@
|
||||
rel="noopener noreferrer" [routerLink]="[itemPageRoute]"
|
||||
class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</div>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</div>
|
||||
</span>
|
||||
<div class="card-body">
|
||||
|
@@ -8,14 +8,14 @@
|
||||
rel="noopener noreferrer" [routerLink]="[itemPageRoute]"
|
||||
class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</div>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</div>
|
||||
</span>
|
||||
<div class="card-body">
|
||||
|
@@ -8,8 +8,13 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [defaultImage]="'assets/images/orgunit-placeholder.svg'"></ds-thumbnail>
|
||||
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
||||
<ds-thumbnail [thumbnail]="thumbnail$ | async"
|
||||
[defaultImage]="'assets/images/orgunit-placeholder.svg'"
|
||||
[alt]="'thumbnail.orgunit.alt'"
|
||||
[placeholder]="'thumbnail.orgunit.placeholder'"
|
||||
>
|
||||
</ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<ds-generic-item-page-field [item]="object"
|
||||
[fields]="['organization.foundingDate']"
|
||||
|
@@ -8,8 +8,12 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [defaultImage]="'assets/images/person-placeholder.svg'"></ds-thumbnail>
|
||||
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
||||
<ds-thumbnail [thumbnail]="thumbnail$ | async"
|
||||
[defaultImage]="'assets/images/person-placeholder.svg'"
|
||||
[alt]="'thumbnail.person.alt'"
|
||||
[placeholder]="'thumbnail.person.placeholder'">
|
||||
</ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<ds-generic-item-page-field [item]="object"
|
||||
[fields]="['person.email']"
|
||||
|
@@ -8,8 +8,13 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [defaultImage]="'assets/images/project-placeholder.svg'"></ds-thumbnail>
|
||||
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
||||
<ds-thumbnail
|
||||
[thumbnail]="thumbnail$ | async"
|
||||
[defaultImage]="'assets/images/project-placeholder.svg'"
|
||||
[alt]="'thumbnail.project.alt'"
|
||||
[placeholder]="'thumbnail.project.placeholder'">
|
||||
</ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<!--<ds-generic-item-page-field [item]="object"-->
|
||||
<!--[fields]="['project.identifier.status']"-->
|
||||
|
@@ -3,6 +3,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
export function getMockTranslateService(): TranslateService {
|
||||
return jasmine.createSpyObj('translateService', {
|
||||
get: jasmine.createSpy('get'),
|
||||
use: jasmine.createSpy('use'),
|
||||
instant: jasmine.createSpy('instant'),
|
||||
setDefaultLang: jasmine.createSpy('setDefaultLang')
|
||||
});
|
||||
|
@@ -9,7 +9,7 @@
|
||||
</h2>
|
||||
<div class="row mb-1">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<ng-container *ngVar="(getFiles() | async) as bitstreams">
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<div class="card">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/collections/', object.id]" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="(object.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="(object.logo | async)?.payload" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="(object.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="(object.logo | async)?.payload" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</span>
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">{{object.name}}</h4>
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<div class="card">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/communities/', object.id]" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="(object.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="(object.logo | async)?.payload" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="(object.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="(object.logo | async)?.payload" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</span>
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">{{object.name}}</h4>
|
||||
|
@@ -1,3 +0,0 @@
|
||||
<div class="thumbnail">
|
||||
<img [src]="src | dsSafeUrl" (error)="errorHandler($event)" />
|
||||
</div>
|
@@ -1,50 +0,0 @@
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||
import { SafeUrlPipe } from '../../utils/safe-url-pipe';
|
||||
|
||||
import { GridThumbnailComponent } from './grid-thumbnail.component';
|
||||
|
||||
describe('GridThumbnailComponent', () => {
|
||||
let comp: GridThumbnailComponent;
|
||||
let fixture: ComponentFixture<GridThumbnailComponent>;
|
||||
let de: DebugElement;
|
||||
let el: HTMLElement;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [GridThumbnailComponent, SafeUrlPipe]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GridThumbnailComponent);
|
||||
comp = fixture.componentInstance; // BannerComponent test instance
|
||||
de = fixture.debugElement.query(By.css('div.thumbnail'));
|
||||
el = de.nativeElement;
|
||||
});
|
||||
|
||||
it('should display image', () => {
|
||||
const thumbnail = new Bitstream();
|
||||
thumbnail._links = {
|
||||
self: { href: 'self.url' },
|
||||
bundle: { href: 'bundle.url' },
|
||||
format: { href: 'format.url' },
|
||||
content: { href: 'content.url' },
|
||||
};
|
||||
comp.thumbnail = thumbnail;
|
||||
fixture.detectChanges();
|
||||
const image: HTMLElement = de.query(By.css('img')).nativeElement;
|
||||
expect(image.getAttribute('src')).toBe(comp.thumbnail._links.content.href);
|
||||
});
|
||||
|
||||
it('should display placeholder', () => {
|
||||
const thumbnail = new Bitstream();
|
||||
comp.thumbnail = thumbnail;
|
||||
fixture.detectChanges();
|
||||
const image: HTMLElement = de.query(By.css('img')).nativeElement;
|
||||
expect(image.getAttribute('src')).toBe(comp.defaultImage);
|
||||
});
|
||||
|
||||
});
|
@@ -1,72 +0,0 @@
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||
import { hasValue } from '../../empty.util';
|
||||
|
||||
/**
|
||||
* This component renders a given Bitstream as a thumbnail.
|
||||
* One input parameter of type Bitstream is expected.
|
||||
* If no Bitstream is provided, a holderjs image will be rendered instead.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-grid-thumbnail',
|
||||
styleUrls: ['./grid-thumbnail.component.scss'],
|
||||
templateUrl: './grid-thumbnail.component.html',
|
||||
})
|
||||
export class GridThumbnailComponent implements OnInit, OnChanges {
|
||||
@Input() thumbnail: Bitstream;
|
||||
|
||||
data: any = {};
|
||||
|
||||
/**
|
||||
* The default 'holder.js' image
|
||||
*/
|
||||
@Input() defaultImage? =
|
||||
'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjYwIiBoZWlnaHQ9IjE4MCIgdmlld0JveD0iMCAwIDI2MCAxODAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjwhLS0KU291cmNlIFVSTDogaG9sZGVyLmpzLzEwMCV4MTgwL3RleHQ6Tm8gVGh1bWJuYWlsCkNyZWF0ZWQgd2l0aCBIb2xkZXIuanMgMi42LjAuCkxlYXJuIG1vcmUgYXQgaHR0cDovL2hvbGRlcmpzLmNvbQooYykgMjAxMi0yMDE1IEl2YW4gTWFsb3BpbnNreSAtIGh0dHA6Ly9pbXNreS5jbwotLT48ZGVmcz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwhW0NEQVRBWyNob2xkZXJfMTVmNzJmMmFlMGIgdGV4dCB7IGZpbGw6I0FBQUFBQTtmb250LXdlaWdodDpib2xkO2ZvbnQtZmFtaWx5OkFyaWFsLCBIZWx2ZXRpY2EsIE9wZW4gU2Fucywgc2Fucy1zZXJpZiwgbW9ub3NwYWNlO2ZvbnQtc2l6ZToxM3B0IH0gXV0+PC9zdHlsZT48L2RlZnM+PGcgaWQ9ImhvbGRlcl8xNWY3MmYyYWUwYiI+PHJlY3Qgd2lkdGg9IjI2MCIgaGVpZ2h0PSIxODAiIGZpbGw9IiNFRUVFRUUiLz48Zz48dGV4dCB4PSI3Mi4yNDIxODc1IiB5PSI5NiI+Tm8gVGh1bWJuYWlsPC90ZXh0PjwvZz48L2c+PC9zdmc+';
|
||||
|
||||
src: string;
|
||||
|
||||
errorHandler(event) {
|
||||
event.currentTarget.src = this.defaultImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the src
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.src = this.defaultImage;
|
||||
|
||||
this.checkThumbnail(this.thumbnail);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the old input is undefined and the new one is a bitsream then set src
|
||||
*/
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (
|
||||
!hasValue(changes.thumbnail.previousValue) &&
|
||||
hasValue(changes.thumbnail.currentValue)
|
||||
) {
|
||||
this.checkThumbnail(changes.thumbnail.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the Bitstream has any content than set the src
|
||||
*/
|
||||
checkThumbnail(thumbnail: Bitstream) {
|
||||
if (
|
||||
hasValue(thumbnail) &&
|
||||
hasValue(thumbnail._links) &&
|
||||
thumbnail._links.content.href
|
||||
) {
|
||||
this.src = thumbnail._links.content.href;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
:host ::ng-deep {
|
||||
--ds-wrapper-grid-spacing: calc(var(--bs-spacer) / 2);
|
||||
|
||||
div.thumbnail > img {
|
||||
div.thumbnail > .thumbnail-content {
|
||||
height: var(--ds-card-thumbnail-height);
|
||||
width: 100%;
|
||||
display: block;
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<div class="card">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/collections/', dso.id]" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="(dso.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="(dso.logo | async)?.payload" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="(dso.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="(dso.logo | async)?.payload" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</span>
|
||||
<div class="card-body">
|
||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<div class="card">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/communities/', dso.id]" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="(dso.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="(dso.logo | async)?.payload" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top">
|
||||
<ds-grid-thumbnail [thumbnail]="(dso.logo | async)?.payload">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="(dso.logo | async)?.payload" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</span>
|
||||
<div class="card-body">
|
||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||
|
@@ -6,14 +6,14 @@
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="[itemPageRoute]"
|
||||
class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</div>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
</div>
|
||||
</span>
|
||||
<div class="card-body">
|
||||
|
@@ -46,7 +46,6 @@ import { ThumbnailComponent } from '../thumbnail/thumbnail.component';
|
||||
import { SearchFormComponent } from './search-form/search-form.component';
|
||||
import { SearchResultGridElementComponent } from './object-grid/search-result-grid-element/search-result-grid-element.component';
|
||||
import { ViewModeSwitchComponent } from './view-mode-switch/view-mode-switch.component';
|
||||
import { GridThumbnailComponent } from './object-grid/grid-thumbnail/grid-thumbnail.component';
|
||||
import { VarDirective } from './utils/var.directive';
|
||||
import { AuthNavMenuComponent } from './auth-nav-menu/auth-nav-menu.component';
|
||||
import { LogOutComponent } from './log-out/log-out.component';
|
||||
@@ -54,8 +53,7 @@ import { FormComponent } from './form/form.component';
|
||||
import { DsDynamicOneboxComponent } from './form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component';
|
||||
import { DsDynamicScrollableDropdownComponent } from './form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component';
|
||||
import {
|
||||
DsDynamicFormControlContainerComponent,
|
||||
dsDynamicFormControlMapFn
|
||||
DsDynamicFormControlContainerComponent, dsDynamicFormControlMapFn,
|
||||
} from './form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component';
|
||||
import { DsDynamicFormComponent } from './form/builder/ds-dynamic-form-ui/ds-dynamic-form.component';
|
||||
import { DragClickDirective } from './utils/drag-click.directive';
|
||||
@@ -340,7 +338,6 @@ const COMPONENTS = [
|
||||
SidebarFilterComponent,
|
||||
SidebarFilterSelectedOptionComponent,
|
||||
ThumbnailComponent,
|
||||
GridThumbnailComponent,
|
||||
UploaderComponent,
|
||||
FileDropzoneNoUploaderComponent,
|
||||
ItemListPreviewComponent,
|
||||
|
@@ -1,4 +1,14 @@
|
||||
<div class="thumbnail">
|
||||
<img [src]="src | dsSafeUrl" (error)="errorHandler()" class="img-fluid"/>
|
||||
<div class="thumbnail" [class.limit-width]="limitWidth">
|
||||
<ds-loading *ngIf="isLoading; else showThumbnail" class="thumbnail-content" [showMessage]="false">
|
||||
text-content
|
||||
</ds-loading>
|
||||
<ng-template #showThumbnail>
|
||||
<img *ngIf="src !== null" class="thumbnail-content img-fluid"
|
||||
[src]="src | dsSafeUrl" [alt]="alt | translate" (error)="errorHandler()">
|
||||
<div *ngIf="src === null" class="thumbnail-content outer">
|
||||
<div class="inner">
|
||||
<div class="thumbnail-placeholder w-100 h-100 p-3 lead">{{ placeholder | translate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
|
@@ -1,3 +1,35 @@
|
||||
.limit-width {
|
||||
max-width: var(--ds-thumbnail-max-width);
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.outer { // .outer/.inner generated ~ https://ratiobuddy.com/
|
||||
position: relative;
|
||||
&:before {
|
||||
display: block;
|
||||
content: "";
|
||||
width: 100%;
|
||||
padding-top: (297 / 210) * 100%; // A4 ratio
|
||||
}
|
||||
> .inner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
> .thumbnail-placeholder {
|
||||
background: var(--ds-thumbnail-placeholder-background);
|
||||
border: var(--ds-thumbnail-placeholder-border);
|
||||
color: var(--ds-thumbnail-placeholder-color);
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,22 @@
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { DebugElement, Pipe, PipeTransform } from '@angular/core';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Bitstream } from '../core/shared/bitstream.model';
|
||||
import { SafeUrlPipe } from '../shared/utils/safe-url-pipe';
|
||||
|
||||
import { THUMBNAIL_PLACEHOLDER, ThumbnailComponent } from './thumbnail.component';
|
||||
import { ThumbnailComponent } from './thumbnail.component';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import {
|
||||
createFailedRemoteDataObject, createPendingRemoteDataObject, createSuccessfulRemoteDataObject,
|
||||
} from '../shared/remote-data.utils';
|
||||
|
||||
// tslint:disable-next-line:pipe-prefix
|
||||
@Pipe({ name: 'translate' })
|
||||
class MockTranslatePipe implements PipeTransform {
|
||||
transform(key: string): string {
|
||||
return 'TRANSLATED ' + key;
|
||||
}
|
||||
}
|
||||
|
||||
describe('ThumbnailComponent', () => {
|
||||
let comp: ThumbnailComponent;
|
||||
@@ -14,33 +26,18 @@ describe('ThumbnailComponent', () => {
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ThumbnailComponent, SafeUrlPipe]
|
||||
declarations: [ThumbnailComponent, SafeUrlPipe, MockTranslatePipe],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ThumbnailComponent);
|
||||
comp = fixture.componentInstance; // BannerComponent test instance
|
||||
comp = fixture.componentInstance; // ThumbnailComponent test instance
|
||||
de = fixture.debugElement.query(By.css('div.thumbnail'));
|
||||
el = de.nativeElement;
|
||||
});
|
||||
|
||||
describe('when the thumbnail exists', () => {
|
||||
it('should display an image', () => {
|
||||
const thumbnail = new Bitstream();
|
||||
thumbnail._links = {
|
||||
self: { href: 'self.url' },
|
||||
bundle: { href: 'bundle.url' },
|
||||
format: { href: 'format.url' },
|
||||
content: { href: 'content.url' },
|
||||
};
|
||||
comp.thumbnail = thumbnail;
|
||||
fixture.detectChanges();
|
||||
const image: HTMLElement = de.query(By.css('img')).nativeElement;
|
||||
expect(image.getAttribute('src')).toBe(comp.thumbnail._links.content.href);
|
||||
});
|
||||
});
|
||||
describe(`when the thumbnail doesn't exist`, () => {
|
||||
const withoutThumbnail = () => {
|
||||
describe('and there is a default image', () => {
|
||||
it('should display the default image', () => {
|
||||
comp.src = 'http://bit.stream';
|
||||
@@ -48,14 +45,114 @@ describe('ThumbnailComponent', () => {
|
||||
comp.errorHandler();
|
||||
expect(comp.src).toBe(comp.defaultImage);
|
||||
});
|
||||
it('should include the alt text', () => {
|
||||
comp.src = 'http://bit.stream';
|
||||
comp.defaultImage = 'http://default.img';
|
||||
comp.errorHandler();
|
||||
comp.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
const image: HTMLElement = de.query(By.css('img')).nativeElement;
|
||||
expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt);
|
||||
});
|
||||
});
|
||||
describe('and there is no default image', () => {
|
||||
it('should display the placeholder', () => {
|
||||
comp.src = 'http://default.img';
|
||||
comp.defaultImage = 'http://default.img';
|
||||
comp.errorHandler();
|
||||
expect(comp.src).toBe(THUMBNAIL_PLACEHOLDER);
|
||||
expect(comp.src).toBe(null);
|
||||
|
||||
comp.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
const placeholder = fixture.debugElement.query(By.css('div.thumbnail-placeholder')).nativeElement;
|
||||
expect(placeholder.innerHTML).toBe('TRANSLATED ' + comp.placeholder);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('with thumbnail as Bitstream', () => {
|
||||
let thumbnail: Bitstream;
|
||||
beforeEach(() => {
|
||||
thumbnail = new Bitstream();
|
||||
thumbnail._links = {
|
||||
self: { href: 'self.url' },
|
||||
bundle: { href: 'bundle.url' },
|
||||
format: { href: 'format.url' },
|
||||
content: { href: 'content.url' },
|
||||
};
|
||||
});
|
||||
|
||||
it('should display an image', () => {
|
||||
comp.thumbnail = thumbnail;
|
||||
comp.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
const image: HTMLElement = de.query(By.css('img')).nativeElement;
|
||||
expect(image.getAttribute('src')).toBe(comp.thumbnail._links.content.href);
|
||||
});
|
||||
|
||||
it('should include the alt text', () => {
|
||||
comp.thumbnail = thumbnail;
|
||||
comp.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
const image: HTMLElement = de.query(By.css('img')).nativeElement;
|
||||
expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt);
|
||||
});
|
||||
|
||||
describe('when there is no thumbnail', () => {
|
||||
withoutThumbnail();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with thumbnail as RemoteData<Bitstream>', () => {
|
||||
let thumbnail: RemoteData<Bitstream>;
|
||||
|
||||
describe('while loading', () => {
|
||||
beforeEach(() => {
|
||||
thumbnail = createPendingRemoteDataObject();
|
||||
});
|
||||
|
||||
it('should show a loading animation', () => {
|
||||
comp.thumbnail = thumbnail;
|
||||
comp.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
expect(de.query(By.css('ds-loading'))).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there is a thumbnail', () => {
|
||||
beforeEach(() => {
|
||||
const bitstream = new Bitstream();
|
||||
bitstream._links = {
|
||||
self: { href: 'self.url' },
|
||||
bundle: { href: 'bundle.url' },
|
||||
format: { href: 'format.url' },
|
||||
content: { href: 'content.url' },
|
||||
};
|
||||
thumbnail = createSuccessfulRemoteDataObject(bitstream);
|
||||
});
|
||||
|
||||
it('should display an image', () => {
|
||||
comp.thumbnail = thumbnail;
|
||||
comp.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
const image: HTMLElement = de.query(By.css('img')).nativeElement;
|
||||
expect(image.getAttribute('src')).toBe(comp.thumbnail.payload._links.content.href);
|
||||
});
|
||||
|
||||
it('should display the alt text', () => {
|
||||
comp.thumbnail = thumbnail;
|
||||
comp.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
const image: HTMLElement = de.query(By.css('img')).nativeElement;
|
||||
expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there is no thumbnail', () => {
|
||||
beforeEach(() => {
|
||||
thumbnail = createFailedRemoteDataObject();
|
||||
});
|
||||
|
||||
withoutThumbnail();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,61 +1,93 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { Bitstream } from '../core/shared/bitstream.model';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
|
||||
/**
|
||||
* A fallback placeholder image as a base64 string
|
||||
*/
|
||||
export const THUMBNAIL_PLACEHOLDER = 'data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2293%22%20height%3D%22120%22%20viewBox%3D%220%200%2093%20120%22%20preserveAspectRatio%3D%22none%22%3E%3C!--%0ASource%20URL%3A%20holder.js%2F93x120%3Ftext%3DNo%20Thumbnail%0ACreated%20with%20Holder.js%202.8.2.%0ALearn%20more%20at%20http%3A%2F%2Fholderjs.com%0A(c)%202012-2015%20Ivan%20Malopinsky%20-%20http%3A%2F%2Fimsky.co%0A--%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%3C!%5BCDATA%5B%23holder_1543e460b05%20text%20%7B%20fill%3A%23AAAAAA%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A10pt%20%7D%20%5D%5D%3E%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_1543e460b05%22%3E%3Crect%20width%3D%2293%22%20height%3D%22120%22%20fill%3D%22%23FFFFFF%22%2F%3E%3Cg%3E%3Ctext%20x%3D%2235.6171875%22%20y%3D%2257%22%3ENo%3C%2Ftext%3E%3Ctext%20x%3D%2210.8125%22%20y%3D%2272%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
|
||||
/**
|
||||
* This component renders a given Bitstream as a thumbnail.
|
||||
* One input parameter of type Bitstream is expected.
|
||||
* If no Bitstream is provided, a holderjs image will be rendered instead.
|
||||
* If no Bitstream is provided, a HTML placeholder will be rendered instead.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-thumbnail',
|
||||
styleUrls: ['./thumbnail.component.scss'],
|
||||
templateUrl: './thumbnail.component.html'
|
||||
templateUrl: './thumbnail.component.html',
|
||||
})
|
||||
export class ThumbnailComponent implements OnInit {
|
||||
|
||||
export class ThumbnailComponent implements OnChanges {
|
||||
/**
|
||||
* The thumbnail Bitstream
|
||||
*/
|
||||
@Input() thumbnail: Bitstream;
|
||||
@Input() thumbnail: Bitstream | RemoteData<Bitstream>;
|
||||
|
||||
/**
|
||||
* The default image, used if the thumbnail isn't set or can't be downloaded
|
||||
* 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? = THUMBNAIL_PLACEHOLDER;
|
||||
@Input() defaultImage? = null;
|
||||
|
||||
/**
|
||||
* The src attribute used in the template to render the image.
|
||||
*/
|
||||
src: string;
|
||||
src: string = null;
|
||||
|
||||
/**
|
||||
* Initialize the thumbnail.
|
||||
* i18n key of thumbnail alt text
|
||||
*/
|
||||
@Input() alt? = 'thumbnail.default.alt';
|
||||
|
||||
/**
|
||||
* i18n key of HTML placeholder text
|
||||
*/
|
||||
@Input() placeholder? = 'thumbnail.default.placeholder';
|
||||
|
||||
/**
|
||||
* Limit thumbnail width to --ds-thumbnail-max-width
|
||||
*/
|
||||
@Input() limitWidth? = true;
|
||||
|
||||
isLoading: boolean;
|
||||
|
||||
/**
|
||||
* Resolve the thumbnail.
|
||||
* Use a default image if no actual image is available.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
if (hasValue(this.thumbnail) && hasValue(this.thumbnail._links) && hasValue(this.thumbnail._links.content) && this.thumbnail._links.content.href) {
|
||||
this.src = this.thumbnail._links.content.href;
|
||||
ngOnChanges(): void {
|
||||
if (this.thumbnail === undefined || this.thumbnail === null) {
|
||||
return;
|
||||
}
|
||||
if (this.thumbnail instanceof Bitstream) {
|
||||
this.resolveThumbnail(this.thumbnail as Bitstream);
|
||||
} else {
|
||||
const thumbnailRD = this.thumbnail as RemoteData<Bitstream>;
|
||||
if (thumbnailRD.isLoading) {
|
||||
this.isLoading = true;
|
||||
} else {
|
||||
this.resolveThumbnail(thumbnailRD.payload as Bitstream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private resolveThumbnail(thumbnail: Bitstream): void {
|
||||
if (hasValue(thumbnail) && hasValue(thumbnail._links)
|
||||
&& hasValue(thumbnail._links.content)
|
||||
&& thumbnail._links.content.href) {
|
||||
this.src = thumbnail._links.content.href;
|
||||
} else {
|
||||
this.src = this.defaultImage;
|
||||
}
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle image download errors.
|
||||
* If the image can't be found, use the defaultImage instead.
|
||||
* If that also can't be found, use the base64 placeholder.
|
||||
* If that also can't be found, use null to fall back to the HTML placeholder.
|
||||
*/
|
||||
errorHandler() {
|
||||
if (this.src !== this.defaultImage) {
|
||||
this.src = this.defaultImage;
|
||||
} else {
|
||||
this.src = THUMBNAIL_PLACEHOLDER;
|
||||
this.src = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3544,6 +3544,24 @@
|
||||
|
||||
|
||||
|
||||
"thumbnail.default.alt": "Thumbnail Image",
|
||||
|
||||
"thumbnail.default.placeholder": "No Thumbnail Available",
|
||||
|
||||
"thumbnail.project.alt": "Project Logo",
|
||||
|
||||
"thumbnail.project.placeholder": "Project Placeholder Image",
|
||||
|
||||
"thumbnail.orgunit.alt": "OrgUnit Logo",
|
||||
|
||||
"thumbnail.orgunit.placeholder": "OrgUnit Placeholder Image",
|
||||
|
||||
"thumbnail.person.alt": "Profile Picture",
|
||||
|
||||
"thumbnail.person.placeholder": "No Profile Picture Available",
|
||||
|
||||
|
||||
|
||||
"title": "DSpace",
|
||||
|
||||
|
||||
|
@@ -46,6 +46,9 @@
|
||||
--ds-edit-item-language-field-width: 43px;
|
||||
|
||||
--ds-thumbnail-max-width: 175px;
|
||||
--ds-thumbnail-placeholder-background: #{$gray-100};
|
||||
--ds-thumbnail-placeholder-border: 1px solid #{$gray-300};
|
||||
--ds-thumbnail-placeholder-color: #{lighten($gray-800, 7%)};
|
||||
|
||||
--ds-dso-selector-list-max-height: 475px;
|
||||
--ds-dso-selector-current-background-color: #eeeeee;
|
||||
|
Reference in New Issue
Block a user