diff --git a/src/renderer/src/atoms/index.ts b/src/renderer/src/atoms/index.ts index ca6d44f23..a44692743 100644 --- a/src/renderer/src/atoms/index.ts +++ b/src/renderer/src/atoms/index.ts @@ -1,2 +1,5 @@ export * from "./dom" export * from "./route" +export * from "./sidebar" +export * from "./ui" +export * from "./user" diff --git a/src/renderer/src/atoms/ui.ts b/src/renderer/src/atoms/ui.ts new file mode 100644 index 000000000..7e06a60bb --- /dev/null +++ b/src/renderer/src/atoms/ui.ts @@ -0,0 +1,75 @@ +import { useRefValue } from "@renderer/hooks" +import { createAtomHooks } from "@renderer/lib/jotai" +import { getStorageNS } from "@renderer/lib/ns" +import { useAtomValue } from "jotai" +import { atomWithStorage, selectAtom } from "jotai/utils" +import { useMemo } from "react" + +const createDefaultSettings = () => ({ + // Sidebar + entryColWidth: 340, + opaqueSidebar: false, + sidebarShowUnreadCount: true, + + // Global UI + uiTextSize: 16, + // System + showDockBadge: true, + // Misc + modalOverlay: true, + modalDraggable: true, + modalOpaque: true, + reduceMotion: false, + + // Content + readerFontFamily: "SN Pro", + readerRenderInlineStyle: false, + codeHighlightTheme: "github-dark", +}) +const atom = atomWithStorage(getStorageNS("ui"), createDefaultSettings()) +const [, , useUISettingValue, , getUISettings, setUISettings] = + createAtomHooks(atom) + +export const initializeDefaultUISettings = () => { + const currentSettings = getUISettings() + const defaultSettings = createDefaultSettings() + if (typeof currentSettings !== "object") setUISettings(defaultSettings) + const newSettings = { ...defaultSettings, ...currentSettings } + setUISettings(newSettings) +} + +export { getUISettings, useUISettingValue } +export const useUISettingKey = < + T extends keyof ReturnType, +>( + key: T, + ) => useAtomValue(useMemo(() => selectAtom(atom, (s) => s[key]), [key])) + +export const useUISettingSelector = < + T extends keyof ReturnType, + S extends ReturnType, + R = S[T], +>( + selector: (s: S) => R, + ): R => { + const stableSelector = useRefValue(selector) + + return useAtomValue( + // @ts-expect-error + useMemo(() => selectAtom(atom, stableSelector.current), [stableSelector]), + ) +} + +export const setUISetting = >( + key: K, + value: ReturnType[K], +) => { + setUISettings({ + ...getUISettings(), + [key]: value, + }) +} + +export const clearUISettings = () => { + setUISettings(createDefaultSettings()) +} diff --git a/src/renderer/src/components/common/Motion.tsx b/src/renderer/src/components/common/Motion.tsx new file mode 100644 index 000000000..8501a85c9 --- /dev/null +++ b/src/renderer/src/components/common/Motion.tsx @@ -0,0 +1,34 @@ +import { useReduceMotion } from "@renderer/hooks/biz/useReduceMotion" +import type { MotionProps } from "framer-motion" +import { m as M } from "framer-motion" +import { createElement, forwardRef } from "react" + +const cacheMap = new Map() +export const m: typeof M = new Proxy(M, { + get(target, p: string) { + const Component = target[p] + + if (cacheMap.has(p)) { + return cacheMap.get(p) + } + const MotionComponent = forwardRef((props: MotionProps, ref) => { + const shouldReduceMotion = useReduceMotion() + const nextProps = { ...props } + if (shouldReduceMotion) { + if (props.exit) { + delete nextProps.exit + } + + if (props.initial) { + nextProps.initial = true + } + } + + return createElement(Component, { ...nextProps, ref }) + }) + + cacheMap.set(p, MotionComponent) + + return MotionComponent + }, +}) diff --git a/src/renderer/src/components/ui/background/vibrancy.tsx b/src/renderer/src/components/ui/background/vibrancy.tsx index 0c7101fe9..e3b7de410 100644 --- a/src/renderer/src/components/ui/background/vibrancy.tsx +++ b/src/renderer/src/components/ui/background/vibrancy.tsx @@ -1,12 +1,12 @@ +import { useUISettingKey } from "@renderer/atoms" import { useDark } from "@renderer/hooks" import { cn } from "@renderer/lib/utils" -import { useUIStore } from "@renderer/store" import { useMediaQuery } from "usehooks-ts" export const Vibrancy: Component< React.DetailedHTMLProps, HTMLDivElement> > = ({ className, children, ...rest }) => { - const opaqueSidebar = useUIStore((s) => s.opaqueSidebar) + const opaqueSidebar = useUISettingKey("opaqueSidebar") const canVibrancy = window.electron && window.electron.process.platform === "darwin" && diff --git a/src/renderer/src/components/ui/code-highlighter/copy-button.tsx b/src/renderer/src/components/ui/code-highlighter/copy-button.tsx index e6b62ee8c..562993f09 100644 --- a/src/renderer/src/components/ui/code-highlighter/copy-button.tsx +++ b/src/renderer/src/components/ui/code-highlighter/copy-button.tsx @@ -1,6 +1,7 @@ +import { m } from "@renderer/components/common/Motion" import { cn } from "@renderer/lib/utils" import type { Variants } from "framer-motion" -import { AnimatePresence, m } from "framer-motion" +import { AnimatePresence } from "framer-motion" import { useCallback, useRef, useState } from "react" import { MotionButtonBase } from "../button" diff --git a/src/renderer/src/components/ui/code-highlighter/shiki/Shiki.tsx b/src/renderer/src/components/ui/code-highlighter/shiki/Shiki.tsx index f25d58d1e..533894330 100644 --- a/src/renderer/src/components/ui/code-highlighter/shiki/Shiki.tsx +++ b/src/renderer/src/components/ui/code-highlighter/shiki/Shiki.tsx @@ -1,6 +1,6 @@ /* eslint-disable @eslint-react/dom/no-dangerously-set-innerhtml */ +import { useUISettingSelector } from "@renderer/atoms" import { cn } from "@renderer/lib/utils" -import { useUIStore } from "@renderer/store" import type { FC } from "react" import { useLayoutEffect, useMemo, useRef, useState } from "react" import type { @@ -51,7 +51,7 @@ export const ShikiHighLighter: FC = (props) => { const [loaded, setLoaded] = useState(false) - const codeTheme = useUIStore((s) => overrideTheme || s.codeHighlightTheme) + const codeTheme = useUISettingSelector((s) => overrideTheme || s.codeHighlightTheme) useLayoutEffect(() => { let isMounted = true setLoaded(false) diff --git a/src/renderer/src/components/ui/image/preview-image.tsx b/src/renderer/src/components/ui/image/preview-image.tsx index 1933066e0..7aceb6f55 100644 --- a/src/renderer/src/components/ui/image/preview-image.tsx +++ b/src/renderer/src/components/ui/image/preview-image.tsx @@ -1,5 +1,5 @@ +import { m } from "@renderer/components/common/Motion" import { stopPropagation } from "@renderer/lib/dom" -import { m } from "framer-motion" import type { FC } from "react" import { useState } from "react" import { Mousewheel, Scrollbar, Virtual } from "swiper/modules" diff --git a/src/renderer/src/components/ui/modal/stacked/hooks.tsx b/src/renderer/src/components/ui/modal/stacked/hooks.tsx index 8af9a1290..c64956f40 100644 --- a/src/renderer/src/components/ui/modal/stacked/hooks.tsx +++ b/src/renderer/src/components/ui/modal/stacked/hooks.tsx @@ -1,5 +1,5 @@ +import { getUISettings } from "@renderer/atoms" import { jotaiStore } from "@renderer/lib/jotai" -import { useUIStore } from "@renderer/store" import { useCallback, useContext, useEffect, useId, useRef } from "react" import { useLocation } from "react-router-dom" @@ -31,9 +31,9 @@ export const useModalStack = (options?: ModalStackOptions) => { } else { // NOTE: The props of the Command Modal are immutable, so we'll just take the store value and inject it. // There is no need to inject `overlay` props, this is rendered responsively based on ui changes. - const uiState = useUIStore.getState() + const uiSettings = getUISettings() const modalConfig: Partial = { - draggable: uiState.modalDraggable, + draggable: uiSettings.modalDraggable, } jotaiStore.set(modalStackAtom, (p) => { const modalProps: ModalProps = { diff --git a/src/renderer/src/components/ui/modal/stacked/modal.tsx b/src/renderer/src/components/ui/modal/stacked/modal.tsx index 9452c7341..7fdba1d11 100644 --- a/src/renderer/src/components/ui/modal/stacked/modal.tsx +++ b/src/renderer/src/components/ui/modal/stacked/modal.tsx @@ -1,8 +1,9 @@ import * as Dialog from "@radix-ui/react-dialog" +import { useUISettingKey } from "@renderer/atoms" +import { m } from "@renderer/components/common/Motion" import { stopPropagation } from "@renderer/lib/dom" import { cn } from "@renderer/lib/utils" -import { useUIStore } from "@renderer/store" -import { m, useAnimationControls, useDragControls } from "framer-motion" +import { useAnimationControls, useDragControls } from "framer-motion" import { useSetAtom } from "jotai" import type { PointerEventHandler, SyntheticEvent } from "react" import { @@ -60,7 +61,8 @@ export const ModalInternal: Component<{ // opaque: state.modalOpaque, // })), // ) - const opaque = useUIStore((state) => state.modalOpaque) + // const opaque = useUIStore((state) => state.modalOpaque) + const opaque = useUISettingKey("modalOpaque") const { CustomModalComponent, diff --git a/src/renderer/src/components/ui/modal/stacked/overlay.tsx b/src/renderer/src/components/ui/modal/stacked/overlay.tsx index 577a10275..9b30778df 100644 --- a/src/renderer/src/components/ui/modal/stacked/overlay.tsx +++ b/src/renderer/src/components/ui/modal/stacked/overlay.tsx @@ -1,4 +1,4 @@ -import { m } from "framer-motion" +import { m } from "@renderer/components/common/Motion" import { RootPortal } from "../../portal" diff --git a/src/renderer/src/components/ui/modal/stacked/provider.tsx b/src/renderer/src/components/ui/modal/stacked/provider.tsx index dfd3b321b..1c78c6c38 100644 --- a/src/renderer/src/components/ui/modal/stacked/provider.tsx +++ b/src/renderer/src/components/ui/modal/stacked/provider.tsx @@ -1,10 +1,11 @@ -import { useUIStore } from "@renderer/store" +import { useUISettingKey } from "@renderer/atoms" import { AnimatePresence } from "framer-motion" import { useAtomValue } from "jotai" import type { FC, PropsWithChildren } from "react" import { modalStackAtom } from "./atom" import { MODAL_STACK_Z_INDEX } from "./constants" +import { useDismissAllWhenRouterChange } from "./hooks" // import { useDismissAllWhenRouterChange } from "./hooks" import { ModalInternal } from "./modal" import { ModalOverlay } from "./overlay" @@ -20,9 +21,8 @@ const ModalStack = () => { const stack = useAtomValue(modalStackAtom) // Vite HMR issue - // useDismissAllWhenRouterChange() - - const modalSettingOverlay = useUIStore((state) => state.modalOverlay) + useDismissAllWhenRouterChange() + const modalSettingOverlay = useUISettingKey("modalOverlay") const forceOverlay = stack.some((item) => item.overlay) diff --git a/src/renderer/src/database/hooks.ts b/src/renderer/src/database/hooks.ts index 8da6b9882..a3c3021dc 100644 --- a/src/renderer/src/database/hooks.ts +++ b/src/renderer/src/database/hooks.ts @@ -1,8 +1,8 @@ import { createAtomHooks, jotaiStore } from "@renderer/lib/jotai" -import { buildStorageNS } from "@renderer/lib/ns" +import { getStorageNS } from "@renderer/lib/ns" import { atomWithStorage } from "jotai/utils" -const SHOULD_USE_INDEXED_DB_KEY = buildStorageNS("shouldUseIndexedDB") +const SHOULD_USE_INDEXED_DB_KEY = getStorageNS("shouldUseIndexedDB") export const [ __shouldUseIndexedDBAtom, diff --git a/src/renderer/src/hooks/biz/useReduceMotion.ts b/src/renderer/src/hooks/biz/useReduceMotion.ts new file mode 100644 index 000000000..23871a5e3 --- /dev/null +++ b/src/renderer/src/hooks/biz/useReduceMotion.ts @@ -0,0 +1,8 @@ +import { useUISettingKey } from "@renderer/atoms/ui" +import { useReducedMotion } from "framer-motion" + +export const useReduceMotion = () => { + const appReduceMotion = useUISettingKey("reduceMotion") + const reduceMotion = useReducedMotion() + return appReduceMotion || reduceMotion +} diff --git a/src/renderer/src/lib/constants.tsx b/src/renderer/src/lib/constants.tsx index 486db7b9c..b78af5100 100644 --- a/src/renderer/src/lib/constants.tsx +++ b/src/renderer/src/lib/constants.tsx @@ -1,4 +1,4 @@ -import { buildStorageNS } from "./ns" +import { getStorageNS } from "./ns" export const levels = { view: "view", @@ -79,7 +79,7 @@ export const APP_NAME = "Follow" /// Feed export const FEED_COLLECTION_LIST = "collections" /// Local storage keys -export const QUERY_PERSIST_KEY = buildStorageNS("REACT_QUERY_OFFLINE_CACHE") +export const QUERY_PERSIST_KEY = getStorageNS("REACT_QUERY_OFFLINE_CACHE") /// Route Keys export const ROUTE_FEED_PENDING = "pending" diff --git a/src/renderer/src/lib/ns.ts b/src/renderer/src/lib/ns.ts index 016e6c1ec..b54a6116f 100644 --- a/src/renderer/src/lib/ns.ts +++ b/src/renderer/src/lib/ns.ts @@ -1,2 +1,6 @@ const ns = "follow" -export const buildStorageNS = (key: string) => `${ns}:${key}` +export const getStorageNS = (key: string) => `${ns}:${key}` +/** + * @deprecated Use `getStorageNS` instead. + */ +export const buildStorageKey = getStorageNS diff --git a/src/renderer/src/modules/entry-column/index.tsx b/src/renderer/src/modules/entry-column/index.tsx index 4a2911c4d..4f0b7fa8e 100644 --- a/src/renderer/src/modules/entry-column/index.tsx +++ b/src/renderer/src/modules/entry-column/index.tsx @@ -1,5 +1,6 @@ import { useMainContainerElement } from "@renderer/atoms" import { useUser } from "@renderer/atoms/user" +import { m } from "@renderer/components/common/Motion" import { ActionButton, StyledButton } from "@renderer/components/ui/button" import { Popover, @@ -16,7 +17,7 @@ import { } from "@renderer/hooks/biz/useRouteParams" import { apiClient } from "@renderer/lib/api-fetch" import { ROUTE_FEED_PENDING, views } from "@renderer/lib/constants" -import { buildStorageNS } from "@renderer/lib/ns" +import { getStorageNS } from "@renderer/lib/ns" import { shortcuts } from "@renderer/lib/shortcuts" import { cn, getEntriesParams, getOS, isBizId } from "@renderer/lib/utils" import { useEntries } from "@renderer/queries/entries" @@ -32,7 +33,6 @@ import { useEntryIdsByFeedIdOrView, } from "@renderer/store/entry/hooks" import type { HTMLMotionProps } from "framer-motion" -import { m } from "framer-motion" import { useAtom, useAtomValue } from "jotai" import { atomWithStorage } from "jotai/utils" import { debounce } from "lodash-es" @@ -56,7 +56,7 @@ import { LoadingCircle } from "../../components/ui/loading" import { EntryItem } from "./item" const unreadOnlyAtom = atomWithStorage( - buildStorageNS("entry-unreadonly"), + getStorageNS("entry-unreadonly"), true, undefined, { diff --git a/src/renderer/src/modules/entry-content/index.tsx b/src/renderer/src/modules/entry-content/index.tsx index 139207492..76c35d138 100644 --- a/src/renderer/src/modules/entry-content/index.tsx +++ b/src/renderer/src/modules/entry-content/index.tsx @@ -1,3 +1,5 @@ +import { useUISettingKey } from "@renderer/atoms" +import { m } from "@renderer/components/common/Motion" import { Logo } from "@renderer/components/icons/logo" import { AutoResizeHeight } from "@renderer/components/ui/auto-resize-height" import { useBizQuery } from "@renderer/hooks" @@ -9,8 +11,7 @@ import { WrappedElementProvider, } from "@renderer/providers/wrapped-element-provider" import { Queries } from "@renderer/queries" -import { useEntry, useFeedHeaderTitle, useUIStore } from "@renderer/store" -import { m } from "framer-motion" +import { useEntry, useFeedHeaderTitle } from "@renderer/store" import { useEffect, useState } from "react" import { LoadingCircle } from "../../components/ui/loading" @@ -43,9 +44,7 @@ function EntryContentRender({ entryId }: { entryId: string }) { const entry = useEntry(entryId) const [content, setContent] = useState() - const readerRenderInlineStyle = useUIStore( - (state) => state.readerRenderInlineStyle, - ) + const readerRenderInlineStyle = useUISettingKey("readerRenderInlineStyle") useEffect(() => { // Fallback data, if local data is broken should fallback to cached query data. const processContent = entry?.entries.content ?? data?.entries.content @@ -85,7 +84,7 @@ function EntryContentRender({ entryId }: { entryId: string }) { }, ) - const readerFontFamily = useUIStore((state) => state.readerFontFamily) + const readerFontFamily = useUISettingKey("readerFontFamily") if (!entry) return null diff --git a/src/renderer/src/modules/feed-column/index.tsx b/src/renderer/src/modules/feed-column/index.tsx index 82c712678..b96844b2a 100644 --- a/src/renderer/src/modules/feed-column/index.tsx +++ b/src/renderer/src/modules/feed-column/index.tsx @@ -4,6 +4,7 @@ import { Logo } from "@renderer/components/icons/logo" import { ActionButton } from "@renderer/components/ui/button" import { ProfileButton } from "@renderer/components/user-button" import { useNavigateEntry } from "@renderer/hooks/biz/useNavigateEntry" +import { useReduceMotion } from "@renderer/hooks/biz/useReduceMotion" import { APP_NAME, levels, views } from "@renderer/lib/constants" import { stopPropagation } from "@renderer/lib/dom" import { Routes } from "@renderer/lib/enum" @@ -99,6 +100,7 @@ export function FeedColumn() { const normalStyle = !window.electron || window.electron.process.platform !== "darwin" + const reduceMotion = useReduceMotion() return (
- + {views.map((item, index) => (
state.sidebarShowUnreadCount) + const showUnreadCount = useUISettingKey("sidebarShowUnreadCount") return (
diff --git a/src/renderer/src/modules/settings/modal/content.tsx b/src/renderer/src/modules/settings/modal/content.tsx index 3f762781b..459b88418 100644 --- a/src/renderer/src/modules/settings/modal/content.tsx +++ b/src/renderer/src/modules/settings/modal/content.tsx @@ -43,7 +43,7 @@ const Close = () => { const { dismiss } = useCurrentModal() return ( - + ) diff --git a/src/renderer/src/modules/settings/modal/layout.tsx b/src/renderer/src/modules/settings/modal/layout.tsx index 6520abab4..2cffdd82b 100644 --- a/src/renderer/src/modules/settings/modal/layout.tsx +++ b/src/renderer/src/modules/settings/modal/layout.tsx @@ -1,12 +1,12 @@ +import { useUISettingSelector } from "@renderer/atoms" +import { m } from "@renderer/components/common/Motion" import { Logo } from "@renderer/components/icons/logo" import { APP_NAME } from "@renderer/lib/constants" import { preventDefault } from "@renderer/lib/dom" import { cn } from "@renderer/lib/utils" -import { useUIStore } from "@renderer/store" -import { m, useDragControls } from "framer-motion" +import { useDragControls } from "framer-motion" import type { PointerEventHandler, PropsWithChildren } from "react" import { useCallback, useEffect } from "react" -import { useShallow } from "zustand/react/shallow" import { settings } from "../constants" import { SettingsSidebarTitle } from "../title" @@ -31,8 +31,8 @@ export function SettingModalLayout( } }, []) - const { draggable, overlay } = useUIStore( - useShallow((state) => ({ + const { draggable, overlay } = useUISettingSelector( + ((state) => ({ draggable: state.modalDraggable, overlay: state.modalOverlay, })), diff --git a/src/renderer/src/modules/settings/tabs/apperance.tsx b/src/renderer/src/modules/settings/tabs/apperance.tsx index 3f8fa9c03..a1644c06a 100644 --- a/src/renderer/src/modules/settings/tabs/apperance.tsx +++ b/src/renderer/src/modules/settings/tabs/apperance.tsx @@ -1,3 +1,9 @@ +import { + setUISetting, + useUISettingKey, + useUISettingSelector, + useUISettingValue, +} from "@renderer/atoms" import { Select, SelectContent, @@ -8,12 +14,11 @@ import { import { useDark } from "@renderer/hooks" import { tipcClient } from "@renderer/lib/client" import { getOS } from "@renderer/lib/utils" -import { uiActions, useUIStore } from "@renderer/store" import { useQuery } from "@tanstack/react-query" import { useCallback } from "react" import { bundledThemes } from "shiki/themes" -import { SettingSwitch } from "../control" +import { SettingDescription, SettingSwitch } from "../control" import { SettingSectionTitle } from "../section" import { SettingsTitle } from "../title" @@ -23,7 +28,7 @@ export const SettingAppearance = () => { toggleDark() }, []) - const state = useUIStore() + const state = useUISettingValue() const onlyMacos = window.electron && getOS() === "macOS" return ( @@ -40,7 +45,7 @@ export const SettingAppearance = () => { label="Opaque Sidebars" checked={state.opaqueSidebar} onCheckedChange={(checked) => { - uiActions.set("opaqueSidebar", checked) + setUISetting("opaqueSidebar", checked) }} /> )} @@ -54,7 +59,7 @@ export const SettingAppearance = () => { label="Dock Badge" checked={state.showDockBadge} onCheckedChange={(c) => { - uiActions.set("showDockBadge", c) + setUISetting("showDockBadge", c) }} /> )} @@ -62,7 +67,7 @@ export const SettingAppearance = () => { label="Show sidebar unread count" checked={state.sidebarShowUnreadCount} onCheckedChange={(c) => { - uiActions.set("sidebarShowUnreadCount", c) + setUISetting("sidebarShowUnreadCount", c) }} /> @@ -70,14 +75,14 @@ export const SettingAppearance = () => { label="Modal draggable" checked={state.modalDraggable} onCheckedChange={(c) => { - uiActions.set("modalDraggable", c) + setUISetting("modalDraggable", c) }} /> */} {/* { - uiActions.set("modalOpaque", c) + setUISetting("modalOpaque", c) }} /> */} @@ -88,22 +93,35 @@ export const SettingAppearance = () => { label="Render inline style" checked={state.readerRenderInlineStyle} onCheckedChange={(c) => { - uiActions.set("readerRenderInlineStyle", c) + setUISetting("readerRenderInlineStyle", c) }} /> + { - uiActions.set("modalOverlay", c) + setUISetting("modalOverlay", c) }} /> + { + setUISetting("reduceMotion", c) + }} + /> + + Enabling this option will reduce the motion of the element to improve + performance and device life, and if it is disabled, it will adapt to the + system settings. +
) } const ShikiTheme = () => { - const codeHighlightTheme = useUIStore((state) => state.codeHighlightTheme) + const codeHighlightTheme = useUISettingKey("codeHighlightTheme") return (
Code Highlight Theme @@ -111,19 +129,18 @@ const ShikiTheme = () => { defaultValue="github-dark" value={codeHighlightTheme} onValueChange={(value) => { - uiActions.set("codeHighlightTheme", value) + setUISetting("codeHighlightTheme", value) }} > - {Object.keys(bundledThemes) - ?.map((theme) => ( - - {theme} - - ))} + {Object.keys(bundledThemes)?.map((theme) => ( + + {theme} + + ))}
@@ -135,7 +152,7 @@ const Fonts = () => { queryFn: () => tipcClient?.getSystemFonts(), queryKey: ["systemFonts"], }) - const readerFontFamily = useUIStore( + const readerFontFamily = useUISettingSelector( (state) => state.readerFontFamily || "SN Pro", ) return ( @@ -145,7 +162,7 @@ const Fonts = () => { defaultValue="SN Pro" value={readerFontFamily} onValueChange={(value) => { - uiActions.set("readerFontFamily", value) + setUISetting("readerFontFamily", value) }} > @@ -173,7 +190,7 @@ const textSizeMap = { } const TextSize = () => { - const uiTextSize = useUIStore((state) => state.uiTextSize) + const uiTextSize = useUISettingSelector((state) => state.uiTextSize) return (
@@ -182,7 +199,7 @@ const TextSize = () => { defaultValue={textSizeMap.default.toString()} value={uiTextSize.toString() || textSizeMap.default.toString()} onValueChange={(value) => { - uiActions.set( + setUISetting( "uiTextSize", Number.parseInt(value) || textSizeMap.default, ) diff --git a/src/renderer/src/modules/settings/title.tsx b/src/renderer/src/modules/settings/title.tsx index 8f826c4a5..dfd843400 100644 --- a/src/renderer/src/modules/settings/title.tsx +++ b/src/renderer/src/modules/settings/title.tsx @@ -48,7 +48,7 @@ export const SettingsTitle = ({
diff --git a/src/renderer/src/pages/(main)/(layer)/feeds/[feedId]/layout.tsx b/src/renderer/src/pages/(main)/(layer)/feeds/[feedId]/layout.tsx index 2332520d6..a81467c56 100644 --- a/src/renderer/src/pages/(main)/(layer)/feeds/[feedId]/layout.tsx +++ b/src/renderer/src/pages/(main)/(layer)/feeds/[feedId]/layout.tsx @@ -1,8 +1,8 @@ +import { getUISettings, setUISetting } from "@renderer/atoms" import { useRouteView } from "@renderer/hooks/biz/useRouteParams" import { views } from "@renderer/lib/constants" import { cn } from "@renderer/lib/utils" import { EntryColumn } from "@renderer/modules/entry-column" -import { uiActions, useUIStore } from "@renderer/store" import { useMemo, useRef } from "react" import { HotkeysProvider } from "react-hotkeys-hook" import { useResizable } from "react-resizable-layout" @@ -12,8 +12,8 @@ export function Component() { const containerRef = useRef(null) // Memo this initial value to avoid re-render - // eslint-disable-next-line react-compiler/react-compiler - const entryColWidth = useMemo(() => useUIStore.getState().entryColWidth, []) + + const entryColWidth = useMemo(() => getUISettings().entryColWidth, []) const view = useRouteView() const inWideMode = view ? views[view].wideMode : false const { position, separatorProps } = useResizable({ @@ -23,7 +23,7 @@ export function Component() { initial: entryColWidth, containerRef, onResizeEnd({ position }) { - uiActions.set("entryColWidth", position) + setUISetting("entryColWidth", position) }, }) diff --git a/src/renderer/src/providers/ui-setting-Initialize.tsx b/src/renderer/src/providers/ui-setting-Initialize.tsx index 7283717fb..3f9efc833 100644 --- a/src/renderer/src/providers/ui-setting-Initialize.tsx +++ b/src/renderer/src/providers/ui-setting-Initialize.tsx @@ -1,9 +1,11 @@ +import { initializeDefaultUISettings, useUISettingValue } from "@renderer/atoms" import { tipcClient } from "@renderer/lib/client" -import { unreadActions, useUIStore } from "@renderer/store" +import { unreadActions } from "@renderer/store" import { useEffect, useInsertionEffect } from "react" +initializeDefaultUISettings() export const UISettingInitialize = () => { - const state = useUIStore() + const state = useUISettingValue() useInsertionEffect(() => { const root = document.documentElement diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index 3db45c327..f802785dc 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -1,5 +1,4 @@ export * from "./entry" export * from "./feed" export * from "./subscription" -export * from "./ui" export * from "./unread" diff --git a/src/renderer/src/store/ui.ts b/src/renderer/src/store/ui.ts deleted file mode 100644 index 91bc663d3..000000000 --- a/src/renderer/src/store/ui.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { buildStorageNS } from "@renderer/lib/ns" - -import { - createZustandStore, - getStoreActions, - localStorage, -} from "./utils/helper" - -interface UIState { - entryColWidth: number - opaqueSidebar: boolean - // UI - uiTextSize: number - - // Display counts - /** macOS only */ - showDockBadge: boolean - sidebarShowUnreadCount: boolean - - // modal - modalOverlay: boolean - modalDraggable: boolean - modalOpaque: boolean - - // content - readerFontFamily: string - readerRenderInlineStyle: boolean - codeHighlightTheme: string -} - -const createDefaultUIState = (): UIState => ({ - entryColWidth: 340, - opaqueSidebar: false, - readerFontFamily: "SN Pro", - uiTextSize: 16, - - showDockBadge: true, - sidebarShowUnreadCount: true, - - modalOverlay: true, - modalDraggable: true, - modalOpaque: true, - readerRenderInlineStyle: false, - codeHighlightTheme: "github-dark", -}) -interface UIActions { - clear: () => void - set: (key: T, value: UIState[T]) => void -} -const storageKey = buildStorageNS("ui") -export const useUIStore = createZustandStore(storageKey, { - version: 1, - storage: localStorage, -})((set) => ({ - ...createDefaultUIState(), - - clear() { - set(createDefaultUIState()) - }, - set(key, value) { - set({ [key]: value }) - }, -})) - -export const uiActions = getStoreActions(useUIStore) diff --git a/src/renderer/src/store/utils/clear.ts b/src/renderer/src/store/utils/clear.ts index 87c4f45c3..2f40fde46 100644 --- a/src/renderer/src/store/utils/clear.ts +++ b/src/renderer/src/store/utils/clear.ts @@ -1,9 +1,9 @@ +import { clearUISettings } from "@renderer/atoms" import { browserDB } from "@renderer/database" import { entryActions } from "../entry" import { feedActions } from "../feed" import { subscriptionActions } from "../subscription" -import { uiActions } from "../ui" import { unreadActions } from "../unread" export const clearLocalPersistStoreData = () => { @@ -12,11 +12,12 @@ export const clearLocalPersistStoreData = () => { entryActions, subscriptionActions, unreadActions, - uiActions, feedActions, ].forEach((actions) => { actions.clear() }) + clearUISettings() + browserDB.delete() }