hide muted users in stream chat

This commit is contained in:
hzrd149 2023-08-03 09:04:29 -05:00
parent 1f1bf752df
commit 615e19ba9e
8 changed files with 93 additions and 28 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Hide muted users in stream chat

View File

@ -0,0 +1,16 @@
import { useMemo } from "react";
import { NostrEvent } from "../types/nostr-event";
import { ParsedStream, parseStreamEvent } from "../helpers/nostr/stream";
export default function useParsedStreams(events: NostrEvent[]) {
return useMemo(() => {
const parsedStreams: ParsedStream[] = [];
for (const event of events) {
try {
const parsed = parseStreamEvent(event);
parsedStreams.push(parsed);
} catch (e) {}
}
return parsedStreams.sort((a, b) => (b.starts ?? 0) - (a.starts ?? 0));
}, [events]);
}

View File

@ -0,0 +1,15 @@
import { useMemo } from "react";
import { useReadRelayUrls } from "./use-client-relays";
import useSubject from "./use-subject";
import userMuteListService from "../services/user-mute-list";
export default function useUserMuteList(pubkey?: string, additionalRelays?: string[], alwaysRequest = false) {
const relays = useReadRelayUrls(additionalRelays);
const sub = useMemo(() => {
if (!pubkey) return;
return userMuteListService.requestMuteList(relays, pubkey, alwaysRequest);
}, [pubkey]);
return useSubject(sub);
}

View File

@ -0,0 +1,14 @@
import replaceableEventLoaderService from "./replaceable-event-requester";
class UserMuteListService {
getMuteList(pubkey: string) {
return replaceableEventLoaderService.getEvent(10000, pubkey);
}
requestMuteList(relays: string[], pubkey: string, alwaysRequest = false) {
return replaceableEventLoaderService.requestEvent(relays, 10000, pubkey, undefined, alwaysRequest);
}
}
const userMuteListService = new UserMuteListService();
export default userMuteListService;

View File

@ -13,6 +13,7 @@ import useRelaysChanged from "../../hooks/use-relays-changed";
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
import PeopleListProvider, { usePeopleListContext } from "../../components/people-list-selection/people-list-provider";
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
import useParsedStreams from "../../hooks/use-parsed-streams";
function StreamsPage() {
const relays = useRelaySelectionRelays();
@ -44,16 +45,7 @@ function StreamsPage() {
const callback = useTimelineCurserIntersectionCallback(timeline);
const events = useSubject(timeline.timeline);
const streams = useMemo(() => {
const parsedStreams: ParsedStream[] = [];
for (const event of events) {
try {
const parsed = parseStreamEvent(event);
parsedStreams.push(parsed);
} catch (e) {}
}
return parsedStreams.sort((a, b) => (b.starts ?? 0) - (a.starts ?? 0));
}, [events]);
const streams = useParsedStreams(events);
return (
<Flex p="2" gap="2" overflow="hidden" direction="column">

View File

@ -1,7 +1,7 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { useScroll } from "react-use";
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 { useParams, Navigate, useSearchParams, useNavigate } from "react-router-dom";
import { nip19 } from "nostr-tools";
import { Global, css } from "@emotion/react";
@ -27,6 +27,7 @@ function StreamPage({ stream, displayMode }: { stream: ParsedStream; displayMode
const isMobile = useIsMobile();
const scrollBox = useRef<HTMLDivElement | null>(null);
const scrollState = useScroll(scrollBox);
const navigate = useNavigate();
const renderActions = () => {
const toggleButton =
@ -110,9 +111,7 @@ function StreamPage({ stream, displayMode }: { stream: ParsedStream; displayMode
<Spacer />
<StreamDebugButton stream={stream} variant="ghost" />
<RelaySelectionButton />
<Button as={RouterLink} to="/streams">
Back
</Button>
<Button onClick={() => navigate(-1)}>Back</Button>
</Flex>
<StreamSummaryContent stream={stream} px={isMobile ? "2" : 0} />
</Flex>

View File

@ -1,4 +1,4 @@
import { useMemo, useRef } from "react";
import { useCallback, useMemo, useRef } from "react";
import {
Box,
Button,
@ -15,8 +15,6 @@ import {
} from "@chakra-ui/react";
import { ParsedStream, STREAM_CHAT_MESSAGE_KIND, buildChatMessage, getATag } from "../../../../helpers/nostr/stream";
import { useAdditionalRelayContext } from "../../../../providers/additional-relay-context";
import { useReadRelayUrls } from "../../../../hooks/use-client-relays";
import { useUserRelays } from "../../../../hooks/use-user-relays";
import { RelayMode } from "../../../../classes/relay";
import ZapModal from "../../../../components/zap-modal";
@ -40,6 +38,9 @@ import TopZappers from "./top-zappers";
import { parseZapEvent } from "../../../../helpers/zaps";
import { Kind } from "nostr-tools";
import { useRelaySelectionRelays } from "../../../../providers/relay-selection-provider";
import useUserMuteList from "../../../../hooks/use-user-mute-list";
import { NostrEvent, isPTag } from "../../../../types/nostr-event";
import { useCurrentAccount } from "../../../../hooks/use-current-account";
const hideScrollbar = css`
scrollbar-width: 0;
@ -58,6 +59,7 @@ export default function StreamChat({
...props
}: CardProps & { stream: ParsedStream; actions?: React.ReactNode; displayMode?: ChatDisplayMode }) {
const toast = useToast();
const account = useCurrentAccount();
const streamRelays = useRelaySelectionRelays();
const hostReadRelays = useUserRelays(stream.host)
.filter((r) => r.mode & RelayMode.READ)
@ -65,10 +67,23 @@ export default function StreamChat({
const relays = useMemo(() => unique([...streamRelays, ...hostReadRelays]), [hostReadRelays, streamRelays]);
const timeline = useTimelineLoader(`${truncatedId(stream.identifier)}-chat`, streamRelays, {
"#a": [getATag(stream)],
kinds: [STREAM_CHAT_MESSAGE_KIND, Kind.Zap],
});
const hostMuteList = useUserMuteList(stream.host);
const muteList = useUserMuteList(account?.pubkey);
const mutedPubkeys = useMemo(
() => [...(hostMuteList?.tags ?? []), ...(muteList?.tags ?? [])].filter(isPTag).map((t) => t[1] as string),
[hostMuteList, muteList]
);
const eventFilter = useCallback((event: NostrEvent) => !mutedPubkeys.includes(event.pubkey), [mutedPubkeys]);
const timeline = useTimelineLoader(
`${truncatedId(stream.identifier)}-chat`,
streamRelays,
{
"#a": [getATag(stream)],
kinds: [STREAM_CHAT_MESSAGE_KIND, Kind.Zap],
},
{ eventFilter }
);
const events = useSubject(timeline.timeline).sort((a, b) => b.created_at - a.created_at);

View File

@ -1,13 +1,15 @@
import { Flex } from "@chakra-ui/react";
import { Flex, SimpleGrid } from "@chakra-ui/react";
import { useOutletContext } from "react-router-dom";
import { truncatedId } from "../../helpers/nostr/event";
import { useAdditionalRelayContext } from "../../providers/additional-relay-context";
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
import IntersectionObserverProvider from "../../providers/intersection-observer";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
import GenericNoteTimeline from "../../components/timeline-page/generic-note-timeline";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import { STREAM_KIND } from "../../helpers/nostr/stream";
import useSubject from "../../hooks/use-subject";
import useParsedStreams from "../../hooks/use-parsed-streams";
import StreamCard from "../streams/components/stream-card";
export default function UserStreamsTab() {
const { pubkey } = useOutletContext() as { pubkey: string };
@ -23,12 +25,19 @@ export default function UserStreamsTab() {
const callback = useTimelineCurserIntersectionCallback(timeline);
const events = useSubject(timeline.timeline);
const streams = useParsedStreams(events);
return (
<IntersectionObserverProvider<string> callback={callback}>
<Flex direction="column" gap="2" pt="4" pb="8">
<GenericNoteTimeline timeline={timeline} />
<Flex p="2" gap="2" overflow="hidden" direction="column">
<IntersectionObserverProvider<string> callback={callback}>
<SimpleGrid minChildWidth="20rem" spacing="2">
{streams.map((stream) => (
<StreamCard key={stream.event.id} stream={stream} />
))}
</SimpleGrid>
<TimelineActionAndStatus timeline={timeline} />
</Flex>
</IntersectionObserverProvider>
</IntersectionObserverProvider>
</Flex>
);
}