refactor all widgets

This commit is contained in:
2023-11-01 08:07:49 +07:00
parent e7738fb128
commit fd5ecc18a9
37 changed files with 1096 additions and 1271 deletions

View File

@@ -1,59 +1,145 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useCallback, useEffect } from 'react';
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
import { useCallback, useEffect, useMemo } from 'react';
import { VList } from 'virtua';
import { useNDK } from '@libs/ndk/provider';
import { useStorage } from '@libs/storage/provider';
import { LoaderIcon } from '@shared/icons';
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
import { NoteSkeleton } from '@shared/notes';
import { NotifyNote } from '@shared/notification/notifyNote';
import { TitleBar } from '@shared/titleBar';
import { WidgetWrapper } from '@shared/widgets';
import { useActivities } from '@stores/activities';
import { useNostr } from '@utils/hooks/useNostr';
import { Widget } from '@utils/types';
import { sendNativeNotification } from '@utils/notification';
export function NotificationWidget() {
const queryClient = useQueryClient();
export function LocalNotificationWidget({ params }: { params: Widget }) {
const { db } = useStorage();
const { getAllActivities } = useNostr();
const { sub } = useNostr();
const { ndk, relayUrls, fetcher } = useNDK();
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
useInfiniteQuery({
queryKey: ['notification'],
initialPageParam: 0,
queryFn: async ({
signal,
pageParam,
}: {
signal: AbortSignal;
pageParam: number;
}) => {
const events = await fetcher.fetchLatestEvents(
relayUrls,
{
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap],
'#p': [db.account.pubkey],
},
50,
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
);
const [activities, setActivities] = useActivities((state) => [
state.activities,
state.setActivities,
]);
const ndkEvents = events.map((event) => {
return new NDKEvent(ndk, event);
});
const renderEvent = useCallback(
(event: NDKEvent) => {
if (event.pubkey === db.account.pubkey) return null;
return <NotifyNote key={event.id} event={event} />;
},
[activities]
return ndkEvents.sort((a, b) => b.created_at - a.created_at);
},
getNextPageParam: (lastPage) => {
const lastEvent = lastPage.at(-1);
if (!lastEvent) return;
return lastEvent.created_at - 1;
},
enabled: false,
});
const allEvents = useMemo(
() => (data ? data.pages.flatMap((page) => page) : []),
[data]
);
useEffect(() => {
async function getActivities() {
const events = await getAllActivities(48);
setActivities(events);
}
getActivities();
const renderEvent = useCallback((event: NDKEvent) => {
if (event.pubkey === db.account.pubkey) return null;
return <NotifyNote key={event.id} event={event} />;
}, []);
useEffect(() => {
if (status === 'success' && db.account) {
const filter = {
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap],
'#p': [db.account.pubkey],
since: Math.floor(Date.now() / 1000),
};
sub(
filter,
async (event) => {
queryClient.setQueryData(['notification'], (old: NDKEvent[]) => [
event,
...old,
]);
const user = ndk.getUser({ hexpubkey: event.pubkey });
await user.fetchProfile();
switch (event.kind) {
case NDKKind.Text:
return await sendNativeNotification(
`${
user.profile.displayName || user.profile.name
} has replied to your note`
);
case NDKKind.EncryptedDirectMessage: {
if (location.pathname !== '/chats') {
return await sendNativeNotification(
`${
user.profile.displayName || user.profile.name
} has send you a encrypted message`
);
} else {
break;
}
}
case NDKKind.Repost:
return await sendNativeNotification(
`${
user.profile.displayName || user.profile.name
} has reposted to your note`
);
case NDKKind.Reaction:
return await sendNativeNotification(
`${user.profile.displayName || user.profile.name} has reacted ${
event.content
} to your note`
);
case NDKKind.Zap:
return await sendNativeNotification(
`${user.profile.displayName || user.profile.name} has zapped to your note`
);
default:
break;
}
},
false,
'notification'
);
}
}, [status]);
return (
<WidgetWrapper>
<TitleBar id={params.id} title={params.title} />
<div className="flex-1">
{!activities ? (
<div className="flex h-full w-full items-center justify-center">
<div className="flex flex-col items-center gap-1.5">
<LoaderIcon className="h-5 w-5 animate-spin text-neutral-900 dark:text-neutral-100" />
<p className="text-sm font-medium text-neutral-600 dark:text-neutral-400">
Loading...
</p>
<TitleBar id="9998" title="Notification" />
<VList className="flex-1">
{status === 'pending' ? (
<div className="px-3 py-1.5">
<div className="rounded-xl bg-neutral-100 px-3 py-3 dark:bg-neutral-900">
<NoteSkeleton />
</div>
</div>
) : activities.length < 1 ? (
) : allEvents.length < 1 ? (
<div className="flex h-full w-full flex-col items-center justify-center">
<p className="mb-1 text-4xl">🎉</p>
<p className="text-center font-medium text-neutral-600 dark:text-neutral-400">
@@ -61,12 +147,28 @@ export function LocalNotificationWidget({ params }: { params: Widget }) {
</p>
</div>
) : (
<VList className="h-full" style={{ contentVisibility: 'auto' }}>
{activities.map((event) => renderEvent(event))}
<div className="h-14" />
</VList>
allEvents.map((event) => renderEvent(event))
)}
</div>
<div className="flex h-16 items-center justify-center px-3 pb-3">
{hasNextPage ? (
<button
type="button"
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
className="inline-flex h-10 w-max items-center justify-center gap-2 rounded-full bg-blue-500 px-6 font-medium text-white hover:bg-blue-600 focus:outline-none"
>
{isFetchingNextPage ? (
<LoaderIcon className="h-4 w-4 animate-spin" />
) : (
<>
<ArrowRightCircleIcon className="h-5 w-5" />
Load more
</>
)}
</button>
) : null}
</div>
</VList>
</WidgetWrapper>
);
}