Move visual display logic out of PostThread generators (#2862)

* Split skeleton gen into replies and parents

Co-authored-by: Hailey <me@haileyok.com>

* Move REPLY_PROMPT out of the generator

* Move the rest of visual logic out of gen

---------

Co-authored-by: Hailey <me@haileyok.com>
zio/stable
dan 2024-02-13 22:12:33 +00:00 committed by GitHub
parent 2f6d7606b3
commit 08525b52c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 55 additions and 28 deletions

View File

@ -55,7 +55,15 @@ const CHILD_SPINNER = {_reactKey: '__child_spinner__'}
const LOAD_MORE = {_reactKey: '__load_more__'}
const BOTTOM_COMPONENT = {_reactKey: '__bottom_component__'}
type YieldedItem = ThreadPost | typeof TOP_COMPONENT | typeof REPLY_PROMPT
type YieldedItem = ThreadPost | ThreadBlocked | ThreadNotFound
type RowItem =
| YieldedItem
// TODO: TS doesn't actually enforce it's one of these, it only enforces matching shape.
| typeof TOP_COMPONENT
| typeof REPLY_PROMPT
| typeof CHILD_SPINNER
| typeof LOAD_MORE
| typeof BOTTOM_COMPONENT
export function PostThread({
uri,
@ -160,19 +168,31 @@ function PostThreadLoaded({
// construct content
const posts = React.useMemo(() => {
let arr = Array.from(
flattenThreadSkeleton(
sortThread(thread, threadViewPrefs),
hasSession,
treeView,
),
)
const root = sortThread(thread, threadViewPrefs)
let arr: RowItem[] = []
if (root.type === 'post') {
if (!root.ctx.isParentLoading) {
arr.push(TOP_COMPONENT)
for (const parent of flattenThreadParents(root, hasSession)) {
arr.push(parent)
}
}
arr.push(root)
if (!root.post.viewer?.replyDisabled) {
arr.push(REPLY_PROMPT)
}
if (root.ctx.isChildLoading) {
arr.push(CHILD_SPINNER)
} else {
for (const reply of flattenThreadReplies(root, hasSession, treeView)) {
arr.push(reply)
}
arr.push(BOTTOM_COMPONENT)
}
}
if (arr.length > maxVisible) {
arr = arr.slice(0, maxVisible).concat([LOAD_MORE])
}
if (arr.indexOf(CHILD_SPINNER) === -1) {
arr.push(BOTTOM_COMPONENT)
}
return arr
}, [thread, treeView, maxVisible, threadViewPrefs, hasSession])
@ -239,7 +259,7 @@ function PostThreadLoaded({
}, [setIsPTRing, onRefresh])
const renderItem = React.useCallback(
({item, index}: {item: YieldedItem; index: number}) => {
({item, index}: {item: RowItem; index: number}) => {
if (item === TOP_COMPONENT) {
return isTabletOrMobile ? (
<ViewHeader
@ -489,36 +509,43 @@ function isThreadBlocked(v: unknown): v is ThreadBlocked {
return !!v && typeof v === 'object' && 'type' in v && v.type === 'blocked'
}
function* flattenThreadSkeleton(
function* flattenThreadParents(
node: ThreadNode,
hasSession: boolean,
): Generator<YieldedItem, void> {
if (node.type === 'post') {
if (node.parent) {
yield* flattenThreadParents(node.parent, hasSession)
}
if (!node.ctx.isHighlightedPost) {
yield node
}
} else if (node.type === 'not-found') {
yield node
} else if (node.type === 'blocked') {
yield node
}
}
function* flattenThreadReplies(
node: ThreadNode,
hasSession: boolean,
treeView: boolean,
isTraversingReplies: boolean = false,
): Generator<YieldedItem, void> {
if (node.type === 'post') {
if (!node.ctx.isParentLoading) {
if (node.parent) {
yield* flattenThreadSkeleton(node.parent, hasSession, treeView, false)
} else if (!isTraversingReplies) {
yield TOP_COMPONENT
}
}
if (!hasSession && node.ctx.depth > 0 && hasPwiOptOut(node)) {
if (!hasSession && hasPwiOptOut(node)) {
return
}
yield node
if (node.ctx.isHighlightedPost && !node.post.viewer?.replyDisabled) {
yield REPLY_PROMPT
if (!node.ctx.isHighlightedPost) {
yield node
}
if (node.replies?.length) {
for (const reply of node.replies) {
yield* flattenThreadSkeleton(reply, hasSession, treeView, true)
yield* flattenThreadReplies(reply, hasSession, treeView)
if (!treeView && !node.ctx.isHighlightedPost) {
break
}
}
} else if (node.ctx.isChildLoading) {
yield CHILD_SPINNER
}
} else if (node.type === 'not-found') {
yield node