forked from Kieran/zap.stream
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
73162efe63 | ||
|
e13e3bccf6 | ||
|
aaa2e26bd9 | ||
|
d98440b47b | ||
|
12366bdf2c | ||
|
6dd6c6324f | ||
|
c30ebbb973 | ||
|
040d2252ab |
@ -20,6 +20,7 @@
|
||||
"emoji-mart": "^5.5.2",
|
||||
"hls.js": "^1.4.6",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash.uniqby": "^4.7.0",
|
||||
"moment": "^2.29.4",
|
||||
"qr-code-styling": "^1.6.0-rc.1",
|
||||
"react": "^18.2.0",
|
||||
@ -65,6 +66,7 @@
|
||||
"@formatjs/cli": "^6.0.1",
|
||||
"@formatjs/ts-transformer": "^3.13.1",
|
||||
"@types/lodash": "^4.14.195",
|
||||
"@types/lodash.uniqby": "^4.7.7",
|
||||
"@webbtc/webln-types": "^1.0.12",
|
||||
"babel-loader": "^9.1.2",
|
||||
"babel-plugin-formatjs": "^10.5.1",
|
||||
|
@ -6,6 +6,7 @@ import { NostrLink, ParsedZap, NostrEvent } from "@snort/system";
|
||||
import { Icon } from "./icon";
|
||||
import { findTag } from "utils";
|
||||
import { formatSats } from "number";
|
||||
import usePreviousValue from "hooks/usePreviousValue";
|
||||
|
||||
export function Goal({
|
||||
link,
|
||||
@ -33,6 +34,7 @@ export function Goal({
|
||||
|
||||
const progress = (soFar / goalAmount) * 100;
|
||||
const isFinished = progress >= 100;
|
||||
const previousValue = usePreviousValue(isFinished);
|
||||
|
||||
return (
|
||||
<div className="goal">
|
||||
@ -56,7 +58,9 @@ export function Goal({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{isFinished && <Confetti numberOfPieces={2100} recycle={false} />}
|
||||
{isFinished && previousValue === false && (
|
||||
<Confetti numberOfPieces={2100} recycle={false} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
flex-direction: column;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
height: calc(100vh - 56px - 64px - 56px - 230px);
|
||||
height: calc(100vh - 56px - 64px - 36px - 230px);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
@ -46,10 +46,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.live-chat > .header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px){
|
||||
.live-chat > .header {
|
||||
display: flex;
|
||||
justify-content: space-between
|
||||
}
|
||||
}
|
||||
|
||||
.live-chat .header .title {
|
||||
font-size: 24px;
|
||||
@ -159,6 +165,12 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.top-zappers h3 {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.top-zappers-container {
|
||||
display: flex;
|
||||
overflow-y: scroll;
|
||||
|
@ -31,7 +31,7 @@ export interface LiveChatOptions {
|
||||
}
|
||||
|
||||
function TopZappers({ zaps }: { zaps: ParsedZap[] }) {
|
||||
const zappers = useTopZappers(zaps).slice(0, 3);
|
||||
const zappers = useTopZappers(zaps);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -65,7 +65,8 @@ export function LiveChat({
|
||||
options?: LiveChatOptions;
|
||||
height?: number;
|
||||
}) {
|
||||
const feed = useLiveChatFeed(link);
|
||||
const host = getHost(ev);
|
||||
const feed = useLiveChatFeed(link, host);
|
||||
const login = useLogin();
|
||||
useEffect(() => {
|
||||
const pubkeys = [
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { NostrLink, EventPublisher, EventKind } from "@snort/system";
|
||||
import { useRef, useState, ChangeEvent } from "react";
|
||||
import { useRef, useState, useMemo, ChangeEvent } from "react";
|
||||
import uniqBy from "lodash.uniqby";
|
||||
|
||||
import { LIVE_STREAM_CHAT } from "../const";
|
||||
import useEmoji from "../hooks/emoji";
|
||||
import useEmoji, { packId } from "../hooks/emoji";
|
||||
import { useLogin } from "../hooks/login";
|
||||
import { System } from "../index";
|
||||
import AsyncButton from "./async-button";
|
||||
@ -27,7 +28,9 @@ export function WriteMessage({ link }: { link: NostrLink }) {
|
||||
const channelEmojis = channelEmojiPacks.map((pack) => pack.emojis).flat();
|
||||
const emojis = userEmojis.concat(channelEmojis);
|
||||
const names = emojis.map((t) => t.at(1));
|
||||
const allEmojiPacks = userEmojiPacks.concat(channelEmojiPacks);
|
||||
const allEmojiPacks = useMemo(() => {
|
||||
return uniqBy(channelEmojiPacks.concat(userEmojiPacks), packId);
|
||||
}, [userEmojiPacks, channelEmojiPacks]);
|
||||
// @ts-expect-error
|
||||
const topOffset = ref.current?.getBoundingClientRect().top;
|
||||
// @ts-expect-error
|
||||
@ -122,4 +125,3 @@ export function WriteMessage({ link }: { link: NostrLink }) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -3,12 +3,14 @@ import {
|
||||
EventKind,
|
||||
ReplaceableNoteStore,
|
||||
NoteCollection,
|
||||
NostrEvent,
|
||||
} from "@snort/system";
|
||||
import { useRequestBuilder } from "@snort/system-react";
|
||||
import { System } from "index";
|
||||
import { useMemo } from "react";
|
||||
import { findTag } from "utils";
|
||||
import type { EmojiTag } from "../element/emoji";
|
||||
import uniqBy from "lodash.uniqby";
|
||||
|
||||
export interface EmojiPack {
|
||||
address: string;
|
||||
@ -17,6 +19,20 @@ export interface EmojiPack {
|
||||
emojis: EmojiTag[];
|
||||
}
|
||||
|
||||
function toEmojiPack(ev: NostrEvent): EmojiPack {
|
||||
const d = findTag(ev, "d") || "";
|
||||
return {
|
||||
address: `${ev.kind}:${ev.pubkey}:${d}`,
|
||||
name: d,
|
||||
author: ev.pubkey,
|
||||
emojis: ev.tags.filter((t) => t.at(0) === "emoji") as EmojiTag[],
|
||||
};
|
||||
}
|
||||
|
||||
export function packId(pack: EmojiPack): string {
|
||||
return `${pack.author}:${pack.name}`;
|
||||
}
|
||||
|
||||
export default function useEmoji(pubkey: string) {
|
||||
const sub = useMemo(() => {
|
||||
const rb = new RequestBuilder(`emoji:${pubkey}`);
|
||||
@ -61,11 +77,14 @@ export default function useEmoji(pubkey: string) {
|
||||
.authors(authors)
|
||||
.tag("d", identifiers);
|
||||
|
||||
rb.withFilter()
|
||||
.kinds([30030 as EventKind])
|
||||
.authors([pubkey]);
|
||||
|
||||
return rb;
|
||||
}, [pubkey, related]);
|
||||
|
||||
const { data: relatedData } =
|
||||
useRequestBuilder<NoteCollection>(
|
||||
const { data: relatedData } = useRequestBuilder<NoteCollection>(
|
||||
System,
|
||||
NoteCollection,
|
||||
subRelated
|
||||
@ -76,16 +95,8 @@ export default function useEmoji(pubkey: string) {
|
||||
}, [relatedData]);
|
||||
|
||||
const emojis = useMemo(() => {
|
||||
return emojiPacks.map((ev) => {
|
||||
const d = findTag(ev, "d");
|
||||
return {
|
||||
address: `${ev.kind}:${ev.pubkey}:${d}`,
|
||||
name: d,
|
||||
author: ev.pubkey,
|
||||
emojis: ev.tags.filter((t) => t.at(0) === "emoji") as EmojiTag[],
|
||||
} as EmojiPack;
|
||||
});
|
||||
}, [userEmoji, emojiPacks]);
|
||||
return uniqBy(emojiPacks.map(toEmojiPack), packId);
|
||||
}, [emojiPacks]);
|
||||
|
||||
return emojis;
|
||||
}
|
||||
|
@ -9,22 +9,20 @@ import { System } from "index";
|
||||
import { useMemo } from "react";
|
||||
import { LIVE_STREAM_CHAT } from "const";
|
||||
|
||||
export function useLiveChatFeed(link: NostrLink) {
|
||||
export function useLiveChatFeed(link: NostrLink, host?: string) {
|
||||
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]);
|
||||
rb.withFilter().kinds([LIVE_STREAM_CHAT]).tag("a", [aTag]).limit(100);
|
||||
rb.withFilter().kinds([EventKind.ZapReceipt]).tag("a", [aTag]);
|
||||
if (host) {
|
||||
rb.withFilter().kinds([EventKind.ZapReceipt]).tag("p", [host]);
|
||||
}
|
||||
return rb;
|
||||
}, [link]);
|
||||
}, [link, host]);
|
||||
|
||||
const feed = useRequestBuilder<FlatNoteStore>(System, FlatNoteStore, sub);
|
||||
|
||||
|
20
src/hooks/usePreviousValue.ts
Normal file
20
src/hooks/usePreviousValue.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
/**
|
||||
* On each render returns the previous value of the given variable/constant.
|
||||
*/
|
||||
const usePreviousValue = <TValue>(value?: TValue): TValue | undefined => {
|
||||
const prevValue = useRef<TValue>();
|
||||
|
||||
useEffect(() => {
|
||||
prevValue.current = value;
|
||||
|
||||
return () => {
|
||||
prevValue.current = undefined;
|
||||
};
|
||||
});
|
||||
|
||||
return prevValue.current;
|
||||
};
|
||||
|
||||
export default usePreviousValue;
|
@ -205,6 +205,7 @@ button span.hide-on-mobile {
|
||||
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
|
@ -47,8 +47,7 @@ function ProfileInfo({ ev, goal }: { ev?: NostrEvent; goal?: TaggedRawEvent }) {
|
||||
<div className="f-grow stream-info">
|
||||
<h1>{findTag(ev, "title")}</h1>
|
||||
<p>{findTag(ev, "summary")}</p>
|
||||
{ev && (
|
||||
<Tags ev={ev}>
|
||||
<div className="tags">
|
||||
<StatePill state={status as StreamState} />
|
||||
{viewers > 0 && (
|
||||
<span className="pill viewers">
|
||||
@ -60,8 +59,8 @@ function ProfileInfo({ ev, goal }: { ev?: NostrEvent; goal?: TaggedRawEvent }) {
|
||||
<StreamTimer ev={ev} />
|
||||
</span>
|
||||
)}
|
||||
</Tags>
|
||||
)}
|
||||
{ev && <Tags ev={ev} />}
|
||||
</div>
|
||||
{isMine && (
|
||||
<div className="actions">
|
||||
{ev && <NewStreamDialog text="Edit" ev={ev} btnClassName="btn" />}
|
||||
|
14
yarn.lock
14
yarn.lock
@ -1827,7 +1827,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.34.tgz#c0fb25e4d957e0ee2e497c1f553d7f8bb668fd75"
|
||||
integrity sha512-s2cfwagOQAS8o06TcwKfr9Wx11dNGbH2E9vJz1cqV+a/LOyhWNLUNd6JSRYNzvB4d29UuJX2M0Dj9vE1T8fRXw==
|
||||
|
||||
"@types/lodash@^4.14.195":
|
||||
"@types/lodash.uniqby@^4.7.7":
|
||||
version "4.7.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash.uniqby/-/lodash.uniqby-4.7.7.tgz#48dbb652c41cc8fb30aa61a44174368081835ab5"
|
||||
integrity sha512-sv2g6vkCIvEUsK5/Vq17haoZaisfj2EWW8mP7QWlnKi6dByoNmeuHDDXHR7sabuDqwO4gvU7ModIL22MmnOocg==
|
||||
dependencies:
|
||||
"@types/lodash" "*"
|
||||
|
||||
"@types/lodash@*", "@types/lodash@^4.14.195":
|
||||
version "4.14.195"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632"
|
||||
integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==
|
||||
@ -4558,6 +4565,11 @@ lodash.uniq@^4.5.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
|
||||
|
||||
lodash.uniqby@^4.7.0:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302"
|
||||
integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==
|
||||
|
||||
lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
|
Loading…
Reference in New Issue
Block a user