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:
+14
-9
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user