From caf7e9ca72358772f3e47b593e7553b78578b226 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Wed, 3 Jul 2024 00:12:50 +0800 Subject: [PATCH] feat: add VirtualList support for feed events --- src/client/components/feed/FeedEventItem.tsx | 71 +++++++++++--------- src/client/routes/feed/$channelId/index.tsx | 50 ++++++++------ src/server/trpc/routers/feed/index.ts | 32 ++++++--- 3 files changed, 90 insertions(+), 63 deletions(-) diff --git a/src/client/components/feed/FeedEventItem.tsx b/src/client/components/feed/FeedEventItem.tsx index 5e98cdf..1c344c2 100644 --- a/src/client/components/feed/FeedEventItem.tsx +++ b/src/client/components/feed/FeedEventItem.tsx @@ -5,44 +5,51 @@ import dayjs from 'dayjs'; import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; import { MarkdownViewer } from '../MarkdownEditor'; import { FeedIcon } from './FeedIcon'; +import { cn } from '@/utils/style'; type FeedEventItemType = AppRouterOutput['feed']['events'][number]; -export const FeedEventItem: React.FC<{ event: FeedEventItemType }> = React.memo( - ({ event }) => { - return ( -
-
-
-
- -
-
- -
+export const FeedEventItem: React.FC<{ + className?: string; + event: FeedEventItemType; +}> = React.memo(({ className, event }) => { + return ( +
+
+
+
+
- -
- {event.source} - - {event.eventName} - - {event.tags.map((tag) => ( - {tag} - ))} +
+
- - -
{dayjs(event.createdAt).fromNow()}
-
- -

{dayjs(event.createdAt).format('YYYY-MM-DD HH:mm:ss')}

-
-
+
+ {event.source} + + {event.eventName} + + {event.tags.map((tag) => ( + {tag} + ))} +
- ); - } -); + + + +
{dayjs(event.createdAt).fromNow()}
+
+ +

{dayjs(event.createdAt).format('YYYY-MM-DD HH:mm:ss')}

+
+
+
+ ); +}); FeedEventItem.displayName = 'FeedEventItem'; diff --git a/src/client/routes/feed/$channelId/index.tsx b/src/client/routes/feed/$channelId/index.tsx index a010486..e666684 100644 --- a/src/client/routes/feed/$channelId/index.tsx +++ b/src/client/routes/feed/$channelId/index.tsx @@ -1,7 +1,6 @@ import { defaultErrorHandler, defaultSuccessHandler, trpc } from '@/api/trpc'; import { CommonHeader } from '@/components/CommonHeader'; import { CommonWrapper } from '@/components/CommonWrapper'; -import { ScrollArea } from '@/components/ui/scroll-area'; import { useCurrentWorkspaceId } from '@/store/user'; import { routeAuthBeforeLoad } from '@/utils/route'; import { useTranslation } from '@i18next-toolkit/react'; @@ -16,6 +15,7 @@ import { FeedIntegration } from '@/components/feed/FeedIntegration'; import { DialogWrapper } from '@/components/DialogWrapper'; import { useSocketSubscribeList } from '@/api/socketio'; import { useMemo } from 'react'; +import { SimpleVirtualList } from '@/components/SimpleVirtualList'; export const Route = createFileRoute('/feed/$channelId/')({ beforeLoad: routeAuthBeforeLoad, @@ -30,10 +30,18 @@ function PageComponent() { workspaceId, channelId, }); - const { data: events = [] } = trpc.feed.events.useQuery({ - workspaceId, - channelId, - }); + + const { data, hasNextPage, fetchNextPage, isFetchingNextPage } = + trpc.feed.fetchEventsByCursor.useInfiniteQuery( + { + workspaceId, + channelId, + }, + { + getNextPageParam: (lastPage) => lastPage.nextCursor, + } + ); + const deleteMutation = trpc.feed.deleteChannel.useMutation({ onSuccess: defaultSuccessHandler, onError: defaultErrorHandler, @@ -55,8 +63,8 @@ function PageComponent() { }); const fullEvents = useMemo( - () => [...realtimeEvents, ...events], - [realtimeEvents, events] + () => [...realtimeEvents, ...(data?.pages.flatMap((p) => p.items) ?? [])], + [realtimeEvents, data] ); return ( @@ -102,19 +110,21 @@ function PageComponent() { /> } > - {fullEvents && fullEvents.length === 0 ? ( -
- -
- ) : ( - -
- {(fullEvents ?? []).map((event) => ( - - ))} -
-
- )} +
+ } + renderEmpty={() => ( +
+ +
+ )} + /> +
); } diff --git a/src/server/trpc/routers/feed/index.ts b/src/server/trpc/routers/feed/index.ts index fc9a92e..b5894ef 100644 --- a/src/server/trpc/routers/feed/index.ts +++ b/src/server/trpc/routers/feed/index.ts @@ -15,6 +15,7 @@ import { import { prisma } from '../../../model/_client'; import _ from 'lodash'; import { buildFeedPublicOpenapi, feedIntegrationRouter } from './integration'; +import { fetchDataByCursor } from '../../../utils/prisma'; export const feedRouter = router({ channels: workspaceProcedure @@ -113,33 +114,42 @@ export const feedRouter = router({ return channel; }), - events: workspaceProcedure + fetchEventsByCursor: workspaceProcedure .meta( buildFeedOpenapi({ method: 'GET', - path: '/{channelId}/events', + path: '/{channelId}/fetchEventsByCursor', + description: 'Fetch workspace feed channel events', }) ) .input( z.object({ channelId: z.string(), + limit: z.number().min(1).max(100).default(50), + cursor: z.string().optional(), + }) + ) + .output( + z.object({ + items: z.array(FeedEventModelSchema), + nextCursor: z.string().optional(), }) ) - .output(z.array(FeedEventModelSchema)) .query(async ({ input }) => { - const { channelId } = input; + const { channelId, cursor, limit } = input; - const events = await prisma.feedEvent.findMany({ + const { items, nextCursor } = await fetchDataByCursor(prisma.feedEvent, { where: { - channelId: channelId, - }, - take: 50, - orderBy: { - createdAt: 'desc', + channelId, }, + limit, + cursor, }); - return events; + return { + items, + nextCursor, + }; }), createChannel: workspaceOwnerProcedure .meta(