improve community explore view

This commit is contained in:
hzrd149 2023-10-20 11:02:33 -05:00
parent 65d2858d14
commit 1e93c7bc23
3 changed files with 96 additions and 64 deletions

View File

@ -1,9 +1,8 @@
import { memo, useRef } from "react";
import { Kind, nip19 } from "nostr-tools";
import { nip19 } from "nostr-tools";
import { Link as RouterLink } from "react-router-dom";
import {
Card,
CardBody,
CardFooter,
CardHeader,
CardProps,
@ -20,14 +19,13 @@ import { NostrEvent } from "../../../types/nostr-event";
import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
import { getEventUID } from "../../../helpers/nostr/events";
import { getCommunityImage, getCommunityName } from "../../../helpers/nostr/communities";
import CommunityDescription from "./community-description";
import useCountCommunityPosts from "../hooks/use-count-community-post";
import UserAvatarLink from "../../../components/user-avatar-link";
import { UserLink } from "../../../components/user-link";
import useCountCommunityMembers from "../../../hooks/use-count-community-members";
import { readablizeSats } from "../../../helpers/bolt11";
import { CommunityIcon } from "../../../components/icons";
import User01 from "../../../components/icons/user-01";
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
import { AddressPointer } from "nostr-tools/lib/types/nip19";
function CommunityCard({ community, ...props }: Omit<CardProps, "children"> & { community: NostrEvent }) {
const ref = useRef<HTMLDivElement | null>(null);
@ -81,4 +79,10 @@ function CommunityCard({ community, ...props }: Omit<CardProps, "children"> & {
);
}
export function PointerCommunityCard({ pointer, ...props }: Omit<CardProps, "children"> & { pointer: AddressPointer }) {
const community = useReplaceableEvent(pointer);
if (!community) return <span>Loading {pointer.identifier}</span>;
return <CommunityCard community={community} {...props} />;
}
export default memo(CommunityCard);

View File

@ -1,69 +1,107 @@
import { useCallback, useMemo } from "react";
import { Flex, SimpleGrid } from "@chakra-ui/react";
import { useMemo } from "react";
import { AvatarGroup, Button, Flex, Switch, useDisclosure } from "@chakra-ui/react";
import PeopleListProvider, { usePeopleListContext } from "../../providers/people-list-provider";
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import IntersectionObserverProvider from "../../providers/intersection-observer";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
import useSubject from "../../hooks/use-subject";
import CommunityCard from "./components/community-card";
import { getEventUID } from "../../helpers/nostr/events";
import { PointerCommunityCard } from "./components/community-card";
import VerticalPageLayout from "../../components/vertical-page-layout";
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
import RelaySelectionProvider, { useRelaySelectionContext } from "../../providers/relay-selection-provider";
import { COMMUNITY_DEFINITION_KIND, validateCommunity } from "../../helpers/nostr/communities";
import { NostrEvent } from "../../types/nostr-event";
import { NostrQuery } from "../../types/nostr-query";
import { COMMUNITY_DEFINITION_KIND, SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER } from "../../helpers/nostr/communities";
import { ErrorBoundary } from "../../components/error-boundary";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
import useSubjects from "../../hooks/use-subjects";
import replaceableEventLoaderService from "../../services/replaceable-event-requester";
import { NOTE_LIST_KIND, getCoordinatesFromList } from "../../helpers/nostr/lists";
import { useNavigate } from "react-router-dom";
import { ChevronLeftIcon } from "../../components/icons";
import { parseCoordinate } from "../../helpers/nostr/events";
import UserAvatarLink from "../../components/user-avatar-link";
import { AddressPointer } from "nostr-tools/lib/types/nip19";
export function useUsersJoinedCommunitiesLists(pubkeys: string[], additionalRelays: string[] = []) {
const readRelays = useReadRelayUrls(additionalRelays);
const muteListSubjects = useMemo(() => {
return pubkeys.map((pubkey) =>
replaceableEventLoaderService.requestEvent(
readRelays,
NOTE_LIST_KIND,
pubkey,
SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER,
),
);
}, [pubkeys]);
return useSubjects(muteListSubjects);
}
function CommunityCardWithMembers({ pointer, pubkeys }: { pointer: AddressPointer; pubkeys: string[] }) {
return (
<ErrorBoundary>
<Flex direction="column" gap="2">
<AvatarGroup size="md">
{pubkeys.map((pubkey) => (
<UserAvatarLink key={pubkey} pubkey={pubkey} />
))}
</AvatarGroup>
<PointerCommunityCard pointer={pointer} maxW="xl" />
</Flex>
</ErrorBoundary>
);
}
function CommunitiesExplorePage() {
const { filter, listId } = usePeopleListContext();
const { relays } = useRelaySelectionContext();
const navigate = useNavigate();
const { people } = usePeopleListContext();
const showMore = useDisclosure();
const eventFilter = useCallback((event: NostrEvent) => {
return validateCommunity(event);
}, []);
const communitiesLists = useUsersJoinedCommunitiesLists(people?.map((p) => p.pubkey) ?? []);
const query = useMemo(() => {
const base: NostrQuery = { kinds: [COMMUNITY_DEFINITION_KIND] };
if (filter?.authors) {
base.authors = filter.authors;
base["#p"] = filter.authors;
const communityPointers = useMemo(() => {
const dir = new Map<string, { pointer: AddressPointer; pubkeys: string[] }>();
for (const list of communitiesLists) {
for (const { coordinate } of getCoordinatesFromList(list)) {
const pointer = parseCoordinate(coordinate, true);
if (!pointer) continue;
if (pointer.kind === COMMUNITY_DEFINITION_KIND) {
if (dir.has(coordinate)) {
dir.get(coordinate)?.pubkeys.push(list.pubkey);
} else dir.set(coordinate, { pointer, pubkeys: [list.pubkey] });
}
}
}
return base;
}, [filter]);
return dir;
}, [communitiesLists]);
const timeline = useTimelineLoader(`${listId}-browse-communities`, relays, query, { enabled: !!filter, eventFilter });
const communities = useSubject(timeline.timeline);
const callback = useTimelineCurserIntersectionCallback(timeline);
const sorted = Array.from(communityPointers.values()).sort((a, b) => b.pubkeys.length - a.pubkeys.length);
return (
<IntersectionObserverProvider callback={callback}>
<VerticalPageLayout>
<Flex gap="2" alignItems="center" wrap="wrap">
<PeopleListSelection />
<RelaySelectionButton />
</Flex>
<SimpleGrid spacing="2" columns={{ base: 1, lg: 2 }}>
{communities.map((event) => (
<ErrorBoundary>
<CommunityCard key={getEventUID(event)} community={event} />
</ErrorBoundary>
<VerticalPageLayout>
<Flex gap="2" alignItems="center" wrap="wrap">
<Button onClick={() => navigate(-1)} leftIcon={<ChevronLeftIcon />}>
Back
</Button>
<PeopleListSelection hideGlobalOption />
<Switch onChange={showMore.onToggle} checked={showMore.isOpen}>
Show More
</Switch>
</Flex>
<Flex gap="4" direction="column">
{sorted
.filter((c) => (showMore ? c.pubkeys.length > 1 : true))
.map(({ pointer, pubkeys }) => (
<CommunityCardWithMembers
key={pointer.kind + pointer.pubkey + pointer.identifier}
pointer={pointer}
pubkeys={pubkeys}
/>
))}
</SimpleGrid>
</VerticalPageLayout>
</IntersectionObserverProvider>
</Flex>
</VerticalPageLayout>
);
}
export default function ExploreCommunitiesView() {
return (
<PeopleListProvider initList="global">
<RelaySelectionProvider>
<CommunitiesExplorePage />
</RelaySelectionProvider>
<PeopleListProvider>
<CommunitiesExplorePage />
</PeopleListProvider>
);
}

View File

@ -1,22 +1,12 @@
import { Button, Center, Flex, Heading, Link, SimpleGrid, Text } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import { Navigate } from "react-router-dom";
import { nip19 } from "nostr-tools";
import VerticalPageLayout from "../../components/vertical-page-layout";
import { ErrorBoundary } from "../../components/error-boundary";
import useSubscribedCommunitiesList from "../../hooks/use-subscribed-communities-list";
import { useCurrentAccount } from "../../hooks/use-current-account";
import { EmbedEventPointer } from "../../components/embed-event";
import { AddressPointer } from "nostr-tools/lib/types/nip19";
import useReplaceableEvent from "../../hooks/use-replaceable-event";
import CommunityCard from "./components/community-card";
function LoadCommunityCard({ pointer }: { pointer: AddressPointer }) {
const community = useReplaceableEvent(pointer);
if (!community) return <span>Loading {pointer.identifier}</span>;
return <CommunityCard community={community} />;
}
import { PointerCommunityCard } from "./components/community-card";
function CommunitiesHomePage() {
const account = useCurrentAccount()!;
@ -33,7 +23,7 @@ function CommunitiesHomePage() {
<SimpleGrid spacing="2" columns={{ base: 1, lg: 2 }}>
{communities.map((pointer) => (
<ErrorBoundary key={pointer.kind + pointer.pubkey + pointer.identifier}>
<LoadCommunityCard pointer={pointer} />
<PointerCommunityCard pointer={pointer} />
</ErrorBoundary>
))}
</SimpleGrid>