mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-10-09 20:33:03 +02:00
improve community explore view
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
import { memo, useRef } from "react";
|
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 { Link as RouterLink } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardBody,
|
|
||||||
CardFooter,
|
CardFooter,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardProps,
|
CardProps,
|
||||||
@@ -20,14 +19,13 @@ import { NostrEvent } from "../../../types/nostr-event";
|
|||||||
import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
||||||
import { getEventUID } from "../../../helpers/nostr/events";
|
import { getEventUID } from "../../../helpers/nostr/events";
|
||||||
import { getCommunityImage, getCommunityName } from "../../../helpers/nostr/communities";
|
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 UserAvatarLink from "../../../components/user-avatar-link";
|
||||||
import { UserLink } from "../../../components/user-link";
|
import { UserLink } from "../../../components/user-link";
|
||||||
import useCountCommunityMembers from "../../../hooks/use-count-community-members";
|
import useCountCommunityMembers from "../../../hooks/use-count-community-members";
|
||||||
import { readablizeSats } from "../../../helpers/bolt11";
|
import { readablizeSats } from "../../../helpers/bolt11";
|
||||||
import { CommunityIcon } from "../../../components/icons";
|
|
||||||
import User01 from "../../../components/icons/user-01";
|
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 }) {
|
function CommunityCard({ community, ...props }: Omit<CardProps, "children"> & { community: NostrEvent }) {
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
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);
|
export default memo(CommunityCard);
|
||||||
|
@@ -1,69 +1,107 @@
|
|||||||
import { useCallback, useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Flex, SimpleGrid } from "@chakra-ui/react";
|
import { AvatarGroup, Button, Flex, Switch, useDisclosure } from "@chakra-ui/react";
|
||||||
|
|
||||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/people-list-provider";
|
import PeopleListProvider, { usePeopleListContext } from "../../providers/people-list-provider";
|
||||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
import { PointerCommunityCard } from "./components/community-card";
|
||||||
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 VerticalPageLayout from "../../components/vertical-page-layout";
|
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||||
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
|
import { COMMUNITY_DEFINITION_KIND, SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER } from "../../helpers/nostr/communities";
|
||||||
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 { ErrorBoundary } from "../../components/error-boundary";
|
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() {
|
function CommunitiesExplorePage() {
|
||||||
const { filter, listId } = usePeopleListContext();
|
const navigate = useNavigate();
|
||||||
const { relays } = useRelaySelectionContext();
|
const { people } = usePeopleListContext();
|
||||||
|
const showMore = useDisclosure();
|
||||||
|
|
||||||
const eventFilter = useCallback((event: NostrEvent) => {
|
const communitiesLists = useUsersJoinedCommunitiesLists(people?.map((p) => p.pubkey) ?? []);
|
||||||
return validateCommunity(event);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const query = useMemo(() => {
|
const communityPointers = useMemo(() => {
|
||||||
const base: NostrQuery = { kinds: [COMMUNITY_DEFINITION_KIND] };
|
const dir = new Map<string, { pointer: AddressPointer; pubkeys: string[] }>();
|
||||||
if (filter?.authors) {
|
for (const list of communitiesLists) {
|
||||||
base.authors = filter.authors;
|
for (const { coordinate } of getCoordinatesFromList(list)) {
|
||||||
base["#p"] = filter.authors;
|
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 sorted = Array.from(communityPointers.values()).sort((a, b) => b.pubkeys.length - a.pubkeys.length);
|
||||||
|
|
||||||
const communities = useSubject(timeline.timeline);
|
|
||||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IntersectionObserverProvider callback={callback}>
|
|
||||||
<VerticalPageLayout>
|
<VerticalPageLayout>
|
||||||
<Flex gap="2" alignItems="center" wrap="wrap">
|
<Flex gap="2" alignItems="center" wrap="wrap">
|
||||||
<PeopleListSelection />
|
<Button onClick={() => navigate(-1)} leftIcon={<ChevronLeftIcon />}>
|
||||||
<RelaySelectionButton />
|
Back
|
||||||
|
</Button>
|
||||||
|
<PeopleListSelection hideGlobalOption />
|
||||||
|
<Switch onChange={showMore.onToggle} checked={showMore.isOpen}>
|
||||||
|
Show More
|
||||||
|
</Switch>
|
||||||
</Flex>
|
</Flex>
|
||||||
<SimpleGrid spacing="2" columns={{ base: 1, lg: 2 }}>
|
<Flex gap="4" direction="column">
|
||||||
{communities.map((event) => (
|
{sorted
|
||||||
<ErrorBoundary>
|
.filter((c) => (showMore ? c.pubkeys.length > 1 : true))
|
||||||
<CommunityCard key={getEventUID(event)} community={event} />
|
.map(({ pointer, pubkeys }) => (
|
||||||
</ErrorBoundary>
|
<CommunityCardWithMembers
|
||||||
|
key={pointer.kind + pointer.pubkey + pointer.identifier}
|
||||||
|
pointer={pointer}
|
||||||
|
pubkeys={pubkeys}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</Flex>
|
||||||
</VerticalPageLayout>
|
</VerticalPageLayout>
|
||||||
</IntersectionObserverProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ExploreCommunitiesView() {
|
export default function ExploreCommunitiesView() {
|
||||||
return (
|
return (
|
||||||
<PeopleListProvider initList="global">
|
<PeopleListProvider>
|
||||||
<RelaySelectionProvider>
|
|
||||||
<CommunitiesExplorePage />
|
<CommunitiesExplorePage />
|
||||||
</RelaySelectionProvider>
|
|
||||||
</PeopleListProvider>
|
</PeopleListProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,22 +1,12 @@
|
|||||||
import { Button, Center, Flex, Heading, Link, SimpleGrid, Text } from "@chakra-ui/react";
|
import { Button, Center, Flex, Heading, Link, SimpleGrid, Text } from "@chakra-ui/react";
|
||||||
import { Link as RouterLink } from "react-router-dom";
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
import { Navigate } from "react-router-dom";
|
import { Navigate } from "react-router-dom";
|
||||||
import { nip19 } from "nostr-tools";
|
|
||||||
|
|
||||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||||
import { ErrorBoundary } from "../../components/error-boundary";
|
import { ErrorBoundary } from "../../components/error-boundary";
|
||||||
import useSubscribedCommunitiesList from "../../hooks/use-subscribed-communities-list";
|
import useSubscribedCommunitiesList from "../../hooks/use-subscribed-communities-list";
|
||||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||||
import { EmbedEventPointer } from "../../components/embed-event";
|
import { PointerCommunityCard } from "./components/community-card";
|
||||||
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} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommunitiesHomePage() {
|
function CommunitiesHomePage() {
|
||||||
const account = useCurrentAccount()!;
|
const account = useCurrentAccount()!;
|
||||||
@@ -33,7 +23,7 @@ function CommunitiesHomePage() {
|
|||||||
<SimpleGrid spacing="2" columns={{ base: 1, lg: 2 }}>
|
<SimpleGrid spacing="2" columns={{ base: 1, lg: 2 }}>
|
||||||
{communities.map((pointer) => (
|
{communities.map((pointer) => (
|
||||||
<ErrorBoundary key={pointer.kind + pointer.pubkey + pointer.identifier}>
|
<ErrorBoundary key={pointer.kind + pointer.pubkey + pointer.identifier}>
|
||||||
<LoadCommunityCard pointer={pointer} />
|
<PointerCommunityCard pointer={pointer} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
Reference in New Issue
Block a user