React-Query fully working on existing components

This commit is contained in:
Zhiyuan Zheng 2020-11-04 22:26:38 +01:00
parent d1f32524ba
commit 631636db15
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
32 changed files with 6666 additions and 9038 deletions

16
App.tsx
View File

@ -1,6 +1,20 @@
import React from 'react'
import { QueryCache, ReactQueryCacheProvider, setConsole } from 'react-query'
import { Index } from 'src/Index'
const App: React.FC = () => <Index />
const queryCache = new QueryCache()
setConsole({
log: console.log,
warn: console.warn,
error: console.warn
})
const App: React.FC = () => (
<ReactQueryCacheProvider queryCache={queryCache}>
<Index />
</ReactQueryCacheProvider>
)
export default App

8391
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -38,6 +38,7 @@
"react-native-web": "~0.13.7",
"react-native-webview": "10.7.0",
"react-navigation": "^4.4.3",
"react-query": "^2.26.1",
"react-redux": "^7.2.1"
},
"devDependencies": {

View File

@ -3,7 +3,7 @@ import { StyleSheet, Text } from 'react-native'
import HTMLView, { HTMLViewNode } from 'react-native-htmlview'
import { useNavigation } from '@react-navigation/native'
import Emojis from 'src/components/Toot/Emojis'
import Emojis from 'src/components/Status/Emojis'
const renderNode = ({
node,
@ -14,7 +14,7 @@ const renderNode = ({
}: {
node: HTMLViewNode
index: number
navigation: object
navigation: any
mentions?: mastodon.Mention[]
showFullLink: boolean
}) => {

View File

@ -42,7 +42,6 @@ const Actions: React.FC<Props> = ({
useEffect(() => {
setTimeout(() => {
setSuccessMessage(undefined)
console.log('ajwieorjawioejri')
}, 2000)
return () => {}
}, [successMessage])

View File

@ -20,9 +20,7 @@ const Emojis: React.FC<Props> = ({ content, emojis, dimension }) => {
return emojiShortcode === `:${emoji.shortcode}:`
})
return emojiIndex === -1 ? (
<Text key={i} style={{ color: 'red' }}>
Something wrong with emoji!
</Text>
<Text key={i}>{emojiShortcode}</Text>
) : (
<Image
key={i}

View File

@ -8,7 +8,7 @@ export interface Props {
const Success: React.FC<Props> = ({ message }) => {
const fadeAnim = useRef(new Animated.Value(0)).current
useEffect(() => {
console.log(message)
// console.log(message)
if (message !== undefined) {
fadeIn()
} else {

View File

@ -2,7 +2,7 @@ import { Dispatch } from '@reduxjs/toolkit'
import { Alert } from 'react-native'
import client from 'src/api/client'
import { updateStatus } from 'src/stacks/common/timelineSlice'
// import { updateStatus } from 'src/stacks/common/timelineSlice'
const action = async ({
dispatch,
@ -35,7 +35,7 @@ const action = async ({
})
if (!res.body[stateKey] === statePrev) {
dispatch(updateStatus(res.body))
// dispatch(updateStatus(res.body))
} else {
Alert.alert(alert.title, alert.message, [
{ text: 'OK', onPress: () => console.log('OK Pressed') }

View File

@ -0,0 +1,107 @@
import React, { useMemo } from 'react'
import { Dimensions, Pressable, StyleSheet, View } from 'react-native'
import { useNavigation } from '@react-navigation/native'
import Actioned from './Status/Actioned'
import Avatar from './Status/Avatar'
import Header from './Status/Header'
import Content from './Status/Content'
import Poll from './Status/Poll'
import Attachment from './Status/Attachment'
import Card from './Status/Card'
import Actions from './Status/Actions'
export interface Props {
status: mastodon.Notification
}
const TootNotification: React.FC<Props> = ({ status }) => {
const navigation = useNavigation()
const actualAccount = status.status ? status.status.account : status.account
const statusView = useMemo(() => {
return (
<View style={styles.statusView}>
<Actioned
action={status.type}
name={status.account.display_name || status.account.username}
emojis={status.account.emojis}
notification
/>
<View style={styles.status}>
<Avatar uri={actualAccount.avatar} id={actualAccount.id} />
<View style={styles.details}>
<Header
name={actualAccount.display_name || actualAccount.username}
emojis={actualAccount.emojis}
account={actualAccount.acct}
created_at={status.created_at}
/>
<Pressable
onPress={() => navigation.navigate('Toot', { toot: status.id })}
>
{status.status ? (
<>
{status.status.content && (
<Content
content={status.status.content}
emojis={status.status.emojis}
mentions={status.status.mentions}
spoiler_text={status.status.spoiler_text}
// tags={status.status.tags}
// style={{ flex: 1 }}
/>
)}
{status.status.poll && <Poll poll={status.status.poll} />}
{status.status.media_attachments.length > 0 && (
<Attachment
media_attachments={status.status.media_attachments}
sensitive={status.status.sensitive}
width={Dimensions.get('window').width - 24 - 50 - 8}
/>
)}
{status.status.card && <Card card={status.status.card} />}
</>
) : (
<></>
)}
</Pressable>
{status.status && (
<Actions
id={status.status.id}
url={status.status.url}
replies_count={status.status.replies_count}
reblogs_count={status.status.reblogs_count}
reblogged={status.status.reblogged}
favourites_count={status.status.favourites_count}
favourited={status.status.favourited}
bookmarked={status.status.bookmarked}
/>
)}
</View>
</View>
</View>
)
}, [status])
return statusView
}
const styles = StyleSheet.create({
statusView: {
flex: 1,
flexDirection: 'column',
padding: 12
},
status: {
flex: 1,
flexDirection: 'row'
},
details: {
flex: 1,
flexGrow: 1
}
})
export default TootNotification

View File

@ -2,35 +2,35 @@ import React, { useMemo } from 'react'
import { Dimensions, Pressable, StyleSheet, View } from 'react-native'
import { useNavigation } from '@react-navigation/native'
import Actioned from './Toot/Actioned'
import Avatar from './Toot/Avatar'
import Header from './Toot/Header'
import Content from './Toot/Content'
import Poll from './Toot/Poll'
import Attachment from './Toot/Attachment'
import Card from './Toot/Card'
import Actions from './Toot/Actions'
import Actioned from './Status/Actioned'
import Avatar from './Status/Avatar'
import Header from './Status/Header'
import Content from './Status/Content'
import Poll from './Status/Poll'
import Attachment from './Status/Attachment'
import Card from './Status/Card'
import Actions from './Status/Actions'
export interface Props {
toot: mastodon.Status
status: mastodon.Status
}
const TootTimeline: React.FC<Props> = ({ toot }) => {
const StatusInTimeline: React.FC<Props> = ({ status }) => {
const navigation = useNavigation()
let actualContent = toot.reblog ? toot.reblog : toot
let actualContent = status.reblog ? status.reblog : status
const tootView = useMemo(() => {
const statusView = useMemo(() => {
return (
<View style={styles.tootTimeline}>
{toot.reblog && (
<View style={styles.statusView}>
{status.reblog && (
<Actioned
action='reblog'
name={toot.account.display_name || toot.account.username}
emojis={toot.account.emojis}
name={status.account.display_name || status.account.username}
emojis={status.account.emojis}
/>
)}
<View style={styles.toot}>
<View style={styles.status}>
<Avatar
uri={actualContent.account.avatar}
id={actualContent.account.id}
@ -43,8 +43,8 @@ const TootTimeline: React.FC<Props> = ({ toot }) => {
}
emojis={actualContent.account.emojis}
account={actualContent.account.acct}
created_at={toot.created_at}
application={toot.application}
created_at={status.created_at}
application={status.application}
/>
{/* Can pass toot info to next page to speed up performance */}
<Pressable
@ -88,18 +88,18 @@ const TootTimeline: React.FC<Props> = ({ toot }) => {
</View>
</View>
)
}, [toot])
}, [status])
return tootView
return statusView
}
const styles = StyleSheet.create({
tootTimeline: {
statusView: {
flex: 1,
flexDirection: 'column',
padding: 12
},
toot: {
status: {
flex: 1,
flexDirection: 'row'
},
@ -109,4 +109,4 @@ const styles = StyleSheet.create({
}
})
export default TootTimeline
export default StatusInTimeline

View File

@ -1,106 +0,0 @@
import React, { useMemo } from 'react'
import { Dimensions, Pressable, StyleSheet, Text, View } from 'react-native'
import { useNavigation } from '@react-navigation/native'
import Actioned from './Toot/Actioned'
import Avatar from './Toot/Avatar'
import Header from './Toot/Header'
import Content from './Toot/Content'
import Poll from './Toot/Poll'
import Attachment from './Toot/Attachment'
import Card from './Toot/Card'
import Actions from './Toot/Actions'
export interface Props {
toot: mastodon.Notification
}
const TootNotification: React.FC<Props> = ({ toot }) => {
const navigation = useNavigation()
const actualAccount = toot.status ? toot.status.account : toot.account
const tootView = useMemo(() => {
return (
<View style={styles.tootNotification}>
<Actioned
action={toot.type}
name={toot.account.display_name || toot.account.username}
emojis={toot.account.emojis}
notification
/>
<View style={styles.toot}>
<Avatar uri={actualAccount.avatar} id={actualAccount.id} />
<View style={styles.details}>
<Header
name={actualAccount.display_name || actualAccount.username}
emojis={actualAccount.emojis}
account={actualAccount.acct}
created_at={toot.created_at}
/>
<Pressable
onPress={() => navigation.navigate('Toot', { toot: toot.id })}
>
{toot.status ? (
<>
{toot.status.content && (
<Content
content={toot.status.content}
emojis={toot.status.emojis}
mentions={toot.status.mentions}
spoiler_text={toot.status.spoiler_text}
// tags={toot.status.tags}
// style={{ flex: 1 }}
/>
)}
{toot.status.poll && <Poll poll={toot.status.poll} />}
{toot.status.media_attachments.length > 0 && (
<Attachment
media_attachments={toot.status.media_attachments}
sensitive={toot.status.sensitive}
width={Dimensions.get('window').width - 24 - 50 - 8}
/>
)}
{toot.status.card && <Card card={toot.status.card} />}
</>
) : (
<></>
)}
</Pressable>
{toot.status && (
<Actions
id={toot.status.id}
replies_count={toot.status.replies_count}
reblogs_count={toot.status.reblogs_count}
reblogged={toot.status.reblogged}
favourites_count={toot.status.favourites_count}
favourited={toot.status.favourited}
bookmarked={toot.status.bookmarked}
/>
)}
</View>
</View>
</View>
)
}, [toot])
return tootView
}
const styles = StyleSheet.create({
tootNotification: {
flex: 1,
flexDirection: 'column',
padding: 12
},
toot: {
flex: 1,
flexDirection: 'row'
},
details: {
flex: 1,
flexGrow: 1
}
})
export default TootNotification

View File

@ -9,16 +9,14 @@ import {
View
} from 'react-native'
import SegmentedControl from '@react-native-community/segmented-control'
import { useDispatch, useSelector } from 'react-redux'
import { useFocusEffect } from '@react-navigation/native'
import { Feather } from '@expo/vector-icons'
import * as accountSlice from 'src/stacks/common/accountSlice'
import * as relationshipsSlice from 'src/stacks/common/relationshipsSlice'
import * as timelineSlice from 'src/stacks/common/timelineSlice'
// import * as relationshipsSlice from 'src/stacks/common/relationshipsSlice'
import ParseContent from 'src/components/ParseContent'
import Timeline from 'src/stacks/common/Timeline'
import { useQuery } from 'react-query'
import { accountFetch } from '../common/accountFetch'
// Moved account example: https://m.cmx.im/web/accounts/27812
@ -33,11 +31,25 @@ const Header = ({
return (
<Image
source={{ uri: uri }}
style={styles.header(size ? size.height / size.width : 1 / 2)}
style={[
styles.header,
{
height:
Dimensions.get('window').width *
(size ? size.height / size.width : 1 / 2)
}
]}
/>
)
} else {
return <View style={styles.header(1 / 3)} />
return (
<View
style={[
styles.header,
{ height: Dimensions.get('window').width * (1 / 3) }
]}
/>
)
}
}
@ -102,7 +114,7 @@ const Toots = ({ account }: { account: string }) => {
const [segmentManuallyTriggered, setSegmentManuallyTriggered] = useState(
false
)
const horizontalPaging = useRef()
const horizontalPaging = useRef<any>()
const pages = ['Account_Default', 'Account_All', 'Account_Media']
@ -186,55 +198,46 @@ const Account: React.FC<Props> = ({
params: { id }
}
}) => {
const dispatch = useDispatch()
const accountState = useSelector(state => state.account)
const { isLoading, isFetchingMore, isError, isSuccess, data } = useQuery(
['Account', { id }],
accountFetch
)
// const stateRelationships = useSelector(relationshipsState)
const [loaded, setLoaded] = useState(false)
const [headerImageSize, setHeaderImageSize] = useState()
interface isHeaderImageSize {
width: number
height: number
}
const [headerImageSize, setHeaderImageSize] = useState<
isHeaderImageSize | undefined
>(undefined)
useEffect(() => {
if (accountState.status === 'idle') {
dispatch(accountSlice.fetch({ id }))
}
if (accountState.account.header) {
Image.getSize(accountState.account.header, (width, height) => {
if (data.header) {
Image.getSize(data.header, (width, height) => {
setHeaderImageSize({ width, height })
setLoaded(true)
})
} else {
setLoaded(true)
}
}, [accountState, dispatch])
}, [data])
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
return () => {
dispatch(accountSlice.reset())
dispatch(timelineSlice.reset('Account_Default'))
dispatch(timelineSlice.reset('Account_All'))
dispatch(timelineSlice.reset('Account_Media'))
}
}, [])
)
// add emoji support
return loaded ? (
return isSuccess ? (
<View>
<Header
uri={accountState.account.header}
size={
headerImageSize && {
{headerImageSize && (
<Header
uri={data.header}
size={{
width: headerImageSize.width,
height: headerImageSize.height
}
}
/>
<Information
account={accountState.account}
emojis={accountState.account.emojis}
/>
{accountState.account.id && <Toots account={accountState.account.id} />}
}}
/>
)}
<Information account={data} emojis={data.emojis} />
<Toots account={id} />
</View>
) : (
<></>
@ -242,11 +245,10 @@ const Account: React.FC<Props> = ({
}
const styles = StyleSheet.create({
header: ratio => ({
header: {
width: '100%',
height: Dimensions.get('window').width * ratio,
backgroundColor: 'gray'
}),
},
information: { marginTop: -30, paddingLeft: 12, paddingRight: 12 },
avatar: {
width: 90,

View File

@ -1,9 +1,6 @@
import React from 'react'
import { useDispatch } from 'react-redux'
import { useFocusEffect } from '@react-navigation/native'
import Timeline from 'src/stacks/common/Timeline'
import { reset } from 'src/stacks/common/timelineSlice'
// Show remote hashtag? Only when private, show local version?
@ -20,18 +17,6 @@ const Hashtag: React.FC<Props> = ({
params: { hashtag }
}
}) => {
const dispatch = useDispatch()
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
return () => {
dispatch(reset('Hashtag'))
}
}, [])
)
return <Timeline page='Hashtag' hashtag={hashtag} />
}

View File

@ -1,9 +1,6 @@
import React from 'react'
import { useDispatch } from 'react-redux'
import { useFocusEffect } from '@react-navigation/native'
import Timeline from 'src/stacks/common/Timeline'
import { reset } from 'src/stacks/common/timelineSlice'
// Show remote hashtag? Only when private, show local version?
@ -20,18 +17,6 @@ const Toot: React.FC<Props> = ({
params: { toot }
}
}) => {
const dispatch = useDispatch()
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
return () => {
dispatch(reset('Toot'))
}
}, [])
)
return <Timeline page='Toot' toot={toot} disableRefresh />
}

View File

@ -5,11 +5,7 @@ import Hashtag from 'src/stacks/Shared/Hashtag'
import Toot from 'src/stacks/Shared/Toot'
import Webview from 'src/stacks/Shared/Webview'
export interface Props {
Stack: any
}
const sharedScreens = Stack => {
const sharedScreens = (Stack: any) => {
return [
<Stack.Screen
key='Account'

View File

@ -1,84 +1,93 @@
import React, { useEffect, useState } from 'react'
import { ActivityIndicator, FlatList, Text, View } from 'react-native'
import { useSelector, useDispatch } from 'react-redux'
import React from 'react'
import { ActivityIndicator, AppState, FlatList, Text, View } from 'react-native'
import { setFocusHandler, useInfiniteQuery } from 'react-query'
import TootNotification from 'src/components/TootNotification'
import TootTimeline from 'src/components/TootTimeline'
import { RootState } from 'src/stacks/common/store'
import { fetch } from './timelineSlice'
import StatusInNotifications from 'src/components/StatusInNotifications'
import StatusInTimeline from 'src/components/StatusInTimeline'
import { timelineFetch } from './timelineFetch'
// Opening nesting hashtag pages
const Timeline: React.FC<{
export interface Props {
page: store.TimelinePage
hashtag?: string
list?: string
toot?: string
account?: string
disableRefresh?: boolean
}> = ({ page, hashtag, list, toot, account, disableRefresh = false }) => {
const dispatch = useDispatch()
const state = useSelector((state: RootState) => state.timelines[page])
const [timelineReady, setTimelineReady] = useState(false)
}
useEffect(() => {
let mounted = true
if (state.status === 'idle' && mounted) {
dispatch(fetch({ page, hashtag, list, toot, account }))
setTimelineReady(true)
const Timeline: React.FC<Props> = ({
page,
hashtag,
list,
toot,
account,
disableRefresh = false
}) => {
setFocusHandler(handleFocus => {
const handleAppStateChange = (appState: string) => {
if (appState === 'active') {
handleFocus()
}
}
return () => {
mounted = false
}
}, [state, dispatch])
AppState.addEventListener('change', handleAppStateChange)
return () => AppState.removeEventListener('change', handleAppStateChange)
})
const {
isLoading,
isFetchingMore,
isError,
isSuccess,
data,
fetchMore
} = useInfiniteQuery(
[page, { page, hashtag, list, toot, account }],
timelineFetch
)
const flattenData = data ? data.flatMap(d => [...d?.toots]) : []
let content
if (state.status === 'failed') {
if (!isSuccess) {
content = <ActivityIndicator />
} else if (isError) {
content = <Text>Error message</Text>
} else {
content = (
<>
<FlatList
style={{ minHeight: '100%' }}
data={state.toots}
data={flattenData}
keyExtractor={({ id }) => id}
renderItem={({ item, index, separators }) =>
page === 'Notifications' ? (
<TootNotification key={index} toot={item} />
<StatusInNotifications key={index} status={item} />
) : (
<TootTimeline key={index} toot={item} />
<StatusInTimeline key={index} status={item} />
)
}
// {...(state.pointer && { initialScrollIndex: state.pointer })}
{...(!disableRefresh && {
onRefresh: () =>
dispatch(
fetch({
page,
hashtag,
list,
paginationDirection: 'prev'
})
fetchMore(
{
direction: 'prev',
id: flattenData[0].id
},
{ previous: true }
),
refreshing: state.status === 'loading',
refreshing: isLoading,
onEndReached: () => {
if (!timelineReady) {
dispatch(
fetch({
page,
hashtag,
list,
paginationDirection: 'next'
})
)
setTimelineReady(true)
}
fetchMore({
direction: 'next',
id: flattenData[flattenData.length - 1].id
})
},
onEndReachedThreshold: 0.5
})}
onMomentumScrollBegin={() => setTimelineReady(false)}
/>
{state.status === 'loading' && <ActivityIndicator />}
{isFetchingMore && <ActivityIndicator />}
</>
)
}

View File

@ -0,0 +1,10 @@
import client from 'src/api/client'
export const accountFetch = async (key: string, { id }: { id: string }) => {
const res = await client({
method: 'get',
instance: 'local',
endpoint: `accounts/${id}`
})
return Promise.resolve(res.body)
}

View File

@ -1,46 +0,0 @@
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import client from 'src/api/client'
export const fetch = createAsyncThunk(
'account/fetch',
async ({ id }: { id: string }) => {
const res = await client({
method: 'get',
instance: 'local',
endpoint: `accounts/${id}`
})
return res.body
}
)
const accountInitState = {
account: {},
status: 'idle'
}
export const accountSlice = createSlice({
name: 'account',
initialState: accountInitState,
reducers: {
reset: () => accountInitState
},
extraReducers: builder => {
builder.addCase(fetch.pending, state => {
state.status = 'loading'
})
builder.addCase(fetch.fulfilled, (state, action) => {
state.status = 'succeeded'
state.account = action.payload
})
builder.addCase(fetch.rejected, (state, action) => {
state.status = 'failed'
console.error(action.error.message)
})
}
})
export const { reset } = accountSlice.actions
export default accountSlice.reducer

View File

@ -1,58 +0,0 @@
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { client } from 'src/api/client'
export const fetch = createAsyncThunk(
'relationships/fetch',
async ({ ids }, { getState }) => {
if (!ids.length) console.error('Relationships empty')
const instanceLocal = getState().instanceInfo.local + '/api/v1/'
const query = ids.map(id => ({
key: 'id[]',
value: id
}))
const header = {
headers: {
Authorization: `Bearer ${getState().instanceInfo.localToken}`
}
}
return await client.get(
`${instanceLocal}accounts/relationships`,
query,
header
)
}
)
const relationshipsInitState = {
relationships: [],
status: 'idle'
}
export const relationshipSlice = createSlice({
name: 'relationships',
initialState: {
relationships: [],
status: 'idle'
},
reducers: {
reset: () => relationshipsInitState
},
extraReducers: {
[fetch.pending]: state => {
state.status = 'loading'
},
[fetch.fulfilled]: (state, action) => {
state.status = 'succeeded'
state.relationships = action.payload
},
[fetch.rejected]: (state, action) => {
state.status = 'failed'
console.error(action.error.message)
}
}
})
export const { reset } = relationshipSlice.actions
export default relationshipSlice.reducer

View File

@ -1,9 +1,6 @@
import { configureStore } from '@reduxjs/toolkit'
import instanceInfoSlice from 'src/stacks/common/instanceInfoSlice'
import timelineSlice from 'src/stacks/common/timelineSlice'
import accountSlice from 'src/stacks/common/accountSlice'
// import relationshipsSlice from 'src/stacks/common/relationshipsSlice'
// get site information from local storage and pass to reducers
const preloadedState = {
@ -15,10 +12,7 @@ const preloadedState = {
}
const reducer = {
instanceInfo: instanceInfoSlice,
timelines: timelineSlice,
account: accountSlice,
// relationships: relationshipsSlice
instanceInfo: instanceInfoSlice
}
const store = configureStore({

View File

@ -0,0 +1,167 @@
import client from 'src/api/client'
export const timelineFetch = async (
key: string,
{
page,
query = {},
account,
hashtag,
list,
toot
}: {
page: string
query?: {
[key: string]: string | number | boolean
}
account?: string
hashtag?: string
list?: string
toot?: string
},
pagination: {
direction: 'prev' | 'next'
id: string
}
) => {
let res
if (pagination && pagination.id) {
switch (pagination.direction) {
case 'prev':
query.min_id = pagination.id
break
case 'next':
query.max_id = pagination.id
break
}
}
switch (page) {
case 'Following':
res = await client({
method: 'get',
instance: 'local',
endpoint: 'timelines/home',
query
})
return Promise.resolve({ toots: res.body })
case 'Local':
query.local = 'true'
res = await client({
method: 'get',
instance: 'local',
endpoint: 'timelines/public',
query
})
return Promise.resolve({ toots: res.body })
case 'LocalPublic':
res = await client({
method: 'get',
instance: 'local',
endpoint: 'timelines/public',
query
})
return Promise.resolve({ toots: res.body })
case 'RemotePublic':
res = await client({
method: 'get',
instance: 'remote',
endpoint: 'timelines/public',
query
})
return Promise.resolve({ toots: res.body })
case 'Notifications':
res = await client({
method: 'get',
instance: 'local',
endpoint: 'notifications',
query
})
return Promise.resolve({ toots: res.body })
case 'Account_Default':
res = await client({
method: 'get',
instance: 'local',
endpoint: `accounts/${account}/statuses`,
query: {
pinned: 'true'
}
})
const toots = res.body
res = await client({
method: 'get',
instance: 'local',
endpoint: `accounts/${account}/statuses`,
query: {
exclude_replies: 'true'
}
})
toots.push(...res.body)
return Promise.resolve({ toots: toots })
case 'Account_All':
res = await client({
method: 'get',
instance: 'local',
endpoint: `accounts/${account}/statuses`,
query
})
return Promise.resolve({ toots: res.body })
case 'Account_Media':
res = await client({
method: 'get',
instance: 'local',
endpoint: `accounts/${account}/statuses`,
query: {
only_media: 'true'
}
})
return Promise.resolve({ toots: res.body })
case 'Hashtag':
res = await client({
method: 'get',
instance: 'local',
endpoint: `timelines/tag/${hashtag}`,
query
})
return Promise.resolve({ toots: res.body })
// case 'List':
// res = await client({
// method: 'get',
// instance: 'local',
// endpoint: `timelines/list/${list}`,
// query
// })
// return {
// toots: res.body
// }
case 'Toot':
const current = await client({
method: 'get',
instance: 'local',
endpoint: `statuses/${toot}`
})
const context = await client({
method: 'get',
instance: 'local',
endpoint: `statuses/${toot}/context`
})
return Promise.resolve({
toots: [...context.ancestors, current, ...context.descendants],
pointer: context.ancestors.length
})
default:
console.error('First time fetching timeline error')
}
}

View File

@ -1,270 +0,0 @@
import {
AnyAction,
createAsyncThunk,
createSlice,
PayloadAction
} from '@reduxjs/toolkit'
import store from 'src'
import client from 'src/api/client'
export const fetch = createAsyncThunk(
'timeline/fetch',
async (
{
page,
paginationDirection,
query = {},
account,
hashtag,
list,
toot
}: {
page: string
paginationDirection?: 'prev' | 'next'
query?: {
[key: string]: string | number | boolean
}
account?: string
hashtag?: string
list?: string
toot?: string
},
{ getState }
) => {
let res
if (paginationDirection) {
//@ts-ignore
const allToots = getState().timelines[page].toots
switch (paginationDirection) {
case 'prev':
query.min_id = allToots[0].id
break
case 'next':
query.max_id = allToots[allToots.length - 1].id
break
}
}
switch (page) {
case 'Following':
res = await client({
method: 'get',
instance: 'local',
endpoint: 'timelines/home',
query
})
return {
toots: res.body
}
case 'Local':
query.local = 'true'
res = await client({
method: 'get',
instance: 'local',
endpoint: 'timelines/public',
query
})
return {
toots: res.body
}
case 'LocalPublic':
res = await client({
method: 'get',
instance: 'local',
endpoint: 'timelines/public',
query
})
return {
toots: res.body
}
case 'RemotePublic':
res = await client({
method: 'get',
instance: 'remote',
endpoint: 'timelines/public',
query
})
return {
toots: res.body
}
case 'Notifications':
res = await client({
method: 'get',
instance: 'local',
endpoint: 'notifications',
query
})
return {
toots: res.body
}
case 'Account_Default':
res = await client({
method: 'get',
instance: 'local',
endpoint: `accounts/${account}/statuses`,
query: {
pinned: 'true'
}
})
const toots = res.body
res = await client({
method: 'get',
instance: 'local',
endpoint: `accounts/${account}/statuses`,
query: {
exclude_replies: 'true'
}
})
toots.push(...res.body)
return { toots: toots }
case 'Account_All':
res = await client({
method: 'get',
instance: 'local',
endpoint: `accounts/${account}/statuses`,
query
})
return {
toots: res.body
}
case 'Account_Media':
res = await client({
method: 'get',
instance: 'local',
endpoint: `accounts/${account}/statuses`,
query: {
only_media: 'true'
}
})
return {
toots: res.body
}
case 'Hashtag':
res = await client({
method: 'get',
instance: 'local',
endpoint: `timelines/tag/${hashtag}`,
query
})
return {
toots: res.body
}
case 'List':
res = await client({
method: 'get',
instance: 'local',
endpoint: `timelines/list/${list}`,
query
})
return {
toots: res.body
}
case 'Toot':
const current = await client({
method: 'get',
instance: 'local',
endpoint: `statuses/${toot}`
})
const context = await client({
method: 'get',
instance: 'local',
endpoint: `statuses/${toot}/context`
})
return {
toots: [...context.ancestors, current, ...context.descendants],
pointer: context.ancestors.length
}
default:
console.error('First time fetching timeline error')
}
}
)
const timelineInitState: store.TimelineState = {
toots: [],
pointer: undefined,
status: 'idle'
}
export const timelineSlice = createSlice({
name: 'timeline',
initialState: {
Following: timelineInitState,
Local: timelineInitState,
LocalPublic: timelineInitState,
RemotePublic: timelineInitState,
Notifications: timelineInitState,
Hashtag: timelineInitState,
List: timelineInitState,
Toot: timelineInitState,
Account_Default: timelineInitState,
Account_All: timelineInitState,
Account_Media: timelineInitState
},
reducers: {
reset (state, action: PayloadAction<store.TimelinePage>) {
//@ts-ignore
state[action.payload] = timelineInitState
},
updateStatus (state, action) {
Object.keys(state).map((page: store.TimelinePage) => {
//@ts-ignore
const index: number = state[page].toots.findIndex(
(toot: mastodon.Status) => toot.id === action.payload.id
)
if (index !== -1) {
//@ts-ignore
state[page].toots[index] = action.payload
}
})
}
},
extraReducers: builder => {
builder.addCase(fetch.pending, (state, action: AnyAction) => {
//@ts-ignore
state[action.meta.arg.page].status = 'loading'
})
builder.addCase(fetch.fulfilled, (state, action) => {
//@ts-ignore
state[action.meta.arg.page].status = 'succeeded'
if (action.payload?.toots) {
if (action.meta.arg.paginationDirection === 'prev') {
//@ts-ignore
state[action.meta.arg.page].toots.unshift(...action.payload.toots)
} else {
//@ts-ignore
state[action.meta.arg.page].toots.push(...action.payload.toots)
}
}
if (action.payload?.pointer) {
//@ts-ignore
state[action.meta.arg.page].pointer = action.payload.pointer
}
})
builder.addCase(fetch.rejected, (state, action) => {
//@ts-ignore
state[action.meta.arg.page].status = 'failed'
console.error(action.error.message)
})
}
})
export const { reset, updateStatus } = timelineSlice.actions
export default timelineSlice.reducer

6232
yarn.lock Normal file

File diff suppressed because it is too large Load Diff