forked from Kieran/snort
bug: try improve loading performance
This commit is contained in:
parent
c91325ecd6
commit
e830b39edf
@ -1,13 +1,22 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useInView } from "react-intersection-observer";
|
import { useInView } from "react-intersection-observer";
|
||||||
|
|
||||||
export default function LoadMore({ onLoadMore }: { onLoadMore: () => void }) {
|
export default function LoadMore({ onLoadMore, shouldLoadMore }: { onLoadMore: () => void, shouldLoadMore: boolean }) {
|
||||||
const { ref, inView } = useInView();
|
const { ref, inView } = useInView();
|
||||||
|
const [tick, setTick] = useState<number>(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inView === true) {
|
if (inView === true && shouldLoadMore === true) {
|
||||||
onLoadMore();
|
onLoadMore();
|
||||||
}
|
}
|
||||||
}, [inView]);
|
}, [inView, shouldLoadMore, tick]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let t = setInterval(() => {
|
||||||
|
setTick(x => x += 1);
|
||||||
|
}, 500);
|
||||||
|
return () => clearInterval(t);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return <div ref={ref} className="mb10">Loading...</div>;
|
return <div ref={ref} className="mb10">Loading...</div>;
|
||||||
}
|
}
|
@ -1,3 +1,7 @@
|
|||||||
|
.note {
|
||||||
|
min-height: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
.note.thread {
|
.note.thread {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import LoadMore from "Element/LoadMore";
|
|||||||
import Note from "Element/Note";
|
import Note from "Element/Note";
|
||||||
import NoteReaction from "Element/NoteReaction";
|
import NoteReaction from "Element/NoteReaction";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faFastForward, faForward } from "@fortawesome/free-solid-svg-icons";
|
import { faForward } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
export interface TimelineProps {
|
export interface TimelineProps {
|
||||||
postsOnly: boolean,
|
postsOnly: boolean,
|
||||||
@ -55,7 +55,7 @@ export default function Timeline({ subject, postsOnly = false, method }: Timelin
|
|||||||
Show latest {latestFeed.length - 1} notes
|
Show latest {latestFeed.length - 1} notes
|
||||||
</div>)}
|
</div>)}
|
||||||
{mainFeed.map(eventElement)}
|
{mainFeed.map(eventElement)}
|
||||||
{mainFeed.length > 0 ? <LoadMore onLoadMore={loadMore} /> : null}
|
<LoadMore onLoadMore={loadMore} shouldLoadMore={main.end}/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -20,40 +20,54 @@ export default function useLoginFeed() {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [pubKey, readNotifications] = useSelector<RootState, [HexKey | undefined, number]>(s => [s.login.publicKey, s.login.readNotifications]);
|
const [pubKey, readNotifications] = useSelector<RootState, [HexKey | undefined, number]>(s => [s.login.publicKey, s.login.readNotifications]);
|
||||||
|
|
||||||
const sub = useMemo(() => {
|
const subMetadata = useMemo(() => {
|
||||||
if (!pubKey) {
|
if (!pubKey) return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sub = new Subscriptions();
|
let sub = new Subscriptions();
|
||||||
sub.Id = `login:${sub.Id}`;
|
sub.Id = `login:meta`;
|
||||||
sub.Authors = new Set([pubKey]);
|
sub.Authors = new Set([pubKey]);
|
||||||
sub.Kinds = new Set([EventKind.ContactList, EventKind.SetMetadata, EventKind.DirectMessage]);
|
sub.Kinds = new Set([EventKind.ContactList, EventKind.SetMetadata]);
|
||||||
|
|
||||||
let notifications = new Subscriptions();
|
|
||||||
notifications.Kinds = new Set([EventKind.TextNote]);
|
|
||||||
notifications.PTags = new Set([pubKey]);
|
|
||||||
notifications.Limit = 100;
|
|
||||||
sub.AddSubscription(notifications);
|
|
||||||
|
|
||||||
let dms = new Subscriptions();
|
|
||||||
dms.Kinds = new Set([EventKind.DirectMessage]);
|
|
||||||
dms.PTags = new Set([pubKey]);
|
|
||||||
sub.AddSubscription(dms);
|
|
||||||
|
|
||||||
return sub;
|
return sub;
|
||||||
}, [pubKey]);
|
}, [pubKey]);
|
||||||
|
|
||||||
const main = useSubscription(sub, { leaveOpen: true });
|
const subNotification = useMemo(() => {
|
||||||
|
if (!pubKey) return null;
|
||||||
|
|
||||||
|
let sub = new Subscriptions();
|
||||||
|
sub.Id = "login:notifications";
|
||||||
|
sub.Kinds = new Set([EventKind.TextNote]);
|
||||||
|
sub.PTags = new Set([pubKey]);
|
||||||
|
sub.Limit = 1;
|
||||||
|
return sub;
|
||||||
|
}, [pubKey]);
|
||||||
|
|
||||||
|
const subDms = useMemo(() => {
|
||||||
|
if (!pubKey) return null;
|
||||||
|
|
||||||
|
let dms = new Subscriptions();
|
||||||
|
dms.Id = "login:dms";
|
||||||
|
dms.Kinds = new Set([EventKind.DirectMessage]);
|
||||||
|
dms.PTags = new Set([pubKey]);
|
||||||
|
|
||||||
|
let dmsFromME = new Subscriptions();
|
||||||
|
dmsFromME.Authors = new Set([pubKey]);
|
||||||
|
dmsFromME.Kinds = new Set([EventKind.DirectMessage]);
|
||||||
|
dms.AddSubscription(dmsFromME);
|
||||||
|
|
||||||
|
return dms;
|
||||||
|
}, [pubKey]);
|
||||||
|
|
||||||
|
const metadataFeed = useSubscription(subMetadata, { leaveOpen: true });
|
||||||
|
const notificationFeed = useSubscription(subNotification, { leaveOpen: true });
|
||||||
|
const dmsFeed = useSubscription(subDms, { leaveOpen: true });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let contactList = main.store.notes.filter(a => a.kind === EventKind.ContactList);
|
let contactList = metadataFeed.store.notes.filter(a => a.kind === EventKind.ContactList);
|
||||||
let notifications = main.store.notes.filter(a => a.kind === EventKind.TextNote);
|
let metadata = metadataFeed.store.notes.filter(a => a.kind === EventKind.SetMetadata);
|
||||||
let metadata = main.store.notes.filter(a => a.kind === EventKind.SetMetadata);
|
|
||||||
let profiles = metadata.map(a => mapEventToProfile(a))
|
let profiles = metadata.map(a => mapEventToProfile(a))
|
||||||
.filter(a => a !== undefined)
|
.filter(a => a !== undefined)
|
||||||
.map(a => a!);
|
.map(a => a!);
|
||||||
let dms = main.store.notes.filter(a => a.kind === EventKind.DirectMessage);
|
|
||||||
|
|
||||||
for (let cl of contactList) {
|
for (let cl of contactList) {
|
||||||
if (cl.content !== "") {
|
if (cl.content !== "") {
|
||||||
@ -64,14 +78,6 @@ export default function useLoginFeed() {
|
|||||||
dispatch(setFollows(pTags));
|
dispatch(setFollows(pTags));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Notification" in window && Notification.permission === "granted") {
|
|
||||||
for (let nx of notifications.filter(a => (a.created_at * 1000) > readNotifications)) {
|
|
||||||
sendNotification(nx)
|
|
||||||
.catch(console.warn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dispatch(addNotifications(notifications));
|
|
||||||
dispatch(addDirectMessage(dms));
|
|
||||||
(async () => {
|
(async () => {
|
||||||
let maxProfile = profiles.reduce((acc, v) => {
|
let maxProfile = profiles.reduce((acc, v) => {
|
||||||
if (v.created > acc.created) {
|
if (v.created > acc.created) {
|
||||||
@ -87,7 +93,25 @@ export default function useLoginFeed() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})().catch(console.warn);
|
})().catch(console.warn);
|
||||||
}, [main.store]);
|
}, [metadataFeed.store]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let notifications = notificationFeed.store.notes.filter(a => a.kind === EventKind.TextNote);
|
||||||
|
|
||||||
|
if ("Notification" in window && Notification.permission === "granted") {
|
||||||
|
for (let nx of notifications.filter(a => (a.created_at * 1000) > readNotifications)) {
|
||||||
|
sendNotification(nx)
|
||||||
|
.catch(console.warn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(addNotifications(notifications));
|
||||||
|
}, [notificationFeed.store]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let dms = dmsFeed.store.notes.filter(a => a.kind === EventKind.DirectMessage);
|
||||||
|
dispatch(addDirectMessage(dms));
|
||||||
|
}, [dmsFeed.store]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function makeNotification(ev: TaggedRawEvent) {
|
async function makeNotification(ev: TaggedRawEvent) {
|
||||||
|
@ -13,13 +13,13 @@ export interface TimelineFeedOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TimelineSubject {
|
export interface TimelineSubject {
|
||||||
type: "pubkey" | "hashtag" | "global",
|
type: "pubkey" | "hashtag" | "global" | "ptag",
|
||||||
items: string[]
|
items: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useTimelineFeed(subject: TimelineSubject, options: TimelineFeedOptions) {
|
export default function useTimelineFeed(subject: TimelineSubject, options: TimelineFeedOptions) {
|
||||||
const now = unixNow();
|
const now = unixNow();
|
||||||
const [window, setWindow] = useState<number>(60 * 10);
|
const [window, setWindow] = useState<number>(60 * 60);
|
||||||
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[]>([]);
|
||||||
@ -42,6 +42,10 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
sub.HashTags = new Set(subject.items);
|
sub.HashTags = new Set(subject.items);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "ptag": {
|
||||||
|
sub.PTags = new Set(subject.items);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return sub;
|
return sub;
|
||||||
}
|
}
|
||||||
@ -68,6 +72,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
latestSub.HashTags = sub.HashTags;
|
latestSub.HashTags = sub.HashTags;
|
||||||
latestSub.Kinds = sub.Kinds;
|
latestSub.Kinds = sub.Kinds;
|
||||||
latestSub.Limit = 1;
|
latestSub.Limit = 1;
|
||||||
|
latestSub.Since = Math.floor(new Date().getTime() / 1000);
|
||||||
sub.AddSubscription(latestSub);
|
sub.AddSubscription(latestSub);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,6 +86,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
if (subLatest && !pref.autoShowLatest) {
|
if (subLatest && !pref.autoShowLatest) {
|
||||||
subLatest.Id = `${subLatest.Id}:latest`;
|
subLatest.Id = `${subLatest.Id}:latest`;
|
||||||
subLatest.Limit = 1;
|
subLatest.Limit = 1;
|
||||||
|
subLatest.Since = Math.floor(new Date().getTime() / 1000);
|
||||||
}
|
}
|
||||||
return subLatest;
|
return subLatest;
|
||||||
}, [subject.type, subject.items]);
|
}, [subject.type, subject.items]);
|
||||||
|
@ -1,65 +1,24 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux"
|
import { useDispatch, useSelector } from "react-redux"
|
||||||
import Note from "Element/Note";
|
import { HexKey } from "Nostr";
|
||||||
import NoteReaction from "Element/NoteReaction";
|
|
||||||
import useSubscription from "Feed/Subscription";
|
|
||||||
import { TaggedRawEvent } from "Nostr";
|
|
||||||
import Event from "Nostr/Event";
|
|
||||||
import EventKind from "Nostr/EventKind";
|
|
||||||
import { Subscriptions } from "Nostr/Subscriptions";
|
|
||||||
import { markNotificationsRead } from "State/Login";
|
import { markNotificationsRead } from "State/Login";
|
||||||
import { RootState } from "State/Store";
|
import { RootState } from "State/Store";
|
||||||
|
import Timeline from "Element/Timeline";
|
||||||
|
|
||||||
export default function NotificationsPage() {
|
export default function NotificationsPage() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const notifications = useSelector<RootState, TaggedRawEvent[]>(s => s.login.notifications);
|
const pubkey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(markNotificationsRead());
|
dispatch(markNotificationsRead());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const etagged = useMemo(() => {
|
|
||||||
return notifications?.filter(a => a.kind === EventKind.Reaction)
|
|
||||||
.map(a => {
|
|
||||||
let ev = new Event(a);
|
|
||||||
return ev.Thread?.ReplyTo?.Event ?? ev.Thread?.Root?.Event;
|
|
||||||
}).filter(a => a !== undefined).map(a => a!);
|
|
||||||
}, [notifications]);
|
|
||||||
|
|
||||||
const subEvents = useMemo(() => {
|
|
||||||
let sub = new Subscriptions();
|
|
||||||
sub.Id = `reactions:${sub.Id}`;
|
|
||||||
sub.Kinds = new Set([EventKind.Reaction]);
|
|
||||||
sub.ETags = new Set(notifications?.filter(b => b.kind === EventKind.TextNote).map(b => b.id));
|
|
||||||
|
|
||||||
if (etagged.length > 0) {
|
|
||||||
let reactionsTo = new Subscriptions();
|
|
||||||
reactionsTo.Kinds = new Set([EventKind.TextNote]);
|
|
||||||
reactionsTo.Ids = new Set(etagged);
|
|
||||||
sub.OrSubs.push(reactionsTo);
|
|
||||||
}
|
|
||||||
return sub;
|
|
||||||
}, [etagged]);
|
|
||||||
|
|
||||||
const otherNotes = useSubscription(subEvents, { leaveOpen: true });
|
|
||||||
|
|
||||||
const sorted = [
|
|
||||||
...notifications
|
|
||||||
].sort((a, b) => b.created_at - a.created_at);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{sorted?.map(a => {
|
{pubkey ?
|
||||||
if (a.kind === EventKind.TextNote) {
|
<Timeline subject={{ type: "ptag", items: [pubkey!] }} postsOnly={false} method={"TIME_RANGE"} />
|
||||||
return <Note data={a} key={a.id} related={otherNotes?.store.notes ?? []} />
|
: null}
|
||||||
} else if (a.kind === EventKind.Reaction) {
|
|
||||||
let ev = new Event(a);
|
|
||||||
let reactedTo = ev.Thread?.ReplyTo?.Event ?? ev.Thread?.Root?.Event;
|
|
||||||
let reactedNote = otherNotes?.store.notes?.find(c => c.id === reactedTo);
|
|
||||||
return <NoteReaction data={a} key={a.id} root={reactedNote} />
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user