1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00

Restructure removing remote

This commit is contained in:
Zhiyuan Zheng
2021-02-20 19:12:44 +01:00
parent 9fdf3ab640
commit 45681fc1f5
71 changed files with 998 additions and 756 deletions

View File

@ -1,4 +1,4 @@
import client from '@api/client'
import apiInstance from '@api/instance'
import { AxiosError } from 'axios'
import { useQuery, UseQueryOptions } from 'react-query'
@ -7,9 +7,8 @@ export type QueryKey = ['Account', { id: Mastodon.Account['id'] }]
const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
const { id } = queryKey[1]
return client<Mastodon.Account>({
return apiInstance<Mastodon.Account>({
method: 'get',
instance: 'local',
url: `accounts/${id}`
}).then(res => res.body)
}

View File

@ -1,35 +0,0 @@
import client from '@api/client'
import { InstancesState } from '@utils/slices/instancesSlice'
import { AxiosError } from 'axios'
import { useQuery, UseQueryOptions } from 'react-query'
export type QueryKey = [
'AccountCheck',
{
id: Mastodon.Account['id']
index: NonNullable<InstancesState['local']['activeIndex']>
}
]
const queryFunction = async ({ queryKey }: { queryKey: QueryKey }) => {
const { id, index } = queryKey[1]
return client<Mastodon.Account>({
method: 'get',
instance: 'local',
localIndex: index,
url: `accounts/${id}`
}).then(res => res.body)
}
const useAccountCheckQuery = <TData = Mastodon.Account>({
options,
...queryKeyParams
}: QueryKey[1] & {
options?: UseQueryOptions<Mastodon.Account, AxiosError, TData>
}) => {
const queryKey: QueryKey = ['AccountCheck', { ...queryKeyParams }]
return useQuery(queryKey, queryFunction, options)
}
export { useAccountCheckQuery }

View File

@ -1,4 +1,4 @@
import client from '@api/client'
import apiInstance from '@api/instance'
import { AxiosError } from 'axios'
import {
useMutation,
@ -12,9 +12,8 @@ type QueryKeyAnnouncement = ['Announcements', { showAll?: boolean }]
const queryFunction = ({ queryKey }: { queryKey: QueryKeyAnnouncement }) => {
const { showAll } = queryKey[1]
return client<Mastodon.Announcement[]>({
return apiInstance<Mastodon.Announcement[]>({
method: 'get',
instance: 'local',
url: `announcements`,
...(showAll && {
params: {
@ -52,15 +51,13 @@ const mutationFunction = async ({
}: MutationVarsAnnouncement) => {
switch (type) {
case 'reaction':
return client<{}>({
return apiInstance<{}>({
method: me ? 'delete' : 'put',
instance: 'local',
url: `announcements/${id}/reactions/${name}`
})
case 'dismiss':
return client<{}>({
return apiInstance<{}>({
method: 'post',
instance: 'local',
url: `announcements/${id}/dismiss`
})
}

View File

@ -1,9 +1,9 @@
import client from '@api/client'
import apiGeneral from '@api/general'
import { AxiosError } from 'axios'
import * as AuthSession from 'expo-auth-session'
import { useQuery, UseQueryOptions } from 'react-query'
export type QueryKey = ['Apps', { instanceDomain?: string }]
export type QueryKey = ['Apps', { domain?: string }]
const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
const redirectUri = AuthSession.makeRedirectUri({
@ -11,7 +11,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
useProxy: false
})
const { instanceDomain } = queryKey[1]
const { domain } = queryKey[1]
const formData = new FormData()
formData.append('client_name', 'tooot')
@ -19,11 +19,10 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
formData.append('redirect_uris', redirectUri)
formData.append('scopes', 'read write follow push')
return client<Mastodon.Apps>({
return apiGeneral<Mastodon.Apps>({
method: 'post',
instance: 'remote',
instanceDomain,
url: `apps`,
domain: domain || '',
url: `api/v1/apps`,
body: formData
}).then(res => res.body)
}

View File

@ -1,13 +1,12 @@
import client from '@api/client'
import apiInstance from '@api/instance'
import { AxiosError } from 'axios'
import { useQuery, UseQueryOptions } from 'react-query'
type QueryKey = ['Emojis']
const queryFunction = () => {
return client<Mastodon.Emoji[]>({
return apiInstance<Mastodon.Emoji[]>({
method: 'get',
instance: 'local',
url: 'custom_emojis'
}).then(res => res.body)
}

View File

@ -1,18 +1,21 @@
import client from '@api/client'
import apiGeneral from '@api/general'
import { AxiosError } from 'axios'
import { useQuery, UseQueryOptions } from 'react-query'
export type QueryKey = ['Instance', { instanceDomain?: string }]
export type QueryKey = ['Instance', { domain?: string }]
const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
const { instanceDomain } = queryKey[1]
const queryFunction = async ({ queryKey }: { queryKey: QueryKey }) => {
const { domain } = queryKey[1]
if (!domain) {
return Promise.reject()
}
return client<Mastodon.Instance>({
const res = await apiGeneral<Mastodon.Instance>({
method: 'get',
instance: 'remote',
instanceDomain,
url: `instance`
}).then(res => res.body)
domain: domain,
url: `api/v1/instance`
})
return res.body
}
const useInstanceQuery = <

View File

@ -1,13 +1,12 @@
import client from '@api/client'
import apiInstance from '@api/instance'
import { AxiosError } from 'axios'
import { useQuery, UseQueryOptions } from 'react-query'
export type QueryKey = ['Lists']
const queryFunction = () => {
return client<Mastodon.List[]>({
return apiInstance<Mastodon.List[]>({
method: 'get',
instance: 'local',
url: 'lists'
}).then(res => res.body)
}

View File

@ -0,0 +1,24 @@
import apiInstance from '@api/instance'
import { AxiosError } from 'axios'
import { useQuery, UseQueryOptions } from 'react-query'
export type QueryKey = ['Push']
const queryFunction = async () => {
const res = await apiInstance<Mastodon.PushSubscription>({
method: 'get',
url: 'push/subscription'
})
return res.body
}
const usePushQuery = <TData = Mastodon.PushSubscription>({
options
}: {
options?: UseQueryOptions<Mastodon.PushSubscription, AxiosError, TData>
}) => {
const queryKey: QueryKey = ['Push']
return useQuery(queryKey, queryFunction, options)
}
export { usePushQuery }

View File

@ -1,4 +1,4 @@
import client from '@api/client'
import apiInstance from '@api/instance'
import { AxiosError } from 'axios'
import {
useMutation,
@ -15,9 +15,8 @@ export type QueryKeyRelationship = [
const queryFunction = ({ queryKey }: { queryKey: QueryKeyRelationship }) => {
const { id } = queryKey[1]
return client<Mastodon.Relationship[]>({
return apiInstance<Mastodon.Relationship[]>({
method: 'get',
instance: 'local',
url: `accounts/relationships`,
params: {
'id[]': id
@ -57,15 +56,13 @@ type MutationVarsRelationship =
const mutationFunction = async (params: MutationVarsRelationship) => {
switch (params.type) {
case 'incoming':
return client<Mastodon.Relationship>({
return apiInstance<Mastodon.Relationship>({
method: 'post',
instance: 'local',
url: `follow_requests/${params.id}/${params.payload.action}`
}).then(res => res.body)
case 'outgoing':
return client<Mastodon.Relationship>({
return apiInstance<Mastodon.Relationship>({
method: 'post',
instance: 'local',
url: `accounts/${params.id}/${params.payload.state ? 'un' : ''}${
params.payload.action
}`

View File

@ -1,4 +1,4 @@
import client from '@api/client'
import apiInstance from '@api/instance'
import { AxiosError } from 'axios'
import { useInfiniteQuery, UseInfiniteQueryOptions } from 'react-query'
@ -17,9 +17,8 @@ const queryFunction = ({
const { type, id } = queryKey[1]
let params: { [key: string]: string } = { ...pageParam }
return client<Mastodon.Account[]>({
return apiInstance<Mastodon.Account[]>({
method: 'get',
instance: 'local',
url: `accounts/${id}/${type}`,
params
})

View File

@ -1,4 +1,4 @@
import client from '@api/client'
import apiInstance from '@api/instance'
import { AxiosError } from 'axios'
import { useQuery, UseQueryOptions } from 'react-query'
@ -19,10 +19,9 @@ type SearchResult = {
const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
const { type, term, limit = 20 } = queryKey[1]
return client<SearchResult>({
return apiInstance<SearchResult>({
version: 'v2',
method: 'get',
instance: 'local',
url: 'search',
params: { ...(type && { type }), ...(term && { q: term }), limit }
}).then(res => res.body)

View File

@ -1,4 +1,4 @@
import client from '@api/client'
import apiInstance from '@api/instance'
import haptics from '@components/haptics'
import { AxiosError } from 'axios'
import { uniqBy } from 'lodash'
@ -35,17 +35,15 @@ const queryFunction = ({
switch (page) {
case 'Following':
return client<Mastodon.Status[]>({
return apiInstance<Mastodon.Status[]>({
method: 'get',
instance: 'local',
url: 'timelines/home',
params
})
case 'Local':
return client<Mastodon.Status[]>({
return apiInstance<Mastodon.Status[]>({
method: 'get',
instance: 'local',
url: 'timelines/public',
params: {
...params,
@ -54,26 +52,23 @@ const queryFunction = ({
})
case 'LocalPublic':
return client<Mastodon.Status[]>({
return apiInstance<Mastodon.Status[]>({
method: 'get',
instance: 'local',
url: 'timelines/public',
params
})
case 'Notifications':
return client<Mastodon.Notification[]>({
return apiInstance<Mastodon.Notification[]>({
method: 'get',
instance: 'local',
url: 'notifications',
params
})
case 'Account_Default':
if (pageParam && pageParam.hasOwnProperty('max_id')) {
return client<Mastodon.Status[]>({
return apiInstance<Mastodon.Status[]>({
method: 'get',
instance: 'local',
url: `accounts/${account}/statuses`,
params: {
exclude_replies: 'true',
@ -81,9 +76,8 @@ const queryFunction = ({
}
})
} else {
return client<(Mastodon.Status & { isPinned: boolean })[]>({
return apiInstance<(Mastodon.Status & { isPinned: boolean })[]>({
method: 'get',
instance: 'local',
url: `accounts/${account}/statuses`,
params: {
pinned: 'true'
@ -91,9 +85,8 @@ const queryFunction = ({
}).then(async res1 => {
let pinned: Mastodon.Status['id'][] = []
res1.body.forEach(status => pinned.push(status.id))
const res2 = await client<Mastodon.Status[]>({
const res2 = await apiInstance<Mastodon.Status[]>({
method: 'get',
instance: 'local',
url: `accounts/${account}/statuses`,
params: {
exclude_replies: 'true'
@ -108,17 +101,15 @@ const queryFunction = ({
}
case 'Account_All':
return client<Mastodon.Status[]>({
return apiInstance<Mastodon.Status[]>({
method: 'get',
instance: 'local',
url: `accounts/${account}/statuses`,
params
})
case 'Account_Attachments':
return client<Mastodon.Status[]>({
return apiInstance<Mastodon.Status[]>({
method: 'get',
instance: 'local',
url: `accounts/${account}/statuses`,
params: {
only_media: 'true',
@ -127,57 +118,50 @@ const queryFunction = ({
})
case 'Hashtag':
return client<Mastodon.Status[]>({
return apiInstance<Mastodon.Status[]>({
method: 'get',
instance: 'local',
url: `timelines/tag/${hashtag}`,
params
})
case 'Conversations':
return client<Mastodon.Conversation[]>({
return apiInstance<Mastodon.Conversation[]>({
method: 'get',
instance: 'local',
url: `conversations`,
params
})
case 'Bookmarks':
return client<Mastodon.Status[]>({
return apiInstance<Mastodon.Status[]>({
method: 'get',
instance: 'local',
url: `bookmarks`,
params
})
case 'Favourites':
return client<Mastodon.Status[]>({
return apiInstance<Mastodon.Status[]>({
method: 'get',
instance: 'local',
url: `favourites`,
params
})
case 'List':
return client<Mastodon.Status[]>({
return apiInstance<Mastodon.Status[]>({
method: 'get',
instance: 'local',
url: `timelines/list/${list}`,
params
})
case 'Toot':
return client<Mastodon.Status>({
return apiInstance<Mastodon.Status>({
method: 'get',
instance: 'local',
url: `statuses/${toot}`
}).then(async res1 => {
const res2 = await client<{
const res2 = await apiInstance<{
ancestors: Mastodon.Status[]
descendants: Mastodon.Status[]
}>({
method: 'get',
instance: 'local',
url: `statuses/${toot}/context`
})
return {
@ -296,9 +280,8 @@ const mutationFunction = async (params: MutationVarsTimeline) => {
}
})
return client<Mastodon.Poll>({
return apiInstance<Mastodon.Poll>({
method: params.payload.type === 'vote' ? 'post' : 'get',
instance: 'local',
url:
params.payload.type === 'vote'
? `polls/${params.payload.id}/votes`
@ -306,9 +289,8 @@ const mutationFunction = async (params: MutationVarsTimeline) => {
...(params.payload.type === 'vote' && { body: formData })
})
default:
return client<Mastodon.Status>({
return apiInstance<Mastodon.Status>({
method: 'post',
instance: 'local',
url: `statuses/${params.id}/${
params.payload.currentValue ? 'un' : ''
}${MapPropertyToUrl[params.payload.property]}`
@ -318,15 +300,13 @@ const mutationFunction = async (params: MutationVarsTimeline) => {
switch (params.payload.property) {
case 'block':
case 'mute':
return client<Mastodon.Account>({
return apiInstance<Mastodon.Account>({
method: 'post',
instance: 'local',
url: `accounts/${params.id}/${params.payload.property}`
})
case 'reports':
return client<Mastodon.Account>({
return apiInstance<Mastodon.Account>({
method: 'post',
instance: 'local',
url: `reports`,
params: {
account_id: params.id
@ -334,15 +314,13 @@ const mutationFunction = async (params: MutationVarsTimeline) => {
})
}
case 'deleteItem':
return client<Mastodon.Conversation>({
return apiInstance<Mastodon.Conversation>({
method: 'delete',
instance: 'local',
url: `${params.source}/${params.id}`
})
case 'domainBlock':
return client<any>({
return apiInstance<any>({
method: 'post',
instance: 'local',
url: `domain_blocks`,
params: {
domain: params.domain

View File

@ -32,11 +32,11 @@ export const contextsInitialState = {
current: 0,
hidden: false
},
previousTab: 'Tab-Local'
previousTab: 'Tab-Me'
}
const contextsSlice = createSlice({
name: 'settings',
name: 'contexts',
initialState: contextsInitialState as ContextsState,
reducers: {
updateStoreReview: (state, action: PayloadAction<1>) => {

View File

@ -0,0 +1,87 @@
import apiGeneral from '@api/general'
import { createAsyncThunk } from '@reduxjs/toolkit'
import { RootState } from '@root/store'
import { Instance } from '../instancesSlice'
const addInstance = createAsyncThunk(
'instances/add',
async ({
domain,
token,
instance,
max_toot_chars = 500,
appData
}: {
domain: Instance['url']
token: Instance['token']
instance: Mastodon.Instance
max_toot_chars?: number
appData: Instance['appData']
}): Promise<{ type: 'add' | 'overwrite'; data: Instance }> => {
const { store } = require('@root/store')
const instances = (store.getState() as RootState).instances.instances
const {
body: { id, acct, avatar_static }
} = await apiGeneral<Mastodon.Account>({
method: 'get',
domain,
url: `api/v1/accounts/verify_credentials`,
headers: { Authorization: `Bearer ${token}` }
})
let type: 'add' | 'overwrite'
type = 'add'
if (
instances.length &&
instances.filter(instance => {
if (instance.url === domain && instance.account.id === id) {
return true
} else {
return false
}
}).length
) {
type = 'overwrite'
} else {
type = 'add'
}
const { body: preferences } = await apiGeneral<Mastodon.Preferences>({
method: 'get',
domain,
url: `api/v1/preferences`,
headers: { Authorization: `Bearer ${token}` }
})
return Promise.resolve({
type,
data: {
active: true,
appData,
url: domain,
token,
uri: instance.uri,
urls: instance.urls,
max_toot_chars,
account: {
id,
acct,
avatarStatic: avatar_static,
preferences
},
notification: {
readTime: undefined,
latestTime: undefined
},
push: {
loading: false,
enabled: false
},
drafts: []
}
})
}
)
export default addInstance

View File

@ -0,0 +1,21 @@
import apiGeneral from '@api/general'
import * as Notifications from 'expo-notifications'
const serverUnregister = async () => {
const deviceToken = (await Notifications.getDevicePushTokenAsync()).data
return apiGeneral<{ endpoint: string; publicKey: string; auth: string }>({
method: 'post',
domain: 'testpush.home.xmflsct.com',
url: 'unregister',
body: { deviceToken }
})
}
const pushDisable = async () => {
await serverUnregister()
return false
}
export default pushDisable

View File

@ -0,0 +1,61 @@
import apiGeneral from '@api/general'
import apiInstance from '@api/instance'
import * as Notifications from 'expo-notifications'
import { Platform } from 'react-native'
const serverRegister = async () => {
const deviceToken = (await Notifications.getDevicePushTokenAsync()).data
const expoToken = (
await Notifications.getExpoPushTokenAsync({
experienceId: '@xmflsct/tooot'
})
).data
return apiGeneral<{ endpoint: string; publicKey: string; auth: string }>({
method: 'post',
domain: 'testpush.home.xmflsct.com',
url: 'register',
body: { deviceToken, expoToken }
})
}
const pushEnable = async (): Promise<Mastodon.PushSubscription> => {
const { status: existingStatus } = await Notifications.getPermissionsAsync()
let finalStatus = existingStatus
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync()
finalStatus = status
}
if (finalStatus !== 'granted') {
alert('Failed to get push token for push notification!')
return Promise.reject()
}
const serverRes = (await serverRegister()).body
const formData = new FormData()
formData.append(
'subscription[endpoint]',
'https://testpush.home.xmflsct.com/test1'
)
formData.append('subscription[keys][p256dh]', serverRes.publicKey)
formData.append('subscription[keys][auth]', serverRes.auth)
const res = await apiInstance<Mastodon.PushSubscription>({
method: 'post',
url: 'push/subscription',
body: formData
})
return res.body
// if (Platform.OS === 'android') {
// Notifications.setNotificationChannelAsync('default', {
// name: 'default',
// importance: Notifications.AndroidImportance.MAX,
// vibrationPattern: [0, 250, 250, 250],
// lightColor: '#FF231F7C'
// })
// }
}
export default pushEnable

View File

@ -0,0 +1,40 @@
import { createAsyncThunk } from '@reduxjs/toolkit'
import { RootState } from '@root/store'
import * as AuthSession from 'expo-auth-session'
const removeInstance = createAsyncThunk(
'instances/remove',
async (index: number): Promise<number> => {
const { store } = require('@root/store')
const instances = (store.getState() as RootState).instances.instances
if (index !== -1) {
const currentInstance = instances[index]
let revoked = undefined
try {
revoked = await AuthSession.revokeAsync(
{
clientId: currentInstance.appData.clientId,
clientSecret: currentInstance.appData.clientSecret,
token: currentInstance.token,
scopes: ['read', 'write', 'follow', 'push']
},
{
revocationEndpoint: `https://${currentInstance.url}/oauth/revoke`
}
)
} catch {
console.warn('Revoking error')
}
if (!revoked) {
console.warn('Revoking error')
}
}
return Promise.resolve(index)
}
)
export default removeInstance

View File

@ -0,0 +1,12 @@
import apiInstance from '@api/instance'
import { createAsyncThunk } from '@reduxjs/toolkit'
export const updateAccountPreferences = createAsyncThunk(
'instances/updateAccountPreferences',
async (): Promise<Mastodon.Preferences> => {
return apiInstance<Mastodon.Preferences>({
method: 'get',
url: `preferences`
}).then(res => res.body)
}
)

View File

@ -0,0 +1,17 @@
import { createAsyncThunk } from '@reduxjs/toolkit'
import { Instance } from '../instancesSlice'
import pushDisable from './push/disable'
import pushEnable from './push/enable'
export const updatePush = createAsyncThunk(
'instances/updatePush',
async (
enable: boolean
): Promise<Instance['push']['subscription'] | boolean> => {
if (enable) {
return pushEnable()
} else {
return pushDisable()
}
}
)

View File

@ -1,13 +1,15 @@
import client from '@api/client'
import analytics from '@components/analytics'
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { RootState } from '@root/store'
import { ComposeStateDraft } from '@screens/Compose/utils/types'
import * as AuthSession from 'expo-auth-session'
import * as Localization from 'expo-localization'
import { findIndex } from 'lodash'
import addInstance from './instances/add'
import removeInstance from './instances/remove'
import { updateAccountPreferences } from './instances/updateAccountPreferences'
import { updatePush } from './instances/updatePush'
export type InstanceLocal = {
export type Instance = {
active: boolean
appData: {
clientId: string
clientSecret: string
@ -27,245 +29,105 @@ export type InstanceLocal = {
readTime?: Mastodon.Notification['created_at']
latestTime?: Mastodon.Notification['created_at']
}
push: {
loading: boolean
enabled: boolean
subscription?: Mastodon.PushSubscription
}
drafts: ComposeStateDraft[]
}
export type InstancesState = {
local: {
activeIndex: number | null
instances: InstanceLocal[]
}
remote: {
url: string
}
instances: Instance[]
}
export const updateLocalAccountPreferences = createAsyncThunk(
'instances/updateLocalAccountPreferences',
async (): Promise<Mastodon.Preferences> => {
const res = await client<Mastodon.Preferences>({
method: 'get',
instance: 'local',
url: `preferences`
})
return Promise.resolve(res.body)
}
)
export const localAddInstance = createAsyncThunk(
'instances/localAddInstance',
async ({
url,
token,
instance,
max_toot_chars = 500,
appData
}: {
url: InstanceLocal['url']
token: InstanceLocal['token']
instance: Mastodon.Instance
max_toot_chars?: number
appData: InstanceLocal['appData']
}): Promise<{ type: 'add' | 'overwrite'; data: InstanceLocal }> => {
const { store } = require('@root/store')
const instanceLocal: InstancesState['local'] = store.getState().instances
.local
const {
body: { id, acct, avatar_static }
} = await client<Mastodon.Account>({
method: 'get',
instance: 'remote',
instanceDomain: url,
url: `accounts/verify_credentials`,
headers: { Authorization: `Bearer ${token}` }
})
let type: 'add' | 'overwrite'
if (
instanceLocal.instances.filter(instance => {
if (instance) {
if (instance.url === url && instance.account.id === id) {
return true
} else {
return false
}
} else {
return false
}
}).length
) {
type = 'overwrite'
} else {
type = 'add'
}
const { body: preferences } = await client<Mastodon.Preferences>({
method: 'get',
instance: 'remote',
instanceDomain: url,
url: `preferences`,
headers: { Authorization: `Bearer ${token}` }
})
return Promise.resolve({
type,
data: {
appData,
url,
token,
uri: instance.uri,
urls: instance.urls,
max_toot_chars,
account: {
id,
acct,
avatarStatic: avatar_static,
preferences
},
notification: {
readTime: undefined,
latestTime: undefined
},
drafts: []
}
})
}
)
export const localRemoveInstance = createAsyncThunk(
'instances/localRemoveInstance',
async (index?: InstancesState['local']['activeIndex']): Promise<number> => {
const { store } = require('@root/store')
const instanceLocal: InstancesState['local'] = store.getState().instances
.local
if (index) {
return Promise.resolve(index)
} else {
if (instanceLocal.activeIndex !== null) {
const currentInstance =
instanceLocal.instances[instanceLocal.activeIndex]
let revoked = undefined
try {
revoked = await AuthSession.revokeAsync(
{
clientId: currentInstance.appData.clientId,
clientSecret: currentInstance.appData.clientSecret,
token: currentInstance.token,
scopes: ['read', 'write', 'follow', 'push']
},
{
revocationEndpoint: `https://${currentInstance.url}/oauth/revoke`
}
)
} catch {}
if (!revoked) {
console.warn('Revoking error')
}
return Promise.resolve(instanceLocal.activeIndex)
} else {
throw new Error('Active index invalid, cannot remove instance')
}
}
}
)
export const instancesInitialState: InstancesState = {
local: {
activeIndex: null,
instances: []
},
remote: {
url: Localization.locale.includes('zh') ? 'm.cmx.im' : 'mastodon.social'
}
instances: []
}
const findInstanceActive = (state: Instance[]) =>
state.findIndex(instance => instance.active)
const instancesSlice = createSlice({
name: 'instances',
initialState: instancesInitialState,
reducers: {
updateLocalActiveIndex: (state, action: PayloadAction<InstanceLocal>) => {
state.local.activeIndex = state.local.instances.findIndex(
instance =>
updateInstanceActive: ({ instances }, action: PayloadAction<Instance>) => {
instances = instances.map(instance => {
instance.active =
instance.url === action.payload.url &&
instance.token === action.payload.token &&
instance.account.id === action.payload.account.id
)
return instance
})
},
updateLocalAccount: (
state,
action: PayloadAction<
Pick<InstanceLocal['account'], 'acct' & 'avatarStatic'>
>
updateInstanceAccount: (
{ instances },
action: PayloadAction<Pick<Instance['account'], 'acct' & 'avatarStatic'>>
) => {
if (state.local.activeIndex !== null) {
state.local.instances[state.local.activeIndex].account = {
...state.local.instances[state.local.activeIndex].account,
...action.payload
}
const activeIndex = findInstanceActive(instances)
instances[activeIndex].account = {
...instances[activeIndex].account,
...action.payload
}
},
updateLocalNotification: (
state,
action: PayloadAction<Partial<InstanceLocal['notification']>>
updateInstanceNotification: (
{ instances },
action: PayloadAction<Partial<Instance['notification']>>
) => {
if (state.local.activeIndex !== null) {
state.local.instances[state.local.activeIndex].notification = {
...state.local.instances[state.local.activeIndex].notification,
...action.payload
}
const activeIndex = findInstanceActive(instances)
instances[activeIndex].notification = {
...instances[activeIndex].notification,
...action.payload
}
},
updateLocalDraft: (state, action: PayloadAction<ComposeStateDraft>) => {
if (state.local.activeIndex !== null) {
const draftIndex = findIndex(
state.local.instances[state.local.activeIndex].drafts,
['timestamp', action.payload.timestamp]
)
if (draftIndex === -1) {
state.local.instances[state.local.activeIndex].drafts.unshift(
action.payload
)
} else {
state.local.instances[state.local.activeIndex].drafts[draftIndex] =
action.payload
}
updateInstanceDraft: (
{ instances },
action: PayloadAction<ComposeStateDraft>
) => {
const activeIndex = findInstanceActive(instances)
const draftIndex = findIndex(instances[activeIndex].drafts, [
'timestamp',
action.payload.timestamp
])
if (draftIndex === -1) {
instances[activeIndex].drafts.unshift(action.payload)
} else {
instances[activeIndex].drafts[draftIndex] = action.payload
}
},
removeLocalDraft: (
state,
removeInstanceDraft: (
{ instances },
action: PayloadAction<ComposeStateDraft['timestamp']>
) => {
if (state.local.activeIndex !== null) {
state.local.instances[
state.local.activeIndex
].drafts = state.local.instances[
state.local.activeIndex
].drafts?.filter(draft => draft.timestamp !== action.payload)
}
const activeIndex = findInstanceActive(instances)
instances[activeIndex].drafts = instances[activeIndex].drafts?.filter(
draft => draft.timestamp !== action.payload
)
}
},
extraReducers: builder => {
builder
.addCase(localAddInstance.fulfilled, (state, action) => {
.addCase(addInstance.fulfilled, (state, action) => {
switch (action.payload.type) {
case 'add':
state.local.instances.push(action.payload.data)
state.local.activeIndex = state.local.instances.length - 1
state.instances.length &&
(state.instances = state.instances.map(instance => {
instance.active = false
return instance
}))
state.instances.push(action.payload.data)
break
case 'overwrite':
state.local.instances = state.local.instances.map(instance => {
console.log('overwriting')
state.instances = state.instances.map(instance => {
if (
instance.url === action.payload.data.url &&
instance.account.id === action.payload.data.account.id
) {
return action.payload.data
} else {
instance.active = false
return instance
}
})
@ -273,76 +135,99 @@ const instancesSlice = createSlice({
analytics('login')
})
.addCase(localAddInstance.rejected, (state, action) => {
console.error(state.local)
.addCase(addInstance.rejected, (state, action) => {
console.error(state.instances)
console.error(action.error)
})
.addCase(localRemoveInstance.fulfilled, (state, action) => {
state.local.instances.splice(action.payload, 1)
state.local.activeIndex = state.local.instances.length
? state.local.instances.length - 1
: null
.addCase(removeInstance.fulfilled, (state, action) => {
state.instances.splice(action.payload, 1)
state.instances.length &&
(state.instances[state.instances.length - 1].active = true)
analytics('logout')
})
.addCase(localRemoveInstance.rejected, (state, action) => {
console.error(state.local)
.addCase(removeInstance.rejected, (state, action) => {
console.error(state)
console.error(action.error)
})
.addCase(updateLocalAccountPreferences.fulfilled, (state, action) => {
state.local.instances[state.local.activeIndex!].account.preferences =
action.payload
.addCase(updateAccountPreferences.fulfilled, (state, action) => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].account.preferences = action.payload
})
.addCase(updateLocalAccountPreferences.rejected, (_, action) => {
.addCase(updateAccountPreferences.rejected, (_, action) => {
console.error(action.error)
})
.addCase(updatePush.fulfilled, (state, action) => {
const activeIndex = findInstanceActive(state.instances)
if (typeof action.payload === 'boolean') {
state.instances[activeIndex].push.enabled = action.payload
} else {
state.instances[activeIndex].push.enabled = true
state.instances[activeIndex].push.subscription = action.payload
}
})
}
})
export const getLocalActiveIndex = ({ instances: { local } }: RootState) =>
local.activeIndex
export const getLocalInstances = ({ instances: { local } }: RootState) =>
local.instances
export const getLocalInstance = ({ instances: { local } }: RootState) =>
local.activeIndex !== null ? local.instances[local.activeIndex] : undefined
export const getLocalUrl = ({ instances: { local } }: RootState) =>
local.activeIndex !== null
? local.instances[local.activeIndex].url
: undefined
export const getLocalUri = ({ instances: { local } }: RootState) =>
local.activeIndex !== null
? local.instances[local.activeIndex].uri
: undefined
export const getLocalUrls = ({ instances: { local } }: RootState) =>
local.activeIndex !== null
? local.instances[local.activeIndex].urls
: undefined
export const getLocalMaxTootChar = ({ instances: { local } }: RootState) =>
local.activeIndex !== null
? local.instances[local.activeIndex].max_toot_chars
: 500
export const getLocalAccount = ({ instances: { local } }: RootState) =>
local.activeIndex !== null
? local.instances[local.activeIndex].account
: undefined
export const getLocalNotification = ({ instances: { local } }: RootState) =>
local.activeIndex !== null
? local.instances[local.activeIndex].notification
: undefined
export const getLocalDrafts = ({ instances: { local } }: RootState) =>
local.activeIndex !== null
? local.instances[local.activeIndex].drafts
: undefined
export const getRemoteUrl = ({ instances: { remote } }: RootState) => remote.url
export const getInstanceActive = ({ instances: { instances } }: RootState) =>
findInstanceActive(instances)
export const getInstances = ({ instances: { instances } }: RootState) =>
instances
export const getInstance = ({ instances: { instances } }: RootState) => {
const instanceActive = findInstanceActive(instances)
return instanceActive !== -1 ? instances[instanceActive] : null
}
export const getInstanceUrl = ({ instances: { instances } }: RootState) => {
const instanceActive = findInstanceActive(instances)
return instanceActive !== -1 ? instances[instanceActive].url : null
}
export const getInstanceUri = ({ instances: { instances } }: RootState) => {
const instanceActive = findInstanceActive(instances)
return instanceActive !== -1 ? instances[instanceActive].uri : null
}
export const getInstanceUrls = ({ instances: { instances } }: RootState) => {
const instanceActive = findInstanceActive(instances)
return instanceActive !== -1 ? instances[instanceActive].urls : null
}
export const getInstanceMaxTootChar = ({
instances: { instances }
}: RootState) => {
const instanceActive = findInstanceActive(instances)
return instanceActive !== -1 ? instances[instanceActive].max_toot_chars : null
}
export const getInstanceAccount = ({ instances: { instances } }: RootState) => {
const instanceActive = findInstanceActive(instances)
return instanceActive !== -1 ? instances[instanceActive].account : null
}
export const getInstanceNotification = ({
instances: { instances }
}: RootState) => {
const instanceActive = findInstanceActive(instances)
return instanceActive !== -1 ? instances[instanceActive].notification : null
}
export const getInstanceDrafts = ({ instances: { instances } }: RootState) => {
const instanceActive = findInstanceActive(instances)
return instanceActive !== -1 ? instances[instanceActive].drafts : null
}
export const {
updateLocalActiveIndex,
updateLocalAccount,
updateLocalNotification,
updateLocalDraft,
removeLocalDraft
updateInstanceActive,
updateInstanceAccount,
updateInstanceNotification,
updateInstanceDraft,
removeInstanceDraft
} = instancesSlice.actions
export default instancesSlice.reducer

View File

@ -9,6 +9,14 @@ enum availableLanguages {
'en'
}
export const changeAnalytics = createAsyncThunk(
'settings/changeAnalytics',
async (newValue: SettingsState['analytics']) => {
await Analytics.setAnalyticsCollectionEnabled(newValue)
return newValue
}
)
export type SettingsState = {
language: keyof availableLanguages
theme: 'light' | 'dark' | 'auto'
@ -17,6 +25,9 @@ export type SettingsState = {
}
export const settingsInitialState = {
notification: {
enabled: false
},
language: Object.keys(
pickBy(availableLanguages, (_, key) => Localization.locale.includes(key))
)
@ -31,14 +42,6 @@ export const settingsInitialState = {
analytics: true
}
export const changeAnalytics = createAsyncThunk(
'settings/changeAnalytics',
async (newValue: SettingsState['analytics']) => {
await Analytics.setAnalyticsCollectionEnabled(newValue)
return newValue
}
)
const settingsSlice = createSlice({
name: 'settings',
initialState: settingsInitialState as SettingsState,