Skip to content

Commit

Permalink
Fix: Thumbnail multiple images fallback when 404 (#570)
Browse files Browse the repository at this point in the history
* WIP : trying to send multiple images to gn ui thumbnail component

need to get rid of this multiple fit and fits inputs. find a better way to implement it

* Fix: Show org logo if thumbnail returns 404, show placeholder if also org log returns 404

* Remove console.log

* Fix using original array and mutation of it

* Add getter for each image array

* wip changes to thumbnail

* rollback api changes to thumbnail component

---------

Co-authored-by: Florian Necas <florian.necas@camptocamp.com>
Co-authored-by: Olivia Guyot <olivia.guyot@camptocamp.com>
  • Loading branch information
3 people authored Aug 14, 2023
1 parent 7503133 commit 260378e
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 41 deletions.
5 changes: 3 additions & 2 deletions libs/ui/elements/src/lib/thumbnail/thumbnail.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
<img
#imageElement
class="relative w-full object-center"
[ngClass]="objectFit === 'contain' ? 'h-4/5' : 'h-full'"
[ngStyle]="{ objectFit: objectFit }"
[ngClass]="imgFit === 'contain' ? 'h-4/5' : 'h-full'"
[ngStyle]="{ objectFit: imgFit }"
alt="thumbnail"
loading="lazy"
(load)="setObjectFit()"
[src]="imgUrl | safe: 'url'"
(error)="useFallback()"
/>
</div>
52 changes: 50 additions & 2 deletions libs/ui/elements/src/lib/thumbnail/thumbnail.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('ThumbnailComponent', () => {
describe('When no url is given', () => {
let img
beforeEach(fakeAsync(() => {
component.thumbnailUrl = undefined
component.thumbnailUrl = null
fixture.detectChanges()
img = de.query(By.css('img'))
tick(10)
Expand Down Expand Up @@ -145,7 +145,7 @@ describe('ThumbnailComponent', () => {
let img
beforeEach(() => {
component.placeholderUrl = placeholderUrl
component.thumbnailUrl = undefined
component.thumbnailUrl = null
fixture.detectChanges()
img = de.query(By.css('img'))
})
Expand Down Expand Up @@ -175,4 +175,52 @@ describe('ThumbnailComponent', () => {
expect(img.nativeElement.style.objectFit).toEqual('scale-down')
})
})

describe('thumbnail image url returns 404 and organisation logo exists', () => {
const url = 'http://test.com/img.png'
const orgLogoUrl = 'http://test.com/orgLogo.png'
const placeholderUrl = 'http://localhost/assets/img/placeholder.png'
let img
beforeEach(() => {
component.thumbnailUrl = [url, orgLogoUrl]
component.fit = ['cover', 'contain']
component.placeholderUrl = placeholderUrl
fixture.detectChanges()
img = de.query(By.css('img'))
img.nativeElement.dispatchEvent(new Event('error'))
fixture.detectChanges()
})
it('displays organisation logo', () => {
expect(img.nativeElement.src).toEqual(orgLogoUrl)
expect(img.nativeElement.style.objectFit).toEqual('contain')
})

describe('if organisation logo also returns 404', () => {
beforeEach(() => {
img.nativeElement.dispatchEvent(new Event('error'))
fixture.detectChanges()
})
it('displays placeholder', () => {
expect(img.nativeElement.src).toEqual(placeholderUrl)
expect(img.nativeElement.style.objectFit).toEqual('scale-down')
})
})
})
describe('thumbnail image url returns 404 and no organisation logo', () => {
const url = 'http://test.com/img.png'
const placeholderUrl = 'http://localhost/assets/img/placeholder.png'
let img
beforeEach(() => {
component.thumbnailUrl = url
component.placeholderUrl = placeholderUrl
fixture.detectChanges()
img = de.query(By.css('img'))
img.nativeElement.dispatchEvent(new Event('error'))
fixture.detectChanges()
})

it('displays placeholder', () => {
expect(img.nativeElement.src).toEqual(placeholderUrl)
})
})
})
82 changes: 54 additions & 28 deletions libs/ui/elements/src/lib/thumbnail/thumbnail.component.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,95 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
Inject,
InjectionToken,
Input,
OnDestroy,
OnChanges,
OnInit,
Optional,
SimpleChanges,
ViewChild,
} from '@angular/core'
import { fromEvent, Subscription } from 'rxjs'

export const THUMBNAIL_PLACEHOLDER = new InjectionToken<string>(
'thumbnail-placeholder'
)

type ThumbnailImageObject = {
url: string
fit?: 'cover' | 'contain' | 'scale-down'
}

const DEFAULT_PLACEHOLDER =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH5gkNDCUFYjA1nwAAA1pJREFUeNrtnW2TmjAURh8CLlTdrmun///3tZ22+zLuYlehH7jsMGogwRiiec4MM44K6D3x3hAwAIQQQgghhJDYSM5cPwNQAMgBpACUg22GTg2gArAD8A9AKY+9CsgALAF8YRsGALwDeAWw9yGgALCKoKWP+WU8iwxjUsudLBj83sZciIiPSwhoWz7pJ5dUZFQXlEXOZ/DNeTBt3JnhBpc9aacGsOn0BuoIUk3b+5trGnEC4B7Ak4sUlInRU+wB/JbgVxG18KrTBc01EmYmcTFJQUVPy/9zTh/4BthLDOqeenB2DdBtZBN58LsSNpcUoEtTJWM/GIvMhQDde9j6h2OhXAjo6/2Q/lgkLgSQC0IBFEABZEKyiaQv5AAv7fSlS+lPVxRwOXTnEtoTPAs04yfRHGMoz8F/HOiaJfKeggLc78dmOHsVS33y9SUXsDuLlsg6FOAw/fhYhwI0pJ7WoQASpoC9p3UoQEPpaR0K0LCB3fB1e6KfAhxRweAKgQ5PsQxJ+CzCJYC/A7+EWt4TzVCE77GgEsBPHA/G7QBswcE4b+noVRZ2QxkCCqAAQgEUQCiAAggFUAChAAogFEABhAJcfT9FAdOxQuB/ML9lAfdo/qWYy2MK8EiO5mrrliUCvdIudAEzWWzQzWuxwjRnAK9WwAzAWhZTCe3l7cryNQrQBL/txZhKGGrlwc36ogIPfvdzfhuQsDDM80EVZXUFwe+mEJ2EOwBfLfYTTFFWVxL8Qwl3nedSye1jjhEyCjAPflfCWiScU1iDKMrqyoJ/KOFxRDf1sCg/xCxgTPC7EnIHn6GYsiirKw2+ayYryorBn7YoKwb/qCgntywg1OBPdqSsGPyTRXnp07gvPgD8AAl6KIICCAVQAAlLwOg5MSNi9NyqJgKqAHpQoZNZxs5KgG7SDN7AZzgWOxcCtprn5/wVfLb+uWXsrASUPXlvHbmETGKQjBVgMitVJTuaaQS2t/GoEMeE3onEor2jlK4RvwN4G1u9T4n6zp6PMTWAXzCYdCq12OCOhdeYZzT3mIErAW1Fr+HmNOAt82KSesYIAJoRzR2aIVumo+Ms8WwT/HOOZlM0Y+Zzxv2zp/gCTzfzPOyGtdfgp7LEcDvbvSxbWXg/HUIIIYQQQogx/wHLoX7NoCMFPwAAAABJRU5ErkJggg=='

type FitOptions = 'cover' | 'contain' | 'scale-down'

@Component({
selector: 'gn-ui-thumbnail',
templateUrl: './thumbnail.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ThumbnailComponent implements AfterViewInit, OnDestroy {
@Input() set thumbnailUrl(url: string) {
this.imgUrl = url || this.placeholderUrl
this.isPlaceholder = !url
}
@Input() fit: 'cover' | 'contain' | 'scale-down' = 'cover'
export class ThumbnailComponent implements OnInit, OnChanges {
@Input() thumbnailUrl: string | string[]
@Input() fit: FitOptions | FitOptions[] = 'cover'
@ViewChild('imageElement') imgElement: ElementRef<HTMLImageElement>
@ViewChild('containerElement') containerElement: ElementRef<HTMLDivElement>
imgUrl: string
imgFit: FitOptions
placeholderUrl = this.optionalPlaceholderUrl || DEFAULT_PLACEHOLDER
isPlaceholder = false
sub: Subscription

get objectFit() {
return this.isPlaceholder ? 'scale-down' : this.fit
}
private images: ThumbnailImageObject[] = []

constructor(
@Optional()
@Inject(THUMBNAIL_PLACEHOLDER)
private optionalPlaceholderUrl: string,
private changeDetector: ChangeDetectorRef
private optionalPlaceholderUrl: string
) {}

ngAfterViewInit() {
this.sub = fromEvent(this.imgElement.nativeElement, 'error').subscribe(() =>
this.useFallback()
)
ngOnInit() {
this.updateImageList()
}

ngOnChanges(changes: SimpleChanges): void {
if (!('thumbnailUrl' in changes) && !('fit' in changes)) {
return
}
this.updateImageList()
}

private updateImageList() {
if (!this.thumbnailUrl) {
this.setPlaceholder()
return
}
const urls = Array.isArray(this.thumbnailUrl)
? this.thumbnailUrl
: [this.thumbnailUrl]
this.images = urls.map((url, index) => ({
url,
fit: (Array.isArray(this.fit) ? this.fit[index] : this.fit) || 'cover',
}))
this.setNewSrcImage(this.images[0])
}

private setNewSrcImage(image: ThumbnailImageObject) {
this.imgFit = image.fit
this.imgUrl = image.url
}

ngOnDestroy() {
this.sub.unsubscribe()
private setPlaceholder(): void {
this.isPlaceholder = true
this.setNewSrcImage({ url: this.placeholderUrl, fit: 'scale-down' })
}

useFallback() {
if (!this.isPlaceholder) {
this.isPlaceholder = true
this.imgUrl = this.placeholderUrl
this.changeDetector.detectChanges()
if (this.images.length > 1) {
this.images.shift()
this.setNewSrcImage(this.images[0])
} else {
this.setPlaceholder()
}
}

Expand All @@ -74,7 +100,7 @@ export class ThumbnailComponent implements AfterViewInit, OnDestroy {
this.imgElement.nativeElement.naturalWidth < cw &&
this.imgElement.nativeElement.naturalHeight < ch
) {
this.fit = 'scale-down'
this.imgFit = 'scale-down'
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
>
<gn-ui-thumbnail
class="relative h-full w-full object-cover object-left-top"
[thumbnailUrl]="record.thumbnailUrl || contact.logoUrl"
[fit]="record.thumbnailUrl ? 'cover' : 'contain'"
[thumbnailUrl]="[record.thumbnailUrl, contact.logoUrl]"
[fit]="['cover', 'contain']"
></gn-ui-thumbnail>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {
Component,
Input,
Output,
ElementRef,
EventEmitter,
OnInit,
Input,
OnDestroy,
ElementRef,
OnInit,
Output,
TemplateRef,
} from '@angular/core'
import {
Expand Down
3 changes: 0 additions & 3 deletions libs/util/app-config/src/lib/app-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ let customTranslations: CustomTranslationsAllLanguages = null

export function getCustomTranslations(langCode: string): CustomTranslations {
if (customTranslations === null) throw new Error(MISSING_CONFIG_ERROR)
console.log(
langCode in customTranslations ? customTranslations[langCode] : {}
)
return langCode in customTranslations ? customTranslations[langCode] : {}
}

Expand Down

0 comments on commit 260378e

Please sign in to comment.