diff --git a/packages/app/src/Pages/settings/Menu.tsx b/packages/app/src/Pages/settings/Menu.tsx index e2649f6e6..456d0e22c 100644 --- a/packages/app/src/Pages/settings/Menu.tsx +++ b/packages/app/src/Pages/settings/Menu.tsx @@ -8,14 +8,14 @@ import classNames from "classnames"; import { getCurrentSubscription } from "@/Subscription"; export type SettingsMenuItems = Array<{ - title: ReactNode, + title: ReactNode; items: Array<{ icon: string; iconBg: string; - message: ReactNode, + message: ReactNode; path?: string; action?: () => void; - }> + }>; }>; const SettingsIndex = () => { @@ -64,20 +64,20 @@ const SettingsIndex = () => { }, ...(sub ? [ - { - icon: "code-circle", - iconBg: "bg-indigo-500", - message: , - path: "accounts", - }, - ] + { + icon: "code-circle", + iconBg: "bg-indigo-500", + message: , + path: "accounts", + }, + ] : []), { icon: "tool", iconBg: "bg-slate-800", message: , - path: "tools" - } + path: "tools", + }, ], }, { @@ -126,23 +126,23 @@ const SettingsIndex = () => { }, ...(CONFIG.features.subscriptions ? [ - { - icon: "diamond", - iconBg: "bg-violet-500", - message: , - path: "/subscribe/manage", - }, - ] + { + icon: "diamond", + iconBg: "bg-violet-500", + message: , + path: "/subscribe/manage", + }, + ] : []), ...(CONFIG.features.zapPool ? [ - { - icon: "piggy-bank", - iconBg: "bg-rose-500", - message: , - path: "/zap-pool", - }, - ] + { + icon: "piggy-bank", + iconBg: "bg-rose-500", + message: , + path: "/zap-pool", + }, + ] : []), ], }, @@ -159,7 +159,7 @@ const SettingsIndex = () => { }, ] as SettingsMenuItems; - return + return ; }; export function SettingsMenuComponent({ menu }: { menu: SettingsMenuItems }) { diff --git a/packages/app/src/Pages/settings/tools/follows-relay-health.tsx b/packages/app/src/Pages/settings/tools/follows-relay-health.tsx index c4dbcbf7e..19e4099ed 100644 --- a/packages/app/src/Pages/settings/tools/follows-relay-health.tsx +++ b/packages/app/src/Pages/settings/tools/follows-relay-health.tsx @@ -8,51 +8,87 @@ import { SnortContext } from "@snort/system-react"; import { ReactNode, useContext, useMemo } from "react"; import { FormattedMessage, FormattedNumber } from "react-intl"; -export function FollowsRelayHealth({ withTitle, popularRelays, missingRelaysActions }: { withTitle?: boolean, popularRelays?: boolean, missingRelaysActions?: (k: string) => ReactNode }) { - const system = useContext(SnortContext); - const follows = useLogin(s => s.follows); - const uniqueFollows = dedupe(follows.item); +export function FollowsRelayHealth({ + withTitle, + popularRelays, + missingRelaysActions, +}: { + withTitle?: boolean; + popularRelays?: boolean; + missingRelaysActions?: (k: string) => ReactNode; +}) { + const system = useContext(SnortContext); + const follows = useLogin(s => s.follows); + const uniqueFollows = dedupe(follows.item); - const hasRelays = useMemo(() => { - return uniqueFollows.filter(a => (system.RelayCache.getFromCache(a)?.relays.length ?? 0) > 0); - }, [uniqueFollows]); + const hasRelays = useMemo(() => { + return uniqueFollows.filter(a => (system.RelayCache.getFromCache(a)?.relays.length ?? 0) > 0); + }, [uniqueFollows]); - const missingRelays = useMemo(() => { - return uniqueFollows.filter(a => !hasRelays.includes(a)); - }, [hasRelays]); + const missingRelays = useMemo(() => { + return uniqueFollows.filter(a => !hasRelays.includes(a)); + }, [hasRelays]); - const topWriteRelays = useMemo(() => { - return pickTopRelays(system.RelayCache, uniqueFollows, 1e31, "write"); - }, [uniqueFollows]); + const topWriteRelays = useMemo(() => { + return pickTopRelays(system.RelayCache, uniqueFollows, 1e31, "write"); + }, [uniqueFollows]); - return
- {(withTitle ?? true) &&
- -
} -
- - }} /> + return ( +
+ {(withTitle ?? true) && ( +
+
- {missingRelays.length > 0 &&
}> -
- {missingRelays.map(a => )} + )} +
+ , + }} + /> +
+ {missingRelays.length > 0 && ( + +
- } - {(popularRelays ?? true) &&
-
Popular Relays
- {dedupe(topWriteRelays.flatMap(a => a.relays)) - .map(a => ({ relay: a, count: topWriteRelays.filter(b => b.relays.includes(a)).length })) - .sort((a, b) => a.count > b.count ? -1 : 1) - .slice(0, 10) - .map(a =>
-
{getRelayName(a.relay)}
-
{a.count} ()
-
)} -
} + }> +
+ {missingRelays.map(a => ( + + ))} +
+ + )} + {(popularRelays ?? true) && ( +
+
Popular Relays
+ {dedupe(topWriteRelays.flatMap(a => a.relays)) + .map(a => ({ relay: a, count: topWriteRelays.filter(b => b.relays.includes(a)).length })) + .sort((a, b) => (a.count > b.count ? -1 : 1)) + .slice(0, 10) + .map(a => ( +
+
{getRelayName(a.relay)}
+
+ {a.count} () +
+
+ ))} +
+ )}
-} \ No newline at end of file + ); +} diff --git a/packages/app/src/Pages/settings/tools/index.tsx b/packages/app/src/Pages/settings/tools/index.tsx index 565edca20..4e674ef87 100644 --- a/packages/app/src/Pages/settings/tools/index.tsx +++ b/packages/app/src/Pages/settings/tools/index.tsx @@ -1,50 +1,51 @@ -import { FormattedMessage } from "react-intl" -import { Outlet, RouteObject } from "react-router-dom" -import { SettingsMenuComponent, SettingsMenuItems } from "../Menu" +import { FormattedMessage } from "react-intl"; +import { Outlet, RouteObject } from "react-router-dom"; +import { SettingsMenuComponent, SettingsMenuItems } from "../Menu"; import { PruneFollowList } from "./prune-follows"; import { FollowsRelayHealth } from "./follows-relay-health"; - const ToolMenuItems = [ - { - title: , - items: [ - { - icon: "trash", - iconBg: "bg-red-500", - message: , - path: "prune-follows" - }, - { - icon: "medical-cross", - iconBg: "bg-green-800", - message: , - path: "follows-relay-health" - } - ] - } + { + title: , + items: [ + { + icon: "trash", + iconBg: "bg-red-500", + message: , + path: "prune-follows", + }, + { + icon: "medical-cross", + iconBg: "bg-green-800", + message: , + path: "follows-relay-health", + }, + ], + }, ] as SettingsMenuItems; export const ToolsPages = [ - { - path: "", - element: <> -

- -

- - - }, - { - path: "prune-follows", - element: - }, - { - path: "follows-relay-health", - element: - } -] as Array + { + path: "", + element: ( + <> +

+ +

+ + + ), + }, + { + path: "prune-follows", + element: , + }, + { + path: "follows-relay-health", + element: , + }, +] as Array; export function ToolsPage() { - return + return ; } diff --git a/packages/app/src/Pages/settings/tools/prune-follows.tsx b/packages/app/src/Pages/settings/tools/prune-follows.tsx index d7ed4ecec..ad0c94e79 100644 --- a/packages/app/src/Pages/settings/tools/prune-follows.tsx +++ b/packages/app/src/Pages/settings/tools/prune-follows.tsx @@ -1,6 +1,6 @@ import { Day } from "@/Const"; import AsyncButton from "@/Element/Button/AsyncButton"; -import useLogin from "@/Hooks/useLogin" +import useLogin from "@/Hooks/useLogin"; import { dedupe, unixNow } from "@snort/shared"; import { RequestBuilder } from "@snort/system"; import { useMemo, useState } from "react"; @@ -11,126 +11,164 @@ import useEventPublisher from "@/Hooks/useEventPublisher"; import { setFollows } from "@/Login"; const enum PruneStage { - FetchLastPostTimestamp, - Done + FetchLastPostTimestamp, + Done, } export function PruneFollowList() { - const { id, follows } = useLogin(s => ({ id: s.id, follows: s.follows })); - const { publisher, system } = useEventPublisher(); - const uniqueFollows = dedupe(follows.item); - const [status, setStatus] = useState(); - const [progress, setProgress] = useState(0); - const [lastPost, setLastPosts] = useState>(); - const [unfollow, setUnfollow] = useState>([]); + const { id, follows } = useLogin(s => ({ id: s.id, follows: s.follows })); + const { publisher, system } = useEventPublisher(); + const uniqueFollows = dedupe(follows.item); + const [status, setStatus] = useState(); + const [progress, setProgress] = useState(0); + const [lastPost, setLastPosts] = useState>(); + const [unfollow, setUnfollow] = useState>([]); - async function fetchLastPosts() { - setStatus(PruneStage.FetchLastPostTimestamp); - setProgress(0); - setLastPosts(undefined); + async function fetchLastPosts() { + setStatus(PruneStage.FetchLastPostTimestamp); + setProgress(0); + setLastPosts(undefined); - const BatchSize = 10; - const chunks = uniqueFollows.reduce((acc, v, i) => { - const batch = Math.floor(i / BatchSize).toString(); - acc[batch] ??= []; - acc[batch].push(v); - return acc; - }, {} as Record>); + const BatchSize = 10; + const chunks = uniqueFollows.reduce( + (acc, v, i) => { + const batch = Math.floor(i / BatchSize).toString(); + acc[batch] ??= []; + acc[batch].push(v); + return acc; + }, + {} as Record>, + ); - const result = {} as Record; - const batches = Math.ceil(uniqueFollows.length / BatchSize); - for (const [batch, pubkeys] of Object.entries(chunks)) { - console.debug(batch, pubkeys); - const req = new RequestBuilder(`prune-${batch}`); - req.withOptions({ - outboxPickN: 10, - timeout: 10_000 - }); - pubkeys.forEach(p => req.withFilter().limit(1).kinds([0, 1, 3, 5, 6, 7, 10002]).authors([p])); - const results = await system.Fetch(req); - console.debug(results); - for (const rx of results) { - if ((result[rx.pubkey] ?? 0) < rx.created_at) { - result[rx.pubkey] = rx.created_at; - } - } - setProgress(Number(batch) / batches); + const result = {} as Record; + const batches = Math.ceil(uniqueFollows.length / BatchSize); + for (const [batch, pubkeys] of Object.entries(chunks)) { + console.debug(batch, pubkeys); + const req = new RequestBuilder(`prune-${batch}`); + req.withOptions({ + outboxPickN: 10, + timeout: 10_000, + }); + pubkeys.forEach(p => req.withFilter().limit(1).kinds([0, 1, 3, 5, 6, 7, 10002]).authors([p])); + const results = await system.Fetch(req); + console.debug(results); + for (const rx of results) { + if ((result[rx.pubkey] ?? 0) < rx.created_at) { + result[rx.pubkey] = rx.created_at; } - - for (const pk of uniqueFollows) { - result[pk] ??= 0; - } - setLastPosts(result); - setStatus(PruneStage.Done); + } + setProgress(Number(batch) / batches); } - const newFollowList = useMemo(() => { - return uniqueFollows.filter(a => !unfollow.includes(a) && a.length === 64); - }, [uniqueFollows, unfollow]); - - async function publishFollowList() { - const newFollows = newFollowList.map(a => ["p", a]) as Array<[string, string]>; - if (publisher) { - const ev = await publisher.contactList(newFollows); - await system.BroadcastEvent(ev); - setFollows(id, newFollowList, ev.created_at * 1000); - } + for (const pk of uniqueFollows) { + result[pk] ??= 0; } + setLastPosts(result); + setStatus(PruneStage.Done); + } + const newFollowList = useMemo(() => { + return uniqueFollows.filter(a => !unfollow.includes(a) && a.length === 64); + }, [uniqueFollows, unfollow]); - function getStatus() { - switch (status) { - case PruneStage.FetchLastPostTimestamp: return - }} /> - } + async function publishFollowList() { + const newFollows = newFollowList.map(a => ["p", a]) as Array<[string, string]>; + if (publisher) { + const ev = await publisher.contactList(newFollows); + await system.BroadcastEvent(ev); + setFollows(id, newFollowList, ev.created_at * 1000); } + } - function personToggle(k: string,) { - return
- setUnfollow(v => e.target.checked ? dedupe([...v, k]) : v.filter(a => a !== k))} checked={unfollow.includes(k)} /> - -
+ function getStatus() { + switch (status) { + case PruneStage.FetchLastPostTimestamp: + return ( + , + }} + /> + ); } + } - return
-
- -
-

- -

-
- -
- personToggle(k)} /> - - - - {getStatus()} -
- {lastPost && Object.entries(lastPost).filter(([, v]) => v <= unixNow() - (90 * Day)).sort(([, a], [, b]) => a > b ? -1 : 1).map(([k, v]) => { - return
- -
- - {personToggle(k)} -
+ function personToggle(k: string) { + return ( +
+ setUnfollow(v => (e.target.checked ? dedupe([...v, k]) : v.filter(a => a !== k)))} + checked={unfollow.includes(k)} + /> + +
+ ); + } + + return ( +
+
+ +
+

+ +

+
+ +
+ personToggle(k)} /> + + + + {getStatus()} +
+ {lastPost && + Object.entries(lastPost) + .filter(([, v]) => v <= unixNow() - 90 * Day) + .sort(([, a], [, b]) => (a > b ? -1 : 1)) + .map(([k, v]) => { + return ( +
+ +
+ + {personToggle(k)} +
+ ); })} -
-
-

- -

- - - -
+
+
+

+ +

+ + + +
-} \ No newline at end of file + ); +}