snort/packages/app/src/Pages/Root.tsx

265 lines
6.2 KiB
TypeScript

import { useContext, useEffect, useState } from "react";
import { Link, Outlet, RouteObject, useParams } from "react-router-dom";
import { FormattedMessage } from "react-intl";
import { unixNow } from "@snort/shared";
import { NostrLink } from "@snort/system";
import { SnortContext } from "@snort/system-react";
import Timeline from "@/Element/Feed/Timeline";
import { TimelineSubject } from "@/Feed/TimelineFeed";
import { debounce, getRelayName, sha256 } from "@/SnortUtils";
import useLogin from "@/Hooks/useLogin";
import Discover from "@/Pages/Discover";
import TrendingUsers from "@/Element/TrendingUsers";
import TrendingNotes from "@/Element/TrendingPosts";
import HashTagsPage from "@/Pages/HashTagsPage";
import SuggestedProfiles from "@/Element/SuggestedProfiles";
import { TaskList } from "@/Tasks/TaskList";
import TimelineFollows from "@/Element/Feed/TimelineFollows";
import { RootTabs } from "@/Element/RootTabs";
import { DeckContext } from "@/Pages/DeckLayout";
import { TopicsPage } from "./TopicsPage";
import TrendingHashtags from "@/Element/TrendingHashtags";
import messages from "./messages";
interface RelayOption {
url: string;
paid: boolean;
}
export default function RootPage() {
return (
<>
<div className="main-content p">
<RootTabs base="" />
</div>
<div className="main-content">
<Outlet />
</div>
</>
);
}
const FollowsHint = () => {
const { publicKey: pubKey, follows } = useLogin();
if (follows.item?.length === 0 && pubKey) {
return (
<FormattedMessage
{...messages.NoFollows}
values={{
newUsersPage: (
<Link to={"/discover"}>
<FormattedMessage {...messages.NewUsers} />
</Link>
),
}}
/>
);
}
return null;
};
export const GlobalTab = () => {
const { relays } = useLogin();
const [relay, setRelay] = useState<RelayOption>();
const [allRelays, setAllRelays] = useState<RelayOption[]>();
const [now] = useState(unixNow());
const system = useContext(SnortContext);
function globalRelaySelector() {
if (!allRelays || allRelays.length === 0) return null;
const paidRelays = allRelays.filter(a => a.paid);
const publicRelays = allRelays.filter(a => !a.paid);
return (
<div className="flex items-center g8 justify-end nowrap">
<h3>
<FormattedMessage
defaultMessage="Relay"
id="KHK8B9"
description="Label for reading global feed from specific relays"
/>
</h3>
<select
className="f-ellipsis"
onChange={e => setRelay(allRelays.find(a => a.url === e.target.value))}
value={relay?.url}>
{paidRelays.length > 0 && (
<optgroup label="Paid Relays">
{paidRelays.map(a => (
<option key={a.url} value={a.url}>
{getRelayName(a.url)}
</option>
))}
</optgroup>
)}
<optgroup label="Public Relays">
{publicRelays.map(a => (
<option key={a.url} value={a.url}>
{getRelayName(a.url)}
</option>
))}
</optgroup>
</select>
</div>
);
}
useEffect(() => {
return debounce(500, () => {
const ret: RelayOption[] = [];
system.Sockets.forEach(v => {
if (v.connected) {
ret.push({
url: v.address,
paid: v.info?.limitation?.payment_required ?? false,
});
}
});
ret.sort(a => (a.paid ? -1 : 1));
if (ret.length > 0 && !relay) {
setRelay(ret[0]);
}
setAllRelays(ret);
});
}, [relays, relay]);
return (
<>
{globalRelaySelector()}
{relay && (
<Timeline
subject={{
type: "global",
items: [],
relay: [relay.url],
discriminator: `all-${sha256(relay.url)}`,
}}
postsOnly={false}
method={"TIME_RANGE"}
window={600}
now={now}
/>
)}
</>
);
};
export const NotesTab = () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const deckContext = useContext(DeckContext);
return (
<>
<FollowsHint />
<TaskList />
<TimelineFollows
postsOnly={true}
noteOnClick={
deckContext
? ev => {
deckContext.setThread(NostrLink.fromEvent(ev));
}
: undefined
}
/>
</>
);
};
export const ConversationsTab = () => {
return <TimelineFollows postsOnly={false} />;
};
export const TagsTab = (params: { tag?: string }) => {
const { tag } = useParams();
const t = params.tag ?? tag ?? "";
const subject: TimelineSubject = {
type: "hashtag",
items: [t],
discriminator: `tags-${t}`,
streams: true,
};
return <Timeline subject={subject} postsOnly={false} method={"TIME_RANGE"} />;
};
const DefaultTab = () => {
const { preferences, publicKey } = useLogin(s => ({
preferences: s.appData.item.preferences,
publicKey: s.publicKey,
}));
const tab = publicKey ? preferences.defaultRootTab ?? `notes` : `trending/notes`;
const elm = RootTabRoutes.find(a => a.path === tab)?.element;
return elm;
};
export const RootTabRoutes = [
{
path: "",
element: <DefaultTab />,
},
{
path: "global",
element: <GlobalTab />,
},
{
path: "notes",
element: <NotesTab />,
},
{
path: "conversations",
element: <ConversationsTab />,
},
{
path: "discover",
element: <Discover />,
},
{
path: "tag/:tag",
element: <TagsTab />,
},
{
path: "trending/notes",
element: <TrendingNotes />,
},
{
path: "trending/people",
element: (
<div className="p">
<TrendingUsers />
</div>
),
},
{
path: "trending/hashtags",
element: <TrendingHashtags />,
},
{
path: "suggested",
element: (
<div className="p">
<SuggestedProfiles />
</div>
),
},
{
path: "t/:tag",
element: <HashTagsPage />,
},
{
path: "topics",
element: <TopicsPage />,
},
];
export const RootRoutes = [
{
path: "/",
element: <RootPage />,
children: RootTabRoutes,
},
] as RouteObject[];