diff options
Diffstat (limited to 'src/lib/media/image-sizes.ts')
-rw-r--r-- | src/lib/media/image-sizes.ts | 100 |
1 files changed, 79 insertions, 21 deletions
diff --git a/src/lib/media/image-sizes.ts b/src/lib/media/image-sizes.ts index 7a1555688..8eaa9467f 100644 --- a/src/lib/media/image-sizes.ts +++ b/src/lib/media/image-sizes.ts @@ -1,35 +1,93 @@ +import {useEffect, useState} from 'react' import {Image} from 'react-native' import type {Dimensions} from '#/lib/media/types' -const sizes: Map<string, Dimensions> = new Map() +type CacheStorageItem<T> = {key: string; value: T} +const createCache = <T>(cacheSize: number) => ({ + _storage: [] as CacheStorageItem<T>[], + get(key: string) { + const {value} = + this._storage.find(({key: storageKey}) => storageKey === key) || {} + return value + }, + set(key: string, value: T) { + if (this._storage.length >= cacheSize) { + this._storage.shift() + } + this._storage.push({key, value}) + }, +}) + +const sizes = createCache<Dimensions>(50) const activeRequests: Map<string, Promise<Dimensions>> = new Map() export function get(uri: string): Dimensions | undefined { return sizes.get(uri) } -export async function fetch(uri: string): Promise<Dimensions> { - const Dimensions = sizes.get(uri) - if (Dimensions) { - return Dimensions +export function fetch(uri: string): Promise<Dimensions> { + const dims = sizes.get(uri) + if (dims) { + return Promise.resolve(dims) + } + const activeRequest = activeRequests.get(uri) + if (activeRequest) { + return activeRequest + } + const prom = new Promise<Dimensions>((resolve, reject) => { + Image.getSize( + uri, + (width: number, height: number) => { + const size = {width, height} + sizes.set(uri, size) + resolve(size) + }, + (err: any) => { + console.error('Failed to fetch image dimensions for', uri, err) + reject(new Error('Could not fetch dimensions')) + }, + ) + }).finally(() => { + activeRequests.delete(uri) + }) + activeRequests.set(uri, prom) + return prom +} + +export function useImageDimensions({ + src, + knownDimensions, +}: { + src: string + knownDimensions: Dimensions | null +}): [number | undefined, Dimensions | undefined] { + const [dims, setDims] = useState(() => knownDimensions ?? get(src)) + const [prevSrc, setPrevSrc] = useState(src) + if (src !== prevSrc) { + setDims(knownDimensions ?? get(src)) + setPrevSrc(src) } - const prom = - activeRequests.get(uri) || - new Promise<Dimensions>(resolve => { - Image.getSize( - uri, - (width: number, height: number) => resolve({width, height}), - (err: any) => { - console.error('Failed to fetch image dimensions for', uri, err) - resolve({width: 0, height: 0}) - }, - ) + useEffect(() => { + let aborted = false + if (dims !== undefined) return + fetch(src).then(newDims => { + if (aborted) return + setDims(newDims) }) - activeRequests.set(uri, prom) - const res = await prom - activeRequests.delete(uri) - sizes.set(uri, res) - return res + return () => { + aborted = true + } + }, [dims, setDims, src]) + + let aspectRatio: number | undefined + if (dims) { + aspectRatio = dims.width / dims.height + if (Number.isNaN(aspectRatio)) { + aspectRatio = undefined + } + } + + return [aspectRatio, dims] } |