parent
9e3b2c1ee7
commit
148e399775
@ -15,8 +15,8 @@
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@scure/base": "^1.1.1",
|
||||
"@snort/shared": "^1.0.9",
|
||||
"@snort/system": "^1.1.1",
|
||||
"@snort/system-react": "^1.1.1",
|
||||
"@snort/system": "^1.1.4",
|
||||
"@snort/system-react": "^1.1.4",
|
||||
"@snort/system-web": "^1.0.2",
|
||||
"@szhsin/react-menu": "^4.0.2",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
|
@ -55,7 +55,7 @@ export function ChatMessage({
|
||||
const profile = useUserProfile(inView?.isIntersecting ? ev.pubkey : undefined);
|
||||
const shouldShowMuteButton = ev.pubkey !== streamer && ev.pubkey !== login?.pubkey;
|
||||
const zapTarget = profile?.lud16 ?? profile?.lud06;
|
||||
const { zaps, reactions } = useEventReactions(ev, related);
|
||||
const { zaps, reactions } = useEventReactions(link, related);
|
||||
const emojiNames = emojiPacks.map(p => p.emojis).flat();
|
||||
|
||||
const filteredReactions = useMemo(() => {
|
||||
@ -175,15 +175,15 @@ export function ChatMessage({
|
||||
style={
|
||||
isTablet
|
||||
? {
|
||||
display: showZapDialog || isHovering ? "flex" : "none",
|
||||
}
|
||||
display: showZapDialog || isHovering ? "flex" : "none",
|
||||
}
|
||||
: {
|
||||
position: "fixed",
|
||||
top: topOffset ? topOffset - 12 : 0,
|
||||
left: leftOffset ? leftOffset - 32 : 0,
|
||||
opacity: showZapDialog || isHovering ? 1 : 0,
|
||||
pointerEvents: showZapDialog || isHovering ? "auto" : "none",
|
||||
}
|
||||
position: "fixed",
|
||||
top: topOffset ? topOffset - 12 : 0,
|
||||
left: leftOffset ? leftOffset - 32 : 0,
|
||||
opacity: showZapDialog || isHovering ? 1 : 0,
|
||||
pointerEvents: showZapDialog || isHovering ? "auto" : "none",
|
||||
}
|
||||
}>
|
||||
{zapTarget && (
|
||||
<SendZapsDialog
|
||||
|
@ -30,7 +30,7 @@ export function Goal({ ev }: { ev: NostrEvent }) {
|
||||
}
|
||||
|
||||
const soFar = useMemo(() => {
|
||||
return zaps.filter(z => z.receiver === ev.pubkey && z.event?.id === ev.id).reduce((acc, z) => acc + z.amount, 0);
|
||||
return zaps.filter(z => z.receiver === ev.pubkey && z.targetEvents.some(a => a.matchesEvent(ev))).reduce((acc, z) => acc + z.amount, 0);
|
||||
}, [zaps]);
|
||||
|
||||
const progress = Math.max(0, Math.min(100, (soFar / goalAmount) * 100));
|
||||
|
@ -1,8 +1,9 @@
|
||||
import "./live-chat.css";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { EventKind, NostrPrefix, NostrLink, ParsedZap, NostrEvent, parseZap, encodeTLV } from "@snort/system";
|
||||
import { EventKind, NostrLink, ParsedZap, NostrEvent } from "@snort/system";
|
||||
import { useEventReactions } from "@snort/system-react";
|
||||
import { unixNow } from "@snort/shared";
|
||||
import { useContext, useMemo } from "react";
|
||||
import { useMemo } from "react";
|
||||
import uniqBy from "lodash.uniqby";
|
||||
|
||||
import { Icon } from "element/icon";
|
||||
@ -23,7 +24,6 @@ import { formatSats } from "number";
|
||||
import { WEEK, LIVE_STREAM_CHAT } from "const";
|
||||
import { findTag, getTagValues, getHost } from "utils";
|
||||
import { TopZappers } from "element/top-zappers";
|
||||
import { SnortContext } from "@snort/system-react";
|
||||
|
||||
export interface LiveChatOptions {
|
||||
canWrite?: boolean;
|
||||
@ -61,7 +61,6 @@ export function LiveChat({
|
||||
options?: LiveChatOptions;
|
||||
height?: number;
|
||||
}) {
|
||||
const system = useContext(SnortContext);
|
||||
const host = getHost(ev);
|
||||
const feed = useLiveChatFeed(link, goal ? [goal.id] : undefined);
|
||||
const login = useLogin();
|
||||
@ -80,15 +79,11 @@ export function LiveChat({
|
||||
return uniqBy(userEmojiPacks.concat(channelEmojiPacks), packId);
|
||||
}, [userEmojiPacks, channelEmojiPacks]);
|
||||
|
||||
const zaps = feed.zaps.map(ev => parseZap(ev, system.ProfileLoader.Cache)).filter(z => z && z.valid);
|
||||
const reactions = useEventReactions(link, feed.reactions);
|
||||
const events = useMemo(() => {
|
||||
return [...feed.messages, ...feed.zaps, ...awards].sort((a, b) => b.created_at - a.created_at);
|
||||
}, [feed.messages, feed.zaps, awards]);
|
||||
const naddr = useMemo(() => {
|
||||
if (ev) {
|
||||
return encodeTLV(NostrPrefix.Address, findTag(ev, "d") ?? "", undefined, ev.kind, ev.pubkey);
|
||||
}
|
||||
}, [ev]);
|
||||
return [...feed.messages, ...feed.reactions, ...awards].sort((a, b) => b.created_at - a.created_at);
|
||||
}, [feed.messages, feed.reactions, awards]);
|
||||
|
||||
const filteredEvents = useMemo(() => {
|
||||
return events.filter(e => !mutedPubkeys.has(e.pubkey) && !hostMutedPubkeys.has(e.pubkey));
|
||||
}, [events, mutedPubkeys, hostMutedPubkeys]);
|
||||
@ -104,17 +99,17 @@ export function LiveChat({
|
||||
name="link"
|
||||
className="secondary"
|
||||
size={32}
|
||||
onClick={() => window.open(`/chat/${naddr}?chat=true`, "_blank", "popup,width=400,height=800")}
|
||||
onClick={() => window.open(`/chat/${link.encode()}?chat=true`, "_blank", "popup,width=400,height=800")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{zaps.length > 0 && (
|
||||
{reactions.zaps.length > 0 && (
|
||||
<div className="top-zappers">
|
||||
<h3>
|
||||
<FormattedMessage defaultMessage="Top zappers" />
|
||||
</h3>
|
||||
<div className="top-zappers-container">
|
||||
<TopZappers zaps={zaps} />
|
||||
<TopZappers zaps={reactions.zaps} />
|
||||
</div>
|
||||
{goal && <Goal ev={goal} />}
|
||||
</div>
|
||||
@ -138,7 +133,7 @@ export function LiveChat({
|
||||
);
|
||||
}
|
||||
case EventKind.ZapReceipt: {
|
||||
const zap = zaps.find(b => b.id === a.id && b.receiver === host);
|
||||
const zap = reactions.zaps.find(b => b.id === a.id && b.receiver === host);
|
||||
if (zap) {
|
||||
return <ChatZap zap={zap} key={a.id} />;
|
||||
}
|
||||
|
@ -1,25 +1,18 @@
|
||||
import { NostrLink, RequestBuilder, EventKind, NoteCollection } from "@snort/system";
|
||||
import { SnortContext, useRequestBuilder } from "@snort/system-react";
|
||||
import { unixNow, unwrap } from "@snort/shared";
|
||||
import { useContext, useEffect, useMemo } from "react";
|
||||
import { NostrLink, RequestBuilder, NoteCollection } from "@snort/system";
|
||||
import { useReactions, useRequestBuilder } from "@snort/system-react";
|
||||
import { unixNow } from "@snort/shared";
|
||||
import { useMemo } from "react";
|
||||
import { LIVE_STREAM_CHAT, WEEK } from "const";
|
||||
import { findTag } from "utils";
|
||||
|
||||
export function useLiveChatFeed(link: NostrLink, eZaps?: Array<string>) {
|
||||
const system = useContext(SnortContext);
|
||||
const since = useMemo(() => unixNow() - WEEK, [link.id]);
|
||||
const sub = useMemo(() => {
|
||||
const rb = new RequestBuilder(`live:${link.id}:${link.author}`);
|
||||
rb.withOptions({
|
||||
leaveOpen: true,
|
||||
});
|
||||
|
||||
const aTag = `${link.kind}:${link.author}:${link.id}`;
|
||||
rb.withFilter().kinds([LIVE_STREAM_CHAT]).tag("a", [aTag]).limit(100);
|
||||
rb.withFilter().kinds([EventKind.ZapReceipt]).tag("a", [aTag]).since(since);
|
||||
if (eZaps) {
|
||||
rb.withFilter().kinds([EventKind.ZapReceipt]).tag("e", eZaps);
|
||||
}
|
||||
return rb;
|
||||
}, [link.id, since, eZaps]);
|
||||
|
||||
@ -28,33 +21,7 @@ export function useLiveChatFeed(link: NostrLink, eZaps?: Array<string>) {
|
||||
const messages = useMemo(() => {
|
||||
return (feed.data ?? []).filter(ev => ev.kind === LIVE_STREAM_CHAT);
|
||||
}, [feed.data]);
|
||||
const zaps = useMemo(() => {
|
||||
return (feed.data ?? []).filter(ev => ev.kind === EventKind.ZapReceipt);
|
||||
}, [feed.data]);
|
||||
|
||||
const etags = useMemo(() => {
|
||||
return messages.map(e => e.id);
|
||||
}, [messages]);
|
||||
|
||||
const esub = useMemo(() => {
|
||||
if (etags.length === 0) return null;
|
||||
const rb = new RequestBuilder(`reactions:${link.id}:${link.author}`);
|
||||
rb.withOptions({
|
||||
leaveOpen: true,
|
||||
});
|
||||
rb.withFilter().kinds([EventKind.Reaction, EventKind.ZapReceipt]).tag("e", etags);
|
||||
return rb;
|
||||
}, [etags]);
|
||||
|
||||
useEffect(() => {
|
||||
const pubkeys = [...new Set(zaps.flatMap(a => [a.pubkey, unwrap(findTag(a, "p"))]))];
|
||||
system.ProfileLoader.TrackMetadata(pubkeys);
|
||||
return () => system.ProfileLoader.UntrackMetadata(pubkeys);
|
||||
}, [zaps]);
|
||||
|
||||
const reactionsSub = useRequestBuilder(NoteCollection, esub);
|
||||
|
||||
const reactions = reactionsSub.data ?? [];
|
||||
|
||||
return { messages, zaps, reactions };
|
||||
const reactions = useReactions(`live:${link.id}:${link.author}:reactions`, messages.map(a => NostrLink.fromEvent(a)).concat(link), undefined, true);
|
||||
return { messages, reactions: reactions.data ?? [] };
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { useContext, useMemo } from "react";
|
||||
import { RequestBuilder, NoteCollection, NostrLink, EventKind, parseZap } from "@snort/system";
|
||||
import { SnortContext, useRequestBuilder } from "@snort/system-react";
|
||||
import { useMemo } from "react";
|
||||
import { RequestBuilder, NoteCollection, NostrLink } from "@snort/system";
|
||||
import { useRequestBuilder } from "@snort/system-react";
|
||||
import { LIVE_STREAM } from "const";
|
||||
import { findTag } from "utils";
|
||||
import { useZaps } from "./zaps";
|
||||
|
||||
export function useProfile(link: NostrLink, leaveOpen = false) {
|
||||
const system = useContext(SnortContext);
|
||||
const sub = useMemo(() => {
|
||||
const b = new RequestBuilder(`profile:${link.id.slice(0, 12)}`);
|
||||
b.withOptions({
|
||||
@ -20,35 +19,11 @@ export function useProfile(link: NostrLink, leaveOpen = false) {
|
||||
return b;
|
||||
}, [link, leaveOpen]);
|
||||
|
||||
const { data: streamsData } = useRequestBuilder(NoteCollection, sub);
|
||||
const streams = streamsData ?? [];
|
||||
|
||||
const addresses = useMemo(() => {
|
||||
if (streamsData) {
|
||||
return streamsData.map(e => `${e.kind}:${e.pubkey}:${findTag(e, "d")}`);
|
||||
}
|
||||
return [];
|
||||
}, [streamsData]);
|
||||
|
||||
const zapsSub = useMemo(() => {
|
||||
const b = new RequestBuilder(`profile-zaps:${link.id.slice(0, 12)}`);
|
||||
b.withOptions({
|
||||
leaveOpen,
|
||||
})
|
||||
.withFilter()
|
||||
.kinds([EventKind.ZapReceipt])
|
||||
.tag("a", addresses);
|
||||
return b;
|
||||
}, [link, addresses, leaveOpen]);
|
||||
|
||||
const { data: zapsData } = useRequestBuilder(NoteCollection, zapsSub);
|
||||
const zaps = (zapsData ?? [])
|
||||
.map(ev => parseZap(ev, system.ProfileLoader.Cache))
|
||||
.filter(z => z && z.valid && z.receiver === link.id);
|
||||
const streams = useRequestBuilder(NoteCollection, sub);
|
||||
const zaps = useZaps(link);
|
||||
|
||||
const sortedStreams = useMemo(() => {
|
||||
const sorted = [...streams];
|
||||
sorted.sort((a, b) => b.created_at - a.created_at);
|
||||
const sorted = [...(streams.data ?? [])].sort((a, b) => b.created_at - a.created_at);
|
||||
return sorted;
|
||||
}, [streams]);
|
||||
|
||||
|
@ -1,22 +1,13 @@
|
||||
import { useContext, useMemo, useEffect } from "react";
|
||||
import { unwrap } from "@snort/shared";
|
||||
import { NostrLink, RequestBuilder, NostrPrefix, EventKind, NoteCollection, parseZap } from "@snort/system";
|
||||
import { SnortContext, useRequestBuilder } from "@snort/system-react";
|
||||
import { findTag } from "utils";
|
||||
import { useMemo } from "react";
|
||||
import { NostrLink, RequestBuilder, EventKind, NoteCollection, parseZap } from "@snort/system";
|
||||
import { useRequestBuilder } from "@snort/system-react";
|
||||
|
||||
export function useZaps(link?: NostrLink, leaveOpen = false) {
|
||||
const system = useContext(SnortContext);
|
||||
const sub = useMemo(() => {
|
||||
if (link) {
|
||||
const b = new RequestBuilder(`zaps:${link.id}`);
|
||||
b.withOptions({ leaveOpen });
|
||||
if (link.type === NostrPrefix.Event || link.type === NostrPrefix.Note) {
|
||||
b.withFilter().kinds([EventKind.ZapReceipt]).tag("e", [link.id]);
|
||||
} else if (link.type === NostrPrefix.Address) {
|
||||
b.withFilter()
|
||||
.kinds([EventKind.ZapReceipt])
|
||||
.tag("a", [`${link.kind}:${link.author}:${link.id}`]);
|
||||
}
|
||||
b.withFilter().kinds([EventKind.ZapReceipt]).replyToLink([link]);
|
||||
return b;
|
||||
}
|
||||
return null;
|
||||
@ -24,16 +15,10 @@ export function useZaps(link?: NostrLink, leaveOpen = false) {
|
||||
|
||||
const { data: zaps } = useRequestBuilder(NoteCollection, sub);
|
||||
|
||||
useEffect(() => {
|
||||
const pubkeys = zaps ? [...new Set(zaps.flatMap(a => [a.pubkey, unwrap(findTag(a, "p"))]))] : [];
|
||||
system.ProfileLoader.TrackMetadata(pubkeys);
|
||||
return () => system.ProfileLoader.UntrackMetadata(pubkeys);
|
||||
}, [zaps]);
|
||||
|
||||
return (
|
||||
[...(zaps ?? [])]
|
||||
.sort((a, b) => (b.created_at > a.created_at ? 1 : -1))
|
||||
.map(ev => parseZap(ev, system.ProfileLoader.Cache))
|
||||
.map(ev => parseZap(ev))
|
||||
.filter(z => z && z.valid) ?? []
|
||||
);
|
||||
}
|
||||
|
@ -5,11 +5,13 @@ import { NostrPrefix, encodeTLV, parseNostrLink } from "@snort/system";
|
||||
import { unwrap } from "@snort/shared";
|
||||
import { useCurrentStreamFeed } from "hooks/current-stream-feed";
|
||||
import { findTag } from "utils";
|
||||
import { useZapGoal } from "hooks/goals";
|
||||
|
||||
export function ChatPopout() {
|
||||
const params = useParams();
|
||||
const link = parseNostrLink(unwrap(params.id));
|
||||
const ev = useCurrentStreamFeed(link, true);
|
||||
const goal = useZapGoal(findTag(ev, "goal"));
|
||||
|
||||
const lnk = parseNostrLink(encodeTLV(NostrPrefix.Address, findTag(ev, "d") ?? "", undefined, ev?.kind, ev?.pubkey));
|
||||
const chat = Boolean(new URL(window.location.href).searchParams.get("chat"));
|
||||
@ -22,6 +24,7 @@ export function ChatPopout() {
|
||||
canWrite: chat,
|
||||
showHeader: false,
|
||||
}}
|
||||
goal={goal}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -77,6 +77,7 @@ function ZapAlertConfiguration({ npub, baseUrl }: ZapAlertConfigurationProps) {
|
||||
errors: [],
|
||||
sender: login?.pubkey,
|
||||
amount: 1_000_000,
|
||||
targetEvents: []
|
||||
}}
|
||||
/>
|
||||
<div className="text-to-speech-settings">
|
||||
|
32
yarn.lock
32
yarn.lock
@ -2492,14 +2492,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@snort/system-react@npm:^1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "@snort/system-react@npm:1.1.1"
|
||||
"@snort/system-react@npm:^1.1.4":
|
||||
version: 1.1.4
|
||||
resolution: "@snort/system-react@npm:1.1.4"
|
||||
dependencies:
|
||||
"@snort/shared": ^1.0.9
|
||||
"@snort/system": ^1.1.1
|
||||
"@snort/system": ^1.1.4
|
||||
react: ^18.2.0
|
||||
checksum: fab77abfc738a5e415c2369aa509200b484faa09ff9dbf328872d0c527e315811a1adfcd991956837a3699e23a61d379b2eb536c9cf8dd45d013f8122c4824d5
|
||||
checksum: 22a9a4f5bdc0ad1428d0c051a5d13c7168591ee0d1e30921acc5a6cc3aee98172f76439b777b9cfb03d689098ee052ed0276c77bbcd79af0944750dd90975de0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -2532,6 +2532,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@snort/system@npm:^1.1.4":
|
||||
version: 1.1.4
|
||||
resolution: "@snort/system@npm:1.1.4"
|
||||
dependencies:
|
||||
"@noble/curves": ^1.2.0
|
||||
"@noble/hashes": ^1.3.2
|
||||
"@scure/base": ^1.1.2
|
||||
"@snort/shared": ^1.0.9
|
||||
"@stablelib/xchacha20": ^1.0.1
|
||||
debug: ^4.3.4
|
||||
eventemitter3: ^5.0.1
|
||||
isomorphic-ws: ^5.0.0
|
||||
uuid: ^9.0.0
|
||||
ws: ^8.14.0
|
||||
checksum: f0c7d920e7ea6aa1d8ca3b24a69841dbb33dec0e560d5b2f665c4ab93ccf1604bb01ec9733422daf3d275706e1c5b518fec80d2440a3c9136ba8d4e91d42ba2b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@stablelib/binary@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@stablelib/binary@npm:1.0.1"
|
||||
@ -9482,8 +9500,8 @@ __metadata:
|
||||
"@react-hook/resize-observer": ^1.2.6
|
||||
"@scure/base": ^1.1.1
|
||||
"@snort/shared": ^1.0.9
|
||||
"@snort/system": ^1.1.1
|
||||
"@snort/system-react": ^1.1.1
|
||||
"@snort/system": ^1.1.4
|
||||
"@snort/system-react": ^1.1.4
|
||||
"@snort/system-web": ^1.0.2
|
||||
"@szhsin/react-menu": ^4.0.2
|
||||
"@testing-library/dom": ^9.3.1
|
||||
|
Loading…
x
Reference in New Issue
Block a user