mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-29 16:30:55 +00:00
feat: fix relay manager
This commit is contained in:
parent
c3482cddd8
commit
240fe8bc7c
@ -1,9 +1,9 @@
|
||||
import { useRelaylist } from "@lume/ark";
|
||||
import { PlusIcon, ShareIcon } from "@lume/icons";
|
||||
import { User, useRelaylist } from "@lume/ark";
|
||||
import { PlusIcon, SearchIcon } from "@lume/icons";
|
||||
import { normalizeRelayUrl } from "nostr-fetch";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export function RelayItem({ url }: { url: string }) {
|
||||
export function RelayItem({ url, users }: { url: string; users?: string[] }) {
|
||||
const domain = new URL(url).hostname;
|
||||
const { connectRelay } = useRelaylist();
|
||||
|
||||
@ -18,19 +18,35 @@ export function RelayItem({ url }: { url: string }) {
|
||||
</span>
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-2">
|
||||
{users ? (
|
||||
<div className="isolate flex -space-x-2 mr-4">
|
||||
{users.slice(0, 4).map((item) => (
|
||||
<User.Provider pubkey={item}>
|
||||
<User.Root>
|
||||
<User.Avatar className="size-8 inline-block rounded-full ring-1 ring-neutral-100 dark:ring-neutral-900" />
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
))}
|
||||
{users.length > 4 ? (
|
||||
<div className="inline-flex size-8 items-center justify-center rounded-full bg-neutral-100 text-neutral-900 ring-1 ring-neutral-200 dark:bg-neutral-900 dark:text-neutral-100 dark:ring-neutral-800">
|
||||
<span className="text-xs font-medium">+{users.length - 4}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<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"
|
||||
className="inline-flex h-8 items-center justify-center gap-2 rounded-lg bg-neutral-100 px-2 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||
>
|
||||
<ShareIcon className="h-3 w-3" />
|
||||
<SearchIcon className="size-4" />
|
||||
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"
|
||||
className="inline-flex size-8 items-center justify-center rounded-lg bg-blue-100 text-blue-500 hover:bg-blue-200 dark:bg-blue-900 hover:dark:bg-blue-800"
|
||||
>
|
||||
<PlusIcon className="size-4" />
|
||||
<PlusIcon className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useArk, useRelay } from "@lume/ark";
|
||||
import { useArk, useRelaylist } from "@lume/ark";
|
||||
import { LoaderIcon, PlusIcon, ShareIcon } from "@lume/icons";
|
||||
import { User } from "@lume/ui";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
@ -7,7 +7,7 @@ import { VList } from "virtua";
|
||||
|
||||
export function RelayList() {
|
||||
const ark = useArk();
|
||||
const { connectRelay } = useRelay();
|
||||
const { connectRelay } = useRelaylist();
|
||||
const { status, data } = useQuery({
|
||||
queryKey: ["relays"],
|
||||
queryFn: async () => {
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { useArk } from "@lume/ark";
|
||||
import { CancelIcon, RefreshIcon } from "@lume/icons";
|
||||
import { useArk, useRelaylist } from "@lume/ark";
|
||||
import { CancelIcon, LoaderIcon, RefreshIcon } from "@lume/icons";
|
||||
import { cn } from "@lume/utils";
|
||||
import { NDKKind } from "@nostr-dev-kit/ndk";
|
||||
import { NDKKind, NDKSubscriptionCacheUsage } from "@nostr-dev-kit/ndk";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { RelayForm } from "./relayForm";
|
||||
|
||||
export function RelaySidebar({ className }: { className?: string }) {
|
||||
const ark = useArk();
|
||||
|
||||
const { status, data, refetch } = useQuery({
|
||||
const { removeRelay } = useRelaylist();
|
||||
const { status, data, isRefetching, refetch } = useQuery({
|
||||
queryKey: ["relay-personal"],
|
||||
queryFn: async () => {
|
||||
const event = await ark.getEventByFilter({
|
||||
@ -16,12 +17,15 @@ export function RelaySidebar({ className }: { className?: string }) {
|
||||
kinds: [NDKKind.RelayList],
|
||||
authors: [ark.account.pubkey],
|
||||
},
|
||||
cache: NDKSubscriptionCacheUsage.ONLY_RELAY,
|
||||
});
|
||||
|
||||
if (!event) return [];
|
||||
return event.tags.filter((tag) => tag[0] === "r");
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
staleTime: Infinity,
|
||||
});
|
||||
|
||||
const currentRelays = new Set(
|
||||
@ -42,17 +46,19 @@ export function RelaySidebar({ className }: { className?: string }) {
|
||||
onClick={() => refetch()}
|
||||
className="inline-flex items-center justify-center w-6 h-6 rounded-md shrink-0 hover:bg-neutral-100 dark:hover:bg-neutral-900"
|
||||
>
|
||||
<RefreshIcon className="w-4 h-4" />
|
||||
<RefreshIcon
|
||||
className={cn("size-4", isRefetching ? "animate-spin" : "")}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 px-3 mt-3">
|
||||
{status === "pending" ? (
|
||||
<p>Loading...</p>
|
||||
<div className="flex items-center justify-center w-full h-20 rounded-lg bg-black/10 dark:bg-white/10">
|
||||
<LoaderIcon className="size-5 animate-spin" />
|
||||
</div>
|
||||
) : !data.length ? (
|
||||
<div className="flex items-center justify-center w-full h-20 rounded-xl bg-neutral-50 dark:bg-neutral-950">
|
||||
<p className="text-sm font-medium">
|
||||
You not have personal relay list yet
|
||||
</p>
|
||||
<div className="flex items-center justify-center w-full h-20 rounded-lg bg-black/10 dark:bg-white/10">
|
||||
<p className="text-sm font-medium">Empty.</p>
|
||||
</div>
|
||||
) : (
|
||||
data.map((item) => (
|
||||
@ -73,7 +79,10 @@ export function RelaySidebar({ className }: { className?: string }) {
|
||||
</span>
|
||||
)}
|
||||
<p className="max-w-[20rem] truncate text-sm font-medium text-neutral-900 dark:text-neutral-100">
|
||||
{item[1].replace("wss://", "").replace("ws://", "")}
|
||||
{item[1]
|
||||
.replace("wss://", "")
|
||||
.replace("ws://", "")
|
||||
.replace("/", "")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-2">
|
||||
@ -84,9 +93,10 @@ export function RelaySidebar({ className }: { className?: string }) {
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
className="items-center justify-center hidden w-6 h-6 rounded group-hover:inline-flex hover:bg-neutral-300 dark:hover:bg-neutral-700"
|
||||
onClick={() => removeRelay.mutate(item[1])}
|
||||
className="items-center justify-center hidden size-6 rounded group-hover:inline-flex hover:bg-neutral-300 dark:hover:bg-neutral-700"
|
||||
>
|
||||
<CancelIcon className="w-4 h-4 text-neutral-900 dark:text-neutral-100" />
|
||||
<CancelIcon className="size-4 text-neutral-900 dark:text-neutral-100" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,18 +1,25 @@
|
||||
import { useArk } from "@lume/ark";
|
||||
import { LoaderIcon, PlusIcon, ShareIcon } from "@lume/icons";
|
||||
import { LoaderIcon } 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({
|
||||
const {
|
||||
isLoading,
|
||||
isError,
|
||||
data: relays,
|
||||
} = useQuery({
|
||||
queryKey: ["relay-follows"],
|
||||
queryFn: async ({ signal }: { signal: AbortSignal }) => {
|
||||
return await ark.getAllRelaysFromContacts();
|
||||
const data = await ark.getAllRelaysFromContacts({ signal });
|
||||
if (!data) throw new Error("Failed to get relay list from contacts");
|
||||
return data;
|
||||
},
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
@ -23,10 +30,18 @@ export function RelayFollowsScreen() {
|
||||
);
|
||||
}
|
||||
|
||||
if (isError || !relays) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<p>Error</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<VList itemSize={49}>
|
||||
{relays.map((item: string) => (
|
||||
<RelayItem key={item} url={item} />
|
||||
{[...relays].map(([key, value]) => (
|
||||
<RelayItem key={key} url={key} users={value} />
|
||||
))}
|
||||
</VList>
|
||||
);
|
||||
|
@ -14,6 +14,7 @@ export function RelayGlobalScreen() {
|
||||
},
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
|
@ -254,9 +254,12 @@ export class Ark {
|
||||
}
|
||||
}
|
||||
|
||||
public async getEventByFilter({ filter }: { filter: NDKFilter }) {
|
||||
public async getEventByFilter({
|
||||
filter,
|
||||
cache,
|
||||
}: { filter: NDKFilter; cache?: NDKSubscriptionCacheUsage }) {
|
||||
const event = await this.ndk.fetchEvent(filter, {
|
||||
cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST,
|
||||
cacheUsage: cache || NDKSubscriptionCacheUsage.CACHE_FIRST,
|
||||
});
|
||||
|
||||
if (!event) return null;
|
||||
@ -352,7 +355,7 @@ export class Ark {
|
||||
}
|
||||
}
|
||||
|
||||
public async getAllRelaysFromContacts() {
|
||||
public async getAllRelaysFromContacts({ signal }: { signal: AbortSignal }) {
|
||||
const fetcher = NostrFetcher.withCustomPool(ndkAdapter(this.ndk));
|
||||
const connectedRelays = this.ndk.pool
|
||||
.connectedRelays()
|
||||
@ -367,15 +370,21 @@ export class Ark {
|
||||
},
|
||||
{ kinds: [NDKKind.RelayList] },
|
||||
1,
|
||||
{ abortSignal: signal },
|
||||
);
|
||||
|
||||
for await (const { author, events } of relayEvents) {
|
||||
if (events[0]) {
|
||||
for (const tag of events[0].tags) {
|
||||
const users = relayMap.get(tag[1]);
|
||||
console.log(relayEvents);
|
||||
|
||||
if (!users) relayMap.set(tag[1], [author]);
|
||||
users.push(author);
|
||||
for await (const { author, events } of relayEvents) {
|
||||
if (events.length) {
|
||||
const relayTags = events[0].tags.filter((item) => item[0] === "r");
|
||||
for (const tag of relayTags) {
|
||||
const item = relayMap.get(tag[1]);
|
||||
if (item?.length) {
|
||||
item.push(author);
|
||||
} else {
|
||||
relayMap.set(tag[1], [author]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { NDKKind, NDKRelayUrl, NDKTag } from "@nostr-dev-kit/ndk";
|
||||
import { NDKKind, NDKTag } from "@nostr-dev-kit/ndk";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { normalizeRelayUrl } from "nostr-fetch";
|
||||
import { useArk } from "./useArk";
|
||||
|
||||
export function useRelaylist() {
|
||||
@ -8,7 +9,7 @@ export function useRelaylist() {
|
||||
|
||||
const connectRelay = useMutation({
|
||||
mutationFn: async (
|
||||
relay: NDKRelayUrl,
|
||||
relay: WebSocket["url"],
|
||||
purpose?: "read" | "write" | undefined,
|
||||
) => {
|
||||
// Cancel any outgoing refetches
|
||||
@ -16,11 +17,10 @@ export function useRelaylist() {
|
||||
queryKey: ["relay-personal"],
|
||||
});
|
||||
|
||||
const relayUrl = normalizeRelayUrl(relay);
|
||||
|
||||
// Snapshot the previous value
|
||||
const prevRelays: NDKTag[] = queryClient.getQueryData([
|
||||
"relays",
|
||||
ark.account.pubkey,
|
||||
]);
|
||||
const prevRelays: NDKTag[] = queryClient.getQueryData(["relay-personal"]);
|
||||
|
||||
// create new relay list if not exist
|
||||
if (!prevRelays) {
|
||||
@ -36,13 +36,13 @@ export function useRelaylist() {
|
||||
|
||||
await ark.createEvent({
|
||||
kind: NDKKind.RelayList,
|
||||
tags: [...prevRelays, ["r", relay, purpose ?? ""]],
|
||||
tags: [...prevRelays, ["r", relayUrl, purpose ?? ""]],
|
||||
});
|
||||
|
||||
// Optimistically update to the new value
|
||||
queryClient.setQueryData(["relay-personal"], (prev: NDKTag[]) => [
|
||||
...prev,
|
||||
["r", relay, purpose ?? ""],
|
||||
["r", relayUrl, purpose ?? ""],
|
||||
]);
|
||||
|
||||
// Return a context object with the snapshotted value
|
||||
@ -56,17 +56,14 @@ export function useRelaylist() {
|
||||
});
|
||||
|
||||
const removeRelay = useMutation({
|
||||
mutationFn: async (relay: NDKRelayUrl) => {
|
||||
mutationFn: async (relay: WebSocket["url"]) => {
|
||||
// Cancel any outgoing refetches
|
||||
await queryClient.cancelQueries({
|
||||
queryKey: ["relay-personal"],
|
||||
});
|
||||
|
||||
// Snapshot the previous value
|
||||
const prevRelays: NDKTag[] = queryClient.getQueryData([
|
||||
"relays",
|
||||
ark.account.pubkey,
|
||||
]);
|
||||
const prevRelays: NDKTag[] = queryClient.getQueryData(["relay-personal"]);
|
||||
|
||||
if (!prevRelays) return;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user