refactor: grouped list (#210)

* refactor: grouped list

Signed-off-by: Innei <i@innei.in>

* fix: padding

Signed-off-by: Innei <i@innei.in>

---------

Signed-off-by: Innei <i@innei.in>
This commit is contained in:
Innei 2024-08-06 15:57:27 +08:00 committed by GitHub
parent 744422fc12
commit f43e7e57f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 308 additions and 170 deletions

View File

@ -39,5 +39,6 @@
"rsshub"
],
"editor.foldingImportsByDefault": true,
"commentTranslate.hover.enabled": false
"commentTranslate.hover.enabled": false,
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@ -18,6 +18,7 @@ const createDefaultSettings = (): GeneralSettings => ({
renderMarkUnread: false,
// UX
// autoHideFeedColumn: true,
groupByDate: true,
})
export const {

View File

@ -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 = <T extends object>(
settingAtom: atom,
}
}
export const createDefineSettingItem =
<T>(
_getSetting: () => T,
setSetting: (key: any, value: Partial<T>) => void,
) =>
<K extends keyof T>(
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 = <T extends object>(
useSetting: () => T,
setSetting: (key: any, value: Partial<T>) => void,
) => {
const SettingBuilder = createSettingBuilder(useSetting)
const defineSettingItem = createDefineSettingItem(useSetting, setSetting)
return {
SettingBuilder,
defineSettingItem,
}
}

View File

@ -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<Nullable<User>>(null),
)
export { useMe as useWhoAmI }
export const [
,
,

View File

@ -76,7 +76,7 @@ export const ListItemHoverOverlay = ({
)}
<div
ref={ref}
className="relative z-[1]"
className="relative"
onMouseEnter={() => {
setMouseEnter(true)
}}

View File

@ -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,

View File

@ -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()

View File

@ -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 })
}

View File

@ -20,7 +20,7 @@ export function ArticleItem({
export const ArticleItemSkeleton = (
<div className="relative h-[120px] rounded-md bg-theme-background text-zinc-700 transition-colors dark:text-neutral-400">
<div className="relative z-[1]">
<div className="relative">
<div className="group relative flex py-4 pl-3 pr-2">
<Skeleton className="mr-2 size-5 rounded-sm" />
<div className="-mt-0.5 flex-1 text-sm leading-tight">

View File

@ -20,7 +20,7 @@ export function AudioItem({
export const AudioItemSkeleton = (
<div className="relative mx-auto w-full max-w-lg rounded-md bg-theme-background text-zinc-700 transition-colors dark:text-neutral-400">
<div className="relative z-[1]">
<div className="relative">
<div className="group relative flex py-4 pl-3 pr-2">
<div className="-mt-0.5 line-clamp-4 flex-1 text-sm leading-tight">
<div className="flex gap-1 text-[10px] font-bold text-zinc-400 dark:text-neutral-500">

View File

@ -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) {

View File

@ -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,
}
}

View File

@ -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<HTMLDivElement>(null)
const virtuosoOptions = {
components: {
List: ListContent,
List: EntryListContent,
Footer: useCallback(() => {
if (!isFetchingNextPage) return null
return <EntryItemSkeletonWithDelayShow view={view} />
@ -119,14 +116,10 @@ export function EntryColumn() {
}
},
itemContent: useCallback(
(index, entryId) => {
(_, entryId) => {
if (!entryId) return null
if (entryId.includes(" ")) {
return <DateItem date={entryId} view={view} isFirst={index === 0} />
} else {
return <EntryItem key={entryId} entryId={entryId} view={view} />
}
return <EntryItem key={entryId} entryId={entryId} view={view} />
},
[view],
),
@ -178,7 +171,7 @@ export function EntryColumn() {
entries.isLoading ?
null :
(
<EmptyList />
<EntryEmptyList />
)
) : view && views[view].gridMode ?
(
@ -193,6 +186,7 @@ export function EntryColumn() {
{...virtuosoOptions}
virtuosoRef={virtuosoRef}
refetch={entries.refetch}
groupCounts={groupedCounts}
/>
)}
</ScrollArea.ScrollArea>
@ -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<{
</div>
)
}
const ListContent = forwardRef<HTMLDivElement>((props, ref) => (
<div className="px-2" {...props} ref={ref} />
))
const EmptyList = forwardRef<HTMLDivElement, HTMLMotionProps<"div">>(
(props, ref) => {
const unreadOnly = useGeneralSettingKey("unreadOnly")
return (
<m.div
className="absolute -mt-6 flex size-full grow flex-col items-center justify-center gap-2 text-zinc-400"
{...props}
ref={ref}
>
{unreadOnly ? (
<>
<i className="i-mgc-celebrate-cute-re -mt-11 text-3xl" />
<span className="text-base">Zero Unread</span>
</>
) : (
<div className="flex -translate-y-6 flex-col items-center justify-center gap-2">
<EmptyIcon className="size-[30px]" />
<span className="text-base">Zero Items</span>
</div>
)}
</m.div>
)
},
)
const EntryList: FC<
VirtuosoProps<string, unknown> & {
virtuosoRef: React.RefObject<VirtuosoHandle>
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<HTMLDivElement> = useCallback(
(e) => {
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
e.preventDefault()
}
},
[],
)
return (
<>
<Virtuoso
onKeyDown={handleKeyDown}
{...virtuosoOptions}
ref={virtuosoRef}
/>
<EntryColumnShortcutHandler
refetch={refetch}
data={virtuosoOptions.data!}
virtuosoRef={virtuosoRef}
/>
</>
)
}

View File

@ -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<HTMLDivElement>((props, ref) => (
<div className="px-2" {...props} ref={ref} />
))
export const EntryEmptyList = forwardRef<
HTMLDivElement,
HTMLMotionProps<"div">
>((props, ref) => {
const unreadOnly = useGeneralSettingKey("unreadOnly")
return (
<m.div
className="absolute -mt-6 flex size-full grow flex-col items-center justify-center gap-2 text-zinc-400"
{...props}
ref={ref}
>
{unreadOnly ? (
<>
<i className="i-mgc-celebrate-cute-re -mt-11 text-3xl" />
<span className="text-base">Zero Unread</span>
</>
) : (
<div className="flex -translate-y-6 flex-col items-center justify-center gap-2">
<EmptyIcon className="size-[30px]" />
<span className="text-base">Zero Items</span>
</div>
)}
</m.div>
)
})
type BaseEntryProps = {
virtuosoRef: React.RefObject<VirtuosoHandle>
refetch: () => void
}
type EntryListProps = VirtuosoProps<string, unknown> & {
groupCounts?: number[]
} & BaseEntryProps
export const EntryList: FC<EntryListProps> = 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<HTMLDivElement> =
useCallback((e) => {
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
e.preventDefault()
}
}, [])
return (
<>
{groupCounts ? (
<EntryGroupedList
groupCounts={groupCounts}
onKeyDown={handleKeyDown}
{...virtuosoOptions}
ref={virtuosoRef}
/>
) : (
<Virtuoso
onKeyDown={handleKeyDown}
{...virtuosoOptions}
ref={virtuosoRef}
/>
)}
<EntryColumnShortcutHandler
refetch={refetch}
data={virtuosoOptions.data!}
virtuosoRef={virtuosoRef}
/>
</>
)
},
)
const EntryGroupedList = forwardRef<
VirtuosoHandle,
VirtuosoProps<string, unknown> &
DOMAttributes<HTMLDivElement> & {
groupCounts: number[]
}
>(
(
{
groupCounts,
itemContent,
onKeyDown,
...virtuosoOptions
},
ref,
) => (
<GroupedVirtuoso
ref={ref}
groupContent={useCallback(
(index: number) => {
const entryId = getGetGroupDataIndex(
groupCounts!,
index,
virtuosoOptions.data!,
)
return <EntryHeadDateItem entryId={entryId} />
},
[groupCounts, virtuosoOptions.data],
)}
groupCounts={groupCounts}
onKeyDown={onKeyDown}
{...virtuosoOptions}
itemContent={useCallback(
(index: number, _: number, entryId: string, c: any) =>
itemContent?.(index, entryId, c),
[itemContent],
)}
/>
),
)
function getGetGroupDataIndex<T>(
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 <ReactVirtuosoItemPlaceholder />
const date = new Date(entry.entries.publishedAt).toDateString()
return <DateItem date={date} view={view} isFirst={true} />
}

View File

@ -18,7 +18,7 @@ export function NotificationItem({
}
export const NotificationItemSkeleton = (
<div className="relative z-[1] mx-auto w-full max-w-lg">
<div className="relative mx-auto w-full max-w-lg">
<div className="group relative flex py-4 pl-3 pr-2">
<Skeleton className="mr-2 size-5 shrink-0 overflow-hidden rounded-sm " />
<div className="-mt-0.5 line-clamp-4 flex-1 text-sm leading-tight">

View File

@ -56,7 +56,7 @@ export function PictureItem({
export const PictureItemSkeleton = (
<div className="relative max-w-md rounded-md bg-theme-background text-zinc-700 transition-colors dark:text-neutral-400">
<div className="relative z-[1]">
<div className="relative">
<div className="p-1.5">
<div className="relative flex gap-2 overflow-x-auto">
<div className="relative flex aspect-square w-full shrink-0 items-center overflow-hidden rounded-md">

View File

@ -141,7 +141,7 @@ const ActionBar = ({ entryId }: { entryId: string }) => {
export const SocialMediaItemSkeleton = (
<div className="relative m-auto w-[75ch] rounded-md bg-theme-background text-zinc-700 transition-colors dark:text-neutral-400">
<div className="relative z-[1]">
<div className="relative">
<div className="group relative flex py-4 pl-3 pr-2">
<Skeleton className="mr-2 size-9" />
<div className="ml-2 min-w-0 flex-1">

View File

@ -153,7 +153,7 @@ const PreviewVideoModalContent: ModalContentComponent<{
export const VideoItemSkeleton = (
<div className="relative mx-auto w-full max-w-lg rounded-md bg-theme-background text-zinc-700 transition-colors dark:text-neutral-400">
<div className="relative z-[1]">
<div className="relative">
<div className="p-1.5">
<div className="w-full">
<div className="overflow-x-auto">

View File

@ -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),

View File

@ -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), {

View File

@ -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

View File

@ -14,7 +14,7 @@ type SharedSettingItem = {
disabled?: boolean
}
type SettingItem<T, K extends keyof T = keyof T> = {
export type SettingItem<T, K extends keyof T = keyof T> = {
key: K
label: string
description?: string
@ -113,15 +113,3 @@ export const createSettingBuilder =
)
})
}
export function createSettingItemTypeHelper<Setting extends () => object>(
_t: Setting,
) {
return function <const Key extends keyof ReturnType<Setting>>(schema: {
key: Key
onChange: (v: ReturnType<Setting>[Key]) => void
label: string
}): any {
return schema
}
}

View File

@ -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 () => {

View File

@ -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]

View File

@ -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

View File

@ -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]

View File

@ -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<HTMLDivElement>(null)

View File

@ -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<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),

View File

@ -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)

View File

@ -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]

View File

@ -6,6 +6,7 @@ export interface GeneralSettings {
scrollMarkUnread: boolean
hoverMarkUnread: boolean
renderMarkUnread: boolean
groupByDate: boolean
}
export interface UISettings {