about summary refs log tree commit diff
path: root/src/lib/media/image-sizes.ts
blob: 8eaa9467f94fae92dd94b6ca027cc03d07298d6c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import {useEffect, useState} from 'react'
import {Image} from 'react-native'

import type {Dimensions} from '#/lib/media/types'

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 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)
  }

  useEffect(() => {
    let aborted = false
    if (dims !== undefined) return
    fetch(src).then(newDims => {
      if (aborted) return
      setDims(newDims)
    })
    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]
}