clean thread loading

This commit is contained in:
smolgrrr 2024-08-16 19:41:22 +10:00
parent 6f7d41edd0
commit 361187c5b7
7 changed files with 52 additions and 107 deletions

View File

@ -4,14 +4,14 @@ import { Event } from "nostr-tools";
import NewNoteCard from "../forms/PostFormCard"; import NewNoteCard from "../forms/PostFormCard";
import RepostCard from "../modals/RepostCard"; import RepostCard from "../modals/RepostCard";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { useUniqEvents } from "../../hooks/useUniqEvents"; import { useFetchEvents } from "../../hooks/useFetchEvents";
const DEFAULT_DIFFICULTY = 0; const DEFAULT_DIFFICULTY = 0;
const HashtagPage = () => { const HashtagPage = () => {
const { id } = useParams(); const { id } = useParams();
const filterDifficulty = localStorage.getItem("filterHashtagDifficulty") || DEFAULT_DIFFICULTY; const filterDifficulty = localStorage.getItem("filterHashtagDifficulty") || DEFAULT_DIFFICULTY;
const { noteEvents, metadataEvents } = useUniqEvents(id as string, false); const { noteEvents, metadataEvents } = useFetchEvents(id as string, false);
const postEvents: Event[] = noteEvents const postEvents: Event[] = noteEvents
.filter((event) => .filter((event) =>

View File

@ -3,12 +3,12 @@ import { Event } from "nostr-tools";
import NewNoteCard from "../forms/PostFormCard"; import NewNoteCard from "../forms/PostFormCard";
import RepostCard from "../modals/RepostCard"; import RepostCard from "../modals/RepostCard";
import { DEFAULT_DIFFICULTY } from "../../config"; import { DEFAULT_DIFFICULTY } from "../../config";
import { useUniqEvents } from "../../hooks/useUniqEvents";
import PostCard from "../modals/PostCard"; import PostCard from "../modals/PostCard";
import { useFetchEvents } from "../../hooks/useFetchEvents";
const Home = () => { const Home = () => {
const filterDifficulty = localStorage.getItem("filterDifficulty") || DEFAULT_DIFFICULTY; const filterDifficulty = localStorage.getItem("filterDifficulty") || DEFAULT_DIFFICULTY;
const { noteEvents, metadataEvents } = useUniqEvents(); const { noteEvents, metadataEvents } = useFetchEvents();
const postEvents: Event[] = noteEvents const postEvents: Event[] = noteEvents
.filter((event) => .filter((event) =>

View File

@ -2,11 +2,11 @@ import { useState, useCallback } from "react";
import PostCard from "../modals/PostCard"; import PostCard from "../modals/PostCard";
import { Event } from "nostr-tools"; import { Event } from "nostr-tools";
import RepostCard from "../modals/RepostCard"; import RepostCard from "../modals/RepostCard";
import { useUniqEvents } from "../../hooks/useUniqEvents"; import { useFetchEvents } from "../../hooks/useFetchEvents";
const Notifications = () => { const Notifications = () => {
const [notifsView, setNotifsView] = useState(false); const [notifsView, setNotifsView] = useState(false);
const { noteEvents, metadataEvents } = useUniqEvents(undefined,true); const { noteEvents, metadataEvents } = useFetchEvents(undefined,true);
const storedKeys = JSON.parse(localStorage.getItem('usedKeys') || '[]'); const storedKeys = JSON.parse(localStorage.getItem('usedKeys') || '[]');
const storedPubkeys = storedKeys.map((key: any[]) => key[1]); const storedPubkeys = storedKeys.map((key: any[]) => key[1]);

View File

@ -1,7 +1,7 @@
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { useState } from "react"; import { useState } from "react";
import { Event, nip19 } from "nostr-tools" import { Event, nip19 } from "nostr-tools"
import { subNote, subNotesOnce } from '../../utils/subscriptions'; import { subNotesOnce } from '../../utils/subscriptions';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { uniqBy } from '../../utils/otherUtils'; import { uniqBy } from '../../utils/otherUtils';
import { DocumentTextIcon, FolderPlusIcon, DocumentDuplicateIcon, ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'; import { DocumentTextIcon, FolderPlusIcon, DocumentDuplicateIcon, ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline';
@ -9,118 +9,56 @@ import Placeholder from '../modals/Placeholder';
import NewNoteCard from '../forms/PostFormCard'; import NewNoteCard from '../forms/PostFormCard';
import RepostNote from '../forms/RepostNote'; import RepostNote from '../forms/RepostNote';
import PostCard from '../modals/PostCard'; import PostCard from '../modals/PostCard';
import { useFetchEvents } from '../../hooks/useFetchEvents';
type PostType = "" | "Reply" | "Quote" | undefined; type PostType = "" | "Reply" | "Quote" | undefined;
const Thread = () => { const Thread = () => {
const { id } = useParams(); const { id } = useParams();
const [events, setEvents] = useState<Event[]>([]); // Initialize state
const [OPEvent, setOPEvent] = useState<Event>()
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
const [showRepost, setShowRepost] = useState(false); const [showRepost, setShowRepost] = useState(false);
const [postType, setPostType] = useState<PostType>(""); const [postType, setPostType] = useState<PostType>("");
const [hasRun, setHasRun] = useState(false); const [prevMentions, setPrevMentions] = useState<Event[]>([]);
const [preOPEvents, setPreOPEvents] = useState(['']);
// const filterDifficulty = useState(localStorage.getItem("filterDifficulty") || "20"); // const filterDifficulty = useState(localStorage.getItem("filterDifficulty") || "20");
// Load cached metadataEvents from localStorage
const [cachedMetadataEvents, setCachedMetadataEvents] = useState<Event[]>(
JSON.parse(localStorage.getItem("cachedMetadataEvents") || "[]")
);
let decodeResult = nip19.decode(id as string); let decodeResult = nip19.decode(id as string);
let hexID = decodeResult.data as string; let hexID = decodeResult.data as string;
const { noteEvents, metadataEvents } = useFetchEvents(undefined,false,hexID);
// Define your callback function for subGlobalFeed
const onEvent = (event: Event, relay: string) => {
setEvents((prevEvents) => [...prevEvents, event]);
// If the new event is a metadata event, add it to the cached metadata events
if (event.kind === 0) {
setCachedMetadataEvents((prevMetadataEvents) => {
// Check if the event already exists in the cached metadata events
const existingEvent = prevMetadataEvents.find((e) => e.id === event.id || e.pubkey === event.pubkey)
if (!existingEvent) {
// If the event doesn't exist, add it to the cached metadata events
return [...prevMetadataEvents, event];
} else if (existingEvent && existingEvent.created_at < event.created_at) {
// Remove any existing metadata event with the same pubkey and id
const updatedMetadataEvents = prevMetadataEvents.filter(
(e) => e.id !== existingEvent.id
);
// Add the new metadata event
return [...updatedMetadataEvents, event];
}
// If the event already exists, return the previous cached metadata events
return prevMetadataEvents;
});
}
};
useEffect(() => {
setHasRun(false)
if (decodeResult.type === 'note') {
// Call your subNote function or do whatever you need to do with id_to_hex
subNote(hexID, onEvent);
}
}, [id]); // Empty dependency array means this useEffect runs once when the component mounts
// Save the cached metadataEvents to localStorage
useEffect(() => {
localStorage.setItem("cachedMetadataEvents", JSON.stringify(cachedMetadataEvents));
}, [cachedMetadataEvents]);
const uniqEvents = events.length > 0 ? uniqBy(events, "id") : [];
const metadataEvents = [...cachedMetadataEvents, ...uniqEvents.filter(event => event.kind === 0)];
useEffect(() => {
if (!hasRun && events.length > 0) {
let OPEvent = uniqEvents.find(event => event.id === hexID);
setOPEvent(OPEvent);
console.log(OPEvent)
if (OPEvent && OPEvent.id !== hexID) {
OPEvent = events.find(e => e.id === hexID) as Event;
}
if (OPEvent) {
setOPEvent(OPEvent);
let OPNoteEvents = OPEvent.tags.filter(tag => tag[0] === 'e').map(tag => tag[1]);
setHasRun(true);
setPreOPEvents(OPNoteEvents)
subNotesOnce(OPNoteEvents, onEvent)
}
}
}, [uniqEvents, hasRun]);
const countReplies = (event: Event) => { const countReplies = (event: Event) => {
return uniqEvents.filter(e => e.tags.some(tag => tag[0] === 'e' && tag[1] === event.id)); return noteEvents.filter(e => e.tags.some(tag => tag[0] === 'e' && tag[1] === event.id));
} }
const repliedList = (event: Event): Event[] => { const repliedList = (event: Event): Event[] => {
return uniqEvents.filter(e => event.tags.some(tag => tag[0] === 'p' && tag[1] === e.pubkey)); return noteEvents.filter(e => event.tags.some(tag => tag[0] === 'p' && tag[1] === e.pubkey));
} }
const earlierEvents = uniqEvents
.filter(event =>
event.kind === 1 &&
preOPEvents.includes(event.id)
).sort((a, b) => (b.created_at as any) - (a.created_at as any));
const displayedEvents = [...uniqEvents].slice(1) // Define your callback function for subGlobalFeed
.filter(event => const onEvent = (event: Event, relay: string) => {
event.kind === 1 && setPrevMentions((prevEvents) => [...prevEvents, event]);
!earlierEvents.map(e => e.id).includes(event.id) && };
(OPEvent ? OPEvent.id !== event.id : true)
).sort((a, b) => a.created_at - b.created_at); const OPEvent = noteEvents.find(event => event.id === hexID);
useEffect(() => {
if (OPEvent && prevMentions.length == 0) {
const OPMentionIDs = OPEvent.tags.filter(tag => tag[0] === 'e').map(tag => tag[1]);
subNotesOnce(OPMentionIDs, onEvent);
}
}, [OPEvent]);
if (OPEvent) {
const uniqEvents = uniqBy(prevMentions, "id");
const earlierEvents = uniqEvents
.filter(e =>
e.created_at < OPEvent.created_at
)
const replyEvents = [...noteEvents].slice(1)
.filter(event =>
!earlierEvents.map(e => e.id).includes(event.id) &&
(OPEvent ? OPEvent.id !== event.id : true)
).sort((a, b) => a.created_at - b.created_at);
if (uniqEvents.length === 0) {
return (
<>
<Placeholder />
<div className="col-span-full h-0.5 bg-neutral-900"/> {/* This is the white line separator */}
</>
);
}
return ( return (
<> <>
<main className="bg-black text-white min-h-screen"> <main className="bg-black text-white min-h-screen">
@ -130,7 +68,7 @@ const Thread = () => {
.sort((a, b) => a.created_at - b.created_at).map((event, index) => ( .sort((a, b) => a.created_at - b.created_at).map((event, index) => (
<PostCard event={event} metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null} replies={countReplies(event)} /> <PostCard event={event} metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null} replies={countReplies(event)} />
))} ))}
{OPEvent && <PostCard event={OPEvent} metadata={metadataEvents.find((e) => e.pubkey === OPEvent.pubkey && e.kind === 0) || null} replies={countReplies(OPEvent)} type={'OP'}/>} <PostCard event={OPEvent} metadata={metadataEvents.find((e) => e.pubkey === OPEvent.pubkey && e.kind === 0) || null} replies={countReplies(OPEvent)} type={'OP'}/>
</div> </div>
<div className="col-span-full flex justify-center space-x-16 pb-4"> <div className="col-span-full flex justify-center space-x-16 pb-4">
<DocumentTextIcon <DocumentTextIcon
@ -177,12 +115,12 @@ const Thread = () => {
</div>} </div>}
<div className="col-span-full h-0.5 bg-neutral-900"/> {/* This is the white line separator */} <div className="col-span-full h-0.5 bg-neutral-900"/> {/* This is the white line separator */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
{displayedEvents.map((event, index) => ( {replyEvents.map((event, index) => (
<PostCard <PostCard
key={index} key={index}
event={event} event={event}
metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null} metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null}
replies={displayedEvents.filter((e) => e.tags.some((tag) => tag[0] === "e" && tag[1] === event.id))} replies={replyEvents.filter((e) => e.tags.some((tag) => tag[0] === "e" && tag[1] === event.id))}
repliedTo={repliedList(event)} repliedTo={repliedList(event)}
/> />
))} ))}
@ -190,6 +128,13 @@ const Thread = () => {
</main> </main>
</> </>
); );
}
return (
<>
<Placeholder />
<div className="col-span-full h-0.5 bg-neutral-900"/> {/* This is the white line separator */}
</>
);
}; };
export default Thread; export default Thread;

View File

@ -1,10 +1,9 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { subGlobalFeed, subHashtagFeed, subNotifications } from "../utils/subscriptions"; import { subGlobalFeed, subHashtagFeed, subNote, subNotifications, subNotesOnce} from "../utils/subscriptions";
import { uniqBy } from "../utils/otherUtils"; import { uniqBy } from "../utils/otherUtils";
import { Event } from "nostr-tools"; import { Event } from "nostr-tools";
import { } from "../utils/subscriptions";
export const useUniqEvents = (hashtag?: string, notifications?: boolean) => { export const useFetchEvents = (hashtag?: string, notifications?: boolean, OP_eventID?: string) => {
const [events, setEvents] = useState<Event[]>([]); const [events, setEvents] = useState<Event[]>([]);
const age = Number(localStorage.getItem("age")) || 24; const age = Number(localStorage.getItem("age")) || 24;
@ -45,6 +44,8 @@ export const useUniqEvents = (hashtag?: string, notifications?: boolean) => {
if (hashtag) { if (hashtag) {
// Code from the second function // Code from the second function
unsubscribe = subHashtagFeed(hashtag, onEvent, age); unsubscribe = subHashtagFeed(hashtag, onEvent, age);
} else if (OP_eventID) {
unsubscribe = subNote(OP_eventID, onEvent);
} else if (notifications) { } else if (notifications) {
// Code from the third function // Code from the third function
let storedKeys = JSON.parse(localStorage.getItem("usedKeys") || "[]"); let storedKeys = JSON.parse(localStorage.getItem("usedKeys") || "[]");

View File

@ -236,7 +236,6 @@ export const subNotifications = (
onEvent: SubCallback, onEvent: SubCallback,
) => { ) => {
unsubAll(); unsubAll();
sub({ sub({
cb: (evt, relay) => { cb: (evt, relay) => {
onEvent(evt, relay); onEvent(evt, relay);

View File

@ -5,7 +5,7 @@
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
"forceConsistentCasingInFileNames": false, "forceConsistentCasingInFileNames": true,
"noEmit": true, "noEmit": true,
"esModuleInterop": true, "esModuleInterop": true,
"module": "esnext", "module": "esnext",