mirror of
https://github.com/luminous-devs/lume.git
synced 2024-10-01 09:21:07 +00:00
feat: redesign relay screen
This commit is contained in:
parent
f908c46a19
commit
dae4b1d52b
@ -42,30 +42,6 @@ export default function Router() {
|
|||||||
return { Component: NWCScreen };
|
return { Component: NWCScreen };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "relays",
|
|
||||||
async lazy() {
|
|
||||||
const { RelaysScreen } = await import("./routes/relays");
|
|
||||||
return { Component: RelaysScreen };
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "relays/:url",
|
|
||||||
loader: async ({ params }) => {
|
|
||||||
return defer({
|
|
||||||
relay: fetch(`https://${params.url}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
Accept: "application/nostr+json",
|
|
||||||
},
|
|
||||||
}).then((res) => res.json()),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async lazy() {
|
|
||||||
const { RelayScreen } = await import("./routes/relays/relay");
|
|
||||||
return { Component: RelayScreen };
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "settings",
|
path: "settings",
|
||||||
element: <SettingsLayout />,
|
element: <SettingsLayout />,
|
||||||
@ -155,6 +131,51 @@ export default function Router() {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "relays",
|
||||||
|
async lazy() {
|
||||||
|
const { RelaysScreen } = await import("./routes/relays");
|
||||||
|
return { Component: RelaysScreen };
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
index: true,
|
||||||
|
async lazy() {
|
||||||
|
const { RelayGlobalScreen } = await import(
|
||||||
|
"./routes/relays/global"
|
||||||
|
);
|
||||||
|
return { Component: RelayGlobalScreen };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "follows",
|
||||||
|
async lazy() {
|
||||||
|
const { RelayFollowsScreen } = await import(
|
||||||
|
"./routes/relays/follows"
|
||||||
|
);
|
||||||
|
return { Component: RelayFollowsScreen };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ":url",
|
||||||
|
loader: async ({ request, params }) => {
|
||||||
|
return defer({
|
||||||
|
relay: fetch(`https://${params.url}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/nostr+json",
|
||||||
|
},
|
||||||
|
signal: request.signal,
|
||||||
|
}).then((res) => res.json()),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async lazy() {
|
||||||
|
const { RelayUrlScreen } = await import("./routes/relays/url");
|
||||||
|
return { Component: RelayUrlScreen };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "depot",
|
path: "depot",
|
||||||
children: [
|
children: [
|
||||||
|
@ -91,13 +91,13 @@ export function ActivityList() {
|
|||||||
) : (
|
) : (
|
||||||
allEvents.map((event) => renderEvenKind(event))
|
allEvents.map((event) => renderEvenKind(event))
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center justify-center h-16">
|
<div className="flex items-center justify-center h-16 px-5">
|
||||||
{hasNextPage ? (
|
{hasNextPage ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => fetchNextPage()}
|
onClick={() => fetchNextPage()}
|
||||||
disabled={!hasNextPage || isFetchingNextPage}
|
disabled={!hasNextPage || isFetchingNextPage}
|
||||||
className="inline-flex items-center justify-center w-full h-12 gap-2 font-medium bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800 rounded-xl focus:outline-none"
|
className="inline-flex items-center justify-center w-full h-12 gap-2 font-medium bg-black/10 hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20 rounded-xl focus:outline-none"
|
||||||
>
|
>
|
||||||
{isFetchingNextPage ? (
|
{isFetchingNextPage ? (
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
<LoaderIcon className="size-5 animate-spin" />
|
||||||
|
@ -49,18 +49,18 @@ export function RelayEventList({ relayUrl }: { relayUrl: string }) {
|
|||||||
(event: NDKEvent) => {
|
(event: NDKEvent) => {
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case NDKKind.Text:
|
case NDKKind.Text:
|
||||||
return <TextNote key={event.id} event={event} />;
|
return <TextNote key={event.id} event={event} className="mt-3" />;
|
||||||
case NDKKind.Repost:
|
case NDKKind.Repost:
|
||||||
return <RepostNote key={event.id} event={event} />;
|
return <RepostNote key={event.id} event={event} className="mt-3" />;
|
||||||
default:
|
default:
|
||||||
return <TextNote key={event.id} event={event} />;
|
return <TextNote key={event.id} event={event} className="mt-3" />;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[data],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VList className="mx-auto h-full w-full max-w-[500px] pt-10 scrollbar-none">
|
<VList className="mx-auto h-full w-full max-w-[500px] px-3 scrollbar-none">
|
||||||
{status === "pending" ? (
|
{status === "pending" ? (
|
||||||
<NoteSkeleton />
|
<NoteSkeleton />
|
||||||
) : (
|
) : (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useRelay } from "@lume/ark";
|
import { useRelaylist } from "@lume/ark";
|
||||||
import { PlusIcon } from "@lume/icons";
|
import { PlusIcon } from "@lume/icons";
|
||||||
import { NDKRelayUrl } from "@nostr-dev-kit/ndk";
|
import { NDKRelayUrl } from "@nostr-dev-kit/ndk";
|
||||||
import { normalizeRelayUrl } from "nostr-fetch";
|
import { normalizeRelayUrl } from "nostr-fetch";
|
||||||
@ -8,7 +8,8 @@ import { toast } from "sonner";
|
|||||||
const domainRegex = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/;
|
const domainRegex = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/;
|
||||||
|
|
||||||
export function RelayForm() {
|
export function RelayForm() {
|
||||||
const { connectRelay } = useRelay();
|
const { connectRelay } = useRelaylist();
|
||||||
|
|
||||||
const [relay, setRelay] = useState<{
|
const [relay, setRelay] = useState<{
|
||||||
url: NDKRelayUrl;
|
url: NDKRelayUrl;
|
||||||
purpose: "read" | "write" | undefined;
|
purpose: "read" | "write" | undefined;
|
||||||
@ -35,28 +36,24 @@ export function RelayForm() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex gap-2">
|
||||||
<div className="flex gap-2">
|
<input
|
||||||
<input
|
className="h-11 w-full rounded-lg border-transparent px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 bg-white/50 dark:bg-black/50 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
|
||||||
className="h-11 flex-1 rounded-lg border-transparent bg-neutral-100 px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-900 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
|
placeholder="wss://"
|
||||||
placeholder="wss://"
|
spellCheck={false}
|
||||||
spellCheck={false}
|
autoComplete="off"
|
||||||
autoComplete="off"
|
autoCorrect="off"
|
||||||
autoCorrect="off"
|
autoCapitalize="off"
|
||||||
autoCapitalize="off"
|
value={relay.url}
|
||||||
value={relay.url}
|
onChange={(e) => setRelay((prev) => ({ ...prev, url: e.target.value }))}
|
||||||
onChange={(e) =>
|
/>
|
||||||
setRelay((prev) => ({ ...prev, url: e.target.value }))
|
<button
|
||||||
}
|
type="button"
|
||||||
/>
|
onClick={() => create()}
|
||||||
<button
|
className="inline-flex size-11 shrink-0 items-center justify-center rounded-lg bg-blue-500 text-white hover:bg-blue-600"
|
||||||
type="button"
|
>
|
||||||
onClick={() => create()}
|
<PlusIcon className="size-5" />
|
||||||
className="inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-lg bg-blue-500 text-white hover:bg-blue-600"
|
</button>
|
||||||
>
|
|
||||||
<PlusIcon className="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
38
apps/desktop/src/routes/relays/components/relayItem.tsx
Normal file
38
apps/desktop/src/routes/relays/components/relayItem.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { useRelaylist } from "@lume/ark";
|
||||||
|
import { PlusIcon, ShareIcon } from "@lume/icons";
|
||||||
|
import { normalizeRelayUrl } from "nostr-fetch";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
export function RelayItem({ url }: { url: string }) {
|
||||||
|
const domain = new URL(url).hostname;
|
||||||
|
const { connectRelay } = useRelaylist();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-14 w-full items-center justify-between border-b border-neutral-100 px-5 dark:border-neutral-950">
|
||||||
|
<div className="inline-flex items-center gap-2">
|
||||||
|
<span className="text-sm font-semibold text-neutral-500 dark:text-neutral-400">
|
||||||
|
Relay:{" "}
|
||||||
|
</span>
|
||||||
|
<span className="max-w-[200px] truncate text-sm font-medium text-neutral-900 dark:text-neutral-100">
|
||||||
|
{url}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="inline-flex items-center gap-2">
|
||||||
|
<Link
|
||||||
|
to={`/relays/${domain}/`}
|
||||||
|
className="inline-flex h-6 items-center justify-center gap-1 rounded bg-neutral-100 px-1.5 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||||
|
>
|
||||||
|
<ShareIcon className="h-3 w-3" />
|
||||||
|
Inspect
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => connectRelay.mutate(normalizeRelayUrl(url))}
|
||||||
|
className="inline-flex h-6 w-6 items-center justify-center rounded bg-blue-100 text-blue-500 hover:bg-blue-200 dark:bg-blue-900 hover:dark:bg-blue-800"
|
||||||
|
>
|
||||||
|
<PlusIcon className="size-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -27,7 +27,7 @@ export function RelayList() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-span-2 border-r border-neutral-100 dark:border-neutral-900">
|
<div className="col-span-2 bg-white">
|
||||||
{status === "pending" ? (
|
{status === "pending" ? (
|
||||||
<div className="flex h-full w-full items-center justify-center pb-10">
|
<div className="flex h-full w-full items-center justify-center pb-10">
|
||||||
<div className="inline-flex flex-col items-center justify-center gap-2">
|
<div className="inline-flex flex-col items-center justify-center gap-2">
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
import { useArk } from "@lume/ark";
|
import { useArk } from "@lume/ark";
|
||||||
import { CancelIcon, RefreshIcon } from "@lume/icons";
|
import { CancelIcon, RefreshIcon } from "@lume/icons";
|
||||||
import { useStorage } from "@lume/storage";
|
import { cn } from "@lume/utils";
|
||||||
import { NDKKind } from "@nostr-dev-kit/ndk";
|
import { NDKKind } from "@nostr-dev-kit/ndk";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { RelayForm } from "./relayForm";
|
import { RelayForm } from "./relayForm";
|
||||||
|
|
||||||
export function UserRelayList() {
|
export function RelaySidebar({ className }: { className?: string }) {
|
||||||
const ark = useArk();
|
const ark = useArk();
|
||||||
const storage = useStorage();
|
|
||||||
|
|
||||||
const { status, data, refetch } = useQuery({
|
const { status, data, refetch } = useQuery({
|
||||||
queryKey: ["relays", ark.account.pubkey],
|
queryKey: ["relay-personal"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const event = await ark.getEventByFilter({
|
const event = await ark.getEventByFilter({
|
||||||
filter: {
|
filter: {
|
||||||
@ -20,7 +19,7 @@ export function UserRelayList() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!event) return [];
|
if (!event) return [];
|
||||||
return event.tags;
|
return event.tags.filter((tag) => tag[0] === "r");
|
||||||
},
|
},
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
});
|
});
|
||||||
@ -30,8 +29,13 @@ export function UserRelayList() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-span-1">
|
<div
|
||||||
<div className="inline-flex items-center justify-between w-full h-16 px-3 border-b border-neutral-100 dark:border-neutral-900">
|
className={cn(
|
||||||
|
"rounded-l-xl bg-white/50 backdrop-blur-xl dark:bg-black/50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center justify-between w-full h-14 px-3 border-b border-black/10 dark:border-white/10">
|
||||||
<h3 className="font-semibold">Connected relays</h3>
|
<h3 className="font-semibold">Connected relays</h3>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -54,7 +58,7 @@ export function UserRelayList() {
|
|||||||
data.map((item) => (
|
data.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item[1]}
|
key={item[1]}
|
||||||
className="flex items-center justify-between px-3 rounded-lg group h-11 bg-neutral-100 dark:bg-neutral-900"
|
className="flex items-center justify-between px-3 rounded-lg group h-11 bg-white/50 dark:bg-black/50"
|
||||||
>
|
>
|
||||||
<div className="inline-flex items-baseline gap-2">
|
<div className="inline-flex items-baseline gap-2">
|
||||||
{currentRelays.has(item[1]) ? (
|
{currentRelays.has(item[1]) ? (
|
||||||
@ -69,7 +73,7 @@ export function UserRelayList() {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<p className="max-w-[20rem] truncate text-sm font-medium text-neutral-900 dark:text-neutral-100">
|
<p className="max-w-[20rem] truncate text-sm font-medium text-neutral-900 dark:text-neutral-100">
|
||||||
{item[1]}
|
{item[1].replace("wss://", "").replace("ws://", "")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="inline-flex items-center gap-2">
|
<div className="inline-flex items-center gap-2">
|
33
apps/desktop/src/routes/relays/follows.tsx
Normal file
33
apps/desktop/src/routes/relays/follows.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { useArk } from "@lume/ark";
|
||||||
|
import { LoaderIcon, PlusIcon, ShareIcon } from "@lume/icons";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { VList } from "virtua";
|
||||||
|
import { RelayItem } from "./components/relayItem";
|
||||||
|
|
||||||
|
export function RelayFollowsScreen() {
|
||||||
|
const ark = useArk();
|
||||||
|
const { isLoading, data: relays } = useQuery({
|
||||||
|
queryKey: ["relay-follows"],
|
||||||
|
queryFn: async ({ signal }: { signal: AbortSignal }) => {
|
||||||
|
return await ark.getAllRelaysFromContacts();
|
||||||
|
},
|
||||||
|
refetchOnMount: false,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
<LoaderIcon className="size-5 animate-spin" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VList itemSize={49}>
|
||||||
|
{relays.map((item: string) => (
|
||||||
|
<RelayItem key={item} url={item} />
|
||||||
|
))}
|
||||||
|
</VList>
|
||||||
|
);
|
||||||
|
}
|
34
apps/desktop/src/routes/relays/global.tsx
Normal file
34
apps/desktop/src/routes/relays/global.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { LoaderIcon } from "@lume/icons";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { fetch } from "@tauri-apps/plugin-http";
|
||||||
|
import { VList } from "virtua";
|
||||||
|
import { RelayItem } from "./components/relayItem";
|
||||||
|
|
||||||
|
export function RelayGlobalScreen() {
|
||||||
|
const { isLoading, data: relays } = useQuery({
|
||||||
|
queryKey: ["relay-global"],
|
||||||
|
queryFn: async ({ signal }: { signal: AbortSignal }) => {
|
||||||
|
const res = await fetch("https://api.nostr.watch/v1/online", { signal });
|
||||||
|
if (!res.ok) throw new Error("Failed to get online relays");
|
||||||
|
return (await res.json()) as string[];
|
||||||
|
},
|
||||||
|
refetchOnMount: false,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
<LoaderIcon className="size-5 animate-spin" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VList itemSize={49}>
|
||||||
|
{relays.map((item: string) => (
|
||||||
|
<RelayItem key={item} url={item} />
|
||||||
|
))}
|
||||||
|
</VList>
|
||||||
|
);
|
||||||
|
}
|
@ -1,11 +1,45 @@
|
|||||||
import { RelayList } from "./components/relayList";
|
import { cn } from "@lume/utils";
|
||||||
import { UserRelayList } from "./components/userRelayList";
|
import { NavLink, Outlet } from "react-router-dom";
|
||||||
|
import { RelaySidebar } from "./components/sidebar";
|
||||||
|
|
||||||
export function RelaysScreen() {
|
export function RelaysScreen() {
|
||||||
return (
|
return (
|
||||||
<div className="grid h-full w-full grid-cols-3">
|
<div className="grid h-full w-full lg:grid-cols-4 xl:grid-cols-5 rounded-xl shadow-[rgba(50,_50,_105,_0.15)_0px_2px_5px_0px,_rgba(0,_0,_0,_0.05)_0px_1px_1px_0px] dark:shadow-none dark:ring-1 dark:ring-white/10">
|
||||||
<RelayList />
|
<RelaySidebar className="col-span-1" />
|
||||||
<UserRelayList />
|
<div className="col-span-3 xl:col-span-4 flex flex-col rounded-r-xl bg-white dark:bg-black">
|
||||||
|
<div className="h-14 shrink-0 flex px-5 items-center gap-6 border-b border-neutral-100 dark:border-neutral-950">
|
||||||
|
<NavLink
|
||||||
|
end
|
||||||
|
to={"/relays/"}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
cn(
|
||||||
|
"h-9 w-24 rounded-lg inline-flex items-center justify-center font-medium",
|
||||||
|
isActive
|
||||||
|
? "bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-950 dark:hover:bg-neutral-900"
|
||||||
|
: "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Global
|
||||||
|
</NavLink>
|
||||||
|
<NavLink
|
||||||
|
to={"/relays/follows/"}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
cn(
|
||||||
|
"h-9 w-24 rounded-lg inline-flex items-center justify-center font-medium",
|
||||||
|
isActive
|
||||||
|
? "bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-950 dark:hover:bg-neutral-900"
|
||||||
|
: "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Follows
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col flex-1 min-h-0 overflow-y-auto">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,178 +0,0 @@
|
|||||||
import { ArrowLeftIcon, LoaderIcon } from "@lume/icons";
|
|
||||||
import { NIP11 } from "@lume/types";
|
|
||||||
import { User } from "@lume/ui";
|
|
||||||
import { Suspense } from "react";
|
|
||||||
import { Await, useLoaderData, useNavigate, useParams } from "react-router-dom";
|
|
||||||
import { RelayEventList } from "./components/relayEventList";
|
|
||||||
|
|
||||||
export function RelayScreen() {
|
|
||||||
const { url } = useParams();
|
|
||||||
|
|
||||||
const data: { relay?: { [key: string]: string } } = useLoaderData();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const getSoftwareName = (url: string) => {
|
|
||||||
const filename = url.substring(url.lastIndexOf("/") + 1);
|
|
||||||
return filename.replace(".git", "");
|
|
||||||
};
|
|
||||||
|
|
||||||
const titleCase = (s: string) => {
|
|
||||||
return s
|
|
||||||
.replace(/^[-_]*(.)/, (_, c) => c.toUpperCase())
|
|
||||||
.replace(/[-_]+(.)/g, (_, c) => ` ${c.toUpperCase()}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="grid h-full w-full grid-cols-3">
|
|
||||||
<div className="col-span-2 border-r border-neutral-100 dark:border-neutral-900">
|
|
||||||
<div className="inline-flex h-16 w-full items-center gap-2.5 border-b border-neutral-100 px-3 dark:border-neutral-900">
|
|
||||||
<button type="button" onClick={() => navigate(-1)}>
|
|
||||||
<ArrowLeftIcon className="h-5 w-5 text-neutral-500 hover:text-neutral-600 dark:text-neutral-600 dark:hover:text-neutral-500" />
|
|
||||||
</button>
|
|
||||||
<h3 className="font-semibold text-neutral-950 dark:text-neutral-50">
|
|
||||||
Global events
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<RelayEventList relayUrl={url} />
|
|
||||||
</div>
|
|
||||||
<div className="col-span-1">
|
|
||||||
<div className="inline-flex h-16 w-full items-center border-b border-neutral-100 px-3 dark:border-neutral-900">
|
|
||||||
<h3 className="font-semibold text-neutral-900 dark:text-neutral-100">
|
|
||||||
Information
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div className="mt-4 px-3">
|
|
||||||
<Suspense
|
|
||||||
fallback={
|
|
||||||
<div className="flex items-center gap-2 text-sm font-medium text-neutral-900 dark:text-neutral-100">
|
|
||||||
<LoaderIcon className="h-4 w-4 animate-spin" />
|
|
||||||
Loading...
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Await
|
|
||||||
resolve={data.relay}
|
|
||||||
errorElement={
|
|
||||||
<div className="text-sm font-medium">
|
|
||||||
<p>Could not load relay information 😬</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{(resolvedRelay: NIP11) => (
|
|
||||||
<div className="flex flex-col gap-5">
|
|
||||||
<div>
|
|
||||||
<h3 className="font-semibold leading-tight text-neutral-900 dark:text-neutral-100">
|
|
||||||
{resolvedRelay.name}
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm font-medium text-neutral-600 dark:text-neutral-500">
|
|
||||||
{resolvedRelay.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{resolvedRelay.pubkey ? (
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<h5 className="text-sm font-semibold text-neutral-600 dark:text-neutral-400">
|
|
||||||
Owner:
|
|
||||||
</h5>
|
|
||||||
<div className="w-full rounded-lg bg-neutral-100 px-2 py-2 dark:bg-neutral-900">
|
|
||||||
<User pubkey={resolvedRelay.pubkey} variant="simple" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{resolvedRelay.contact ? (
|
|
||||||
<div>
|
|
||||||
<h5 className="text-sm font-semibold text-neutral-600 dark:text-neutral-400">
|
|
||||||
Contact:
|
|
||||||
</h5>
|
|
||||||
<a
|
|
||||||
href={`mailto:${resolvedRelay.contact}`}
|
|
||||||
target="_blank"
|
|
||||||
className="underline after:content-['_↗'] hover:text-blue-600"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
mailto:{resolvedRelay.contact}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div>
|
|
||||||
<h5 className="text-sm font-semibold text-neutral-600 dark:text-neutral-400">
|
|
||||||
Software:
|
|
||||||
</h5>
|
|
||||||
<a
|
|
||||||
href={resolvedRelay.software}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="underline after:content-['_↗'] hover:text-blue-600"
|
|
||||||
>
|
|
||||||
{`${getSoftwareName(resolvedRelay.software)} - ${
|
|
||||||
resolvedRelay.version
|
|
||||||
}`}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h5 className="text-sm font-semibold text-neutral-600 dark:text-neutral-400">
|
|
||||||
Supported NIPs:
|
|
||||||
</h5>
|
|
||||||
<div className="mt-2 grid grid-cols-7 gap-2">
|
|
||||||
{resolvedRelay.supported_nips.map((item) => (
|
|
||||||
<a
|
|
||||||
key={item}
|
|
||||||
href={`https://nips.be/${item}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="inline-flex aspect-square h-auto w-full items-center justify-center rounded-lg bg-neutral-100 text-sm font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-900"
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{resolvedRelay.limitation ? (
|
|
||||||
<div>
|
|
||||||
<h5 className="text-sm font-semibold text-neutral-600 dark:text-neutral-400">
|
|
||||||
Limitation
|
|
||||||
</h5>
|
|
||||||
<div className="flex flex-col gap-2 divide-y divide-white/5">
|
|
||||||
{Object.keys(resolvedRelay.limitation).map(
|
|
||||||
(key, index) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={key + index}
|
|
||||||
className="flex items-baseline justify-between pt-2"
|
|
||||||
>
|
|
||||||
<p className="text-sm font-medium text-neutral-900 dark:text-neutral-100">
|
|
||||||
{titleCase(key)}:
|
|
||||||
</p>
|
|
||||||
<p className="text-sm font-medium text-neutral-600 dark:text-neutral-400">
|
|
||||||
{resolvedRelay.limitation[key].toString()}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{resolvedRelay.payments_url ? (
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<a
|
|
||||||
href={resolvedRelay.payments_url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="inline-flex h-10 w-full items-center justify-center rounded-lg bg-blue-500 text-sm font-medium hover:bg-blue-600"
|
|
||||||
>
|
|
||||||
Open payment website
|
|
||||||
</a>
|
|
||||||
<span className="text-center text-xs text-neutral-600 dark:text-neutral-400">
|
|
||||||
You need to make a payment to connect this relay
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Await>
|
|
||||||
</Suspense>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
161
apps/desktop/src/routes/relays/url.tsx
Normal file
161
apps/desktop/src/routes/relays/url.tsx
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import { ArrowLeftIcon, LoaderIcon } from "@lume/icons";
|
||||||
|
import { NIP11 } from "@lume/types";
|
||||||
|
import { User } from "@lume/ui";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import { Await, useLoaderData, useNavigate, useParams } from "react-router-dom";
|
||||||
|
import { RelayEventList } from "./components/relayEventList";
|
||||||
|
|
||||||
|
export function RelayUrlScreen() {
|
||||||
|
const { url } = useParams();
|
||||||
|
|
||||||
|
const data: { relay?: { [key: string]: string } } = useLoaderData();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const getSoftwareName = (url: string) => {
|
||||||
|
const filename = url.substring(url.lastIndexOf("/") + 1);
|
||||||
|
return filename.replace(".git", "");
|
||||||
|
};
|
||||||
|
|
||||||
|
const titleCase = (s: string) => {
|
||||||
|
return s
|
||||||
|
.replace(/^[-_]*(.)/, (_, c) => c.toUpperCase())
|
||||||
|
.replace(/[-_]+(.)/g, (_, c) => ` ${c.toUpperCase()}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid h-full w-full grid-cols-3">
|
||||||
|
<div className="col-span-2 border-r border-neutral-100 dark:border-neutral-900">
|
||||||
|
<RelayEventList relayUrl={url} />
|
||||||
|
</div>
|
||||||
|
<div className="col-span-1 px-3 py-3">
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<div className="flex items-center gap-2 text-sm font-medium text-neutral-900 dark:text-neutral-100">
|
||||||
|
<LoaderIcon className="h-4 w-4 animate-spin" />
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Await
|
||||||
|
resolve={data.relay}
|
||||||
|
errorElement={
|
||||||
|
<div className="text-sm font-medium">
|
||||||
|
<p>Could not load relay information 😬</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{(resolvedRelay: NIP11) => (
|
||||||
|
<div className="flex flex-col gap-5">
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold">{resolvedRelay.name}</h3>
|
||||||
|
<p className="text-sm text-neutral-600 dark:text-neutral-500">
|
||||||
|
{resolvedRelay.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{resolvedRelay.pubkey ? (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<h5 className="text-sm font-semibold text-neutral-600 dark:text-neutral-400">
|
||||||
|
Owner:
|
||||||
|
</h5>
|
||||||
|
<div className="w-full rounded-lg bg-neutral-100 px-2 py-2 dark:bg-neutral-900">
|
||||||
|
<User pubkey={resolvedRelay.pubkey} variant="simple" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{resolvedRelay.contact ? (
|
||||||
|
<div>
|
||||||
|
<h5 className="text-sm font-semibold text-neutral-600 dark:text-neutral-400">
|
||||||
|
Contact:
|
||||||
|
</h5>
|
||||||
|
<a
|
||||||
|
href={`mailto:${resolvedRelay.contact}`}
|
||||||
|
target="_blank"
|
||||||
|
className="truncate underline after:content-['_↗'] hover:text-blue-500"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{resolvedRelay.contact}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div>
|
||||||
|
<h5 className="text-sm font-semibold text-neutral-600 dark:text-neutral-400">
|
||||||
|
Software:
|
||||||
|
</h5>
|
||||||
|
<a
|
||||||
|
href={resolvedRelay.software}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="underline after:content-['_↗'] hover:text-blue-500"
|
||||||
|
>
|
||||||
|
{`${getSoftwareName(resolvedRelay.software)} - ${
|
||||||
|
resolvedRelay.version
|
||||||
|
}`}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 className="text-sm font-semibold text-neutral-600 dark:text-neutral-400">
|
||||||
|
Supported NIPs:
|
||||||
|
</h5>
|
||||||
|
<div className="mt-2 grid grid-cols-7 gap-2">
|
||||||
|
{resolvedRelay.supported_nips.map((item) => (
|
||||||
|
<a
|
||||||
|
key={item}
|
||||||
|
href={`https://nips.be/${item}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="inline-flex aspect-square h-auto w-full items-center justify-center rounded bg-neutral-100 text-sm font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-900"
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{resolvedRelay.limitation ? (
|
||||||
|
<div>
|
||||||
|
<h5 className="text-sm font-semibold text-neutral-600 dark:text-neutral-400">
|
||||||
|
Limitation
|
||||||
|
</h5>
|
||||||
|
<div className="flex flex-col gap-2 divide-y divide-white/5">
|
||||||
|
{Object.keys(resolvedRelay.limitation).map(
|
||||||
|
(key, index) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={key + index}
|
||||||
|
className="flex items-baseline justify-between pt-2"
|
||||||
|
>
|
||||||
|
<p className="text-sm font-medium text-neutral-900 dark:text-neutral-100">
|
||||||
|
{titleCase(key)}:
|
||||||
|
</p>
|
||||||
|
<p className="text-sm font-medium text-neutral-600 dark:text-neutral-400">
|
||||||
|
{resolvedRelay.limitation[key].toString()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{resolvedRelay.payments_url ? (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<a
|
||||||
|
href={resolvedRelay.payments_url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="inline-flex h-10 w-full items-center justify-center rounded-lg bg-blue-500 text-sm font-medium hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
Open payment website
|
||||||
|
</a>
|
||||||
|
<span className="text-center text-xs text-neutral-600 dark:text-neutral-400">
|
||||||
|
You need to make a payment to connect this relay
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Await>
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -355,11 +355,11 @@ export class Ark {
|
|||||||
|
|
||||||
public async getAllRelaysFromContacts() {
|
public async getAllRelaysFromContacts() {
|
||||||
const fetcher = NostrFetcher.withCustomPool(ndkAdapter(this.ndk));
|
const fetcher = NostrFetcher.withCustomPool(ndkAdapter(this.ndk));
|
||||||
|
const connectedRelays = this.ndk.pool
|
||||||
|
.connectedRelays()
|
||||||
|
.map((item) => item.url);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const LIMIT = 1;
|
|
||||||
const connectedRelays = this.ndk.pool
|
|
||||||
.connectedRelays()
|
|
||||||
.map((item) => item.url);
|
|
||||||
const relayMap = new Map<string, string[]>();
|
const relayMap = new Map<string, string[]>();
|
||||||
const relayEvents = fetcher.fetchLatestEventsPerAuthor(
|
const relayEvents = fetcher.fetchLatestEventsPerAuthor(
|
||||||
{
|
{
|
||||||
@ -367,7 +367,7 @@ export class Ark {
|
|||||||
relayUrls: connectedRelays,
|
relayUrls: connectedRelays,
|
||||||
},
|
},
|
||||||
{ kinds: [NDKKind.RelayList] },
|
{ kinds: [NDKKind.RelayList] },
|
||||||
LIMIT,
|
1,
|
||||||
);
|
);
|
||||||
|
|
||||||
for await (const { author, events } of relayEvents) {
|
for await (const { author, events } of relayEvents) {
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { useStorage } from "@lume/storage";
|
|
||||||
import { NDKKind, NDKRelayUrl, NDKTag } from "@nostr-dev-kit/ndk";
|
import { NDKKind, NDKRelayUrl, NDKTag } from "@nostr-dev-kit/ndk";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useArk } from "./useArk";
|
import { useArk } from "./useArk";
|
||||||
|
|
||||||
export function useRelay() {
|
export function useRelaylist() {
|
||||||
const ark = useArk();
|
const ark = useArk();
|
||||||
const storage = useStorage();
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const connectRelay = useMutation({
|
const connectRelay = useMutation({
|
||||||
@ -15,7 +13,7 @@ export function useRelay() {
|
|||||||
) => {
|
) => {
|
||||||
// Cancel any outgoing refetches
|
// Cancel any outgoing refetches
|
||||||
await queryClient.cancelQueries({
|
await queryClient.cancelQueries({
|
||||||
queryKey: ["relays", ark.account.pubkey],
|
queryKey: ["relay-personal"],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Snapshot the previous value
|
// Snapshot the previous value
|
||||||
@ -42,17 +40,17 @@ export function useRelay() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Optimistically update to the new value
|
// Optimistically update to the new value
|
||||||
queryClient.setQueryData(
|
queryClient.setQueryData(["relay-personal"], (prev: NDKTag[]) => [
|
||||||
["relays", ark.account.pubkey],
|
...prev,
|
||||||
(prev: NDKTag[]) => [...prev, ["r", relay, purpose ?? ""]],
|
["r", relay, purpose ?? ""],
|
||||||
);
|
]);
|
||||||
|
|
||||||
// Return a context object with the snapshotted value
|
// Return a context object with the snapshotted value
|
||||||
return { prevRelays };
|
return { prevRelays };
|
||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["relays", ark.account.pubkey],
|
queryKey: ["relay-personal"],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -61,7 +59,7 @@ export function useRelay() {
|
|||||||
mutationFn: async (relay: NDKRelayUrl) => {
|
mutationFn: async (relay: NDKRelayUrl) => {
|
||||||
// Cancel any outgoing refetches
|
// Cancel any outgoing refetches
|
||||||
await queryClient.cancelQueries({
|
await queryClient.cancelQueries({
|
||||||
queryKey: ["relays", ark.account.pubkey],
|
queryKey: ["relay-personal"],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Snapshot the previous value
|
// Snapshot the previous value
|
||||||
@ -81,14 +79,14 @@ export function useRelay() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Optimistically update to the new value
|
// Optimistically update to the new value
|
||||||
queryClient.setQueryData(["relays", ark.account.pubkey], prevRelays);
|
queryClient.setQueryData(["relay-personal"], prevRelays);
|
||||||
|
|
||||||
// Return a context object with the snapshotted value
|
// Return a context object with the snapshotted value
|
||||||
return { prevRelays };
|
return { prevRelays };
|
||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["relays", ark.account.pubkey],
|
queryKey: ["relay-personal"],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
@ -4,7 +4,7 @@ export * from "./provider";
|
|||||||
export * from "./hooks/useEvent";
|
export * from "./hooks/useEvent";
|
||||||
export * from "./hooks/useArk";
|
export * from "./hooks/useArk";
|
||||||
export * from "./hooks/useProfile";
|
export * from "./hooks/useProfile";
|
||||||
export * from "./hooks/useRelay";
|
export * from "./hooks/useRelayList";
|
||||||
export * from "./components/user";
|
export * from "./components/user";
|
||||||
export * from "./components/column";
|
export * from "./components/column";
|
||||||
export * from "./components/column/provider";
|
export * from "./components/column/provider";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { LoaderIcon } from "@lume/icons";
|
import { LoaderIcon } from "@lume/icons";
|
||||||
import { NDKCacheAdapterTauri } from "@lume/ndk-cache-tauri";
|
import { NDKCacheAdapterTauri } from "@lume/ndk-cache-tauri";
|
||||||
import { useStorage } from "@lume/storage";
|
import { useStorage } from "@lume/storage";
|
||||||
import { QUOTES, sendNativeNotification } from "@lume/utils";
|
import { FETCH_LIMIT, QUOTES, sendNativeNotification } from "@lume/utils";
|
||||||
import NDK, {
|
import NDK, {
|
||||||
NDKEvent,
|
NDKEvent,
|
||||||
NDKKind,
|
NDKKind,
|
||||||
@ -11,6 +11,7 @@ import NDK, {
|
|||||||
NDKRelayAuthPolicies,
|
NDKRelayAuthPolicies,
|
||||||
NDKUser,
|
NDKUser,
|
||||||
} from "@nostr-dev-kit/ndk";
|
} from "@nostr-dev-kit/ndk";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { fetch } from "@tauri-apps/plugin-http";
|
import { fetch } from "@tauri-apps/plugin-http";
|
||||||
import Linkify from "linkify-react";
|
import Linkify from "linkify-react";
|
||||||
import { normalizeRelayUrlSet } from "nostr-fetch";
|
import { normalizeRelayUrlSet } from "nostr-fetch";
|
||||||
@ -20,6 +21,7 @@ import { LumeContext } from "./context";
|
|||||||
|
|
||||||
export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
|
export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
|
||||||
const storage = useStorage();
|
const storage = useStorage();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const [ark, setArk] = useState<Ark>(undefined);
|
const [ark, setArk] = useState<Ark>(undefined);
|
||||||
const [ndk, setNDK] = useState<NDK>(undefined);
|
const [ndk, setNDK] = useState<NDK>(undefined);
|
||||||
@ -151,6 +153,56 @@ export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
|
|||||||
{ closeOnEose: false, groupable: false },
|
{ closeOnEose: false, groupable: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// prefetch activty
|
||||||
|
await queryClient.prefetchInfiniteQuery({
|
||||||
|
queryKey: ["activity"],
|
||||||
|
initialPageParam: 0,
|
||||||
|
queryFn: async ({
|
||||||
|
signal,
|
||||||
|
pageParam,
|
||||||
|
}: {
|
||||||
|
signal: AbortSignal;
|
||||||
|
pageParam: number;
|
||||||
|
}) => {
|
||||||
|
const events = await ark.getInfiniteEvents({
|
||||||
|
filter: {
|
||||||
|
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Zap],
|
||||||
|
"#p": [ark.account.pubkey],
|
||||||
|
},
|
||||||
|
limit: FETCH_LIMIT,
|
||||||
|
pageParam,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
return events;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// prefetch timeline
|
||||||
|
await queryClient.prefetchInfiniteQuery({
|
||||||
|
queryKey: ["timeline-9999"],
|
||||||
|
initialPageParam: 0,
|
||||||
|
queryFn: async ({
|
||||||
|
signal,
|
||||||
|
pageParam,
|
||||||
|
}: {
|
||||||
|
signal: AbortSignal;
|
||||||
|
pageParam: number;
|
||||||
|
}) => {
|
||||||
|
const events = await ark.getInfiniteEvents({
|
||||||
|
filter: {
|
||||||
|
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||||
|
authors: ark.account.contacts,
|
||||||
|
},
|
||||||
|
limit: FETCH_LIMIT,
|
||||||
|
pageParam,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
return events;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
sub.addListener("event", async (event: NDKEvent) => {
|
sub.addListener("event", async (event: NDKEvent) => {
|
||||||
const profile = await ark.getUserProfile(event.pubkey);
|
const profile = await ark.getUserProfile(event.pubkey);
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
|
@ -3,9 +3,13 @@ import {
|
|||||||
BellIcon,
|
BellIcon,
|
||||||
ComposeFilledIcon,
|
ComposeFilledIcon,
|
||||||
ComposeIcon,
|
ComposeIcon,
|
||||||
|
DepotFilledIcon,
|
||||||
|
DepotIcon,
|
||||||
HomeFilledIcon,
|
HomeFilledIcon,
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
NwcIcon,
|
NwcIcon,
|
||||||
|
RelayFilledIcon,
|
||||||
|
RelayIcon,
|
||||||
SettingsFilledIcon,
|
SettingsFilledIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
} from "@lume/icons";
|
} from "@lume/icons";
|
||||||
@ -27,7 +31,12 @@ export function Navigation() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setIsEditorOpen((state) => !state)}
|
onClick={() => setIsEditorOpen((state) => !state)}
|
||||||
className="flex items-center justify-center h-auto w-full text-black aspect-square rounded-xl bg-black/5 hover:bg-blue-500 hover:text-white dark:bg-white/5 dark:text-white dark:hover:bg-blue-500"
|
className={cn(
|
||||||
|
"flex items-center justify-center h-auto w-full text-black aspect-square rounded-xl hover:text-white dark:text-white",
|
||||||
|
isEditorOpen
|
||||||
|
? "bg-blue-500"
|
||||||
|
: "bg-black/5 hover:bg-blue-500 dark:bg-white/5 dark:hover:bg-blue-500",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{isEditorOpen ? (
|
{isEditorOpen ? (
|
||||||
<ComposeFilledIcon className="size-5" />
|
<ComposeFilledIcon className="size-5" />
|
||||||
@ -61,7 +70,7 @@ export function Navigation() {
|
|||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/activity"
|
to="/activity/"
|
||||||
preventScrollReset={true}
|
preventScrollReset={true}
|
||||||
className="inline-flex flex-col items-center justify-center"
|
className="inline-flex flex-col items-center justify-center"
|
||||||
>
|
>
|
||||||
@ -82,6 +91,28 @@ export function Navigation() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
<NavLink
|
||||||
|
to="/relays/"
|
||||||
|
preventScrollReset={true}
|
||||||
|
className="inline-flex flex-col items-center justify-center"
|
||||||
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"inline-flex aspect-square h-auto w-full items-center justify-center rounded-xl",
|
||||||
|
isActive
|
||||||
|
? "bg-black/10 text-black dark:bg-white/10 dark:text-white"
|
||||||
|
: "text-black/50 dark:text-neutral-400",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isActive ? (
|
||||||
|
<DepotFilledIcon className="size-6" />
|
||||||
|
) : (
|
||||||
|
<DepotIcon className="size-6" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../apps/desktop/node_modules/@tauri-apps/cli/schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeBuildCommand": "pnpm run build",
|
"beforeBuildCommand": "pnpm run build",
|
||||||
"beforeDevCommand": "pnpm run dev",
|
"beforeDevCommand": "pnpm run dev",
|
||||||
@ -50,7 +50,7 @@
|
|||||||
"deb": {
|
"deb": {
|
||||||
"depends": []
|
"depends": []
|
||||||
},
|
},
|
||||||
"externalBin": ["bin/depot"],
|
"externalBin": [],
|
||||||
"resources": ["resources/*"],
|
"resources": ["resources/*"],
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
|
Loading…
Reference in New Issue
Block a user