Skip to content

Commit

Permalink
feat(ui): add pagination to recent activities (#3427)
Browse files Browse the repository at this point in the history
* feat(ui): add pagination to recent activities

* update: animation

* [autofix.ci] apply automated fixes

* update

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
liangfung and autofix-ci[bot] authored Nov 18, 2024
1 parent 5f0d9f0 commit d260d12
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 160 deletions.
8 changes: 3 additions & 5 deletions ee/tabby-ui/app/(home)/components/animation-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ import { motion, Transition, UseInViewOptions, Variants } from 'framer-motion'

const cardTransition: Transition = {
ease: 'easeOut',
duration: 0.5
duration: 0.2
}

function getCardVariants(delay?: number): Variants {
return {
initial: {
opacity: 0,
y: 24,
y: 30,
transition: cardTransition
},
onscreen: {
opacity: 1,
y: 0,
transition: {
...cardTransition,
delay
delay: 0.3 + (delay || 0)
}
}
}
Expand All @@ -44,8 +44,6 @@ export function AnimationWrapper({
initial="initial"
whileInView="onscreen"
viewport={viewport}
// onViewportEnter={handleEnterViewport}
// onViewportLeave={handleLeaveViewport}
style={style}
className={className}
>
Expand Down
130 changes: 86 additions & 44 deletions ee/tabby-ui/app/(home)/components/thread-feeds.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createContext, useContext, useMemo, useState } from 'react'
import { createContext, useContext, useEffect, useMemo, useState } from 'react'
import Link from 'next/link'
import slugify from '@sindresorhus/slugify'
import { motion, Variants } from 'framer-motion'
import { motion } from 'framer-motion'
import moment from 'moment'
import { useQuery } from 'urql'

Expand All @@ -10,36 +10,31 @@ import { MARKDOWN_SOURCE_REGEX } from '@/lib/constants/regex'
import { graphql } from '@/lib/gql/generates'
import { ContextSource, ListThreadsQuery } from '@/lib/gql/generates/graphql'
import { Member, useAllMembers } from '@/lib/hooks/use-all-members'
import {
resetThreadsPageNo,
useAnswerEngineStore
} from '@/lib/stores/answer-engine-store'
import { contextInfoQuery, listThreadMessages } from '@/lib/tabby/query'
import { cn, getTitleFromMessages } from '@/lib/utils'
import { IconFiles, IconSpinner } from '@/components/ui/icons'
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationNext,
PaginationPrevious
} from '@/components/ui/pagination'
import { Separator } from '@/components/ui/separator'
import { Skeleton } from '@/components/ui/skeleton'
import { LoadMoreIndicator } from '@/components/load-more-indicator'
import LoadingWrapper from '@/components/loading-wrapper'
import { Mention } from '@/components/mention-tag'
import { UserAvatar } from '@/components/user-avatar'

import { AnimationWrapper } from './animation-wrapper'

const threadItemVariants: Variants = {
initial: {
opacity: 0,
y: 40
},
onscreen: {
opacity: 1,
y: 0,
transition: {
duration: 0.5,
ease: 'easeOut'
}
}
}

interface ThreadFeedsProps {
className?: string
onNavigateToThread: () => void
onNavigateToThread: (params: { pageNo: number }) => void
}

type ThreadFeedsContextValue = {
Expand All @@ -54,7 +49,7 @@ export const ThreadFeedsContext = createContext<ThreadFeedsContextValue>(
{} as ThreadFeedsContextValue
)

const PAGE_SIZE = 10
const PAGE_SIZE = 8

const listThreads = graphql(/* GraphQL */ `
query ListThreads(
Expand Down Expand Up @@ -96,8 +91,10 @@ export function ThreadFeeds({
className,
onNavigateToThread
}: ThreadFeedsProps) {
const storedPageNo = useAnswerEngineStore(state => state.threadsPageNo)
const [allUsers, fetchingUsers] = useAllMembers()
const [beforeCursor, setBeforeCursor] = useState<string | undefined>()
const [page, setPage] = useState(storedPageNo)
const [{ data, fetching }] = useQuery({
query: listThreads,
variables: {
Expand All @@ -110,34 +107,50 @@ export function ThreadFeeds({
const [{ data: contextInfoData, fetching: fetchingSources }] = useQuery({
query: contextInfoQuery
})
const pageInfo = data?.threads.pageInfo
const threadCount = data?.threads?.edges?.length
const pageCount = Math.ceil((threadCount || 0) / PAGE_SIZE)
const showPagination =
pageCount > 1 || (pageCount === 1 && pageInfo?.hasPreviousPage)

// threads for current page
const threads = useMemo(() => {
const _threads = data?.threads?.edges
if (!_threads?.length) return []

return _threads.slice().reverse()
}, [data?.threads?.edges])

const pageInfo = data?.threads.pageInfo
return _threads
.slice()
.reverse()
.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)
}, [data?.threads?.edges, page])

const loadMore = () => {
if (pageInfo?.startCursor) {
setBeforeCursor(pageInfo.startCursor)
}
}

const handleNavigateToThread = () => {
onNavigateToThread({ pageNo: page })
}

useEffect(() => {
// reset pageNo store after it has been used
resetThreadsPageNo()
}, [])

return (
<ThreadFeedsContext.Provider
value={{
allUsers,
fetchingUsers,
sources: contextInfoData?.contextInfo.sources,
fetchingSources,
onNavigateToThread
onNavigateToThread: handleNavigateToThread
}}
>
<div className={cn('w-full', className)}>
<AnimationWrapper delay={0.4} style={{ width: '100%' }}>
<AnimationWrapper style={{ width: '100%' }} viewport={{ once: true }}>
<LoadingWrapper
loading={fetching || fetchingUsers}
fallback={
Expand All @@ -154,31 +167,60 @@ export function ThreadFeeds({
initial="initial"
whileInView="onscreen"
viewport={{
margin: '0px 0px -140px 0px',
amount: 'some',
// margin: '0px 0px -140px 0px',
once: true
}}
transition={{
delay: 0.5,
delayChildren: 0.3,
staggerChildren: 0.2
}}
style={{ width: '100%', paddingBottom: '1rem' }}
style={{ width: '100%' }}
>
<div className="flex flex-col gap-3 text-sm">
<div className="relative flex min-h-[40.25rem] flex-col gap-3 text-sm">
{threads.map(t => {
return <ThreadItem data={t} key={t.node.id} />
})}
</div>
{!!pageInfo?.hasPreviousPage && (
<LoadMoreIndicator
onLoad={loadMore}
isFetching={fetching}
intersectionOptions={{ rootMargin: '0px 0px 200px 0px' }}
>
<div className="mt-8 flex justify-center">
{!threads.length && fetching && (
<div className="absolute inset-0 flex items-center justify-center">
<IconSpinner className="h-8 w-8" />
</div>
</LoadMoreIndicator>
)}
</div>
{showPagination && (
<Pagination className={cn('flex items-center justify-end')}>
<PaginationContent>
<span className="flex w-[64px] items-center justify-start text-sm font-medium">
Page {page}
</span>
<PaginationItem>
<PaginationPrevious
disabled={page === 1}
onClick={() => {
if (page === 1) return

const _page = page - 1
setPage(_page)
}}
/>
</PaginationItem>
<PaginationItem>
<PaginationNext
disabled={
page === pageCount && !pageInfo?.hasPreviousPage
}
onClick={() => {
if (
page === pageCount &&
!pageInfo?.hasPreviousPage
) {
return
}

const _page = page + 1
setPage(_page)
loadMore()
}}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
)}
</motion.div>
</LoadingWrapper>
Expand Down Expand Up @@ -226,7 +268,7 @@ function ThreadItem({ data }: ThreadItemProps) {

return (
<Link
href={title ? `/search/${titleSlug}-${threadId}` : 'javascript:void'}
href={title ? `/search/${titleSlug}-${threadId}` : ''}
onClick={onNavigateToThread}
>
<div className="transform-bg group flex-1 overflow-hidden rounded-lg px-3 py-2 hover:bg-accent">
Expand Down
Loading

0 comments on commit d260d12

Please sign in to comment.