tooot/src/utils/queryHooks/timeline.ts

465 lines
12 KiB
TypeScript
Raw Normal View History

2021-12-18 19:59:38 +01:00
import apiInstance, { InstanceResponse } from '@api/instance'
2021-01-11 21:36:57 +01:00
import haptics from '@components/haptics'
2021-05-12 15:40:55 +02:00
import queryClient from '@helpers/queryClient'
2021-03-17 15:30:28 +01:00
import { store } from '@root/store'
import {
checkInstanceFeature,
getInstanceNotificationsFilter
} from '@utils/slices/instancesSlice'
2021-01-07 19:13:09 +01:00
import { AxiosError } from 'axios'
import { uniqBy } from 'lodash'
2021-01-11 21:36:57 +01:00
import {
MutationOptions,
2021-12-18 19:59:38 +01:00
QueryFunctionContext,
2021-01-11 21:36:57 +01:00
useInfiniteQuery,
UseInfiniteQueryOptions,
useMutation
} from 'react-query'
import deleteItem from './timeline/deleteItem'
2022-04-30 17:44:39 +02:00
import editItem from './timeline/editItem'
2021-01-11 21:36:57 +01:00
import updateStatusProperty from './timeline/updateStatusProperty'
2021-01-07 19:13:09 +01:00
export type QueryKeyTimeline = [
'Timeline',
{
page: App.Pages
hashtag?: Mastodon.Tag['name']
list?: Mastodon.List['id']
toot?: Mastodon.Status['id']
account?: Mastodon.Account['id']
}
]
2021-03-29 00:22:30 +02:00
const queryFunction = async ({
2021-01-07 19:13:09 +01:00
queryKey,
pageParam
2021-12-18 19:59:38 +01:00
}: QueryFunctionContext<QueryKeyTimeline>) => {
2021-01-07 19:13:09 +01:00
const { page, account, hashtag, list, toot } = queryKey[1]
2021-02-11 01:33:31 +01:00
let params: { [key: string]: string } = { ...pageParam }
2021-01-07 19:13:09 +01:00
switch (page) {
case 'Following':
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Status[]>({
2021-01-07 19:13:09 +01:00
method: 'get',
url: 'timelines/home',
params
})
case 'Local':
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Status[]>({
2021-01-07 19:13:09 +01:00
method: 'get',
url: 'timelines/public',
2021-01-11 21:36:57 +01:00
params: {
...params,
local: 'true'
}
2021-01-07 19:13:09 +01:00
})
case 'LocalPublic':
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Status[]>({
2021-01-07 19:13:09 +01:00
method: 'get',
url: 'timelines/public',
params
})
case 'Notifications':
2021-03-17 15:30:28 +01:00
const rootStore = store.getState()
const notificationsFilter = getInstanceNotificationsFilter(rootStore)
const usePositiveFilter = checkInstanceFeature(
'notification_types_positive_filter'
)(rootStore)
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Notification[]>({
2021-01-07 19:13:09 +01:00
method: 'get',
url: 'notifications',
2021-03-17 15:30:28 +01:00
params: {
...params,
...(notificationsFilter &&
(usePositiveFilter
? {
types: Object.keys(notificationsFilter)
// @ts-ignore
.filter(filter => notificationsFilter[filter] === true)
}
: {
exclude_types: Object.keys(notificationsFilter)
// @ts-ignore
.filter(filter => notificationsFilter[filter] === false)
}))
2021-03-17 15:30:28 +01:00
}
2021-01-07 19:13:09 +01:00
})
case 'Account_Default':
2021-02-11 23:42:13 +01:00
if (pageParam && pageParam.hasOwnProperty('max_id')) {
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Status[]>({
2021-01-07 19:13:09 +01:00
method: 'get',
url: `accounts/${account}/statuses`,
params: {
exclude_replies: 'true',
...params
}
})
} else {
2021-08-29 15:25:38 +02:00
const res1 = await apiInstance<
(Mastodon.Status & { _pinned: boolean })[]
>({
2021-01-07 19:13:09 +01:00
method: 'get',
url: `accounts/${account}/statuses`,
params: {
pinned: 'true'
}
2021-03-29 00:22:30 +02:00
})
res1.body = res1.body.map(status => {
status._pinned = true
return status
})
const res2 = await apiInstance<Mastodon.Status[]>({
method: 'get',
url: `accounts/${account}/statuses`,
params: {
exclude_replies: 'true'
2021-02-11 01:33:31 +01:00
}
2021-01-07 19:13:09 +01:00
})
2021-08-29 15:25:38 +02:00
return {
2021-03-29 00:22:30 +02:00
body: uniqBy([...res1.body, ...res2.body], 'id'),
...(res2.links.next && { links: { next: res2.links.next } })
}
2021-01-07 19:13:09 +01:00
}
case 'Account_All':
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Status[]>({
2021-01-07 19:13:09 +01:00
method: 'get',
url: `accounts/${account}/statuses`,
params
})
2021-01-16 00:00:31 +01:00
case 'Account_Attachments':
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Status[]>({
2021-01-07 19:13:09 +01:00
method: 'get',
url: `accounts/${account}/statuses`,
params: {
only_media: 'true',
...params
}
})
case 'Hashtag':
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Status[]>({
2021-01-07 19:13:09 +01:00
method: 'get',
url: `timelines/tag/${hashtag}`,
params
})
case 'Conversations':
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Conversation[]>({
2021-01-07 19:13:09 +01:00
method: 'get',
url: `conversations`,
params
})
case 'Bookmarks':
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Status[]>({
2021-01-07 19:13:09 +01:00
method: 'get',
url: `bookmarks`,
params
})
case 'Favourites':
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Status[]>({
2021-01-07 19:13:09 +01:00
method: 'get',
url: `favourites`,
params
})
case 'List':
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Status[]>({
2021-01-07 19:13:09 +01:00
method: 'get',
url: `timelines/list/${list}`,
params
})
case 'Toot':
2021-03-29 00:22:30 +02:00
const res1_1 = await apiInstance<Mastodon.Status>({
2021-01-07 19:13:09 +01:00
method: 'get',
url: `statuses/${toot}`
})
2021-03-29 00:22:30 +02:00
const res2_1 = await apiInstance<{
ancestors: Mastodon.Status[]
descendants: Mastodon.Status[]
}>({
method: 'get',
url: `statuses/${toot}/context`
})
2021-08-29 15:25:38 +02:00
return {
body: [
...res2_1.body.ancestors,
res1_1.body,
...res2_1.body.descendants
]
2021-03-29 00:22:30 +02:00
}
2021-01-07 19:13:09 +01:00
default:
return Promise.reject()
}
}
type Unpromise<T extends Promise<any>> = T extends Promise<infer U> ? U : never
2021-01-11 21:36:57 +01:00
export type TimelineData = Unpromise<ReturnType<typeof queryFunction>>
2021-12-18 19:59:38 +01:00
const useTimelineQuery = ({
2021-01-07 19:13:09 +01:00
options,
...queryKeyParams
}: QueryKeyTimeline[1] & {
2021-02-11 01:33:31 +01:00
options?: UseInfiniteQueryOptions<
2022-01-16 23:26:05 +01:00
InstanceResponse<Mastodon.Status[]>,
2021-12-18 19:59:38 +01:00
AxiosError
2021-02-11 01:33:31 +01:00
>
2021-01-07 19:13:09 +01:00
}) => {
const queryKey: QueryKeyTimeline = ['Timeline', { ...queryKeyParams }]
2021-03-14 00:47:55 +01:00
return useInfiniteQuery(queryKey, queryFunction, {
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
...options
})
2021-01-07 19:13:09 +01:00
}
2022-01-16 23:26:05 +01:00
const prefetchTimelineQuery = async ({
ids,
queryKey
}: {
ids: Mastodon.Status['id'][]
queryKey: QueryKeyTimeline
}): Promise<Mastodon.Status['id'] | undefined> => {
let page: string = ''
let local: boolean = false
switch (queryKey[1].page) {
case 'Following':
page = 'home'
break
case 'Local':
page = 'public'
local = true
break
case 'LocalPublic':
page = 'public'
break
}
for (const id of ids) {
const statuses = await apiInstance<Mastodon.Status[]>({
method: 'get',
url: `timelines/${page}`,
params: {
min_id: id,
limit: 1,
...(local && { local: 'true' })
}
})
if (statuses.body.length) {
await queryClient.prefetchInfiniteQuery(queryKey, props =>
queryFunction({
...props,
queryKey,
pageParam: {
max_id: statuses.body[0].id
}
})
)
return id
}
}
}
2021-01-11 21:36:57 +01:00
// --- Separator ---
enum MapPropertyToUrl {
bookmarked = 'bookmark',
favourited = 'favourite',
muted = 'mute',
pinned = 'pin',
reblogged = 'reblog'
}
export type MutationVarsTimelineUpdateStatusProperty = {
// This is status in general, including "status" inside conversation and notification
type: 'updateStatusProperty'
queryKey: QueryKeyTimeline
2021-02-13 01:26:02 +01:00
rootQueryKey?: QueryKeyTimeline
2021-01-11 21:36:57 +01:00
id: Mastodon.Status['id'] | Mastodon.Poll['id']
reblog?: boolean
payload:
| {
2021-01-23 02:41:50 +01:00
property: 'bookmarked' | 'muted' | 'pinned'
2021-01-11 21:36:57 +01:00
currentValue: boolean
2021-01-23 02:41:50 +01:00
propertyCount: undefined
countValue: undefined
}
| {
property: 'favourited' | 'reblogged'
currentValue: boolean
propertyCount: 'favourites_count' | 'reblogs_count'
countValue: number
2021-01-11 21:36:57 +01:00
}
| {
property: 'poll'
id: Mastodon.Poll['id']
type: 'vote' | 'refresh'
options?: boolean[]
data?: Mastodon.Poll
}
}
export type MutationVarsTimelineUpdateAccountProperty = {
// This is status in general, including "status" inside conversation and notification
type: 'updateAccountProperty'
queryKey?: QueryKeyTimeline
id: Mastodon.Account['id']
payload: {
property: 'mute' | 'block' | 'reports'
}
}
2022-04-30 17:44:39 +02:00
export type MutationVarsTimelineEditItem = {
// This is for editing status
type: 'editItem'
queryKey?: QueryKeyTimeline
rootQueryKey?: QueryKeyTimeline
status: Mastodon.Status
}
2021-01-11 21:36:57 +01:00
export type MutationVarsTimelineDeleteItem = {
// This is for deleting status and conversation
type: 'deleteItem'
source: 'statuses' | 'conversations'
2021-02-13 01:26:02 +01:00
queryKey?: QueryKeyTimeline
rootQueryKey?: QueryKeyTimeline
2022-04-30 17:44:39 +02:00
id: Mastodon.Status['id']
2021-01-11 21:36:57 +01:00
}
export type MutationVarsTimelineDomainBlock = {
// This is for deleting status and conversation
type: 'domainBlock'
queryKey: QueryKeyTimeline
domain: string
}
export type MutationVarsTimeline =
| MutationVarsTimelineUpdateStatusProperty
| MutationVarsTimelineUpdateAccountProperty
2022-04-30 17:44:39 +02:00
| MutationVarsTimelineEditItem
2021-01-11 21:36:57 +01:00
| MutationVarsTimelineDeleteItem
| MutationVarsTimelineDomainBlock
const mutationFunction = async (params: MutationVarsTimeline) => {
switch (params.type) {
case 'updateStatusProperty':
switch (params.payload.property) {
case 'poll':
const formData = new FormData()
params.payload.type === 'vote' &&
2021-01-22 01:34:20 +01:00
params.payload.options?.forEach((option, index) => {
2021-01-11 21:36:57 +01:00
if (option) {
formData.append('choices[]', index.toString())
}
})
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Poll>({
2021-01-11 21:36:57 +01:00
method: params.payload.type === 'vote' ? 'post' : 'get',
url:
params.payload.type === 'vote'
? `polls/${params.payload.id}/votes`
: `polls/${params.payload.id}`,
...(params.payload.type === 'vote' && { body: formData })
})
default:
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Status>({
2021-01-11 21:36:57 +01:00
method: 'post',
url: `statuses/${params.id}/${
params.payload.currentValue ? 'un' : ''
}${MapPropertyToUrl[params.payload.property]}`
})
}
case 'updateAccountProperty':
switch (params.payload.property) {
case 'block':
case 'mute':
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Account>({
2021-01-11 21:36:57 +01:00
method: 'post',
url: `accounts/${params.id}/${params.payload.property}`
})
case 'reports':
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Account>({
2021-01-11 21:36:57 +01:00
method: 'post',
url: `reports`,
params: {
account_id: params.id
}
})
}
2022-04-30 17:44:39 +02:00
case 'editItem':
return { body: params.status }
2021-01-11 21:36:57 +01:00
case 'deleteItem':
2021-02-20 19:12:44 +01:00
return apiInstance<Mastodon.Conversation>({
2021-01-11 21:36:57 +01:00
method: 'delete',
url: `${params.source}/${params.id}`
})
case 'domainBlock':
2021-02-20 19:12:44 +01:00
return apiInstance<any>({
2021-01-11 21:36:57 +01:00
method: 'post',
url: `domain_blocks`,
params: {
domain: params.domain
}
})
}
}
type MutationOptionsTimeline = MutationOptions<
2021-02-11 01:33:31 +01:00
{ body: Mastodon.Conversation | Mastodon.Notification | Mastodon.Status },
2021-01-11 21:36:57 +01:00
AxiosError,
MutationVarsTimeline
>
const useTimelineMutation = ({
onError,
onMutate,
onSettled,
onSuccess
}: {
onError?: MutationOptionsTimeline['onError']
onMutate?: boolean
onSettled?: MutationOptionsTimeline['onSettled']
2021-02-13 01:26:02 +01:00
onSuccess?: MutationOptionsTimeline['onSuccess']
2021-01-11 21:36:57 +01:00
}) => {
return useMutation<
2021-02-11 01:33:31 +01:00
{ body: Mastodon.Conversation | Mastodon.Notification | Mastodon.Status },
2021-01-11 21:36:57 +01:00
AxiosError,
MutationVarsTimeline
>(mutationFunction, {
onError,
onSettled,
2021-02-13 01:26:02 +01:00
onSuccess,
2021-01-11 21:36:57 +01:00
...(onMutate && {
onMutate: params => {
queryClient.cancelQueries(params.queryKey)
2022-06-01 00:33:59 +02:00
const oldData =
params.queryKey && queryClient.getQueryData(params.queryKey)
2021-01-11 21:36:57 +01:00
2021-02-27 16:33:54 +01:00
haptics('Light')
2021-01-11 21:36:57 +01:00
switch (params.type) {
case 'updateStatusProperty':
updateStatusProperty(params)
2021-01-11 21:36:57 +01:00
break
2022-04-30 17:44:39 +02:00
case 'editItem':
editItem(params)
break
2021-01-11 21:36:57 +01:00
case 'deleteItem':
deleteItem(params)
2021-01-11 21:36:57 +01:00
break
}
return oldData
}
})
})
}
2022-01-16 23:26:05 +01:00
export { prefetchTimelineQuery, useTimelineQuery, useTimelineMutation }