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
This commit is contained in:
+15
-17
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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'
|
||||
@@ -40,6 +42,16 @@ export type VideoAction =
|
||||
height: number
|
||||
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
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user