mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-26 17:52:18 +01:00
Support using nostr-relay-tray as cache relay
This commit is contained in:
parent
96fc7cde44
commit
f965281520
5
.changeset/fluffy-coats-obey.md
Normal file
5
.changeset/fluffy-coats-obey.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Add support for using nostr-relay-tray as cache relay
|
11
src/app.tsx
11
src/app.tsx
@ -86,6 +86,9 @@ import VideoDetailsView from "./views/videos/video";
|
||||
import BookmarksView from "./views/bookmarks";
|
||||
import MailboxesView from "./views/mailboxes";
|
||||
import RequireReadRelays from "./providers/route/require-read-relays";
|
||||
import CacheRelayView from "./views/relays/cache";
|
||||
import RelaySetView from "./views/relays/relay-set";
|
||||
import AppRelays from "./views/relays/app";
|
||||
const TracksView = lazy(() => import("./views/tracks"));
|
||||
const UserTracksTab = lazy(() => import("./views/user/tracks"));
|
||||
const UserVideosTab = lazy(() => import("./views/user/videos"));
|
||||
@ -255,11 +258,13 @@ const router = createHashRouter([
|
||||
{ path: "settings", element: <SettingsView /> },
|
||||
{
|
||||
path: "relays",
|
||||
element: <RelaysView />,
|
||||
children: [
|
||||
{ path: "", element: <RelaysView /> },
|
||||
{ path: "popular", element: <PopularRelaysView /> },
|
||||
{ path: "reviews", element: <RelayReviewsView /> },
|
||||
{ path: "", element: <AppRelays /> },
|
||||
{ path: "app", element: <AppRelays /> },
|
||||
{ path: "cache", element: <CacheRelayView /> },
|
||||
{ path: "sets", element: <BrowseRelaySetsView /> },
|
||||
{ path: ":id", element: <RelaySetView /> },
|
||||
],
|
||||
},
|
||||
{ path: "r/:relay", element: <RelayView /> },
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Button, ButtonProps } from "@chakra-ui/react";
|
||||
import { IconButton, IconButtonProps } from "@chakra-ui/react";
|
||||
import { ChevronLeftIcon } from "./icons";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export default function BackButton({ ...props }: Omit<ButtonProps, "onClick" | "children">) {
|
||||
export default function BackButton({ ...props }: Omit<IconButtonProps, "onClick" | "children" | "aria-label">) {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<Button leftIcon={<ChevronLeftIcon />} {...props} onClick={() => navigate(-1)}>
|
||||
<IconButton icon={<ChevronLeftIcon />} aria-label="Back" {...props} onClick={() => navigate(-1)}>
|
||||
Back
|
||||
</Button>
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { useCallback } from "react";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
import useTimelineLoader from "./use-timeline-loader";
|
||||
import { NostrEvent, isRTag } from "../types/nostr-event";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
export default function useUserRelaySets(pubkey?: string, additionalRelays?: Iterable<string>) {
|
||||
const readRelays = useReadRelays(additionalRelays);
|
||||
|
@ -2,6 +2,22 @@ import { CacheRelay, openDB, pruneLastUsed } from "nostr-idb";
|
||||
import { Relay } from "nostr-tools";
|
||||
import { logger } from "../helpers/debug";
|
||||
import _throttle from "lodash.throttle";
|
||||
import { safeRelayUrl } from "../helpers/relay";
|
||||
|
||||
export const NOSTR_RELAY_TRAY_URL = "ws://localhost:4869/";
|
||||
|
||||
export async function checkNostrRelayTray() {
|
||||
return new Promise((res) => {
|
||||
const test = new Relay(NOSTR_RELAY_TRAY_URL);
|
||||
test
|
||||
.connect()
|
||||
.then(() => {
|
||||
test.close();
|
||||
res(true);
|
||||
})
|
||||
.catch(() => res(false));
|
||||
});
|
||||
}
|
||||
|
||||
const log = logger.extend(`LocalRelay`);
|
||||
|
||||
@ -15,39 +31,58 @@ if (paramRelay) {
|
||||
}
|
||||
|
||||
const storedCacheRelayURL = localStorage.getItem("localRelay");
|
||||
const url = (storedCacheRelayURL && new URL(storedCacheRelayURL)) || new URL("/local-relay", location.href);
|
||||
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
||||
|
||||
/** @deprecated */
|
||||
const localRelayURL = (storedCacheRelayURL && new URL(storedCacheRelayURL)) || new URL("/local-relay", location.href);
|
||||
localRelayURL.protocol = localRelayURL.protocol === "https:" ? "wss:" : "ws:";
|
||||
|
||||
/** @deprecated */
|
||||
export const LOCAL_CACHE_RELAY_ENABLED = !!window.CACHE_RELAY_ENABLED || !!localStorage.getItem("localRelay");
|
||||
/** @deprecated */
|
||||
export const LOCAL_CACHE_RELAY = url.toString();
|
||||
export const LOCAL_CACHE_RELAY = localRelayURL.toString();
|
||||
|
||||
export const localDatabase = await openDB();
|
||||
|
||||
function createRelay() {
|
||||
if (LOCAL_CACHE_RELAY_ENABLED) {
|
||||
log(`Using ${LOCAL_CACHE_RELAY}`);
|
||||
return new Relay(LOCAL_CACHE_RELAY);
|
||||
} else {
|
||||
log(`Using IndexedDB`);
|
||||
const stored = localStorage.getItem("localRelay");
|
||||
if (!stored || stored.startsWith("nostr-idb://")) {
|
||||
return new CacheRelay(localDatabase, { maxEvents: 10000 });
|
||||
} else if (safeRelayUrl(stored)) {
|
||||
return new Relay(safeRelayUrl(stored)!);
|
||||
} else if (window.CACHE_RELAY_ENABLED) {
|
||||
return new Relay(new URL("/local-relay", location.href).toString());
|
||||
}
|
||||
|
||||
return new CacheRelay(localDatabase, { maxEvents: 10000 });
|
||||
|
||||
// if (LOCAL_CACHE_RELAY_ENABLED) {
|
||||
// log(`Using ${LOCAL_CACHE_RELAY}`);
|
||||
// return new Relay(LOCAL_CACHE_RELAY);
|
||||
// } else {
|
||||
// log(`Using IndexedDB`);
|
||||
// return new CacheRelay(localDatabase, { maxEvents: 10000 });
|
||||
// }
|
||||
}
|
||||
|
||||
async function connectRelay() {
|
||||
const relay = createRelay();
|
||||
try {
|
||||
await relay.connect();
|
||||
log("Connected");
|
||||
return relay;
|
||||
} catch (e) {
|
||||
log("Failed to connect to local relay, falling back to internal");
|
||||
return new CacheRelay(localDatabase, { maxEvents: 10000 });
|
||||
}
|
||||
}
|
||||
|
||||
export const localRelay = createRelay();
|
||||
export const localRelay = await connectRelay();
|
||||
|
||||
function pruneLocalDatabase() {
|
||||
if (localRelay instanceof CacheRelay) {
|
||||
pruneLastUsed(localRelay.db, 20_000);
|
||||
}
|
||||
}
|
||||
// connect without waiting
|
||||
localRelay.connect().then(() => {
|
||||
log("Connected");
|
||||
|
||||
pruneLocalDatabase();
|
||||
});
|
||||
|
||||
// keep the relay connection alive
|
||||
setInterval(() => {
|
||||
|
27
src/views/relays/app/index.tsx
Normal file
27
src/views/relays/app/index.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { Button, Flex, Heading } from "@chakra-ui/react";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { offlineMode } from "../../../services/offline-mode";
|
||||
import WifiOff from "../../../components/icons/wifi-off";
|
||||
import Wifi from "../../../components/icons/wifi";
|
||||
import BackButton from "../../../components/back-button";
|
||||
|
||||
export default function AppRelays() {
|
||||
const offline = useSubject(offlineMode);
|
||||
|
||||
return (
|
||||
<Flex gap="2" direction="column" overflow="auto hidden" flex={1}>
|
||||
<Flex gap="2" alignItems="center">
|
||||
<BackButton hideFrom="lg" size="sm" />
|
||||
<Heading size="md">App Relays</Heading>
|
||||
<Button
|
||||
onClick={() => offlineMode.next(!offline)}
|
||||
leftIcon={offline ? <WifiOff /> : <Wifi />}
|
||||
ml="auto"
|
||||
size={{ base: "sm", lg: "md" }}
|
||||
>
|
||||
{offline ? "Offline" : "Online"}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
85
src/views/relays/cache/index.tsx
vendored
Normal file
85
src/views/relays/cache/index.tsx
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
import { Button, Card, CardBody, CardHeader, Flex, Heading, Link, Text } from "@chakra-ui/react";
|
||||
import BackButton from "../../../components/back-button";
|
||||
import { useAsync } from "react-use";
|
||||
import { NOSTR_RELAY_TRAY_URL, checkNostrRelayTray, localRelay } from "../../../services/local-relay";
|
||||
import { CacheRelay } from "nostr-idb";
|
||||
|
||||
function InternalRelay() {
|
||||
const enabled = localRelay instanceof CacheRelay;
|
||||
const enable = () => {
|
||||
localStorage.setItem("localRelay", "nostr-idb://internal");
|
||||
location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<Card borderColor={enabled ? "primary.500" : undefined} variant="outline">
|
||||
<CardHeader p="4" display="flex" gap="2" alignItems="center">
|
||||
<Heading size="md">Browser Cache</Heading>
|
||||
<Button size="sm" colorScheme="primary" ml="auto" onClick={enable} isDisabled={enabled}>
|
||||
{enabled ? "Enabled" : "Enable"}
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardBody p="4" pt="0">
|
||||
<Text mb="2">Use the browsers built-in database to cache events.</Text>
|
||||
<Text>Maximum capacity: 10k events</Text>
|
||||
<Text>Performance: Usable, but limited by the browser</Text>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
function NostrRelayTray() {
|
||||
const { value: available, loading: checking } = useAsync(checkNostrRelayTray);
|
||||
|
||||
const enabled = localRelay.url.startsWith(NOSTR_RELAY_TRAY_URL);
|
||||
const enable = () => {
|
||||
localStorage.setItem("localRelay", NOSTR_RELAY_TRAY_URL);
|
||||
location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<Card borderColor={enabled ? "primary.500" : undefined} variant="outline">
|
||||
<CardHeader p="4" display="flex" gap="2" alignItems="center">
|
||||
<Heading size="md">Nostr Relay Tray</Heading>
|
||||
<Link color="blue.500" href="https://github.com/CodyTseng/nostr-relay-tray" isExternal>
|
||||
GitHub
|
||||
</Link>
|
||||
{available ? (
|
||||
<Button size="sm" colorScheme="primary" ml="auto" isLoading={checking} onClick={enable} isDisabled={enabled}>
|
||||
{enabled ? "Enabled" : "Enable"}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
as={Link}
|
||||
isExternal
|
||||
href="https://github.com/CodyTseng/nostr-relay-tray"
|
||||
colorScheme="blue"
|
||||
size="sm"
|
||||
ml="auto"
|
||||
>
|
||||
Get the app
|
||||
</Button>
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardBody p="4" pt="0">
|
||||
<Text mb="2">A cool little app that runs a local relay in your systems tray</Text>
|
||||
<Text>Maximum capacity: Unlimited</Text>
|
||||
<Text>Performance: As fast as your computer</Text>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CacheRelayView() {
|
||||
return (
|
||||
<Flex gap="2" direction="column" flex={1}>
|
||||
<Flex gap="2" alignItems="center">
|
||||
<BackButton hideFrom="lg" size="sm" />
|
||||
<Heading size="lg" my="1">
|
||||
Cache Relay
|
||||
</Heading>
|
||||
</Flex>
|
||||
<InternalRelay />
|
||||
<NostrRelayTray />
|
||||
</Flex>
|
||||
);
|
||||
}
|
101
src/views/relays/index-old.tsx
Normal file
101
src/views/relays/index-old.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import { useDeferredValue, useMemo, useState } from "react";
|
||||
import { useAsync } from "react-use";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { Button, Flex, Heading, Input, SimpleGrid, Spacer, useDisclosure } from "@chakra-ui/react";
|
||||
|
||||
import relayPoolService from "../../services/relay-pool";
|
||||
import AddCustomRelayModal from "./components/add-custom-modal";
|
||||
import RelayCard from "./components/relay-card";
|
||||
import clientRelaysService from "../../services/client-relays";
|
||||
import { RelayMode } from "../../classes/relay";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import { isValidRelayURL } from "../../helpers/relay";
|
||||
import { useReadRelays, useWriteRelays } from "../../hooks/use-client-relays";
|
||||
import { offlineMode } from "../../services/offline-mode";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import Wifi from "../../components/icons/wifi";
|
||||
import WifiOff from "../../components/icons/wifi-off";
|
||||
|
||||
export default function RelaysView() {
|
||||
const [search, setSearch] = useState("");
|
||||
const deboundedSearch = useDeferredValue(search);
|
||||
const isSearching = deboundedSearch.length > 2;
|
||||
const addRelayModal = useDisclosure();
|
||||
const offline = useSubject(offlineMode);
|
||||
|
||||
const readRelays = useReadRelays();
|
||||
const writeRelays = useWriteRelays();
|
||||
const discoveredRelays = relayPoolService
|
||||
.getRelays()
|
||||
.filter((r) => !readRelays.has(r.url) && !writeRelays.has(r.url))
|
||||
.map((r) => r.url)
|
||||
.filter(isValidRelayURL);
|
||||
|
||||
const { value: onlineRelays = [] } = useAsync(async () =>
|
||||
fetch("https://api.nostr.watch/v1/online").then((res) => res.json() as Promise<string[]>),
|
||||
);
|
||||
|
||||
const filteredRelays = useMemo(() => {
|
||||
if (isSearching) {
|
||||
return onlineRelays.filter((url) => url.toLowerCase().includes(deboundedSearch.toLowerCase()));
|
||||
}
|
||||
|
||||
return [...readRelays];
|
||||
}, [isSearching, deboundedSearch, onlineRelays, readRelays]);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
<Flex alignItems="center" gap="2" wrap="wrap">
|
||||
<Input type="search" placeholder="search" value={search} onChange={(e) => setSearch(e.target.value)} w="auto" />
|
||||
<Button onClick={() => offlineMode.next(!offline)} leftIcon={offline ? <WifiOff /> : <Wifi />}>
|
||||
{offline ? "Offline" : "Online"}
|
||||
</Button>
|
||||
<Spacer />
|
||||
<Button as={RouterLink} to="/relays/popular">
|
||||
Popular Relays
|
||||
</Button>
|
||||
<Button as={RouterLink} to="/relays/reviews">
|
||||
Browse Reviews
|
||||
</Button>
|
||||
<Button colorScheme="primary" onClick={addRelayModal.onOpen}>
|
||||
Add Custom
|
||||
</Button>
|
||||
</Flex>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
{filteredRelays.map((url) => (
|
||||
<ErrorBoundary>
|
||||
<RelayCard key={url} url={url} variant="outline" />
|
||||
</ErrorBoundary>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
||||
{discoveredRelays.length > 0 && !isSearching && (
|
||||
<>
|
||||
<Heading size="lg" my="2">
|
||||
Discovered Relays
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
{discoveredRelays.map((url) => (
|
||||
<ErrorBoundary>
|
||||
<RelayCard key={url} url={url} variant="outline" />
|
||||
</ErrorBoundary>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
|
||||
{addRelayModal.isOpen && (
|
||||
<AddCustomRelayModal
|
||||
isOpen
|
||||
onClose={addRelayModal.onClose}
|
||||
size="2xl"
|
||||
onSubmit={(url) => {
|
||||
clientRelaysService.addRelay(url, RelayMode.ALL);
|
||||
addRelayModal.onClose();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
@ -1,101 +1,75 @@
|
||||
import { useDeferredValue, useMemo, useState } from "react";
|
||||
import { useAsync } from "react-use";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { Button, Flex, Heading, Input, SimpleGrid, Spacer, useDisclosure } from "@chakra-ui/react";
|
||||
import { useState } from "react";
|
||||
import { Outlet, Link as RouterLink, useLocation, useMatch } from "react-router-dom";
|
||||
import { Button, Divider, Flex, Heading, VStack } from "@chakra-ui/react";
|
||||
|
||||
import relayPoolService from "../../services/relay-pool";
|
||||
import AddCustomRelayModal from "./components/add-custom-modal";
|
||||
import RelayCard from "./components/relay-card";
|
||||
import clientRelaysService from "../../services/client-relays";
|
||||
import { RelayMode } from "../../classes/relay";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import { isValidRelayURL } from "../../helpers/relay";
|
||||
import { useReadRelays, useWriteRelays } from "../../hooks/use-client-relays";
|
||||
import { offlineMode } from "../../services/offline-mode";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import Wifi from "../../components/icons/wifi";
|
||||
import WifiOff from "../../components/icons/wifi-off";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import useUserRelaySets from "../../hooks/use-user-relay-sets";
|
||||
import { getListName } from "../../helpers/nostr/lists";
|
||||
import { getEventCoordinate } from "../../helpers/nostr/events";
|
||||
import { useBreakpointValue } from "../../providers/global/breakpoint-provider";
|
||||
import BackButton from "../../components/back-button";
|
||||
|
||||
export default function RelaysView() {
|
||||
const [search, setSearch] = useState("");
|
||||
const deboundedSearch = useDeferredValue(search);
|
||||
const isSearching = deboundedSearch.length > 2;
|
||||
const addRelayModal = useDisclosure();
|
||||
const offline = useSubject(offlineMode);
|
||||
const account = useCurrentAccount();
|
||||
const relaySets = useUserRelaySets(account?.pubkey, undefined);
|
||||
const vertical = useBreakpointValue({ base: true, lg: false });
|
||||
|
||||
const readRelays = useReadRelays();
|
||||
const writeRelays = useWriteRelays();
|
||||
const discoveredRelays = relayPoolService
|
||||
.getRelays()
|
||||
.filter((r) => !readRelays.has(r.url) && !writeRelays.has(r.url))
|
||||
.map((r) => r.url)
|
||||
.filter(isValidRelayURL);
|
||||
const location = useLocation();
|
||||
|
||||
const { value: onlineRelays = [] } = useAsync(async () =>
|
||||
fetch("https://api.nostr.watch/v1/online").then((res) => res.json() as Promise<string[]>),
|
||||
);
|
||||
|
||||
const filteredRelays = useMemo(() => {
|
||||
if (isSearching) {
|
||||
return onlineRelays.filter((url) => url.toLowerCase().includes(deboundedSearch.toLowerCase()));
|
||||
}
|
||||
|
||||
return [...readRelays];
|
||||
}, [isSearching, deboundedSearch, onlineRelays, readRelays]);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
<Flex alignItems="center" gap="2" wrap="wrap">
|
||||
<Input type="search" placeholder="search" value={search} onChange={(e) => setSearch(e.target.value)} w="auto" />
|
||||
<Button onClick={() => offlineMode.next(!offline)} leftIcon={offline ? <WifiOff /> : <Wifi />}>
|
||||
{offline ? "Offline" : "Online"}
|
||||
const renderContent = () => {
|
||||
const nav = (
|
||||
<Flex gap="2" direction="column" minW="xs" overflowY="auto" overflowX="hidden" w={vertical ? "full" : undefined}>
|
||||
<Button
|
||||
as={RouterLink}
|
||||
variant="outline"
|
||||
colorScheme={
|
||||
(location.pathname === "/relays" && !vertical) || location.pathname === "/relays/app"
|
||||
? "primary"
|
||||
: undefined
|
||||
}
|
||||
to="/relays/app"
|
||||
>
|
||||
App Relays
|
||||
</Button>
|
||||
<Spacer />
|
||||
<Button as={RouterLink} to="/relays/popular">
|
||||
Popular Relays
|
||||
<Button
|
||||
as={RouterLink}
|
||||
variant="outline"
|
||||
colorScheme={location.pathname === "/relays/cache" ? "primary" : undefined}
|
||||
to="/relays/cache"
|
||||
>
|
||||
Cache Relay
|
||||
</Button>
|
||||
<Button as={RouterLink} to="/relays/reviews">
|
||||
Browse Reviews
|
||||
</Button>
|
||||
<Button colorScheme="primary" onClick={addRelayModal.onOpen}>
|
||||
Add Custom
|
||||
</Button>
|
||||
</Flex>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
{filteredRelays.map((url) => (
|
||||
<ErrorBoundary>
|
||||
<RelayCard key={url} url={url} variant="outline" />
|
||||
</ErrorBoundary>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
||||
{discoveredRelays.length > 0 && !isSearching && (
|
||||
<>
|
||||
<Heading size="lg" my="2">
|
||||
Discovered Relays
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
{discoveredRelays.map((url) => (
|
||||
<ErrorBoundary>
|
||||
<RelayCard key={url} url={url} variant="outline" />
|
||||
</ErrorBoundary>
|
||||
{account && (
|
||||
<>
|
||||
<Heading size="sm" mt="2">
|
||||
Relay Sets
|
||||
</Heading>
|
||||
{relaySets.map((set) => (
|
||||
<Button
|
||||
as={RouterLink}
|
||||
variant="outline"
|
||||
colorScheme={location.pathname.endsWith(getEventCoordinate(set)) ? "primary" : undefined}
|
||||
to={`/relays/${getEventCoordinate(set)}`}
|
||||
>
|
||||
{getListName(set)}
|
||||
</Button>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
if (vertical) {
|
||||
if (location.pathname !== "/relays") return <Outlet />;
|
||||
else return nav;
|
||||
} else
|
||||
return (
|
||||
<Flex gap="2" maxH="100vh" overflow="hidden">
|
||||
{nav}
|
||||
<Outlet />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
{addRelayModal.isOpen && (
|
||||
<AddCustomRelayModal
|
||||
isOpen
|
||||
onClose={addRelayModal.onClose}
|
||||
size="2xl"
|
||||
onSubmit={(url) => {
|
||||
clientRelaysService.addRelay(url, RelayMode.ALL);
|
||||
addRelayModal.onClose();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
return <VerticalPageLayout>{renderContent()}</VerticalPageLayout>;
|
||||
}
|
||||
|
9
src/views/relays/relay-set/index.tsx
Normal file
9
src/views/relays/relay-set/index.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { Flex } from "@chakra-ui/react";
|
||||
|
||||
export default function RelaySetView() {
|
||||
return (
|
||||
<Flex gap="2" direction="column">
|
||||
Relay Set
|
||||
</Flex>
|
||||
);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user