From c222b37cb555a7f6b7db012210c23423e036cc4b Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Thu, 23 May 2024 20:59:15 -0500 Subject: [PATCH] move ghost timeline to sidebar --- src/components/debug-modal/event-tags.tsx | 2 +- src/components/layout/ghost-toolbar.tsx | 128 ---------------------- src/components/layout/ghost/sidebar.tsx | 26 +++++ src/components/layout/ghost/timeline.tsx | 115 +++++++++++++++++++ src/components/layout/index.tsx | 5 +- 5 files changed, 144 insertions(+), 132 deletions(-) delete mode 100644 src/components/layout/ghost-toolbar.tsx create mode 100644 src/components/layout/ghost/sidebar.tsx create mode 100644 src/components/layout/ghost/timeline.tsx diff --git a/src/components/debug-modal/event-tags.tsx b/src/components/debug-modal/event-tags.tsx index 10b5eb245..073d65524 100644 --- a/src/components/debug-modal/event-tags.tsx +++ b/src/components/debug-modal/event-tags.tsx @@ -81,7 +81,7 @@ export default function DebugEventTags({ event }: { event: NostrEvent }) { return ( <> - {expand.isOpen && ( diff --git a/src/components/layout/ghost-toolbar.tsx b/src/components/layout/ghost-toolbar.tsx deleted file mode 100644 index 023263752..000000000 --- a/src/components/layout/ghost-toolbar.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { useCallback, useState } from "react"; -import { Box, Card, CloseButton, Divider, Flex, FlexProps, Spacer, Text } from "@chakra-ui/react"; -import { kinds, nip18, nip19, nip25 } from "nostr-tools"; -import { useNavigate } from "react-router-dom"; -import { useInterval } from "react-use"; -import dayjs from "dayjs"; - -import useCurrentAccount from "../../hooks/use-current-account"; -import useSubject from "../../hooks/use-subject"; -import accountService from "../../services/account"; -import UserAvatar from "../user/user-avatar"; -import UserLink from "../user/user-link"; -import { GhostIcon } from "../icons"; -import useTimelineLoader from "../../hooks/use-timeline-loader"; -import { useReadRelays } from "../../hooks/use-client-relays"; -import TimelineLoader from "../../classes/timeline-loader"; -import { NostrEvent } from "../../types/nostr-event"; -import { getSharableEventAddress } from "../../helpers/nip19"; -import { safeRelayUrls } from "../../helpers/relay"; - -const kindColors: Record = { - [kinds.ShortTextNote]: "blue.500", - [kinds.RecommendRelay]: "pink", - [kinds.EncryptedDirectMessage]: "orange.500", - [kinds.Repost]: "yellow", - [kinds.GenericRepost]: "yellow", - [kinds.Reaction]: "green.500", - [kinds.LongFormArticle]: "purple.500", -}; - -function EventChunk({ event, ...props }: { event: NostrEvent } & Omit) { - const navigate = useNavigate(); - const handleClick = useCallback(() => { - switch (event.kind) { - case kinds.Reaction: { - const pointer = nip25.getReactedEventPointer(event); - if (pointer) navigate(`/l/${nip19.neventEncode(pointer)}`); - return; - } - case kinds.Repost: { - const pointer = nip18.getRepostedEventPointer(event); - if (pointer?.relays) pointer.relays = safeRelayUrls(pointer.relays); - if (pointer) navigate(`/l/${nip19.neventEncode(pointer)}`); - return; - } - } - navigate(`/l/${getSharableEventAddress(event)}`); - }, [event]); - - const getTitle = () => { - switch (event.kind) { - case kinds.ShortTextNote: - return "Note"; - case kinds.Reaction: - return "Reaction"; - case kinds.EncryptedDirectMessage: - return "Direct Message"; - } - }; - - return ( - - - {getTitle()} - - - - ); -} - -function CompactEventTimeline({ timeline, ...props }: { timeline: TimelineLoader } & Omit) { - const events = useSubject(timeline.timeline); - const [now, setNow] = useState(dayjs().unix()); - - useInterval(() => setNow(dayjs().unix()), 1000 * 10); - - return ( - - {Array.from(events) - .reverse() - .map((event, i, arr) => { - const next = arr[i + 1]; - return ( - - ); - })} - - ); -} - -export default function GhostToolbar() { - const account = useCurrentAccount()!; - const isGhost = useSubject(accountService.isGhost); - - const readRelays = useReadRelays(); - const [since] = useState(dayjs().subtract(6, "hours").unix()); - - const timeline = useTimelineLoader(`${account.pubkey}-ghost`, readRelays, { since, authors: [account.pubkey] }); - - const events = useSubject(timeline.timeline); - - return ( - - - Ghosting: - - - - - - accountService.stopGhost()} /> - - ); -} diff --git a/src/components/layout/ghost/sidebar.tsx b/src/components/layout/ghost/sidebar.tsx new file mode 100644 index 000000000..29bd0da34 --- /dev/null +++ b/src/components/layout/ghost/sidebar.tsx @@ -0,0 +1,26 @@ +import { Box, CloseButton, Flex, FlexProps } from "@chakra-ui/react"; + +import useCurrentAccount from "../../../hooks/use-current-account"; +import GhostTimeline from "./timeline"; +import UserAvatar from "../../user/user-avatar"; +import UserLink from "../../user/user-link"; +import UserDnsIdentity from "../../user/user-dns-identity"; +import accountService from "../../../services/account"; + +export default function GhostSideBar({ ...props }: Omit) { + const account = useCurrentAccount()!; + + return ( + + + + + + + + accountService.stopGhost()} /> + + + + ); +} diff --git a/src/components/layout/ghost/timeline.tsx b/src/components/layout/ghost/timeline.tsx new file mode 100644 index 000000000..8efe21cfc --- /dev/null +++ b/src/components/layout/ghost/timeline.tsx @@ -0,0 +1,115 @@ +import { useRef } from "react"; +import { Code, Flex, FlexProps, LinkBox, Text } from "@chakra-ui/react"; +import { NostrEvent, kinds, nip19, nip25 } from "nostr-tools"; +import { getEventUID } from "nostr-idb"; +import { Link as RouterLink } from "react-router-dom"; + +import { useReadRelays } from "../../../hooks/use-client-relays"; +import useCurrentAccount from "../../../hooks/use-current-account"; +import useTimelineLoader from "../../../hooks/use-timeline-loader"; +import useSubject from "../../../hooks/use-subject"; +import TimelineActionAndStatus from "../../timeline-page/timeline-action-and-status"; +import IntersectionObserverProvider, { + useRegisterIntersectionEntity, +} from "../../../providers/local/intersection-observer"; +import Timestamp from "../../timestamp"; +import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback"; +import { getDMRecipient, getDMSender } from "../../../helpers/nostr/dms"; +import UserName from "../../user/user-name"; +import HoverLinkOverlay from "../../hover-link-overlay"; +import { getSharableEventAddress } from "../../../helpers/nip19"; + +const kindColors: Record = { + [kinds.ShortTextNote]: "blue.500", + [kinds.EncryptedDirectMessage]: "orange.500", + [kinds.Repost]: "yellow.500", + [kinds.GenericRepost]: "yellow.500", + [kinds.Reaction]: "green.500", + [kinds.LongFormArticle]: "purple.500", +}; + +function KindTag({ event }: { event: NostrEvent }) { + return ( + + {event.kind} + + ); +} + +function TimelineItem({ event }: { event: NostrEvent }) { + const ref = useRef(null); + useRegisterIntersectionEntity(ref, getEventUID(event)); + + const renderContent = () => { + switch (event.kind) { + case kinds.EncryptedDirectMessage: + const sender = getDMSender(event); + const recipient = getDMRecipient(event); + return ( + + messaged + + ); + case kinds.Contacts: + return ( + + Updated contacts + + ); + case kinds.Reaction: + const pointer = nip25.getReactedEventPointer(event); + return ( + + {event.content} + + ); + default: + return ( + + {event.content} + + ); + } + }; + + return ( + + + {renderContent()} + + + ); +} + +export default function GhostTimeline({ ...props }: Omit) { + const account = useCurrentAccount()!; + const readRelays = useReadRelays(); + + const timeline = useTimelineLoader(`${account.pubkey}-ghost`, readRelays, { authors: [account.pubkey] }); + const events = useSubject(timeline.timeline); + + const callback = useTimelineCurserIntersectionCallback(timeline); + + return ( + + + {events.map((event) => ( + + ))} + + + + ); +} diff --git a/src/components/layout/index.tsx b/src/components/layout/index.tsx index 08b9dd484..a8e02dc76 100644 --- a/src/components/layout/index.tsx +++ b/src/components/layout/index.tsx @@ -7,8 +7,8 @@ import DesktopSideNav from "./desktop-side-nav"; import MobileBottomNav from "./mobile-bottom-nav"; import useSubject from "../../hooks/use-subject"; import accountService from "../../services/account"; -import GhostToolbar from "./ghost-toolbar"; import { useBreakpointValue } from "../../providers/global/breakpoint-provider"; +import GhostSideBar from "./ghost/sidebar"; export default function Layout({ children }: { children: React.ReactNode }) { const isMobile = useBreakpointValue({ base: true, md: false }); @@ -34,7 +34,7 @@ export default function Layout({ children }: { children: React.ReactNode }) { > {children} - {!isMobile && } + {!isMobile && isGhost ? : } {isMobile && ( - {isGhost && } ); }