Add Shadow type (#1900)

zio/stable
Paul Frazee 2023-11-14 12:10:39 -08:00 committed by GitHub
parent 00f8c8b06d
commit 8e4a3ad5b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 54 additions and 21 deletions

View File

@ -1,6 +1,8 @@
import {useEffect, useState, useCallback, useRef} from 'react' import {useEffect, useState, useCallback, useRef} from 'react'
import EventEmitter from 'eventemitter3' import EventEmitter from 'eventemitter3'
import {AppBskyFeedDefs} from '@atproto/api' import {AppBskyFeedDefs} from '@atproto/api'
import {Shadow} from './types'
export type {Shadow} from './types'
const emitter = new EventEmitter() const emitter = new EventEmitter()
@ -22,7 +24,7 @@ interface CacheEntry {
export function usePostShadow( export function usePostShadow(
post: AppBskyFeedDefs.PostView, post: AppBskyFeedDefs.PostView,
ifAfterTS: number, ifAfterTS: number,
): AppBskyFeedDefs.PostView | typeof POST_TOMBSTONE { ): Shadow<AppBskyFeedDefs.PostView> | typeof POST_TOMBSTONE {
const [state, setState] = useState<CacheEntry>({ const [state, setState] = useState<CacheEntry>({
ts: Date.now(), ts: Date.now(),
value: fromPost(post), value: fromPost(post),
@ -53,13 +55,21 @@ export function usePostShadow(
firstRun.current = false firstRun.current = false
}, [post]) }, [post])
return state.ts > ifAfterTS ? mergeShadow(post, state.value) : post return state.ts > ifAfterTS
? mergeShadow(post, state.value)
: {...post, isShadowed: true}
} }
export function updatePostShadow(uri: string, value: Partial<PostShadow>) { export function updatePostShadow(uri: string, value: Partial<PostShadow>) {
emitter.emit(uri, value) emitter.emit(uri, value)
} }
export function isPostShadowed(
v: AppBskyFeedDefs.PostView | Shadow<AppBskyFeedDefs.PostView>,
): v is Shadow<AppBskyFeedDefs.PostView> {
return 'isShadowed' in v && !!v.isShadowed
}
function fromPost(post: AppBskyFeedDefs.PostView): PostShadow { function fromPost(post: AppBskyFeedDefs.PostView): PostShadow {
return { return {
likeUri: post.viewer?.like, likeUri: post.viewer?.like,
@ -73,7 +83,7 @@ function fromPost(post: AppBskyFeedDefs.PostView): PostShadow {
function mergeShadow( function mergeShadow(
post: AppBskyFeedDefs.PostView, post: AppBskyFeedDefs.PostView,
shadow: PostShadow, shadow: PostShadow,
): AppBskyFeedDefs.PostView | typeof POST_TOMBSTONE { ): Shadow<AppBskyFeedDefs.PostView> | typeof POST_TOMBSTONE {
if (shadow.isDeleted) { if (shadow.isDeleted) {
return POST_TOMBSTONE return POST_TOMBSTONE
} }
@ -86,5 +96,6 @@ function mergeShadow(
like: shadow.likeUri, like: shadow.likeUri,
repost: shadow.repostUri, repost: shadow.repostUri,
}, },
isShadowed: true,
} }
} }

View File

@ -1,6 +1,8 @@
import {useEffect, useState, useCallback, useRef} from 'react' import {useEffect, useState, useCallback, useRef} from 'react'
import EventEmitter from 'eventemitter3' import EventEmitter from 'eventemitter3'
import {AppBskyActorDefs} from '@atproto/api' import {AppBskyActorDefs} from '@atproto/api'
import {Shadow} from './types'
export type {Shadow} from './types'
const emitter = new EventEmitter() const emitter = new EventEmitter()
@ -20,10 +22,10 @@ type ProfileView =
| AppBskyActorDefs.ProfileViewBasic | AppBskyActorDefs.ProfileViewBasic
| AppBskyActorDefs.ProfileViewDetailed | AppBskyActorDefs.ProfileViewDetailed
export function useProfileShadow<T extends ProfileView>( export function useProfileShadow(
profile: T, profile: ProfileView,
ifAfterTS: number, ifAfterTS: number,
): T { ): Shadow<ProfileView> {
const [state, setState] = useState<CacheEntry>({ const [state, setState] = useState<CacheEntry>({
ts: Date.now(), ts: Date.now(),
value: fromProfile(profile), value: fromProfile(profile),
@ -54,7 +56,9 @@ export function useProfileShadow<T extends ProfileView>(
firstRun.current = false firstRun.current = false
}, [profile]) }, [profile])
return state.ts > ifAfterTS ? mergeShadow(profile, state.value) : profile return state.ts > ifAfterTS
? mergeShadow(profile, state.value)
: {...profile, isShadowed: true}
} }
export function updateProfileShadow( export function updateProfileShadow(
@ -64,6 +68,12 @@ export function updateProfileShadow(
emitter.emit(uri, value) emitter.emit(uri, value)
} }
export function isProfileShadowed<T extends ProfileView>(
v: T | Shadow<T>,
): v is Shadow<T> {
return 'isShadowed' in v && !!v.isShadowed
}
function fromProfile(profile: ProfileView): ProfileShadow { function fromProfile(profile: ProfileView): ProfileShadow {
return { return {
followingUri: profile.viewer?.following, followingUri: profile.viewer?.following,
@ -72,10 +82,10 @@ function fromProfile(profile: ProfileView): ProfileShadow {
} }
} }
function mergeShadow<T extends ProfileView>( function mergeShadow(
profile: T, profile: ProfileView,
shadow: ProfileShadow, shadow: ProfileShadow,
): T { ): Shadow<ProfileView> {
return { return {
...profile, ...profile,
viewer: { viewer: {
@ -84,5 +94,6 @@ function mergeShadow<T extends ProfileView>(
muted: shadow.muted, muted: shadow.muted,
blocking: shadow.blockingUri, blocking: shadow.blockingUri,
}, },
isShadowed: true,
} }
} }

1
src/state/cache/types.ts vendored 100644
View File

@ -0,0 +1 @@
export type Shadow<T> = T & {isShadowed: true}

View File

@ -13,7 +13,7 @@ import Animated, {FadeInRight} from 'react-native-reanimated'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {Trans} from '@lingui/macro' import {Trans} from '@lingui/macro'
import {useProfileShadow} from '#/state/cache/profile-shadow' import {Shadow, useProfileShadow} from '#/state/cache/profile-shadow'
import { import {
useProfileFollowMutation, useProfileFollowMutation,
useProfileUnfollowMutation, useProfileUnfollowMutation,
@ -66,7 +66,14 @@ export function ProfileCard({
profile, profile,
onFollowStateChange, onFollowStateChange,
moderation, moderation,
}: Omit<Props, 'dataUpdatedAt'>) { }: {
profile: Shadow<SuggestedActor>
moderation: ProfileModeration
onFollowStateChange: (props: {
did: string
following: boolean
}) => Promise<void>
}) {
const {track} = useAnalytics() const {track} = useAnalytics()
const pal = usePalette('default') const pal = usePalette('default')
const [addingMoreSuggestions, setAddingMoreSuggestions] = const [addingMoreSuggestions, setAddingMoreSuggestions] =

View File

@ -37,9 +37,9 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {MAX_POST_LINES} from 'lib/constants' import {MAX_POST_LINES} from 'lib/constants'
import {Trans} from '@lingui/macro' import {Trans} from '@lingui/macro'
import {useLanguagePrefs} from '#/state/preferences' import {useLanguagePrefs} from '#/state/preferences'
import {usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
import {useComposerControls} from '#/state/shell/composer' import {useComposerControls} from '#/state/shell/composer'
import {useModerationOpts} from '#/state/queries/preferences' import {useModerationOpts} from '#/state/queries/preferences'
import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
export function PostThreadItem({ export function PostThreadItem({
post, post,
@ -132,7 +132,7 @@ function PostThreadItemLoaded({
hasPrecedingItem, hasPrecedingItem,
onPostReply, onPostReply,
}: { }: {
post: AppBskyFeedDefs.PostView post: Shadow<AppBskyFeedDefs.PostView>
record: AppBskyFeedPost.Record record: AppBskyFeedPost.Record
richText: RichTextAPI richText: RichTextAPI
moderation: PostModeration moderation: PostModeration

View File

@ -25,8 +25,8 @@ import {makeProfileLink} from 'lib/routes/links'
import {MAX_POST_LINES} from 'lib/constants' import {MAX_POST_LINES} from 'lib/constants'
import {countLines} from 'lib/strings/helpers' import {countLines} from 'lib/strings/helpers'
import {useModerationOpts} from '#/state/queries/preferences' import {useModerationOpts} from '#/state/queries/preferences'
import {usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
import {useComposerControls} from '#/state/shell/composer' import {useComposerControls} from '#/state/shell/composer'
import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
export function Post({ export function Post({
post, post,
@ -89,7 +89,7 @@ function PostInner({
showReplyLine, showReplyLine,
style, style,
}: { }: {
post: AppBskyFeedDefs.PostView post: Shadow<AppBskyFeedDefs.PostView>
record: AppBskyFeedPost.Record record: AppBskyFeedPost.Record
richText: RichTextAPI richText: RichTextAPI
moderation: PostModeration moderation: PostModeration

View File

@ -32,8 +32,8 @@ import {makeProfileLink} from 'lib/routes/links'
import {isEmbedByEmbedder} from 'lib/embeds' import {isEmbedByEmbedder} from 'lib/embeds'
import {MAX_POST_LINES} from 'lib/constants' import {MAX_POST_LINES} from 'lib/constants'
import {countLines} from 'lib/strings/helpers' import {countLines} from 'lib/strings/helpers'
import {usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
import {useComposerControls} from '#/state/shell/composer' import {useComposerControls} from '#/state/shell/composer'
import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
export function FeedItem({ export function FeedItem({
post, post,
@ -93,7 +93,7 @@ function FeedItemInner({
isThreadLastChild, isThreadLastChild,
isThreadParent, isThreadParent,
}: { }: {
post: AppBskyFeedDefs.PostView post: Shadow<AppBskyFeedDefs.PostView>
record: AppBskyFeedPost.Record record: AppBskyFeedPost.Record
reason: AppBskyFeedDefs.ReasonRepost | ReasonFeedSource | undefined reason: AppBskyFeedDefs.ReasonRepost | ReasonFeedSource | undefined
richText: RichTextAPI richText: RichTextAPI

View File

@ -54,9 +54,10 @@ import {shareUrl} from 'lib/sharing'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {Shadow} from '#/state/cache/types'
interface Props { interface Props {
profile: AppBskyActorDefs.ProfileViewDetailed profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>
moderation: ProfileModeration moderation: ProfileModeration
hideBackButton?: boolean hideBackButton?: boolean
isProfilePreview?: boolean isProfilePreview?: boolean

View File

@ -20,6 +20,7 @@ import {usePostDeleteMutation} from '#/state/queries/post'
import {useMutedThreads, useToggleThreadMute} from '#/state/muted-threads' import {useMutedThreads, useToggleThreadMute} from '#/state/muted-threads'
import {useLanguagePrefs} from '#/state/preferences' import {useLanguagePrefs} from '#/state/preferences'
import {logger} from '#/logger' import {logger} from '#/logger'
import {Shadow} from '#/state/cache/types'
export function PostDropdownBtn({ export function PostDropdownBtn({
testID, testID,
@ -28,7 +29,7 @@ export function PostDropdownBtn({
style, style,
}: { }: {
testID: string testID: string
post: AppBskyFeedDefs.PostView post: Shadow<AppBskyFeedDefs.PostView>
record: AppBskyFeedPost.Record record: AppBskyFeedPost.Record
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {

View File

@ -24,6 +24,7 @@ import {
usePostUnrepostMutation, usePostUnrepostMutation,
} from '#/state/queries/post' } from '#/state/queries/post'
import {useComposerControls} from '#/state/shell/composer' import {useComposerControls} from '#/state/shell/composer'
import {Shadow} from '#/state/cache/types'
export function PostCtrls({ export function PostCtrls({
big, big,
@ -33,7 +34,7 @@ export function PostCtrls({
onPressReply, onPressReply,
}: { }: {
big?: boolean big?: boolean
post: AppBskyFeedDefs.PostView post: Shadow<AppBskyFeedDefs.PostView>
record: AppBskyFeedPost.Record record: AppBskyFeedPost.Record
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
onPressReply: () => void onPressReply: () => void