From 26bf02e771ba0f3bf722d1bd8f5ccd32f6fe1e66 Mon Sep 17 00:00:00 2001 From: smolgrrr Date: Wed, 8 Nov 2023 12:54:46 +1100 Subject: [PATCH] card modal and feed clean-up --- client/src/App.tsx | 2 +- .../{PostCard => Forms}/NewThreadCard.tsx | 2 +- .../{Thread => Forms}/ThreadPost.tsx | 0 client/src/components/Home.tsx | 14 ++- client/src/components/Modals/Card.tsx | 116 ++++++++++++++++++ .../{PostCard => Modals}/CardContainer.tsx | 0 client/src/components/Modals/Placeholder.tsx | 24 ++++ client/src/components/PostCard/PostCard.tsx | 101 --------------- client/src/components/Settings.tsx | 1 + client/src/components/{Thread => }/Thread.tsx | 71 +++++------ client/src/components/Thread/OPCard.tsx | 80 ------------ client/src/components/Thread/ReplyCard.tsx | 95 -------------- 12 files changed, 179 insertions(+), 327 deletions(-) rename client/src/components/{PostCard => Forms}/NewThreadCard.tsx (99%) rename client/src/components/{Thread => Forms}/ThreadPost.tsx (100%) create mode 100644 client/src/components/Modals/Card.tsx rename client/src/components/{PostCard => Modals}/CardContainer.tsx (100%) create mode 100644 client/src/components/Modals/Placeholder.tsx delete mode 100644 client/src/components/PostCard/PostCard.tsx rename client/src/components/{Thread => }/Thread.tsx (67%) delete mode 100644 client/src/components/Thread/OPCard.tsx delete mode 100644 client/src/components/Thread/ReplyCard.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index 5b29dc6..86a05f0 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -4,7 +4,7 @@ import Home from "./components/Home"; import Settings from "./components/Settings"; import SwipeableViews from "react-swipeable-views"; import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; -import Thread from "./components/Thread/Thread"; +import Thread from "./components/Thread"; import Header from "./components/Header/Header"; function App() { diff --git a/client/src/components/PostCard/NewThreadCard.tsx b/client/src/components/Forms/NewThreadCard.tsx similarity index 99% rename from client/src/components/PostCard/NewThreadCard.tsx rename to client/src/components/Forms/NewThreadCard.tsx index f062f3a..0489304 100644 --- a/client/src/components/PostCard/NewThreadCard.tsx +++ b/client/src/components/Forms/NewThreadCard.tsx @@ -1,4 +1,4 @@ -import CardContainer from "./CardContainer"; +import CardContainer from "../Modals/CardContainer"; import { ArrowUpTrayIcon, CpuChipIcon, diff --git a/client/src/components/Thread/ThreadPost.tsx b/client/src/components/Forms/ThreadPost.tsx similarity index 100% rename from client/src/components/Thread/ThreadPost.tsx rename to client/src/components/Forms/ThreadPost.tsx diff --git a/client/src/components/Home.tsx b/client/src/components/Home.tsx index f4771e6..80980dc 100644 --- a/client/src/components/Home.tsx +++ b/client/src/components/Home.tsx @@ -1,11 +1,13 @@ -import { useEffect, useState } from "react"; -import PostCard from "./PostCard/PostCard"; -import NewThreadCard from "./PostCard/NewThreadCard"; +import { useEffect, useState, useCallback } from "react"; +import PostCard from "./Modals/Card"; +import NewThreadCard from "./Forms/NewThreadCard"; import { uniqBy } from "../utils/utils"; // Assume getPow is a correct import now import { subGlobalFeed } from "../utils/subscriptions"; import { verifyPow } from "../utils/mine"; import { Event } from "nostr-tools"; +const DEFAULT_DIFFICULTY = 20; + const useUniqEvents = () => { const [events, setEvents] = useState([]); @@ -20,7 +22,7 @@ const useUniqEvents = () => { }; const Home = () => { - const filterDifficulty = localStorage.getItem("filterDifficulty") || 20; + const filterDifficulty = localStorage.getItem("filterDifficulty") || DEFAULT_DIFFICULTY; const [sortByTime, setSortByTime] = useState(true); const uniqEvents = useUniqEvents(); @@ -35,9 +37,9 @@ const Home = () => { sortByTime ? b.created_at - a.created_at : verifyPow(b) - verifyPow(a) ); - const toggleSort = () => { + const toggleSort = useCallback(() => { setSortByTime(prev => !prev); - }; + }, []); const getMetadataEvent = (event: Event) => { return uniqEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null; diff --git a/client/src/components/Modals/Card.tsx b/client/src/components/Modals/Card.tsx new file mode 100644 index 0000000..2dadd8a --- /dev/null +++ b/client/src/components/Modals/Card.tsx @@ -0,0 +1,116 @@ +import CardContainer from "./CardContainer"; +import { FolderIcon, CpuChipIcon } from "@heroicons/react/24/outline"; +import { parseContent } from "../../utils/content"; +import { Event, nip19 } from "nostr-tools"; +import { getMetadata } from "../../utils/utils"; +import ContentPreview from "./TextModal"; +import { renderMedia } from "../../utils/FileUpload"; +import { getIconFromHash } from "../../utils/deterministicProfileIcon"; +import { verifyPow } from "../../utils/mine"; +import { uniqBy } from "../../utils/utils"; +import { useNavigate } from 'react-router-dom'; + +const timeUnits = [ + { unit: 'w', value: 60 * 60 * 24 * 7 }, + { unit: 'd', value: 60 * 60 * 24 }, + { unit: 'h', value: 60 * 60 }, + { unit: 'm', value: 60 }, +]; + +const timeAgo = (unixTime: number) => { + let seconds = Math.floor(new Date().getTime() / 1000 - unixTime); + + if (seconds < 60) return `now`; + + for (let unit of timeUnits) { + if (seconds >= unit.value) { + return `${Math.floor(seconds / unit.value)}${unit.unit}`; + } + seconds %= unit.value; + } +}; + +interface CardProps { + key?: string | number; + event: Event; + metadata: Event | null; + replyCount: number; + repliedTo?: Event[] + type?: 'OP' | 'Reply' | 'Post'; +} + +const PostCard = ({ + key, + event, + metadata, + replyCount, + repliedTo, + type +}: CardProps) => { + const { comment, file } = parseContent(event); + const icon = getIconFromHash(event.pubkey); + const metadataParsed = metadata ? getMetadata(metadata) : null; + const navigate = useNavigate(); + + const handleClick = () => { + if (type !== "OP") { + navigate(`/thread/${nip19.noteEncode(event.id)}`); + } + }; + + return ( + +
+
+
+ {metadataParsed ? + + : +
+ } +
+ {metadataParsed?.name ?? 'Anonymous'} +
+
+
+
+ {verifyPow(event)} +
+ · +
+ {timeAgo(event.created_at)} +
+ · +
+ + {replyCount} +
+
+
+ {repliedTo &&
+ Reply to: + {uniqBy(repliedTo, 'pubkey').map((event, index) => ( +
+ {event.kind == 0 ? ( + + ) : ( +
+ )} +
+ ))} +
} +
+ +
+
+ {renderMedia(file)} + + ); +}; + +export default PostCard; \ No newline at end of file diff --git a/client/src/components/PostCard/CardContainer.tsx b/client/src/components/Modals/CardContainer.tsx similarity index 100% rename from client/src/components/PostCard/CardContainer.tsx rename to client/src/components/Modals/CardContainer.tsx diff --git a/client/src/components/Modals/Placeholder.tsx b/client/src/components/Modals/Placeholder.tsx new file mode 100644 index 0000000..a5bf659 --- /dev/null +++ b/client/src/components/Modals/Placeholder.tsx @@ -0,0 +1,24 @@ +const Placeholder = () => { + + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +}; + +export default Placeholder; diff --git a/client/src/components/PostCard/PostCard.tsx b/client/src/components/PostCard/PostCard.tsx deleted file mode 100644 index afdb2aa..0000000 --- a/client/src/components/PostCard/PostCard.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import CardContainer from "./CardContainer"; -import { FolderIcon, CpuChipIcon } from "@heroicons/react/24/outline"; -import { parseContent } from "../../utils/content"; -import { Event, nip19 } from "nostr-tools"; -import { getMetadata } from "../../utils/utils"; -import ContentPreview from "../Modals/TextModal"; -import { renderMedia } from "../../utils/FileUpload"; -import { getIconFromHash } from "../../utils/deterministicProfileIcon"; -import { verifyPow } from "../../utils/mine"; - -const timeAgo = (unixTime: number) => { - const seconds = Math.floor(new Date().getTime() / 1000 - unixTime); - - if (seconds < 60) return `now`; - - const minutes = Math.floor(seconds / 60); - if (minutes < 60) return `${minutes}m`; - - const hours = Math.floor(minutes / 60); - if (hours < 24) return `${hours}h`; - - const days = Math.floor(hours / 24); - if (days < 7) return `${days}d`; - - const weeks = Math.floor(days / 7); - return `${weeks}w`; -}; - -const PostCard = ({ - key, - event, - metadata, - replyCount, -}: { - key: string; - event: Event; - metadata: Event | null; - replyCount: number; -}) => { - let { comment, file } = parseContent(event); - const icon = getIconFromHash(event.pubkey); - - let metadataParsed = null; - if (metadata !== null) { - metadataParsed = getMetadata(metadata); - } - - return ( - - -
-
-
- {metadataParsed ? ( - <> - -
- {metadataParsed.name} -
- - ) : ( - <> -
-
Anonymous
- - )} -
-
-
- {verifyPow(event)} -
- · -
- {timeAgo(event.created_at)} -
- · -
- - {replyCount} -
-
-
-
- -
-
-
- {renderMedia(file)} - - ); -}; - -export default PostCard; diff --git a/client/src/components/Settings.tsx b/client/src/components/Settings.tsx index dd5ac3a..212a349 100644 --- a/client/src/components/Settings.tsx +++ b/client/src/components/Settings.tsx @@ -3,6 +3,7 @@ import React, { useEffect, useState } from 'react'; // import {publish} from './relays'; import { addRelay } from '../utils/relays'; import { CpuChipIcon } from '@heroicons/react/24/outline'; + const Settings = () => { const [filterDifficulty, setFilterDifficulty] = useState(localStorage.getItem('filterDifficulty') || 20); const [difficulty, setDifficulty] = useState(localStorage.getItem('difficulty') || 21); diff --git a/client/src/components/Thread/Thread.tsx b/client/src/components/Thread.tsx similarity index 67% rename from client/src/components/Thread/Thread.tsx rename to client/src/components/Thread.tsx index 21f83a7..e23094f 100644 --- a/client/src/components/Thread/Thread.tsx +++ b/client/src/components/Thread.tsx @@ -1,16 +1,14 @@ import { useParams } from 'react-router-dom'; import { useState } from "react"; import { Event, nip19 } from "nostr-tools" -import { subNote, subNotesOnce } from '../../utils/subscriptions'; +import { subNote, subNotesOnce } from '../utils/subscriptions'; import { useEffect } from 'react'; -import { uniqBy } from '../../utils/utils'; +import { uniqBy } from '../utils/utils'; import { DocumentTextIcon, FolderPlusIcon } from '@heroicons/react/24/outline'; -import { generatePrivateKey, getPublicKey, finishEvent } from 'nostr-tools'; -import { getPow } from '../../utils/mine'; -import { publish } from '../../utils/relays'; -import ThreadPost from './ThreadPost'; -import ReplyCard from './ReplyCard'; -import OPCard from './OPCard'; +import { getPow } from '../utils/mine'; +import ThreadPost from './Forms/ThreadPost'; +import PostCard from './Modals/Card'; +import Placeholder from './Modals/Placeholder'; const difficulty = 20 @@ -18,13 +16,16 @@ const difficulty = 20 const Thread = () => { const { id } = useParams(); const [events, setEvents] = useState([]); // Initialize state - let decodeResult = nip19.decode(id as string); + const [OPEvent, setOPEvent] = useState() const [showForm, setShowForm] = useState(false); const [postType, setPostType] = useState(""); const [hasRun, setHasRun] = useState(false); const [preOPEvents, setPreOPEvents] = useState(['']); const [sortByTime, setSortByTime] = useState(true); - const [filterDifficulty, setFilterDifficulty] = useState(localStorage.getItem("filterDifficulty") || "20"); + const filterDifficulty = useState(localStorage.getItem("filterDifficulty") || "20"); + + let decodeResult = nip19.decode(id as string); + let hexID = decodeResult.data as string; // Define your callback function for subGlobalFeed const onEvent = (event: Event, relay: string) => { @@ -32,28 +33,30 @@ const Thread = () => { }; useEffect(() => { + setHasRun(false) if (decodeResult.type === 'note') { - let id_to_hex: string = decodeResult.data; // Call your subNote function or do whatever you need to do with id_to_hex - subNote(id_to_hex, onEvent); + subNote(hexID, onEvent); } - // Subscribe to global feed when the component mounts - // Optionally, return a cleanup function to unsubscribe when the component unmounts return () => { // Your cleanup code here }; - }, []); // Empty dependency array means this useEffect runs once when the component mounts + }, [id]); // Empty dependency array means this useEffect runs once when the component mounts const uniqEvents = events.length > 0 ? uniqBy(events, "id") : []; useEffect(() => { if (!hasRun && events.length > 0) { - let OPNoteEvents = events[0].tags.filter(tag => tag[0] === 'e').map(tag => tag[1]); - console.log(OPNoteEvents); + let OPEvent = events.find(e => e.id === hexID); + + 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]); @@ -88,34 +91,16 @@ const Thread = () => { // Events sorted by PoW (assuming `getPow` returns a numerical representation of the PoW) const eventsSortedByPow = [...uniqEvents].slice(1) - .filter((event) => - getPow(event.id) > Number(filterDifficulty) && - event.kind === 1 - ).sort((a, b) => getPow(b.id) - getPow(a.id)); + .filter((event) => + getPow(event.id) > Number(filterDifficulty) && + event.kind === 1 + ).sort((a, b) => getPow(b.id) - getPow(a.id)); const displayedEvents = sortByTime ? eventsSortedByTime : eventsSortedByPow; if (!uniqEvents[0]) { return ( - <> -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- + ); } return ( @@ -125,9 +110,9 @@ const Thread = () => { {earlierEvents .filter(event => event.kind === 1) .sort((a, b) => a.created_at - b.created_at).map((event, index) => ( - + ))} - + {OPEvent && }
{
{/* This is the white line separator */} {displayedEvents.map((event, index) => ( - + ))}
diff --git a/client/src/components/Thread/OPCard.tsx b/client/src/components/Thread/OPCard.tsx deleted file mode 100644 index 121851b..0000000 --- a/client/src/components/Thread/OPCard.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import CardContainer from '../PostCard/CardContainer'; -import { FolderIcon } from '@heroicons/react/24/outline'; -import { parseContent } from '../../utils/content'; -import { Event } from 'nostr-tools'; -import { getMetadata } from '../../utils/utils'; -import ContentPreview from '../Modals/TextModal'; -import { renderMedia } from '../../utils/FileUpload'; -import { getIconFromHash } from '../../utils/deterministicProfileIcon'; - -const timeAgo = (unixTime: number) => { - const seconds = Math.floor((new Date().getTime() / 1000) - unixTime); - - if (seconds < 60) return `now`; - - const minutes = Math.floor(seconds / 60); - if (minutes < 60) return `${minutes}m`; - - const hours = Math.floor(minutes / 60); - if (hours < 24) return `${hours}h`; - - const days = Math.floor(hours / 24); - if (days < 7) return `${days}d`; - - const weeks = Math.floor(days / 7); - return `${weeks}w`; -}; - -const OPCard = ({ event, metadata, replyCount }: { event: Event, metadata: Event | null, replyCount: number}) => { - const { comment, file } = parseContent(event); - const icon = getIconFromHash(event.pubkey); - - let metadataParsed = null; - if (metadata !== null) { - metadataParsed = getMetadata(metadata); - } - - return ( - <> - -
-
-
- {metadataParsed ? - <> - -
{metadataParsed.name}
- - : - <> -
-
Anonymous
- - } -
-
-
- {event.id.match(/^0*([^\0]{2})/)?.[0] || 0} -
- · -
- {timeAgo(event.created_at)} -
- · -
- - {replyCount} -
-
-
-
- -
- {renderMedia(file)} -
- - - ); -}; - -export default OPCard; \ No newline at end of file diff --git a/client/src/components/Thread/ReplyCard.tsx b/client/src/components/Thread/ReplyCard.tsx deleted file mode 100644 index a415130..0000000 --- a/client/src/components/Thread/ReplyCard.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import CardContainer from '../PostCard/CardContainer' -import { FolderIcon } from '@heroicons/react/24/outline'; -import { parseContent } from '../../utils/content'; -import { Event } from 'nostr-tools'; -import { nip19 } from 'nostr-tools'; -import { getMetadata, uniqBy } from '../../utils/utils'; -import ContentPreview from '../Modals/TextModal'; -import { renderMedia } from '../../utils/FileUpload'; -import { getIconFromHash } from '../../utils/deterministicProfileIcon'; - -const timeAgo = (unixTime: number) => { - const seconds = Math.floor((new Date().getTime() / 1000) - unixTime); - - if (seconds < 60) return `now`; - - const minutes = Math.floor(seconds / 60); - if (minutes < 60) return `${minutes}m`; - - const hours = Math.floor(minutes / 60); - if (hours < 24) return `${hours}h`; - - const days = Math.floor(hours / 24); - if (days < 7) return `${days}d`; - - const weeks = Math.floor(days / 7); - return `${weeks}w`; -}; - -const ReplyCard = ({ event, metadata, replyCount, repliedTo }: { event: Event, metadata: Event | null, replyCount: number, repliedTo: Event[] }) => { - const { comment, file } = parseContent(event); - const icon = getIconFromHash(event.pubkey); - // const [events, setEvents] = useState([]); - - let metadataParsed = null; - if (metadata !== null) { - metadataParsed = getMetadata(metadata); - } - - // const replyPubkeys = event.tags.filter(tag => tag[0] === 'p'); - - - return ( - <> - - -