about summary refs log tree commit diff
path: root/src/lib/media/image-sizes.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/media/image-sizes.ts')
-rw-r--r--src/lib/media/image-sizes.ts100
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]
 }