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