diff --git a/packages/app/src/Pages/settings/Menu.tsx b/packages/app/src/Pages/settings/Menu.tsx
index e2649f6e..456d0e22 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 c4dbcbf7..19e4099e 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 565edca2..4e674ef8 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 d7ed4ece..ad0c94e7 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
+ );
+}