diff options
Diffstat (limited to 'gui/src/components/trill')
| -rw-r--r-- | gui/src/components/trill/Thread.tsx | 219 | ||||
| -rw-r--r-- | gui/src/components/trill/User.tsx | 71 |
2 files changed, 255 insertions, 35 deletions
diff --git a/gui/src/components/trill/Thread.tsx b/gui/src/components/trill/Thread.tsx new file mode 100644 index 0000000..a56ccf1 --- /dev/null +++ b/gui/src/components/trill/Thread.tsx @@ -0,0 +1,219 @@ +import useLocalState from "@/state/state"; +import Icon from "@/components/Icon"; +import spinner from "@/assets/triangles.svg"; +import Post from "@/components/post/Post"; +import { extractThread, toFlat } from "@/logic/trill/helpers"; +import type { FC, FullNode, Poast } from "@/types/trill"; +import Composer from "@/components/composer/Composer"; +import type { UserProfile } from "@/types/nostrill"; +import type { Ship } from "@/types/urbit"; +import { useEffect, useState } from "react"; + +export default function Thread({ + host, + id, + feed, + profile, +}: { + host: Ship; + id: string; + feed?: FC; + profile?: UserProfile; +}) { + const poast = feed?.feed[id]; + console.log({ poast }); + return ( + <> + <div className="thread-header"> + <div className="thread-nav"> + <button + className="back-btn" + onClick={() => window.history.back()} + title="Go back" + > + <Icon name="reply" size={16} /> + <span>Back to Feed</span> + </button> + </div> + <h2>Thread</h2> + <div className="thread-info"> + <span className="thread-host">~{host}</span> + <span className="thread-separator">•</span> + <span className="thread-id">#{id}</span> + </div> + </div> + <div id="feed-proper"> + {poast && poast.children.length === 0 ? ( + <Head poast={poast} profile={profile} /> + ) : ( + <Loader poast={poast} host={host} id={id} profile={profile} /> + )} + </div> + </> + ); +} +function Loader({ + host, + id, + profile, + poast, +}: { + host: Ship; + id: string; + poast?: Poast; + profile?: UserProfile; +}) { + const api = useLocalState((s) => s.api); + const [data, setData] = useState<FullNode>(); + const [error, setError] = useState(""); + console.log({ data }); + async function fetchThread() { + const res = await api!.scryThread(host, id); + if ("error" in res) setError(res.error); + else setData(res.ok); + } + useEffect(() => { + fetchThread(); + }, [host, id]); + + if (data) + return ( + <> + <Head poast={toFlat(data)} profile={profile} /> + <div id="thread-children"> + <ChildTree node={data} /> + </div> + </> + ); + if (poast) + return ( + <> + <Head poast={poast} profile={profile} /> + <div id="thread-children"> + <h2>Loading Replies...</h2> + <div className="loading-container"> + <img className="x-center" src={spinner} alt="Loading" /> + </div> + </div> + </> + ); + if (error) + return ( + <div className="thread-header"> + <h2>Error Loading Thread</h2> + <p className="error">{error}</p> + </div> + ); + else + return ( + <div id="feed-proper"> + <h2>Loading Thread...</h2> + <div className="loading-container"> + <img className="x-center" src={spinner} alt="Loading" /> + </div> + </div> + ); +} + +function Head({ poast, profile }: { poast: Poast; profile?: UserProfile }) { + return ( + <div id="thread-head"> + <Post user={{ urbit: poast.host }} poast={poast} profile={profile} /> + </div> + ); +} + +function ChildTree({ node }: { node: FullNode }) { + const profiles = useLocalState((s) => s.profiles); + const kids = Object.values(node.children || {}); + kids.sort((a, b) => b.time - a.time); + return ( + <> + {kids.map((k) => { + const profile = profiles.get(k.author); + return ( + <div key={k.id} className="minithread"> + <Post + user={{ urbit: k.author }} + profile={profile} + poast={toFlat(k)} + /> + <Grandchildren node={k} /> + </div> + ); + })} + </> + ); + function Grandchildren({ node }: { node: FullNode }) { + return ( + <div className="tail"> + <ChildTree node={node} /> + </div> + ); + } +} +// function ChildTree({ node }: { node: FullNode }) { +// const { threadChildren, replies } = extractThread(node); +// return ( +// <> +// <div id="tail"> +// {threadChildren.map((n) => { +// return ( +// <Post user={{ urbit: n.author }} key={n.id} poast={toFlat(n)} /> +// ); +// })} +// </div> +// <div id="replies"> +// {replies.map((n) => ( +// <ReplyThread key={n.id} node={n} /> +// ))} +// </div> +// </> +// ); +// } + +// function ReplyThread({ node }: { node: FullNode }) { +// // const { threadChildren, replies } = extractThread(node); +// const { replies } = extractThread(node); +// return ( +// <div className="trill-reply-thread"> +// <div className="head"> +// <Post user={{ urbit: node.author }} poast={toFlat(node)} /> +// </div> +// <div className="tail"> +// {replies.map((r) => ( +// <Post key={r.id} user={{ urbit: r.author }} poast={toFlat(r)} /> +// ))} +// </div> +// </div> +// ); +// } + +// function OwnData(props: Props) { +// const { api } = useLocalState((s) => ({ +// api: s.api, +// })); +// const { host, id } = props; +// async function fetchThread() { +// return await api!.scryThread(host, id); +// } +// const { isLoading, isError, data, refetch } = useQuery({ +// queryKey: ["trill-thread", host, id], +// queryFn: fetchThread, +// }); +// return isLoading ? ( +// <div className={props.className}> +// <div className="x-center not-found"> +// <p className="x-center">Scrying Post, please wait...</p> +// <img src={spinner} className="x-center s-100" alt="" /> +// </div> +// </div> +// ) : null; +// } +// function SomeoneElses(props: Props) { +// // const { api, following } = useLocalState((s) => ({ +// // api: s.api, +// // following: s.following, +// // })); +// return <div>ho</div>; +// } diff --git a/gui/src/components/trill/User.tsx b/gui/src/components/trill/User.tsx index b7b53d6..488b925 100644 --- a/gui/src/components/trill/User.tsx +++ b/gui/src/components/trill/User.tsx @@ -6,19 +6,17 @@ import Icon from "@/components/Icon"; import toast from "react-hot-toast"; import { useEffect, useState } from "react"; import type { FC } from "@/types/trill"; -import type { UserType } from "@/types/nostrill"; +import type { Ship } from "@/types/urbit"; function UserFeed({ - user, - userString, + patp, feed, isFollowLoading, setIsFollowLoading, isAccessLoading, setIsAccessLoading, }: { - user: UserType; - userString: string; + patp: Ship; feed: FC | undefined; isFollowLoading: boolean; setIsFollowLoading: (b: boolean) => void; @@ -40,27 +38,29 @@ function UserFeed({ console.log("fact", lastFact); console.log(isFollowLoading); if (!isFollowLoading) return; - const follow = lastFact?.fols; + if (!lastFact) return; + if (!("fols" in lastFact)) return; + const follow = lastFact.fols; if (!follow) return; if ("new" in follow) { - if (userString !== follow.new.user) return; - toast.success(`Now following ${userString}`); + if (patp !== follow.new.user) return; + toast.success(`Now following ${patp}`); setIsFollowLoading(false); addNotification({ type: "follow", - from: userString, - message: `You are now following ${userString}`, + from: patp, + message: `You are now following ${patp}`, }); } else if ("quit" in follow) { - toast.success(`Unfollowed ${userString}`); + toast.success(`Unfollowed ${patp}`); setIsFollowLoading(false); addNotification({ type: "unfollow", - from: userString, - message: `You unfollowed ${userString}`, + from: patp, + message: `You unfollowed ${patp}`, }); } - }, [lastFact, userString, isFollowLoading]); + }, [lastFact, patp, isFollowLoading]); const handleFollow = async () => { if (!api) return; @@ -68,13 +68,13 @@ function UserFeed({ setIsFollowLoading(true); try { if (!!feed) { - await api.unfollow(user); + await api.unfollow({ urbit: patp }); } else { - await api.follow(user); - toast.success(`Follow request sent to ${userString}`); + await api.follow({ urbit: patp }); + toast.success(`Follow request sent to ${patp}`); } } catch (error) { - toast.error(`Failed to ${!!feed ? "unfollow" : "follow"} ${userString}`); + toast.error(`Failed to ${!!feed ? "unfollow" : "follow"} ${patp}`); setIsFollowLoading(false); console.error("Follow error:", error); } @@ -82,31 +82,29 @@ function UserFeed({ const handleRequestAccess = async () => { if (!api) return; - if (!("urbit" in user)) return; - setIsAccessLoading(true); try { - const res = await api.peekFeed(user.urbit); - toast.success(`Access request sent to ${user.urbit}`); + const res = await api.peekFeed(patp); + toast.success(`Access request sent to ${patp}`); addNotification({ type: "access_request", - from: userString, - message: `Access request sent to ${userString}`, + from: patp, + message: `Access request sent to ${patp}`, }); if ("error" in res) toast.error(res.error); else { console.log("peeked", res.ok.feed); setFC(res.ok.feed); - if (res.ok.profile) addProfile(userString, res.ok.profile); + if (res.ok.profile) addProfile(patp, res.ok.profile); } } catch (error) { - toast.error(`Failed to request access from ${user.urbit}`); + toast.error(`Failed to request access from ${patp}`); console.error("Access request error:", error); } finally { setIsAccessLoading(false); } }; - console.log({ user, userString, feed, fc }); + console.log({ patp, feed, fc }); return ( <> @@ -149,15 +147,9 @@ function UserFeed({ </div> {feed && hasFeed ? ( - <div id="feed-proper"> - <Composer /> - <PostList data={feed} refetch={refetch} /> - </div> + <Inner feed={feed} refetch={refetch} /> ) : fc ? ( - <div id="feed-proper"> - <Composer /> - <PostList data={fc} refetch={refetch} /> - </div> + <Inner feed={fc} refetch={refetch} /> ) : null} {!feed && !fc && ( @@ -178,3 +170,12 @@ function UserFeed({ } export default UserFeed; + +export function Inner({ feed, refetch }: { feed: FC; refetch: any }) { + return ( + <div id="feed-proper"> + <Composer /> + <PostList data={feed} refetch={refetch} /> + </div> + ); +} |
