79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
/**
|
|
* Renders the "journal" tab of the index page, which lays the feed out as a
|
|
* mosaic of tiles of several sizes.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import { Fragment, useMemo } from "react";
|
|
import { Trans } from "@lingui/react/macro";
|
|
import { ListState } from "../shared/ListState.tsx";
|
|
import { JournalCard } from "../dump/JournalCard.tsx";
|
|
import { useAuth } from "../../hooks/auth/useAuth.ts";
|
|
import { useWS } from "../../hooks/ws/useWS.ts";
|
|
import { FeedFooter } from "./FeedFooter.tsx";
|
|
import { composeJournal } from "../../utils/feed/journalLayout.ts";
|
|
import type { MainFeedProps } from "./DumpFeed.tsx";
|
|
|
|
/**
|
|
* Renders the feed `Index` passes as blocks of {@link JournalCard}, laid out
|
|
* by `composeJournal`, followed by the infinite-scroll sentinel, or the
|
|
* loading, error or empty state. The votes come from `WSContext`.
|
|
*/
|
|
export function JournalFeed(
|
|
{ state, dumps, sentinelRef }: MainFeedProps,
|
|
): React.JSX.Element {
|
|
const { user } = useAuth();
|
|
const { voteCounts, myVotes, castVote, removeVote } = useWS();
|
|
const blocks = useMemo(() => composeJournal(dumps), [dumps]);
|
|
|
|
return (
|
|
<ListState
|
|
state={state}
|
|
isEmpty={() => blocks.length === 0}
|
|
empty={
|
|
<p className="index-status">
|
|
<Trans>No dumps yet. Be the first!</Trans>
|
|
</p>
|
|
}
|
|
>
|
|
{({ data: { hasMore, loadingMore } }) => (
|
|
<>
|
|
{
|
|
/* Each block is its own grid, with a themable <hr> between them.
|
|
Inside a grid the <hr> would be stretched to a 9rem row, while out
|
|
here a theme can shrink it to nothing. */
|
|
}
|
|
<div className="journal-feed">
|
|
{blocks.map((block, index) => (
|
|
<Fragment key={block.key}>
|
|
{index > 0 && <hr className="journal-break" />}
|
|
<ul className="journal-grid">
|
|
{block.entries.map(({ dump, shape }) => (
|
|
<JournalCard
|
|
key={dump.id}
|
|
dump={dump}
|
|
shape={shape}
|
|
voteCount={voteCounts[dump.id] ?? dump.voteCount}
|
|
voted={myVotes.has(dump.id)}
|
|
canVote={!!user}
|
|
castVote={castVote}
|
|
removeVote={removeVote}
|
|
isOwner={user?.id === dump.userId}
|
|
/>
|
|
))}
|
|
</ul>
|
|
</Fragment>
|
|
))}
|
|
</div>
|
|
<FeedFooter
|
|
hasMore={hasMore}
|
|
loadingMore={loadingMore}
|
|
sentinelRef={sentinelRef}
|
|
/>
|
|
</>
|
|
)}
|
|
</ListState>
|
|
);
|
|
}
|