From f43e7e57f45b5ebda7040832f0bb5ba3deeb6cfb Mon Sep 17 00:00:00 2001 From: Innei Date: Tue, 6 Aug 2024 15:57:27 +0800 Subject: [PATCH] refactor: grouped list (#210) * refactor: grouped list Signed-off-by: Innei * fix: padding Signed-off-by: Innei --------- Signed-off-by: Innei --- .vscode/settings.json | 3 +- src/renderer/src/atoms/settings/general.ts | 1 + src/renderer/src/atoms/settings/helper.ts | 38 ++++ src/renderer/src/atoms/user.ts | 4 +- .../components/ui/list-item-hover-overlay.tsx | 2 +- src/renderer/src/hooks/biz/useFeedActions.tsx | 4 +- src/renderer/src/hooks/biz/useSignOut.ts | 4 +- src/renderer/src/initialize/posthog.ts | 4 +- .../src/modules/entry-column/article-item.tsx | 2 +- .../src/modules/entry-column/audio-item.tsx | 2 +- .../src/modules/entry-column/date-item.tsx | 2 +- .../src/modules/entry-column/hooks.ts | 35 ++-- .../src/modules/entry-column/index.tsx | 89 ++-------- .../src/modules/entry-column/lists.tsx | 165 ++++++++++++++++++ .../entry-column/notification-item.tsx | 2 +- .../src/modules/entry-column/picture-item.tsx | 2 +- .../entry-column/social-media-item.tsx | 2 +- .../src/modules/entry-column/video-item.tsx | 2 +- .../src/modules/entry-content/index.tsx | 4 +- .../modules/entry-content/read-history.tsx | 4 +- src/renderer/src/modules/feed-column/item.tsx | 4 +- .../src/modules/settings/setting-builder.tsx | 14 +- .../src/modules/settings/tabs/general.tsx | 58 +++--- .../tabs/wallet/my-wallet-section/index.tsx | 4 +- .../tabs/wallet/transaction-section/index.tsx | 6 +- src/renderer/src/modules/wallet/tip-modal.tsx | 4 +- src/renderer/src/pages/(main)/layout.tsx | 4 +- .../src/pages/settings/(settings)/profile.tsx | 4 +- src/renderer/src/providers/user-provider.tsx | 4 +- src/renderer/src/store/feed/store.ts | 4 +- src/shared/src/interface/settings.ts | 1 + 31 files changed, 308 insertions(+), 170 deletions(-) create mode 100644 src/renderer/src/modules/entry-column/lists.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 687624ccc..54908876e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -39,5 +39,6 @@ "rsshub" ], "editor.foldingImportsByDefault": true, - "commentTranslate.hover.enabled": false + "commentTranslate.hover.enabled": false, + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/src/renderer/src/atoms/settings/general.ts b/src/renderer/src/atoms/settings/general.ts index 10edc9346..011846ee5 100644 --- a/src/renderer/src/atoms/settings/general.ts +++ b/src/renderer/src/atoms/settings/general.ts @@ -18,6 +18,7 @@ const createDefaultSettings = (): GeneralSettings => ({ renderMarkUnread: false, // UX // autoHideFeedColumn: true, + groupByDate: true, }) export const { diff --git a/src/renderer/src/atoms/settings/helper.ts b/src/renderer/src/atoms/settings/helper.ts index 392dd735c..9c2ccd050 100644 --- a/src/renderer/src/atoms/settings/helper.ts +++ b/src/renderer/src/atoms/settings/helper.ts @@ -1,6 +1,7 @@ import { useRefValue } from "@renderer/hooks/common" import { createAtomHooks } from "@renderer/lib/jotai" import { getStorageNS } from "@renderer/lib/ns" +import { createSettingBuilder } from "@renderer/modules/settings/setting-builder" import { useAtomValue } from "jotai" import { atomWithStorage, selectAtom } from "jotai/utils" import { useMemo } from "react" @@ -74,3 +75,40 @@ export const createSettingAtom = ( settingAtom: atom, } } + +export const createDefineSettingItem = + ( + _getSetting: () => T, + setSetting: (key: any, value: Partial) => void, + ) => + ( + key: K, + options: { + label: string + description?: string + onChange?: (value: T[K]) => void + }, + ) => { + const { label, description, onChange } = options + return { + key, + label, + description, + onChange: (value: any) => { + if (onChange) return onChange(value as any) + setSetting(key, value as any) + }, + } as any + } + +export const createSetting = ( + useSetting: () => T, + setSetting: (key: any, value: Partial) => void, +) => { + const SettingBuilder = createSettingBuilder(useSetting) + const defineSettingItem = createDefineSettingItem(useSetting, setSetting) + return { + SettingBuilder, + defineSettingItem, + } +} diff --git a/src/renderer/src/atoms/user.ts b/src/renderer/src/atoms/user.ts index 584a9ad36..958066d14 100644 --- a/src/renderer/src/atoms/user.ts +++ b/src/renderer/src/atoms/user.ts @@ -2,12 +2,10 @@ import type { User } from "@auth/core/types" import { createAtomHooks } from "@renderer/lib/jotai" import { atom } from "jotai" -export const [, , useMe, useSetMe, getMe, setMe] = createAtomHooks( +export const [, , useWhoami, useSetWhoami, whoami, setWhoami] = createAtomHooks( atom>(null), ) -export { useMe as useWhoAmI } - export const [ , , diff --git a/src/renderer/src/components/ui/list-item-hover-overlay.tsx b/src/renderer/src/components/ui/list-item-hover-overlay.tsx index 75a798f96..dbd7179c2 100644 --- a/src/renderer/src/components/ui/list-item-hover-overlay.tsx +++ b/src/renderer/src/components/ui/list-item-hover-overlay.tsx @@ -76,7 +76,7 @@ export const ListItemHoverOverlay = ({ )}
{ setMouseEnter(true) }} diff --git a/src/renderer/src/hooks/biz/useFeedActions.tsx b/src/renderer/src/hooks/biz/useFeedActions.tsx index 531f6e067..a7dfdf27e 100644 --- a/src/renderer/src/hooks/biz/useFeedActions.tsx +++ b/src/renderer/src/hooks/biz/useFeedActions.tsx @@ -1,4 +1,4 @@ -import { getMe } from "@renderer/atoms/user" +import { whoami } from "@renderer/atoms/user" import { useModalStack } from "@renderer/components/ui/modal" import type { NativeMenuItem } from "@renderer/lib/native-menu" import { useFeedClaimModal } from "@renderer/modules/claim" @@ -88,7 +88,7 @@ export const useFeedActions = ({ }, ] : []), - ...(feed.ownerUserId === getMe()?.id ? + ...(feed.ownerUserId === whoami()?.id ? [ { type: "text" as const, diff --git a/src/renderer/src/hooks/biz/useSignOut.ts b/src/renderer/src/hooks/biz/useSignOut.ts index 22a4754fb..3f015b0f9 100644 --- a/src/renderer/src/hooks/biz/useSignOut.ts +++ b/src/renderer/src/hooks/biz/useSignOut.ts @@ -1,5 +1,5 @@ import { signOut } from "@hono/auth-js/react" -import { setMe } from "@renderer/atoms/user" +import { setWhoami } from "@renderer/atoms/user" import { QUERY_PERSIST_KEY } from "@renderer/constants" import { tipcClient } from "@renderer/lib/client" import { clearStorage } from "@renderer/lib/ns" @@ -12,7 +12,7 @@ export const useSignOut = () => localStorage.removeItem(QUERY_PERSIST_KEY) // setLoginModalShow(true) - setMe(null) + setWhoami(null) // Clear local storage clearStorage() diff --git a/src/renderer/src/initialize/posthog.ts b/src/renderer/src/initialize/posthog.ts index 78d2a8450..bcffb5e9f 100644 --- a/src/renderer/src/initialize/posthog.ts +++ b/src/renderer/src/initialize/posthog.ts @@ -1,5 +1,5 @@ import { env } from "@env" -import { getMe } from "@renderer/atoms/user" +import { whoami } from "@renderer/atoms/user" import type { CaptureOptions, Properties } from "posthog-js" declare global { @@ -43,7 +43,7 @@ export const initPostHog = async () => { }, } - const user = getMe() + const user = whoami() if (user) { posthog.identify(user.id, { name: user.name, handle: user.handle }) } diff --git a/src/renderer/src/modules/entry-column/article-item.tsx b/src/renderer/src/modules/entry-column/article-item.tsx index 10932a82c..b13c6e3dc 100644 --- a/src/renderer/src/modules/entry-column/article-item.tsx +++ b/src/renderer/src/modules/entry-column/article-item.tsx @@ -20,7 +20,7 @@ export function ArticleItem({ export const ArticleItemSkeleton = (
-
+
diff --git a/src/renderer/src/modules/entry-column/audio-item.tsx b/src/renderer/src/modules/entry-column/audio-item.tsx index b5d63fd76..d80831864 100644 --- a/src/renderer/src/modules/entry-column/audio-item.tsx +++ b/src/renderer/src/modules/entry-column/audio-item.tsx @@ -20,7 +20,7 @@ export function AudioItem({ export const AudioItemSkeleton = (
-
+
diff --git a/src/renderer/src/modules/entry-column/date-item.tsx b/src/renderer/src/modules/entry-column/date-item.tsx index 40ce49ae6..b9619ea96 100644 --- a/src/renderer/src/modules/entry-column/date-item.tsx +++ b/src/renderer/src/modules/entry-column/date-item.tsx @@ -23,7 +23,7 @@ export const DateItem = memo( const className = cn( isFirst ? "pt-2" : "pt-8", - "flex items-center gap-1 pl-2 text-sm font-bold text-zinc-800 dark:text-neutral-400", + `relative z-10 -mx-2 flex items-center gap-1 bg-background px-4 text-sm font-bold text-zinc-800 dark:text-neutral-400`, ) if (view === FeedViewType.SocialMedia) { diff --git a/src/renderer/src/modules/entry-column/hooks.ts b/src/renderer/src/modules/entry-column/hooks.ts index 7a28ee1f8..a6da7dc55 100644 --- a/src/renderer/src/modules/entry-column/hooks.ts +++ b/src/renderer/src/modules/entry-column/hooks.ts @@ -159,26 +159,34 @@ export const useEntriesByView = ({ onReset }: { onReset?: () => void }) => { sortEntriesIdByStarAt(mergedEntries) : sortEntriesIdByEntryPublishedAt(mergedEntries) - const entriesWithDate = useMemo(() => { + const groupByDate = useGeneralSettingKey("groupByDate") + const groupedCounts: number[] | undefined = useMemo(() => { if (views[view].gridMode) { - return sortEntries + return + } + if (!groupByDate) { + return } const entriesId2Map = entryActions.getFlattenMapEntries() + const counts = [] as number[] let lastDate = "" - const entriesWithDate = [] as string[] for (const id of sortEntries) { const entry = entriesId2Map[id] - if (entry) { - const date = new Date(entry.entries.publishedAt).toDateString() - if (date !== lastDate) { - entriesWithDate.push(date) - lastDate = date - } + if (!entry) { + continue + } + const date = new Date(entry.entries.publishedAt).toDateString() + if (date !== lastDate) { + counts.push(1) + lastDate = date + } else { + const last = counts.pop() + if (last) counts.push(last + 1) } - entriesWithDate.push(id) } - return entriesWithDate - }, [sortEntries, view]) + + return counts + }, [groupByDate, sortEntries, view]) return { ...query, @@ -187,7 +195,8 @@ export const useEntriesByView = ({ onReset }: { onReset?: () => void }) => { refetch: useCallback(() => { query.refetch() }, [query]), - entriesIds: entriesWithDate, + entriesIds: sortEntries, + groupedCounts, totalCount: query.data?.pages?.[0]?.total ?? mergedEntries.length, } } diff --git a/src/renderer/src/modules/entry-column/index.tsx b/src/renderer/src/modules/entry-column/index.tsx index a0a8d995b..707a012f7 100644 --- a/src/renderer/src/modules/entry-column/index.tsx +++ b/src/renderer/src/modules/entry-column/index.tsx @@ -2,9 +2,8 @@ import { setGeneralSetting, useGeneralSettingKey, } from "@renderer/atoms/settings/general" -import { useMe } from "@renderer/atoms/user" +import { useWhoami } from "@renderer/atoms/user" import { m } from "@renderer/components/common/Motion" -import { EmptyIcon } from "@renderer/components/icons/empty" import { AutoResizeHeight } from "@renderer/components/ui/auto-resize-height" import { ActionButton } from "@renderer/components/ui/button" import { DividerVertical } from "@renderer/components/ui/divider" @@ -25,24 +24,22 @@ import { EntryHeader } from "@renderer/modules/entry-content/header" import { useRefreshFeedMutation } from "@renderer/queries/feed" import { entryActions, useEntry } from "@renderer/store/entry" import { useFeedById, useFeedHeaderTitle } from "@renderer/store/feed" -import type { HTMLMotionProps } from "framer-motion" import type { FC } from "react" -import { forwardRef, useCallback, useEffect, useRef } from "react" +import { useCallback, useEffect, useRef } from "react" import type { ScrollSeekConfiguration, VirtuosoHandle, VirtuosoProps, } from "react-virtuoso" -import { Virtuoso, VirtuosoGrid } from "react-virtuoso" +import { VirtuosoGrid } from "react-virtuoso" -import { DateItem } from "./date-item" -import { EntryColumnShortcutHandler } from "./EntryColumnShortcutHandler" import { useEntriesByView, useEntryMarkReadHandler } from "./hooks" import { EntryItem, EntryItemSkeleton, EntryItemSkeletonWithDelayShow, } from "./item" +import { EntryEmptyList, EntryList, EntryListContent } from "./lists" import { MarkAllButton } from "./mark-all-button" import { girdClassNames } from "./styles" @@ -59,7 +56,7 @@ export function EntryColumn() { }) }, []), }) - const { entriesIds, isFetchingNextPage } = entries + const { entriesIds, isFetchingNextPage, groupedCounts } = entries const { entryId: activeEntryId, @@ -88,7 +85,7 @@ export function EntryColumn() { const scrollRef = useRef(null) const virtuosoOptions = { components: { - List: ListContent, + List: EntryListContent, Footer: useCallback(() => { if (!isFetchingNextPage) return null return @@ -119,14 +116,10 @@ export function EntryColumn() { } }, itemContent: useCallback( - (index, entryId) => { + (_, entryId) => { if (!entryId) return null - if (entryId.includes(" ")) { - return - } else { - return - } + return }, [view], ), @@ -178,7 +171,7 @@ export function EntryColumn() { entries.isLoading ? null : ( - + ) ) : view && views[view].gridMode ? ( @@ -193,6 +186,7 @@ export function EntryColumn() { {...virtuosoOptions} virtuosoRef={virtuosoRef} refetch={entries.refetch} + groupCounts={groupedCounts} /> )} @@ -239,7 +233,7 @@ const ListHeader: FC<{ routerParams.feedId, ) - const user = useMe() + const user = useWhoami() const isOnline = useIsOnline() const feed = useFeedById(routerParams.feedId) @@ -338,64 +332,3 @@ const ListHeader: FC<{
) } - -const ListContent = forwardRef((props, ref) => ( -
-)) - -const EmptyList = forwardRef>( - (props, ref) => { - const unreadOnly = useGeneralSettingKey("unreadOnly") - return ( - - {unreadOnly ? ( - <> - - Zero Unread - - ) : ( -
- - Zero Items -
- )} -
- ) - }, -) - -const EntryList: FC< - VirtuosoProps & { - virtuosoRef: React.RefObject - - refetch: () => void - } -> = ({ virtuosoRef, refetch, ...virtuosoOptions }) => { - // Prevent scroll list move when press up/down key, the up/down key should be taken over by the shortcut key we defined. - const handleKeyDown: React.KeyboardEventHandler = useCallback( - (e) => { - if (e.key === "ArrowDown" || e.key === "ArrowUp") { - e.preventDefault() - } - }, - [], - ) - return ( - <> - - - - ) -} diff --git a/src/renderer/src/modules/entry-column/lists.tsx b/src/renderer/src/modules/entry-column/lists.tsx new file mode 100644 index 000000000..5774f428f --- /dev/null +++ b/src/renderer/src/modules/entry-column/lists.tsx @@ -0,0 +1,165 @@ +import { useGeneralSettingKey } from "@renderer/atoms/settings/general" +import { m } from "@renderer/components/common/Motion" +import { EmptyIcon } from "@renderer/components/icons/empty" +import { ReactVirtuosoItemPlaceholder } from "@renderer/components/ui/placeholder" +import { useRouteParamsSelector } from "@renderer/hooks/biz/useRouteParams" +import { useEntry } from "@renderer/store/entry" +import type { HTMLMotionProps } from "framer-motion" +import type { DOMAttributes, FC } from "react" +import { forwardRef, memo, useCallback } from "react" +import type { + VirtuosoHandle, + VirtuosoProps, +} from "react-virtuoso" +import { GroupedVirtuoso, Virtuoso } from "react-virtuoso" + +import { DateItem } from "./date-item" +import { EntryColumnShortcutHandler } from "./EntryColumnShortcutHandler" + +export const EntryListContent = forwardRef((props, ref) => ( +
+)) + +export const EntryEmptyList = forwardRef< + HTMLDivElement, + HTMLMotionProps<"div"> +>((props, ref) => { + const unreadOnly = useGeneralSettingKey("unreadOnly") + return ( + + {unreadOnly ? ( + <> + + Zero Unread + + ) : ( +
+ + Zero Items +
+ )} +
+ ) +}) + +type BaseEntryProps = { + virtuosoRef: React.RefObject + refetch: () => void +} +type EntryListProps = VirtuosoProps & { + groupCounts?: number[] +} & BaseEntryProps +export const EntryList: FC = memo( + ({ + virtuosoRef, + refetch, + groupCounts, + + ...virtuosoOptions + }) => { + // Prevent scroll list move when press up/down key, the up/down key should be taken over by the shortcut key we defined. + const handleKeyDown: React.KeyboardEventHandler = + useCallback((e) => { + if (e.key === "ArrowDown" || e.key === "ArrowUp") { + e.preventDefault() + } + }, []) + + return ( + <> + {groupCounts ? ( + + ) : ( + + )} + + + ) + }, +) + +const EntryGroupedList = forwardRef< + VirtuosoHandle, + VirtuosoProps & + DOMAttributes & { + groupCounts: number[] + } +>( + ( + { + groupCounts, + itemContent, + onKeyDown, + + ...virtuosoOptions + }, + ref, + ) => ( + { + const entryId = getGetGroupDataIndex( + groupCounts!, + index, + virtuosoOptions.data!, + ) + + return + }, + [groupCounts, virtuosoOptions.data], + )} + groupCounts={groupCounts} + onKeyDown={onKeyDown} + {...virtuosoOptions} + itemContent={useCallback( + (index: number, _: number, entryId: string, c: any) => + itemContent?.(index, entryId, c), + [itemContent], + )} + /> + ), +) + +function getGetGroupDataIndex( + groupCounts: number[], + groupIndex: number, + data: readonly T[], +) { + // Get first grouped of data index + // + let sum = 0 + for (let i = 0; i < groupIndex; i++) { + sum += groupCounts[i] + } + return data[sum] +} + +const EntryHeadDateItem: FC<{ + entryId: string +}> = ({ entryId }) => { + const entry = useEntry(entryId) + + const view = useRouteParamsSelector((s) => s.view) + + if (!entry) return + const date = new Date(entry.entries.publishedAt).toDateString() + return +} diff --git a/src/renderer/src/modules/entry-column/notification-item.tsx b/src/renderer/src/modules/entry-column/notification-item.tsx index f14a24971..1ee13acc7 100644 --- a/src/renderer/src/modules/entry-column/notification-item.tsx +++ b/src/renderer/src/modules/entry-column/notification-item.tsx @@ -18,7 +18,7 @@ export function NotificationItem({ } export const NotificationItemSkeleton = ( -
+
diff --git a/src/renderer/src/modules/entry-column/picture-item.tsx b/src/renderer/src/modules/entry-column/picture-item.tsx index ddc9ed903..5a0826e73 100644 --- a/src/renderer/src/modules/entry-column/picture-item.tsx +++ b/src/renderer/src/modules/entry-column/picture-item.tsx @@ -56,7 +56,7 @@ export function PictureItem({ export const PictureItemSkeleton = (
-
+
diff --git a/src/renderer/src/modules/entry-column/social-media-item.tsx b/src/renderer/src/modules/entry-column/social-media-item.tsx index 5a74e0905..df87edc91 100644 --- a/src/renderer/src/modules/entry-column/social-media-item.tsx +++ b/src/renderer/src/modules/entry-column/social-media-item.tsx @@ -141,7 +141,7 @@ const ActionBar = ({ entryId }: { entryId: string }) => { export const SocialMediaItemSkeleton = (
-
+
diff --git a/src/renderer/src/modules/entry-column/video-item.tsx b/src/renderer/src/modules/entry-column/video-item.tsx index 7de6fe51d..a9483eb64 100644 --- a/src/renderer/src/modules/entry-column/video-item.tsx +++ b/src/renderer/src/modules/entry-column/video-item.tsx @@ -153,7 +153,7 @@ const PreviewVideoModalContent: ModalContentComponent<{ export const VideoItemSkeleton = (
-
+
diff --git a/src/renderer/src/modules/entry-content/index.tsx b/src/renderer/src/modules/entry-content/index.tsx index e0f9b1d6b..60e7a6ae8 100644 --- a/src/renderer/src/modules/entry-content/index.tsx +++ b/src/renderer/src/modules/entry-content/index.tsx @@ -5,7 +5,7 @@ import { useEntryReadabilityContent, } from "@renderer/atoms/readability" import { useUISettingKey } from "@renderer/atoms/settings/ui" -import { useMe } from "@renderer/atoms/user" +import { useWhoami } from "@renderer/atoms/user" import { m } from "@renderer/components/common/Motion" import { Logo } from "@renderer/components/icons/logo" import { AutoResizeHeight } from "@renderer/components/ui/auto-resize-height" @@ -55,7 +55,7 @@ export const EntryContent = ({ entryId }: { entryId: ActiveEntryId }) => { } function EntryContentRender({ entryId }: { entryId: string }) { - const user = useMe() + const user = useWhoami() const { error, data, isPending } = useAuthQuery( Queries.entries.byId(entryId), diff --git a/src/renderer/src/modules/entry-content/read-history.tsx b/src/renderer/src/modules/entry-content/read-history.tsx index 77cf788c5..67703d8e1 100644 --- a/src/renderer/src/modules/entry-content/read-history.tsx +++ b/src/renderer/src/modules/entry-content/read-history.tsx @@ -1,4 +1,4 @@ -import { useMe } from "@renderer/atoms/user" +import { useWhoami } from "@renderer/atoms/user" import { Avatar, AvatarFallback, @@ -20,7 +20,7 @@ import { usePresentUserProfileModal } from "../profile/hooks" export const EntryReadHistory: Component<{ entryId: string }> = ({ entryId, }) => { - const me = useMe() + const me = useWhoami() const entryHistory = useEntryReadHistory(entryId) useAuthQuery(Queries.entries.entryReadingHistory(entryId), { diff --git a/src/renderer/src/modules/feed-column/item.tsx b/src/renderer/src/modules/feed-column/item.tsx index 6584740d3..74641dd49 100644 --- a/src/renderer/src/modules/feed-column/item.tsx +++ b/src/renderer/src/modules/feed-column/item.tsx @@ -1,5 +1,5 @@ import { getMainContainerElement } from "@renderer/atoms/dom" -import { useMe } from "@renderer/atoms/user" +import { useWhoami } from "@renderer/atoms/user" import { FeedIcon } from "@renderer/components/feed-icon" import { Tooltip, @@ -63,7 +63,7 @@ const FeedItemImpl = ({ const feed = useFeedById(feedId) const { items } = useFeedActions({ feedId, view }) - const me = useMe() + const me = useWhoami() const isOwned = feed && feed.ownerUserId === me?.id if (!feed) return null diff --git a/src/renderer/src/modules/settings/setting-builder.tsx b/src/renderer/src/modules/settings/setting-builder.tsx index ec6e820fc..f8e2785e4 100644 --- a/src/renderer/src/modules/settings/setting-builder.tsx +++ b/src/renderer/src/modules/settings/setting-builder.tsx @@ -14,7 +14,7 @@ type SharedSettingItem = { disabled?: boolean } -type SettingItem = { +export type SettingItem = { key: K label: string description?: string @@ -113,15 +113,3 @@ export const createSettingBuilder = ) }) } - -export function createSettingItemTypeHelper object>( - _t: Setting, -) { - return function >(schema: { - key: Key - onChange: (v: ReturnType[Key]) => void - label: string - }): any { - return schema - } -} diff --git a/src/renderer/src/modules/settings/tabs/general.tsx b/src/renderer/src/modules/settings/tabs/general.tsx index 139afe994..437985bde 100644 --- a/src/renderer/src/modules/settings/tabs/general.tsx +++ b/src/renderer/src/modules/settings/tabs/general.tsx @@ -2,15 +2,20 @@ import { setGeneralSetting, useGeneralSettingValue, } from "@renderer/atoms/settings/general" +import { + createSetting, +} from "@renderer/atoms/settings/helper" import { initPostHog } from "@renderer/initialize/posthog" import { tipcClient } from "@renderer/lib/client" import { clearLocalPersistStoreData } from "@renderer/store/utils/clear" import { useCallback, useEffect } from "react" -import { createSettingBuilder } from "../setting-builder" import { SettingsTitle } from "../title" -const SettingBuilder = createSettingBuilder(useGeneralSettingValue) +const { defineSettingItem, SettingBuilder } = createSetting( + useGeneralSettingValue, + setGeneralSetting, +) export const SettingGeneral = () => { useEffect(() => { tipcClient?.getLoginItemSettings().then((settings) => { @@ -45,53 +50,51 @@ export const SettingGeneral = () => { }, { type: "title", - value: "view", + value: "timeline", }, - { - key: "unreadOnly", + defineSettingItem("unreadOnly", { label: "Show unread content on launch", description: "Display only unread content when the app is launched.", - onChange: (value) => setGeneralSetting("unreadOnly", value), - }, - { - key: "scrollMarkUnread", + }), + defineSettingItem("groupByDate", { + label: "Group by date", + description: "Group entries by date.", + }), + + { type: "title", value: "unread" }, + + defineSettingItem("scrollMarkUnread", { label: "Mark as read when scrolling", description: "Automatically mark entries as read when scrolled out of the view.", - onChange: (value) => setGeneralSetting("scrollMarkUnread", value), - }, - { - key: "hoverMarkUnread", + }), + defineSettingItem("hoverMarkUnread", { label: "Mark as read when hovering", description: "Automatically mark entries as read when hovered.", - onChange: (value) => setGeneralSetting("hoverMarkUnread", value), - }, - { - key: "renderMarkUnread", + }), + defineSettingItem("renderMarkUnread", { label: "Mark as read when in the view", description: "Automatically mark single-level entries (e.g., social media posts, pictures, video views) as read when they enter the view.", - onChange: (value) => setGeneralSetting("renderMarkUnread", value), - }, + }), { type: "title", value: "Privacy & Data", }, - { - key: "dataPersist", + + defineSettingItem("dataPersist", { label: "Persist data for offline usage", description: "Persist data locally to enable offline access and local search.", - onChange: (value) => setGeneralSetting("dataPersist", value), - }, - { - key: "sendAnonymousData", + }), + + defineSettingItem("sendAnonymousData", { label: "Send anonymous data", description: "By opting to send anonymized telemetry data, you contribute to improving the overall user experience of Follow.", - onChange: (value) => { + onChange(value) { setGeneralSetting("sendAnonymousData", value) if (value) { initPostHog() @@ -100,7 +103,8 @@ export const SettingGeneral = () => { delete window.posthog } }, - }, + }), + { label: "Rebuild Database", action: async () => { diff --git a/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/index.tsx b/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/index.tsx index 93edf0c93..306027059 100644 --- a/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/index.tsx +++ b/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/index.tsx @@ -1,4 +1,4 @@ -import { useMe } from "@renderer/atoms/user" +import { useWhoami } from "@renderer/atoms/user" import { Divider } from "@renderer/components/ui/divider" import { LoadingCircle } from "@renderer/components/ui/loading" import { @@ -15,7 +15,7 @@ import { ClaimDailyReward } from "./claim-daily-reward" import { CreateWallet } from "./create-wallet" export const MyWalletSection = () => { - const user = useMe() + const user = useWhoami() const wallet = useWallet({ userId: user?.id }) const myWallet = wallet.data?.[0] diff --git a/src/renderer/src/modules/settings/tabs/wallet/transaction-section/index.tsx b/src/renderer/src/modules/settings/tabs/wallet/transaction-section/index.tsx index 0fe2141d2..9da58c033 100644 --- a/src/renderer/src/modules/settings/tabs/wallet/transaction-section/index.tsx +++ b/src/renderer/src/modules/settings/tabs/wallet/transaction-section/index.tsx @@ -1,4 +1,4 @@ -import { useMe } from "@renderer/atoms/user" +import { useWhoami } from "@renderer/atoms/user" import { Logo } from "@renderer/components/icons/logo" import { Avatar, @@ -21,7 +21,7 @@ import { SettingSectionTitle } from "@renderer/modules/settings/section" import { useWallet, useWalletTransactions } from "@renderer/queries/wallet" export const TransactionsSection = () => { - const user = useMe() + const user = useWhoami() const wallet = useWallet({ userId: user?.id }) const myWallet = wallet.data?.[0] @@ -146,7 +146,7 @@ const UserRenderer = ({ | "fromUser" | "toUser"] }) => { - const me = useMe() + const me = useWhoami() const isMe = user?.id === me?.id const name = isMe ? "You" : user?.name || APP_NAME diff --git a/src/renderer/src/modules/wallet/tip-modal.tsx b/src/renderer/src/modules/wallet/tip-modal.tsx index 69d4cb5bc..7ca2feb4e 100644 --- a/src/renderer/src/modules/wallet/tip-modal.tsx +++ b/src/renderer/src/modules/wallet/tip-modal.tsx @@ -1,4 +1,4 @@ -import { useMe } from "@renderer/atoms/user" +import { useWhoami } from "@renderer/atoms/user" import { StyledButton } from "@renderer/components/ui/button" import { Divider } from "@renderer/components/ui/divider" import { LoadingCircle } from "@renderer/components/ui/loading" @@ -22,7 +22,7 @@ export const TipModalContent: FC<{ userId?: string feedId?: string }> = ({ userId, feedId }) => { - const user = useMe() + const user = useWhoami() const myWallet = useWallet({ userId: user?.id }) const myWalletData = myWallet.data?.[0] diff --git a/src/renderer/src/pages/(main)/layout.tsx b/src/renderer/src/pages/(main)/layout.tsx index 217831406..0e8fcdf62 100644 --- a/src/renderer/src/pages/(main)/layout.tsx +++ b/src/renderer/src/pages/(main)/layout.tsx @@ -2,7 +2,7 @@ import { repository } from "@pkg" import { useFeedColumnShow } from "@renderer/atoms/app" import { setMainContainerElement } from "@renderer/atoms/dom" import { getUISettings, setUISetting } from "@renderer/atoms/settings/ui" -import { useLoginModalShow, useMe } from "@renderer/atoms/user" +import { useLoginModalShow, useWhoami } from "@renderer/atoms/user" import { AppErrorBoundary } from "@renderer/components/common/AppErrorBoundary" import { ErrorComponentType } from "@renderer/components/errors" import { PanelSplitter } from "@renderer/components/ui/divider" @@ -54,7 +54,7 @@ const FooterInfo = () => ( export function Component() { const isAuthFail = useLoginModalShow() - const user = useMe() + const user = useWhoami() const containerRef = useRef(null) diff --git a/src/renderer/src/pages/settings/(settings)/profile.tsx b/src/renderer/src/pages/settings/(settings)/profile.tsx index 5866b8cab..6e40da2f7 100644 --- a/src/renderer/src/pages/settings/(settings)/profile.tsx +++ b/src/renderer/src/pages/settings/(settings)/profile.tsx @@ -1,5 +1,5 @@ import { zodResolver } from "@hookform/resolvers/zod" -import { useMe } from "@renderer/atoms/user" +import { useWhoami } from "@renderer/atoms/user" import { StyledButton } from "@renderer/components/ui/button" import { Form, @@ -35,7 +35,7 @@ export const loader = defineSettingPage({ }) export function Component() { - const user = useMe() + const user = useWhoami() const form = useForm>({ resolver: zodResolver(formSchema), diff --git a/src/renderer/src/providers/user-provider.tsx b/src/renderer/src/providers/user-provider.tsx index 2b4321734..e9e17a9b7 100644 --- a/src/renderer/src/providers/user-provider.tsx +++ b/src/renderer/src/providers/user-provider.tsx @@ -1,4 +1,4 @@ -import { useSetMe } from "@renderer/atoms/user" +import { useSetWhoami } from "@renderer/atoms/user" import { tipcClient } from "@renderer/lib/client" import { useSession } from "@renderer/queries/auth" import { CleanerService } from "@renderer/services/cleaner" @@ -6,7 +6,7 @@ import { useEffect } from "react" export const UserProvider = () => { const { session } = useSession() - const setUser = useSetMe() + const setUser = useSetWhoami() useEffect(() => { if (!session?.user) return setUser(session.user) diff --git a/src/renderer/src/store/feed/store.ts b/src/renderer/src/store/feed/store.ts index 4a6b6672c..030811f0d 100644 --- a/src/renderer/src/store/feed/store.ts +++ b/src/renderer/src/store/feed/store.ts @@ -1,4 +1,4 @@ -import { getMe } from "@renderer/atoms/user" +import { whoami } from "@renderer/atoms/user" import { runTransactionInScope } from "@renderer/database" import { apiClient } from "@renderer/lib/api-fetch" import type { FeedModel } from "@renderer/models" @@ -61,7 +61,7 @@ class FeedActions { }), async () => { - const currentUser = getMe() + const currentUser = whoami() if (!currentUser) return this.updateFeedOwnership(feedId, currentUser.id) const feed = get().feeds[feedId] diff --git a/src/shared/src/interface/settings.ts b/src/shared/src/interface/settings.ts index 403061548..62c54e5d3 100644 --- a/src/shared/src/interface/settings.ts +++ b/src/shared/src/interface/settings.ts @@ -6,6 +6,7 @@ export interface GeneralSettings { scrollMarkUnread: boolean hoverMarkUnread: boolean renderMarkUnread: boolean + groupByDate: boolean } export interface UISettings {