feat: virtual list support for infinite list (#439)
This commit is contained in:
parent
b811993b35
commit
d078d8986d
|
|
@ -101,12 +101,12 @@
|
|||
"react-hook-form": "7.43.9",
|
||||
"react-hot-toast": "2.4.0",
|
||||
"react-i18next": "^12.2.2",
|
||||
"react-infinite-scroller": "1.2.6",
|
||||
"react-parallax-tilt": "^1.7.131",
|
||||
"react-popper": "^2.3.0",
|
||||
"react-scroll": "1.8.9",
|
||||
"react-sortablejs": "6.1.4",
|
||||
"react-string-replace": "^1.1.0",
|
||||
"react-virtuoso": "4.3.1",
|
||||
"refractor": "4.8.1",
|
||||
"rehype-autolink-headings": "6.1.1",
|
||||
"rehype-infer-description-meta": "^1.1.0",
|
||||
|
|
|
|||
|
|
@ -221,9 +221,6 @@ dependencies:
|
|||
react-i18next:
|
||||
specifier: ^12.2.2
|
||||
version: 12.2.2(i18next@22.4.15)(react-dom@18.2.0)(react@18.2.0)
|
||||
react-infinite-scroller:
|
||||
specifier: 1.2.6
|
||||
version: 1.2.6(react@18.2.0)
|
||||
react-parallax-tilt:
|
||||
specifier: ^1.7.131
|
||||
version: 1.7.131(react-dom@18.2.0)(react@18.2.0)
|
||||
|
|
@ -239,6 +236,9 @@ dependencies:
|
|||
react-string-replace:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
react-virtuoso:
|
||||
specifier: 4.3.1
|
||||
version: 4.3.1(react-dom@18.2.0)(react@18.2.0)
|
||||
refractor:
|
||||
specifier: 4.8.1
|
||||
version: 4.8.1
|
||||
|
|
@ -12131,15 +12131,6 @@ packages:
|
|||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-infinite-scroller@1.2.6(react@18.2.0):
|
||||
resolution: {integrity: sha512-mGdMyOD00YArJ1S1F3TVU9y4fGSfVVl6p5gh/Vt4u99CJOptfVu/q5V/Wlle72TMgYlBwIhbxK5wF0C/R33PXQ==}
|
||||
peerDependencies:
|
||||
react: ^0.14.9 || ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/react-intersection-observer@9.4.3(react@18.2.0):
|
||||
resolution: {integrity: sha512-WNRqMQvKpupr6MzecAQI0Pj0+JQong307knLP4g/nBex7kYfIaZsPpXaIhKHR+oV8z+goUbH9e10j6lGRnTzlQ==}
|
||||
peerDependencies:
|
||||
|
|
@ -12311,6 +12302,17 @@ packages:
|
|||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-virtuoso@4.3.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-2+V0bvA1fASO+etlBG6YB0uj+StizxP3ecDJXgGW/r2z9AH067ehpJy2TSRiEIGQtDTmJAcmZnZzYVnk7AUmbw==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
react: '>=16 || >=17 || >= 18'
|
||||
react-dom: '>=16 || >=17 || >= 18'
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react@18.2.0:
|
||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
import React from "react"
|
||||
import InfiniteScroll from "react-infinite-scroller"
|
||||
import { Virtuoso } from "react-virtuoso"
|
||||
|
||||
import { Modal } from "~/components/ui/Modal"
|
||||
|
||||
|
|
@ -18,40 +18,38 @@ export const CharacterList: React.FC<{
|
|||
title: string
|
||||
}> = ({ open, setOpen, hasMore, loadMore, list, title }) => {
|
||||
const { t } = useTranslation("common")
|
||||
const flattenList = list?.reduce(
|
||||
(acc, cur) => acc.concat(cur?.list || []),
|
||||
[] as any[],
|
||||
) as any[]
|
||||
|
||||
return (
|
||||
<Modal open={open} setOpen={setOpen} title={title} zIndex={20}>
|
||||
<div className="px-5 overflow-auto flex-1">
|
||||
<InfiniteScroll
|
||||
loadMore={loadMore}
|
||||
hasMore={hasMore}
|
||||
loader={
|
||||
<div className="text-sm py-3 text-center" key={0}>
|
||||
{t("Loading")} ...
|
||||
</div>
|
||||
}
|
||||
useWindow={false}
|
||||
>
|
||||
{list?.length ? (
|
||||
list.map((page) =>
|
||||
page?.list?.map((sub: any, index) => {
|
||||
const character = sub?.character || sub?.fromCharacter
|
||||
return (
|
||||
<CharacterListItem
|
||||
key={index}
|
||||
sub={sub}
|
||||
character={character}
|
||||
/>
|
||||
)
|
||||
}),
|
||||
{list?.length ? (
|
||||
<Virtuoso
|
||||
overscan={10}
|
||||
style={{ height: Infinity }}
|
||||
className="h-screen"
|
||||
endReached={() => loadMore()}
|
||||
components={{
|
||||
Footer: hasMore ? Loader : undefined,
|
||||
}}
|
||||
data={flattenList}
|
||||
itemContent={(index, sub) => {
|
||||
const character = sub?.character || sub?.fromCharacter
|
||||
return (
|
||||
<CharacterListItem key={index} sub={sub} character={character} />
|
||||
)
|
||||
) : (
|
||||
<div className="py-3 text-center text-zinc-300">
|
||||
{t("No Content Yet.")}
|
||||
</div>
|
||||
)}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
}}
|
||||
></Virtuoso>
|
||||
) : (
|
||||
<div className="px-5 overflow-auto flex-1">
|
||||
<div className="py-3 text-center text-zinc-300">
|
||||
{t("No Content Yet.")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="h-16 border-t flex items-center px-5 py-4">
|
||||
<Button isBlock onClick={() => setOpen(false)}>
|
||||
{t("Close")}
|
||||
|
|
@ -60,3 +58,12 @@ export const CharacterList: React.FC<{
|
|||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const Loader = () => {
|
||||
const { t } = useTranslation("common")
|
||||
return (
|
||||
<div className="text-sm py-3 text-center" key={0}>
|
||||
{t("Loading")} ...
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@ import { Avatar } from "../ui/Avatar"
|
|||
import { UniLink } from "../ui/UniLink"
|
||||
import { FollowingButton } from "./FollowingButton"
|
||||
|
||||
const noopArr = [] as any[]
|
||||
const CharacterListItem: React.FC<{
|
||||
character: any
|
||||
sub: any
|
||||
}> = ({ character, sub }) => {
|
||||
return (
|
||||
<div className="py-3 flex items-center justify-between space-x-2 text-sm">
|
||||
<div className="py-3 flex items-center justify-between space-x-2 text-sm px-5">
|
||||
<div className="flex flex-1 overflow-hidden space-x-2">
|
||||
<UniLink
|
||||
href={getSiteLink({
|
||||
|
|
@ -25,7 +26,7 @@ const CharacterListItem: React.FC<{
|
|||
<CharacterFloatCard siteId={character?.handle}>
|
||||
<Avatar
|
||||
className="align-middle border-2 border-white"
|
||||
images={character?.metadata?.content?.avatars || []}
|
||||
images={character?.metadata?.content?.avatars || noopArr}
|
||||
name={character?.metadata?.content?.name || character?.handle}
|
||||
size={40}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
import InfiniteScroll from "react-infinite-scroller"
|
||||
import { Virtuoso } from "react-virtuoso"
|
||||
|
||||
import { CommentInput } from "~/components/common/CommentInput"
|
||||
import { CommentItem } from "~/components/common/CommentItem"
|
||||
|
|
@ -38,36 +38,44 @@ export const Comment: React.FC<{
|
|||
</span>
|
||||
</div>
|
||||
<CommentInput pageId={page?.id} />
|
||||
<InfiniteScroll
|
||||
className="xlog-comment-list space-y-6 pt-6"
|
||||
loadMore={comments.fetchNextPage as any}
|
||||
hasMore={comments.hasNextPage}
|
||||
loader={
|
||||
<div
|
||||
className="relative mt-4 w-full text-sm text-center py-4"
|
||||
key={"loading"}
|
||||
>
|
||||
{t("Loading")}...
|
||||
</div>
|
||||
|
||||
<Virtuoso
|
||||
className="xlog-comment-list mt-6"
|
||||
useWindowScroll
|
||||
data={comments.data?.pages}
|
||||
endReached={() => comments.fetchNextPage()}
|
||||
components={{
|
||||
Footer: comments.isLoading ? Loader : undefined,
|
||||
}}
|
||||
itemContent={(_, p) =>
|
||||
p?.list?.map((comment, idx) => (
|
||||
<CommentItem
|
||||
className={idx === 0 ? "" : "mt-6"}
|
||||
originalId={page?.id}
|
||||
comment={comment}
|
||||
key={comment.transactionHash}
|
||||
depth={0}
|
||||
/>
|
||||
))
|
||||
}
|
||||
>
|
||||
{comments.isLoading ? (
|
||||
<div className="relative mt-4 w-full text-sm text-center py-4">
|
||||
{t("Loading")}...
|
||||
</div>
|
||||
) : (
|
||||
comments.data?.pages?.map((p) =>
|
||||
p?.list?.map((comment) => (
|
||||
<CommentItem
|
||||
originalId={page?.id}
|
||||
comment={comment}
|
||||
key={comment.transactionHash}
|
||||
depth={0}
|
||||
/>
|
||||
)),
|
||||
)
|
||||
)}
|
||||
</InfiniteScroll>
|
||||
></Virtuoso>
|
||||
{comments.isLoading && (
|
||||
<div className="relative mt-4 w-full text-sm text-center py-4">
|
||||
{t("Loading")}...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Loader = () => {
|
||||
const { t } = useTranslation("common")
|
||||
return (
|
||||
<div
|
||||
className="relative mt-4 w-full text-sm text-center py-4"
|
||||
key={"loading"}
|
||||
>
|
||||
{t("Loading")}...
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { UniLink } from "~/components/ui/UniLink"
|
|||
import { useDate } from "~/hooks/useDate"
|
||||
import { CSB_SCAN } from "~/lib/env"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
export const CommentItem: React.FC<{
|
||||
comment: NoteEntity & {
|
||||
|
|
@ -23,7 +24,8 @@ export const CommentItem: React.FC<{
|
|||
}
|
||||
originalId?: string
|
||||
depth: number
|
||||
}> = ({ comment, originalId, depth }) => {
|
||||
className?: string
|
||||
}> = ({ comment, originalId, depth, className }) => {
|
||||
const [replyOpen, setReplyOpen] = useState(false)
|
||||
const [editOpen, setEditOpen] = useState(false)
|
||||
|
||||
|
|
@ -37,7 +39,9 @@ export const CommentItem: React.FC<{
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={depth > 0 ? "" : "border-b border-dashed pb-6"}>
|
||||
<div
|
||||
className={cn(depth > 0 ? "" : "border-b border-dashed pb-6", className)}
|
||||
>
|
||||
<div className="flex group">
|
||||
<div>
|
||||
<CharacterFloatCard siteId={comment?.character?.handle}>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
import Link from "next/link"
|
||||
import { useRouter } from "next/router"
|
||||
import { useEffect, useState } from "react"
|
||||
import InfiniteScroll from "react-infinite-scroller"
|
||||
import { memo, useEffect, useState } from "react"
|
||||
import reactStringReplace from "react-string-replace"
|
||||
import { Virtuoso } from "react-virtuoso"
|
||||
|
||||
import { useAccountState } from "@crossbell/connect-kit"
|
||||
import { Switch } from "@headlessui/react"
|
||||
|
|
@ -43,7 +43,7 @@ const Post = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mt-8">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CharacterFloatCard siteId={post.character?.handle}>
|
||||
<Link
|
||||
|
|
@ -157,6 +157,8 @@ const Post = ({
|
|||
)
|
||||
}
|
||||
|
||||
const MemoedPost = memo(Post)
|
||||
|
||||
export const MainFeed: React.FC<{
|
||||
type?: FeedType
|
||||
noteIds?: string[]
|
||||
|
|
@ -226,19 +228,7 @@ export const MainFeed: React.FC<{
|
|||
|
||||
return (
|
||||
<>
|
||||
<InfiniteScroll
|
||||
loadMore={feed.fetchNextPage as any}
|
||||
hasMore={feed.hasNextPage}
|
||||
loader={
|
||||
<div
|
||||
className="relative mt-4 w-full text-sm text-center py-4"
|
||||
key={"loading"}
|
||||
>
|
||||
{t("Loading")} ...
|
||||
</div>
|
||||
}
|
||||
className="space-y-10"
|
||||
>
|
||||
<div className="space-y-10">
|
||||
{hasFiltering && (
|
||||
<div className="flex items-center text-zinc-500">
|
||||
<i className="i-mingcute:android-2-line mr-2 text-lg" />
|
||||
|
|
@ -279,27 +269,48 @@ export const MainFeed: React.FC<{
|
|||
{type === "search" && (
|
||||
<Tabs items={searchTabs} className="border-none text-sm -my-4"></Tabs>
|
||||
)}
|
||||
|
||||
{feed.isLoading ? (
|
||||
<div className="text-center text-zinc-600">{t("Loading")}...</div>
|
||||
) : !feed.data?.pages[0]?.count ? (
|
||||
<EmptyState />
|
||||
) : (
|
||||
<div className="xlog-posts space-y-8 overflow-x-hidden">
|
||||
{feed.data?.pages.map((posts) =>
|
||||
posts?.list.map((post) => {
|
||||
return (
|
||||
<Post
|
||||
key={`${post.characterId}-${post.noteId}`}
|
||||
post={post}
|
||||
filtering={aiFiltering ? 60 : 0}
|
||||
keyword={keyword}
|
||||
/>
|
||||
)
|
||||
}),
|
||||
)}
|
||||
<div className="xlog-posts !-mt-0">
|
||||
<Virtuoso
|
||||
overscan={5}
|
||||
endReached={() => feed.fetchNextPage()}
|
||||
useWindowScroll
|
||||
data={feed.data?.pages}
|
||||
itemContent={(_, posts) =>
|
||||
posts?.list.map((post) => {
|
||||
return (
|
||||
<MemoedPost
|
||||
key={`${post.characterId}-${post.noteId}`}
|
||||
post={post}
|
||||
filtering={aiFiltering ? 60 : 0}
|
||||
keyword={keyword}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
></Virtuoso>
|
||||
|
||||
{feed.isFetching && feed.hasNextPage && <Loader />}
|
||||
</div>
|
||||
)}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const Loader = () => {
|
||||
const { t } = useTranslation("common")
|
||||
return (
|
||||
<div
|
||||
className="relative w-full text-sm text-center py-4 mt-12"
|
||||
key={"loading"}
|
||||
>
|
||||
{t("Loading")} ...
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue