add skeleton state

This commit is contained in:
Ren Amamiya 2023-05-06 16:09:16 +07:00
parent 77e56b3dd4
commit 417df1796d
12 changed files with 111 additions and 207 deletions

View File

@ -28,6 +28,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.43.9",
"react-loading-skeleton": "^3.3.1",
"react-markdown": "^8.0.7",
"react-virtuoso": "^4.3.5",
"remark-gfm": "^3.0.1",

View File

@ -43,6 +43,9 @@ dependencies:
react-hook-form:
specifier: ^7.43.9
version: 7.43.9(react@18.2.0)
react-loading-skeleton:
specifier: ^3.3.1
version: 3.3.1(react@18.2.0)
react-markdown:
specifier: ^8.0.7
version: 8.0.7(@types/react@18.2.5)(react@18.2.0)
@ -3944,6 +3947,15 @@ packages:
{ integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== }
dev: false
/react-loading-skeleton@3.3.1(react@18.2.0):
resolution:
{ integrity: sha512-NilqqwMh2v9omN7LteiDloEVpFyMIa0VGqF+ukqp0ncVlYu1sKYbYGX9JEl+GtOT9TKsh04zCHAbavnQ2USldA== }
peerDependencies:
react: '>=16.8.0'
dependencies:
react: 18.2.0
dev: false
/react-markdown@8.0.7(@types/react@18.2.5)(react@18.2.0):
resolution:
{ integrity: sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ== }

View File

@ -1,11 +1,11 @@
import NoteBase from '@lume/app/note/components/base';
import { Placeholder } from '@lume/app/note/components/placeholder';
import { NoteQuoteRepost } from '@lume/app/note/components/quoteRepost';
import { getNotes } from '@lume/utils/storage';
import { useInfiniteQuery } from '@tanstack/react-query';
import { useVirtualizer } from '@tanstack/react-virtual';
import { useEffect, useRef } from 'react';
import Skeleton from 'react-loading-skeleton';
const ITEM_PER_PAGE = 10;
const TIME = Math.floor(Date.now() / 1000);
@ -49,7 +49,7 @@ export function Page() {
<div className="flex h-11 w-full shrink-0 items-center justify-between border-b border-zinc-800"></div>
<div className="relative flex w-full flex-1 flex-col justify-between rounded-lg border border-zinc-800 bg-zinc-900 shadow-input shadow-black/20">
{status === 'loading' ? (
<Placeholder />
<Skeleton count={5} containerClassName="flex-1" />
) : status === 'error' ? (
<div>{error.message}</div>
) : (
@ -88,7 +88,13 @@ export function Page() {
</div>
</div>
)}
<div>{isFetching && !isFetchingNextPage ? <Placeholder /> : null}</div>
<div>
{isFetching && !isFetchingNextPage ? (
<div className="px-3 py-5">
<Skeleton count={3} containerClassName="flex-1" />
</div>
) : null}
</div>
</div>
</div>
);

View File

@ -6,6 +6,7 @@ import { READONLY_RELAYS } from '@lume/stores/constants';
import { noteParser } from '@lume/utils/parser';
import { memo, useContext } from 'react';
import Skeleton from 'react-loading-skeleton';
import useSWRSubscription from 'swr/subscription';
export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
@ -38,44 +39,20 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
return (
<div className="relative pb-5">
{error && <div>failed to load</div>}
{!data ? (
<>
<div className="absolute left-[21px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600"></div>
<div className="animated-pulse relative z-10">
<div className="flex items-start gap-2">
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-zinc-700" />
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-2 text-sm">
<div className="h-4 w-16 rounded bg-zinc-700" />
</div>
</div>
</div>
</div>
<div className="-mt-5 pl-[52px]">
<div className="flex flex-col gap-6">
<div className="h-16 w-full rounded bg-zinc-700" />
<div className="flex items-center gap-8">
<div className="h-4 w-12 rounded bg-zinc-700" />
<div className="h-4 w-12 rounded bg-zinc-700" />
</div>
</div>
</div>
</div>
</>
) : (
<>
<div className="absolute left-[21px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600"></div>
<div className="relative z-10 flex flex-col">
<div className="absolute left-[21px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600"></div>
<div className="relative z-10 flex flex-col">
{data ? (
<>
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-1 pl-[52px]">
<NoteContent content={content} />
<NoteMetadata id={data.id} eventPubkey={data.pubkey} />
</div>
</div>
</>
)}
</>
) : (
<Skeleton baseColor="#27272a" containerClassName="flex-1" />
)}
</div>
</div>
);
});

View File

@ -1,25 +0,0 @@
export const Placeholder = () => {
return (
<div className="relative z-10 flex h-min animate-pulse select-text flex-col px-3 py-5">
<div className="flex items-start gap-2">
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-zinc-700" />
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-2 text-sm">
<div className="h-4 w-16 rounded bg-zinc-700" />
</div>
</div>
</div>
</div>
<div className="-mt-5 pl-[52px]">
<div className="flex flex-col gap-6">
<div className="h-16 w-full rounded bg-zinc-700" />
<div className="flex items-center gap-8">
<div className="h-4 w-12 rounded bg-zinc-700" />
<div className="h-4 w-12 rounded bg-zinc-700" />
</div>
</div>
</div>
</div>
);
};

View File

@ -5,6 +5,7 @@ import { READONLY_RELAYS } from '@lume/stores/constants';
import { noteParser } from '@lume/utils/parser';
import { memo, useContext } from 'react';
import Skeleton from 'react-loading-skeleton';
import useSWRSubscription from 'swr/subscription';
import { navigate } from 'vite-plugin-ssr/client/router';
@ -50,17 +51,18 @@ export const NoteQuote = memo(function NoteQuote({ id }: { id: string }) {
onClick={(e) => openNote(e)}
className="relative mb-2 mt-3 rounded-lg border border-zinc-700 bg-zinc-800 p-2 py-3"
>
{error && <div>failed to load</div>}
{!data ? (
<div className="h-6 w-full animate-pulse select-text flex-col rounded bg-zinc-800"></div>
) : (
<div className="relative z-10 flex flex-col">
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-1 pl-[52px]">
<NoteContent content={content} />
</div>
</div>
)}
<div className="relative z-10 flex flex-col">
{data ? (
<>
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-1 pl-[52px]">
<NoteContent content={content} />
</div>
</>
) : (
<Skeleton baseColor="#27272a" containerClassName="flex-1" />
)}
</div>
</div>
);
});

View File

@ -6,6 +6,7 @@ import { READONLY_RELAYS } from '@lume/stores/constants';
import { noteParser } from '@lume/utils/parser';
import { memo, useContext } from 'react';
import Skeleton from 'react-loading-skeleton';
import useSWRSubscription from 'swr/subscription';
import { navigate } from 'vite-plugin-ssr/client/router';
@ -71,39 +72,18 @@ export const RootNote = memo(function RootNote({ id, fallback }: { id: string; f
}
return (
<>
{error && <div>failed to load</div>}
{!data ? (
<div className="relative z-10 flex h-min animate-pulse select-text flex-col">
<div className="flex items-start gap-2">
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-zinc-700" />
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-2 text-sm">
<div className="h-4 w-16 rounded bg-zinc-700" />
</div>
</div>
</div>
</div>
<div className="-mt-5 pl-[52px]">
<div className="flex flex-col gap-6">
<div className="h-16 w-full rounded bg-zinc-700" />
<div className="flex items-center gap-8">
<div className="h-4 w-12 rounded bg-zinc-700" />
<div className="h-4 w-12 rounded bg-zinc-700" />
</div>
</div>
</div>
</div>
) : (
<div onClick={(e) => openNote(e)} className="relative z-10 flex flex-col">
<div onClick={(e) => openNote(e)} className="relative z-10 flex flex-col">
{data ? (
<>
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-1 pl-[52px]">
<NoteContent content={content} />
<NoteMetadata id={data.id} eventPubkey={data.pubkey} />
</div>
</div>
</>
) : (
<Skeleton baseColor="#27272a" containerClassName="flex-1" />
)}
</>
</div>
);
});

View File

@ -9,45 +9,31 @@ import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
export const NoteDefaultUser = ({ pubkey, time }: { pubkey: string; time: number }) => {
const { user, isError, isLoading } = useProfile(pubkey);
const { user } = useProfile(pubkey);
return (
<div className="group relative z-10 flex h-11 items-center gap-2">
{isError || isLoading ? (
<>
<div className="h-11 w-11 shrink animate-pulse overflow-hidden rounded-md bg-zinc-800"></div>
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex flex-col gap-1">
<div className="flex items-baseline gap-2">
<div className="h-4 w-20 animate-pulse rounded bg-zinc-800"></div>
</div>
<div className="h-2.5 w-14 animate-pulse rounded bg-zinc-800"></div>
</div>
<div className="h-11 w-11 shrink overflow-hidden rounded-md bg-zinc-900">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${user?.picture ? user.picture : DEFAULT_AVATAR}`}
alt={pubkey}
className="h-11 w-11 rounded-md object-cover"
/>
</div>
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex flex-col gap-1">
<div className="flex items-baseline gap-2">
<h5 className="text-sm font-semibold leading-none group-hover:underline">
{user?.display_name || shortenKey(pubkey)}
</h5>
</div>
</>
) : (
<>
<div className="h-11 w-11 shrink overflow-hidden rounded-md bg-zinc-900">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${user?.picture ? user.picture : DEFAULT_AVATAR}`}
alt={pubkey}
className="h-11 w-11 rounded-md object-cover"
/>
<div className="flex items-baseline gap-1.5 text-sm leading-none text-zinc-500">
<span>{user?.nip05 || shortenKey(pubkey)}</span>
<span></span>
<span>{dayjs().to(dayjs.unix(time))}</span>
</div>
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex flex-col gap-1">
<div className="flex items-baseline gap-2">
<h5 className="text-sm font-semibold leading-none group-hover:underline">
{user?.display_name || user?.name || shortenKey(pubkey)}
</h5>
</div>
<span className="text-sm leading-none text-zinc-500">
{user?.nip05 || shortenKey(pubkey)} {dayjs().to(dayjs.unix(time))}
</span>
</div>
</div>
</>
)}
</div>
</div>
</div>
);
};

View File

@ -2,15 +2,7 @@ import { useProfile } from '@lume/utils/hooks/useProfile';
import { shortenKey } from '@lume/utils/shortenKey';
export const NoteMentionUser = ({ pubkey }: { pubkey: string }) => {
const { user, isError, isLoading } = useProfile(pubkey);
const { user } = useProfile(pubkey);
return (
<>
{isError || isLoading ? (
<span className="inline-flex h-4 w-10 animate-pulse rounded bg-zinc-800"></span>
) : (
<span className="cursor-pointer text-fuchsia-500">@{user?.username || user?.name || shortenKey(pubkey)}</span>
)}
</>
);
return <span className="cursor-pointer text-fuchsia-500">@{user?.username || user?.name || shortenKey(pubkey)}</span>;
};

View File

@ -9,39 +9,26 @@ import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
export default function NoteReplyUser({ pubkey, time }: { pubkey: string; time: number }) {
const { user, isError, isLoading } = useProfile(pubkey);
const { user } = useProfile(pubkey);
return (
<div className="group flex items-start gap-3">
{isError || isLoading ? (
<>
<div className="relative h-9 w-9 shrink animate-pulse rounded-md bg-zinc-800"></div>
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex items-baseline gap-2 text-sm">
<div className="h-4 w-20 animate-pulse rounded bg-zinc-800"></div>
</div>
</div>
</>
) : (
<>
<div className="relative h-9 w-9 shrink rounded-md">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${user?.picture ? user.picture : DEFAULT_AVATAR}`}
alt={pubkey}
className="h-9 w-9 rounded-md object-cover"
/>
</div>
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex items-baseline gap-2 text-sm">
<span className="font-semibold leading-none text-zinc-200 group-hover:underline">
{user?.display_name || user?.name || shortenKey(pubkey)}
</span>
<span className="leading-none text-zinc-500">·</span>
<span className="leading-none text-zinc-500">{dayjs().to(dayjs.unix(time))}</span>
</div>
</div>
</>
)}
<div className="relative h-9 w-9 shrink rounded-md">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${user?.picture ? user.picture : DEFAULT_AVATAR}`}
alt={pubkey}
className="h-9 w-9 rounded-md object-cover"
/>
</div>
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex items-baseline gap-2 text-sm">
<span className="font-semibold leading-none text-zinc-200 group-hover:underline">
{user?.display_name || user?.name || shortenKey(pubkey)}
</span>
<span className="leading-none text-zinc-500">·</span>
<span className="leading-none text-zinc-500">{dayjs().to(dayjs.unix(time))}</span>
</div>
</div>
</div>
);
}

View File

@ -9,42 +9,27 @@ import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
export const NoteRepostUser = ({ pubkey, time }: { pubkey: string; time: number }) => {
const { user, isError, isLoading } = useProfile(pubkey);
const { user } = useProfile(pubkey);
return (
<div className="group flex items-center gap-2">
{isError || isLoading ? (
<>
<div className="relative h-11 w-11 shrink animate-pulse overflow-hidden rounded-md bg-zinc-800"></div>
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex flex-col gap-1">
<div className="flex items-baseline gap-2">
<div className="h-4 w-20 animate-pulse rounded bg-zinc-800"></div>
</div>
</div>
</div>
</>
) : (
<>
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-zinc-900">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${user?.picture ? user.picture : DEFAULT_AVATAR}`}
alt={pubkey}
className="h-11 w-11 rounded-md object-cover"
/>
</div>
<div className="flex items-baseline gap-2 text-sm">
<h5 className="font-semibold leading-tight group-hover:underline">
{user?.display_name || user?.name || shortenKey(pubkey)}{' '}
<span className="bg-gradient-to-r from-fuchsia-300 via-orange-100 to-amber-300 bg-clip-text text-transparent">
reposted
</span>
</h5>
<span className="leading-tight text-zinc-500">·</span>
<span className="text-zinc-500">{dayjs().to(dayjs.unix(time))}</span>
</div>
</>
)}
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-zinc-900">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${user?.picture ? user.picture : DEFAULT_AVATAR}`}
alt={pubkey}
className="h-11 w-11 rounded-md object-cover"
/>
</div>
<div className="flex items-baseline gap-2 text-sm">
<h5 className="font-semibold leading-tight group-hover:underline">
{user?.display_name || user?.name || shortenKey(pubkey)}{' '}
<span className="bg-gradient-to-r from-fuchsia-300 via-orange-100 to-amber-300 bg-clip-text text-transparent">
reposted
</span>
</h5>
<span className="leading-tight text-zinc-500">·</span>
<span className="text-zinc-500">{dayjs().to(dayjs.unix(time))}</span>
</div>
</div>
);
};

View File

@ -4,6 +4,7 @@ import { PageContextClient } from '@lume/renderer/types';
import { StrictMode } from 'react';
import { Root, createRoot, hydrateRoot } from 'react-dom/client';
import 'react-loading-skeleton/dist/skeleton.css';
import 'vidstack/styles/defaults.css';
export const clientRouting = true;