diff options
author | dan <dan.abramov@gmail.com> | 2024-11-04 21:28:27 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-04 21:28:27 +0000 |
commit | 174988bc5ab00774d200a882312985f55d903d81 (patch) | |
tree | af52d6f05093ceeea3e3293db0cbab3d5cf43156 /src/lib/media/image-sizes.ts | |
parent | ac9d910e1e77c559eff8b32cd8412335f41074f1 (diff) | |
download | voidsky-174988bc5ab00774d200a882312985f55d903d81.tar.zst |
Unify dimensions cache between lightbox and feed (#6047)
* Remove useless memo * Use explicit values when useImageAspectRatio doesn't know It's not very good that you can't distingiush when we haven't loaded vs when we're certain. This shifts the burden of dealing with missing values to the caller. * Check cache early * Handle src change * Rewrite image-sizes.fetch to avoid mixing async styles * Make image-sizes LRU Code is copy paste from useImageDimensions.ts * Rm unused fields * Derive aspect on the fly * Factor useImageDimensions out of useImageAspectRatio * Move useImageDimensions into image-sizes * Make lightbox use the same cache * Wire up known dimensions to the lightbox * Handle division by zero in the hook * Use safe aspect for lightbox calculations
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] } |