diff --git a/commafeed-client/src/app/slices/entries.ts b/commafeed-client/src/app/slices/entries.ts index dbcd8d10..f28168ee 100644 --- a/commafeed-client/src/app/slices/entries.ts +++ b/commafeed-client/src/app/slices/entries.ts @@ -1,8 +1,8 @@ -import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit" +import { createSlice, PayloadAction } from "@reduxjs/toolkit" import { client } from "app/client" import { Constants } from "app/constants" -import { RootState } from "app/store" -import { Entries, Entry, MarkRequest, TagRequest } from "app/types" +import { createAppAsyncThunk, RootState } from "app/store" +import { Entry, MarkRequest, TagRequest } from "app/types" import { scrollToWithCallback } from "app/utils" import { flushSync } from "react-dom" // eslint-disable-next-line import/no-cycle @@ -47,27 +47,24 @@ const initialState: EntriesState = { const getEndpoint = (sourceType: EntrySourceType) => sourceType === "category" || sourceType === "tag" ? client.category.getEntries : client.feed.getEntries -export const loadEntries = createAsyncThunk< - Entries, - { source: EntrySource; clearSearch: boolean }, - { - state: RootState - } ->("entries/load", async (arg, thunkApi) => { - if (arg.clearSearch) thunkApi.dispatch(setSearch("")) +export const loadEntries = createAppAsyncThunk( + "entries/load", + async ( + arg: { + source: EntrySource + clearSearch: boolean + }, + thunkApi + ) => { + if (arg.clearSearch) thunkApi.dispatch(setSearch("")) - const state = thunkApi.getState() - const endpoint = getEndpoint(arg.source.type) - const result = await endpoint(buildGetEntriesPaginatedRequest(state, arg.source, 0)) - return result.data -}) -export const loadMoreEntries = createAsyncThunk< - Entries, - void, - { - state: RootState + const state = thunkApi.getState() + const endpoint = getEndpoint(arg.source.type) + const result = await endpoint(buildGetEntriesPaginatedRequest(state, arg.source, 0)) + return result.data } ->("entries/loadMore", async (_, thunkApi) => { +) +export const loadMoreEntries = createAppAsyncThunk("entries/loadMore", async (_, thunkApi) => { const state = thunkApi.getState() const { source } = state.entries const offset = @@ -85,22 +82,16 @@ const buildGetEntriesPaginatedRequest = (state: RootState, source: EntrySource, tag: source.type === "tag" ? source.id : undefined, keywords: state.entries.search, }) -export const reloadEntries = createAsyncThunk< - void, - void, - { - state: RootState - } ->("entries/reload", async (arg, thunkApi) => { +export const reloadEntries = createAppAsyncThunk("entries/reload", async (arg, thunkApi) => { const state = thunkApi.getState() thunkApi.dispatch(loadEntries({ source: state.entries.source, clearSearch: false })) }) -export const search = createAsyncThunk("entries/search", async (arg, thunkApi) => { +export const search = createAppAsyncThunk("entries/search", async (arg: string, thunkApi) => { const state = thunkApi.getState() thunkApi.dispatch(setSearch(arg)) thunkApi.dispatch(loadEntries({ source: state.entries.source, clearSearch: false })) }) -export const markEntry = createAsyncThunk( +export const markEntry = createAppAsyncThunk( "entries/entry/mark", (arg: { entry: Entry; read: boolean }) => { client.entry.mark({ @@ -112,9 +103,15 @@ export const markEntry = createAsyncThunk( condition: arg => arg.entry.read !== arg.read, } ) -export const markMultipleEntries = createAsyncThunk( +export const markMultipleEntries = createAppAsyncThunk( "entries/entry/markMultiple", - async (arg: { entries: Entry[]; read: boolean }, thunkApi) => { + async ( + arg: { + entries: Entry[] + read: boolean + }, + thunkApi + ) => { const requests: MarkRequest[] = arg.entries.map(e => ({ id: e.id, read: arg.read, @@ -123,88 +120,90 @@ export const markMultipleEntries = createAsyncThunk( thunkApi.dispatch(reloadTree()) } ) -export const markEntriesUpToEntry = createAsyncThunk( - "entries/entry/upToEntry", - async (arg, thunkApi) => { - const state = thunkApi.getState() - const { entries } = state.entries +export const markEntriesUpToEntry = createAppAsyncThunk("entries/entry/upToEntry", async (arg: Entry, thunkApi) => { + const state = thunkApi.getState() + const { entries } = state.entries - const index = entries.findIndex(e => e.id === arg.id) - if (index === -1) return + const index = entries.findIndex(e => e.id === arg.id) + if (index === -1) return - thunkApi.dispatch( - markMultipleEntries({ - entries: entries.slice(0, index + 1), - read: true, - }) - ) + thunkApi.dispatch( + markMultipleEntries({ + entries: entries.slice(0, index + 1), + 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< - 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 }) => { +export const starEntry = createAppAsyncThunk("entries/entry/star", (arg: { entry: Entry; starred: boolean }) => { client.entry.star({ id: arg.entry.id, feedId: +arg.entry.feedId, starred: arg.starred, }) }) -export const selectEntry = createAsyncThunk< - void, - { - entry: Entry - expand: boolean - markAsRead: boolean - scrollToEntry: boolean - }, - { state: RootState } ->("entries/entry/select", (arg, thunkApi) => { - const state = thunkApi.getState() - const entry = state.entries.entries.find(e => e.id === arg.entry.id) - if (!entry) return +export const selectEntry = createAppAsyncThunk( + "entries/entry/select", + ( + arg: { + entry: Entry + expand: boolean + markAsRead: boolean + scrollToEntry: boolean + }, + thunkApi + ) => { + const state = thunkApi.getState() + 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 - // and the previously selected entry to be collapsed to be able to scroll to the right position - flushSync(() => { - // mark as read if requested - if (arg.markAsRead) { - thunkApi.dispatch(markEntry({ entry, read: true })) - } + // 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 + flushSync(() => { + // mark as read if requested + if (arg.markAsRead) { + thunkApi.dispatch(markEntry({ entry, read: true })) + } - // set entry as selected - thunkApi.dispatch(entriesSlice.actions.setSelectedEntry(entry)) + // set entry as selected + thunkApi.dispatch(entriesSlice.actions.setSelectedEntry(entry)) - // expand if requested - const previouslySelectedEntry = state.entries.entries.find(e => e.id === state.entries.selectedEntryId) - if (previouslySelectedEntry) { - thunkApi.dispatch(entriesSlice.actions.setEntryExpanded({ entry: previouslySelectedEntry, expanded: false })) - } - thunkApi.dispatch(entriesSlice.actions.setEntryExpanded({ entry, expanded: arg.expand })) - }) + // expand if requested + const previouslySelectedEntry = state.entries.entries.find(e => e.id === state.entries.selectedEntryId) + if (previouslySelectedEntry) { + thunkApi.dispatch(entriesSlice.actions.setEntryExpanded({ entry: previouslySelectedEntry, expanded: false })) + } + thunkApi.dispatch(entriesSlice.actions.setEntryExpanded({ entry, expanded: arg.expand })) + }) - if (arg.scrollToEntry) { - const entryElement = document.getElementById(Constants.dom.entryId(entry)) - if (entryElement) { - const alwaysScrollToEntry = state.user.settings?.alwaysScrollToEntry - const entryEntirelyVisible = Constants.layout.isTopVisible(entryElement) && Constants.layout.isBottomVisible(entryElement) - if (alwaysScrollToEntry || !entryEntirelyVisible) { - const scrollSpeed = state.user.settings?.scrollSpeed - thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(true)) - scrollToEntry(entryElement, scrollSpeed, () => thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(false))) + if (arg.scrollToEntry) { + const entryElement = document.getElementById(Constants.dom.entryId(entry)) + if (entryElement) { + const alwaysScrollToEntry = state.user.settings?.alwaysScrollToEntry + const entryEntirelyVisible = Constants.layout.isTopVisible(entryElement) && Constants.layout.isBottomVisible(entryElement) + if (alwaysScrollToEntry || !entryEntirelyVisible) { + const scrollSpeed = state.user.settings?.scrollSpeed + thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(true)) + scrollToEntry(entryElement, scrollSpeed, () => thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(false))) + } } } } -}) +) const scrollToEntry = (entryElement: HTMLElement, scrollSpeed: number | undefined, onScrollEnded: () => void) => { scrollToWithCallback({ options: { @@ -216,59 +215,57 @@ const scrollToEntry = (entryElement: HTMLElement, scrollSpeed: number | undefine }) } -export const selectPreviousEntry = createAsyncThunk< - void, - { - expand: boolean - markAsRead: boolean - scrollToEntry: boolean - }, - { state: RootState } ->("entries/entry/selectPrevious", (arg, thunkApi) => { - const state = thunkApi.getState() - const { entries } = state.entries - const previousIndex = entries.findIndex(e => e.id === state.entries.selectedEntryId) - 1 - if (previousIndex >= 0) { - thunkApi.dispatch( - selectEntry({ - entry: entries[previousIndex], - expand: arg.expand, - markAsRead: arg.markAsRead, - scrollToEntry: arg.scrollToEntry, - }) - ) +export const selectPreviousEntry = createAppAsyncThunk( + "entries/entry/selectPrevious", + ( + arg: { + expand: boolean + markAsRead: boolean + scrollToEntry: boolean + }, + thunkApi + ) => { + const state = thunkApi.getState() + const { entries } = state.entries + const previousIndex = entries.findIndex(e => e.id === state.entries.selectedEntryId) - 1 + if (previousIndex >= 0) { + thunkApi.dispatch( + selectEntry({ + entry: entries[previousIndex], + expand: arg.expand, + markAsRead: arg.markAsRead, + scrollToEntry: arg.scrollToEntry, + }) + ) + } } -}) -export const selectNextEntry = createAsyncThunk< - void, - { - expand: boolean - markAsRead: boolean - scrollToEntry: boolean - }, - { state: RootState } ->("entries/entry/selectNext", (arg, thunkApi) => { - const state = thunkApi.getState() - const { entries } = state.entries - const nextIndex = entries.findIndex(e => e.id === state.entries.selectedEntryId) + 1 - if (nextIndex < entries.length) { - thunkApi.dispatch( - selectEntry({ - entry: entries[nextIndex], - expand: arg.expand, - markAsRead: arg.markAsRead, - scrollToEntry: arg.scrollToEntry, - }) - ) +) +export const selectNextEntry = createAppAsyncThunk( + "entries/entry/selectNext", + ( + arg: { + expand: boolean + markAsRead: boolean + scrollToEntry: boolean + }, + thunkApi + ) => { + const state = thunkApi.getState() + const { entries } = state.entries + const nextIndex = entries.findIndex(e => e.id === state.entries.selectedEntryId) + 1 + if (nextIndex < entries.length) { + thunkApi.dispatch( + selectEntry({ + entry: entries[nextIndex], + expand: arg.expand, + markAsRead: arg.markAsRead, + scrollToEntry: arg.scrollToEntry, + }) + ) + } } -}) -export const tagEntry = createAsyncThunk< - void, - TagRequest, - { - state: RootState - } ->("entries/entry/tag", async (arg, thunkApi) => { +) +export const tagEntry = createAppAsyncThunk("entries/entry/tag", async (arg: TagRequest, thunkApi) => { await client.entry.tag(arg) thunkApi.dispatch(reloadTags()) }) diff --git a/commafeed-client/src/app/slices/redirect.ts b/commafeed-client/src/app/slices/redirect.ts index 30fbbe39..b9dce6d2 100644 --- a/commafeed-client/src/app/slices/redirect.ts +++ b/commafeed-client/src/app/slices/redirect.ts @@ -1,6 +1,6 @@ -import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit" +import { createSlice, PayloadAction } from "@reduxjs/toolkit" import { Constants } from "app/constants" -import { RootState } from "app/store" +import { createAppAsyncThunk } from "app/store" interface RedirectState { to?: string @@ -8,52 +8,46 @@ interface RedirectState { const initialState: RedirectState = {} -export const redirectToLogin = createAsyncThunk("redirect/login", (_, thunkApi) => thunkApi.dispatch(redirectTo("/login"))) -export const redirectToRegistration = createAsyncThunk("redirect/register", (_, thunkApi) => thunkApi.dispatch(redirectTo("/register"))) -export const redirectToPasswordRecovery = createAsyncThunk("redirect/passwordRecovery", (_, thunkApi) => +export const redirectToLogin = createAppAsyncThunk("redirect/login", (_, thunkApi) => thunkApi.dispatch(redirectTo("/login"))) +export const redirectToRegistration = createAppAsyncThunk("redirect/register", (_, thunkApi) => thunkApi.dispatch(redirectTo("/register"))) +export const redirectToPasswordRecovery = createAppAsyncThunk("redirect/passwordRecovery", (_, thunkApi) => 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< - void, - void, - { - state: RootState - } ->("redirect/selectedSource", (_, thunkApi) => { +export const redirectToSelectedSource = createAppAsyncThunk("redirect/selectedSource", (_, thunkApi) => { const { source } = thunkApi.getState().entries 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}`)) ) -export const redirectToRootCategory = createAsyncThunk("redirect/category/root", (_, thunkApi) => +export const redirectToRootCategory = createAppAsyncThunk("redirect/category/root", (_, thunkApi) => 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`)) ) -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}`)) ) -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`)) ) -export const redirectToTag = createAsyncThunk("redirect/tag", (id: string, thunkApi) => thunkApi.dispatch(redirectTo(`/app/tag/${id}`))) -export const redirectToTagDetails = createAsyncThunk("redirect/tag/details", (id: string, thunkApi) => +export const redirectToTag = createAppAsyncThunk("redirect/tag", (id: string, thunkApi) => thunkApi.dispatch(redirectTo(`/app/tag/${id}`))) +export const redirectToTagDetails = createAppAsyncThunk("redirect/tag/details", (id: string, thunkApi) => thunkApi.dispatch(redirectTo(`/app/tag/${id}/details`)) ) -export const redirectToAdd = createAsyncThunk("redirect/add", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/add"))) -export const redirectToSettings = createAsyncThunk("redirect/settings", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/settings"))) -export const redirectToAdminUsers = createAsyncThunk("redirect/admin/users", (_, thunkApi) => +export const redirectToAdd = createAppAsyncThunk("redirect/add", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/add"))) +export const redirectToSettings = createAppAsyncThunk("redirect/settings", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/settings"))) +export const redirectToAdminUsers = createAppAsyncThunk("redirect/admin/users", (_, thunkApi) => 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")) ) -export const redirectToDonate = createAsyncThunk("redirect/donate", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/donate"))) -export const redirectToAbout = createAsyncThunk("redirect/about", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/about"))) +export const redirectToDonate = createAppAsyncThunk("redirect/donate", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/donate"))) +export const redirectToAbout = createAppAsyncThunk("redirect/about", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/about"))) export const redirectSlice = createSlice({ name: "redirect", diff --git a/commafeed-client/src/app/slices/server.ts b/commafeed-client/src/app/slices/server.ts index 07d5bbcd..be03fdc1 100644 --- a/commafeed-client/src/app/slices/server.ts +++ b/commafeed-client/src/app/slices/server.ts @@ -1,5 +1,6 @@ -import { PayloadAction, createAsyncThunk, createSlice } from "@reduxjs/toolkit" +import { PayloadAction, createSlice } from "@reduxjs/toolkit" import { client } from "app/client" +import { createAppAsyncThunk } from "app/store" import { ServerInfo } from "app/types" interface ServerState { @@ -11,7 +12,7 @@ const initialState: ServerState = { 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({ name: "server", initialState, diff --git a/commafeed-client/src/app/slices/tree.ts b/commafeed-client/src/app/slices/tree.ts index 69e992cf..9a7385b6 100644 --- a/commafeed-client/src/app/slices/tree.ts +++ b/commafeed-client/src/app/slices/tree.ts @@ -1,5 +1,6 @@ -import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit" +import { createSlice, PayloadAction } from "@reduxjs/toolkit" import { client } from "app/client" +import { createAppAsyncThunk } from "app/store" import { Category, CollapseRequest } from "app/types" import { visitCategoryTree } from "app/utils" // eslint-disable-next-line import/no-cycle @@ -19,8 +20,8 @@ const initialState: TreeState = { sidebarVisible: true, } -export const reloadTree = createAsyncThunk("tree/reload", () => client.category.getRoot().then(r => r.data)) -export const collapseTreeCategory = createAsyncThunk("tree/category/collapse", async (req: CollapseRequest) => +export const reloadTree = createAppAsyncThunk("tree/reload", () => client.category.getRoot().then(r => r.data)) +export const collapseTreeCategory = createAppAsyncThunk("tree/category/collapse", async (req: CollapseRequest) => client.category.collapse(req) ) diff --git a/commafeed-client/src/app/slices/user.ts b/commafeed-client/src/app/slices/user.ts index b495135b..2c671280 100644 --- a/commafeed-client/src/app/slices/user.ts +++ b/commafeed-client/src/app/slices/user.ts @@ -1,8 +1,8 @@ import { t } from "@lingui/macro" import { showNotification } from "@mantine/notifications" -import { createAsyncThunk, createSlice, isAnyOf } from "@reduxjs/toolkit" +import { createSlice, isAnyOf } from "@reduxjs/toolkit" import { client } from "app/client" -import { RootState } from "app/store" +import { createAppAsyncThunk } from "app/store" import { ReadingMode, ReadingOrder, Settings, SharingSettings, UserModel } from "app/types" // eslint-disable-next-line import/no-cycle import { reloadEntries } from "./entries" @@ -15,121 +15,79 @@ interface UserState { const initialState: UserState = {} -export const reloadSettings = createAsyncThunk("settings/reload", () => client.user.getSettings().then(r => r.data)) -export const reloadProfile = createAsyncThunk("profile/reload", () => client.user.getProfile().then(r => r.data)) -export const reloadTags = createAsyncThunk("entries/tags", () => client.entry.getTags().then(r => r.data)) -export const changeReadingMode = createAsyncThunk( - "settings/readingMode", - (readingMode, thunkApi) => { - const { settings } = thunkApi.getState().user - if (!settings) return - client.user.saveSettings({ ...settings, readingMode }) - thunkApi.dispatch(reloadEntries()) - } -) -export const changeReadingOrder = createAsyncThunk( - "settings/readingOrder", - (readingOrder, thunkApi) => { - const { settings } = thunkApi.getState().user - if (!settings) return - client.user.saveSettings({ ...settings, readingOrder }) - thunkApi.dispatch(reloadEntries()) - } -) -export const changeLanguage = createAsyncThunk< - void, - string, - { - state: RootState - } ->("settings/language", (language, thunkApi) => { +export const reloadSettings = createAppAsyncThunk("settings/reload", () => client.user.getSettings().then(r => r.data)) +export const reloadProfile = createAppAsyncThunk("profile/reload", () => client.user.getProfile().then(r => r.data)) +export const reloadTags = createAppAsyncThunk("entries/tags", () => client.entry.getTags().then(r => r.data)) +export const changeReadingMode = createAppAsyncThunk("settings/readingMode", (readingMode: ReadingMode, thunkApi) => { + const { settings } = thunkApi.getState().user + if (!settings) return + 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 + client.user.saveSettings({ ...settings, readingOrder }) + thunkApi.dispatch(reloadEntries()) +}) +export const changeLanguage = createAppAsyncThunk("settings/language", (language: string, thunkApi) => { const { settings } = thunkApi.getState().user if (!settings) return client.user.saveSettings({ ...settings, language }) }) -export const changeScrollSpeed = createAsyncThunk< - void, - boolean, - { - state: RootState - } ->("settings/scrollSpeed", (speed, thunkApi) => { +export const changeScrollSpeed = createAppAsyncThunk("settings/scrollSpeed", (speed: boolean, thunkApi) => { const { settings } = thunkApi.getState().user if (!settings) return client.user.saveSettings({ ...settings, scrollSpeed: speed ? 400 : 0 }) }) -export const changeShowRead = createAsyncThunk< - void, - boolean, - { - state: RootState - } ->("settings/showRead", (showRead, thunkApi) => { +export const changeShowRead = createAppAsyncThunk("settings/showRead", (showRead: boolean, thunkApi) => { const { settings } = thunkApi.getState().user if (!settings) return client.user.saveSettings({ ...settings, showRead }) }) -export const changeScrollMarks = createAsyncThunk< - void, - boolean, - { - state: RootState - } ->("settings/scrollMarks", (scrollMarks, thunkApi) => { +export const changeScrollMarks = createAppAsyncThunk("settings/scrollMarks", (scrollMarks: boolean, thunkApi) => { const { settings } = thunkApi.getState().user if (!settings) return client.user.saveSettings({ ...settings, scrollMarks }) }) -export const changeAlwaysScrollToEntry = createAsyncThunk< - void, - boolean, - { - state: RootState - } ->("settings/alwaysScrollToEntry", (alwaysScrollToEntry, thunkApi) => { +export const changeAlwaysScrollToEntry = createAppAsyncThunk("settings/alwaysScrollToEntry", (alwaysScrollToEntry: boolean, thunkApi) => { const { settings } = thunkApi.getState().user if (!settings) return client.user.saveSettings({ ...settings, alwaysScrollToEntry }) }) -export const changeMarkAllAsReadConfirmation = createAsyncThunk< - void, - boolean, - { - state: RootState +export const changeMarkAllAsReadConfirmation = createAppAsyncThunk( + "settings/markAllAsReadConfirmation", + (markAllAsReadConfirmation: boolean, thunkApi) => { + const { settings } = thunkApi.getState().user + if (!settings) return + client.user.saveSettings({ ...settings, markAllAsReadConfirmation }) } ->("settings/markAllAsReadConfirmation", (markAllAsReadConfirmation, thunkApi) => { - const { settings } = thunkApi.getState().user - if (!settings) return - client.user.saveSettings({ ...settings, markAllAsReadConfirmation }) -}) -export const changeCustomContextMenu = createAsyncThunk< - void, - boolean, - { - state: RootState - } ->("settings/customContextMenu", (customContextMenu, thunkApi) => { +) +export const changeCustomContextMenu = createAppAsyncThunk("settings/customContextMenu", (customContextMenu: boolean, thunkApi) => { const { settings } = thunkApi.getState().user if (!settings) return client.user.saveSettings({ ...settings, customContextMenu }) }) -export const changeSharingSetting = createAsyncThunk< - void, - { site: keyof SharingSettings; value: boolean }, - { - state: RootState - } ->("settings/sharingSetting", (sharingSetting, thunkApi) => { - const { settings } = thunkApi.getState().user - if (!settings) return - client.user.saveSettings({ - ...settings, - sharingSettings: { - ...settings.sharingSettings, - [sharingSetting.site]: sharingSetting.value, +export const changeSharingSetting = createAppAsyncThunk( + "settings/sharingSetting", + ( + sharingSetting: { + site: keyof SharingSettings + value: boolean }, - }) -}) + thunkApi + ) => { + const { settings } = thunkApi.getState().user + if (!settings) return + client.user.saveSettings({ + ...settings, + sharingSettings: { + ...settings.sharingSettings, + [sharingSetting.site]: sharingSetting.value, + }, + }) + } +) export const userSlice = createSlice({ name: "user", diff --git a/commafeed-client/src/app/store.ts b/commafeed-client/src/app/store.ts index b05becce..1118fb7f 100644 --- a/commafeed-client/src/app/store.ts +++ b/commafeed-client/src/app/store.ts @@ -1,4 +1,4 @@ -import { configureStore } from "@reduxjs/toolkit" +import { configureStore, createAsyncThunk } from "@reduxjs/toolkit" import { setupListeners } from "@reduxjs/toolkit/query" import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux" import entriesReducer from "./slices/entries" @@ -24,3 +24,7 @@ export type AppDispatch = typeof store.dispatch export const useAppDispatch: () => AppDispatch = useDispatch export const useAppSelector: TypedUseSelectorHook = useSelector +export const createAppAsyncThunk = createAsyncThunk.withTypes<{ + state: RootState + dispatch: AppDispatch +}>()