chore: cleanup subs

This commit is contained in:
Kieran 2023-01-24 14:09:56 +00:00
parent a49b40d9b3
commit 3cbec9f272
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
7 changed files with 93 additions and 37 deletions

View File

@ -15,7 +15,7 @@ import LNURLTip from "Element/LNURLTip";
import Copy from "Element/Copy"; import Copy from "Element/Copy";
import useProfile from "Feed/ProfileFeed"; import useProfile from "Feed/ProfileFeed";
import useEventPublisher from "Feed/EventPublisher"; import useEventPublisher from "Feed/EventPublisher";
import { hexToBech32 } from "Util"; import { debounce, hexToBech32 } from "Util";
import { UserMetadata } from "Nostr"; import { UserMetadata } from "Nostr";
type Nip05ServiceProps = { type Nip05ServiceProps = {
@ -77,7 +77,7 @@ export default function Nip5Service(props: Nip05ServiceProps) {
setAvailabilityResponse({ available: false, why: "REGEX" }); setAvailabilityResponse({ available: false, why: "REGEX" });
return; return;
} }
let t = setTimeout(() => { return debounce(500, () => {
svc.CheckAvailable(handle, domain) svc.CheckAvailable(handle, domain)
.then(a => { .then(a => {
if ('error' in a) { if ('error' in a) {
@ -87,8 +87,7 @@ export default function Nip5Service(props: Nip05ServiceProps) {
} }
}) })
.catch(console.error); .catch(console.error);
}, 500); });
return () => clearTimeout(t);
} }
}, [handle, domain]); }, [handle, domain]);
@ -182,7 +181,7 @@ export default function Nip5Service(props: Nip05ServiceProps) {
show={showInvoice} show={showInvoice}
onClose={() => setShowInvoice(false)} onClose={() => setShowInvoice(false)}
title={`Buying ${handle}@${domain}`} title={`Buying ${handle}@${domain}`}
notice="DO NOT CLOSE THIS POPUP OR YOUR ORDER WILL GET STUCK"/> notice="DO NOT CLOSE THIS POPUP OR YOUR ORDER WILL GET STUCK" />
{registerStatus?.paid && <div className="flex f-col"> {registerStatus?.paid && <div className="flex f-col">
<h4>Order Paid!</h4> <h4>Order Paid!</h4>
<p>Your new NIP-05 handle is: <code>{handle}@{domain}</code></p> <p>Your new NIP-05 handle is: <code>{handle}@{domain}</code></p>

View File

@ -63,7 +63,7 @@ export default function NoteReaction(props: NoteReactionProps) {
const root = extractRoot(); const root = extractRoot();
const opt = { const opt = {
showHeader: ev?.Kind === EventKind.Repost, showHeader: ev?.Kind === EventKind.Repost,
showFooter: ev?.Kind === EventKind.Repost, showFooter: false,
}; };
return ( return (

View File

@ -19,7 +19,7 @@ export interface TimelineProps {
* A list of notes by pubkeys * A list of notes by pubkeys
*/ */
export default function Timeline({ subject, postsOnly = false, method }: TimelineProps) { export default function Timeline({ subject, postsOnly = false, method }: TimelineProps) {
const { main, related, latest, loadMore, showLatest } = useTimelineFeed(subject, { const { main, related, latest, parent, loadMore, showLatest } = useTimelineFeed(subject, {
method method
}); });
@ -42,7 +42,8 @@ export default function Timeline({ subject, postsOnly = false, method }: Timelin
} }
case EventKind.Reaction: case EventKind.Reaction:
case EventKind.Repost: { case EventKind.Repost: {
return <NoteReaction data={e} key={e.id} /> let eRef = e.tags.find(a => a[0] === "e")?.at(1);
return <NoteReaction data={e} key={e.id} root={parent.notes.find(a => a.id === eRef)}/>
} }
} }
} }

View File

@ -2,6 +2,7 @@ import { useEffect, useMemo, useReducer, useState } from "react";
import { System } from "Nostr/System"; import { System } from "Nostr/System";
import { TaggedRawEvent } from "Nostr"; import { TaggedRawEvent } from "Nostr";
import { Subscriptions } from "Nostr/Subscriptions"; import { Subscriptions } from "Nostr/Subscriptions";
import { debounce } from "Util";
export type NoteStore = { export type NoteStore = {
notes: Array<TaggedRawEvent>, notes: Array<TaggedRawEvent>,
@ -60,6 +61,11 @@ export interface UseSubscriptionState {
append: (notes: TaggedRawEvent[]) => void append: (notes: TaggedRawEvent[]) => void
} }
/**
* Wait time before returning changed state
*/
const DebounceMs = 200;
/** /**
* *
* @param {Subscriptions} sub * @param {Subscriptions} sub
@ -68,7 +74,16 @@ export interface UseSubscriptionState {
*/ */
export default function useSubscription(sub: Subscriptions | null, options?: UseSubscriptionOptions): UseSubscriptionState { export default function useSubscription(sub: Subscriptions | null, options?: UseSubscriptionOptions): UseSubscriptionState {
const [state, dispatch] = useReducer(notesReducer, initStore); const [state, dispatch] = useReducer(notesReducer, initStore);
const [debounce, setDebounce] = useState<number>(0); const [debounceOutput, setDebounceOutput] = useState<number>(0);
const [subDebounce, setSubDebounced] = useState<Subscriptions>();
useEffect(() => {
if (sub) {
return debounce(DebounceMs, () => {
setSubDebounced(sub);
});
}
}, [sub]);
useEffect(() => { useEffect(() => {
if (sub) { if (sub) {
@ -99,16 +114,14 @@ export default function useSubscription(sub: Subscriptions | null, options?: Use
System.RemoveSubscription(sub.Id); System.RemoveSubscription(sub.Id);
}; };
} }
}, [sub]); }, [subDebounce]);
useEffect(() => { useEffect(() => {
let t = setTimeout(() => { return debounce(DebounceMs, () => {
setDebounce(s => s += 1); setDebounceOutput(s => s += 1);
}, 100); });
return () => clearTimeout(t);
}, [state]); }, [state]);
const stateDebounced = useMemo(() => state, [debounce]); const stateDebounced = useMemo(() => state, [debounceOutput]);
return { return {
store: stateDebounced, store: stateDebounced,
clear: () => { clear: () => {

View File

@ -6,6 +6,7 @@ import useSubscription from "Feed/Subscription";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { RootState } from "State/Store"; import { RootState } from "State/Store";
import { UserPreferences } from "State/Login"; import { UserPreferences } from "State/Login";
import { debounce } from "Util";
export default function useThreadFeed(id: u256) { export default function useThreadFeed(id: u256) {
const [trackingEvents, setTrackingEvent] = useState<u256[]>([id]); const [trackingEvents, setTrackingEvent] = useState<u256[]>([id]);
@ -14,9 +15,8 @@ export default function useThreadFeed(id: u256) {
function addId(id: u256[]) { function addId(id: u256[]) {
setTrackingEvent((s) => { setTrackingEvent((s) => {
let orig = new Set(s); let orig = new Set(s);
let idsMissing = id.filter(a => !orig.has(a)); if (id.some(a => !orig.has(a))) {
if (idsMissing.length > 0) { let tmp = new Set([...s, ...id]);
let tmp = new Set([...s, ...idsMissing]);
return Array.from(tmp); return Array.from(tmp);
} else { } else {
return s; return s;
@ -41,14 +41,18 @@ export default function useThreadFeed(id: u256) {
const main = useSubscription(sub, { leaveOpen: true }); const main = useSubscription(sub, { leaveOpen: true });
useEffect(() => { useEffect(() => {
// debounce if (main.store) {
let t = setTimeout(() => { return debounce(200, () => {
let eTags = main.store.notes.map(a => a.tags.filter(b => b[0] === "e").map(b => b[1])).flat(); let mainNotes = main.store.notes.filter(a => a.kind === EventKind.TextNote);
let ids = main.store.notes.map(a => a.id);
let allEvents = new Set([...eTags, ...ids]); let eTags = mainNotes
addId(Array.from(allEvents)); .filter(a => a.kind === EventKind.TextNote)
}, 200); .map(a => a.tags.filter(b => b[0] === "e").map(b => b[1])).flat();
return () => clearTimeout(t); let ids = mainNotes.map(a => a.id);
let allEvents = new Set([...eTags, ...ids]);
addId(Array.from(allEvents));
})
}
}, [main.store]); }, [main.store]);
return main.store; return main.store;

View File

@ -23,6 +23,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
const [until, setUntil] = useState<number>(now); const [until, setUntil] = useState<number>(now);
const [since, setSince] = useState<number>(now - window); const [since, setSince] = useState<number>(now - window);
const [trackingEvents, setTrackingEvent] = useState<u256[]>([]); const [trackingEvents, setTrackingEvent] = useState<u256[]>([]);
const [trackingParentEvents, setTrackingParentEvents] = useState<u256[]>([]);
const pref = useSelector<RootState, UserPreferences>(s => s.login.preferences); const pref = useSelector<RootState, UserPreferences>(s => s.login.preferences);
function createSub() { function createSub() {
@ -94,18 +95,30 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
const latest = useSubscription(subRealtime, { leaveOpen: true }); const latest = useSubscription(subRealtime, { leaveOpen: true });
const subNext = useMemo(() => { const subNext = useMemo(() => {
let sub: Subscriptions | undefined;
if (trackingEvents.length > 0 && pref.enableReactions) { if (trackingEvents.length > 0 && pref.enableReactions) {
let sub = new Subscriptions(); sub = new Subscriptions();
sub.Id = `timeline-related:${subject.type}`; sub.Id = `timeline-related:${subject.type}`;
sub.Kinds = new Set([EventKind.Reaction, EventKind.Deletion, EventKind.Repost]); sub.Kinds = new Set([EventKind.Reaction, EventKind.Deletion]);
sub.ETags = new Set(trackingEvents); sub.ETags = new Set(trackingEvents);
return sub;
} }
return null; return sub ?? null;
}, [trackingEvents]); }, [trackingEvents]);
const others = useSubscription(subNext, { leaveOpen: true }); const others = useSubscription(subNext, { leaveOpen: true });
const subParents = useMemo(() => {
if (trackingParentEvents.length > 0) {
let parents = new Subscriptions();
parents.Id = `timeline-parent:${subject.type}`;
parents.Ids = new Set(trackingParentEvents);
return parents;
}
return null;
}, [trackingParentEvents]);
const parent = useSubscription(subParents);
useEffect(() => { useEffect(() => {
if (main.store.notes.length > 0) { if (main.store.notes.length > 0) {
setTrackingEvent(s => { setTrackingEvent(s => {
@ -113,6 +126,20 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
let temp = new Set([...s, ...ids]); let temp = new Set([...s, ...ids]);
return Array.from(temp); return Array.from(temp);
}); });
let reposts = main.store.notes
.filter(a => a.kind === EventKind.Repost && a.content === "")
.map(a => a.tags.find(b => b[0] === "e"))
.filter(a => a)
.map(a => a![1]);
if (reposts.length > 0) {
setTrackingParentEvents(s => {
if (reposts.some(a => !s.includes(a))) {
let temp = new Set([...s, ...reposts]);
return Array.from(temp);
}
return s;
})
}
} }
}, [main.store]); }, [main.store]);
@ -120,6 +147,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
main: main.store, main: main.store,
related: others.store, related: others.store,
latest: latest.store, latest: latest.store,
parent: parent.store,
loadMore: () => { loadMore: () => {
console.debug("Timeline load more!") console.debug("Timeline load more!")
if (options.method === "LIMIT_UNTIL") { if (options.method === "LIMIT_UNTIL") {

View File

@ -146,3 +146,14 @@ export function extractLnAddress(lnurl: string) {
export function unixNow() { export function unixNow() {
return Math.floor(new Date().getTime() / 1000); return Math.floor(new Date().getTime() / 1000);
} }
/**
* Simple debounce
* @param timeout Time until falling edge
* @param fn Callack to run on falling edge
* @returns Cancel timeout function
*/
export function debounce(timeout: number, fn: () => void) {
let t = setTimeout(fn, timeout);
return () => clearTimeout(t);
}