diff options
Diffstat (limited to 'src/state/models/media')
-rw-r--r-- | src/state/models/media/gallery.ts | 85 | ||||
-rw-r--r-- | src/state/models/media/image.ts | 85 |
2 files changed, 170 insertions, 0 deletions
diff --git a/src/state/models/media/gallery.ts b/src/state/models/media/gallery.ts new file mode 100644 index 000000000..fbe6c92a0 --- /dev/null +++ b/src/state/models/media/gallery.ts @@ -0,0 +1,85 @@ +import {makeAutoObservable, runInAction} from 'mobx' +import {RootStoreModel} from 'state/index' +import {ImageModel} from './image' +import {Image as RNImage} from 'react-native-image-crop-picker' +import {openPicker} from 'lib/media/picker' +import {getImageDim} from 'lib/media/manip' +import {getDataUriSize} from 'lib/media/util' + +export class GalleryModel { + images: ImageModel[] = [] + + constructor(public rootStore: RootStoreModel) { + makeAutoObservable(this, { + rootStore: false, + }) + } + + get isEmpty() { + return this.size === 0 + } + + get size() { + return this.images.length + } + + get paths() { + return this.images.map(image => + image.compressed === undefined ? image.path : image.compressed.path, + ) + } + + async add(image_: RNImage) { + if (this.size >= 4) { + return + } + + // Temporarily enforce uniqueness but can eventually also use index + if (!this.images.some(i => i.path === image_.path)) { + const image = new ImageModel(this.rootStore, image_) + await image.compress() + + runInAction(() => { + this.images.push(image) + }) + } + } + + async paste(uri: string) { + if (this.size >= 4) { + return + } + + const {width, height} = await getImageDim(uri) + + const image: RNImage = { + path: uri, + height, + width, + size: getDataUriSize(uri), + mime: 'image/jpeg', + } + + runInAction(() => { + this.add(image) + }) + } + + crop(image: ImageModel) { + image.crop() + } + + remove(image: ImageModel) { + const index = this.images.findIndex(image_ => image_.path === image.path) + this.images.splice(index, 1) + } + + async pick() { + const images = await openPicker(this.rootStore, { + multiple: true, + maxFiles: 4 - this.images.length, + }) + + await Promise.all(images.map(image => this.add(image))) + } +} diff --git a/src/state/models/media/image.ts b/src/state/models/media/image.ts new file mode 100644 index 000000000..584bf90cc --- /dev/null +++ b/src/state/models/media/image.ts @@ -0,0 +1,85 @@ +import {Image as RNImage} from 'react-native-image-crop-picker' +import {RootStoreModel} from 'state/index' +import {compressAndResizeImageForPost} from 'lib/media/manip' +import {makeAutoObservable, runInAction} from 'mobx' +import {openCropper} from 'lib/media/picker' +import {POST_IMG_MAX} from 'lib/constants' +import {scaleDownDimensions} from 'lib/media/util' + +// TODO: EXIF embed +// Cases to consider: ExternalEmbed +export class ImageModel implements RNImage { + path: string + mime = 'image/jpeg' + width: number + height: number + size: number + cropped?: RNImage = undefined + compressed?: RNImage = undefined + scaledWidth: number = POST_IMG_MAX.width + scaledHeight: number = POST_IMG_MAX.height + + constructor(public rootStore: RootStoreModel, image: RNImage) { + makeAutoObservable(this, { + rootStore: false, + }) + + this.path = image.path + this.width = image.width + this.height = image.height + this.size = image.size + this.calcScaledDimensions() + } + + calcScaledDimensions() { + const {width, height} = scaleDownDimensions( + {width: this.width, height: this.height}, + POST_IMG_MAX, + ) + + this.scaledWidth = width + this.scaledHeight = height + } + + async crop() { + try { + const cropped = await openCropper(this.rootStore, { + mediaType: 'photo', + path: this.path, + freeStyleCropEnabled: true, + width: this.scaledWidth, + height: this.scaledHeight, + }) + + runInAction(() => { + this.cropped = cropped + }) + } catch (err) { + this.rootStore.log.error('Failed to crop photo', err) + } + + this.compress() + } + + async compress() { + try { + const {width, height} = scaleDownDimensions( + this.cropped + ? {width: this.cropped.width, height: this.cropped.height} + : {width: this.width, height: this.height}, + POST_IMG_MAX, + ) + const compressed = await compressAndResizeImageForPost({ + ...(this.cropped === undefined ? this : this.cropped), + width, + height, + }) + + runInAction(() => { + this.compressed = compressed + }) + } catch (err) { + this.rootStore.log.error('Failed to compress photo', err) + } + } +} |