about summary refs log tree commit diff
diff options
context:
space:
mode:
authordan <dan.abramov@gmail.com>2024-10-05 03:45:34 +0900
committerGitHub <noreply@github.com>2024-10-05 03:45:34 +0900
commit09caf327d9809311f78e7f0d5c2f7213924e53ae (patch)
tree259a25ae18baa208d2e2089503be6c0fbfaff067
parent282db85c069841ef57bb3a0a7c57a93d7d31939a (diff)
downloadvoidsky-09caf327d9809311f78e7f0d5c2f7213924e53ae.tar.zst
Make composer reducer source of truth for images/video when publishing (#5595)
* Move caption and altText state into video reducer

* Make composer state source of truth for images and video publish
-rw-r--r--src/lib/api/index.ts32
-rw-r--r--src/view/com/composer/Composer.tsx47
-rw-r--r--src/view/com/composer/state/video.ts48
-rw-r--r--src/view/com/composer/videos/SubtitleDialog.tsx12
4 files changed, 89 insertions, 50 deletions
diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts
index 8b7925004..c7608ae55 100644
--- a/src/lib/api/index.ts
+++ b/src/lib/api/index.ts
@@ -1,5 +1,4 @@
 import {
-  AppBskyEmbedDefs,
   AppBskyEmbedExternal,
   AppBskyEmbedImages,
   AppBskyEmbedRecord,
@@ -7,7 +6,6 @@ import {
   AppBskyEmbedVideo,
   AppBskyFeedPostgate,
   AtUri,
-  BlobRef,
   BskyAgent,
   ComAtprotoLabelDefs,
   RichText,
@@ -46,14 +44,7 @@ interface PostOpts {
     uri: string
     cid: string
   }
-  video?: {
-    blobRef: BlobRef
-    altText: string
-    captions: {lang: string; file: File}[]
-    aspectRatio?: AppBskyEmbedDefs.AspectRatio
-  }
   extLink?: ExternalEmbedDraft
-  images?: ComposerImage[]
   labels?: string[]
   threadgate: ThreadgateAllowUISetting[]
   postgate: AppBskyFeedPostgate.Record
@@ -230,13 +221,15 @@ async function resolveMedia(
   | AppBskyEmbedVideo.Main
   | undefined
 > {
-  if (opts.images?.length) {
+  const state = opts.composerState
+  const media = state.embed.media
+  if (media?.type === 'images') {
     logger.debug(`Uploading images`, {
-      count: opts.images.length,
+      count: media.images.length,
     })
     opts.onStateChange?.(`Uploading images...`)
     const images: AppBskyEmbedImages.Image[] = await Promise.all(
-      opts.images.map(async (image, i) => {
+      media.images.map(async (image, i) => {
         logger.debug(`Compressing image #${i}`)
         const {path, width, height, mime} = await compressImage(image)
         logger.debug(`Uploading image #${i}`)
@@ -253,9 +246,10 @@ async function resolveMedia(
       images,
     }
   }
-  if (opts.video) {
+  if (media?.type === 'video' && media.video.status === 'done') {
+    const video = media.video
     const captions = await Promise.all(
-      opts.video.captions
+      video.captions
         .filter(caption => caption.lang !== '')
         .map(async caption => {
           const {data} = await agent.uploadBlob(caption.file, {
@@ -266,13 +260,17 @@ async function resolveMedia(
     )
     return {
       $type: 'app.bsky.embed.video',
-      video: opts.video.blobRef,
-      alt: opts.video.altText || undefined,
+      video: video.pendingPublish.blobRef,
+      alt: video.altText || undefined,
       captions: captions.length === 0 ? undefined : captions,
-      aspectRatio: opts.video.aspectRatio,
+      aspectRatio: {
+        width: video.asset.width,
+        height: video.asset.height,
+      },
     }
   }
   if (opts.extLink) {
+    // TODO: Read this from composer state as well.
     if (opts.extLink.embed) {
       return undefined
     }
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index b5f2d84d4..227964907 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -184,9 +184,6 @@ export const ComposePost = ({
     initQuote,
   )
 
-  const [videoAltText, setVideoAltText] = useState('')
-  const [captions, setCaptions] = useState<{lang: string; file: File}[]>([])
-
   // TODO: Move more state here.
   const [composerState, dispatch] = useReducer(
     composerReducer,
@@ -422,10 +419,9 @@ export const ComposePost = ({
       try {
         postUri = (
           await apilib.post(agent, {
-            composerState, // TODO: not used yet.
+            composerState, // TODO: move more state here.
             rawText: richtext.text,
             replyTo: replyTo?.uri,
-            images,
             quote,
             extLink,
             labels,
@@ -433,18 +429,6 @@ export const ComposePost = ({
             postgate,
             onStateChange: setProcessingState,
             langs: toPostLanguages(langPrefs.postLanguage),
-            video:
-              videoState.status === 'done'
-                ? {
-                    blobRef: videoState.pendingPublish.blobRef,
-                    altText: videoAltText,
-                    captions: captions,
-                    aspectRatio: {
-                      width: videoState.asset.width,
-                      height: videoState.asset.height,
-                    },
-                  }
-                : undefined,
           })
         ).uri
         try {
@@ -522,7 +506,6 @@ export const ComposePost = ({
     [
       _,
       agent,
-      captions,
       composerState,
       extLink,
       images,
@@ -541,9 +524,7 @@ export const ComposePost = ({
       setExtLink,
       setLangPrefs,
       threadgateAllowUISettings,
-      videoAltText,
       videoState.asset,
-      videoState.pendingPublish,
       videoState.status,
     ],
   )
@@ -811,10 +792,28 @@ export const ComposePost = ({
                     />
                   ) : null)}
                 <SubtitleDialogBtn
-                  defaultAltText={videoAltText}
-                  saveAltText={setVideoAltText}
-                  captions={captions}
-                  setCaptions={setCaptions}
+                  defaultAltText={videoState.altText}
+                  saveAltText={altText =>
+                    dispatch({
+                      type: 'embed_update_video',
+                      videoAction: {
+                        type: 'update_alt_text',
+                        altText,
+                        signal: videoState.abortController.signal,
+                      },
+                    })
+                  }
+                  captions={videoState.captions}
+                  setCaptions={updater => {
+                    dispatch({
+                      type: 'embed_update_video',
+                      videoAction: {
+                        type: 'update_captions',
+                        updater,
+                        signal: videoState.abortController.signal,
+                      },
+                    })
+                  }}
                 />
               </Animated.View>
             )}
diff --git a/src/view/com/composer/state/video.ts b/src/view/com/composer/state/video.ts
index 269505657..e29687200 100644
--- a/src/view/com/composer/state/video.ts
+++ b/src/view/com/composer/state/video.ts
@@ -4,8 +4,6 @@ import {JobStatus} from '@atproto/api/dist/client/types/app/bsky/video/defs'
 import {I18n} from '@lingui/core'
 import {msg} from '@lingui/macro'
 
-import {createVideoAgent} from '#/lib/media/video/util'
-import {uploadVideo} from '#/lib/media/video/upload'
 import {AbortError} from '#/lib/async/cancelable'
 import {compressVideo} from '#/lib/media/video/compress'
 import {
@@ -14,8 +12,12 @@ import {
   VideoTooLargeError,
 } from '#/lib/media/video/errors'
 import {CompressedVideo} from '#/lib/media/video/types'
+import {uploadVideo} from '#/lib/media/video/upload'
+import {createVideoAgent} from '#/lib/media/video/util'
 import {logger} from '#/logger'
 
+type CaptionsTrack = {lang: string; file: File}
+
 export type VideoAction =
   | {
       type: 'compressing_to_uploading'
@@ -41,6 +43,16 @@ export type VideoAction =
       signal: AbortSignal
     }
   | {
+      type: 'update_alt_text'
+      altText: string
+      signal: AbortSignal
+    }
+  | {
+      type: 'update_captions'
+      updater: (prev: CaptionsTrack[]) => CaptionsTrack[]
+      signal: AbortSignal
+    }
+  | {
       type: 'update_job_status'
       jobStatus: AppBskyVideoDefs.JobStatus
       signal: AbortSignal
@@ -57,6 +69,8 @@ export const NO_VIDEO = Object.freeze({
   video: undefined,
   jobId: undefined,
   pendingPublish: undefined,
+  altText: '',
+  captions: [],
 })
 
 export type NoVideoState = typeof NO_VIDEO
@@ -70,6 +84,8 @@ type ErrorState = {
   jobId: string | null
   error: string
   pendingPublish?: undefined
+  altText: string
+  captions: CaptionsTrack[]
 }
 
 type CompressingState = {
@@ -80,6 +96,8 @@ type CompressingState = {
   video?: undefined
   jobId?: undefined
   pendingPublish?: undefined
+  altText: string
+  captions: CaptionsTrack[]
 }
 
 type UploadingState = {
@@ -90,6 +108,8 @@ type UploadingState = {
   video: CompressedVideo
   jobId?: undefined
   pendingPublish?: undefined
+  altText: string
+  captions: CaptionsTrack[]
 }
 
 type ProcessingState = {
@@ -101,6 +121,8 @@ type ProcessingState = {
   jobId: string
   jobStatus: AppBskyVideoDefs.JobStatus | null
   pendingPublish?: undefined
+  altText: string
+  captions: CaptionsTrack[]
 }
 
 type DoneState = {
@@ -111,6 +133,8 @@ type DoneState = {
   video: CompressedVideo
   jobId?: undefined
   pendingPublish: {blobRef: BlobRef; mutableProcessed: boolean}
+  altText: string
+  captions: CaptionsTrack[]
 }
 
 export type VideoState =
@@ -129,6 +153,8 @@ export function createVideoState(
     progress: 0,
     abortController,
     asset,
+    altText: '',
+    captions: [],
   }
 }
 
@@ -149,6 +175,8 @@ export function videoReducer(
       asset: state.asset ?? null,
       video: state.video ?? null,
       jobId: state.jobId ?? null,
+      altText: state.altText,
+      captions: state.captions,
     }
   } else if (action.type === 'update_progress') {
     if (state.status === 'compressing' || state.status === 'uploading') {
@@ -164,6 +192,16 @@ export function videoReducer(
         asset: {...state.asset, width: action.width, height: action.height},
       }
     }
+  } else if (action.type === 'update_alt_text') {
+    return {
+      ...state,
+      altText: action.altText,
+    }
+  } else if (action.type === 'update_captions') {
+    return {
+      ...state,
+      captions: action.updater(state.captions),
+    }
   } else if (action.type === 'compressing_to_uploading') {
     if (state.status === 'compressing') {
       return {
@@ -172,6 +210,8 @@ export function videoReducer(
         abortController: state.abortController,
         asset: state.asset,
         video: action.video,
+        altText: state.altText,
+        captions: state.captions,
       }
     }
     return state
@@ -185,6 +225,8 @@ export function videoReducer(
         video: state.video,
         jobId: action.jobId,
         jobStatus: null,
+        altText: state.altText,
+        captions: state.captions,
       }
     }
   } else if (action.type === 'update_job_status') {
@@ -210,6 +252,8 @@ export function videoReducer(
           blobRef: action.blobRef,
           mutableProcessed: false,
         },
+        altText: state.altText,
+        captions: state.captions,
       }
     }
   }
diff --git a/src/view/com/composer/videos/SubtitleDialog.tsx b/src/view/com/composer/videos/SubtitleDialog.tsx
index c07fdfc56..f66684d4e 100644
--- a/src/view/com/composer/videos/SubtitleDialog.tsx
+++ b/src/view/com/composer/videos/SubtitleDialog.tsx
@@ -22,13 +22,13 @@ import {SubtitleFilePicker} from './SubtitleFilePicker'
 
 const MAX_NUM_CAPTIONS = 1
 
+type CaptionsTrack = {lang: string; file: File}
+
 interface Props {
   defaultAltText: string
-  captions: {lang: string; file: File}[]
+  captions: CaptionsTrack[]
   saveAltText: (altText: string) => void
-  setCaptions: React.Dispatch<
-    React.SetStateAction<{lang: string; file: File}[]>
-  >
+  setCaptions: (updater: (prev: CaptionsTrack[]) => CaptionsTrack[]) => void
 }
 
 export function SubtitleDialogBtn(props: Props) {
@@ -198,9 +198,7 @@ function SubtitleFileRow({
   language: string
   file: File
   otherLanguages: {code2: string; code3: string; name: string}[]
-  setCaptions: React.Dispatch<
-    React.SetStateAction<{lang: string; file: File}[]>
-  >
+  setCaptions: (updater: (prev: CaptionsTrack[]) => CaptionsTrack[]) => void
   style: StyleProp<ViewStyle>
 }) {
   const {_} = useLingui()