Thread composer UI (#6050)

* Basic adding of posts

* Switch active post on focus

* Conditionally show plus button

* Insert posts midthread

* Track active/inactive post

* Delete posts in a thread

* Focus after deletion

* Tweak empty post detection

* Mix height for active only

* Move toolbar with post on web

* Fix footer positioning

* Post All button

* Fix reply to positioning

* Improve memoization

* Improve memoization for clearVideo

* Remove unnecessary argument

* Add some manual memoization to fix re-renders

* Scroll to bottom on add new

* Fix opacity on Android

* Add backdrop

* Fix videos

* Check alt for video too

* Clear pending publish on error

* Fork alt message by type

* Separate placeholder for next posts

* Limit hitslop to avoid clashes
This commit is contained in:
dan
2024-11-01 03:45:55 +00:00
committed by GitHub
parent 01c9ac0e13
commit 7a08d61d88
11 changed files with 470 additions and 222 deletions
+80 -1
View File
@@ -85,7 +85,9 @@ export type ThreadDraft = {
export type ComposerState = {
thread: ThreadDraft
activePostIndex: number // TODO: Add actions to update this.
activePostIndex: number
mutableNeedsFocusActive: boolean
mutableNeedsScrollToBottom: boolean
}
export type ComposerAction =
@@ -96,6 +98,17 @@ export type ComposerAction =
postId: string
postAction: PostAction
}
| {
type: 'add_post'
}
| {
type: 'remove_post'
postId: string
}
| {
type: 'focus_post'
postId: string
}
export const MAX_IMAGES = 4
@@ -142,6 +155,69 @@ export function composerReducer(
},
}
}
case 'add_post': {
const activePostIndex = state.activePostIndex
const isAtTheEnd = activePostIndex === state.thread.posts.length - 1
const nextPosts = [...state.thread.posts]
nextPosts.splice(activePostIndex + 1, 0, {
id: nanoid(),
richtext: new RichText({text: ''}),
shortenedGraphemeLength: 0,
labels: [],
embed: {
quote: undefined,
media: undefined,
link: undefined,
},
})
return {
...state,
mutableNeedsScrollToBottom: isAtTheEnd,
thread: {
...state.thread,
posts: nextPosts,
},
}
}
case 'remove_post': {
if (state.thread.posts.length < 2) {
return state
}
let nextActivePostIndex = state.activePostIndex
const indexToRemove = state.thread.posts.findIndex(
p => p.id === action.postId,
)
let nextPosts = [...state.thread.posts]
if (indexToRemove !== -1) {
const postToRemove = state.thread.posts[indexToRemove]
if (postToRemove.embed.media?.type === 'video') {
postToRemove.embed.media.video.abortController.abort()
}
nextPosts.splice(indexToRemove, 1)
nextActivePostIndex = Math.max(0, indexToRemove - 1)
}
return {
...state,
activePostIndex: nextActivePostIndex,
mutableNeedsFocusActive: true,
thread: {
...state.thread,
posts: nextPosts,
},
}
}
case 'focus_post': {
const nextActivePostIndex = state.thread.posts.findIndex(
p => p.id === action.postId,
)
if (nextActivePostIndex === -1) {
return state
}
return {
...state,
activePostIndex: nextActivePostIndex,
}
}
}
}
@@ -275,6 +351,7 @@ function postReducer(state: PostDraft, action: PostAction): PostDraft {
const prevMedia = state.embed.media
let nextMedia = prevMedia
if (prevMedia?.type === 'video') {
prevMedia.video.abortController.abort()
nextMedia = undefined
}
let nextLabels = state.labels
@@ -436,6 +513,8 @@ export function createComposerState({
})
return {
activePostIndex: 0,
mutableNeedsFocusActive: false,
mutableNeedsScrollToBottom: false,
thread: {
posts: [
{