perf(app): optimize subs loading as much as possible

This commit is contained in:
plebeius.eth
2024-03-27 15:10:58 +01:00
parent 38b3793dc9
commit 311896d122
7 changed files with 163 additions and 101 deletions
+27 -16
View File
@@ -1,33 +1,39 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { Outlet, Route, Routes, useLocation, useParams } from 'react-router-dom'; import { Outlet, Route, Routes, useLocation, useParams } from 'react-router-dom';
import { isHomeView } from './lib/utils/view-utils'; import { isHomeView } from './lib/utils/view-utils';
import { useSubplebbit } from '@plebbit/plebbit-react-hooks'; import { Subplebbit as SubplebbitType, useSubplebbits } from '@plebbit/plebbit-react-hooks';
import styles from './app.module.css'; import { useDefaultSubplebbitAddresses } from './hooks/use-default-subplebbits';
import useTheme from './hooks/use-theme'; import useTheme from './hooks/use-theme';
import styles from './app.module.css';
import Home from './views/home'; import Home from './views/home';
import Settings from './views/settings'; import Settings from './views/settings';
import Subplebbit from './views/subplebbit'; import Subplebbit from './views/subplebbit';
import BoardNav from './components/board-nav'; import BoardNav from './components/board-nav';
import BoardBanner from './components/board-banner'; import BoardBanner from './components/board-banner';
import { DesktopBoardButtons } from './components/board-buttons';
import { MobileBoardButtons } from './components/board-buttons';
import BoardStats from './components/board-stats'; import BoardStats from './components/board-stats';
import PostForm from './components/post-form'; import PostForm from './components/post-form';
import { MobileBoardButtons } from './components/board-buttons';
import { DesktopBoardButtons } from './components/board-buttons';
const BoardLayout = () => { interface BoardLayoutProps {
subplebbits: (SubplebbitType | undefined)[];
}
const BoardLayout = ({ subplebbits }: BoardLayoutProps) => {
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>(); const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
const subplebbit = useSubplebbit({ subplebbitAddress }); const subplebbit = subplebbits.find((s) => s?.address === subplebbitAddress);
const { address, createdAt, title } = subplebbit || {};
return ( return (
<> <>
{subplebbitAddress && ( {address && (
<> <>
<BoardNav address={subplebbitAddress} /> <BoardNav address={address} subplebbits={subplebbits} />
<BoardBanner title={subplebbit.title} address={subplebbitAddress} /> <BoardBanner title={title} address={address} />
<MobileBoardButtons address={subplebbitAddress} /> <MobileBoardButtons address={address} />
<PostForm address={subplebbitAddress} /> <PostForm address={address} />
<BoardStats address={subplebbitAddress} createdAt={subplebbit.createdAt} /> <BoardStats address={address} createdAt={createdAt} />
<DesktopBoardButtons address={subplebbitAddress} /> <DesktopBoardButtons address={address} />
</> </>
)} )}
<Outlet /> <Outlet />
@@ -40,6 +46,11 @@ const App = () => {
const location = useLocation(); const location = useLocation();
const isInHomeView = isHomeView(location.pathname); const isInHomeView = isHomeView(location.pathname);
const subplebbitAddresses = useDefaultSubplebbitAddresses();
const { subplebbits } = useSubplebbits({ subplebbitAddresses });
console.log(subplebbits);
useEffect(() => { useEffect(() => {
document.body.classList.forEach((className) => document.body.classList.remove(className)); document.body.classList.forEach((className) => document.body.classList.remove(className));
document.body.classList.add(theme); document.body.classList.add(theme);
@@ -55,9 +66,9 @@ const App = () => {
return ( return (
<div className={`${styles.app} ${isInHomeView ? 'yotsuba' : theme}`}> <div className={`${styles.app} ${isInHomeView ? 'yotsuba' : theme}`}>
<Routes> <Routes>
<Route path='/' element={<Home />} /> <Route path='/' element={<Home subplebbits={subplebbits} />} />
<Route element={<BoardLayout />}> <Route element={<BoardLayout subplebbits={subplebbits} />}>
<Route path='/p/:subplebbitAddress' element={<Subplebbit />} /> <Route path='/p/:subplebbitAddress' element={<Subplebbit subplebbits={subplebbits} />} />
</Route> </Route>
<Route path='/settings' element={<Settings />} /> <Route path='/settings' element={<Settings />} />
</Routes> </Routes>
+17 -16
View File
@@ -1,11 +1,11 @@
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useDefaultSubplebbits from '../../hooks/use-default-subplebbits'; import { Subplebbit } from '@plebbit/plebbit-react-hooks';
import styles from './board-nav.module.css'; import styles from './board-nav.module.css';
interface BoardNavProps { interface BoardNavProps {
address?: string | undefined; address?: string | undefined;
subplebbits?: any; subplebbits: (Subplebbit | undefined)[];
currentSubplebbit?: string | undefined; currentSubplebbit?: string | undefined;
} }
@@ -16,15 +16,18 @@ const BoardNavDesktop = ({ subplebbits }: BoardNavProps) => {
<div className={styles.boardNavDesktop}> <div className={styles.boardNavDesktop}>
<span className={styles.boardList}> <span className={styles.boardList}>
[ [
{subplebbits.map((subplebbit: any, index: number) => ( {subplebbits.map((subplebbit: any, index: number) => {
<span key={subplebbit.address}> const { address, title } = subplebbit;
{index === 0 ? null : ' '} return (
<Link to={`/p/${subplebbit.address}`} title={subplebbit.title || ''}> <span key={address}>
{subplebbit.address.includes('.') ? subplebbit.address : subplebbit.title || subplebbit.address.slice(0, 10).concat('...')} {index === 0 ? null : ' '}
</Link> <Link to={`/p/${address}`} title={title || ''}>
{index !== subplebbits.length - 1 ? ' /' : null} {address.includes('.') ? address : title || address.slice(0, 10).concat('...')}
</span> </Link>
))} {index !== subplebbits.length - 1 ? ' /' : null}
</span>
);
})}
] ]
</span> </span>
<span className={styles.navTopRight}> <span className={styles.navTopRight}>
@@ -72,13 +75,11 @@ const BoardNavMobile = ({ subplebbits, currentSubplebbit }: BoardNavProps) => {
); );
}; };
const BoardNav = ({ address }: BoardNavProps) => { const BoardNav = ({ address, subplebbits }: BoardNavProps) => {
const defaultSubplebbits = useDefaultSubplebbits();
return ( return (
<> <>
<BoardNavDesktop subplebbits={defaultSubplebbits} /> <BoardNavDesktop subplebbits={subplebbits} />
<BoardNavMobile subplebbits={defaultSubplebbits} currentSubplebbit={address} /> <BoardNavMobile subplebbits={subplebbits} currentSubplebbit={address} />
</> </>
); );
}; };
-1
View File
@@ -4,7 +4,6 @@ import ReactMarkdown from 'react-markdown';
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'; import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
import supersub from 'remark-supersub'; import supersub from 'remark-supersub';
import { visit } from 'unist-util-visit';
interface MarkdownProps { interface MarkdownProps {
content: string; content: string;
+18 -2
View File
@@ -2,11 +2,11 @@
clear: both; clear: both;
} }
.hrWrapper hr { .postDesktop .hrWrapper hr {
margin: 0; /* margin bugs Virtuoso scrolling */ margin: 0; /* margin bugs Virtuoso scrolling */
} }
.hrWrapper { .postDesktop .hrWrapper {
padding-top: 0.5em; /* instead of using margin, wrap with padding */ padding-top: 0.5em; /* instead of using margin, wrap with padding */
padding-bottom: 0.5em; padding-bottom: 0.5em;
} }
@@ -89,8 +89,24 @@
padding: 1em 40px; padding: 1em 40px;
} }
.postMobile hr {
padding-bottom: 30px;
}
.postMobile .thread {
border-top: none;
margin: 0;
clear: both;
}
@media (max-width: 640px) { @media (max-width: 640px) {
.postDesktop { .postDesktop {
display: none; display: none;
} }
}
@media (min-width: 641px) {
.postMobile {
display: none;
}
} }
+73 -52
View File
@@ -7,7 +7,7 @@ import { getLinkMediaInfoMemoized } from '../../lib/utils/media-utils';
import { getFormattedDate } from '../../lib/utils/time-utils'; import { getFormattedDate } from '../../lib/utils/time-utils';
import Markdown from '../markdown'; import Markdown from '../markdown';
const Post = ({ post }: Comment) => { const PostDesktop = ({ post }: Comment) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { author, cid, content, link, locked, pinned, postCid, shortCid, subplebbitAddress, timestamp, title } = post || {}; const { author, cid, content, link, locked, pinned, postCid, shortCid, subplebbitAddress, timestamp, title } = post || {};
const { displayName, shortAddress } = author || {}; const { displayName, shortAddress } = author || {};
@@ -15,64 +15,85 @@ const Post = ({ post }: Comment) => {
const linkMediaInfo = getLinkMediaInfoMemoized(link); const linkMediaInfo = getLinkMediaInfoMemoized(link);
return ( return (
<div className={styles.thread}> <div className={styles.postDesktop}>
<div className={styles.postContainer}> <div className={styles.hrWrapper}>
<div className={styles.postDesktop}> <hr />
<div className={styles.hrWrapper}> </div>
<hr /> {linkMediaInfo?.url && (
</div> <div className={styles.link}>
{linkMediaInfo?.url && ( {t('link')}:{' '}
<div className={styles.link}> <a href={linkMediaInfo?.url} target='_blank' rel='noopener noreferrer'>
{t('link')}:{' '} {linkMediaInfo.url.length > 30 ? linkMediaInfo?.url.slice(0, 30) + '...' : linkMediaInfo?.url}
<a href={linkMediaInfo?.url} target='_blank' rel='noopener noreferrer'> </a>{' '}
{linkMediaInfo.url.length > 30 ? linkMediaInfo?.url.slice(0, 30) + '...' : linkMediaInfo?.url} ({linkMediaInfo?.type})
</a>{' '} </div>
({linkMediaInfo?.type}) )}
</div> <div className={styles.postInfo}>
)} {title && <span className={styles.subject}>{title.length > 75 ? title.slice(0, 75) + '...' : title}</span>}{' '}
<div className={styles.postInfo}> <span className={styles.nameBlock}>
{title && <span className={styles.subject}>{title.length > 75 ? title.slice(0, 75) + '...' : title}</span>}{' '} <span className={styles.name}>{displayName || 'Anonymous'} </span>
<span className={styles.nameBlock}> <span className={styles.userAddress}>(u/{shortAddress}) </span>
<span className={styles.name}>{displayName || 'Anonymous'} </span> </span>
<span className={styles.userAddress}>(u/{shortAddress}) </span> <span className={styles.dateTime}>{getFormattedDate(timestamp)} </span>
<span className={styles.postNum}>
<span className={styles.postNumLink}>
<Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')}>
c/
</Link>
<span className={styles.replyToPost} title={t('reply_to_post')}>
{shortCid}
</span> </span>
<span className={styles.dateTime}>{getFormattedDate(timestamp)} </span> </span>
<span className={styles.postNum}> {pinned && (
<span className={styles.postNumLink}> <span className={styles.stickyIconWrapper}>
<Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')}> <img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
c/
</Link>
<span className={styles.replyToPost} title={t('reply_to_post')}>
{shortCid}
</span>
</span>
{pinned && (
<span className={styles.stickyIconWrapper}>
<img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
</span>
)}
{locked && (
<span className={styles.closedIconWrapper}>
<img src='assets/icons/closed.gif' alt='' className={styles.closedIcon} title={t('closed')} />
</span>
)}
<span className={styles.replyButton}>
[<Link to={`/p/${subplebbitAddress}/c/${postCid}`}>{t('reply')}</Link>]
</span>
</span> </span>
<span className={styles.postMenuBtn}></span>
</div>
{content && (
<div className={styles.postMessage}>
<blockquote>
<Markdown content={content} />
</blockquote>
</div>
)} )}
{locked && (
<span className={styles.closedIconWrapper}>
<img src='assets/icons/closed.gif' alt='' className={styles.closedIcon} title={t('closed')} />
</span>
)}
<span className={styles.replyButton}>
[<Link to={`/p/${subplebbitAddress}/c/${postCid}`}>{t('reply')}</Link>]
</span>
</span>
<span className={styles.postMenuBtn}></span>
</div>
{content && (
<div className={styles.postMessage}>
<blockquote>
<Markdown content={content} />
</blockquote>
</div>
)}
</div>
);
};
const PostMobile = ({ post }: Comment) => {
return (
<div className={styles.postMobile}>
<hr />
<div className={styles.thread}>
<div className={styles.postContainer}>
<div className={styles.postOp}></div>
<div className={styles.postLinkMobile}></div>
</div> </div>
</div> </div>
</div> </div>
); );
}; };
const Post = ({ post }: Comment) => {
return (
<div className={styles.thread}>
<div className={styles.postContainer}>
<PostDesktop post={post} />
<PostMobile post={post} />
</div>
</div>
);
};
export default Post; export default Post;
+18 -10
View File
@@ -2,9 +2,13 @@ import { useRef } from 'react';
import styles from './home.module.css'; import styles from './home.module.css';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits'; import { Subplebbit } from '@plebbit/plebbit-react-hooks';
import packageJson from '../../../package.json'; import packageJson from '../../../package.json';
interface HomeProps {
subplebbits: (Subplebbit | undefined)[];
}
const isValidAddress = (address: string): boolean => { const isValidAddress = (address: string): boolean => {
if (address.includes('/') || address.includes('\\') || address.includes(' ')) { if (address.includes('/') || address.includes('\\') || address.includes(' ')) {
return false; return false;
@@ -65,9 +69,8 @@ const InfoBox = () => {
); );
}; };
const Boards = () => { const Boards = ({ subplebbits }: HomeProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const defaultSubplebbitAddresses = useDefaultSubplebbitAddresses();
return ( return (
<div className={styles.box}> <div className={styles.box}>
@@ -79,11 +82,16 @@ const Boards = () => {
<div className={styles.column}> <div className={styles.column}>
<h3>Default SFW</h3> <h3>Default SFW</h3>
<div className={styles.list}> <div className={styles.list}>
{defaultSubplebbitAddresses.map((address) => ( {subplebbits
<div className={styles.subplebbit} key={address}> .filter((subplebbit): subplebbit is Subplebbit => subplebbit !== undefined)
<Link to={`/p/${address}`}>{address}</Link> .map((subplebbit) => {
</div> const address = subplebbit.address;
))} return (
<div className={styles.subplebbit} key={address}>
<Link to={`/p/${address}`}>{address}</Link>
</div>
);
})}
</div> </div>
</div> </div>
<div className={styles.column}> <div className={styles.column}>
@@ -193,7 +201,7 @@ const Footer = () => {
); );
}; };
const Home = () => { const Home = ({ subplebbits }: HomeProps) => {
return ( return (
<div className={styles.content}> <div className={styles.content}>
<Link to='/'> <Link to='/'>
@@ -203,7 +211,7 @@ const Home = () => {
</Link> </Link>
<SearchBar /> <SearchBar />
<InfoBox /> <InfoBox />
<Boards /> <Boards subplebbits={subplebbits} />
<PopularThreads /> <PopularThreads />
<Stats /> <Stats />
<Footer /> <Footer />
+10 -4
View File
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useRef } from 'react'; import { useEffect, useMemo, useRef } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { useFeed, useSubplebbit } from '@plebbit/plebbit-react-hooks'; import { Subplebbit as SubplebbitType, useFeed } from '@plebbit/plebbit-react-hooks';
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso'; import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import styles from './subplebbit.module.css'; import styles from './subplebbit.module.css';
@@ -9,12 +9,18 @@ import LoadingEllipsis from '../../components/loading-ellipsis';
import Post from '../../components/post'; import Post from '../../components/post';
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {}; const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
const Subplebbit = () => { interface SubplebbitProps {
subplebbits: (SubplebbitType | undefined)[];
}
const Subplebbit = ({ subplebbits }: SubplebbitProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>(); const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
const subplebbit = subplebbits.find((s) => s?.address === subplebbitAddress);
const subplebbitAddresses = useMemo(() => [subplebbitAddress], [subplebbitAddress]) as string[]; const subplebbitAddresses = useMemo(() => [subplebbitAddress], [subplebbitAddress]) as string[];
const subplebbit = useSubplebbit({ subplebbitAddress }); const { shortAddress, state, title } = subplebbit || {};
const { createdAt, description, roles, rules, shortAddress, state, title, updatedAt, settings } = subplebbit || {};
const sortType = 'active'; const sortType = 'active';
const { feed, hasMore, loadMore } = useFeed({ subplebbitAddresses, sortType }); const { feed, hasMore, loadMore } = useFeed({ subplebbitAddresses, sortType });
const loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading'); const loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading');