diff --git a/commafeed-client/package-lock.json b/commafeed-client/package-lock.json index d0810c66..c780153b 100644 --- a/commafeed-client/package-lock.json +++ b/commafeed-client/package-lock.json @@ -44,7 +44,6 @@ "throttle-debounce": "^5.0.2", "tinycon": "^0.6.8", "tss-react": "^4.9.13", - "use-local-storage": "^3.0.0", "vite-plugin-biome": "^1.0.12", "websocket-heartbeat-js": "^1.1.3" }, @@ -8380,15 +8379,6 @@ } } }, - "node_modules/use-local-storage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/use-local-storage/-/use-local-storage-3.0.0.tgz", - "integrity": "sha512-wlPNnBCG3ULIJMr5A+dvWqLiPWCfsN1Kwijq+sAhT5yV4ex0u6XmZuNwP+RerIOfzBuz1pwSZuzhZMiluGQHfQ==", - "license": "MIT", - "peerDependencies": { - "react": ">=16.8.1" - } - }, "node_modules/use-sidecar": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", diff --git a/commafeed-client/package.json b/commafeed-client/package.json index ef453901..964eba21 100644 --- a/commafeed-client/package.json +++ b/commafeed-client/package.json @@ -51,7 +51,6 @@ "throttle-debounce": "^5.0.2", "tinycon": "^0.6.8", "tss-react": "^4.9.13", - "use-local-storage": "^3.0.0", "vite-plugin-biome": "^1.0.12", "websocket-heartbeat-js": "^1.1.3" }, diff --git a/commafeed-client/src/app/store.ts b/commafeed-client/src/app/store.ts index e73a31d5..cca234d7 100644 --- a/commafeed-client/src/app/store.ts +++ b/commafeed-client/src/app/store.ts @@ -3,8 +3,7 @@ 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 type { ViewMode } from "app/types" -import { userSlice } from "app/user/slice" +import { initialLocalSettings, userSlice } from "app/user/slice" import { type TypedUseSelectorHook, useDispatch, useSelector } from "react-redux" export const reducers = { @@ -19,15 +18,13 @@ export const store = configureStore({ reducer: reducers, preloadedState: { user: { - localSettings: { - viewMode: localStorage.getItem("view-mode") as ViewMode, - }, + localSettings: JSON.parse(localStorage.getItem("commafeed-local-settings") ?? JSON.stringify(initialLocalSettings)), }, }, }) store.subscribe(() => { - const state = store.getState() - localStorage.setItem("view-mode", state.user.localSettings.viewMode) + const localSettings = store.getState().user.localSettings + localStorage.setItem("commafeed-local-settings", JSON.stringify(localSettings)) }) export type RootState = ReturnType diff --git a/commafeed-client/src/app/types.ts b/commafeed-client/src/app/types.ts index b44a10b4..86c44789 100644 --- a/commafeed-client/src/app/types.ts +++ b/commafeed-client/src/app/types.ts @@ -254,6 +254,12 @@ export interface Settings { sharingSettings: SharingSettings } +export interface LocalSettings { + viewMode: ViewMode + sidebarWidth: number + announcementHash: string +} + export interface StarRequest { id: string feedId: number diff --git a/commafeed-client/src/app/user/slice.ts b/commafeed-client/src/app/user/slice.ts index 56fe5025..207060d6 100644 --- a/commafeed-client/src/app/user/slice.ts +++ b/commafeed-client/src/app/user/slice.ts @@ -1,7 +1,7 @@ import { t } from "@lingui/macro" import { showNotification } from "@mantine/notifications" import { type PayloadAction, createSlice, isAnyOf } from "@reduxjs/toolkit" -import type { Settings, UserModel, ViewMode } from "app/types" +import type { LocalSettings, Settings, UserModel, ViewMode } from "app/types" import { changeCustomContextMenu, changeEntriesToKeepOnTopWhenScrolling, @@ -26,17 +26,19 @@ import { interface UserState { settings?: Settings - localSettings: { - viewMode: ViewMode - } + localSettings: LocalSettings profile?: UserModel tags?: string[] } +export const initialLocalSettings: LocalSettings = { + viewMode: "detailed", + sidebarWidth: 360, + announcementHash: "no-hash", +} + const initialState: UserState = { - localSettings: { - viewMode: "detailed", - }, + localSettings: initialLocalSettings, } export const userSlice = createSlice({ @@ -46,6 +48,12 @@ export const userSlice = createSlice({ setViewMode: (state, action: PayloadAction) => { state.localSettings.viewMode = action.payload }, + setSidebarWidth: (state, action: PayloadAction) => { + state.localSettings.sidebarWidth = action.payload + }, + setAnnouncementHash: (state, action: PayloadAction) => { + state.localSettings.announcementHash = action.payload + }, }, extraReducers: builder => { builder.addCase(reloadSettings.fulfilled, (state, action) => { @@ -148,4 +156,4 @@ export const userSlice = createSlice({ }, }) -export const { setViewMode } = userSlice.actions +export const { setViewMode, setSidebarWidth, setAnnouncementHash } = userSlice.actions diff --git a/commafeed-client/src/components/AnnouncementDialog.tsx b/commafeed-client/src/components/AnnouncementDialog.tsx index a83a5473..bb7ba486 100644 --- a/commafeed-client/src/components/AnnouncementDialog.tsx +++ b/commafeed-client/src/components/AnnouncementDialog.tsx @@ -1,9 +1,9 @@ import { Trans } from "@lingui/macro" import { Box, Dialog, Text } from "@mantine/core" -import { useAppSelector } from "app/store" +import { useAppDispatch, useAppSelector } from "app/store" +import { setAnnouncementHash } from "app/user/slice" import { Content } from "components/content/Content" import { useAsync } from "react-async-hook" -import useLocalStorage from "use-local-storage" const sha256Hex = async (input: string | undefined) => { const data = new TextEncoder().encode(input) @@ -15,10 +15,11 @@ const sha256Hex = async (input: string | undefined) => { export function AnnouncementDialog() { const announcement = useAppSelector(state => state.server.serverInfos?.announcement) const announcementHash = useAsync(sha256Hex, [announcement]).result - const [localStorageHash, setLocalStorageHash] = useLocalStorage("announcement-hash", "no-hash") + const existingAnnouncementHash = useAppSelector(state => state.user.localSettings.announcementHash) + const dispatch = useAppDispatch() - const opened = !!announcementHash && announcementHash !== localStorageHash - const onClosed = () => setLocalStorageHash(announcementHash) + const opened = !!announcementHash && announcementHash !== existingAnnouncementHash + const onClosed = () => announcementHash && dispatch(setAnnouncementHash(announcementHash)) if (!announcement) return null return ( diff --git a/commafeed-client/src/pages/app/Layout.tsx b/commafeed-client/src/pages/app/Layout.tsx index f47dab22..046cb61e 100644 --- a/commafeed-client/src/pages/app/Layout.tsx +++ b/commafeed-client/src/pages/app/Layout.tsx @@ -5,6 +5,7 @@ import { redirectToAdd, redirectToRootCategory } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { setMobileMenuOpen } from "app/tree/slice" import { reloadTree } from "app/tree/thunks" +import { setSidebarWidth } from "app/user/slice" import { reloadProfile, reloadSettings, reloadTags } from "app/user/thunks" import { ActionButton } from "components/ActionButton" import { AnnouncementDialog } from "components/AnnouncementDialog" @@ -23,7 +24,6 @@ import { TbMenu2, TbPlus, TbX } from "react-icons/tb" import { Outlet } from "react-router-dom" import { useSwipeable } from "react-swipeable" import { tss } from "tss" -import useLocalStorage from "use-local-storage" interface LayoutProps { sidebar: ReactNode @@ -64,21 +64,23 @@ export default function Layout(props: LayoutProps) { const theme = useMantineTheme() const mobile = useMobile() const { isBrowserExtensionPopup } = useBrowserExtension() - const [sidebarWidth, setSidebarWidth] = useLocalStorage("sidebar-width", 350) + + const { loading } = useAppLoading() + const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen) + const webSocketConnected = useAppSelector(state => state.server.webSocketConnected) + const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval) + const mobileFooter = useAppSelector(state => state.user.settings?.mobileFooter) + const sidebarWidth = useAppSelector(state => state.user.localSettings.sidebarWidth) + const headerInFooter = mobile && !isBrowserExtensionPopup && mobileFooter + const dispatch = useAppDispatch() + useWebSocket() + const sidebarPadding = theme.spacing.xs const { classes } = useStyles({ sidebarWidth, sidebarPadding, sidebarRightBorderWidth: "1px", }) - const { loading } = useAppLoading() - const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen) - const webSocketConnected = useAppSelector(state => state.server.webSocketConnected) - const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval) - const mobileFooter = useAppSelector(state => state.user.settings?.mobileFooter) - const headerInFooter = mobile && !isBrowserExtensionPopup && mobileFooter - const dispatch = useAppDispatch() - useWebSocket() useEffect(() => { // load initial data @@ -192,7 +194,10 @@ export default function Layout(props: LayoutProps) { right: 1000, }} grid={[30, 30]} - onDrag={(_e, data) => setSidebarWidth(data.x)} + onDrag={(_e, data) => { + dispatch(setSidebarWidth(data.x)) + return + }} >