use typed createAsyncThunk

This commit is contained in:
Athou
2023-12-28 19:49:38 +01:00
parent aa009c366d
commit f4e48383cc
6 changed files with 227 additions and 272 deletions

View File

@@ -1,8 +1,8 @@
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit" import { createSlice, PayloadAction } from "@reduxjs/toolkit"
import { client } from "app/client" import { client } from "app/client"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { RootState } from "app/store" import { createAppAsyncThunk, RootState } from "app/store"
import { Entries, Entry, MarkRequest, TagRequest } from "app/types" import { Entry, MarkRequest, TagRequest } from "app/types"
import { scrollToWithCallback } from "app/utils" import { scrollToWithCallback } from "app/utils"
import { flushSync } from "react-dom" import { flushSync } from "react-dom"
// eslint-disable-next-line import/no-cycle // eslint-disable-next-line import/no-cycle
@@ -47,27 +47,24 @@ const initialState: EntriesState = {
const getEndpoint = (sourceType: EntrySourceType) => const getEndpoint = (sourceType: EntrySourceType) =>
sourceType === "category" || sourceType === "tag" ? client.category.getEntries : client.feed.getEntries sourceType === "category" || sourceType === "tag" ? client.category.getEntries : client.feed.getEntries
export const loadEntries = createAsyncThunk< export const loadEntries = createAppAsyncThunk(
Entries, "entries/load",
{ source: EntrySource; clearSearch: boolean }, async (
{ arg: {
state: RootState source: EntrySource
} clearSearch: boolean
>("entries/load", async (arg, thunkApi) => { },
if (arg.clearSearch) thunkApi.dispatch(setSearch("")) thunkApi
) => {
if (arg.clearSearch) thunkApi.dispatch(setSearch(""))
const state = thunkApi.getState() const state = thunkApi.getState()
const endpoint = getEndpoint(arg.source.type) const endpoint = getEndpoint(arg.source.type)
const result = await endpoint(buildGetEntriesPaginatedRequest(state, arg.source, 0)) const result = await endpoint(buildGetEntriesPaginatedRequest(state, arg.source, 0))
return result.data return result.data
})
export const loadMoreEntries = createAsyncThunk<
Entries,
void,
{
state: RootState
} }
>("entries/loadMore", async (_, thunkApi) => { )
export const loadMoreEntries = createAppAsyncThunk("entries/loadMore", async (_, thunkApi) => {
const state = thunkApi.getState() const state = thunkApi.getState()
const { source } = state.entries const { source } = state.entries
const offset = const offset =
@@ -85,22 +82,16 @@ const buildGetEntriesPaginatedRequest = (state: RootState, source: EntrySource,
tag: source.type === "tag" ? source.id : undefined, tag: source.type === "tag" ? source.id : undefined,
keywords: state.entries.search, keywords: state.entries.search,
}) })
export const reloadEntries = createAsyncThunk< export const reloadEntries = createAppAsyncThunk("entries/reload", async (arg, thunkApi) => {
void,
void,
{
state: RootState
}
>("entries/reload", async (arg, thunkApi) => {
const state = thunkApi.getState() const state = thunkApi.getState()
thunkApi.dispatch(loadEntries({ source: state.entries.source, clearSearch: false })) thunkApi.dispatch(loadEntries({ source: state.entries.source, clearSearch: false }))
}) })
export const search = createAsyncThunk<void, string, { state: RootState }>("entries/search", async (arg, thunkApi) => { export const search = createAppAsyncThunk("entries/search", async (arg: string, thunkApi) => {
const state = thunkApi.getState() const state = thunkApi.getState()
thunkApi.dispatch(setSearch(arg)) thunkApi.dispatch(setSearch(arg))
thunkApi.dispatch(loadEntries({ source: state.entries.source, clearSearch: false })) thunkApi.dispatch(loadEntries({ source: state.entries.source, clearSearch: false }))
}) })
export const markEntry = createAsyncThunk( export const markEntry = createAppAsyncThunk(
"entries/entry/mark", "entries/entry/mark",
(arg: { entry: Entry; read: boolean }) => { (arg: { entry: Entry; read: boolean }) => {
client.entry.mark({ client.entry.mark({
@@ -112,9 +103,15 @@ export const markEntry = createAsyncThunk(
condition: arg => arg.entry.read !== arg.read, condition: arg => arg.entry.read !== arg.read,
} }
) )
export const markMultipleEntries = createAsyncThunk( export const markMultipleEntries = createAppAsyncThunk(
"entries/entry/markMultiple", "entries/entry/markMultiple",
async (arg: { entries: Entry[]; read: boolean }, thunkApi) => { async (
arg: {
entries: Entry[]
read: boolean
},
thunkApi
) => {
const requests: MarkRequest[] = arg.entries.map(e => ({ const requests: MarkRequest[] = arg.entries.map(e => ({
id: e.id, id: e.id,
read: arg.read, read: arg.read,
@@ -123,88 +120,90 @@ export const markMultipleEntries = createAsyncThunk(
thunkApi.dispatch(reloadTree()) thunkApi.dispatch(reloadTree())
} }
) )
export const markEntriesUpToEntry = createAsyncThunk<void, Entry, { state: RootState }>( export const markEntriesUpToEntry = createAppAsyncThunk("entries/entry/upToEntry", async (arg: Entry, thunkApi) => {
"entries/entry/upToEntry", const state = thunkApi.getState()
async (arg, thunkApi) => { const { entries } = state.entries
const state = thunkApi.getState()
const { entries } = state.entries
const index = entries.findIndex(e => e.id === arg.id) const index = entries.findIndex(e => e.id === arg.id)
if (index === -1) return if (index === -1) return
thunkApi.dispatch( thunkApi.dispatch(
markMultipleEntries({ markMultipleEntries({
entries: entries.slice(0, index + 1), entries: entries.slice(0, index + 1),
read: true, read: true,
}) })
) )
})
export const markAllEntries = createAppAsyncThunk(
"entries/entry/markAll",
async (
arg: {
sourceType: EntrySourceType
req: MarkRequest
},
thunkApi
) => {
const endpoint = arg.sourceType === "category" ? client.category.markEntries : client.feed.markEntries
await endpoint(arg.req)
thunkApi.dispatch(reloadEntries())
thunkApi.dispatch(reloadTree())
} }
) )
export const markAllEntries = createAsyncThunk< export const starEntry = createAppAsyncThunk("entries/entry/star", (arg: { entry: Entry; starred: boolean }) => {
void,
{ sourceType: EntrySourceType; req: MarkRequest },
{
state: RootState
}
>("entries/entry/markAll", async (arg, thunkApi) => {
const endpoint = arg.sourceType === "category" ? client.category.markEntries : client.feed.markEntries
await endpoint(arg.req)
thunkApi.dispatch(reloadEntries())
thunkApi.dispatch(reloadTree())
})
export const starEntry = createAsyncThunk("entries/entry/star", (arg: { entry: Entry; starred: boolean }) => {
client.entry.star({ client.entry.star({
id: arg.entry.id, id: arg.entry.id,
feedId: +arg.entry.feedId, feedId: +arg.entry.feedId,
starred: arg.starred, starred: arg.starred,
}) })
}) })
export const selectEntry = createAsyncThunk< export const selectEntry = createAppAsyncThunk(
void, "entries/entry/select",
{ (
entry: Entry arg: {
expand: boolean entry: Entry
markAsRead: boolean expand: boolean
scrollToEntry: boolean markAsRead: boolean
}, scrollToEntry: boolean
{ state: RootState } },
>("entries/entry/select", (arg, thunkApi) => { thunkApi
const state = thunkApi.getState() ) => {
const entry = state.entries.entries.find(e => e.id === arg.entry.id) const state = thunkApi.getState()
if (!entry) return const entry = state.entries.entries.find(e => e.id === arg.entry.id)
if (!entry) return
// flushSync is required because we need the newly selected entry to be expanded // flushSync is required because we need the newly selected entry to be expanded
// and the previously selected entry to be collapsed to be able to scroll to the right position // and the previously selected entry to be collapsed to be able to scroll to the right position
flushSync(() => { flushSync(() => {
// mark as read if requested // mark as read if requested
if (arg.markAsRead) { if (arg.markAsRead) {
thunkApi.dispatch(markEntry({ entry, read: true })) thunkApi.dispatch(markEntry({ entry, read: true }))
} }
// set entry as selected // set entry as selected
thunkApi.dispatch(entriesSlice.actions.setSelectedEntry(entry)) thunkApi.dispatch(entriesSlice.actions.setSelectedEntry(entry))
// expand if requested // expand if requested
const previouslySelectedEntry = state.entries.entries.find(e => e.id === state.entries.selectedEntryId) const previouslySelectedEntry = state.entries.entries.find(e => e.id === state.entries.selectedEntryId)
if (previouslySelectedEntry) { if (previouslySelectedEntry) {
thunkApi.dispatch(entriesSlice.actions.setEntryExpanded({ entry: previouslySelectedEntry, expanded: false })) thunkApi.dispatch(entriesSlice.actions.setEntryExpanded({ entry: previouslySelectedEntry, expanded: false }))
} }
thunkApi.dispatch(entriesSlice.actions.setEntryExpanded({ entry, expanded: arg.expand })) thunkApi.dispatch(entriesSlice.actions.setEntryExpanded({ entry, expanded: arg.expand }))
}) })
if (arg.scrollToEntry) { if (arg.scrollToEntry) {
const entryElement = document.getElementById(Constants.dom.entryId(entry)) const entryElement = document.getElementById(Constants.dom.entryId(entry))
if (entryElement) { if (entryElement) {
const alwaysScrollToEntry = state.user.settings?.alwaysScrollToEntry const alwaysScrollToEntry = state.user.settings?.alwaysScrollToEntry
const entryEntirelyVisible = Constants.layout.isTopVisible(entryElement) && Constants.layout.isBottomVisible(entryElement) const entryEntirelyVisible = Constants.layout.isTopVisible(entryElement) && Constants.layout.isBottomVisible(entryElement)
if (alwaysScrollToEntry || !entryEntirelyVisible) { if (alwaysScrollToEntry || !entryEntirelyVisible) {
const scrollSpeed = state.user.settings?.scrollSpeed const scrollSpeed = state.user.settings?.scrollSpeed
thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(true)) thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(true))
scrollToEntry(entryElement, scrollSpeed, () => thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(false))) scrollToEntry(entryElement, scrollSpeed, () => thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(false)))
}
} }
} }
} }
}) )
const scrollToEntry = (entryElement: HTMLElement, scrollSpeed: number | undefined, onScrollEnded: () => void) => { const scrollToEntry = (entryElement: HTMLElement, scrollSpeed: number | undefined, onScrollEnded: () => void) => {
scrollToWithCallback({ scrollToWithCallback({
options: { options: {
@@ -216,59 +215,57 @@ const scrollToEntry = (entryElement: HTMLElement, scrollSpeed: number | undefine
}) })
} }
export const selectPreviousEntry = createAsyncThunk< export const selectPreviousEntry = createAppAsyncThunk(
void, "entries/entry/selectPrevious",
{ (
expand: boolean arg: {
markAsRead: boolean expand: boolean
scrollToEntry: boolean markAsRead: boolean
}, scrollToEntry: boolean
{ state: RootState } },
>("entries/entry/selectPrevious", (arg, thunkApi) => { thunkApi
const state = thunkApi.getState() ) => {
const { entries } = state.entries const state = thunkApi.getState()
const previousIndex = entries.findIndex(e => e.id === state.entries.selectedEntryId) - 1 const { entries } = state.entries
if (previousIndex >= 0) { const previousIndex = entries.findIndex(e => e.id === state.entries.selectedEntryId) - 1
thunkApi.dispatch( if (previousIndex >= 0) {
selectEntry({ thunkApi.dispatch(
entry: entries[previousIndex], selectEntry({
expand: arg.expand, entry: entries[previousIndex],
markAsRead: arg.markAsRead, expand: arg.expand,
scrollToEntry: arg.scrollToEntry, markAsRead: arg.markAsRead,
}) scrollToEntry: arg.scrollToEntry,
) })
)
}
} }
}) )
export const selectNextEntry = createAsyncThunk< export const selectNextEntry = createAppAsyncThunk(
void, "entries/entry/selectNext",
{ (
expand: boolean arg: {
markAsRead: boolean expand: boolean
scrollToEntry: boolean markAsRead: boolean
}, scrollToEntry: boolean
{ state: RootState } },
>("entries/entry/selectNext", (arg, thunkApi) => { thunkApi
const state = thunkApi.getState() ) => {
const { entries } = state.entries const state = thunkApi.getState()
const nextIndex = entries.findIndex(e => e.id === state.entries.selectedEntryId) + 1 const { entries } = state.entries
if (nextIndex < entries.length) { const nextIndex = entries.findIndex(e => e.id === state.entries.selectedEntryId) + 1
thunkApi.dispatch( if (nextIndex < entries.length) {
selectEntry({ thunkApi.dispatch(
entry: entries[nextIndex], selectEntry({
expand: arg.expand, entry: entries[nextIndex],
markAsRead: arg.markAsRead, expand: arg.expand,
scrollToEntry: arg.scrollToEntry, markAsRead: arg.markAsRead,
}) scrollToEntry: arg.scrollToEntry,
) })
)
}
} }
}) )
export const tagEntry = createAsyncThunk< export const tagEntry = createAppAsyncThunk("entries/entry/tag", async (arg: TagRequest, thunkApi) => {
void,
TagRequest,
{
state: RootState
}
>("entries/entry/tag", async (arg, thunkApi) => {
await client.entry.tag(arg) await client.entry.tag(arg)
thunkApi.dispatch(reloadTags()) thunkApi.dispatch(reloadTags())
}) })

View File

@@ -1,6 +1,6 @@
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit" import { createSlice, PayloadAction } from "@reduxjs/toolkit"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { RootState } from "app/store" import { createAppAsyncThunk } from "app/store"
interface RedirectState { interface RedirectState {
to?: string to?: string
@@ -8,52 +8,46 @@ interface RedirectState {
const initialState: RedirectState = {} const initialState: RedirectState = {}
export const redirectToLogin = createAsyncThunk("redirect/login", (_, thunkApi) => thunkApi.dispatch(redirectTo("/login"))) export const redirectToLogin = createAppAsyncThunk("redirect/login", (_, thunkApi) => thunkApi.dispatch(redirectTo("/login")))
export const redirectToRegistration = createAsyncThunk("redirect/register", (_, thunkApi) => thunkApi.dispatch(redirectTo("/register"))) export const redirectToRegistration = createAppAsyncThunk("redirect/register", (_, thunkApi) => thunkApi.dispatch(redirectTo("/register")))
export const redirectToPasswordRecovery = createAsyncThunk("redirect/passwordRecovery", (_, thunkApi) => export const redirectToPasswordRecovery = createAppAsyncThunk("redirect/passwordRecovery", (_, thunkApi) =>
thunkApi.dispatch(redirectTo("/passwordRecovery")) thunkApi.dispatch(redirectTo("/passwordRecovery"))
) )
export const redirectToApiDocumentation = createAsyncThunk("redirect/api", (_, thunkApi) => thunkApi.dispatch(redirectTo("/api"))) export const redirectToApiDocumentation = createAppAsyncThunk("redirect/api", (_, thunkApi) => thunkApi.dispatch(redirectTo("/api")))
export const redirectToSelectedSource = createAsyncThunk< export const redirectToSelectedSource = createAppAsyncThunk("redirect/selectedSource", (_, thunkApi) => {
void,
void,
{
state: RootState
}
>("redirect/selectedSource", (_, thunkApi) => {
const { source } = thunkApi.getState().entries const { source } = thunkApi.getState().entries
thunkApi.dispatch(redirectTo(`/app/${source.type}/${source.id}`)) thunkApi.dispatch(redirectTo(`/app/${source.type}/${source.id}`))
}) })
export const redirectToCategory = createAsyncThunk("redirect/category", (id: string, thunkApi) => export const redirectToCategory = createAppAsyncThunk("redirect/category", (id: string, thunkApi) =>
thunkApi.dispatch(redirectTo(`/app/category/${id}`)) thunkApi.dispatch(redirectTo(`/app/category/${id}`))
) )
export const redirectToRootCategory = createAsyncThunk("redirect/category/root", (_, thunkApi) => export const redirectToRootCategory = createAppAsyncThunk("redirect/category/root", (_, thunkApi) =>
thunkApi.dispatch(redirectToCategory(Constants.categories.all.id)) thunkApi.dispatch(redirectToCategory(Constants.categories.all.id))
) )
export const redirectToCategoryDetails = createAsyncThunk("redirect/category/details", (id: string, thunkApi) => export const redirectToCategoryDetails = createAppAsyncThunk("redirect/category/details", (id: string, thunkApi) =>
thunkApi.dispatch(redirectTo(`/app/category/${id}/details`)) thunkApi.dispatch(redirectTo(`/app/category/${id}/details`))
) )
export const redirectToFeed = createAsyncThunk("redirect/feed", (id: string | number, thunkApi) => export const redirectToFeed = createAppAsyncThunk("redirect/feed", (id: string | number, thunkApi) =>
thunkApi.dispatch(redirectTo(`/app/feed/${id}`)) thunkApi.dispatch(redirectTo(`/app/feed/${id}`))
) )
export const redirectToFeedDetails = createAsyncThunk("redirect/feed/details", (id: string, thunkApi) => export const redirectToFeedDetails = createAppAsyncThunk("redirect/feed/details", (id: string, thunkApi) =>
thunkApi.dispatch(redirectTo(`/app/feed/${id}/details`)) thunkApi.dispatch(redirectTo(`/app/feed/${id}/details`))
) )
export const redirectToTag = createAsyncThunk("redirect/tag", (id: string, thunkApi) => thunkApi.dispatch(redirectTo(`/app/tag/${id}`))) export const redirectToTag = createAppAsyncThunk("redirect/tag", (id: string, thunkApi) => thunkApi.dispatch(redirectTo(`/app/tag/${id}`)))
export const redirectToTagDetails = createAsyncThunk("redirect/tag/details", (id: string, thunkApi) => export const redirectToTagDetails = createAppAsyncThunk("redirect/tag/details", (id: string, thunkApi) =>
thunkApi.dispatch(redirectTo(`/app/tag/${id}/details`)) thunkApi.dispatch(redirectTo(`/app/tag/${id}/details`))
) )
export const redirectToAdd = createAsyncThunk("redirect/add", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/add"))) export const redirectToAdd = createAppAsyncThunk("redirect/add", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/add")))
export const redirectToSettings = createAsyncThunk("redirect/settings", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/settings"))) export const redirectToSettings = createAppAsyncThunk("redirect/settings", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/settings")))
export const redirectToAdminUsers = createAsyncThunk("redirect/admin/users", (_, thunkApi) => export const redirectToAdminUsers = createAppAsyncThunk("redirect/admin/users", (_, thunkApi) =>
thunkApi.dispatch(redirectTo("/app/admin/users")) thunkApi.dispatch(redirectTo("/app/admin/users"))
) )
export const redirectToMetrics = createAsyncThunk("redirect/admin/metrics", (_, thunkApi) => export const redirectToMetrics = createAppAsyncThunk("redirect/admin/metrics", (_, thunkApi) =>
thunkApi.dispatch(redirectTo("/app/admin/metrics")) thunkApi.dispatch(redirectTo("/app/admin/metrics"))
) )
export const redirectToDonate = createAsyncThunk("redirect/donate", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/donate"))) export const redirectToDonate = createAppAsyncThunk("redirect/donate", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/donate")))
export const redirectToAbout = createAsyncThunk("redirect/about", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/about"))) export const redirectToAbout = createAppAsyncThunk("redirect/about", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/about")))
export const redirectSlice = createSlice({ export const redirectSlice = createSlice({
name: "redirect", name: "redirect",

View File

@@ -1,5 +1,6 @@
import { PayloadAction, createAsyncThunk, createSlice } from "@reduxjs/toolkit" import { PayloadAction, createSlice } from "@reduxjs/toolkit"
import { client } from "app/client" import { client } from "app/client"
import { createAppAsyncThunk } from "app/store"
import { ServerInfo } from "app/types" import { ServerInfo } from "app/types"
interface ServerState { interface ServerState {
@@ -11,7 +12,7 @@ const initialState: ServerState = {
webSocketConnected: false, webSocketConnected: false,
} }
export const reloadServerInfos = createAsyncThunk("server/infos", () => client.server.getServerInfos().then(r => r.data)) export const reloadServerInfos = createAppAsyncThunk("server/infos", () => client.server.getServerInfos().then(r => r.data))
export const serverSlice = createSlice({ export const serverSlice = createSlice({
name: "server", name: "server",
initialState, initialState,

View File

@@ -1,5 +1,6 @@
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit" import { createSlice, PayloadAction } from "@reduxjs/toolkit"
import { client } from "app/client" import { client } from "app/client"
import { createAppAsyncThunk } from "app/store"
import { Category, CollapseRequest } from "app/types" import { Category, CollapseRequest } from "app/types"
import { visitCategoryTree } from "app/utils" import { visitCategoryTree } from "app/utils"
// eslint-disable-next-line import/no-cycle // eslint-disable-next-line import/no-cycle
@@ -19,8 +20,8 @@ const initialState: TreeState = {
sidebarVisible: true, sidebarVisible: true,
} }
export const reloadTree = createAsyncThunk("tree/reload", () => client.category.getRoot().then(r => r.data)) export const reloadTree = createAppAsyncThunk("tree/reload", () => client.category.getRoot().then(r => r.data))
export const collapseTreeCategory = createAsyncThunk("tree/category/collapse", async (req: CollapseRequest) => export const collapseTreeCategory = createAppAsyncThunk("tree/category/collapse", async (req: CollapseRequest) =>
client.category.collapse(req) client.category.collapse(req)
) )

View File

@@ -1,8 +1,8 @@
import { t } from "@lingui/macro" import { t } from "@lingui/macro"
import { showNotification } from "@mantine/notifications" import { showNotification } from "@mantine/notifications"
import { createAsyncThunk, createSlice, isAnyOf } from "@reduxjs/toolkit" import { createSlice, isAnyOf } from "@reduxjs/toolkit"
import { client } from "app/client" import { client } from "app/client"
import { RootState } from "app/store" import { createAppAsyncThunk } from "app/store"
import { ReadingMode, ReadingOrder, Settings, SharingSettings, UserModel } from "app/types" import { ReadingMode, ReadingOrder, Settings, SharingSettings, UserModel } from "app/types"
// eslint-disable-next-line import/no-cycle // eslint-disable-next-line import/no-cycle
import { reloadEntries } from "./entries" import { reloadEntries } from "./entries"
@@ -15,121 +15,79 @@ interface UserState {
const initialState: UserState = {} const initialState: UserState = {}
export const reloadSettings = createAsyncThunk("settings/reload", () => client.user.getSettings().then(r => r.data)) export const reloadSettings = createAppAsyncThunk("settings/reload", () => client.user.getSettings().then(r => r.data))
export const reloadProfile = createAsyncThunk("profile/reload", () => client.user.getProfile().then(r => r.data)) export const reloadProfile = createAppAsyncThunk("profile/reload", () => client.user.getProfile().then(r => r.data))
export const reloadTags = createAsyncThunk("entries/tags", () => client.entry.getTags().then(r => r.data)) export const reloadTags = createAppAsyncThunk("entries/tags", () => client.entry.getTags().then(r => r.data))
export const changeReadingMode = createAsyncThunk<void, ReadingMode, { state: RootState }>( export const changeReadingMode = createAppAsyncThunk("settings/readingMode", (readingMode: ReadingMode, thunkApi) => {
"settings/readingMode", const { settings } = thunkApi.getState().user
(readingMode, thunkApi) => { if (!settings) return
const { settings } = thunkApi.getState().user client.user.saveSettings({ ...settings, readingMode })
if (!settings) return thunkApi.dispatch(reloadEntries())
client.user.saveSettings({ ...settings, readingMode }) })
thunkApi.dispatch(reloadEntries()) export const changeReadingOrder = createAppAsyncThunk("settings/readingOrder", (readingOrder: ReadingOrder, thunkApi) => {
} const { settings } = thunkApi.getState().user
) if (!settings) return
export const changeReadingOrder = createAsyncThunk<void, ReadingOrder, { state: RootState }>( client.user.saveSettings({ ...settings, readingOrder })
"settings/readingOrder", thunkApi.dispatch(reloadEntries())
(readingOrder, thunkApi) => { })
const { settings } = thunkApi.getState().user export const changeLanguage = createAppAsyncThunk("settings/language", (language: string, thunkApi) => {
if (!settings) return
client.user.saveSettings({ ...settings, readingOrder })
thunkApi.dispatch(reloadEntries())
}
)
export const changeLanguage = createAsyncThunk<
void,
string,
{
state: RootState
}
>("settings/language", (language, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, language }) client.user.saveSettings({ ...settings, language })
}) })
export const changeScrollSpeed = createAsyncThunk< export const changeScrollSpeed = createAppAsyncThunk("settings/scrollSpeed", (speed: boolean, thunkApi) => {
void,
boolean,
{
state: RootState
}
>("settings/scrollSpeed", (speed, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, scrollSpeed: speed ? 400 : 0 }) client.user.saveSettings({ ...settings, scrollSpeed: speed ? 400 : 0 })
}) })
export const changeShowRead = createAsyncThunk< export const changeShowRead = createAppAsyncThunk("settings/showRead", (showRead: boolean, thunkApi) => {
void,
boolean,
{
state: RootState
}
>("settings/showRead", (showRead, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, showRead }) client.user.saveSettings({ ...settings, showRead })
}) })
export const changeScrollMarks = createAsyncThunk< export const changeScrollMarks = createAppAsyncThunk("settings/scrollMarks", (scrollMarks: boolean, thunkApi) => {
void,
boolean,
{
state: RootState
}
>("settings/scrollMarks", (scrollMarks, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, scrollMarks }) client.user.saveSettings({ ...settings, scrollMarks })
}) })
export const changeAlwaysScrollToEntry = createAsyncThunk< export const changeAlwaysScrollToEntry = createAppAsyncThunk("settings/alwaysScrollToEntry", (alwaysScrollToEntry: boolean, thunkApi) => {
void,
boolean,
{
state: RootState
}
>("settings/alwaysScrollToEntry", (alwaysScrollToEntry, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, alwaysScrollToEntry }) client.user.saveSettings({ ...settings, alwaysScrollToEntry })
}) })
export const changeMarkAllAsReadConfirmation = createAsyncThunk< export const changeMarkAllAsReadConfirmation = createAppAsyncThunk(
void, "settings/markAllAsReadConfirmation",
boolean, (markAllAsReadConfirmation: boolean, thunkApi) => {
{ const { settings } = thunkApi.getState().user
state: RootState if (!settings) return
client.user.saveSettings({ ...settings, markAllAsReadConfirmation })
} }
>("settings/markAllAsReadConfirmation", (markAllAsReadConfirmation, thunkApi) => { )
const { settings } = thunkApi.getState().user export const changeCustomContextMenu = createAppAsyncThunk("settings/customContextMenu", (customContextMenu: boolean, thunkApi) => {
if (!settings) return
client.user.saveSettings({ ...settings, markAllAsReadConfirmation })
})
export const changeCustomContextMenu = createAsyncThunk<
void,
boolean,
{
state: RootState
}
>("settings/customContextMenu", (customContextMenu, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, customContextMenu }) client.user.saveSettings({ ...settings, customContextMenu })
}) })
export const changeSharingSetting = createAsyncThunk< export const changeSharingSetting = createAppAsyncThunk(
void, "settings/sharingSetting",
{ site: keyof SharingSettings; value: boolean }, (
{ sharingSetting: {
state: RootState site: keyof SharingSettings
} value: boolean
>("settings/sharingSetting", (sharingSetting, thunkApi) => {
const { settings } = thunkApi.getState().user
if (!settings) return
client.user.saveSettings({
...settings,
sharingSettings: {
...settings.sharingSettings,
[sharingSetting.site]: sharingSetting.value,
}, },
}) thunkApi
}) ) => {
const { settings } = thunkApi.getState().user
if (!settings) return
client.user.saveSettings({
...settings,
sharingSettings: {
...settings.sharingSettings,
[sharingSetting.site]: sharingSetting.value,
},
})
}
)
export const userSlice = createSlice({ export const userSlice = createSlice({
name: "user", name: "user",

View File

@@ -1,4 +1,4 @@
import { configureStore } from "@reduxjs/toolkit" import { configureStore, createAsyncThunk } from "@reduxjs/toolkit"
import { setupListeners } from "@reduxjs/toolkit/query" import { setupListeners } from "@reduxjs/toolkit/query"
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux" import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
import entriesReducer from "./slices/entries" import entriesReducer from "./slices/entries"
@@ -24,3 +24,7 @@ export type AppDispatch = typeof store.dispatch
export const useAppDispatch: () => AppDispatch = useDispatch export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
export const createAppAsyncThunk = createAsyncThunk.withTypes<{
state: RootState
dispatch: AppDispatch
}>()