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:
@@ -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: [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user