about summary refs log tree commit diff
path: root/src/view/com/composer/state/composer.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/composer/state/composer.ts')
-rw-r--r--src/view/com/composer/state/composer.ts199
1 files changed, 199 insertions, 0 deletions
diff --git a/src/view/com/composer/state/composer.ts b/src/view/com/composer/state/composer.ts
new file mode 100644
index 000000000..a23a5d8c8
--- /dev/null
+++ b/src/view/com/composer/state/composer.ts
@@ -0,0 +1,199 @@
+import {ImagePickerAsset} from 'expo-image-picker'
+
+import {ComposerImage, createInitialImages} from '#/state/gallery'
+import {ComposerOpts} from '#/state/shell/composer'
+import {createVideoState, VideoAction, videoReducer, VideoState} from './video'
+
+type PostRecord = {
+  uri: string
+}
+
+type ImagesMedia = {
+  type: 'images'
+  images: ComposerImage[]
+  labels: string[]
+}
+
+type VideoMedia = {
+  type: 'video'
+  video: VideoState
+}
+
+type ComposerEmbed = {
+  // TODO: Other record types.
+  record: PostRecord | undefined
+  // TODO: Other media types.
+  media: ImagesMedia | VideoMedia | undefined
+}
+
+export type ComposerState = {
+  // TODO: Other draft data.
+  embed: ComposerEmbed
+}
+
+export type ComposerAction =
+  | {type: 'embed_add_images'; images: ComposerImage[]}
+  | {type: 'embed_update_image'; image: ComposerImage}
+  | {type: 'embed_remove_image'; image: ComposerImage}
+  | {
+      type: 'embed_add_video'
+      asset: ImagePickerAsset
+      abortController: AbortController
+    }
+  | {type: 'embed_remove_video'}
+  | {type: 'embed_update_video'; videoAction: VideoAction}
+
+const MAX_IMAGES = 4
+
+export function composerReducer(
+  state: ComposerState,
+  action: ComposerAction,
+): ComposerState {
+  switch (action.type) {
+    case 'embed_add_images': {
+      if (action.images.length === 0) {
+        return state
+      }
+      const prevMedia = state.embed.media
+      let nextMedia = prevMedia
+      if (!prevMedia) {
+        nextMedia = {
+          type: 'images',
+          images: action.images.slice(0, MAX_IMAGES),
+          labels: [],
+        }
+      } else if (prevMedia.type === 'images') {
+        nextMedia = {
+          ...prevMedia,
+          images: [...prevMedia.images, ...action.images].slice(0, MAX_IMAGES),
+        }
+      }
+      return {
+        ...state,
+        embed: {
+          ...state.embed,
+          media: nextMedia,
+        },
+      }
+    }
+    case 'embed_update_image': {
+      const prevMedia = state.embed.media
+      if (prevMedia?.type === 'images') {
+        const updatedImage = action.image
+        const nextMedia = {
+          ...prevMedia,
+          images: prevMedia.images.map(img => {
+            if (img.source.id === updatedImage.source.id) {
+              return updatedImage
+            }
+            return img
+          }),
+        }
+        return {
+          ...state,
+          embed: {
+            ...state.embed,
+            media: nextMedia,
+          },
+        }
+      }
+      return state
+    }
+    case 'embed_remove_image': {
+      const prevMedia = state.embed.media
+      if (prevMedia?.type === 'images') {
+        const removedImage = action.image
+        let nextMedia: ImagesMedia | undefined = {
+          ...prevMedia,
+          images: prevMedia.images.filter(img => {
+            return img.source.id !== removedImage.source.id
+          }),
+        }
+        if (nextMedia.images.length === 0) {
+          nextMedia = undefined
+        }
+        return {
+          ...state,
+          embed: {
+            ...state.embed,
+            media: nextMedia,
+          },
+        }
+      }
+      return state
+    }
+    case 'embed_add_video': {
+      const prevMedia = state.embed.media
+      let nextMedia = prevMedia
+      if (!prevMedia) {
+        nextMedia = {
+          type: 'video',
+          video: createVideoState(action.asset, action.abortController),
+        }
+      }
+      return {
+        ...state,
+        embed: {
+          ...state.embed,
+          media: nextMedia,
+        },
+      }
+    }
+    case 'embed_update_video': {
+      const videoAction = action.videoAction
+      const prevMedia = state.embed.media
+      let nextMedia = prevMedia
+      if (prevMedia?.type === 'video') {
+        nextMedia = {
+          ...prevMedia,
+          video: videoReducer(prevMedia.video, videoAction),
+        }
+      }
+      return {
+        ...state,
+        embed: {
+          ...state.embed,
+          media: nextMedia,
+        },
+      }
+    }
+    case 'embed_remove_video': {
+      const prevMedia = state.embed.media
+      let nextMedia = prevMedia
+      if (prevMedia?.type === 'video') {
+        nextMedia = undefined
+      }
+      return {
+        ...state,
+        embed: {
+          ...state.embed,
+          media: nextMedia,
+        },
+      }
+    }
+    default:
+      return state
+  }
+}
+
+export function createComposerState({
+  initImageUris,
+}: {
+  initImageUris: ComposerOpts['imageUris']
+}): ComposerState {
+  let media: ImagesMedia | undefined
+  if (initImageUris?.length) {
+    media = {
+      type: 'images',
+      images: createInitialImages(initImageUris),
+      labels: [],
+    }
+  }
+  // TODO: initial video.
+  return {
+    embed: {
+      record: undefined,
+      media,
+    },
+  }
+}