Skip to content

Commit

Permalink
Add Fragment Photo Thumbnails (#466)
Browse files Browse the repository at this point in the history
* add findThumbnail methods

* add test for findThumbnail

* add test for findThumbnail

* add ThumbnailBlob and ThumbnailImage component
Allow `findThumbnail` to return a `null` instance for fragments without a thumbnail.
Since `withData` does not allow `null` (it keeps waiting for data forever), wrap the image in an object.

* add thumbnail to additions and results

* fix findThumbnail mock

* fix test

* update snapshots

* test handling missing thumbnails

* add more padding

* add responsive spacing
  • Loading branch information
fsimonjetz authored Apr 12, 2024
1 parent fccf1a1 commit 0da6560
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 48 deletions.
13 changes: 13 additions & 0 deletions src/common/BlobImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,16 @@ export default function BlobImage({
image
)
}

export function ThumbnailImage({
photo,
url,
alt,
}: {
photo: Blob
url?: string
alt?: string
}): JSX.Element {
const image = <Image src={useObjectUrl(photo)} alt={alt} fluid />
return url ? <ExternalLink href={url}>{image}</ExternalLink> : image
}
8 changes: 8 additions & 0 deletions src/fragmentarium/application/FragmentService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const imageRepository = {
find: jest.fn(),
findFolio: jest.fn(),
findPhoto: jest.fn(),
findThumbnail: jest.fn(),
}
const bibliographyService = new (BibliographyService as jest.Mock)()
const wordRepository = new (WordRepository as jest.Mock)()
Expand Down Expand Up @@ -114,6 +115,13 @@ const testData: TestData<FragmentService>[] = [
new TestData('findPhoto', [fragment], imageRepository.findPhoto, resultStub, [
fragment.number,
]),
new TestData(
'findThumbnail',
[fragment, 'small'],
imageRepository.findThumbnail,
resultStub,
[fragment.number, 'small']
),
new TestData(
'folioPager',
[folio, 'K.1'],
Expand Down
14 changes: 14 additions & 0 deletions src/fragmentarium/application/FragmentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { MesopotamianDate } from 'chronology/domain/Date'
import { FragmentAfoRegisterQueryResult, QueryResult } from 'query/QueryResult'
import { ArchaeologyDto } from 'fragmentarium/domain/archaeologyDtos'

export type ThumbnailSize = 'small' | 'medium' | 'large'

export const onError = (error) => {
if (error.message === '403 Forbidden') {
throw new Error("You don't have permissions to view this fragment.")
Expand All @@ -41,10 +43,15 @@ export interface CdliInfo {
readonly detailLineArtUrl: string | null
}

export interface ThumbnailBlob {
readonly blob: Blob | null
}

export interface ImageRepository {
find(fileName: string): Bluebird<Blob>
findFolio(folio: Folio): Bluebird<Blob>
findPhoto(number: string): Bluebird<Blob>
findThumbnail(number: string, size: ThumbnailSize): Bluebird<ThumbnailBlob>
}

export interface FragmentRepository {
Expand Down Expand Up @@ -275,6 +282,13 @@ export class FragmentService {
}
}

findThumbnail(
fragment: Fragment,
size: ThumbnailSize
): Bluebird<ThumbnailBlob> {
return this.imageRepository.findThumbnail(fragment.number, size)
}

folioPager(folio: Folio, fragmentNumber: string): Bluebird<FolioPagerData> {
return this.fragmentRepository.folioPager(folio, fragmentNumber)
}
Expand Down
46 changes: 46 additions & 0 deletions src/fragmentarium/infrastructure/ImageRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import Promise from 'bluebird'
import ApiImageRepository from './ImageRepository'
import Folio from 'fragmentarium/domain/Folio'
import { folioFactory } from 'test-support/fragment-fixtures'
import { ThumbnailSize } from 'fragmentarium/application/FragmentService'
import { ApiError } from 'http/ApiClient'

const image = new Blob([''], { type: 'image/jpeg' })

Expand Down Expand Up @@ -84,3 +86,47 @@ describe('findPhoto', () => {
await expect(promise).resolves.toEqual(image)
})
})

describe('findThumbnail', () => {
const number = 'ABC 123+456'
const size: ThumbnailSize = 'small'

beforeEach(async () => {
jest
.spyOn(apiClient, 'fetchBlob')
.mockReturnValueOnce(Promise.resolve(image))
promise = imageRepository.findThumbnail(number, size)
})

it('Queries the thumbnail', () => {
expect(apiClient.fetchBlob).toBeCalledWith(
`/fragments/${encodeURIComponent(number)}/thumbnail/${size}`,
false
)
})

it('Resolves to blob', async () => {
await expect(promise).resolves.toEqual({ blob: image })
})
})

describe('findThumbnail', () => {
const number = 'foo.number'
const size: ThumbnailSize = 'small'
const errorMsg = 'my error message'

it('Returns empty if no thumbnail is found', async () => {
jest
.spyOn(apiClient, 'fetchBlob')
.mockRejectedValueOnce(new ApiError(errorMsg, { title: '404 Not Found' }))
promise = imageRepository.findThumbnail(number, size)
await expect(promise).resolves.toEqual({ blob: null })
})
it('Throws error if another problem occurs', async () => {
jest
.spyOn(apiClient, 'fetchBlob')
.mockRejectedValueOnce(new ApiError(errorMsg, { title: '500' }))
promise = imageRepository.findThumbnail(number, size)
await expect(promise).rejects.toThrow(errorMsg)
})
})
22 changes: 21 additions & 1 deletion src/fragmentarium/infrastructure/ImageRepository.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Promise from 'bluebird'
import Folio from 'fragmentarium/domain/Folio'
import { ImageRepository } from 'fragmentarium/application/FragmentService'
import {
ImageRepository,
ThumbnailSize,
ThumbnailBlob,
} from 'fragmentarium/application/FragmentService'

class ApiImageRepository implements ImageRepository {
private readonly apiClient
Expand Down Expand Up @@ -30,6 +34,22 @@ class ApiImageRepository implements ImageRepository {
false
)
}

findThumbnail(number: string, size: ThumbnailSize): Promise<ThumbnailBlob> {
return this.apiClient
.fetchBlob(
`/fragments/${encodeURIComponent(number)}/thumbnail/${size}`,
false
)
.then((data) => ({ blob: data }))
.catch((error) => {
if (error.data.title === '404 Not Found') {
return { blob: null }
} else {
throw error
}
})
}
}

export default ApiImageRepository
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ exports[`Snapshot 1`] = `
</p>
</small>
</div>
<ul
class="ResultList"
/>
</div>
<div
class="text-secondary fragment-result__genre col-sm-4 col-12"
Expand All @@ -63,10 +66,10 @@ exports[`Snapshot 1`] = `
</ul>
</div>
<div
class="col-sm-4 col-12"
class="fragment-result__record col-sm-4 col-12"
>
<ol
class="Record fragment-result__record"
class="Record"
>
<li
class="Record__entry"
Expand Down Expand Up @@ -5830,12 +5833,8 @@ exports[`Snapshot 1`] = `
</table>
</div>
<div
class="fragment-result__project-logos col-sm-4 col-12"
>
<ul
class="ResultList"
/>
</div>
class="fragment-result__preview col-sm-4 col-12"
/>
</div>
<hr />
</div>
Expand Down Expand Up @@ -5876,6 +5875,9 @@ exports[`Snapshot 1`] = `
</p>
</small>
</div>
<ul
class="ResultList"
/>
</div>
<div
class="text-secondary fragment-result__genre col-sm-4 col-12"
Expand All @@ -5894,10 +5896,10 @@ exports[`Snapshot 1`] = `
</ul>
</div>
<div
class="col-sm-4 col-12"
class="fragment-result__record col-sm-4 col-12"
>
<ol
class="Record fragment-result__record"
class="Record"
>
<li
class="Record__entry"
Expand Down Expand Up @@ -11665,12 +11667,8 @@ exports[`Snapshot 1`] = `
</table>
</div>
<div
class="fragment-result__project-logos col-sm-4 col-12"
>
<ul
class="ResultList"
/>
</div>
class="fragment-result__preview col-sm-4 col-12"
/>
</div>
<hr />
</div>
Expand Down
7 changes: 7 additions & 0 deletions src/fragmentarium/ui/search/FragmentariumSearch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ describe('Searching fragments by transliteration', () => {
fragmentService.find
.mockReturnValueOnce(Promise.resolve(fragments[0]))
.mockReturnValueOnce(Promise.resolve(fragments[1]))
fragmentService.findThumbnail
.mockReturnValueOnce(
Promise.resolve({
blob: new Blob(['imagedata'], { type: 'image/jpeg' }),
})
)
.mockReturnValueOnce(Promise.resolve({ blob: null }))
wordService.findAll.mockReturnValue(Promise.resolve([]))
textService.findChapterDisplay
.mockReturnValueOnce(Promise.resolve(chapters[0]))
Expand Down
34 changes: 25 additions & 9 deletions src/fragmentarium/ui/search/FragmentariumSearchResult.sass
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,44 @@
&__match-info
display: flex
padding: 1em 0

.fragment-result
&__fragment-number, &__record
margin-bottom: 0
&__record
display: flex
justify-content: flex-start

.Record__entry
font-size: 80%
&__genre ul
padding-left: 0
margin-bottom: 0.2em
&__project-logos
display: flex
justify-content: start

&__header
flex-wrap: wrap-reverse

ul.ResultList
padding: 0
margin: 0

&__header
flex-wrap: wrap-reverse
img
margin: 0

&__archaeology-info
padding-bottom: 1em

p
margin-bottom: 0
padding-bottom: 1em

&__preview
display: flex
justify-content: flex-start
align-items: flex-start
a
display: contents
img
min-width: 120px
max-width: 40%

&__record, &__preview
@media (min-width: 576px)
padding-left: 10vw
Loading

0 comments on commit 0da6560

Please sign in to comment.