Skip to content

Commit

Permalink
Tree view threads experiment (#1480)
Browse files Browse the repository at this point in the history
* Add tree-view experiment to threads

* Fix typo

* Remove extra minimalshellmode call

* Fix to parent line rendering

* Fix extra border

* Some ui cleanup
  • Loading branch information
pfrazee authored Sep 20, 2023
1 parent d2c253a commit 1af8e83
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 53 deletions.
15 changes: 14 additions & 1 deletion src/state/models/ui/preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export class PreferencesModel {
homeFeedMergeFeedEnabled: boolean = false
threadDefaultSort: string = 'oldest'
threadFollowedUsersFirst: boolean = true
threadTreeViewEnabled: boolean = false
requireAltTextEnabled: boolean = false

// used to linearize async modifications to state
Expand Down Expand Up @@ -91,6 +92,7 @@ export class PreferencesModel {
homeFeedMergeFeedEnabled: this.homeFeedMergeFeedEnabled,
threadDefaultSort: this.threadDefaultSort,
threadFollowedUsersFirst: this.threadFollowedUsersFirst,
threadTreeViewEnabled: this.threadTreeViewEnabled,
requireAltTextEnabled: this.requireAltTextEnabled,
}
}
Expand Down Expand Up @@ -202,13 +204,20 @@ export class PreferencesModel {
) {
this.threadDefaultSort = v.threadDefaultSort
}
// check if tread followed-users-first is enabled in preferences, then hydrate
// check if thread followed-users-first is enabled in preferences, then hydrate
if (
hasProp(v, 'threadFollowedUsersFirst') &&
typeof v.threadFollowedUsersFirst === 'boolean'
) {
this.threadFollowedUsersFirst = v.threadFollowedUsersFirst
}
// check if thread treeview is enabled in preferences, then hydrate
if (
hasProp(v, 'threadTreeViewEnabled') &&
typeof v.threadTreeViewEnabled === 'boolean'
) {
this.threadTreeViewEnabled = v.threadTreeViewEnabled
}
// check if requiring alt text is enabled in preferences, then hydrate
if (
hasProp(v, 'requireAltTextEnabled') &&
Expand Down Expand Up @@ -524,6 +533,10 @@ export class PreferencesModel {
this.threadFollowedUsersFirst = !this.threadFollowedUsersFirst
}

toggleThreadTreeViewEnabled() {
this.threadTreeViewEnabled = !this.threadTreeViewEnabled
}

toggleRequireAltTextEnabled() {
this.requireAltTextEnabled = !this.requireAltTextEnabled
}
Expand Down
48 changes: 36 additions & 12 deletions src/view/com/post-thread/PostThread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const LOAD_MORE = {
const BOTTOM_COMPONENT = {
_reactKey: '__bottom_component__',
_isHighlightedPost: false,
_showBorder: true,
}
type YieldedItem =
| PostThreadItemModel
Expand All @@ -69,10 +70,12 @@ export const PostThread = observer(function PostThread({
uri,
view,
onPressReply,
treeView,
}: {
uri: string
view: PostThreadModel
onPressReply: () => void
treeView: boolean
}) {
const pal = usePalette('default')
const {isTablet} = useWebMediaQueries()
Expand All @@ -99,6 +102,13 @@ export const PostThread = observer(function PostThread({
}
return []
}, [view.isLoadingFromCache, view.thread, maxVisible])
const highlightedPostIndex = posts.findIndex(post => post._isHighlightedPost)
const showBottomBorder =
!treeView ||
// in the treeview, only show the bottom border
// if there are replies under the highlighted posts
posts.findLast(v => v instanceof PostThreadItemModel) !==
posts[highlightedPostIndex]
useSetTitle(
view.thread?.postRecord &&
`${sanitizeDisplayName(
Expand Down Expand Up @@ -135,17 +145,16 @@ export const PostThread = observer(function PostThread({
return
}

const index = posts.findIndex(post => post._isHighlightedPost)
if (index !== -1) {
if (highlightedPostIndex !== -1) {
ref.current?.scrollToIndex({
index,
index: highlightedPostIndex,
animated: false,
viewPosition: 0,
})
hasScrolledIntoView.current = true
}
}, [
posts,
highlightedPostIndex,
view.hasContent,
view.isFromCache,
view.isLoadingFromCache,
Expand Down Expand Up @@ -184,7 +193,14 @@ export const PostThread = observer(function PostThread({
</View>
)
} else if (item === REPLY_PROMPT) {
return <ComposePrompt onPressCompose={onPressReply} />
return (
<View
style={
treeView && [pal.border, {borderBottomWidth: 1, marginBottom: 6}]
}>
{isDesktopWeb && <ComposePrompt onPressCompose={onPressReply} />}
</View>
)
} else if (item === DELETED) {
return (
<View style={[pal.border, pal.viewLight, styles.itemContainer]}>
Expand Down Expand Up @@ -224,7 +240,18 @@ export const PostThread = observer(function PostThread({
// due to some complexities with how flatlist works, this is the easiest way
// I could find to get a border positioned directly under the last item
// -prf
return <View style={[pal.border, styles.bottomSpacer]} />
return (
<View
style={[
{height: 400},
showBottomBorder && {
borderTopWidth: 1,
borderColor: pal.colors.border,
},
treeView && {marginTop: 10},
]}
/>
)
} else if (item === CHILD_SPINNER) {
return (
<View style={styles.childSpinner}>
Expand All @@ -240,12 +267,13 @@ export const PostThread = observer(function PostThread({
item={item}
onPostReply={onRefresh}
hasPrecedingItem={prev?._showChildReplyLine}
treeView={treeView}
/>
)
}
return <></>
},
[onRefresh, onPressReply, pal, posts, isTablet],
[onRefresh, onPressReply, pal, posts, isTablet, treeView, showBottomBorder],
)

// loading
Expand Down Expand Up @@ -377,7 +405,7 @@ function* flattenThread(
}
}
yield post
if (isDesktopWeb && post._isHighlightedPost) {
if (post._isHighlightedPost) {
yield REPLY_PROMPT
}
if (post.replies?.length) {
Expand Down Expand Up @@ -411,8 +439,4 @@ const styles = StyleSheet.create({
paddingVertical: 10,
},
childSpinner: {},
bottomSpacer: {
height: 400,
borderTopWidth: 1,
},
})
141 changes: 103 additions & 38 deletions src/view/com/post-thread/PostThreadItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,18 @@ import {formatCount} from '../util/numeric/format'
import {TimeElapsed} from 'view/com/util/TimeElapsed'
import {makeProfileLink} from 'lib/routes/links'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'

export const PostThreadItem = observer(function PostThreadItem({
item,
onPostReply,
hasPrecedingItem,
treeView,
}: {
item: PostThreadItemModel
onPostReply: () => void
hasPrecedingItem: boolean
treeView: boolean
}) {
const pal = usePalette('default')
const store = useStores()
Expand Down Expand Up @@ -389,25 +392,28 @@ export const PostThreadItem = observer(function PostThreadItem({
</>
)
} else {
const isThreadedChild = treeView && item._depth > 0
return (
<>
<PostOuterWrapper
item={item}
hasPrecedingItem={hasPrecedingItem}
treeView={treeView}>
<PostHider
testID={`postThreadItem-by-${item.post.author.handle}`}
href={itemHref}
style={[
styles.outer,
pal.border,
pal.view,
item._showParentReplyLine && hasPrecedingItem && styles.noTopBorder,
styles.cursor,
]}
style={[pal.view]}
moderation={item.moderation.content}>
<PostSandboxWarning />

<View
style={{flexDirection: 'row', gap: 10, paddingLeft: 8, height: 16}}>
style={{
flexDirection: 'row',
gap: 10,
paddingLeft: 8,
height: isThreadedChild ? 8 : 16,
}}>
<View style={{width: 52}}>
{item._showParentReplyLine && (
{!isThreadedChild && item._showParentReplyLine && (
<View
style={[
styles.replyLine,
Expand All @@ -431,7 +437,7 @@ export const PostThreadItem = observer(function PostThreadItem({
]}>
<View style={styles.layoutAvi}>
<PreviewableUserAvatar
size={52}
size={isThreadedChild ? 24 : 52}
did={item.post.author.did}
handle={item.post.author.handle}
avatar={item.post.author.avatar}
Expand All @@ -444,7 +450,9 @@ export const PostThreadItem = observer(function PostThreadItem({
styles.replyLine,
{
flexGrow: 1,
backgroundColor: pal.colors.replyLine,
backgroundColor: isThreadedChild
? pal.colors.border
: pal.colors.replyLine,
marginTop: 4,
},
]}
Expand All @@ -464,7 +472,11 @@ export const PostThreadItem = observer(function PostThreadItem({
style={styles.alert}
/>
{item.richText?.text ? (
<View style={styles.postTextContainer}>
<View
style={[
styles.postTextContainer,
isThreadedChild && {paddingTop: 2},
]}>
<RichText
type="post-text"
richText={item.richText}
Expand Down Expand Up @@ -508,30 +520,84 @@ export const PostThreadItem = observer(function PostThreadItem({
/>
</View>
</View>
{item._hasMore ? (
<Link
style={[
styles.loadMore,
{
paddingLeft: treeView ? 44 : 70,
paddingTop: 0,
paddingBottom: treeView ? 4 : 12,
},
]}
href={itemHref}
title={itemTitle}
noFeedback>
<Text type="sm-medium" style={pal.textLight}>
More
</Text>
<FontAwesomeIcon
icon="angle-right"
color={pal.colors.textLight}
size={14}
/>
</Link>
) : undefined}
</PostHider>
{item._hasMore ? (
<Link
style={[
styles.loadMore,
{borderTopColor: pal.colors.border},
pal.view,
]}
href={itemHref}
title={itemTitle}
noFeedback>
<Text style={pal.link}>Continue thread...</Text>
<FontAwesomeIcon
icon="angle-right"
style={pal.link as FontAwesomeIconStyle}
size={18}
/>
</Link>
) : undefined}
</>
</PostOuterWrapper>
)
}
})

function PostOuterWrapper({
item,
hasPrecedingItem,
treeView,
children,
}: React.PropsWithChildren<{
item: PostThreadItemModel
hasPrecedingItem: boolean
treeView: boolean
}>) {
const {isMobile} = useWebMediaQueries()
const pal = usePalette('default')
if (treeView && item._depth > 0) {
return (
<View
style={[
pal.view,
styles.cursor,
{flexDirection: 'row', paddingLeft: 10},
]}>
{Array.from(Array(item._depth - 1)).map((_, n: number) => (
<View
key={`${item.uri}-padding-${n}`}
style={{
borderLeftWidth: 2,
borderLeftColor: pal.colors.border,
marginLeft: 19,
paddingLeft: isMobile ? 0 : 4,
}}
/>
))}
<View style={{flex: 1}}>{children}</View>
</View>
)
}
return (
<View
style={[
styles.outer,
pal.view,
pal.border,
item._showParentReplyLine && hasPrecedingItem && styles.noTopBorder,
styles.cursor,
]}>
{children}
</View>
)
}

function ExpandedPostDetails({
post,
needsTranslation,
Expand Down Expand Up @@ -600,7 +666,7 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
paddingBottom: 8,
paddingBottom: 4,
paddingRight: 10,
},
postTextLargeContainer: {
Expand Down Expand Up @@ -629,11 +695,10 @@ const styles = StyleSheet.create({
},
loadMore: {
flexDirection: 'row',
justifyContent: 'space-between',
borderTopWidth: 1,
paddingLeft: 80,
paddingRight: 20,
paddingVertical: 12,
alignItems: 'center',
justifyContent: 'flex-start',
gap: 4,
paddingHorizontal: 20,
},
replyLine: {
width: 2,
Expand Down
Loading

0 comments on commit 1af8e83

Please sign in to comment.