Refactor composer state for threads (#5945)

* Refactor composer state for threads

* Remove unnecessary default case

TS can see it's exhaustive.
This commit is contained in:
dan
2024-10-29 20:27:44 +00:00
committed by GitHub
parent 3bf91eb814
commit bab44a5a13
4 changed files with 130 additions and 62 deletions
+14 -9
View File
@@ -30,14 +30,18 @@ import {
createThreadgateRecord,
threadgateAllowUISettingToAllowRecordValue,
} from '#/state/queries/threadgate'
import {ComposerDraft, EmbedDraft} from '#/view/com/composer/state/composer'
import {
EmbedDraft,
PostDraft,
ThreadDraft,
} from '#/view/com/composer/state/composer'
import {createGIFDescription} from '../gif-alt-text'
import {uploadBlob} from './upload-blob'
export {uploadBlob}
interface PostOpts {
draft: ComposerDraft
thread: ThreadDraft
replyTo?: string
onStateChange?: (state: string) => void
langs?: string[]
@@ -48,7 +52,8 @@ export async function post(
queryClient: QueryClient,
opts: PostOpts,
) {
const draft = opts.draft
const thread = opts.thread
const draft = thread.posts[0] // TODO: Support threads.
opts.onStateChange?.(t`Processing...`)
// NB -- Do not await anything here to avoid waterfalls!
@@ -111,11 +116,11 @@ export async function post(
}
// Create threadgate record
if (draft.threadgate.some(tg => tg.type !== 'everybody')) {
if (thread.threadgate.some(tg => tg.type !== 'everybody')) {
const record = createThreadgateRecord({
createdAt: date,
post: uri,
allow: threadgateAllowUISettingToAllowRecordValue(draft.threadgate),
allow: threadgateAllowUISettingToAllowRecordValue(thread.threadgate),
})
writes.push({
@@ -128,11 +133,11 @@ export async function post(
// Create postgate record
if (
draft.postgate.embeddingRules?.length ||
draft.postgate.detachedEmbeddingUris?.length
thread.postgate.embeddingRules?.length ||
thread.postgate.detachedEmbeddingUris?.length
) {
const record: AppBskyFeedPostgate.Record = {
...draft.postgate,
...thread.postgate,
$type: 'app.bsky.feed.postgate',
createdAt: date,
post: uri,
@@ -198,7 +203,7 @@ async function resolveReply(agent: BskyAgent, replyTo: string) {
async function resolveEmbed(
agent: BskyAgent,
queryClient: QueryClient,
draft: ComposerDraft,
draft: PostDraft,
onStateChange: ((state: string) => void) | undefined,
): Promise<
| AppBskyEmbedImages.Main
+41 -20
View File
@@ -114,11 +114,13 @@ import {Text as NewText} from '#/components/Typography'
import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
import {
ComposerAction,
ComposerDraft,
composerReducer,
createComposerState,
EmbedDraft,
MAX_IMAGES,
PostAction,
PostDraft,
ThreadDraft,
} from './state/composer'
import {NO_VIDEO, NoVideoState, processVideo, VideoState} from './state/video'
@@ -161,11 +163,21 @@ export const ComposePost = ({
const [publishingStage, setPublishingStage] = useState('')
const [error, setError] = useState('')
const [draft, dispatch] = useReducer(
const [composerState, composerDispatch] = useReducer(
composerReducer,
{initImageUris, initQuoteUri: initQuote?.uri, initText, initMention},
createComposerState,
)
// TODO: Display drafts for other posts in the thread.
const draft = composerState.thread.posts[composerState.activePostIndex]
const dispatch = useCallback((postAction: PostAction) => {
composerDispatch({
type: 'update_post',
postAction,
})
}, [])
const richtext = draft.richtext
let quote: string | undefined
if (draft.embed.quote) {
@@ -207,7 +219,7 @@ export const ComposePost = ({
_,
)
},
[_, agent, currentDid],
[_, agent, currentDid, dispatch],
)
// Whenever we receive an initial video uri, we should immediately run compression if necessary
@@ -333,7 +345,7 @@ export const ComposePost = ({
try {
postUri = (
await apilib.post(agent, queryClient, {
draft: draft,
thread: composerState.thread,
replyTo: replyTo?.uri,
onStateChange: setPublishingStage,
langs: toPostLanguages(langPrefs.postLanguage),
@@ -409,7 +421,7 @@ export const ComposePost = ({
[
_,
agent,
draft,
composerState.thread,
extLink,
images,
canPost,
@@ -504,8 +516,9 @@ export const ComposePost = ({
<ComposerPills
isReply={!!replyTo}
draft={draft}
dispatch={dispatch}
post={draft}
thread={composerState.thread}
dispatch={composerDispatch}
bottomBarAnimatedStyle={bottomBarAnimatedStyle}
/>
@@ -543,8 +556,8 @@ function ComposerPost({
onError,
onPublish,
}: {
draft: ComposerDraft
dispatch: (action: ComposerAction) => void
draft: PostDraft
dispatch: (action: PostAction) => void
textInput: React.Ref<TextInputRef>
isReply: boolean
canRemoveQuote: boolean
@@ -736,7 +749,7 @@ function ComposerEmbeds({
canRemoveQuote,
}: {
embed: EmbedDraft
dispatch: (action: ComposerAction) => void
dispatch: (action: PostAction) => void
clearVideo: () => void
canRemoveQuote: boolean
}) {
@@ -850,19 +863,21 @@ function ComposerEmbeds({
function ComposerPills({
isReply,
draft,
thread,
post,
dispatch,
bottomBarAnimatedStyle,
}: {
isReply: boolean
draft: ComposerDraft
thread: ThreadDraft
post: PostDraft
dispatch: (action: ComposerAction) => void
bottomBarAnimatedStyle: StyleProp<ViewStyle>
}) {
const t = useTheme()
const media = draft.embed.media
const media = post.embed.media
const hasMedia = media?.type === 'images' || media?.type === 'video'
const hasLink = !!draft.embed.link
const hasLink = !!post.embed.link
// Don't render anything if no pills are going to be displayed
if (isReply && !hasMedia && !hasLink) {
@@ -879,11 +894,11 @@ function ComposerPills({
showsHorizontalScrollIndicator={false}>
{isReply ? null : (
<ThreadgateBtn
postgate={draft.postgate}
postgate={thread.postgate}
onChangePostgate={nextPostgate => {
dispatch({type: 'update_postgate', postgate: nextPostgate})
}}
threadgateAllowUISettings={draft.threadgate}
threadgateAllowUISettings={thread.threadgate}
onChangeThreadgateAllowUISettings={nextThreadgate => {
dispatch({
type: 'update_threadgate',
@@ -895,9 +910,15 @@ function ComposerPills({
)}
{hasMedia || hasLink ? (
<LabelsBtn
labels={draft.labels}
labels={post.labels}
onChange={nextLabels => {
dispatch({type: 'update_labels', labels: nextLabels})
dispatch({
type: 'update_post',
postAction: {
type: 'update_labels',
labels: nextLabels,
},
})
}}
/>
) : null}
@@ -914,8 +935,8 @@ function ComposerFooter({
onError,
onSelectVideo,
}: {
draft: ComposerDraft
dispatch: (action: ComposerAction) => void
draft: PostDraft
dispatch: (action: PostAction) => void
graphemeLength: number
onEmojiButtonPress: () => void
onError: (error: string) => void
+2 -2
View File
@@ -21,7 +21,7 @@ import {ComposerImage, cropImage} from '#/state/gallery'
import {Text} from '#/view/com/util/text/Text'
import {useTheme} from '#/alf'
import * as Dialog from '#/components/Dialog'
import {ComposerAction} from '../state/composer'
import {PostAction} from '../state/composer'
import {EditImageDialog} from './EditImageDialog'
import {ImageAltTextDialog} from './ImageAltTextDialog'
@@ -29,7 +29,7 @@ const IMAGE_GAP = 8
interface GalleryProps {
images: ComposerImage[]
dispatch: (action: ComposerAction) => void
dispatch: (action: PostAction) => void
}
export let Gallery = (props: GalleryProps): React.ReactNode => {
+73 -31
View File
@@ -47,19 +47,15 @@ export type EmbedDraft = {
link: Link | undefined
}
export type ComposerDraft = {
export type PostDraft = {
richtext: RichText
labels: SelfLabel[]
postgate: AppBskyFeedPostgate.Record
threadgate: ThreadgateAllowUISetting[]
embed: EmbedDraft
}
export type ComposerAction =
export type PostAction =
| {type: 'update_richtext'; richtext: RichText}
| {type: 'update_labels'; labels: SelfLabel[]}
| {type: 'update_postgate'; postgate: AppBskyFeedPostgate.Record}
| {type: 'update_threadgate'; threadgate: ThreadgateAllowUISetting[]}
| {type: 'embed_add_images'; images: ComposerImage[]}
| {type: 'embed_update_image'; image: ComposerImage}
| {type: 'embed_remove_image'; image: ComposerImage}
@@ -77,12 +73,65 @@ export type ComposerAction =
| {type: 'embed_update_gif'; alt: string}
| {type: 'embed_remove_gif'}
export type ThreadDraft = {
posts: PostDraft[]
postgate: AppBskyFeedPostgate.Record
threadgate: ThreadgateAllowUISetting[]
}
export type ComposerState = {
thread: ThreadDraft
activePostIndex: number // TODO: Add actions to update this.
}
export type ComposerAction =
| {type: 'update_postgate'; postgate: AppBskyFeedPostgate.Record}
| {type: 'update_threadgate'; threadgate: ThreadgateAllowUISetting[]}
| {type: 'update_post'; postAction: PostAction}
export const MAX_IMAGES = 4
export function composerReducer(
state: ComposerDraft,
state: ComposerState,
action: ComposerAction,
): ComposerDraft {
): ComposerState {
switch (action.type) {
case 'update_postgate': {
return {
...state,
thread: {
...state.thread,
postgate: action.postgate,
},
}
}
case 'update_threadgate': {
return {
...state,
thread: {
...state.thread,
threadgate: action.threadgate,
},
}
}
case 'update_post': {
const nextPosts = [...state.thread.posts]
nextPosts[state.activePostIndex] = postReducer(
state.thread.posts[state.activePostIndex],
action.postAction,
)
return {
...state,
thread: {
...state.thread,
posts: nextPosts,
},
}
}
}
}
function postReducer(state: PostDraft, action: PostAction): PostDraft {
switch (action.type) {
case 'update_richtext': {
return {
@@ -96,18 +145,6 @@ export function composerReducer(
labels: action.labels,
}
}
case 'update_postgate': {
return {
...state,
postgate: action.postgate,
}
}
case 'update_threadgate': {
return {
...state,
threadgate: action.threadgate,
}
}
case 'embed_add_images': {
if (action.images.length === 0) {
return state
@@ -339,8 +376,6 @@ export function composerReducer(
},
}
}
default:
return state
}
}
@@ -354,7 +389,7 @@ export function createComposerState({
initMention: string | undefined
initImageUris: ComposerOpts['imageUris']
initQuoteUri: string | undefined
}): ComposerDraft {
}): ComposerState {
let media: ImagesMedia | undefined
if (initImageUris?.length) {
media = {
@@ -385,14 +420,21 @@ export function createComposerState({
: '',
})
return {
richtext: initRichText,
labels: [],
postgate: createPostgateRecord({post: ''}),
threadgate: threadgateViewToAllowUISetting(undefined),
embed: {
quote,
media,
link: undefined,
activePostIndex: 0,
thread: {
posts: [
{
richtext: initRichText,
labels: [],
embed: {
quote,
media,
link: undefined,
},
},
],
postgate: createPostgateRecord({post: ''}),
threadgate: threadgateViewToAllowUISetting(undefined),
},
}
}