wip: refactor
This commit is contained in:
@@ -1,92 +1,112 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import { NoteKind_1, NoteKind_1063, NoteThread, Repost } from '@shared/notes';
|
||||
import { NoteKindUnsupport } from '@shared/notes/kinds/unsupport';
|
||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
|
||||
import { LumeEvent, Widget } from '@utils/types';
|
||||
import { DBEvent, Widget } from '@utils/types';
|
||||
|
||||
export function FeedBlock({ params }: { params: Widget }) {
|
||||
const { status, data, fetchNextPage, hasNextPage, isFetchingNextPage } =
|
||||
export function FeedWidget({ params }: { params: Widget }) {
|
||||
const { db } = useStorage();
|
||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['newsfeed', params.content],
|
||||
queryFn: async () => {
|
||||
return { data: [], nextCursor: 0 };
|
||||
queryFn: async ({ pageParam = 0 }) => {
|
||||
const authors = JSON.parse(params.content);
|
||||
return await db.getAllEventsByAuthors(authors, 20, pageParam);
|
||||
},
|
||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||
});
|
||||
|
||||
const notes = data ? data.pages.flatMap((d: { data: LumeEvent[] }) => d.data) : [];
|
||||
|
||||
const parentRef = useRef();
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: hasNextPage ? notes.length + 1 : notes.length,
|
||||
const dbEvents = useMemo(
|
||||
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
||||
[data]
|
||||
);
|
||||
const parentRef = useRef<HTMLDivElement>();
|
||||
const virtualizer = useVirtualizer({
|
||||
count: hasNextPage ? dbEvents.length : dbEvents.length,
|
||||
getScrollElement: () => parentRef.current,
|
||||
estimateSize: () => 500,
|
||||
overscan: 2,
|
||||
estimateSize: () => 650,
|
||||
overscan: 4,
|
||||
});
|
||||
const items = virtualizer.getVirtualItems();
|
||||
|
||||
const itemsVirtualizer = rowVirtualizer.getVirtualItems();
|
||||
const totalSize = rowVirtualizer.getTotalSize();
|
||||
|
||||
useEffect(() => {
|
||||
const [lastItem] = [...rowVirtualizer.getVirtualItems()].reverse();
|
||||
|
||||
if (!lastItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastItem.index >= notes.length - 1 && hasNextPage && !isFetchingNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
}, [notes.length, fetchNextPage, rowVirtualizer.getVirtualItems()]);
|
||||
|
||||
// render event match event kind
|
||||
const renderItem = useCallback(
|
||||
(index: string | number) => {
|
||||
const note: LumeEvent = notes[index];
|
||||
if (!note) return;
|
||||
switch (note.kind) {
|
||||
const dbEvent: DBEvent = dbEvents[index];
|
||||
if (!dbEvent) return;
|
||||
|
||||
const event: NDKEvent = JSON.parse(dbEvent.event as string);
|
||||
|
||||
switch (event.kind) {
|
||||
case 1: {
|
||||
const root = note.tags.find((el) => el[3] === 'root')?.[1];
|
||||
const reply = note.tags.find((el) => el[3] === 'reply')?.[1];
|
||||
if (root || reply) {
|
||||
if (dbEvent.root_id || dbEvent.reply_id) {
|
||||
return (
|
||||
<div key={note.id} data-index={index} ref={rowVirtualizer.measureElement}>
|
||||
<NoteThread event={note} root={root} reply={reply} />
|
||||
<div
|
||||
key={(dbEvent.root_id || dbEvent.reply_id) + dbEvent.id + index}
|
||||
data-index={index}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<NoteThread
|
||||
event={event}
|
||||
root={dbEvent.root_id}
|
||||
reply={dbEvent.reply_id}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div key={note.id} data-index={index} ref={rowVirtualizer.measureElement}>
|
||||
<NoteKind_1 event={note} skipMetadata={false} />
|
||||
<div
|
||||
key={dbEvent.id + index}
|
||||
data-index={index}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<NoteKind_1 event={event} skipMetadata={false} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
case 6:
|
||||
return (
|
||||
<div key={note.id} data-index={index} ref={rowVirtualizer.measureElement}>
|
||||
<Repost key={note.id} event={note} />
|
||||
<div
|
||||
key={dbEvent.id + index}
|
||||
data-index={index}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<Repost key={dbEvent.id} event={event} />
|
||||
</div>
|
||||
);
|
||||
case 1063:
|
||||
return (
|
||||
<div key={note.id} data-index={index} ref={rowVirtualizer.measureElement}>
|
||||
<NoteKind_1063 key={note.id} event={note} />
|
||||
<div
|
||||
key={dbEvent.id + index}
|
||||
data-index={index}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<NoteKind_1063 key={dbEvent.id} event={event} />
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div key={note.id} data-index={index} ref={rowVirtualizer.measureElement}>
|
||||
<NoteKindUnsupport key={note.id} event={note} />
|
||||
<div
|
||||
key={dbEvent.id + index}
|
||||
data-index={index}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<NoteKindUnsupport key={dbEvent.id} event={event} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
[notes]
|
||||
[dbEvents]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -99,32 +119,31 @@ export function FeedBlock({ params }: { params: Widget }) {
|
||||
<NoteSkeleton />
|
||||
</div>
|
||||
</div>
|
||||
) : itemsVirtualizer.length === 0 ? (
|
||||
) : items.length === 0 ? (
|
||||
<div className="px-3 py-1.5">
|
||||
<div className="bbg-white/10 rounded-xl px-3 py-6">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<p className="text-center text-sm text-white">
|
||||
Not found any posts from last 48 hours
|
||||
Not found any postrs from last 48 hours
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="relative w-full"
|
||||
style={{
|
||||
height: `${totalSize}px`,
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: `${virtualizer.getTotalSize()}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute left-0 top-0 w-full"
|
||||
style={{
|
||||
transform: `translateY(${
|
||||
itemsVirtualizer[0].start - rowVirtualizer.options.scrollMargin
|
||||
}px)`,
|
||||
transform: `translateY(${items[0].start}px)`,
|
||||
}}
|
||||
>
|
||||
{itemsVirtualizer.map((virtualRow) => renderItem(virtualRow.index))}
|
||||
{items.map((item) => renderItem(item.index))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -135,6 +154,33 @@ export function FeedBlock({ params }: { params: Widget }) {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="px-3 py-1.5">
|
||||
<button
|
||||
onClick={() => fetchNextPage()}
|
||||
disabled={!hasNextPage || isFetchingNextPage}
|
||||
className="inline-flex h-11 w-full items-center justify-between gap-2 rounded-lg bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none"
|
||||
>
|
||||
{isFetchingNextPage ? (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Loading...</span>
|
||||
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||
</>
|
||||
) : hasNextPage ? (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Load more</span>
|
||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Nothing more to load</span>
|
||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,35 +1,74 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { useRef } from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
import { useNDK } from '@libs/ndk/provider';
|
||||
|
||||
import { NoteKind_1, NoteSkeleton } from '@shared/notes';
|
||||
import { NoteKind_1, NoteSkeleton, Repost } from '@shared/notes';
|
||||
import { NoteKindUnsupport } from '@shared/notes/kinds/unsupport';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
|
||||
import { nHoursAgo } from '@utils/date';
|
||||
import { LumeEvent, Widget } from '@utils/types';
|
||||
import { Widget } from '@utils/types';
|
||||
|
||||
export function HashtagBlock({ params }: { params: Widget }) {
|
||||
export function HashtagWidget({ params }: { params: Widget }) {
|
||||
const { ndk } = useNDK();
|
||||
const { status, data } = useQuery(['hashtag', params.content], async () => {
|
||||
const { status, data } = useQuery(['hashtag-widget', params.content], async () => {
|
||||
const events = await ndk.fetchEvents({
|
||||
kinds: [1],
|
||||
'#t': [params.content],
|
||||
since: nHoursAgo(24),
|
||||
});
|
||||
return [...events] as unknown as LumeEvent[];
|
||||
return [...events] as unknown as NDKEvent[];
|
||||
});
|
||||
|
||||
const parentRef = useRef();
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
const virtualizer = useVirtualizer({
|
||||
count: data ? data.length : 0,
|
||||
getScrollElement: () => parentRef.current,
|
||||
estimateSize: () => 400,
|
||||
estimateSize: () => 650,
|
||||
overscan: 4,
|
||||
});
|
||||
const items = virtualizer.getVirtualItems();
|
||||
|
||||
const itemsVirtualizer = rowVirtualizer.getVirtualItems();
|
||||
const totalSize = rowVirtualizer.getTotalSize();
|
||||
// render event match event kind
|
||||
const renderItem = useCallback(
|
||||
(index: string | number) => {
|
||||
const event: NDKEvent = data[index];
|
||||
if (!event) return;
|
||||
|
||||
switch (event.kind) {
|
||||
case 1:
|
||||
return (
|
||||
<div key={event.id} data-index={index} ref={virtualizer.measureElement}>
|
||||
<NoteKind_1 event={event} skipMetadata={false} />
|
||||
</div>
|
||||
);
|
||||
case 6:
|
||||
return (
|
||||
<div
|
||||
key={event.id + index}
|
||||
data-index={index}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<Repost key={event.id} event={event} />
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div
|
||||
key={event.id + index}
|
||||
data-index={index}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<NoteKindUnsupport key={event.id} event={event} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
[data]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative w-[400px] shrink-0 bg-white/10">
|
||||
@@ -41,40 +80,31 @@ export function HashtagBlock({ params }: { params: Widget }) {
|
||||
<NoteSkeleton />
|
||||
</div>
|
||||
</div>
|
||||
) : itemsVirtualizer.length === 0 ? (
|
||||
) : items.length === 0 ? (
|
||||
<div className="px-3 py-1.5">
|
||||
<div className="rounded-xl bg-white/10 px-3 py-6">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<p className="text-center text-sm font-medium text-white">
|
||||
No new posts about this hashtag in 24 hours ago
|
||||
No new postrs about this hashtag in 24 hours ago
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="relative w-full"
|
||||
style={{
|
||||
height: `${totalSize}px`,
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: `${virtualizer.getTotalSize()}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute left-0 top-0 w-full"
|
||||
style={{
|
||||
transform: `translateY(${
|
||||
itemsVirtualizer[0].start - rowVirtualizer.options.scrollMargin
|
||||
}px)`,
|
||||
transform: `translateY(${items[0].start}px)`,
|
||||
}}
|
||||
>
|
||||
{itemsVirtualizer.map((virtualRow) => (
|
||||
<div
|
||||
key={virtualRow.key}
|
||||
data-index={virtualRow.index}
|
||||
ref={rowVirtualizer.measureElement}
|
||||
>
|
||||
<NoteKind_1 event={data[virtualRow.index]} />
|
||||
</div>
|
||||
))}
|
||||
{items.map((item) => renderItem(item.index))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NDKFilter } from '@nostr-dev-kit/ndk';
|
||||
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
@@ -6,85 +6,75 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import { NoteKind_1, NoteKind_1063, NoteThread, Repost } from '@shared/notes';
|
||||
import { NoteKindUnsupport } from '@shared/notes/kinds/unsupport';
|
||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
|
||||
import { useNostr } from '@utils/hooks/useNostr';
|
||||
import { LumeEvent } from '@utils/types';
|
||||
import { DBEvent } from '@utils/types';
|
||||
|
||||
export function NetworkBlock() {
|
||||
export function NetworkWidget() {
|
||||
const { sub } = useNostr();
|
||||
const { db } = useStorage();
|
||||
const { sub, fetchNotes } = useNostr();
|
||||
const { status, data, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
|
||||
queryKey: ['network-widget'],
|
||||
queryFn: async ({ pageParam = 24 }) => {
|
||||
return { data: [], nextCursor: 0 };
|
||||
},
|
||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
});
|
||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['network-widget'],
|
||||
queryFn: async ({ pageParam = 0 }) => {
|
||||
return await db.getAllEvents(20, pageParam);
|
||||
},
|
||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
});
|
||||
|
||||
const parentRef = useRef();
|
||||
const notes = useMemo(
|
||||
() => (data ? data.pages.flatMap((d: { data: LumeEvent[] }) => d.data) : []),
|
||||
const dbEvents = useMemo(
|
||||
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
||||
[data]
|
||||
);
|
||||
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: hasNextPage ? notes.length + 1 : notes.length,
|
||||
const parentRef = useRef<HTMLDivElement>();
|
||||
const virtualizer = useVirtualizer({
|
||||
count: hasNextPage ? dbEvents.length : dbEvents.length,
|
||||
getScrollElement: () => parentRef.current,
|
||||
estimateSize: () => 500,
|
||||
overscan: 2,
|
||||
estimateSize: () => 650,
|
||||
overscan: 4,
|
||||
});
|
||||
const itemsVirtualizer = rowVirtualizer.getVirtualItems();
|
||||
const totalSize = rowVirtualizer.getTotalSize();
|
||||
|
||||
useEffect(() => {
|
||||
const since = Math.floor(Date.now() / 1000);
|
||||
const filter: NDKFilter = {
|
||||
kinds: [1, 6],
|
||||
authors: db.account.network,
|
||||
since: since,
|
||||
};
|
||||
|
||||
sub(filter, (event) => console.log('[network] event received: ', event));
|
||||
}, []);
|
||||
const items = virtualizer.getVirtualItems();
|
||||
|
||||
// render event match event kind
|
||||
const renderItem = useCallback(
|
||||
(index: string | number) => {
|
||||
const note: LumeEvent = notes[index];
|
||||
if (!note) return;
|
||||
switch (note.kind) {
|
||||
const dbEvent: DBEvent = dbEvents[index];
|
||||
if (!dbEvent) return;
|
||||
|
||||
const event: NDKEvent = JSON.parse(dbEvent.event as string);
|
||||
|
||||
switch (event.kind) {
|
||||
case 1: {
|
||||
let root: string;
|
||||
let reply: string;
|
||||
if (note.tags?.[0]?.[0] === 'e' && !note.tags?.[0]?.[3]) {
|
||||
root = note.tags[0][1];
|
||||
} else {
|
||||
root = note.tags.find((el) => el[3] === 'root')?.[1];
|
||||
reply = note.tags.find((el) => el[3] === 'reply')?.[1];
|
||||
}
|
||||
if (root || reply) {
|
||||
if (dbEvent.root_id || dbEvent.reply_id) {
|
||||
return (
|
||||
<div
|
||||
key={(root || reply) + note.id + index}
|
||||
key={(dbEvent.root_id || dbEvent.reply_id) + dbEvent.id + index}
|
||||
data-index={index}
|
||||
ref={rowVirtualizer.measureElement}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<NoteThread event={note} root={root} reply={reply} />
|
||||
<NoteThread
|
||||
event={event}
|
||||
root={dbEvent.root_id}
|
||||
reply={dbEvent.reply_id}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
key={note.id + index}
|
||||
key={dbEvent.id + index}
|
||||
data-index={index}
|
||||
ref={rowVirtualizer.measureElement}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<NoteKind_1 event={note} skipMetadata={false} />
|
||||
<NoteKind_1 event={event} skipMetadata={false} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -92,38 +82,52 @@ export function NetworkBlock() {
|
||||
case 6:
|
||||
return (
|
||||
<div
|
||||
key={note.id + index}
|
||||
key={dbEvent.id + index}
|
||||
data-index={index}
|
||||
ref={rowVirtualizer.measureElement}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<Repost key={note.id} event={note} />
|
||||
<Repost key={dbEvent.id} event={event} />
|
||||
</div>
|
||||
);
|
||||
case 1063:
|
||||
return (
|
||||
<div
|
||||
key={note.id + index}
|
||||
key={dbEvent.id + index}
|
||||
data-index={index}
|
||||
ref={rowVirtualizer.measureElement}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<NoteKind_1063 key={note.id} event={note} />
|
||||
<NoteKind_1063 key={dbEvent.id} event={event} />
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div
|
||||
key={note.id + index}
|
||||
key={dbEvent.id + index}
|
||||
data-index={index}
|
||||
ref={rowVirtualizer.measureElement}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<NoteKindUnsupport key={note.id} event={note} />
|
||||
<NoteKindUnsupport key={dbEvent.id} event={event} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
[notes]
|
||||
[dbEvents]
|
||||
);
|
||||
|
||||
// subscribe for new event
|
||||
// sub will be managed by lru-cache
|
||||
useEffect(() => {
|
||||
if (db.account && db.account.network) {
|
||||
const filter: NDKFilter = {
|
||||
kinds: [1, 6],
|
||||
authors: db.account.network,
|
||||
since: Math.floor(Date.now() / 1000),
|
||||
};
|
||||
|
||||
sub(filter, (event) => console.log('[network] event received: ', event.content));
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative w-[400px] shrink-0 bg-white/10">
|
||||
<TitleBar title="Network" />
|
||||
@@ -134,7 +138,7 @@ export function NetworkBlock() {
|
||||
<NoteSkeleton />
|
||||
</div>
|
||||
</div>
|
||||
) : itemsVirtualizer.length === 0 ? (
|
||||
) : dbEvents.length === 0 ? (
|
||||
<div className="px-3 py-1.5">
|
||||
<div className="rounded-xl bg-white/10 px-3 py-6">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
@@ -154,30 +158,56 @@ export function NetworkBlock() {
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="relative w-full"
|
||||
style={{
|
||||
height: `${totalSize}px`,
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: `${virtualizer.getTotalSize()}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute left-0 top-0 w-full"
|
||||
style={{
|
||||
transform: `translateY(${
|
||||
itemsVirtualizer[0].start - rowVirtualizer.options.scrollMargin
|
||||
}px)`,
|
||||
transform: `translateY(${items[0].start}px)`,
|
||||
}}
|
||||
>
|
||||
{itemsVirtualizer.map((virtualRow) => renderItem(virtualRow.index))}
|
||||
{items.map((item) => renderItem(item.index))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isFetchingNextPage && (
|
||||
<div className="px-3 py-1.5">
|
||||
<div className="mb-20 px-3 py-1.5">
|
||||
<div className="rounded-xl bg-white/10 px-3 py-3">
|
||||
<NoteSkeleton />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="px-3 py-1.5">
|
||||
<button
|
||||
onClick={() => fetchNextPage()}
|
||||
disabled={!hasNextPage || isFetchingNextPage}
|
||||
className="inline-flex h-11 w-full items-center justify-between gap-2 rounded-lg bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none"
|
||||
>
|
||||
{isFetchingNextPage ? (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Loading...</span>
|
||||
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||
</>
|
||||
) : hasNextPage ? (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Load more</span>
|
||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Nothing more to load</span>
|
||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,37 +1,79 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { useRef } from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
import { useNDK } from '@libs/ndk/provider';
|
||||
|
||||
import { NoteKind_1, NoteSkeleton } from '@shared/notes';
|
||||
import { NoteKind_1, NoteSkeleton, Repost } from '@shared/notes';
|
||||
import { NoteKindUnsupport } from '@shared/notes/kinds/unsupport';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
import { UserProfile } from '@shared/userProfile';
|
||||
|
||||
import { nHoursAgo } from '@utils/date';
|
||||
import { LumeEvent, Widget } from '@utils/types';
|
||||
|
||||
export function UserBlock({ params }: { params: Widget }) {
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
import { DBEvent, Widget } from '@utils/types';
|
||||
|
||||
export function UserWidget({ params }: { params: Widget }) {
|
||||
const { ndk } = useNDK();
|
||||
const { status, data } = useQuery(['user-feed', params.content], async () => {
|
||||
const events = await ndk.fetchEvents({
|
||||
kinds: [1],
|
||||
authors: [params.content],
|
||||
since: nHoursAgo(48),
|
||||
});
|
||||
return [...events] as unknown as LumeEvent[];
|
||||
});
|
||||
const { status, data } = useQuery(
|
||||
['user-widget', params.content],
|
||||
async () => {
|
||||
const events = await ndk.fetchEvents({
|
||||
kinds: [1],
|
||||
authors: [params.content],
|
||||
since: nHoursAgo(24),
|
||||
});
|
||||
return [...events] as unknown as DBEvent[];
|
||||
},
|
||||
{ refetchOnMount: false, refetchOnReconnect: false, refetchOnWindowFocus: false }
|
||||
);
|
||||
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
const virtualizer = useVirtualizer({
|
||||
count: data ? data.length : 0,
|
||||
getScrollElement: () => parentRef.current,
|
||||
estimateSize: () => 400,
|
||||
overscan: 2,
|
||||
estimateSize: () => 650,
|
||||
overscan: 4,
|
||||
});
|
||||
const items = virtualizer.getVirtualItems();
|
||||
|
||||
const itemsVirtualizer = rowVirtualizer.getVirtualItems();
|
||||
// render event match event kind
|
||||
const renderItem = useCallback(
|
||||
(index: string | number) => {
|
||||
const event: NDKEvent = data[index];
|
||||
if (!event) return;
|
||||
|
||||
switch (event.kind) {
|
||||
case 1:
|
||||
return (
|
||||
<div key={event.id} data-index={index} ref={virtualizer.measureElement}>
|
||||
<NoteKind_1 event={event} skipMetadata={false} />
|
||||
</div>
|
||||
);
|
||||
case 6:
|
||||
return (
|
||||
<div
|
||||
key={event.id + index}
|
||||
data-index={index}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<Repost key={event.id} event={event} />
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div
|
||||
key={event.id + index}
|
||||
data-index={index}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<NoteKindUnsupport key={event.id} event={event} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
[data]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative w-[400px] shrink-0 bg-white/10">
|
||||
@@ -49,41 +91,31 @@ export function UserBlock({ params }: { params: Widget }) {
|
||||
<NoteSkeleton />
|
||||
</div>
|
||||
</div>
|
||||
) : itemsVirtualizer.length === 0 ? (
|
||||
) : items.length === 0 ? (
|
||||
<div className="px-3 py-1.5">
|
||||
<div className="rounded-xl bg-white/10 px-3 py-6">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<p className="text-center text-sm text-white">
|
||||
No new posts from this user in 48 hours ago
|
||||
No new postr from user in 24 hours ago
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="relative w-full"
|
||||
style={{
|
||||
height: `${rowVirtualizer.getTotalSize()}px`,
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: `${virtualizer.getTotalSize()}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute left-0 top-0 w-full"
|
||||
style={{
|
||||
transform: `translateY(${
|
||||
itemsVirtualizer[0].start - rowVirtualizer.options.scrollMargin
|
||||
}px)`,
|
||||
transform: `translateY(${items[0].start}px)`,
|
||||
}}
|
||||
>
|
||||
{itemsVirtualizer.map((virtualRow) => (
|
||||
<div
|
||||
key={virtualRow.key}
|
||||
data-index={virtualRow.index}
|
||||
ref={rowVirtualizer.measureElement}
|
||||
>
|
||||
<NoteKind_1 event={data[virtualRow.index]} />
|
||||
</div>
|
||||
))}
|
||||
<div className="h-20" />
|
||||
{items.map((item) => renderItem(item.index))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,11 +3,11 @@ import { useCallback, useEffect } from 'react';
|
||||
import { FeedModal } from '@app/space/components/modals/feed';
|
||||
import { HashtagModal } from '@app/space/components/modals/hashtag';
|
||||
import { ImageModal } from '@app/space/components/modals/image';
|
||||
import { FeedBlock } from '@app/space/components/widgets/feed';
|
||||
import { HashtagBlock } from '@app/space/components/widgets/hashtag';
|
||||
import { NetworkBlock } from '@app/space/components/widgets/network';
|
||||
import { FeedWidget } from '@app/space/components/widgets/feed';
|
||||
import { HashtagWidget } from '@app/space/components/widgets/hashtag';
|
||||
import { NetworkWidget } from '@app/space/components/widgets/network';
|
||||
import { ThreadBlock } from '@app/space/components/widgets/thread';
|
||||
import { UserBlock } from '@app/space/components/widgets/user';
|
||||
import { UserWidget } from '@app/space/components/widgets/user';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
@@ -29,15 +29,15 @@ export function SpaceScreen() {
|
||||
(widget: Widget) => {
|
||||
switch (widget.kind) {
|
||||
case 1:
|
||||
return <FeedBlock key={widget.id} params={widget} />;
|
||||
return <FeedWidget key={widget.id} params={widget} />;
|
||||
case 2:
|
||||
return <ThreadBlock key={widget.id} params={widget} />;
|
||||
case 3:
|
||||
return <HashtagBlock key={widget.id} params={widget} />;
|
||||
return <HashtagWidget key={widget.id} params={widget} />;
|
||||
case 5:
|
||||
return <UserBlock key={widget.id} params={widget} />;
|
||||
return <UserWidget key={widget.id} params={widget} />;
|
||||
case 9999:
|
||||
return <NetworkBlock key={widget.id} />;
|
||||
return <NetworkWidget key={widget.id} />;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -52,7 +52,7 @@ export function SpaceScreen() {
|
||||
return (
|
||||
<div className="scrollbar-hide flex h-full w-full flex-nowrap divide-x divide-white/5 overflow-x-auto overflow-y-hidden">
|
||||
{!widgets ? (
|
||||
<div className="flex w-[350px] shrink-0 flex-col">
|
||||
<div className="flex w-[400px] shrink-0 flex-col">
|
||||
<div className="flex w-full flex-1 items-center justify-center p-3">
|
||||
<LoaderIcon className="h-5 w-5 animate-spin text-white/10" />
|
||||
</div>
|
||||
@@ -60,14 +60,14 @@ export function SpaceScreen() {
|
||||
) : (
|
||||
widgets.map((widget) => renderItem(widget))
|
||||
)}
|
||||
<div className="flex w-[350px] shrink-0 flex-col">
|
||||
<div className="flex w-[250px] shrink-0 flex-col">
|
||||
<div className="inline-flex h-full w-full flex-col items-center justify-center gap-1">
|
||||
<FeedModal />
|
||||
<ImageModal />
|
||||
<HashtagModal />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[250px] shrink-0" />
|
||||
<div className="w-[150px] shrink-0" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user