refactor: useRequestBuilder

This commit is contained in:
2024-09-12 16:26:30 +01:00
parent 9eb97701b4
commit e877625923
28 changed files with 195 additions and 205 deletions

View File

@ -1,54 +0,0 @@
.stream-list {
display: flex;
gap: 4px;
overflow-x: auto;
}
.stream-list::-webkit-scrollbar {
height: 6.25px;
}
.stream-event {
display: flex;
padding: 8px 12px;
gap: 8px;
text-decoration: none;
}
.stream-event > div:first-of-type {
border-radius: 8px;
height: 49px;
width: 65px;
background-color: var(--gray-light);
background-image: var(--img);
background-position: center;
background-size: cover;
}
.stream-event span.live {
display: flex;
padding: 4px 6px;
justify-content: center;
align-items: center;
gap: 4px;
border-radius: 9px;
background: var(--live);
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
}
.stream-event .details .reactions {
color: var(--font-secondary-color);
}
.stream-event .details > div:nth-of-type(2) {
width: 100px;
min-width: 0;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-size: 16px;
font-weight: 600;
line-height: 24px;
}

View File

@ -1,32 +1,49 @@
import "./LiveStreams.css";
import { unixNow } from "@snort/shared"; import { unixNow } from "@snort/shared";
import { EventKind, NostrEvent, NostrLink, RequestBuilder } from "@snort/system"; import { EventKind, NostrEvent, NostrLink, RequestBuilder } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react"; import { useRequestBuilder, useUserProfile } from "@snort/system-react";
import { CSSProperties, useMemo } from "react"; import { CSSProperties, useMemo } from "react";
import { FormattedMessage } from "react-intl";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import Icon from "@/Components/Icons/Icon";
import useFollowsControls from "@/Hooks/useFollowControls"; import useFollowsControls from "@/Hooks/useFollowControls";
import useImgProxy from "@/Hooks/useImgProxy"; import useImgProxy from "@/Hooks/useImgProxy";
import { findTag } from "@/Utils"; import { findTag } from "@/Utils";
import { Hour } from "@/Utils/Const";
import Avatar from "../User/Avatar";
export function LiveStreams() { export function LiveStreams() {
const { followList } = useFollowsControls(); const { followList } = useFollowsControls();
const sub = useMemo(() => { const sub = useMemo(() => {
const since = unixNow() - 60 * 60 * 24;
const rb = new RequestBuilder("follows:streams"); const rb = new RequestBuilder("follows:streams");
rb.withFilter().kinds([EventKind.LiveEvent]).authors(followList).since(since); if (followList.length > 0) {
rb.withFilter().kinds([EventKind.LiveEvent]).tag("p", followList).since(since); rb.withFilter()
.kinds([EventKind.LiveEvent])
.authors(followList)
.since(unixNow() - Hour);
rb.withFilter()
.kinds([EventKind.LiveEvent])
.tag("p", followList)
.since(unixNow() - Hour);
}
return rb; return rb;
}, [followList]); }, [followList.length]);
const streams = useRequestBuilder(sub); const streams = useRequestBuilder(sub);
if (streams.length === 0) return null; if (streams.length === 0) return null;
return ( return (
<div className="stream-list"> <div className="flex mx-2 gap-4 overflow-x-auto sm-hide-scrollbar">
{streams.map(v => ( {streams
.filter(a => {
return findTag(a, "status") === "live";
})
.sort((a, b) => {
const sA = Number(findTag(a, "starts"));
const sB = Number(findTag(b, "starts"));
return sA > sB ? -1 : 1;
})
.map(v => (
<LiveStreamEvent ev={v} key={`${v.kind}:${v.pubkey}:${findTag(v, "d")}`} /> <LiveStreamEvent ev={v} key={`${v.kind}:${v.pubkey}:${findTag(v, "d")}`} />
))} ))}
</div> </div>
@ -38,27 +55,33 @@ function LiveStreamEvent({ ev }: { ev: NostrEvent }) {
const title = findTag(ev, "title"); const title = findTag(ev, "title");
const image = findTag(ev, "image"); const image = findTag(ev, "image");
const status = findTag(ev, "status"); const status = findTag(ev, "status");
const viewers = findTag(ev, "current_participants");
const host = ev.tags.find(a => a[0] === "p" && a[3] === "host")?.[1] ?? ev.pubkey;
const hostProfile = useUserProfile(host);
const link = NostrLink.fromEvent(ev).encode(CONFIG.eventLinkPrefix); const link = NostrLink.fromEvent(ev).encode();
const imageProxy = proxy(image ?? ""); const imageProxy = proxy(image ?? "");
return ( return (
<Link className="stream-event" to={`https://zap.stream/${link}`} target="_blank"> <Link className="flex gap-2 h-[80px]" to={`https://zap.stream/${link}`} target="_blank">
<div className="relative aspect-video">
<div <div
className="absolute h-full w-full bg-center bg-cover bg-gray-ultradark rounded-lg"
style={ style={
{ {
"--img": `url(${imageProxy})`, backgroundImage: `url(${imageProxy})`,
} as CSSProperties } as CSSProperties
}></div> }></div>
<div className="flex flex-col details"> <div className="absolute left-0 top-7 w-full overflow-hidden">
<div className="flex g2"> <div className="whitespace-nowrap px-2 text-ellipsis overflow-hidden text-xs">{title}</div>
<span className="live">{status}</span>
<div className="reaction-pill">
<Icon name="zap" size={24} />
<div className="reaction-pill-number">0</div>
</div> </div>
<div className="absolute top-1 left-1 bg-heart rounded-md px-2 uppercase font-bold">{status}</div>
<div className="absolute right-1 top-1">
<Avatar pubkey={host} user={hostProfile} size={25} className="outline outline-2 outline-highlight" />
</div>
<div className="absolute left-1 bottom-1 rounded-md px-2 py-1 text-xs bg-gray font-medium">
<FormattedMessage defaultMessage="{n} viewers" values={{ n: viewers }} />
</div> </div>
<div>{title}</div>
</div> </div>
</Link> </Link>
); );

View File

@ -11,14 +11,14 @@ import UptimeLabel from "./uptime-label";
export default function RelayUptime({ url }: { url: string }) { export default function RelayUptime({ url }: { url: string }) {
const sub = useMemo(() => { const sub = useMemo(() => {
const u = sanitizeRelayUrl(url); const u = sanitizeRelayUrl(url);
if (!u) return;
const rb = new RequestBuilder(`uptime`); const rb = new RequestBuilder(`uptime`);
if (u) {
rb.withFilter() rb.withFilter()
.kinds([30_166 as EventKind]) .kinds([30_166 as EventKind])
.tag("d", [u]) .tag("d", [u])
.since(unixNow() - Day) .since(unixNow() - Day)
.relay(MonitorRelays); .relay(MonitorRelays);
}
return rb; return rb;
}, [url]); }, [url]);

View File

@ -2,18 +2,18 @@ import { EventKind, RequestBuilder } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react"; import { useRequestBuilder } from "@snort/system-react";
import { useMemo } from "react"; import { useMemo } from "react";
import useLogin from "@/Hooks/useLogin"; import useFollowsControls from "@/Hooks/useFollowControls";
export function useArticles() { export function useArticles() {
const { publicKey, follows } = useLogin(s => ({ publicKey: s.publicKey, follows: s.state.follows })); const { followList } = useFollowsControls();
const sub = useMemo(() => { const sub = useMemo(() => {
if (!publicKey) return null; const rb = new RequestBuilder("articles");
const rb = new RequestBuilder(`articles:${publicKey}`); if (followList.length > 0) {
rb.withFilter().kinds([EventKind.LongFormTextNote]).authors(follows).limit(20); rb.withFilter().kinds([EventKind.LongFormTextNote]).authors(followList).limit(20);
}
return rb; return rb;
}, [follows, publicKey]); }, [followList]);
return useRequestBuilder(sub); return useRequestBuilder(sub);
} }

View File

@ -11,9 +11,10 @@ type BadgeAwards = {
export default function useProfileBadges(pubkey?: HexKey) { export default function useProfileBadges(pubkey?: HexKey) {
const sub = useMemo(() => { const sub = useMemo(() => {
if (!pubkey) return null; const b = new RequestBuilder("badges");
const b = new RequestBuilder(`badges:${pubkey.slice(0, 12)}`); if (pubkey) {
b.withFilter().kinds([EventKind.ProfileBadges]).tag("d", ["profile_badges"]).authors([pubkey]); b.withFilter().kinds([EventKind.ProfileBadges]).tag("d", ["profile_badges"]).authors([pubkey]);
}
return b; return b;
}, [pubkey]); }, [pubkey]);
@ -47,10 +48,11 @@ export default function useProfileBadges(pubkey?: HexKey) {
const awardsSub = useMemo(() => { const awardsSub = useMemo(() => {
const ids = Object.keys(profile); const ids = Object.keys(profile);
if (!pubkey || ids.length === 0) return null; const b = new RequestBuilder(`profile_awards`);
const b = new RequestBuilder(`profile_awards:${pubkey.slice(0, 12)}`); if (pubkey && ids.length > 0) {
b.withFilter().kinds([EventKind.BadgeAward]).ids(ids); b.withFilter().kinds([EventKind.BadgeAward]).ids(ids);
b.withFilter().kinds([EventKind.Badge]).tag("d", ds).authors(pubkeys); b.withFilter().kinds([EventKind.Badge]).tag("d", ds).authors(pubkeys);
}
return b; return b;
}, [profile, ds]); }, [profile, ds]);

View File

@ -4,9 +4,10 @@ import { useMemo } from "react";
export default function useFollowersFeed(pubkey?: HexKey) { export default function useFollowersFeed(pubkey?: HexKey) {
const sub = useMemo(() => { const sub = useMemo(() => {
if (!pubkey) return null; const b = new RequestBuilder(`followers`);
const b = new RequestBuilder(`followers:${pubkey.slice(0, 12)}`); if (pubkey) {
b.withFilter().kinds([EventKind.ContactList]).tag("p", [pubkey]); b.withFilter().kinds([EventKind.ContactList]).tag("p", [pubkey]);
}
return b; return b;
}, [pubkey]); }, [pubkey]);

View File

@ -2,27 +2,19 @@ import { EventKind, HexKey, RequestBuilder, TaggedNostrEvent } from "@snort/syst
import { useRequestBuilder } from "@snort/system-react"; import { useRequestBuilder } from "@snort/system-react";
import { useMemo } from "react"; import { useMemo } from "react";
import useLogin from "@/Hooks/useLogin";
export default function useFollowsFeed(pubkey?: HexKey) { export default function useFollowsFeed(pubkey?: HexKey) {
const { publicKey, follows } = useLogin(s => ({ publicKey: s.publicKey, follows: s.state.follows }));
const isMe = publicKey === pubkey;
const sub = useMemo(() => { const sub = useMemo(() => {
if (isMe || !pubkey) return null; const b = new RequestBuilder(`follows:for`);
const b = new RequestBuilder(`follows:${pubkey.slice(0, 12)}`); if (pubkey) {
b.withFilter().kinds([EventKind.ContactList]).authors([pubkey]); b.withFilter().kinds([EventKind.ContactList]).authors([pubkey]);
}
return b; return b;
}, [isMe, pubkey]); }, [pubkey]);
const contactFeed = useRequestBuilder(sub); const contactFeed = useRequestBuilder(sub);
return useMemo(() => { return useMemo(() => {
if (isMe) {
return follows;
}
return getFollowing(contactFeed ?? [], pubkey); return getFollowing(contactFeed ?? [], pubkey);
}, [isMe, contactFeed, follows, pubkey]); }, [contactFeed, pubkey]);
} }
export function getFollowing(notes: readonly TaggedNostrEvent[], pubkey?: HexKey) { export function getFollowing(notes: readonly TaggedNostrEvent[], pubkey?: HexKey) {

View File

@ -1,4 +1,4 @@
import { unixNow } from "@snort/shared"; import { removeUndefined, unixNow } from "@snort/shared";
import { EventKind, RequestBuilder } from "@snort/system"; import { EventKind, RequestBuilder } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react"; import { useRequestBuilder } from "@snort/system-react";
import { useMemo } from "react"; import { useMemo } from "react";
@ -7,12 +7,12 @@ import useLogin from "@/Hooks/useLogin";
import { Hour } from "@/Utils/Const"; import { Hour } from "@/Utils/Const";
export default function useHashtagsFeed() { export default function useHashtagsFeed() {
const { hashtags } = useLogin(s => ({ hashtags: s.tags.item })); const { hashtags } = useLogin(s => ({ hashtags: s.state.getList(EventKind.InterestsList) }));
const sub = useMemo(() => { const sub = useMemo(() => {
const rb = new RequestBuilder("hashtags-feed"); const rb = new RequestBuilder("hashtags-feed");
rb.withFilter() rb.withFilter()
.kinds([EventKind.TextNote, EventKind.LiveEvent, EventKind.LongFormTextNote, EventKind.Polls]) .kinds([EventKind.TextNote, EventKind.LiveEvent, EventKind.LongFormTextNote, EventKind.Polls])
.tag("t", hashtags) .tag("t", removeUndefined(hashtags.map(a => a.toEventTag()?.[1])))
.since(unixNow() - Hour * 6); .since(unixNow() - Hour * 6);
return rb; return rb;
}, [hashtags]); }, [hashtags]);

View File

@ -28,19 +28,19 @@ export default function useLoginFeed() {
}, [login, publisher, system]); }, [login, publisher, system]);
const subLogin = useMemo(() => { const subLogin = useMemo(() => {
if (!login || !pubKey) return null;
if (CONFIG.features.subscriptions && !login.readonly) { if (CONFIG.features.subscriptions && !login.readonly) {
const b = new RequestBuilder(`login:${pubKey.slice(0, 12)}`); const b = new RequestBuilder(`login`);
b.withOptions({ b.withOptions({
leaveOpen: true, leaveOpen: true,
}); });
if (pubKey) {
b.withFilter() b.withFilter()
.relay("wss://relay.snort.social/") .relay("wss://relay.snort.social/")
.kinds([EventKind.SnortSubscriptions]) .kinds([EventKind.SnortSubscriptions])
.authors([bech32ToHex(SnortPubKey)]) .authors([bech32ToHex(SnortPubKey)])
.tag("p", [pubKey]) .tag("p", [pubKey])
.limit(10); .limit(10);
}
return b; return b;
} }
}, [pubKey, login]); }, [pubKey, login]);

View File

@ -4,9 +4,10 @@ import { useMemo } from "react";
export default function useRelaysFeed(pubkey?: HexKey) { export default function useRelaysFeed(pubkey?: HexKey) {
const sub = useMemo(() => { const sub = useMemo(() => {
if (!pubkey) return null; const b = new RequestBuilder(`relays:${pubkey ?? ""}`);
const b = new RequestBuilder(`relays:${pubkey.slice(0, 12)}`); if (pubkey) {
b.withFilter().authors([pubkey]).kinds([EventKind.Relays]); b.withFilter().authors([pubkey]).kinds([EventKind.Relays]);
}
return b; return b;
}, [pubkey]); }, [pubkey]);

View File

@ -7,14 +7,13 @@ import { findTag } from "@/Utils";
export function useStatusFeed(id?: string, leaveOpen = false) { export function useStatusFeed(id?: string, leaveOpen = false) {
const sub = useMemo(() => { const sub = useMemo(() => {
if (!id) return null;
const rb = new RequestBuilder(`statud:${id}`); const rb = new RequestBuilder(`statud:${id}`);
rb.withOptions({ leaveOpen }); rb.withOptions({ leaveOpen });
if (id) {
rb.withFilter() rb.withFilter()
.kinds([30315 as EventKind]) .kinds([30315 as EventKind])
.authors([id]); .authors([id]);
}
return rb; return rb;
}, [id]); }, [id]);

View File

@ -34,10 +34,6 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
const { isEventMuted } = useModeration(); const { isEventMuted } = useModeration();
const createBuilder = useCallback(() => { const createBuilder = useCallback(() => {
if (subject.type !== "global" && subject.items.length === 0) {
return null;
}
const kinds = const kinds =
subject.kinds ?? subject.kinds ??
(subject.type === "profile_keyword" (subject.type === "profile_keyword"
@ -80,7 +76,6 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
const sub = useMemo(() => { const sub = useMemo(() => {
const rb = createBuilder(); const rb = createBuilder();
if (rb) {
for (const filter of rb.filterBuilders) { for (const filter of rb.filterBuilders) {
if (options.method === "LIMIT_UNTIL") { if (options.method === "LIMIT_UNTIL") {
filter.until(until).limit(50); filter.until(until).limit(50);
@ -92,7 +87,6 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
} }
} }
return rb; return rb;
}
}, [until, since, options.method, createBuilder]); }, [until, since, options.method, createBuilder]);
const mainQuery = useRequestBuilderAdvanced(sub); const mainQuery = useRequestBuilderAdvanced(sub);
@ -108,7 +102,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
const subRealtime = useMemo(() => { const subRealtime = useMemo(() => {
const rb = createBuilder(); const rb = createBuilder();
if (rb && !autoShowLatest && options.method !== "LIMIT_UNTIL") { if (!autoShowLatest && options.method !== "LIMIT_UNTIL") {
rb.withOptions({ rb.withOptions({
leaveOpen: true, leaveOpen: true,
}); });
@ -116,8 +110,11 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
for (const filter of rb.filterBuilders) { for (const filter of rb.filterBuilders) {
filter.limit(1).since(now); filter.limit(1).since(now);
} }
return rb; } else {
rb.clear();
} }
return rb;
}, [autoShowLatest, createBuilder]); }, [autoShowLatest, createBuilder]);
const latestQuery = useRequestBuilderAdvanced(subRealtime); const latestQuery = useRequestBuilderAdvanced(subRealtime);

View File

@ -4,9 +4,10 @@ import { useMemo } from "react";
export default function useZapsFeed(link?: NostrLink) { export default function useZapsFeed(link?: NostrLink) {
const sub = useMemo(() => { const sub = useMemo(() => {
if (!link) return null; const b = new RequestBuilder(`zaps:${link?.encode()}`);
const b = new RequestBuilder(`zaps:${link.encode()}`); if (link) {
b.withFilter().kinds([EventKind.ZapReceipt]).replyToLink([link]); b.withFilter().kinds([EventKind.ZapReceipt]).replyToLink([link]);
}
return b; return b;
}, [link]); }, [link]);

View File

@ -7,9 +7,7 @@ import { createEmptyChatObject } from "@/chat";
export function useEmptyChatSystem(id?: string) { export function useEmptyChatSystem(id?: string) {
const sub = useMemo(() => { const sub = useMemo(() => {
if (!id) return; if (id?.startsWith(NostrPrefix.Chat28)) {
if (id.startsWith(NostrPrefix.Chat28)) {
const cx = unwrap(decodeTLV(id).find(a => a.type === TLVEntryType.Special)).value as string; const cx = unwrap(decodeTLV(id).find(a => a.type === TLVEntryType.Special)).value as string;
const rb = new RequestBuilder(`nip28:${id}`); const rb = new RequestBuilder(`nip28:${id}`);
rb.withFilter().ids([cx]).kinds([EventKind.PublicChatChannel, EventKind.PublicChatMetadata]); rb.withFilter().ids([cx]).kinds([EventKind.PublicChatChannel, EventKind.PublicChatMetadata]);
@ -18,6 +16,8 @@ export function useEmptyChatSystem(id?: string) {
.kinds([EventKind.PublicChatChannel, EventKind.PublicChatMessage, EventKind.PublicChatMetadata]); .kinds([EventKind.PublicChatChannel, EventKind.PublicChatMessage, EventKind.PublicChatMetadata]);
return rb; return rb;
} else {
return new RequestBuilder(id ?? "");
} }
}, [id]); }, [id]);

View File

@ -9,9 +9,7 @@ export function useLinkList(id: string, fn: (rb: RequestBuilder) => void) {
const sub = useMemo(() => { const sub = useMemo(() => {
const rb = new RequestBuilder(id); const rb = new RequestBuilder(id);
fn(rb); fn(rb);
if (rb.numFilters > 0) {
return rb; return rb;
}
}, [id, fn]); }, [id, fn]);
const listStore = useRequestBuilder(sub); const listStore = useRequestBuilder(sub);

View File

@ -1,6 +1,7 @@
import { lazy } from "react"; import { lazy } from "react";
import { Outlet, RouteObject } from "react-router-dom"; import { Outlet, RouteObject } from "react-router-dom";
import { LiveStreams } from "@/Components/LiveStream/LiveStreams";
import { RootTabRoutes } from "@/Pages/Root/RootTabRoutes"; import { RootTabRoutes } from "@/Pages/Root/RootTabRoutes";
import { getCurrentRefCode } from "@/Utils"; import { getCurrentRefCode } from "@/Utils";
@ -9,6 +10,7 @@ export default function RootPage() {
const code = getCurrentRefCode(); const code = getCurrentRefCode();
return ( return (
<> <>
<LiveStreams />
<div className="main-content"> <div className="main-content">
<Outlet /> <Outlet />
</div> </div>

View File

@ -63,7 +63,7 @@ export interface ChatSystem {
/** /**
* Create a request for this system to get updates * Create a request for this system to get updates
*/ */
subscription(session: LoginSession): RequestBuilder | undefined; subscription(session: LoginSession): RequestBuilder;
/** /**
* Create a list of chats for a given pubkey and set of events * Create a list of chats for a given pubkey and set of events

View File

@ -31,10 +31,11 @@ export class Nip17ChatSystem extends ExternalStore<Array<Chat>> implements ChatS
subscription(session: LoginSession) { subscription(session: LoginSession) {
const pk = session.publicKey; const pk = session.publicKey;
if (!pk || session.readonly) return; const rb = new RequestBuilder(`nip17:${pk?.slice(0, 12)}`);
const rb = new RequestBuilder(`nip17:${pk.slice(0, 12)}`); if (pk && !session.readonly) {
rb.withFilter().kinds([EventKind.GiftWrap]).tag("p", [pk]); rb.withFilter().kinds([EventKind.GiftWrap]).tag("p", [pk]);
}
return rb; return rb;
} }

View File

@ -25,13 +25,12 @@ export class Nip28ChatSystem implements ChatSystem {
EventKind.PublicChatMuteUser, EventKind.PublicChatMuteUser,
]; ];
subscription(session: LoginSession): RequestBuilder | undefined { subscription(session: LoginSession): RequestBuilder {
const chats = (session.extraChats ?? []).filter(a => a.startsWith(NostrPrefix.Chat28)); const chats = (session.extraChats ?? []).filter(a => a.startsWith(NostrPrefix.Chat28));
if (chats.length === 0) return;
const chatId = (v: string) => unwrap(decodeTLV(v).find(a => a.type === TLVEntryType.Special)).value as string; const chatId = (v: string) => unwrap(decodeTLV(v).find(a => a.type === TLVEntryType.Special)).value as string;
const rb = new RequestBuilder(`nip28:${session.id}`); const rb = new RequestBuilder(`nip28:${session.id}`);
if (chats.length > 0) {
rb.withFilter() rb.withFilter()
.ids(chats.map(v => chatId(v))) .ids(chats.map(v => chatId(v)))
.kinds([EventKind.PublicChatChannel, EventKind.PublicChatMetadata]); .kinds([EventKind.PublicChatChannel, EventKind.PublicChatMetadata]);
@ -39,7 +38,7 @@ export class Nip28ChatSystem implements ChatSystem {
const id = chatId(c); const id = chatId(c);
rb.withFilter().tag("e", [id]).kinds(this.ChannelKinds); rb.withFilter().tag("e", [id]).kinds(this.ChannelKinds);
} }
}
return rb; return rb;
} }

View File

@ -59,6 +59,7 @@
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 8px; width: 8px;
height: 8px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
@ -940,6 +941,15 @@ svg.repeat {
.pb-safe-area-plus-footer { .pb-safe-area-plus-footer {
padding-bottom: calc(env(safe-area-inset-bottom) + 56px); padding-bottom: calc(env(safe-area-inset-bottom) + 56px);
} }
.sm-hide-scrollbar {
scrollbar-width: none;
-ms-overflow-style: none;
}
.sm-hide-scrollbar::-webkit-scrollbar {
display: none;
/* Safari and Chrome */
}
} }
.active > .icon-outline { .active > .icon-outline {

View File

@ -189,6 +189,9 @@
"3QwfJR": { "3QwfJR": {
"defaultMessage": "~{amount}" "defaultMessage": "~{amount}"
}, },
"3adEeb": {
"defaultMessage": "{n} viewers"
},
"3cc4Ct": { "3cc4Ct": {
"defaultMessage": "Light" "defaultMessage": "Light"
}, },

View File

@ -62,6 +62,7 @@
"3GWu6/": "User Statuses", "3GWu6/": "User Statuses",
"3KNMbJ": "Articles", "3KNMbJ": "Articles",
"3QwfJR": "~{amount}", "3QwfJR": "~{amount}",
"3adEeb": "{n} viewers",
"3cc4Ct": "Light", "3cc4Ct": "Light",
"3gOsZq": "Translators", "3gOsZq": "Translators",
"3kbIhS": "Untitled", "3kbIhS": "Untitled",

View File

@ -40,6 +40,15 @@ module.exports = {
background: "var(--bg-color)", background: "var(--bg-color)",
secondary: "var(--bg-secondary)", secondary: "var(--bg-secondary)",
}, },
animation: {
"infinite-scroll": "infinite-scroll 25s linear infinite",
},
keyframes: {
"infinite-scroll": {
from: { transform: "translateX(0)" },
to: { transform: "translateX(-100%)" },
},
},
}, },
}, },
plugins: [], plugins: [],

View File

@ -30,7 +30,7 @@ export function useReactions(
} }
} }
others?.(rb); others?.(rb);
return rb.numFilters > 0 ? rb : undefined; return rb;
}, [ids]); }, [ids]);
return useRequestBuilder(sub); return useRequestBuilder(sub);

View File

@ -5,11 +5,10 @@ import { SnortContext } from "./context";
/** /**
* Send a query to the relays and wait for data * Send a query to the relays and wait for data
*/ */
export function useRequestBuilder(rb: RequestBuilder | null | undefined) { export function useRequestBuilder(rb: RequestBuilder) {
const system = useContext(SnortContext); const system = useContext(SnortContext);
return useSyncExternalStore( return useSyncExternalStore(
v => { v => {
if (rb) {
const q = system.Query(rb); const q = system.Query(rb);
q.on("event", v); q.on("event", v);
q.uncancel(); q.uncancel();
@ -17,13 +16,9 @@ export function useRequestBuilder(rb: RequestBuilder | null | undefined) {
q.off("event", v); q.off("event", v);
q.cancel(); q.cancel();
}; };
}
return () => {
// noop
};
}, },
() => { () => {
const q = system.GetQuery(rb?.id ?? ""); const q = system.GetQuery(rb.id);
if (q) { if (q) {
return q.snapshot; return q.snapshot;
} else { } else {
@ -36,14 +31,12 @@ export function useRequestBuilder(rb: RequestBuilder | null | undefined) {
/** /**
* More advanced hook which returns the Query object * More advanced hook which returns the Query object
*/ */
export function useRequestBuilderAdvanced(rb: RequestBuilder | null | undefined) { export function useRequestBuilderAdvanced(rb: RequestBuilder) {
const system = useContext(SnortContext); const system = useContext(SnortContext);
const q = useMemo(() => { const q = useMemo(() => {
if (rb) {
const q = system.Query(rb); const q = system.Query(rb);
q.uncancel(); q.uncancel();
return q; return q;
}
}, [rb]); }, [rb]);
useEffect(() => { useEffect(() => {
return () => { return () => {

View File

@ -56,7 +56,9 @@ export class QueryManager extends EventEmitter<QueryManagerEvents> {
}); });
this.#queries.set(req.id, q); this.#queries.set(req.id, q);
if (req.numFilters > 0) {
this.emit("change"); this.emit("change");
}
return q; return q;
} }
} }

View File

@ -181,11 +181,14 @@ export class Query extends EventEmitter<QueryEvents> {
* Adds another request to this one * Adds another request to this one
*/ */
addRequest(req: RequestBuilder) { addRequest(req: RequestBuilder) {
if (req.numFilters > 0) {
this.#log("Add query %O to %s", req, this.id); this.#log("Add query %O to %s", req, this.id);
this.requests.push(...req.buildRaw()); this.requests.push(...req.buildRaw());
this.#start(); this.#start();
return true; return true;
} }
return false;
}
isOpen() { isOpen() {
return this.#cancelAt === undefined && this.#leaveOpen; return this.#cancelAt === undefined && this.#leaveOpen;
@ -326,8 +329,10 @@ export class Query extends EventEmitter<QueryEvents> {
if (this.#replaceable) { if (this.#replaceable) {
rawFilters.push(...this.filters); rawFilters.push(...this.filters);
} }
if (rawFilters.length > 0) {
this.emit("request", this.id, rawFilters); this.emit("request", this.id, rawFilters);
} }
}
#stopCheckTraces() { #stopCheckTraces() {
if (this.#checkTrace) { if (this.#checkTrace) {

View File

@ -72,6 +72,11 @@ export class RequestBuilder {
return this.#options; return this.#options;
} }
clear() {
this.#builders = [];
this.#options = undefined;
}
/** /**
* Add another request builders filters to this one * Add another request builders filters to this one
*/ */