From 0ea0db48db0a0a5d7da8fd1544474272881ab998 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 28 Dec 2023 22:11:03 +0100 Subject: [PATCH] split thunks from slices to avoid circular dependencies --- commafeed-client/src/App.tsx | 4 +- .../src/app/{thunk.ts => async-thunk.ts} | 0 .../app/{slices => entries}/entries.test.ts | 2 +- commafeed-client/src/app/entries/slice.ts | 134 ++++++++++++++ .../{slices/entries.ts => entries/thunks.ts} | 145 +-------------- .../app/{slices => redirect}/redirect.test.ts | 2 +- commafeed-client/src/app/redirect/slice.ts | 19 ++ .../redirect.ts => redirect/thunks.ts} | 23 +-- .../app/{slices/server.ts => server/slice.ts} | 5 +- commafeed-client/src/app/server/thunks.ts | 4 + commafeed-client/src/app/slices/user.ts | 167 ------------------ commafeed-client/src/app/store.ts | 20 +-- .../src/app/{slices/tree.ts => tree/slice.ts} | 17 +- commafeed-client/src/app/tree/thunks.ts | 9 + commafeed-client/src/app/user/slice.ts | 102 +++++++++++ commafeed-client/src/app/user/thunks.ts | 78 ++++++++ .../src/components/content/Content.tsx | 1 - .../src/components/content/FeedEntries.tsx | 8 +- .../content/FeedEntryContextMenu.tsx | 4 +- .../components/content/FeedEntryFooter.tsx | 19 +- .../components/content/add/AddCategory.tsx | 4 +- .../src/components/content/add/ImportOpml.tsx | 4 +- .../src/components/content/add/Subscribe.tsx | 11 +- .../src/components/header/Header.tsx | 4 +- .../components/header/MarkAllAsReadButton.tsx | 2 +- .../src/components/header/ProfileMenu.tsx | 2 +- .../settings/CustomCodeSettings.tsx | 2 +- .../components/settings/DisplaySettings.tsx | 15 +- .../components/settings/ProfileSettings.tsx | 4 +- .../src/components/sidebar/Tree.tsx | 18 +- .../src/components/sidebar/TreeSearch.tsx | 2 +- commafeed-client/src/hooks/useWebSocket.ts | 4 +- commafeed-client/src/pages/WelcomePage.tsx | 2 +- commafeed-client/src/pages/app/AboutPage.tsx | 2 +- .../src/pages/app/CategoryDetailsPage.tsx | 4 +- .../src/pages/app/FeedDetailsPage.tsx | 4 +- .../src/pages/app/FeedEntriesPage.tsx | 13 +- commafeed-client/src/pages/app/Layout.tsx | 7 +- .../src/pages/app/TagDetailsPage.tsx | 2 +- commafeed-client/src/pages/auth/LoginPage.tsx | 2 +- .../src/pages/auth/RegistrationPage.tsx | 2 +- 41 files changed, 460 insertions(+), 413 deletions(-) rename commafeed-client/src/app/{thunk.ts => async-thunk.ts} (100%) rename commafeed-client/src/app/{slices => entries}/entries.test.ts (96%) create mode 100644 commafeed-client/src/app/entries/slice.ts rename commafeed-client/src/app/{slices/entries.ts => entries/thunks.ts} (60%) rename commafeed-client/src/app/{slices => redirect}/redirect.test.ts (81%) create mode 100644 commafeed-client/src/app/redirect/slice.ts rename commafeed-client/src/app/{slices/redirect.ts => redirect/thunks.ts} (82%) rename commafeed-client/src/app/{slices/server.ts => server/slice.ts} (72%) create mode 100644 commafeed-client/src/app/server/thunks.ts delete mode 100644 commafeed-client/src/app/slices/user.ts rename commafeed-client/src/app/{slices/tree.ts => tree/slice.ts} (74%) create mode 100644 commafeed-client/src/app/tree/thunks.ts create mode 100644 commafeed-client/src/app/user/slice.ts create mode 100644 commafeed-client/src/app/user/thunks.ts diff --git a/commafeed-client/src/App.tsx b/commafeed-client/src/App.tsx index 54c2541a..026a1f7a 100644 --- a/commafeed-client/src/App.tsx +++ b/commafeed-client/src/App.tsx @@ -5,8 +5,8 @@ import { useColorScheme } from "@mantine/hooks" import { ModalsProvider } from "@mantine/modals" import { Notifications } from "@mantine/notifications" import { Constants } from "app/constants" -import { redirectTo } from "app/slices/redirect" -import { reloadServerInfos } from "app/slices/server" +import { redirectTo } from "app/redirect/slice" +import { reloadServerInfos } from "app/server/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { categoryUnreadCount } from "app/utils" import { ErrorBoundary } from "components/ErrorBoundary" diff --git a/commafeed-client/src/app/thunk.ts b/commafeed-client/src/app/async-thunk.ts similarity index 100% rename from commafeed-client/src/app/thunk.ts rename to commafeed-client/src/app/async-thunk.ts diff --git a/commafeed-client/src/app/slices/entries.test.ts b/commafeed-client/src/app/entries/entries.test.ts similarity index 96% rename from commafeed-client/src/app/slices/entries.test.ts rename to commafeed-client/src/app/entries/entries.test.ts index 3e34e22e..7b95c4f9 100644 --- a/commafeed-client/src/app/slices/entries.test.ts +++ b/commafeed-client/src/app/entries/entries.test.ts @@ -1,12 +1,12 @@ /* eslint-disable import/first */ import { configureStore } from "@reduxjs/toolkit" import { type client } from "app/client" +import { loadEntries, loadMoreEntries, markAllEntries, markEntry } from "app/entries/thunks" import { reducers } from "app/store" import { type Entries, type Entry } from "app/types" import { type AxiosResponse } from "axios" import { beforeEach, describe, expect, it, vi } from "vitest" import { mockReset } from "vitest-mock-extended" -import { loadEntries, loadMoreEntries, markAllEntries, markEntry } from "./entries" const mockClient = await vi.hoisted(async () => { const mockModule = await import("vitest-mock-extended") diff --git a/commafeed-client/src/app/entries/slice.ts b/commafeed-client/src/app/entries/slice.ts new file mode 100644 index 00000000..2381c632 --- /dev/null +++ b/commafeed-client/src/app/entries/slice.ts @@ -0,0 +1,134 @@ +import { createSlice, type PayloadAction } from "@reduxjs/toolkit" +import { Constants } from "app/constants" +import { loadEntries, loadMoreEntries, markAllEntries, markEntry, markMultipleEntries, starEntry, tagEntry } from "app/entries/thunks" +import { type Entry } from "app/types" + +export type EntrySourceType = "category" | "feed" | "tag" + +export interface EntrySource { + type: EntrySourceType + id: string +} + +export type ExpendableEntry = Entry & { expanded?: boolean } + +interface EntriesState { + /** selected source */ + source: EntrySource + sourceLabel: string + sourceWebsiteUrl: string + entries: ExpendableEntry[] + /** stores when the first batch of entries were retrieved + * + * this is used when marking all entries of a feed/category to only mark entries up to that timestamp as newer entries were potentially never shown + */ + timestamp?: number + selectedEntryId?: string + hasMore: boolean + loading: boolean + search?: string + scrollingToEntry: boolean +} + +const initialState: EntriesState = { + source: { + type: "category", + id: Constants.categories.all.id, + }, + sourceLabel: "", + sourceWebsiteUrl: "", + entries: [], + hasMore: true, + loading: false, + scrollingToEntry: false, +} + +export const entriesSlice = createSlice({ + name: "entries", + initialState, + reducers: { + setSelectedEntry: (state, action: PayloadAction) => { + state.selectedEntryId = action.payload.id + }, + setEntryExpanded: (state, action: PayloadAction<{ entry: Entry; expanded: boolean }>) => { + state.entries + .filter(e => e.id === action.payload.entry.id) + .forEach(e => { + e.expanded = action.payload.expanded + }) + }, + setScrollingToEntry: (state, action: PayloadAction) => { + state.scrollingToEntry = action.payload + }, + setSearch: (state, action: PayloadAction) => { + state.search = action.payload + }, + }, + extraReducers: builder => { + builder.addCase(markEntry.pending, (state, action) => { + state.entries + .filter(e => e.id === action.meta.arg.entry.id) + .forEach(e => { + e.read = action.meta.arg.read + }) + }) + builder.addCase(markMultipleEntries.pending, (state, action) => { + state.entries + .filter(e => action.meta.arg.entries.some(e2 => e2.id === e.id)) + .forEach(e => { + e.read = action.meta.arg.read + }) + }) + builder.addCase(markAllEntries.pending, (state, action) => { + state.entries + .filter(e => (action.meta.arg.req.olderThan ? e.date < action.meta.arg.req.olderThan : true)) + .forEach(e => { + e.read = true + }) + }) + builder.addCase(starEntry.pending, (state, action) => { + state.entries + .filter(e => action.meta.arg.entry.id === e.id && action.meta.arg.entry.feedId === e.feedId) + .forEach(e => { + e.starred = action.meta.arg.starred + }) + }) + builder.addCase(loadEntries.pending, (state, action) => { + state.source = action.meta.arg.source + state.entries = [] + state.timestamp = undefined + state.sourceLabel = "" + state.sourceWebsiteUrl = "" + state.hasMore = true + state.selectedEntryId = undefined + state.loading = true + }) + builder.addCase(loadMoreEntries.pending, state => { + state.loading = true + }) + builder.addCase(loadEntries.fulfilled, (state, action) => { + state.entries = action.payload.entries + state.timestamp = action.payload.timestamp + state.sourceLabel = action.payload.name + state.sourceWebsiteUrl = action.payload.feedLink + state.hasMore = action.payload.hasMore + state.loading = false + }) + builder.addCase(loadMoreEntries.fulfilled, (state, action) => { + // remove already existing entries + const entriesToAdd = action.payload.entries.filter(e => !state.entries.some(e2 => e.id === e2.id)) + state.entries = [...state.entries, ...entriesToAdd] + state.hasMore = action.payload.hasMore + state.loading = false + }) + builder.addCase(tagEntry.pending, (state, action) => { + state.entries + .filter(e => +e.id === action.meta.arg.entryId) + .forEach(e => { + e.tags = action.meta.arg.tags + }) + }) + }, +}) + +export const { setSearch } = entriesSlice.actions diff --git a/commafeed-client/src/app/slices/entries.ts b/commafeed-client/src/app/entries/thunks.ts similarity index 60% rename from commafeed-client/src/app/slices/entries.ts rename to commafeed-client/src/app/entries/thunks.ts index 5ce49b9b..d9e3a38c 100644 --- a/commafeed-client/src/app/slices/entries.ts +++ b/commafeed-client/src/app/entries/thunks.ts @@ -1,55 +1,13 @@ -import { createSlice, type PayloadAction } from "@reduxjs/toolkit" +import { createAppAsyncThunk } from "app/async-thunk" import { client } from "app/client" import { Constants } from "app/constants" -import { type RootState } from "app/store" -import { createAppAsyncThunk } from "app/thunk" -import { type Entry, type MarkRequest, type TagRequest } from "app/types" +import { entriesSlice, type EntrySource, type EntrySourceType, setSearch } from "app/entries/slice" +import type { RootState } from "app/store" +import { reloadTree } from "app/tree/thunks" +import type { Entry, MarkRequest, TagRequest } from "app/types" +import { reloadTags } from "app/user/thunks" import { scrollToWithCallback } from "app/utils" import { flushSync } from "react-dom" -// eslint-disable-next-line import/no-cycle -import { reloadTree } from "./tree" -// eslint-disable-next-line import/no-cycle -import { reloadTags } from "./user" - -export type EntrySourceType = "category" | "feed" | "tag" - -export interface EntrySource { - type: EntrySourceType - id: string -} - -export type ExpendableEntry = Entry & { expanded?: boolean } - -interface EntriesState { - /** selected source */ - source: EntrySource - sourceLabel: string - sourceWebsiteUrl: string - entries: ExpendableEntry[] - /** stores when the first batch of entries were retrieved - * - * this is used when marking all entries of a feed/category to only mark entries up to that timestamp as newer entries were potentially never shown - */ - timestamp?: number - selectedEntryId?: string - hasMore: boolean - loading: boolean - search?: string - scrollingToEntry: boolean -} - -const initialState: EntriesState = { - source: { - type: "category", - id: Constants.categories.all.id, - }, - sourceLabel: "", - sourceWebsiteUrl: "", - entries: [], - hasMore: true, - loading: false, - scrollingToEntry: false, -} const getEndpoint = (sourceType: EntrySourceType) => sourceType === "category" || sourceType === "tag" ? client.category.getEntries : client.feed.getEntries @@ -280,94 +238,3 @@ export const tagEntry = createAppAsyncThunk("entries/entry/tag", async (arg: Tag await client.entry.tag(arg) thunkApi.dispatch(reloadTags()) }) - -export const entriesSlice = createSlice({ - name: "entries", - initialState, - reducers: { - setSelectedEntry: (state, action: PayloadAction) => { - state.selectedEntryId = action.payload.id - }, - setEntryExpanded: (state, action: PayloadAction<{ entry: Entry; expanded: boolean }>) => { - state.entries - .filter(e => e.id === action.payload.entry.id) - .forEach(e => { - e.expanded = action.payload.expanded - }) - }, - setScrollingToEntry: (state, action: PayloadAction) => { - state.scrollingToEntry = action.payload - }, - setSearch: (state, action: PayloadAction) => { - state.search = action.payload - }, - }, - extraReducers: builder => { - builder.addCase(markEntry.pending, (state, action) => { - state.entries - .filter(e => e.id === action.meta.arg.entry.id) - .forEach(e => { - e.read = action.meta.arg.read - }) - }) - builder.addCase(markMultipleEntries.pending, (state, action) => { - state.entries - .filter(e => action.meta.arg.entries.some(e2 => e2.id === e.id)) - .forEach(e => { - e.read = action.meta.arg.read - }) - }) - builder.addCase(markAllEntries.pending, (state, action) => { - state.entries - .filter(e => (action.meta.arg.req.olderThan ? e.date < action.meta.arg.req.olderThan : true)) - .forEach(e => { - e.read = true - }) - }) - builder.addCase(starEntry.pending, (state, action) => { - state.entries - .filter(e => action.meta.arg.entry.id === e.id && action.meta.arg.entry.feedId === e.feedId) - .forEach(e => { - e.starred = action.meta.arg.starred - }) - }) - builder.addCase(loadEntries.pending, (state, action) => { - state.source = action.meta.arg.source - state.entries = [] - state.timestamp = undefined - state.sourceLabel = "" - state.sourceWebsiteUrl = "" - state.hasMore = true - state.selectedEntryId = undefined - state.loading = true - }) - builder.addCase(loadMoreEntries.pending, state => { - state.loading = true - }) - builder.addCase(loadEntries.fulfilled, (state, action) => { - state.entries = action.payload.entries - state.timestamp = action.payload.timestamp - state.sourceLabel = action.payload.name - state.sourceWebsiteUrl = action.payload.feedLink - state.hasMore = action.payload.hasMore - state.loading = false - }) - builder.addCase(loadMoreEntries.fulfilled, (state, action) => { - // remove already existing entries - const entriesToAdd = action.payload.entries.filter(e => !state.entries.some(e2 => e.id === e2.id)) - state.entries = [...state.entries, ...entriesToAdd] - state.hasMore = action.payload.hasMore - state.loading = false - }) - builder.addCase(tagEntry.pending, (state, action) => { - state.entries - .filter(e => +e.id === action.meta.arg.entryId) - .forEach(e => { - e.tags = action.meta.arg.tags - }) - }) - }, -}) - -export const { setSearch } = entriesSlice.actions -export default entriesSlice.reducer diff --git a/commafeed-client/src/app/slices/redirect.test.ts b/commafeed-client/src/app/redirect/redirect.test.ts similarity index 81% rename from commafeed-client/src/app/slices/redirect.test.ts rename to commafeed-client/src/app/redirect/redirect.test.ts index 0250de18..430ea80e 100644 --- a/commafeed-client/src/app/slices/redirect.test.ts +++ b/commafeed-client/src/app/redirect/redirect.test.ts @@ -1,6 +1,6 @@ +import { redirectToCategory } from "app/redirect/thunks" import { store } from "app/store" import { describe, expect, it } from "vitest" -import { redirectToCategory } from "./redirect" describe("redirects", () => { it("redirects to category", async () => { diff --git a/commafeed-client/src/app/redirect/slice.ts b/commafeed-client/src/app/redirect/slice.ts new file mode 100644 index 00000000..ae2cf6cb --- /dev/null +++ b/commafeed-client/src/app/redirect/slice.ts @@ -0,0 +1,19 @@ +import { createSlice, type PayloadAction } from "@reduxjs/toolkit" + +interface RedirectState { + to?: string +} + +const initialState: RedirectState = {} + +export const redirectSlice = createSlice({ + name: "redirect", + initialState, + reducers: { + redirectTo: (state, action: PayloadAction) => { + state.to = action.payload + }, + }, +}) + +export const { redirectTo } = redirectSlice.actions diff --git a/commafeed-client/src/app/slices/redirect.ts b/commafeed-client/src/app/redirect/thunks.ts similarity index 82% rename from commafeed-client/src/app/slices/redirect.ts rename to commafeed-client/src/app/redirect/thunks.ts index f2b5a1a8..4e44f688 100644 --- a/commafeed-client/src/app/slices/redirect.ts +++ b/commafeed-client/src/app/redirect/thunks.ts @@ -1,12 +1,6 @@ -import { createSlice, type PayloadAction } from "@reduxjs/toolkit" +import { createAppAsyncThunk } from "app/async-thunk" import { Constants } from "app/constants" -import { createAppAsyncThunk } from "app/thunk" - -interface RedirectState { - to?: string -} - -const initialState: RedirectState = {} +import { redirectTo } from "app/redirect/slice" export const redirectToLogin = createAppAsyncThunk("redirect/login", (_, thunkApi) => thunkApi.dispatch(redirectTo("/login"))) export const redirectToRegistration = createAppAsyncThunk("redirect/register", (_, thunkApi) => thunkApi.dispatch(redirectTo("/register"))) @@ -49,16 +43,3 @@ export const redirectToMetrics = createAppAsyncThunk("redirect/admin/metrics", ( ) 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", - initialState, - reducers: { - redirectTo: (state, action: PayloadAction) => { - state.to = action.payload - }, - }, -}) - -export const { redirectTo } = redirectSlice.actions -export default redirectSlice.reducer diff --git a/commafeed-client/src/app/slices/server.ts b/commafeed-client/src/app/server/slice.ts similarity index 72% rename from commafeed-client/src/app/slices/server.ts rename to commafeed-client/src/app/server/slice.ts index fb38ccf0..93ef61fb 100644 --- a/commafeed-client/src/app/slices/server.ts +++ b/commafeed-client/src/app/server/slice.ts @@ -1,6 +1,5 @@ import { createSlice, type PayloadAction } from "@reduxjs/toolkit" -import { client } from "app/client" -import { createAppAsyncThunk } from "app/thunk" +import { reloadServerInfos } from "app/server/thunks" import { type ServerInfo } from "app/types" interface ServerState { @@ -12,7 +11,6 @@ const initialState: ServerState = { webSocketConnected: false, } -export const reloadServerInfos = createAppAsyncThunk("server/infos", async () => await client.server.getServerInfos().then(r => r.data)) export const serverSlice = createSlice({ name: "server", initialState, @@ -29,4 +27,3 @@ export const serverSlice = createSlice({ }) export const { setWebSocketConnected } = serverSlice.actions -export default serverSlice.reducer diff --git a/commafeed-client/src/app/server/thunks.ts b/commafeed-client/src/app/server/thunks.ts new file mode 100644 index 00000000..6d4c8200 --- /dev/null +++ b/commafeed-client/src/app/server/thunks.ts @@ -0,0 +1,4 @@ +import { createAppAsyncThunk } from "app/async-thunk" +import { client } from "app/client" + +export const reloadServerInfos = createAppAsyncThunk("server/infos", async () => await client.server.getServerInfos().then(r => r.data)) diff --git a/commafeed-client/src/app/slices/user.ts b/commafeed-client/src/app/slices/user.ts deleted file mode 100644 index 7b21bb13..00000000 --- a/commafeed-client/src/app/slices/user.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { t } from "@lingui/macro" -import { showNotification } from "@mantine/notifications" -import { createSlice, isAnyOf } from "@reduxjs/toolkit" -import { client } from "app/client" -import { createAppAsyncThunk } from "app/thunk" -import { type ReadingMode, type ReadingOrder, type Settings, type SharingSettings, type UserModel } from "app/types" -// eslint-disable-next-line import/no-cycle -import { reloadEntries } from "./entries" - -interface UserState { - settings?: Settings - profile?: UserModel - tags?: string[] -} - -const initialState: UserState = {} - -export const reloadSettings = createAppAsyncThunk("settings/reload", async () => await client.user.getSettings().then(r => r.data)) -export const reloadProfile = createAppAsyncThunk("profile/reload", async () => await client.user.getProfile().then(r => r.data)) -export const reloadTags = createAppAsyncThunk("entries/tags", async () => await 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 = 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 = createAppAsyncThunk("settings/showRead", (showRead: boolean, thunkApi) => { - const { settings } = thunkApi.getState().user - if (!settings) return - client.user.saveSettings({ ...settings, showRead }) -}) -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 = createAppAsyncThunk("settings/alwaysScrollToEntry", (alwaysScrollToEntry: boolean, thunkApi) => { - const { settings } = thunkApi.getState().user - if (!settings) return - client.user.saveSettings({ ...settings, alwaysScrollToEntry }) -}) -export const changeMarkAllAsReadConfirmation = createAppAsyncThunk( - "settings/markAllAsReadConfirmation", - (markAllAsReadConfirmation: boolean, thunkApi) => { - const { settings } = thunkApi.getState().user - if (!settings) return - client.user.saveSettings({ ...settings, markAllAsReadConfirmation }) - } -) -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 = 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", - initialState, - reducers: {}, - extraReducers: builder => { - builder.addCase(reloadSettings.fulfilled, (state, action) => { - state.settings = action.payload - }) - builder.addCase(reloadProfile.fulfilled, (state, action) => { - state.profile = action.payload - }) - builder.addCase(reloadTags.fulfilled, (state, action) => { - state.tags = action.payload - }) - builder.addCase(changeReadingMode.pending, (state, action) => { - if (!state.settings) return - state.settings.readingMode = action.meta.arg - }) - builder.addCase(changeReadingOrder.pending, (state, action) => { - if (!state.settings) return - state.settings.readingOrder = action.meta.arg - }) - builder.addCase(changeLanguage.pending, (state, action) => { - if (!state.settings) return - state.settings.language = action.meta.arg - }) - builder.addCase(changeScrollSpeed.pending, (state, action) => { - if (!state.settings) return - state.settings.scrollSpeed = action.meta.arg ? 400 : 0 - }) - builder.addCase(changeShowRead.pending, (state, action) => { - if (!state.settings) return - state.settings.showRead = action.meta.arg - }) - builder.addCase(changeScrollMarks.pending, (state, action) => { - if (!state.settings) return - state.settings.scrollMarks = action.meta.arg - }) - builder.addCase(changeAlwaysScrollToEntry.pending, (state, action) => { - if (!state.settings) return - state.settings.alwaysScrollToEntry = action.meta.arg - }) - builder.addCase(changeMarkAllAsReadConfirmation.pending, (state, action) => { - if (!state.settings) return - state.settings.markAllAsReadConfirmation = action.meta.arg - }) - builder.addCase(changeCustomContextMenu.pending, (state, action) => { - if (!state.settings) return - state.settings.customContextMenu = action.meta.arg - }) - builder.addCase(changeSharingSetting.pending, (state, action) => { - if (!state.settings) return - state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value - }) - builder.addMatcher( - isAnyOf( - changeLanguage.fulfilled, - changeScrollSpeed.fulfilled, - changeShowRead.fulfilled, - changeScrollMarks.fulfilled, - changeAlwaysScrollToEntry.fulfilled, - changeMarkAllAsReadConfirmation.fulfilled, - changeCustomContextMenu.fulfilled, - changeSharingSetting.fulfilled - ), - () => { - showNotification({ - message: t`Settings saved.`, - color: "green", - }) - } - ) - }, -}) - -export default userSlice.reducer diff --git a/commafeed-client/src/app/store.ts b/commafeed-client/src/app/store.ts index 4b900c21..5619a8d0 100644 --- a/commafeed-client/src/app/store.ts +++ b/commafeed-client/src/app/store.ts @@ -1,18 +1,18 @@ import { configureStore } from "@reduxjs/toolkit" import { setupListeners } from "@reduxjs/toolkit/query" +import { entriesSlice } from "app/entries/slice" +import { redirectSlice } from "app/redirect/slice" +import { serverSlice } from "app/server/slice" +import { treeSlice } from "app/tree/slice" +import { userSlice } from "app/user/slice" import { type TypedUseSelectorHook, useDispatch, useSelector } from "react-redux" -import entriesReducer from "./slices/entries" -import redirectReducer from "./slices/redirect" -import serverReducer from "./slices/server" -import treeReducer from "./slices/tree" -import userReducer from "./slices/user" export const reducers = { - entries: entriesReducer, - redirect: redirectReducer, - tree: treeReducer, - server: serverReducer, - user: userReducer, + entries: entriesSlice.reducer, + redirect: redirectSlice.reducer, + tree: treeSlice.reducer, + server: serverSlice.reducer, + user: userSlice.reducer, } export const store = configureStore({ reducer: reducers }) diff --git a/commafeed-client/src/app/slices/tree.ts b/commafeed-client/src/app/tree/slice.ts similarity index 74% rename from commafeed-client/src/app/slices/tree.ts rename to commafeed-client/src/app/tree/slice.ts index 6777d360..f535de00 100644 --- a/commafeed-client/src/app/slices/tree.ts +++ b/commafeed-client/src/app/tree/slice.ts @@ -1,11 +1,9 @@ import { createSlice, type PayloadAction } from "@reduxjs/toolkit" -import { client } from "app/client" -import { createAppAsyncThunk } from "app/thunk" -import { type Category, type CollapseRequest } from "app/types" +import { markEntry } from "app/entries/thunks" +import { redirectTo } from "app/redirect/slice" +import { collapseTreeCategory, reloadTree } from "app/tree/thunks" +import { type Category } from "app/types" import { visitCategoryTree } from "app/utils" -// eslint-disable-next-line import/no-cycle -import { markEntry } from "./entries" -import { redirectTo } from "./redirect" interface TreeState { rootCategory?: Category @@ -20,12 +18,6 @@ const initialState: TreeState = { sidebarVisible: true, } -export const reloadTree = createAppAsyncThunk("tree/reload", async () => await client.category.getRoot().then(r => r.data)) -export const collapseTreeCategory = createAppAsyncThunk( - "tree/category/collapse", - async (req: CollapseRequest) => await client.category.collapse(req) -) - export const treeSlice = createSlice({ name: "tree", initialState, @@ -67,4 +59,3 @@ export const treeSlice = createSlice({ }) export const { setMobileMenuOpen, setSidebarWidth, toggleSidebar } = treeSlice.actions -export default treeSlice.reducer diff --git a/commafeed-client/src/app/tree/thunks.ts b/commafeed-client/src/app/tree/thunks.ts new file mode 100644 index 00000000..4d9f9f33 --- /dev/null +++ b/commafeed-client/src/app/tree/thunks.ts @@ -0,0 +1,9 @@ +import { createAppAsyncThunk } from "app/async-thunk" +import { client } from "app/client" +import type { CollapseRequest } from "app/types" + +export const reloadTree = createAppAsyncThunk("tree/reload", async () => await client.category.getRoot().then(r => r.data)) +export const collapseTreeCategory = createAppAsyncThunk( + "tree/category/collapse", + async (req: CollapseRequest) => await client.category.collapse(req) +) diff --git a/commafeed-client/src/app/user/slice.ts b/commafeed-client/src/app/user/slice.ts new file mode 100644 index 00000000..a30421c4 --- /dev/null +++ b/commafeed-client/src/app/user/slice.ts @@ -0,0 +1,102 @@ +import { t } from "@lingui/macro" +import { showNotification } from "@mantine/notifications" +import { createSlice, isAnyOf } from "@reduxjs/toolkit" +import { type Settings, type UserModel } from "app/types" +import { + changeAlwaysScrollToEntry, + changeCustomContextMenu, + changeLanguage, + changeMarkAllAsReadConfirmation, + changeReadingMode, + changeReadingOrder, + changeScrollMarks, + changeScrollSpeed, + changeSharingSetting, + changeShowRead, + reloadProfile, + reloadSettings, + reloadTags, +} from "./thunks" + +interface UserState { + settings?: Settings + profile?: UserModel + tags?: string[] +} + +const initialState: UserState = {} + +export const userSlice = createSlice({ + name: "user", + initialState, + reducers: {}, + extraReducers: builder => { + builder.addCase(reloadSettings.fulfilled, (state, action) => { + state.settings = action.payload + }) + builder.addCase(reloadProfile.fulfilled, (state, action) => { + state.profile = action.payload + }) + builder.addCase(reloadTags.fulfilled, (state, action) => { + state.tags = action.payload + }) + builder.addCase(changeReadingMode.pending, (state, action) => { + if (!state.settings) return + state.settings.readingMode = action.meta.arg + }) + builder.addCase(changeReadingOrder.pending, (state, action) => { + if (!state.settings) return + state.settings.readingOrder = action.meta.arg + }) + builder.addCase(changeLanguage.pending, (state, action) => { + if (!state.settings) return + state.settings.language = action.meta.arg + }) + builder.addCase(changeScrollSpeed.pending, (state, action) => { + if (!state.settings) return + state.settings.scrollSpeed = action.meta.arg ? 400 : 0 + }) + builder.addCase(changeShowRead.pending, (state, action) => { + if (!state.settings) return + state.settings.showRead = action.meta.arg + }) + builder.addCase(changeScrollMarks.pending, (state, action) => { + if (!state.settings) return + state.settings.scrollMarks = action.meta.arg + }) + builder.addCase(changeAlwaysScrollToEntry.pending, (state, action) => { + if (!state.settings) return + state.settings.alwaysScrollToEntry = action.meta.arg + }) + builder.addCase(changeMarkAllAsReadConfirmation.pending, (state, action) => { + if (!state.settings) return + state.settings.markAllAsReadConfirmation = action.meta.arg + }) + builder.addCase(changeCustomContextMenu.pending, (state, action) => { + if (!state.settings) return + state.settings.customContextMenu = action.meta.arg + }) + builder.addCase(changeSharingSetting.pending, (state, action) => { + if (!state.settings) return + state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value + }) + builder.addMatcher( + isAnyOf( + changeLanguage.fulfilled, + changeScrollSpeed.fulfilled, + changeShowRead.fulfilled, + changeScrollMarks.fulfilled, + changeAlwaysScrollToEntry.fulfilled, + changeMarkAllAsReadConfirmation.fulfilled, + changeCustomContextMenu.fulfilled, + changeSharingSetting.fulfilled + ), + () => { + showNotification({ + message: t`Settings saved.`, + color: "green", + }) + } + ) + }, +}) diff --git a/commafeed-client/src/app/user/thunks.ts b/commafeed-client/src/app/user/thunks.ts new file mode 100644 index 00000000..2d493c68 --- /dev/null +++ b/commafeed-client/src/app/user/thunks.ts @@ -0,0 +1,78 @@ +import { createAppAsyncThunk } from "app/async-thunk" +import { client } from "app/client" +import { reloadEntries } from "app/entries/thunks" +import type { ReadingMode, ReadingOrder, SharingSettings } from "app/types" + +export const reloadSettings = createAppAsyncThunk("settings/reload", async () => await client.user.getSettings().then(r => r.data)) +export const reloadProfile = createAppAsyncThunk("profile/reload", async () => await client.user.getProfile().then(r => r.data)) +export const reloadTags = createAppAsyncThunk("entries/tags", async () => await 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 = 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 = createAppAsyncThunk("settings/showRead", (showRead: boolean, thunkApi) => { + const { settings } = thunkApi.getState().user + if (!settings) return + client.user.saveSettings({ ...settings, showRead }) +}) +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 = createAppAsyncThunk("settings/alwaysScrollToEntry", (alwaysScrollToEntry: boolean, thunkApi) => { + const { settings } = thunkApi.getState().user + if (!settings) return + client.user.saveSettings({ ...settings, alwaysScrollToEntry }) +}) +export const changeMarkAllAsReadConfirmation = createAppAsyncThunk( + "settings/markAllAsReadConfirmation", + (markAllAsReadConfirmation: boolean, thunkApi) => { + const { settings } = thunkApi.getState().user + if (!settings) return + client.user.saveSettings({ ...settings, markAllAsReadConfirmation }) + } +) +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 = 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, + }, + }) + } +) diff --git a/commafeed-client/src/components/content/Content.tsx b/commafeed-client/src/components/content/Content.tsx index 44c578c6..fb3af164 100644 --- a/commafeed-client/src/components/content/Content.tsx +++ b/commafeed-client/src/components/content/Content.tsx @@ -77,7 +77,6 @@ class HighlightMatcher extends Matcher { return {children} } - // eslint-disable-next-line class-methods-use-this asTag(): string { return "span" } diff --git a/commafeed-client/src/components/content/FeedEntries.tsx b/commafeed-client/src/components/content/FeedEntries.tsx index 5858f746..be291edd 100644 --- a/commafeed-client/src/components/content/FeedEntries.tsx +++ b/commafeed-client/src/components/content/FeedEntries.tsx @@ -2,8 +2,8 @@ import { Trans } from "@lingui/macro" import { Box } from "@mantine/core" import { openModal } from "@mantine/modals" import { Constants } from "app/constants" +import { type ExpendableEntry } from "app/entries/slice" import { - type ExpendableEntry, loadMoreEntries, markAllEntries, markEntry, @@ -12,10 +12,10 @@ import { selectNextEntry, selectPreviousEntry, starEntry, -} from "app/slices/entries" -import { redirectToRootCategory } from "app/slices/redirect" -import { toggleSidebar } from "app/slices/tree" +} from "app/entries/thunks" +import { redirectToRootCategory } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" +import { toggleSidebar } from "app/tree/slice" import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp" import { Loader } from "components/Loader" import { useBrowserExtension } from "hooks/useBrowserExtension" diff --git a/commafeed-client/src/components/content/FeedEntryContextMenu.tsx b/commafeed-client/src/components/content/FeedEntryContextMenu.tsx index 2c73f957..1c82570c 100644 --- a/commafeed-client/src/components/content/FeedEntryContextMenu.tsx +++ b/commafeed-client/src/components/content/FeedEntryContextMenu.tsx @@ -1,8 +1,8 @@ import { Trans } from "@lingui/macro" import { createStyles, Group } from "@mantine/core" import { Constants } from "app/constants" -import { markEntriesUpToEntry, markEntry, starEntry } from "app/slices/entries" -import { redirectToFeed } from "app/slices/redirect" +import { markEntriesUpToEntry, markEntry, starEntry } from "app/entries/thunks" +import { redirectToFeed } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { type Entry } from "app/types" import { truncate } from "app/utils" diff --git a/commafeed-client/src/components/content/FeedEntryFooter.tsx b/commafeed-client/src/components/content/FeedEntryFooter.tsx index 522de2f3..4e5881b6 100644 --- a/commafeed-client/src/components/content/FeedEntryFooter.tsx +++ b/commafeed-client/src/components/content/FeedEntryFooter.tsx @@ -1,6 +1,6 @@ import { t, Trans } from "@lingui/macro" import { Group, Indicator, MultiSelect, Popover } from "@mantine/core" -import { markEntriesUpToEntry, markEntry, starEntry, tagEntry } from "app/slices/entries" +import { markEntriesUpToEntry, markEntry, starEntry, tagEntry } from "app/entries/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { type Entry } from "app/types" import { ActionButton } from "components/ActionButton" @@ -22,7 +22,13 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) { const showSharingButtons = sharingSettings && Object.values(sharingSettings).some(v => v) - const readStatusButtonClicked = async () => await dispatch(markEntry({ entry: props.entry, read: !props.entry.read })) + const readStatusButtonClicked = async () => + await dispatch( + markEntry({ + entry: props.entry, + read: !props.entry.read, + }) + ) const onTagsChange = async (values: string[]) => await dispatch( tagEntry({ @@ -44,7 +50,14 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) { : } label={props.entry.starred ? Unstar : Star} - onClick={async () => await dispatch(starEntry({ entry: props.entry, starred: !props.entry.starred }))} + onClick={async () => + await dispatch( + starEntry({ + entry: props.entry, + starred: !props.entry.starred, + }) + ) + } /> {showSharingButtons && ( diff --git a/commafeed-client/src/components/content/add/AddCategory.tsx b/commafeed-client/src/components/content/add/AddCategory.tsx index aa38453e..d1060578 100644 --- a/commafeed-client/src/components/content/add/AddCategory.tsx +++ b/commafeed-client/src/components/content/add/AddCategory.tsx @@ -2,9 +2,9 @@ import { t, Trans } from "@lingui/macro" import { Box, Button, Group, Stack, TextInput } from "@mantine/core" import { useForm } from "@mantine/form" import { client, errorToStrings } from "app/client" -import { redirectToSelectedSource } from "app/slices/redirect" -import { reloadTree } from "app/slices/tree" +import { redirectToSelectedSource } from "app/redirect/thunks" import { useAppDispatch } from "app/store" +import { reloadTree } from "app/tree/thunks" import { type AddCategoryRequest } from "app/types" import { Alert } from "components/Alert" import { useAsyncCallback } from "react-async-hook" diff --git a/commafeed-client/src/components/content/add/ImportOpml.tsx b/commafeed-client/src/components/content/add/ImportOpml.tsx index e59dde9d..64c6cee3 100644 --- a/commafeed-client/src/components/content/add/ImportOpml.tsx +++ b/commafeed-client/src/components/content/add/ImportOpml.tsx @@ -2,9 +2,9 @@ import { t, Trans } from "@lingui/macro" import { Box, Button, FileInput, Group, Stack } from "@mantine/core" import { useForm } from "@mantine/form" import { client, errorToStrings } from "app/client" -import { redirectToSelectedSource } from "app/slices/redirect" -import { reloadTree } from "app/slices/tree" +import { redirectToSelectedSource } from "app/redirect/thunks" import { useAppDispatch } from "app/store" +import { reloadTree } from "app/tree/thunks" import { Alert } from "components/Alert" import { useAsyncCallback } from "react-async-hook" import { TbFileImport } from "react-icons/tb" diff --git a/commafeed-client/src/components/content/add/Subscribe.tsx b/commafeed-client/src/components/content/add/Subscribe.tsx index 99a36ab9..9e495b3a 100644 --- a/commafeed-client/src/components/content/add/Subscribe.tsx +++ b/commafeed-client/src/components/content/add/Subscribe.tsx @@ -3,9 +3,9 @@ import { Box, Button, Group, Stack, Stepper, TextInput } from "@mantine/core" import { useForm } from "@mantine/form" import { client, errorToStrings } from "app/client" import { Constants } from "app/constants" -import { redirectToFeed, redirectToSelectedSource } from "app/slices/redirect" -import { reloadTree } from "app/slices/tree" +import { redirectToFeed, redirectToSelectedSource } from "app/redirect/thunks" import { useAppDispatch } from "app/store" +import { reloadTree } from "app/tree/thunks" import { type FeedInfoRequest, type SubscribeRequest } from "app/types" import { Alert } from "components/Alert" import { useState } from "react" @@ -46,8 +46,11 @@ export function Subscribe() { }) const previousStep = () => { - if (activeStep === 0) dispatch(redirectToSelectedSource()) - else setActiveStep(activeStep - 1) + if (activeStep === 0) { + dispatch(redirectToSelectedSource()) + } else { + setActiveStep(activeStep - 1) + } } const nextStep = (e: React.FormEvent) => { if (activeStep === 0) { diff --git a/commafeed-client/src/components/header/Header.tsx b/commafeed-client/src/components/header/Header.tsx index 81e32c61..e5df2798 100644 --- a/commafeed-client/src/components/header/Header.tsx +++ b/commafeed-client/src/components/header/Header.tsx @@ -1,9 +1,9 @@ import { t, Trans } from "@lingui/macro" import { ActionIcon, Box, Center, Divider, Group, Indicator, Popover, TextInput } from "@mantine/core" import { useForm } from "@mantine/form" -import { reloadEntries, search, selectNextEntry, selectPreviousEntry } from "app/slices/entries" -import { changeReadingMode, changeReadingOrder } from "app/slices/user" +import { reloadEntries, search, selectNextEntry, selectPreviousEntry } from "app/entries/thunks" import { useAppDispatch, useAppSelector } from "app/store" +import { changeReadingMode, changeReadingOrder } from "app/user/thunks" import { ActionButton } from "components/ActionButton" import { Loader } from "components/Loader" import { useActionButton } from "hooks/useActionButton" diff --git a/commafeed-client/src/components/header/MarkAllAsReadButton.tsx b/commafeed-client/src/components/header/MarkAllAsReadButton.tsx index ceafa807..d0495e08 100644 --- a/commafeed-client/src/components/header/MarkAllAsReadButton.tsx +++ b/commafeed-client/src/components/header/MarkAllAsReadButton.tsx @@ -1,7 +1,7 @@ import { Trans } from "@lingui/macro" import { Button, Code, Group, Modal, Slider, Stack, Text } from "@mantine/core" -import { markAllEntries } from "app/slices/entries" +import { markAllEntries } from "app/entries/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { ActionButton } from "components/ActionButton" import { useState } from "react" diff --git a/commafeed-client/src/components/header/ProfileMenu.tsx b/commafeed-client/src/components/header/ProfileMenu.tsx index e4a64484..d45ca762 100644 --- a/commafeed-client/src/components/header/ProfileMenu.tsx +++ b/commafeed-client/src/components/header/ProfileMenu.tsx @@ -2,7 +2,7 @@ import { Trans } from "@lingui/macro" import { Box, Divider, Group, Menu, SegmentedControl, type SegmentedControlItem, useMantineColorScheme } from "@mantine/core" import { showNotification } from "@mantine/notifications" import { client } from "app/client" -import { redirectToAbout, redirectToAdminUsers, redirectToDonate, redirectToMetrics, redirectToSettings } from "app/slices/redirect" +import { redirectToAbout, redirectToAdminUsers, redirectToDonate, redirectToMetrics, redirectToSettings } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { type ViewMode } from "app/types" import { useViewMode } from "hooks/useViewMode" diff --git a/commafeed-client/src/components/settings/CustomCodeSettings.tsx b/commafeed-client/src/components/settings/CustomCodeSettings.tsx index dbad865f..3410f78a 100644 --- a/commafeed-client/src/components/settings/CustomCodeSettings.tsx +++ b/commafeed-client/src/components/settings/CustomCodeSettings.tsx @@ -2,7 +2,7 @@ import { Trans } from "@lingui/macro" import { Box, Button, Group, Stack } from "@mantine/core" import { useForm } from "@mantine/form" import { client, errorToStrings } from "app/client" -import { redirectToSelectedSource } from "app/slices/redirect" +import { redirectToSelectedSource } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { Alert } from "components/Alert" import { CodeEditor } from "components/code/CodeEditor" diff --git a/commafeed-client/src/components/settings/DisplaySettings.tsx b/commafeed-client/src/components/settings/DisplaySettings.tsx index 16ca3048..d8f38b9a 100644 --- a/commafeed-client/src/components/settings/DisplaySettings.tsx +++ b/commafeed-client/src/components/settings/DisplaySettings.tsx @@ -1,6 +1,8 @@ import { Trans } from "@lingui/macro" import { Divider, Select, SimpleGrid, Stack, Switch } from "@mantine/core" import { Constants } from "app/constants" +import { useAppDispatch, useAppSelector } from "app/store" +import { type SharingSettings } from "app/types" import { changeAlwaysScrollToEntry, changeCustomContextMenu, @@ -10,9 +12,7 @@ import { changeScrollSpeed, changeSharingSetting, changeShowRead, -} from "app/slices/user" -import { useAppDispatch, useAppSelector } from "app/store" -import { type SharingSettings } from "app/types" +} from "app/user/thunks" import { locales } from "i18n" export function DisplaySettings() { @@ -82,7 +82,14 @@ export function DisplaySettings() { key={site} label={Constants.sharing[site].label} checked={sharingSettings && sharingSettings[site]} - onChange={async e => await dispatch(changeSharingSetting({ site, value: e.currentTarget.checked }))} + onChange={async e => + await dispatch( + changeSharingSetting({ + site, + value: e.currentTarget.checked, + }) + ) + } /> ))} diff --git a/commafeed-client/src/components/settings/ProfileSettings.tsx b/commafeed-client/src/components/settings/ProfileSettings.tsx index 597c8b86..bd26a927 100644 --- a/commafeed-client/src/components/settings/ProfileSettings.tsx +++ b/commafeed-client/src/components/settings/ProfileSettings.tsx @@ -3,10 +3,10 @@ import { Anchor, Box, Button, Checkbox, Divider, Group, Input, PasswordInput, St import { useForm } from "@mantine/form" import { openConfirmModal } from "@mantine/modals" import { client, errorToStrings } from "app/client" -import { redirectToLogin, redirectToSelectedSource } from "app/slices/redirect" -import { reloadProfile } from "app/slices/user" +import { redirectToLogin, redirectToSelectedSource } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { type ProfileModificationRequest } from "app/types" +import { reloadProfile } from "app/user/thunks" import { Alert } from "components/Alert" import { useEffect } from "react" import { useAsyncCallback } from "react-async-hook" diff --git a/commafeed-client/src/components/sidebar/Tree.tsx b/commafeed-client/src/components/sidebar/Tree.tsx index e7af36cf..2548b6db 100644 --- a/commafeed-client/src/components/sidebar/Tree.tsx +++ b/commafeed-client/src/components/sidebar/Tree.tsx @@ -8,9 +8,9 @@ import { redirectToFeedDetails, redirectToTag, redirectToTagDetails, -} from "app/slices/redirect" -import { collapseTreeCategory } from "app/slices/tree" +} from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" +import { collapseTreeCategory } from "app/tree/thunks" import { type Category, type Subscription } from "app/types" import { categoryUnreadCount, flattenCategoryTree } from "app/utils" import { Loader } from "components/Loader" @@ -36,8 +36,11 @@ export function Tree() { const dispatch = useAppDispatch() const feedClicked = (e: React.MouseEvent, id: string) => { - if (e.detail === 2) dispatch(redirectToFeedDetails(id)) - else dispatch(redirectToFeed(id)) + if (e.detail === 2) { + dispatch(redirectToFeedDetails(id)) + } else { + dispatch(redirectToFeed(id)) + } } const categoryClicked = (e: React.MouseEvent, id: string) => { if (e.detail === 2) { @@ -57,8 +60,11 @@ export function Tree() { ) } const tagClicked = (e: React.MouseEvent, id: string) => { - if (e.detail === 2) dispatch(redirectToTagDetails(id)) - else dispatch(redirectToTag(id)) + if (e.detail === 2) { + dispatch(redirectToTagDetails(id)) + } else { + dispatch(redirectToTag(id)) + } } const allCategoryNode = () => ( diff --git a/commafeed-client/src/components/sidebar/TreeSearch.tsx b/commafeed-client/src/components/sidebar/TreeSearch.tsx index 124b0876..3171e6cc 100644 --- a/commafeed-client/src/components/sidebar/TreeSearch.tsx +++ b/commafeed-client/src/components/sidebar/TreeSearch.tsx @@ -1,7 +1,7 @@ import { t, Trans } from "@lingui/macro" import { Box, Center, Kbd, TextInput } from "@mantine/core" import { openSpotlight, type SpotlightAction, SpotlightProvider } from "@mantine/spotlight" -import { redirectToFeed } from "app/slices/redirect" +import { redirectToFeed } from "app/redirect/thunks" import { useAppDispatch } from "app/store" import { type Subscription } from "app/types" import { FeedFavicon } from "components/content/FeedFavicon" diff --git a/commafeed-client/src/hooks/useWebSocket.ts b/commafeed-client/src/hooks/useWebSocket.ts index 1d449ecd..e4e5a7ea 100644 --- a/commafeed-client/src/hooks/useWebSocket.ts +++ b/commafeed-client/src/hooks/useWebSocket.ts @@ -1,6 +1,6 @@ -import { setWebSocketConnected } from "app/slices/server" -import { reloadTree } from "app/slices/tree" +import { setWebSocketConnected } from "app/server/slice" import { useAppDispatch, useAppSelector } from "app/store" +import { reloadTree } from "app/tree/thunks" import { useEffect } from "react" import WebsocketHeartbeatJs from "websocket-heartbeat-js" diff --git a/commafeed-client/src/pages/WelcomePage.tsx b/commafeed-client/src/pages/WelcomePage.tsx index bc0703c7..c6356db7 100644 --- a/commafeed-client/src/pages/WelcomePage.tsx +++ b/commafeed-client/src/pages/WelcomePage.tsx @@ -1,7 +1,7 @@ import { Trans } from "@lingui/macro" import { Anchor, Box, Center, Container, Divider, Group, Image, Title, useMantineColorScheme } from "@mantine/core" import { client } from "app/client" -import { redirectToApiDocumentation, redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/slices/redirect" +import { redirectToApiDocumentation, redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" import welcome_page_dark from "assets/welcome_page_dark.png" import welcome_page_light from "assets/welcome_page_light.png" diff --git a/commafeed-client/src/pages/app/AboutPage.tsx b/commafeed-client/src/pages/app/AboutPage.tsx index 1a2a29dd..dd719bdc 100644 --- a/commafeed-client/src/pages/app/AboutPage.tsx +++ b/commafeed-client/src/pages/app/AboutPage.tsx @@ -1,7 +1,7 @@ import { t, Trans } from "@lingui/macro" import { Anchor, Box, Container, createStyles, List, NativeSelect, SimpleGrid, Title } from "@mantine/core" import { Constants } from "app/constants" -import { redirectToApiDocumentation } from "app/slices/redirect" +import { redirectToApiDocumentation } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { CategorySelect } from "components/content/add/CategorySelect" import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp" diff --git a/commafeed-client/src/pages/app/CategoryDetailsPage.tsx b/commafeed-client/src/pages/app/CategoryDetailsPage.tsx index 23a5729c..ded4a61d 100644 --- a/commafeed-client/src/pages/app/CategoryDetailsPage.tsx +++ b/commafeed-client/src/pages/app/CategoryDetailsPage.tsx @@ -4,9 +4,9 @@ import { useForm } from "@mantine/form" import { openConfirmModal } from "@mantine/modals" import { client, errorToStrings } from "app/client" import { Constants } from "app/constants" -import { redirectToRootCategory, redirectToSelectedSource } from "app/slices/redirect" -import { reloadTree } from "app/slices/tree" +import { redirectToRootCategory, redirectToSelectedSource } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" +import { reloadTree } from "app/tree/thunks" import { type CategoryModificationRequest } from "app/types" import { flattenCategoryTree } from "app/utils" import { Alert } from "components/Alert" diff --git a/commafeed-client/src/pages/app/FeedDetailsPage.tsx b/commafeed-client/src/pages/app/FeedDetailsPage.tsx index de4742d4..c41ad78f 100644 --- a/commafeed-client/src/pages/app/FeedDetailsPage.tsx +++ b/commafeed-client/src/pages/app/FeedDetailsPage.tsx @@ -3,9 +3,9 @@ import { Anchor, Box, Button, Code, Container, Divider, Group, Input, NumberInpu import { useForm } from "@mantine/form" import { openConfirmModal } from "@mantine/modals" import { client, errorToStrings } from "app/client" -import { redirectToRootCategory, redirectToSelectedSource } from "app/slices/redirect" -import { reloadTree } from "app/slices/tree" +import { redirectToRootCategory, redirectToSelectedSource } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" +import { reloadTree } from "app/tree/thunks" import { type FeedModificationRequest } from "app/types" import { Alert } from "components/Alert" import { CategorySelect } from "components/content/add/CategorySelect" diff --git a/commafeed-client/src/pages/app/FeedEntriesPage.tsx b/commafeed-client/src/pages/app/FeedEntriesPage.tsx index 9f53f568..2c81795b 100644 --- a/commafeed-client/src/pages/app/FeedEntriesPage.tsx +++ b/commafeed-client/src/pages/app/FeedEntriesPage.tsx @@ -2,8 +2,9 @@ import { Trans } from "@lingui/macro" import { ActionIcon, Box, Center, createStyles, Divider, Group, Title, useMantineTheme } from "@mantine/core" import { useViewportSize } from "@mantine/hooks" import { Constants } from "app/constants" -import { type EntrySourceType, loadEntries } from "app/slices/entries" -import { redirectToCategoryDetails, redirectToFeedDetails, redirectToTagDetails } from "app/slices/redirect" +import { type EntrySourceType } from "app/entries/slice" +import { loadEntries } from "app/entries/thunks" +import { redirectToCategoryDetails, redirectToFeedDetails, redirectToTagDetails } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { flattenCategoryTree } from "app/utils" import { FeedEntries } from "components/content/FeedEntries" @@ -47,9 +48,11 @@ export function FeedEntriesPage(props: FeedEntriesPageProps) { const dispatch = useAppDispatch() const titleClicked = () => { - if (props.sourceType === "category") dispatch(redirectToCategoryDetails(id)) - else if (props.sourceType === "feed") dispatch(redirectToFeedDetails(id)) - else if (props.sourceType === "tag") dispatch(redirectToTagDetails(id)) + if (props.sourceType === "category") { + dispatch(redirectToCategoryDetails(id)) + } else if (props.sourceType === "feed") { + dispatch(redirectToFeedDetails(id)) + } else if (props.sourceType === "tag") dispatch(redirectToTagDetails(id)) } useEffect(() => { diff --git a/commafeed-client/src/pages/app/Layout.tsx b/commafeed-client/src/pages/app/Layout.tsx index 57287000..3a6d57dc 100644 --- a/commafeed-client/src/pages/app/Layout.tsx +++ b/commafeed-client/src/pages/app/Layout.tsx @@ -14,10 +14,11 @@ import { useMantineTheme, } from "@mantine/core" import { Constants } from "app/constants" -import { redirectToAdd, redirectToRootCategory } from "app/slices/redirect" -import { reloadTree, setMobileMenuOpen, setSidebarWidth } from "app/slices/tree" -import { reloadProfile, reloadSettings, reloadTags } from "app/slices/user" +import { redirectToAdd, redirectToRootCategory } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" +import { setMobileMenuOpen, setSidebarWidth } from "app/tree/slice" +import { reloadTree } from "app/tree/thunks" +import { reloadProfile, reloadSettings, reloadTags } from "app/user/thunks" import { AnnouncementDialog } from "components/AnnouncementDialog" import { Loader } from "components/Loader" import { Logo } from "components/Logo" diff --git a/commafeed-client/src/pages/app/TagDetailsPage.tsx b/commafeed-client/src/pages/app/TagDetailsPage.tsx index f1972901..9ac6b0d6 100644 --- a/commafeed-client/src/pages/app/TagDetailsPage.tsx +++ b/commafeed-client/src/pages/app/TagDetailsPage.tsx @@ -2,7 +2,7 @@ import { Trans } from "@lingui/macro" import { Anchor, Box, Button, Container, Group, Input, Stack, Title } from "@mantine/core" import { Constants } from "app/constants" -import { redirectToSelectedSource } from "app/slices/redirect" +import { redirectToSelectedSource } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { useParams } from "react-router-dom" diff --git a/commafeed-client/src/pages/auth/LoginPage.tsx b/commafeed-client/src/pages/auth/LoginPage.tsx index 145f4071..feedc99a 100644 --- a/commafeed-client/src/pages/auth/LoginPage.tsx +++ b/commafeed-client/src/pages/auth/LoginPage.tsx @@ -2,7 +2,7 @@ import { t, Trans } from "@lingui/macro" import { Anchor, Box, Button, Center, Container, Group, Paper, PasswordInput, Stack, TextInput, Title } from "@mantine/core" import { useForm } from "@mantine/form" import { client, errorToStrings } from "app/client" -import { redirectToRootCategory } from "app/slices/redirect" +import { redirectToRootCategory } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { type LoginRequest } from "app/types" import { Alert } from "components/Alert" diff --git a/commafeed-client/src/pages/auth/RegistrationPage.tsx b/commafeed-client/src/pages/auth/RegistrationPage.tsx index 5ae1055d..2d68e4df 100644 --- a/commafeed-client/src/pages/auth/RegistrationPage.tsx +++ b/commafeed-client/src/pages/auth/RegistrationPage.tsx @@ -2,7 +2,7 @@ import { t, Trans } from "@lingui/macro" import { Anchor, Box, Button, Center, Container, Group, Paper, PasswordInput, Stack, TextInput, Title } from "@mantine/core" import { useForm } from "@mantine/form" import { client, errorToStrings } from "app/client" -import { redirectToRootCategory } from "app/slices/redirect" +import { redirectToRootCategory } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { type RegistrationRequest } from "app/types" import { Alert } from "components/Alert"