about summary refs log tree commit diff
path: root/src/lib/media/picker.web.tsx
diff options
context:
space:
mode:
authorMichael Staub <michael.staub@brightmachines.com>2023-02-23 16:34:25 -0800
committerMichael Staub <michael.staub@brightmachines.com>2023-02-23 16:34:25 -0800
commit693cbb9f18eeec48ea6ed3eb03ff3a96ca6ec7dc (patch)
tree192494fe0751aa279209f447587c311efcd33668 /src/lib/media/picker.web.tsx
parent23f07d8def1f4384022c7fecd0d7eac0ba8b2efc (diff)
parentbbd0b03a46b1087ecca17219441d060c2be69de2 (diff)
downloadvoidsky-693cbb9f18eeec48ea6ed3eb03ff3a96ca6ec7dc.tar.zst
Merge branch 'rnw' of github.com:bluesky-social/social-app into rnw
Diffstat (limited to 'src/lib/media/picker.web.tsx')
-rw-r--r--src/lib/media/picker.web.tsx144
1 files changed, 144 insertions, 0 deletions
diff --git a/src/lib/media/picker.web.tsx b/src/lib/media/picker.web.tsx
new file mode 100644
index 000000000..746feaedd
--- /dev/null
+++ b/src/lib/media/picker.web.tsx
@@ -0,0 +1,144 @@
+/// <reference lib="dom" />
+
+import {PickerOpts, CameraOpts, CropperOpts, PickedMedia} from './types'
+export type {PickedMedia} from './types'
+import {RootStoreModel} from 'state/index'
+import {
+  scaleDownDimensions,
+  getImageDim,
+  Dim,
+  compressIfNeeded,
+  moveToPremanantPath,
+} from 'lib/media/manip'
+
+interface PickedFile {
+  uri: string
+  path: string
+  size: number
+}
+
+export async function openPicker(
+  _store: RootStoreModel,
+  opts: PickerOpts,
+): Promise<PickedMedia[]> {
+  const res = await selectFile(opts)
+  const dim = await getImageDim(res.uri)
+  const mime = extractDataUriMime(res.uri)
+  return [
+    {
+      mediaType: 'photo',
+      path: res.uri,
+      mime,
+      size: res.size,
+      width: dim.width,
+      height: dim.height,
+    },
+  ]
+}
+
+export async function openCamera(
+  _store: RootStoreModel,
+  _opts: CameraOpts,
+): Promise<PickedMedia> {
+  // const mediaType = opts.mediaType || 'photo' TODO
+  throw new Error('TODO')
+}
+
+export async function openCropper(
+  store: RootStoreModel,
+  opts: CropperOpts,
+): Promise<PickedMedia> {
+  // TODO handle more opts
+  return new Promise((resolve, reject) => {
+    store.shell.openModal({
+      name: 'crop-image',
+      uri: opts.path,
+      onSelect: (img?: PickedMedia) => {
+        if (img) {
+          resolve(img)
+        } else {
+          reject(new Error('Canceled'))
+        }
+      },
+    })
+  })
+}
+
+export async function pickImagesFlow(
+  store: RootStoreModel,
+  maxFiles: number,
+  maxDim: Dim,
+  maxSize: number,
+) {
+  const items = await openPicker(store, {
+    multiple: true,
+    maxFiles,
+    mediaType: 'photo',
+  })
+  const result = []
+  for (const image of items) {
+    result.push(
+      await cropAndCompressFlow(store, image.path, image, maxDim, maxSize),
+    )
+  }
+  return result
+}
+
+export async function cropAndCompressFlow(
+  store: RootStoreModel,
+  path: string,
+  imgDim: Dim,
+  maxDim: Dim,
+  maxSize: number,
+) {
+  // choose target dimensions based on the original
+  // this causes the photo cropper to start with the full image "selected"
+  const {width, height} = scaleDownDimensions(imgDim, maxDim)
+  const cropperRes = await openCropper(store, {
+    mediaType: 'photo',
+    path,
+    freeStyleCropEnabled: true,
+    width,
+    height,
+  })
+
+  const img = await compressIfNeeded(cropperRes, maxSize)
+  const permanentPath = await moveToPremanantPath(img.path)
+  return permanentPath
+}
+
+// helpers
+// =
+
+function selectFile(opts: PickerOpts): Promise<PickedFile> {
+  return new Promise((resolve, reject) => {
+    var input = document.createElement('input')
+    input.type = 'file'
+    input.accept = opts.mediaType === 'photo' ? 'image/*' : '*/*'
+    input.onchange = e => {
+      const target = e.target as HTMLInputElement
+      const file = target?.files?.[0]
+      if (!file) {
+        return reject(new Error('Canceled'))
+      }
+
+      var reader = new FileReader()
+      reader.readAsDataURL(file)
+      reader.onload = readerEvent => {
+        if (!readerEvent.target) {
+          return reject(new Error('Canceled'))
+        }
+        resolve({
+          uri: readerEvent.target.result as string,
+          path: file.name,
+          size: file.size,
+        })
+      }
+    }
+    input.click()
+  })
+}
+
+function extractDataUriMime(uri: string): string {
+  return uri.substring(uri.indexOf(':') + 1, uri.indexOf(';'))
+}