From 85d6a2a0981b0d3e497b5784ec57bf45dfc7cc52 Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Tue, 4 Jul 2023 12:19:00 -0500 Subject: [PATCH] add stream chat popup correctly show host user on streams --- src/app.tsx | 46 +++---- src/components/zap-modal.tsx | 19 ++- src/helpers/nostr/stream.ts | 5 +- src/hooks/use-set-color-mode.ts | 15 +++ src/views/streams/components/stream-card.tsx | 5 +- src/views/streams/stream/index.tsx | 126 ++++++++++++------ .../stream/stream-chat/chat-message.tsx | 2 +- .../streams/stream/stream-chat/index.tsx | 89 ++++++++----- 8 files changed, 204 insertions(+), 103 deletions(-) create mode 100644 src/hooks/use-set-color-mode.ts diff --git a/src/app.tsx b/src/app.tsx index 675eda6cd..73281c4fd 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,5 +1,5 @@ import React, { Suspense, useEffect } from "react"; -import { createHashRouter, Outlet, RouterProvider, ScrollRestoration, useLocation } from "react-router-dom"; +import { createHashRouter, Outlet, RouterProvider, ScrollRestoration, useSearchParams } from "react-router-dom"; import { Spinner, useColorMode } from "@chakra-ui/react"; import { ErrorBoundary } from "./components/error-boundary"; import { Page } from "./components/page"; @@ -35,19 +35,24 @@ import ToolsHomeView from "./views/tools"; import Nip19ToolsView from "./views/tools/nip19"; import UserAboutTab from "./views/user/about"; import UserLikesTab from "./views/user/likes"; +import useSetColorMode from "./hooks/use-set-color-mode"; const LiveStreamsTab = React.lazy(() => import("./views/streams")); const StreamView = React.lazy(() => import("./views/streams/stream")); const SearchView = React.lazy(() => import("./views/search")); -const RootPage = () => ( - - - }> - - - -); +const RootPage = () => { + useSetColorMode(); + + return ( + + + }> + + + + ); +}; const router = createHashRouter([ { @@ -118,19 +123,10 @@ const router = createHashRouter([ }, ]); -export const App = () => { - const { setColorMode } = useColorMode(); - const { colorMode } = useSubject(appSettings); - - useEffect(() => { - setColorMode(colorMode); - }, [colorMode]); - - return ( - - }> - - - - ); -}; +export const App = () => ( + + }> + + + +); diff --git a/src/components/zap-modal.tsx b/src/components/zap-modal.tsx index 1b1630dae..0b80da729 100644 --- a/src/components/zap-modal.tsx +++ b/src/components/zap-modal.tsx @@ -35,6 +35,7 @@ import { unique } from "../helpers/array"; import { useUserRelays } from "../hooks/use-user-relays"; import { RelayMode } from "../classes/relay"; import relayScoreboardService from "../services/relay-scoreboard"; +import { useAdditionalRelayContext } from "../providers/additional-relay-context"; type FormValues = { amount: number; @@ -50,6 +51,7 @@ export type ZapModalProps = Omit & { onInvoice: (invoice: string) => void; allowComment?: boolean; showEventPreview?: boolean; + additionalRelays?: string[]; }; export default function ZapModal({ @@ -62,9 +64,11 @@ export default function ZapModal({ onInvoice, allowComment = true, showEventPreview = true, + additionalRelays = [], ...props }: ZapModalProps) { const toast = useToast(); + const contextRelays = useAdditionalRelayContext(); const { requestSignature } = useSigningContext(); const { customZapAmounts } = useSubject(appSettings); const userReadRelays = useUserRelays(pubkey) @@ -104,6 +108,7 @@ export default function ZapModal({ const writeRelays = clientRelaysService.getWriteUrls(); const writeRelaysRanked = relayScoreboardService.getRankedRelays(writeRelays).slice(0, 4); const userReadRelaysRanked = relayScoreboardService.getRankedRelays(userReadRelays).slice(0, 4); + const contextRelaysRanked = relayScoreboardService.getRankedRelays(contextRelays).slice(0, 4); const zapRequest: DraftNostrEvent = { kind: Kind.ZapRequest, @@ -111,10 +116,22 @@ export default function ZapModal({ content: values.comment, tags: [ ["p", pubkey], - ["relays", ...unique([...writeRelaysRanked, ...userReadRelaysRanked, ...eventRelaysRanked])], + [ + "relays", + ...unique([ + ...contextRelaysRanked, + ...writeRelaysRanked, + ...userReadRelaysRanked, + ...eventRelaysRanked, + ...additionalRelays, + ]), + ], ["amount", String(amountInMilisat)], ], }; + + console.log(zapRequest); + if (event) zapRequest.tags.push(["e", event.id]); if (stream) zapRequest.tags.push(["a", getATag(stream)]); diff --git a/src/helpers/nostr/stream.ts b/src/helpers/nostr/stream.ts index 9ddd261bc..74fc38fea 100644 --- a/src/helpers/nostr/stream.ts +++ b/src/helpers/nostr/stream.ts @@ -1,10 +1,11 @@ import dayjs from "dayjs"; -import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event"; +import { DraftNostrEvent, NostrEvent, isPTag } from "../../types/nostr-event"; import { unique } from "../array"; export type ParsedStream = { event: NostrEvent; author: string; + host: string; title: string; summary?: string; image?: string; @@ -42,10 +43,12 @@ export function parseStreamEvent(stream: NostrEvent): ParsedStream { status = "ended"; } + const host = stream.tags.filter(isPTag)[0]?.[1] ?? stream.pubkey; const tags = unique(stream.tags.filter((t) => t[0] === "t" && t[1]).map((t) => t[1] as string)); return { author: stream.pubkey, + host, event: stream, updated: stream.created_at, streaming, diff --git a/src/hooks/use-set-color-mode.ts b/src/hooks/use-set-color-mode.ts new file mode 100644 index 000000000..72c0d7bb4 --- /dev/null +++ b/src/hooks/use-set-color-mode.ts @@ -0,0 +1,15 @@ +import { useColorMode } from "@chakra-ui/react"; +import useSubject from "./use-subject"; +import appSettings from "../services/app-settings"; +import { useSearchParams } from "react-router-dom"; +import { useEffect } from "react"; + +export default function useSetColorMode() { + const { setColorMode } = useColorMode(); + const { colorMode } = useSubject(appSettings); + const [params] = useSearchParams(); + + useEffect(() => { + setColorMode(params.get("colorMode") || colorMode); + }, [colorMode, params.get("colorMode")]); +} diff --git a/src/views/streams/components/stream-card.tsx b/src/views/streams/components/stream-card.tsx index 8bc6b5390..bc500fcf9 100644 --- a/src/views/streams/components/stream-card.tsx +++ b/src/views/streams/components/stream-card.tsx @@ -43,6 +43,7 @@ export default function StreamCard({ stream, ...props }: CardProps & { stream: P const relays = getEventRelays(stream.event.id).value; const ranked = relayScoreboardService.getRankedRelays(relays); const onlyTwo = ranked.slice(0, 2); + return nip19.naddrEncode({ identifier, relays: onlyTwo, @@ -57,9 +58,9 @@ export default function StreamCard({ stream, ...props }: CardProps & { stream: P {image && {title}} - + - + diff --git a/src/views/streams/stream/index.tsx b/src/views/streams/stream/index.tsx index fc6316b9b..eedfbb9c6 100644 --- a/src/views/streams/stream/index.tsx +++ b/src/views/streams/stream/index.tsx @@ -1,41 +1,71 @@ import { useEffect, useRef, useState } from "react"; import { useScroll } from "react-use"; -import { Box, Button, Flex, Heading, Spacer, Spinner, Text } from "@chakra-ui/react"; -import { Link as RouterLink, useParams, Navigate } from "react-router-dom"; +import { Box, Button, ButtonGroup, Flex, Heading, Spacer, Spinner, Text } from "@chakra-ui/react"; +import { Link as RouterLink, useParams, Navigate, useSearchParams } from "react-router-dom"; import { nip19 } from "nostr-tools"; +import { Global, css } from "@emotion/react"; import { ParsedStream, parseStreamEvent } from "../../../helpers/nostr/stream"; import { NostrRequest } from "../../../classes/nostr-request"; import { useReadRelayUrls } from "../../../hooks/use-client-relays"; import { unique } from "../../../helpers/array"; import { LiveVideoPlayer } from "../../../components/live-video-player"; -import StreamChat from "./stream-chat"; +import StreamChat, { ChatDisplayMode } from "./stream-chat"; import { UserAvatarLink } from "../../../components/user-avatar-link"; import { UserLink } from "../../../components/user-link"; import { useIsMobile } from "../../../hooks/use-is-mobile"; import { AdditionalRelayProvider } from "../../../providers/additional-relay-context"; import StreamSummaryContent from "../components/stream-summary-content"; -import { ArrowDownSIcon, ArrowUpSIcon } from "../../../components/icons"; +import { ArrowDownSIcon, ArrowUpSIcon, ExternalLinkIcon } from "../../../components/icons"; +import useSetColorMode from "../../../hooks/use-set-color-mode"; +import { CopyIconButton } from "../../../components/copy-icon-button"; -function StreamPage({ stream }: { stream: ParsedStream }) { +function StreamPage({ stream, displayMode }: { stream: ParsedStream; displayMode?: ChatDisplayMode }) { const isMobile = useIsMobile(); const scrollBox = useRef(null); const scrollState = useScroll(scrollBox); - const action = - scrollState.y === 0 ? ( - - ) : ( - + const renderActions = () => { + const toggleButton = + scrollState.y === 0 ? ( + + ) : ( + + ); + + return ( + + {isMobile && toggleButton} + + + ); + }; return ( - - - - - - - - - {stream.title} - - - + {displayMode && ( + + )} + {!displayMode && ( + + + + + + + + + {stream.title} + + + + + - - + )} ); @@ -79,6 +121,9 @@ function StreamPage({ stream }: { stream: ParsedStream }) { export default function StreamView() { const { naddr } = useParams(); + const [params] = useSearchParams(); + useSetColorMode(); + if (!naddr) return ; const readRelays = useReadRelayUrls(); @@ -104,8 +149,11 @@ export default function StreamView() { if (!stream) return ; return ( - - + // add snort and damus relays so zap.stream will always see zaps + + ); } diff --git a/src/views/streams/stream/stream-chat/chat-message.tsx b/src/views/streams/stream/stream-chat/chat-message.tsx index 491d78a4f..0b959de88 100644 --- a/src/views/streams/stream/stream-chat/chat-message.tsx +++ b/src/views/streams/stream/stream-chat/chat-message.tsx @@ -19,7 +19,7 @@ export default function ChatMessage({ event, stream }: { event: NostrEvent; stre - + {": "} diff --git a/src/views/streams/stream/stream-chat/index.tsx b/src/views/streams/stream/stream-chat/index.tsx index 2ab874883..13898d4ed 100644 --- a/src/views/streams/stream/stream-chat/index.tsx +++ b/src/views/streams/stream/stream-chat/index.tsx @@ -35,16 +35,28 @@ import { useTimelineCurserIntersectionCallback } from "../../../../hooks/use-tim import useSubject from "../../../../hooks/use-subject"; import { useTimelineLoader } from "../../../../hooks/use-timeline-loader"; import { truncatedId } from "../../../../helpers/nostr-event"; +import { css } from "@emotion/react"; + +const hideScrollbar = css` + scrollbar-width: 0; + + ::-webkit-scrollbar { + width: 0; + } +`; + +export type ChatDisplayMode = "log" | "popup"; export default function StreamChat({ stream, actions, + displayMode, ...props -}: CardProps & { stream: ParsedStream; actions?: React.ReactNode }) { +}: CardProps & { stream: ParsedStream; actions?: React.ReactNode; displayMode?: ChatDisplayMode }) { const toast = useToast(); const contextRelays = useAdditionalRelayContext(); const readRelays = useReadRelayUrls(contextRelays); - const userReadRelays = useUserRelays(stream.author) + const hostReadRelays = useUserRelays(stream.host) .filter((r) => r.mode & RelayMode.READ) .map((r) => r.url); @@ -67,7 +79,7 @@ export default function StreamChat({ const draft = buildChatMessage(stream, values.content); const signed = await requestSignature(draft); if (!signed) throw new Error("Failed to sign"); - nostrPostAction(unique([...contextRelays, ...userReadRelays]), signed); + nostrPostAction(unique([...contextRelays, ...hostReadRelays]), signed); reset(); } catch (e) { if (e instanceof Error) toast({ description: e.message, status: "error" }); @@ -76,17 +88,22 @@ export default function StreamChat({ const zapModal = useDisclosure(); const { requestPay } = useInvoiceModalContext(); - const zapMetadata = useUserLNURLMetadata(stream.author); + const zapMetadata = useUserLNURLMetadata(stream.host); + + const isPopup = !!displayMode; + const isChatLog = displayMode === "log"; return ( <> - - - Stream Chat - {actions} - + + {!isPopup && ( + + Stream Chat + {actions} + + )} {events.map((event) => event.kind === 1311 ? ( @@ -106,30 +124,32 @@ export default function StreamChat({ ) )} - - - - {zapMetadata.metadata?.allowsNostr && ( - } - aria-label="Zap stream" - borderColor="yellow.400" - variant="outline" - onClick={zapModal.onOpen} - /> - )} - + {!isChatLog && ( + + + + {zapMetadata.metadata?.allowsNostr && ( + } + aria-label="Zap stream" + borderColor="yellow.400" + variant="outline" + onClick={zapModal.onOpen} + /> + )} + + )} @@ -138,7 +158,7 @@ export default function StreamChat({ { reset(); zapModal.onClose(); @@ -146,6 +166,7 @@ export default function StreamChat({ }} onClose={zapModal.onClose} initialComment={getValues().content} + additionalRelays={contextRelays} /> )}