refactor useProfile and useChannelProfile hooks

This commit is contained in:
Ren Amamiya 2023-04-27 10:28:07 +07:00
parent 8cadb7467f
commit 6918660a5c
24 changed files with 137 additions and 195 deletions

View File

@ -45,8 +45,9 @@ export default function AppHeader({ collector }: { collector: boolean }) {
</button>
</div>
<div data-tauri-drag-region className="flex h-full w-full items-center justify-between">
<div className="flex h-full items-center divide-x divide-zinc-900 px-4 pt-px"></div>
<div>{collector && <EventCollector />}</div>
<div className="flex h-full items-center divide-x divide-zinc-900 px-4 pt-px">
{collector && <EventCollector />}
</div>
</div>
</div>
);

View File

@ -1,12 +1,12 @@
import { DEFAULT_AVATAR } from '@stores/constants';
import { useChannelMetadata } from '@utils/hooks/useChannelMetadata';
import { useChannelProfile } from '@utils/hooks/useChannelProfile';
import { Copy } from 'iconoir-react';
import { nip19 } from 'nostr-tools';
export const ChannelProfile = ({ id, pubkey }: { id: string; pubkey: string }) => {
const metadata = useChannelMetadata(id, pubkey);
const metadata = useChannelProfile(id, pubkey);
const noteID = id ? nip19.noteEncode(id) : null;
const copyNoteID = async () => {

View File

@ -1,13 +1,13 @@
import { DEFAULT_AVATAR } from '@stores/constants';
import { usePageContext } from '@utils/hooks/usePageContext';
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
import { twMerge } from 'tailwind-merge';
export const ChatListItem = ({ pubkey }: { pubkey: string }) => {
const profile = useProfileMetadata(pubkey);
const profile = useProfile(pubkey);
const pageContext = usePageContext();
const searchParams: any = pageContext.urlParsed.search;

View File

@ -1,6 +1,6 @@
import { DEFAULT_AVATAR } from '@stores/constants';
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
import dayjs from 'dayjs';
@ -9,7 +9,7 @@ import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
export const MessageUser = ({ pubkey, time }: { pubkey: string; time: number }) => {
const profile = useProfileMetadata(pubkey);
const profile = useProfile(pubkey);
return (
<div className="group flex items-start gap-3">

View File

@ -7,7 +7,7 @@ import { APP_VERSION } from '@stores/constants';
import LumeSymbol from '@assets/icons/Lume';
import { Plus } from 'iconoir-react';
import { useCallback, useContext } from 'react';
import { useContext } from 'react';
let accounts: any = [];
@ -19,17 +19,6 @@ if (typeof window !== 'undefined') {
export default function MultiAccounts() {
const activeAccount: any = useContext(AccountContext);
const renderAccount = useCallback(
(account: { pubkey: string }) => {
if (account.pubkey === activeAccount.pubkey) {
return <ActiveAccount key={account.pubkey} user={account} />;
} else {
return <InactiveAccount key={account.pubkey} user={account} />;
}
},
[activeAccount.pubkey]
);
return (
<div className="flex h-full flex-col items-center justify-between px-2 pb-4 pt-3">
<div className="flex flex-col gap-3">
@ -39,7 +28,13 @@ export default function MultiAccounts() {
>
<LumeSymbol className="h-6 w-auto text-zinc-400 group-hover:text-zinc-200" />
</a>
{accounts.map((account: { pubkey: string }) => renderAccount(account))}
{accounts.map((account: { pubkey: string }) => {
if (account.pubkey === activeAccount.pubkey) {
return <ActiveAccount key={account.pubkey} user={account} />;
} else {
return <InactiveAccount key={account.pubkey} user={account} />;
}
})}
<a
href="/onboarding"
className="group relative flex h-11 w-11 shrink cursor-pointer items-center justify-center rounded-lg border-2 border-dashed border-zinc-600 hover:border-zinc-400"
@ -48,9 +43,7 @@ export default function MultiAccounts() {
</a>
</div>
<div className="flex flex-col gap-0.5 text-center">
<span className="animate-moveBg from-fuchsia-300 via-orange-100 to-amber-300 text-sm font-black uppercase leading-tight text-zinc-600 hover:bg-gradient-to-r hover:bg-clip-text hover:text-transparent">
Lume
</span>
<span className="text-sm font-black uppercase leading-tight text-zinc-600">Lume</span>
<span className="text-xs font-medium text-zinc-700">v{APP_VERSION}</span>
</div>
</div>

View File

@ -1,83 +1,5 @@
import { AccountContext } from '@components/accountProvider';
import { NoteComment } from '@components/note/meta/comment';
import { NoteReaction } from '@components/note/meta/reaction';
import { RelayContext } from '@components/relaysProvider';
import { memo } from 'react';
import { READONLY_RELAYS } from '@stores/constants';
import { memo, useContext, useEffect, useState } from 'react';
export const NoteMetadata = memo(function NoteMetadata({
eventID,
eventPubkey,
eventContent,
eventTime,
}: {
eventID: string;
eventPubkey: string;
eventTime: any;
eventContent: any;
}) {
const pool: any = useContext(RelayContext);
const activeAccount: any = useContext(AccountContext);
const [liked, setLiked] = useState(false);
const [likeCount, setLikeCount] = useState(0);
const [comments, setComments] = useState(0);
useEffect(() => {
const unsubscribe = pool.subscribe(
[
{
'#e': [eventID],
since: parseInt(eventTime),
kinds: [1, 7],
},
],
READONLY_RELAYS,
(event: any) => {
switch (event.kind) {
case 1:
// update state
setComments((comments) => (comments += 1));
// save comment to database
// createCacheCommentNote(event, eventID);
break;
case 7:
if (event.pubkey === activeAccount.pubkey) {
setLiked(true);
}
if (event.content === '🤙' || event.content === '+') {
setLikeCount((likes) => (likes += 1));
}
break;
default:
break;
}
},
100,
undefined,
{
unsubscribeOnEose: true,
logAllEvents: false,
}
);
return () => {
unsubscribe();
};
}, [eventID, eventTime, pool, activeAccount.pubkey]);
return (
<div className="relative z-10 -ml-1 flex items-center gap-8">
<NoteComment
count={comments}
eventID={eventID}
eventPubkey={eventPubkey}
eventContent={eventContent}
eventTime={eventTime}
/>
<NoteReaction count={likeCount} liked={liked} eventID={eventID} eventPubkey={eventPubkey} />
</div>
);
export const NoteMetadata = memo(function NoteMetadata() {
return <div className="relative z-10 -ml-1 flex items-center gap-8"></div>;
});

View File

@ -1,7 +1,14 @@
export const ImagePreview = ({ url, size }: { url: string; size: string }) => {
return (
<div className={`relative h-full ${size === 'large' ? 'w-4/5' : 'w-1/2'} mt-2 rounded-lg border border-zinc-800`}>
<img src={url} alt={url} className="h-auto w-full rounded-lg object-cover" loading="lazy" />
<img
src={url}
alt={url}
className="h-auto w-full rounded-lg object-cover"
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
/>
</div>
);
};

View File

@ -1,12 +1,12 @@
import { DEFAULT_AVATAR } from '@stores/constants';
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
import { memo } from 'react';
export const UserBase = memo(function UserBase({ pubkey }: { pubkey: string }) {
const profile = useProfileMetadata(pubkey);
const profile = useProfile(pubkey);
return (
<div className="flex items-center gap-2">

View File

@ -1,6 +1,6 @@
import { DEFAULT_AVATAR } from '@stores/constants';
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
import dayjs from 'dayjs';
@ -9,7 +9,7 @@ import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
export const UserExtend = ({ pubkey, time }: { pubkey: string; time: number }) => {
const profile = useProfileMetadata(pubkey);
const profile = useProfile(pubkey);
return (
<div className="group flex h-11 items-center gap-2">

View File

@ -1,10 +1,10 @@
import { DEFAULT_AVATAR } from '@stores/constants';
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
export const UserFollow = ({ pubkey }: { pubkey: string }) => {
const profile = useProfileMetadata(pubkey);
const profile = useProfile(pubkey);
return (
<div className="flex items-center gap-2">

View File

@ -1,6 +1,6 @@
import { DEFAULT_AVATAR } from '@stores/constants';
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
import dayjs from 'dayjs';
@ -10,7 +10,7 @@ import { MoreHoriz } from 'iconoir-react';
dayjs.extend(relativeTime);
export const UserLarge = ({ pubkey, time }: { pubkey: string; time: number }) => {
const profile = useProfileMetadata(pubkey);
const profile = useProfile(pubkey);
return (
<div className="flex items-center gap-2">

View File

@ -1,8 +1,8 @@
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
export const UserMention = ({ pubkey }: { pubkey: string }) => {
const profile = useProfileMetadata(pubkey);
const profile = useProfile(pubkey);
return (
<span className="cursor-pointer text-fuchsia-500">@{profile?.name || profile?.username || shortenKey(pubkey)}</span>
);

View File

@ -1,10 +1,10 @@
import { DEFAULT_AVATAR } from '@stores/constants';
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
export const UserMini = ({ pubkey }: { pubkey: string }) => {
const profile = useProfileMetadata(pubkey);
const profile = useProfile(pubkey);
return (
<div className="group flex items-start gap-1">

View File

@ -1,12 +1,12 @@
import { DEFAULT_AVATAR } from '@stores/constants';
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
import { useState } from 'react';
export const UserMuted = ({ data }: { data: any }) => {
const profile = useProfileMetadata(data.content);
const profile = useProfile(data.content);
const [status, setStatus] = useState(data.status);
const unmute = async () => {

View File

@ -1,6 +1,6 @@
import { DEFAULT_AVATAR } from '@stores/constants';
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
import dayjs from 'dayjs';
@ -9,7 +9,7 @@ import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
export const UserQuoteRepost = ({ pubkey, time }: { pubkey: string; time: number }) => {
const profile = useProfileMetadata(pubkey);
const profile = useProfile(pubkey);
return (
<div className="group flex items-center gap-2">

View File

@ -76,7 +76,7 @@ export function Page() {
) : status === 'error' ? (
<div>{error.message}</div>
) : (
<div ref={parentRef} className="h-full w-full overflow-y-auto" style={{ contain: 'strict' }}>
<div ref={parentRef} className="scrollbar-hide h-full w-full overflow-y-auto" style={{ contain: 'strict' }}>
<FormBase />
<div
className="relative w-full"

View File

@ -5,7 +5,7 @@ import { UserBase } from '@components/user/base';
import { WRITEONLY_RELAYS } from '@stores/constants';
import { usePageContext } from '@utils/hooks/usePageContext';
import { fetchProfileMetadata } from '@utils/hooks/useProfileMetadata';
import { fetchProfileMetadata } from '@utils/hooks/useProfile';
import { createPleb, updateAccount } from '@utils/storage';
import { arrayToNIP02 } from '@utils/transform';

View File

@ -4,7 +4,7 @@ import { RelayContext } from '@components/relaysProvider';
import { DEFAULT_AVATAR, READONLY_RELAYS } from '@stores/constants';
import { usePageContext } from '@utils/hooks/usePageContext';
import { fetchProfileMetadata } from '@utils/hooks/useProfileMetadata';
import { fetchProfileMetadata } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
import { createAccount, createPleb, updateAccount } from '@utils/storage';
import { nip02ToArray } from '@utils/transform';

View File

@ -2,6 +2,7 @@ import '@renderer/index.css';
import { Shell } from '@renderer/shell';
import { PageContextClient } from '@renderer/types';
import { StrictMode } from 'react';
import { Root, createRoot, hydrateRoot } from 'react-dom/client';
import 'vidstack/styles/defaults.css';
@ -14,9 +15,11 @@ export async function render(pageContext: PageContextClient) {
if (!Page) throw new Error('Client-side render() hook expects pageContext.Page to be defined');
const page = (
<Shell pageContext={pageContext}>
<Page {...pageProps} />
</Shell>
<StrictMode>
<Shell pageContext={pageContext}>
<Page {...pageProps} />
</Shell>
</StrictMode>
);
const container = document.getElementById('app');

View File

@ -1,6 +1,7 @@
import { Shell } from '@renderer/shell';
import { PageContextServer } from '@renderer/types';
import { StrictMode } from 'react';
import ReactDOMServer from 'react-dom/server';
import { dangerouslySkipEscape, escapeInject } from 'vite-plugin-ssr/server';
@ -18,9 +19,11 @@ export function render(pageContext: PageContextServer) {
if (!Page) throw new Error('My render() hook expects pageContext.Page to be defined');
pageHtml = ReactDOMServer.renderToString(
<Shell pageContext={pageContext}>
<Page {...pageProps} />
</Shell>
<StrictMode>
<Shell pageContext={pageContext}>
<Page {...pageProps} />
</Shell>
</StrictMode>
);
}

View File

@ -6,20 +6,17 @@ import { PageContextProvider } from '@utils/hooks/usePageContext';
import { PageContext } from '@renderer/types';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { StrictMode } from 'react';
const queryClient = new QueryClient();
export function Shell({ children, pageContext }: { children: React.ReactNode; pageContext: PageContext }) {
return (
<StrictMode>
<PageContextProvider pageContext={pageContext}>
<RelayProvider>
<AccountProvider>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</AccountProvider>
</RelayProvider>
</PageContextProvider>
</StrictMode>
<PageContextProvider pageContext={pageContext}>
<RelayProvider>
<AccountProvider>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</AccountProvider>
</RelayProvider>
</PageContextProvider>
);
}

View File

@ -0,0 +1,56 @@
import { RelayContext } from '@components/relaysProvider';
import { READONLY_RELAYS } from '@stores/constants';
import { useContext } from 'react';
import useSWRSubscription from 'swr/subscription';
export const useChannelProfile = (id: string, channelPubkey: string) => {
const pool: any = useContext(RelayContext);
const { data } = useSWRSubscription(
id
? [
{
kinds: [41],
'#e': [id],
},
{
ids: [id],
kinds: [40],
},
]
: null,
(key, { next }) => {
const unsubscribe = pool.subscribe(
key,
READONLY_RELAYS,
(event: { kind: number; pubkey: string; content: string }) => {
switch (event.kind) {
case 40:
next(null, JSON.parse(event.content));
break;
case 41:
if (event.pubkey === channelPubkey) {
next(null, JSON.parse(event.content));
}
default:
break;
}
},
undefined,
undefined,
{
unsubscribeOnEose: true,
logAllEvents: false,
}
);
return () => {
unsubscribe();
};
}
);
return data;
};

View File

@ -0,0 +1,14 @@
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then((r: any) => r.json());
export const useProfile = (pubkey: string) => {
const { data, error } = useSWR(`https://rbr.bio/${pubkey}/metadata.json`, fetcher);
if (error) {
return error;
}
if (data) {
return JSON.parse(data.content);
}
return null;
};

View File

@ -1,54 +0,0 @@
import { createPleb, getPleb } from '@utils/storage';
import { fetch } from '@tauri-apps/api/http';
import { useCallback, useEffect, useState } from 'react';
export const fetchProfileMetadata = async (pubkey: string) => {
const result = await fetch(`https://rbr.bio/${pubkey}/metadata.json`, {
method: 'GET',
timeout: 5,
});
return await result.data;
};
export const useProfileMetadata = (pubkey: string) => {
const [profile, setProfile] = useState(null);
const getProfileFromDB = useCallback(async (pubkey: string) => {
return await getPleb(pubkey);
}, []);
const insertPlebToDB = useCallback(async (pubkey: string, metadata: string) => {
return createPleb(pubkey, metadata);
}, []);
useEffect(() => {
let ignore = false;
if (!ignore) {
getProfileFromDB(pubkey)
.then((res: any) => {
if (res) {
// update state
setProfile(JSON.parse(res.metadata));
} else {
fetchProfileMetadata(pubkey).then((res: any) => {
if (res) {
// update state
setProfile(JSON.parse(res.content));
// insert to db
insertPlebToDB(pubkey, res.content);
}
});
}
})
.catch(console.error);
}
return () => {
ignore = true;
};
}, [getProfileFromDB, insertPlebToDB, pubkey]);
return profile;
};