bug: load more fix

This commit is contained in:
Kieran 2023-03-28 16:41:57 +01:00
parent 465c59ea20
commit 2ccf593476
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
7 changed files with 71 additions and 39 deletions

View File

@ -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";

View File

@ -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();
}
},
};

View File

@ -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 = <TStore extends NoteStore, TSnapshot = ReturnType<TStore["getSnapshotData"]>>(
@ -28,10 +28,6 @@ const useRequestBuilder = <TStore extends NoteStore, TSnapshot = ReturnType<TSto
release();
};
};
const emptyStore = {
data: undefined,
store: new FlatNoteStore(),
} as StoreSnapshot<TSnapshot>;
const getState = (): StoreSnapshot<TSnapshot> => {
if (rb?.id) {
const feed = System.GetFeed(rb.id);
@ -39,7 +35,7 @@ const useRequestBuilder = <TStore extends NoteStore, TSnapshot = ReturnType<TSto
return unwrap(feed).snapshot as StoreSnapshot<TSnapshot>;
}
}
return emptyStore;
return EmptySnapshot as StoreSnapshot<TSnapshot>;
};
return useSyncExternalStore<StoreSnapshot<TSnapshot>>(
v => subscribe(v),

View File

@ -3,9 +3,22 @@ import { findTag } from "Util";
export interface StoreSnapshot<TSnapshot> {
data: TSnapshot | undefined;
store: NoteStore;
clear: () => void;
loading: () => boolean;
add: (ev: Readonly<TaggedRawEvent> | Readonly<Array<TaggedRawEvent>>) => void;
}
export const EmptySnapshot = {
data: undefined,
clear: () => {
// empty
},
loading: () => true,
add: (ev: Readonly<TaggedRawEvent> | Readonly<Array<TaggedRawEvent>>) => {
// empty
},
} as StoreSnapshot<FlatNoteStore>;
export type NoteStoreSnapshotData = Readonly<Array<TaggedRawEvent>> | Readonly<TaggedRawEvent>;
export type NoteStoreHook = () => void;
export type NoteStoreHookRelease = () => void;
@ -20,7 +33,6 @@ export type OnEoseCallbackRelease = () => void;
export abstract class NoteStore {
abstract add(ev: Readonly<TaggedRawEvent> | Readonly<Array<TaggedRawEvent>>): 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<NoteStoreSnapshotData>;
abstract get loading(): boolean;
@ -38,10 +49,11 @@ export abstract class NoteStore {
export abstract class HookedNoteStore<TSnapshot extends NoteStoreSnapshotData> implements NoteStore {
#hooks: Array<NoteStoreHook> = [];
#eventHooks: Array<OnEventCallback> = [];
#eoseHooks: Array<OnEoseCallback> = [];
#loading = true;
#storeSnapshot: StoreSnapshot<TSnapshot> = {
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<TSnapshot extends NoteStoreSnapshotData> i
this.onChange([]);
}
abstract add(ev: TaggedRawEvent | Array<TaggedRawEvent>): void;
abstract add(ev: Readonly<TaggedRawEvent> | Readonly<Array<TaggedRawEvent>>): 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<TSnapshot extends NoteStoreSnapshotData> 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<Array<TaggedRawEvent>>): void {
@ -115,8 +113,8 @@ export abstract class HookedNoteStore<TSnapshot extends NoteStoreSnapshotData> i
#updateSnapshot() {
if (this.#needsSnapshot) {
this.#storeSnapshot = {
...this.#storeSnapshot,
data: this.takeSnapshot(),
store: this,
};
this.#needsSnapshot = false;
}

View File

@ -31,6 +31,11 @@ export class Query {
*/
#sentToRelays: Array<Readonly<Connection>> = [];
/**
* When each relay returned EOSE
*/
#eoseRelays: Map<string, number> = 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);
}
}

View File

@ -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;

View File

@ -60,6 +60,7 @@ export class Connection {
Authed: boolean;
Ephemeral: boolean;
EphemeralTimeout: ReturnType<typeof setTimeout> | 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 = [];