From a60408220e9c5939ec1f0124a49b384fb046683a Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Sat, 5 Aug 2023 10:53:43 -0500 Subject: [PATCH] show more relay info --- src/services/relay-info.ts | 2 +- src/views/relays/add-custom-modal.tsx | 105 ++++++++++++++ src/views/relays/index.tsx | 183 ++++-------------------- src/views/relays/relay-card.tsx | 184 +++++++++++++++++++++++++ src/views/relays/relay-review-note.tsx | 30 ++++ 5 files changed, 345 insertions(+), 159 deletions(-) create mode 100644 src/views/relays/add-custom-modal.tsx create mode 100644 src/views/relays/relay-card.tsx create mode 100644 src/views/relays/relay-review-note.tsx diff --git a/src/services/relay-info.ts b/src/services/relay-info.ts index 71a29382f..ffe9f11c0 100644 --- a/src/services/relay-info.ts +++ b/src/services/relay-info.ts @@ -33,7 +33,7 @@ async function getInfo(relay: string) { const cached = await db.get("relayInfo", relay); if (cached) { memoryCache.set(relay, cached); - return cached; + return cached as RelayInformationDocument; } return fetchInfo(relay); diff --git a/src/views/relays/add-custom-modal.tsx b/src/views/relays/add-custom-modal.tsx new file mode 100644 index 000000000..e867f8466 --- /dev/null +++ b/src/views/relays/add-custom-modal.tsx @@ -0,0 +1,105 @@ +import { + Box, + Button, + ButtonGroup, + Code, + Flex, + FormControl, + FormLabel, + IconButton, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + ModalProps, + Text, + useDisclosure, +} from "@chakra-ui/react"; +import { useState } from "react"; +import { useRelayInfo } from "../../hooks/use-relay-info"; +import { UserAvatar } from "../../components/user-avatar"; +import { UserLink } from "../../components/user-link"; +import { safeRelayUrl } from "../../helpers/url"; +import { useDebounce } from "react-use"; +import { UserDnsIdentityIcon } from "../../components/user-dns-identity-icon"; +import { CodeIcon } from "../../components/icons"; +import { Metadata } from "./relay-card"; + +function RelayDetails({ url, debug }: { url: string; debug?: boolean }) { + const { info } = useRelayInfo(url); + + if (!info) return null; + + return ( + + {info.name} + {url} + {info.pubkey && ( + + Owner: + + + + + )} + {info.supported_nips?.join(", ")} + {debug && ( + + {JSON.stringify(info, null, 2)} + + )} + + ); +} + +export default function AddCustomRelayModal({ + onSubmit, + ...props +}: { onSubmit: (relay: string) => void } & Omit) { + const [url, setUrl] = useState(""); + const [safeUrl, setSafeUrl] = useState(); + const showDebug = useDisclosure(); + + useDebounce(() => setSafeUrl(safeRelayUrl(url) ?? undefined), 1000, [url]); + + return ( + + + + Custom Relay + + + + Relay URL + setUrl(e.target.value)} + /> + + + {safeUrl && } + + + + + {safeUrl && ( + } aria-label="Show JSON" onClick={showDebug.onToggle} variant="ghost" /> + )} + + + + + + + ); +} diff --git a/src/views/relays/index.tsx b/src/views/relays/index.tsx index befc23fe7..b0fc24708 100644 --- a/src/views/relays/index.tsx +++ b/src/views/relays/index.tsx @@ -1,170 +1,21 @@ -import { - Box, - Button, - ButtonGroup, - Card, - CardBody, - CardHeader, - Divider, - Flex, - Heading, - Input, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalHeader, - ModalOverlay, - ModalProps, - SimpleGrid, - Spacer, - Switch, - Text, - useDisclosure, -} from "@chakra-ui/react"; -import { useAsync } from "react-use"; -import { Link as RouterLink } from "react-router-dom"; -import { useRelayInfo } from "../../hooks/use-relay-info"; -import { RelayFavicon } from "../../components/relay-favicon"; import { useDeferredValue, useMemo, useState } from "react"; -import { ExternalLinkIcon } from "../../components/icons"; -import { UserLink } from "../../components/user-link"; -import { UserAvatar } from "../../components/user-avatar"; -import { useClientRelays, useReadRelayUrls } from "../../hooks/use-client-relays"; +import { Button, Divider, Flex, Heading, Input, SimpleGrid, Spacer, Switch, useDisclosure } from "@chakra-ui/react"; +import { useAsync } from "react-use"; + +import { useClientRelays } from "../../hooks/use-client-relays"; +import relayPoolService from "../../services/relay-pool"; +import { safeRelayUrl } from "../../helpers/url"; +import AddCustomRelayModal from "./add-custom-modal"; +import RelayCard from "./relay-card"; import clientRelaysService from "../../services/client-relays"; import { RelayMode } from "../../classes/relay"; -import useTimelineLoader from "../../hooks/use-timeline-loader"; -import useSubject from "../../hooks/use-subject"; -import { UserDnsIdentityIcon } from "../../components/user-dns-identity-icon"; -import { UserAvatarLink } from "../../components/user-avatar-link"; -import { NostrEvent } from "../../types/nostr-event"; -import dayjs from "dayjs"; -import { safeJson } from "../../helpers/parse"; -import StarRating from "../../components/star-rating"; -import relayPoolService from "../../services/relay-pool"; -import { useCurrentAccount } from "../../hooks/use-current-account"; -import { safeRelayUrl } from "../../helpers/url"; - -function RelayReviewNote({ event }: { event: NostrEvent }) { - const ratingJson = event.tags.find((t) => t[0] === "l" && t[3])?.[3]; - const rating = ratingJson ? (safeJson(ratingJson, undefined) as { quality: number } | undefined) : undefined; - - return ( - - - - - - - {dayjs.unix(event.created_at).fromNow()} - - - {rating && } - {event.content} - - - ); -} -function RelayReviewsModal({ relay, ...props }: { relay: string } & Omit) { - const readRelays = useReadRelayUrls(); - const timeline = useTimelineLoader(`${relay}-reviews`, readRelays, { - kinds: [1985], - "#r": [relay], - "#l": ["review/relay"], - }); - - const events = useSubject(timeline.timeline); - - return ( - - - - - {relay} reviews - - - - - {events.map((event) => ( - - ))} - - - - - ); -} - -function RelayCard({ url }: { url: string }) { - const account = useCurrentAccount(); - const { info } = useRelayInfo(url); - const clientRelays = useClientRelays(); - const reviewsModal = useDisclosure(); - - const joined = clientRelays.some((r) => r.url === url); - - return ( - <> - - - - - {url} - - - - {info?.pubkey && ( - - Owner: - - - - - )} - - {joined ? ( - - ) : ( - - )} - - - - - - - {reviewsModal.isOpen && } - - ); -} export default function RelaysView() { const [search, setSearch] = useState(""); const deboundedSearch = useDeferredValue(search); const isSearching = deboundedSearch.length > 2; const showAll = useDisclosure(); + const addRelayModal = useDisclosure(); const clientRelays = useClientRelays().map((r) => r.url); const discoveredRelays = relayPoolService @@ -191,6 +42,10 @@ export default function RelaysView() { Show All + + {filteredRelays.map((url) => ( @@ -209,6 +64,18 @@ export default function RelaysView() { )} + + {addRelayModal.isOpen && ( + { + clientRelaysService.addRelay(url, RelayMode.ALL); + addRelayModal.onClose(); + }} + /> + )} ); } diff --git a/src/views/relays/relay-card.tsx b/src/views/relays/relay-card.tsx new file mode 100644 index 000000000..60b1a152f --- /dev/null +++ b/src/views/relays/relay-card.tsx @@ -0,0 +1,184 @@ +import { + Box, + Button, + ButtonGroup, + Card, + CardBody, + CardFooter, + CardHeader, + Code, + Flex, + Heading, + IconButton, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, + ModalProps, + Spacer, + Text, + useDisclosure, +} from "@chakra-ui/react"; +import { Link as RouterLink } from "react-router-dom"; +import { useRelayInfo } from "../../hooks/use-relay-info"; +import { RelayFavicon } from "../../components/relay-favicon"; +import { CodeIcon, ExternalLinkIcon } from "../../components/icons"; +import { UserLink } from "../../components/user-link"; +import { UserAvatar } from "../../components/user-avatar"; +import { useClientRelays, useReadRelayUrls } from "../../hooks/use-client-relays"; +import clientRelaysService from "../../services/client-relays"; +import { RelayMode } from "../../classes/relay"; +import { UserDnsIdentityIcon } from "../../components/user-dns-identity-icon"; +import { useCurrentAccount } from "../../hooks/use-current-account"; +import useSubject from "../../hooks/use-subject"; +import useTimelineLoader from "../../hooks/use-timeline-loader"; +import RelayReviewNote from "./relay-review-note"; +import styled from "@emotion/styled"; +import { PropsWithChildren } from "react"; +import RawJson from "../../components/debug-modals/raw-json"; + +function RelayReviewsModal({ relay, ...props }: { relay: string } & Omit) { + const readRelays = useReadRelayUrls(); + const timeline = useTimelineLoader(`${relay}-reviews`, readRelays, { + kinds: [1985], + "#r": [relay], + "#l": ["review/relay"], + }); + + const events = useSubject(timeline.timeline); + + return ( + + + + + {relay} reviews + + + + + {events.map((event) => ( + + ))} + + + + + ); +} + +const B = styled.span` + font-weight: bold; +`; +export const Metadata = ({ name, children }: { name: string } & PropsWithChildren) => + children ? ( +
+ {name}: + {children} +
+ ) : null; + +export function RelayMetadata({ url }: { url: string }) { + const { info } = useRelayInfo(url); + + return ( + + {info?.name} + {info?.pubkey && ( + + Owner: + + + + + )} + + ); +} + +export default function RelayCard({ url }: { url: string }) { + const account = useCurrentAccount(); + const { info } = useRelayInfo(url); + const clientRelays = useClientRelays(); + const reviewsModal = useDisclosure(); + const debugModal = useDisclosure(); + + const joined = clientRelays.some((r) => r.url === url); + + return ( + <> + + + + + {url} + + + + + + + {joined ? ( + + ) : ( + + )} + + + + } + aria-label="Show JSON" + onClick={debugModal.onToggle} + variant="ghost" + size="sm" + ml="auto" + /> + + + + {reviewsModal.isOpen && } + {debugModal.isOpen && ( + + + + Relay Info + + + + + + + )} + + ); +} diff --git a/src/views/relays/relay-review-note.tsx b/src/views/relays/relay-review-note.tsx new file mode 100644 index 000000000..31c729945 --- /dev/null +++ b/src/views/relays/relay-review-note.tsx @@ -0,0 +1,30 @@ +import dayjs from "dayjs"; +import { Box, Card, CardBody, CardHeader, Spacer, Text } from "@chakra-ui/react"; + +import { UserAvatarLink } from "../../components/user-avatar-link"; +import { UserLink } from "../../components/user-link"; +import { UserDnsIdentityIcon } from "../../components/user-dns-identity-icon"; +import StarRating from "../../components/star-rating"; +import { safeJson } from "../../helpers/parse"; +import { NostrEvent } from "../../types/nostr-event"; + +export default function RelayReviewNote({ event }: { event: NostrEvent }) { + const ratingJson = event.tags.find((t) => t[0] === "l" && t[3])?.[3]; + const rating = ratingJson ? (safeJson(ratingJson, undefined) as { quality: number } | undefined) : undefined; + + return ( + + + + + + + {dayjs.unix(event.created_at).fromNow()} + + + {rating && } + {event.content} + + + ); +}