summaryrefslogtreecommitdiff
path: root/gui/src/components/trill
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src/components/trill')
-rw-r--r--gui/src/components/trill/Thread.tsx219
-rw-r--r--gui/src/components/trill/User.tsx71
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>
+ );
+}