From 2ccf593476c199509ca1f3b75534ff10db3d49d3 Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 28 Mar 2023 16:41:57 +0100 Subject: [PATCH] bug: load more fix --- packages/app/src/Element/Timeline.tsx | 2 +- packages/app/src/Feed/TimelineFeed.ts | 10 ++--- packages/app/src/Hooks/useRequestBuilder.tsx | 8 +--- packages/app/src/System/NoteCollection.ts | 40 ++++++++++---------- packages/app/src/System/Query.ts | 33 ++++++++++++++++ packages/app/src/System/index.ts | 10 ++--- packages/nostr/src/legacy/Connection.ts | 7 ++++ 7 files changed, 71 insertions(+), 39 deletions(-) diff --git a/packages/app/src/Element/Timeline.tsx b/packages/app/src/Element/Timeline.tsx index 42f820b1..cfee6b72 100644 --- a/packages/app/src/Element/Timeline.tsx +++ b/packages/app/src/Element/Timeline.tsx @@ -5,7 +5,7 @@ import { useInView } from "react-intersection-observer"; import { TaggedRawEvent, EventKind, u256 } from "@snort/nostr"; import Icon from "Icons/Icon"; -import { dedupeByPubkey, findTag, tagFilterOfTextRepost, unixNow } from "Util"; +import { dedupeByPubkey, findTag, tagFilterOfTextRepost } from "Util"; import ProfileImage from "Element/ProfileImage"; import useTimelineFeed, { TimelineFeed, TimelineSubject } from "Feed/TimelineFeed"; import LoadMore from "Element/LoadMore"; diff --git a/packages/app/src/Feed/TimelineFeed.ts b/packages/app/src/Feed/TimelineFeed.ts index de8d4987..a8b198f2 100644 --- a/packages/app/src/Feed/TimelineFeed.ts +++ b/packages/app/src/Feed/TimelineFeed.ts @@ -121,8 +121,8 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel useEffect(() => { // clear store if changing relays - main.store.clear(); - latest.store.clear(); + main.clear(); + latest.clear(); }, [options.relay]); const subNext = useMemo(() => { @@ -180,7 +180,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel main: main.data, related: related.data, latest: latest.data, - loading: main.store.loading, + loading: main.loading(), loadMore: () => { if (main.data) { console.debug("Timeline load more!"); @@ -194,8 +194,8 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel }, showLatest: () => { if (latest.data) { - main.store.add(latest.data); - latest.store.clear(); + main.add(latest.data); + latest.clear(); } }, }; diff --git a/packages/app/src/Hooks/useRequestBuilder.tsx b/packages/app/src/Hooks/useRequestBuilder.tsx index 58dff3a4..00d4e493 100644 --- a/packages/app/src/Hooks/useRequestBuilder.tsx +++ b/packages/app/src/Hooks/useRequestBuilder.tsx @@ -1,6 +1,6 @@ import { useSyncExternalStore } from "react"; import { RequestBuilder, System } from "System"; -import { FlatNoteStore, NoteStore, StoreSnapshot } from "System/NoteCollection"; +import { EmptySnapshot, NoteStore, StoreSnapshot } from "System/NoteCollection"; import { unwrap } from "Util"; const useRequestBuilder = >( @@ -28,10 +28,6 @@ const useRequestBuilder = ; const getState = (): StoreSnapshot => { if (rb?.id) { const feed = System.GetFeed(rb.id); @@ -39,7 +35,7 @@ const useRequestBuilder = ; } } - return emptyStore; + return EmptySnapshot as StoreSnapshot; }; return useSyncExternalStore>( v => subscribe(v), diff --git a/packages/app/src/System/NoteCollection.ts b/packages/app/src/System/NoteCollection.ts index 09ce4888..65a1d1c7 100644 --- a/packages/app/src/System/NoteCollection.ts +++ b/packages/app/src/System/NoteCollection.ts @@ -3,9 +3,22 @@ import { findTag } from "Util"; export interface StoreSnapshot { data: TSnapshot | undefined; - store: NoteStore; + clear: () => void; + loading: () => boolean; + add: (ev: Readonly | Readonly>) => void; } +export const EmptySnapshot = { + data: undefined, + clear: () => { + // empty + }, + loading: () => true, + add: (ev: Readonly | Readonly>) => { + // empty + }, +} as StoreSnapshot; + export type NoteStoreSnapshotData = Readonly> | Readonly; export type NoteStoreHook = () => void; export type NoteStoreHookRelease = () => void; @@ -20,7 +33,6 @@ export type OnEoseCallbackRelease = () => void; export abstract class NoteStore { abstract add(ev: Readonly | Readonly>): void; abstract clear(): void; - abstract eose(c: string): void; // react hooks abstract hook(cb: NoteStoreHook): NoteStoreHookRelease; @@ -28,7 +40,6 @@ export abstract class NoteStore { // events abstract onEvent(cb: OnEventCallback): OnEventCallbackRelease; - abstract onEose(cb: OnEoseCallback): OnEoseCallback; abstract get snapshot(): StoreSnapshot; abstract get loading(): boolean; @@ -38,10 +49,11 @@ export abstract class NoteStore { export abstract class HookedNoteStore implements NoteStore { #hooks: Array = []; #eventHooks: Array = []; - #eoseHooks: Array = []; #loading = true; #storeSnapshot: StoreSnapshot = { - store: this, + clear: () => this.clear(), + loading: () => this.loading, + add: ev => this.add(ev), data: undefined, }; #needsSnapshot = true; @@ -60,15 +72,9 @@ export abstract class HookedNoteStore i this.onChange([]); } - abstract add(ev: TaggedRawEvent | Array): void; + abstract add(ev: Readonly | Readonly>): void; abstract clear(): void; - eose(c: string): void { - for (const hkE of this.#eoseHooks) { - hkE(c); - } - } - hook(cb: NoteStoreHook): NoteStoreHookRelease { this.#hooks.push(cb); return () => { @@ -90,14 +96,6 @@ export abstract class HookedNoteStore i }; } - onEose(cb: OnEoseCallback): OnEoseCallback { - this.#eoseHooks.push(cb); - return () => { - const idx = this.#eoseHooks.findIndex(a => a === cb); - this.#eoseHooks.splice(idx, 1); - }; - } - protected abstract takeSnapshot(): TSnapshot | undefined; protected onChange(changes: Readonly>): void { @@ -115,8 +113,8 @@ export abstract class HookedNoteStore i #updateSnapshot() { if (this.#needsSnapshot) { this.#storeSnapshot = { + ...this.#storeSnapshot, data: this.takeSnapshot(), - store: this, }; this.#needsSnapshot = false; } diff --git a/packages/app/src/System/Query.ts b/packages/app/src/System/Query.ts index 5e659c9b..bab24aca 100644 --- a/packages/app/src/System/Query.ts +++ b/packages/app/src/System/Query.ts @@ -31,6 +31,11 @@ export class Query { */ #sentToRelays: Array> = []; + /** + * When each relay returned EOSE + */ + #eoseRelays: Map = new Map(); + /** * Leave the query open until its removed */ @@ -88,4 +93,32 @@ export class Query { c.CloseReq(this.id); } } + + eose(sub: string, relay: string) { + if (sub === this.id) { + console.debug(`[EOSE][${sub}] ${relay}`); + this.#eoseRelays.set(relay, unixNowMs()); + } else { + const subQ = this.subQueries.find(a => a.id === sub); + if (subQ) { + subQ.eose(sub, relay); + } + } + } + + /** + * Get the progress to EOSE, can be used to determine when we should load more content + */ + get progress() { + const thisProgress = this.#eoseRelays.size / this.#sentToRelays.reduce((acc, v) => (acc += v.Down ? 0 : 1), 0); + if (this.subQueries.length === 0) { + return thisProgress; + } + + let totalProgress = thisProgress; + for (const sq of this.subQueries) { + totalProgress += sq.progress; + } + return totalProgress / (this.subQueries.length + 1); + } } diff --git a/packages/app/src/System/index.ts b/packages/app/src/System/index.ts index 8d54d941..d9ae4e2c 100644 --- a/packages/app/src/System/index.ts +++ b/packages/app/src/System/index.ts @@ -112,11 +112,11 @@ export class NostrSystem { OnEndOfStoredEvents(c: Connection, sub: string) { const q = this.GetQuery(sub); if (q) { - q.request.finished = unixNowMs(); - const f = this.Feeds.get(sub); + q.eose(sub, c.Address); + const f = this.Feeds.get(q.id); if (f) { - f.eose(c.Address); - f.loading = false; + f.loading = q.progress <= 0.5; + console.debug(`${sub} loading=${f.loading}, progress=${q.progress}`); } if (!q.leaveOpen) { c.CloseReq(sub); @@ -255,8 +255,6 @@ export class NostrSystem { this.Queries.set(rb.id, q); const store = new type(); this.Feeds.set(rb.id, store); - store.onEose(c => console.debug(`[EOSE][${rb.id}]: ${c}`)); - this.SendQuery(q); this.#changed(); return store; diff --git a/packages/nostr/src/legacy/Connection.ts b/packages/nostr/src/legacy/Connection.ts index e2484f82..75398086 100644 --- a/packages/nostr/src/legacy/Connection.ts +++ b/packages/nostr/src/legacy/Connection.ts @@ -60,6 +60,7 @@ export class Connection { Authed: boolean; Ephemeral: boolean; EphemeralTimeout: ReturnType | undefined; + Down = true; constructor(addr: string, options: RelaySettings, auth?: AuthHandler, ephemeral: boolean = false) { this.Id = uuid(); @@ -143,6 +144,7 @@ export class Connection { OnOpen() { this.ConnectTimeout = DefaultConnectTimeout; console.log(`[${this.Address}] Open!`); + this.Down = false; this.OnConnected?.(); } @@ -300,6 +302,7 @@ export class Connection { CloseReq(id: string) { if (this.ActiveRequests.delete(id)) { this.#SendJson(["CLOSE", id]); + this.OnEose?.(id); this.#SendQueuedRequests(); } } @@ -319,6 +322,10 @@ export class Connection { } #ResetQueues() { + //send EOSE on disconnect for active subs + this.ActiveRequests.forEach(v => this.OnEose?.(v)) + this.PendingRequests.forEach(v => this.OnEose?.(v[1])); + this.ActiveRequests.clear(); this.PendingRequests = []; this.PendingRaw = [];