remove relay selection button

This commit is contained in:
hzrd149
2024-02-02 12:15:49 +00:00
parent fba7ef3a2e
commit 6ccd9ea736
43 changed files with 213 additions and 685 deletions

View File

@@ -106,9 +106,6 @@ const StreamView = lazy(() => import("./views/streams/stream"));
const SearchView = lazy(() => import("./views/search"));
const MapView = lazy(() => import("./views/map"));
const ThingsView = lazy(() => import("./views/things/index"));
const ThingUploadView = lazy(() => import("./views/things/upload"));
const ChannelsHomeView = lazy(() => import("./views/channels"));
const ChannelView = lazy(() => import("./views/channels/channel"));
@@ -322,13 +319,6 @@ const router = createHashRouter([
{ path: "unknown", element: <UnknownTimelineView /> },
],
},
{
path: "things",
children: [
{ path: "", element: <ThingsView /> },
{ path: "upload", element: <ThingUploadView /> },
],
},
{
path: "lists",
children: [

View File

@@ -84,7 +84,7 @@ export default function EventDebugModal({ event, ...props }: { event: NostrEvent
<ModalHeader p="4">{event.id}</ModalHeader>
<ModalCloseButton />
<ModalBody p="0">
<Accordion>
<Accordion allowToggle>
<Section label="IDs">
<RawValue heading="Event Id" value={event.id} />
<RawValue heading="NIP-19 Encoded Id" value={nip19.noteEncode(event.id)} />

View File

@@ -36,7 +36,7 @@ async function getPayRequestForPubkey(
event: NostrEvent | undefined,
amount: number,
comment?: string,
additionalRelays?: string[],
additionalRelays?: Iterable<string>,
): Promise<PayRequest> {
const metadata = userMetadataService.getSubject(pubkey).value;
const address = metadata?.lud16 || metadata?.lud06;
@@ -106,7 +106,7 @@ async function getPayRequestsForEvent(
amount: number,
comment?: string,
fallbackPubkey?: string,
additionalRelays?: string[],
additionalRelays?: Iterable<string>,
) {
const splits = getZapSplits(event, fallbackPubkey);
@@ -133,7 +133,7 @@ export type ZapModalProps = Omit<ModalProps, "children"> & {
allowComment?: boolean;
showEmbed?: boolean;
embedProps?: EmbedProps;
additionalRelays?: string[];
additionalRelays?: Iterable<string>;
onZapped: () => void;
};

View File

@@ -58,7 +58,6 @@ export default function NavItems() {
else if (location.pathname.startsWith("/goals")) active = "goals";
else if (location.pathname.startsWith("/badges")) active = "badges";
else if (location.pathname.startsWith("/emojis")) active = "emojis";
else if (location.pathname.startsWith("/things")) active = "things";
else if (location.pathname.startsWith("/settings")) active = "settings";
else if (location.pathname.startsWith("/tools")) active = "tools";
else if (location.pathname.startsWith("/search")) active = "search";

View File

@@ -1,5 +1,6 @@
import React, { LegacyRef, forwardRef } from "react";
import { Image, InputProps, Textarea, TextareaProps } from "@chakra-ui/react";
// NOTE: Do not remove Textarea or Input from the imports. they are used
import { Image, InputProps, Textarea, Input, TextareaProps } from "@chakra-ui/react";
import ReactTextareaAutocomplete, {
ItemComponentProps,
TextareaProps as ReactTextareaAutocompleteProps,

View File

@@ -3,6 +3,7 @@ import { ButtonProps, IconButton, useDisclosure } from "@chakra-ui/react";
import { RelayIcon } from "../icons";
import RelayManagementDrawer from "../relay-management-drawer";
/** @deprecated */
export default function RelaySelectionButton({ ...props }: ButtonProps) {
const relaysModal = useDisclosure();
return (

View File

@@ -1,61 +0,0 @@
import { PropsWithChildren, createContext, useCallback, useContext, useMemo } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { useReadRelays } from "../../hooks/use-client-relays";
import { unique } from "../../helpers/array";
type RelaySelectionContextType = {
relays: string[];
setSelected: (relays: string[]) => void;
};
export const RelaySelectionContext = createContext<RelaySelectionContextType>({
relays: [],
setSelected: () => {},
});
export function useRelaySelectionContext() {
return useContext(RelaySelectionContext);
}
export function useRelaySelectionRelays() {
return useContext(RelaySelectionContext).relays;
}
export type RelaySelectionProviderProps = PropsWithChildren & {
overrideDefault?: Iterable<string>;
additionalDefaults?: Iterable<string>;
};
export default function RelaySelectionProvider({
children,
overrideDefault,
additionalDefaults,
}: RelaySelectionProviderProps) {
const navigate = useNavigate();
const location = useLocation();
const readRelays = useReadRelays();
const relays = useMemo(() => {
if (location.state?.relays) return location.state.relays as string[];
if (overrideDefault) return Array.from(overrideDefault);
if (additionalDefaults) return unique([...readRelays, ...additionalDefaults]);
return readRelays.urls;
}, [location.state?.relays, overrideDefault, readRelays.urls.join("|"), additionalDefaults]);
const setSelected = useCallback(
(relays: string[]) => {
navigate(location.pathname + location.search, { state: { relays }, replace: true });
},
[navigate, location],
);
const context = useMemo(
() => ({
relays,
setSelected,
}),
[relays.join("|"), setSelected],
);
return <RelaySelectionContext.Provider value={context}>{children}</RelaySelectionContext.Provider>;
}

View File

@@ -7,9 +7,7 @@ import useSingleEvent from "../../hooks/use-single-event";
import { ErrorBoundary } from "../../components/error-boundary";
import { NostrEvent } from "../../types/nostr-event";
import useChannelMetadata from "../../hooks/use-channel-metadata";
import RelaySelectionProvider, { useRelaySelectionContext } from "../../providers/local/relay-selection-provider";
import { ChevronLeftIcon } from "../../components/icons";
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
import ChannelMetadataDrawer from "./components/channel-metadata-drawer";
import ChannelJoinButton from "./components/channel-join-button";
import ChannelMenu from "./components/channel-menu";
@@ -25,6 +23,7 @@ import ChannelMessageBlock from "./components/channel-message-block";
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
import ChannelMessageForm from "./components/send-message-form";
import useParamsEventPointer from "../../hooks/use-params-event-pointer";
import { useReadRelays } from "../../hooks/use-client-relays";
const ChannelChatLog = memo(({ timeline, channel }: { timeline: TimelineLoader; channel: NostrEvent }) => {
const messages = useSubject(timeline.timeline);
@@ -45,7 +44,7 @@ const ChannelChatLog = memo(({ timeline, channel }: { timeline: TimelineLoader;
function ChannelPage({ channel }: { channel: NostrEvent }) {
const navigate = useNavigate();
const { relays } = useRelaySelectionContext();
const relays = useReadRelays();
const { metadata } = useChannelMetadata(channel.id, relays);
const drawer = useDisclosure();
@@ -76,7 +75,6 @@ function ChannelPage({ channel }: { channel: NostrEvent }) {
<Button leftIcon={<ChevronLeftIcon />} onClick={() => navigate(-1)}>
Back
</Button>
<RelaySelectionButton hideBelow="lg" />
<Heading hideBelow="lg" size="lg">
{metadata?.name}
</Heading>
@@ -116,9 +114,7 @@ export default function ChannelView() {
return (
<ErrorBoundary>
<RelaySelectionProvider>
<ChannelPage channel={channel} />
</RelaySelectionProvider>
<ChannelPage channel={channel} />
</ErrorBoundary>
);
}

View File

@@ -30,7 +30,7 @@ export default function ChannelCard({
channel,
additionalRelays,
...props
}: Omit<CardProps, "children"> & { channel: NostrEvent; additionalRelays?: string[] }) {
}: Omit<CardProps, "children"> & { channel: NostrEvent; additionalRelays?: Iterable<string> }) {
const readRelays = useReadRelays(additionalRelays);
const { metadata } = useChannelMetadata(channel.id, readRelays);

View File

@@ -25,11 +25,12 @@ import IntersectionObserverProvider from "../../../providers/local/intersection-
import UserLink from "../../../components/user-link";
import HoverLinkOverlay from "../../../components/hover-link-overlay";
import UserAvatar from "../../../components/user-avatar";
import { useRelaySelectionContext } from "../../../providers/local/relay-selection-provider";
import { UserDnsIdentityIcon } from "../../../components/user-dns-identity-icon";
import ChannelJoinButton from "./channel-join-button";
import { ExternalLinkIcon } from "../../../components/icons";
import { CHANNELS_LIST_KIND } from "../../../helpers/nostr/lists";
import { useReadRelays } from "../../../hooks/use-client-relays";
import { useAdditionalRelayContext } from "../../../providers/local/additional-relay-context";
function UserCard({ pubkey }: { pubkey: string }) {
return (
@@ -40,7 +41,7 @@ function UserCard({ pubkey }: { pubkey: string }) {
</Card>
);
}
function ChannelMembers({ channel, relays }: { channel: NostrEvent; relays: string[] }) {
function ChannelMembers({ channel, relays }: { channel: NostrEvent; relays: Iterable<string> }) {
const timeline = useTimelineLoader(`${channel.id}-members`, relays, {
kinds: [CHANNELS_LIST_KIND],
"#e": [channel.id],
@@ -67,7 +68,7 @@ export default function ChannelMetadataDrawer({
...props
}: Omit<DrawerProps, "children"> & { channel: NostrEvent }) {
const { metadata } = useChannelMetadata(channel.id);
const { relays } = useRelaySelectionContext();
const relays = useReadRelays(useAdditionalRelayContext());
return (
<Drawer isOpen={isOpen} placement="right" onClose={onClose} {...props}>

View File

@@ -3,21 +3,20 @@ import { kinds } from "nostr-tools";
import { Flex } from "@chakra-ui/react";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import RelaySelectionProvider, { useRelaySelectionContext } from "../../providers/local/relay-selection-provider";
import useSubject from "../../hooks/use-subject";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
import VerticalPageLayout from "../../components/vertical-page-layout";
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
import { NostrEvent } from "../../types/nostr-event";
import { ErrorBoundary } from "../../components/error-boundary";
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter";
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
import ChannelCard from "./components/channel-card";
import { useReadRelays } from "../../hooks/use-client-relays";
function ChannelsHomePage() {
const { relays } = useRelaySelectionContext();
const relays = useReadRelays();
const { filter, listId } = usePeopleListContext();
const clientMuteFilter = useClientSideMuteFilter();
@@ -42,7 +41,6 @@ function ChannelsHomePage() {
<VerticalPageLayout>
<Flex gap="2">
<PeopleListSelection />
<RelaySelectionButton />
</Flex>
<IntersectionObserverProvider callback={callback}>
{channels.map((channel) => (
@@ -57,10 +55,8 @@ function ChannelsHomePage() {
export default function ChannelsHomeView() {
return (
<RelaySelectionProvider>
<PeopleListProvider>
<ChannelsHomePage />
</PeopleListProvider>
</RelaySelectionProvider>
<PeopleListProvider>
<ChannelsHomePage />
</PeopleListProvider>
);
}

View File

@@ -10,9 +10,7 @@ import useAppSettings from "../../hooks/use-app-settings";
import { TrustProvider, useTrusted } from "../../providers/local/trust";
import BlurredImage from "../../components/blured-image";
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
import RelaySelectionProvider, { useRelaySelectionContext } from "../../providers/local/relay-selection-provider";
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
import { UserAvatarLink } from "../../components/user-avatar-link";
import UserLink from "../../components/user-link";
import MimeTypePicker from "./mime-type-picker";
@@ -24,6 +22,7 @@ import IntersectionObserverProvider, {
useRegisterIntersectionEntity,
} from "../../providers/local/intersection-observer";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
import { useReadRelays } from "../../hooks/use-client-relays";
function ImageFile({ event }: { event: NostrEvent }) {
const parsed = parseImageFile(event);
@@ -112,7 +111,7 @@ function FileType({ event }: { event: NostrEvent }) {
function FilesPage() {
const { listId, filter } = usePeopleListContext();
const { relays } = useRelaySelectionContext();
const relays = useReadRelays();
const [selectedTypes, setSelectedTypes] = useState<string[]>(IMAGE_TYPES);
@@ -131,7 +130,6 @@ function FilesPage() {
<Flex gap="2">
<PeopleListSelection />
<MimeTypePicker selected={selectedTypes} onChange={(v) => setSelectedTypes(v)} />
<RelaySelectionButton ml="auto" />
</Flex>
<IntersectionObserverProvider callback={callback}>
@@ -151,9 +149,7 @@ function FilesPage() {
export default function FilesView() {
return (
<PeopleListProvider>
<RelaySelectionProvider>
<FilesPage />
</RelaySelectionProvider>
<FilesPage />
</PeopleListProvider>
);
}

View File

@@ -18,8 +18,6 @@ import useTimelineLoader from "../../hooks/use-timeline-loader";
import { isReply, isRepost } from "../../helpers/nostr/events";
import { CheckIcon, EditIcon } from "../../components/icons";
import { NostrEvent } from "../../types/nostr-event";
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
import RelaySelectionProvider, { useRelaySelectionRelays } from "../../providers/local/relay-selection-provider";
import useRelaysChanged from "../../hooks/use-relays-changed";
import TimelinePage, { useTimelinePageEventFilter } from "../../components/timeline-page";
import TimelineViewTypeButtons from "../../components/timeline-page/timeline-view-type";
@@ -28,6 +26,7 @@ import PeopleListProvider, { usePeopleListContext } from "../../providers/local/
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
import NoteFilterTypeButtons from "../../components/note-filter-type-buttons";
import { useRouteStateBoolean } from "../../hooks/use-route-state-value";
import { useReadRelays } from "../../hooks/use-client-relays";
function EditableControls() {
const { isEditing, getSubmitButtonProps, getCancelButtonProps, getEditButtonProps } = useEditableControls();
@@ -54,7 +53,7 @@ function HashTagPage() {
const showReplies = useRouteStateBoolean("show-replies", true);
const showReposts = useRouteStateBoolean("show-reposts", true);
const readRelays = useRelaySelectionRelays();
const readRelays = useReadRelays().urls;
const { listId, filter } = usePeopleListContext();
const timelinePageEventFilter = useTimelinePageEventFilter();
@@ -98,7 +97,6 @@ function HashTagPage() {
<EditableControls />
</Editable>
<PeopleListSelection />
<RelaySelectionButton />
<NoteFilterTypeButtons showReplies={showReplies} showReposts={showReposts} />
<Spacer />
<TimelineViewTypeButtons />
@@ -110,10 +108,8 @@ function HashTagPage() {
export default function HashTagView() {
return (
<RelaySelectionProvider>
<PeopleListProvider initList="global">
<HashTagPage />
</PeopleListProvider>
</RelaySelectionProvider>
<PeopleListProvider initList="global">
<HashTagPage />
</PeopleListProvider>
);
}

View File

@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo } from "react";
import { Flex, useDisclosure } from "@chakra-ui/react";
import { useCallback, useEffect } from "react";
import { Flex, Spacer, useDisclosure } from "@chakra-ui/react";
import { kinds } from "nostr-tools";
import { isReply, isRepost } from "../../helpers/nostr/events";
@@ -8,12 +8,11 @@ import { NostrEvent } from "../../types/nostr-event";
import TimelinePage, { useTimelinePageEventFilter } from "../../components/timeline-page";
import TimelineViewTypeButtons from "../../components/timeline-page/timeline-view-type";
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
import RelaySelectionProvider, { useRelaySelectionContext } from "../../providers/local/relay-selection-provider";
import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter";
import NoteFilterTypeButtons from "../../components/note-filter-type-buttons";
import KindSelectionProvider, { useKindSelectionContext } from "../../providers/local/kind-selection-provider";
import { useReadRelays } from "../../hooks/use-client-relays";
const defaultKinds = [
kinds.ShortTextNote,
@@ -46,7 +45,7 @@ function HomePage() {
[timelinePageEventFilter, showReplies.isOpen, showReposts.isOpen, muteFilter],
);
const { relays } = useRelaySelectionContext();
const relays = useReadRelays();
const { listId, filter } = usePeopleListContext();
const { kinds } = useKindSelectionContext();
@@ -58,7 +57,7 @@ function HomePage() {
<Flex gap="2" wrap="wrap" alignItems="center">
<PeopleListSelection />
<NoteFilterTypeButtons showReplies={showReplies} showReposts={showReposts} />
<RelaySelectionButton ml="auto" />
<Spacer />
<TimelineViewTypeButtons />
</Flex>
);
@@ -69,11 +68,9 @@ function HomePage() {
export default function HomeView() {
return (
<PeopleListProvider>
<RelaySelectionProvider>
<KindSelectionProvider initKinds={defaultKinds}>
<HomePage />
</KindSelectionProvider>
</RelaySelectionProvider>
<KindSelectionProvider initKinds={defaultKinds}>
<HomePage />
</KindSelectionProvider>
</PeopleListProvider>
);
}

View File

@@ -42,7 +42,6 @@ export const internalApps: App[] = [
{ title: "Lists", description: "Browse and create lists", icon: ListsIcon, id: "lists", to: "/lists" },
{ title: "Tracks", description: "Browse stemstr tracks", icon: TrackIcon, id: "tracks", to: "/tracks" },
{ title: "Videos", description: "Browse flare videos", icon: Film02, id: "videos", to: "/videos" },
// { title: "Things", icon: ThingsIcon, id: "things", to: "/things" },
];
export const internalTools: App[] = [

View File

@@ -1,72 +1,19 @@
import { useMemo } from "react";
import { Link as RouterLink } from "react-router-dom";
import { CloseIcon } from "@chakra-ui/icons";
import { Button, Flex, Heading, IconButton, Link } from "@chakra-ui/react";
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";
import AddRelayForm from "./add-relay-form";
import relayPoolService from "../../../services/relay-pool";
import clientRelaysService from "../../../services/client-relays";
import { RelayMode } from "../../../classes/relay";
import { RelayFavicon } from "../../../components/relay-favicon";
import UploadCloud01 from "../../../components/icons/upload-cloud-01";
import RelaySet from "../../../classes/relay-set";
import { useReadRelays, useWriteRelays } from "../../../hooks/use-client-relays";
import useCurrentAccount from "../../../hooks/use-current-account";
function RelayControl({ url }: { url: string }) {
const relay = useMemo(() => relayPoolService.requestRelay(url, false), [url]);
const status = useSubject(relay.status);
const writeRelays = useSubject(clientRelaysService.writeRelays);
let color = "gray";
switch (status) {
case WebSocket.OPEN:
color = "green";
break;
case WebSocket.CONNECTING:
color = "yellow";
break;
case WebSocket.CLOSED:
color = "red";
break;
}
const onChange = () => {
if (writeRelays.has(url)) clientRelaysService.removeRelay(url, RelayMode.WRITE);
else clientRelaysService.addRelay(url, RelayMode.WRITE);
};
return (
<Flex gap="2" alignItems="center" pl="2">
<RelayFavicon relay={url} size="xs" outline="2px solid" outlineColor={color} />
<Link as={RouterLink} to={`/r/${encodeURIComponent(url)}`} isTruncated>
{url}
</Link>
<IconButton
ml="auto"
aria-label="Toggle Write"
icon={<UploadCloud01 />}
size="sm"
variant={writeRelays.has(url) ? "solid" : "ghost"}
colorScheme={writeRelays.has(url) ? "green" : "gray"}
onClick={onChange}
title="Toggle Write"
/>
<IconButton
aria-label="Remove Relay"
icon={<CloseIcon />}
size="sm"
colorScheme="red"
onClick={() => clientRelaysService.removeRelay(url, RelayMode.ALL)}
/>
</Flex>
);
}
import RelayControl from "./relay-control";
import SelectRelaySet from "./select-relay-set";
export default function AppRelays() {
const account = useCurrentAccount();
@@ -99,6 +46,15 @@ export default function AppRelays() {
clientRelaysService.addRelay(url, RelayMode.ALL);
}}
/>
{/* {account && (
<>
<Heading size="md" mt="2">
Use relay set
</Heading>
<SelectRelaySet onChange={(cord, set) => set && clientRelaysService.setRelaysFromRelaySet(set)} />
</>
)} */}
</Flex>
);
}

View File

@@ -0,0 +1,61 @@
import { useMemo } from "react";
import { Link as RouterLink } from "react-router-dom";
import { CloseIcon } from "@chakra-ui/icons";
import { Flex, IconButton, Link } from "@chakra-ui/react";
import useSubject from "../../../hooks/use-subject";
import relayPoolService from "../../../services/relay-pool";
import clientRelaysService from "../../../services/client-relays";
import { RelayMode } from "../../../classes/relay";
import { RelayFavicon } from "../../../components/relay-favicon";
import UploadCloud01 from "../../../components/icons/upload-cloud-01";
export default function RelayControl({ url }: { url: string }) {
const relay = useMemo(() => relayPoolService.requestRelay(url, false), [url]);
const status = useSubject(relay.status);
const writeRelays = useSubject(clientRelaysService.writeRelays);
let color = "gray";
switch (status) {
case WebSocket.OPEN:
color = "green";
break;
case WebSocket.CONNECTING:
color = "yellow";
break;
case WebSocket.CLOSED:
color = "red";
break;
}
const onChange = () => {
if (writeRelays.has(url)) clientRelaysService.removeRelay(url, RelayMode.WRITE);
else clientRelaysService.addRelay(url, RelayMode.WRITE);
};
return (
<Flex gap="2" alignItems="center" pl="2">
<RelayFavicon relay={url} size="xs" outline="2px solid" outlineColor={color} />
<Link as={RouterLink} to={`/r/${encodeURIComponent(url)}`} isTruncated>
{url}
</Link>
<IconButton
ml="auto"
aria-label="Toggle Write"
icon={<UploadCloud01 />}
size="sm"
variant={writeRelays.has(url) ? "solid" : "ghost"}
colorScheme={writeRelays.has(url) ? "green" : "gray"}
onClick={onChange}
title="Toggle Write"
/>
<IconButton
aria-label="Remove Relay"
icon={<CloseIcon />}
size="sm"
colorScheme="red"
onClick={() => clientRelaysService.removeRelay(url, RelayMode.ALL)}
/>
</Flex>
);
}

View File

@@ -0,0 +1,41 @@
import { Select } from "@chakra-ui/react";
import { NostrEvent } from "nostr-tools";
import useCurrentAccount from "../../../hooks/use-current-account";
import useUserRelaySets from "../../../hooks/use-user-relay-sets";
import { getEventCoordinate } from "../../../helpers/nostr/events";
import { getListName } from "../../../helpers/nostr/lists";
export default function SelectRelaySet({
value,
onChange,
pubkey,
}: {
value?: string;
onChange: (cord: string, set?: NostrEvent) => void;
pubkey?: string;
}) {
const account = useCurrentAccount();
const relaySets = useUserRelaySets(pubkey || account?.pubkey);
return (
<Select
size="sm"
borderRadius="md"
placeholder="Select set"
value={value}
onChange={(e) =>
onChange(
e.target.value,
relaySets.find((set) => getEventCoordinate(set) === e.target.value),
)
}
>
{relaySets.map((set) => (
<option key={set.id} value={getEventCoordinate(set)}>
{getListName(set)}
</option>
))}
</Select>
);
}

View File

@@ -4,8 +4,6 @@ import { getEventUID } from "nostr-idb";
import VerticalPageLayout from "../../components/vertical-page-layout";
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
import RelaySelectionProvider, { useRelaySelectionRelays } from "../../providers/local/relay-selection-provider";
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
@@ -15,6 +13,7 @@ import { NostrEvent } from "../../types/nostr-event";
import { getListName, getRelaysFromList } from "../../helpers/nostr/lists";
import { RelayFavicon } from "../../components/relay-favicon";
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
import { useReadRelays } from "../../hooks/use-client-relays";
function RelaySetCard({ set }: { set: NostrEvent }) {
const name = getListName(set);
@@ -35,7 +34,7 @@ function RelaySetCard({ set }: { set: NostrEvent }) {
}
function BrowseRelaySetsPage() {
const relays = useRelaySelectionRelays();
const relays = useReadRelays();
const { filter } = usePeopleListContext();
const timeline = useTimelineLoader("relay-sets", relays, filter && { kinds: [kinds.Relaysets], ...filter }, {
eventFilter: (e) => getRelaysFromList(e).length > 0,
@@ -47,7 +46,6 @@ function BrowseRelaySetsPage() {
return (
<VerticalPageLayout>
<Flex gap="2" wrap="wrap">
<RelaySelectionButton />
<PeopleListSelection />
</Flex>
<IntersectionObserverProvider callback={callback}>
@@ -62,10 +60,8 @@ function BrowseRelaySetsPage() {
export default function BrowseRelaySetsView() {
return (
<RelaySelectionProvider>
<PeopleListProvider>
<BrowseRelaySetsPage />
</PeopleListProvider>
</RelaySelectionProvider>
<PeopleListProvider>
<BrowseRelaySetsPage />
</PeopleListProvider>
);
}

View File

@@ -54,7 +54,7 @@ export default function RelaysView() {
Mailboxes
</Button>
)}
{account && (
{/* {account && (
<>
<Heading size="sm" mt="2">
Relay Sets
@@ -70,7 +70,7 @@ export default function RelaysView() {
</Button>
))}
</>
)}
)} */}
</Flex>
);
if (vertical) {

View File

@@ -1,14 +1,14 @@
import { kinds } from "nostr-tools";
import { useRelaySelectionRelays } from "../../providers/local/relay-selection-provider";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
import GenericNoteTimeline from "../../components/timeline-page/generic-note-timeline";
import { usePeopleListContext } from "../../providers/local/people-list-provider";
import { useAdditionalRelayContext } from "../../providers/local/additional-relay-context";
export default function ArticleSearchResults({ search }: { search: string }) {
const searchRelays = useRelaySelectionRelays();
const searchRelays = useAdditionalRelayContext();
const { listId, filter } = usePeopleListContext();
const timeline = useTimelineLoader(

View File

@@ -1,16 +1,17 @@
import { useRelaySelectionRelays } from "../../providers/local/relay-selection-provider";
import { useRef } from "react";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
import IntersectionObserverProvider, {
useRegisterIntersectionEntity,
} from "../../providers/local/intersection-observer";
import { COMMUNITY_DEFINITION_KIND } from "../../helpers/nostr/communities";
import { useRef } from "react";
import { getEventUID } from "../../helpers/nostr/events";
import { NostrEvent } from "../../types/nostr-event";
import CommunityCard from "../communities/components/community-card";
import useSubject from "../../hooks/use-subject";
import { usePeopleListContext } from "../../providers/local/people-list-provider";
import { useAdditionalRelayContext } from "../../providers/local/additional-relay-context";
function CommunityResult({ community }: { community: NostrEvent }) {
const ref = useRef<HTMLDivElement | null>(null);
@@ -24,7 +25,7 @@ function CommunityResult({ community }: { community: NostrEvent }) {
}
export default function CommunitySearchResults({ search }: { search: string }) {
const searchRelays = useRelaySelectionRelays();
const searchRelays = useAdditionalRelayContext();
const { listId, filter } = usePeopleListContext();
const timeline = useTimelineLoader(

View File

@@ -6,7 +6,6 @@ import { SEARCH_RELAYS } from "../../const";
import { safeDecode } from "../../helpers/nip19";
import { getMatchHashtag } from "../../helpers/regexp";
import { CommunityIcon, CopyToClipboardIcon, NotesIcon } from "../../components/icons";
import RelaySelectionProvider from "../../providers/local/relay-selection-provider";
import VerticalPageLayout from "../../components/vertical-page-layout";
import User01 from "../../components/icons/user-01";
import Feather from "../../components/icons/feather";
@@ -19,6 +18,7 @@ import PeopleListSelection from "../../components/people-list-selection/people-l
import useRouteSearchValue from "../../hooks/use-route-search-value";
import { useBreakpointValue } from "../../providers/global/breakpoint-provider";
import QRCodeScannerButton from "../../components/qr-code-scanner-button";
import { AdditionalRelayProvider } from "../../providers/local/additional-relay-context";
export function SearchPage() {
const navigate = useNavigate();
@@ -147,10 +147,10 @@ export function SearchPage() {
export default function SearchView() {
return (
<RelaySelectionProvider overrideDefault={SEARCH_RELAYS}>
<AdditionalRelayProvider relays={SEARCH_RELAYS}>
<PeopleListProvider initList="global">
<SearchPage />
</PeopleListProvider>
</RelaySelectionProvider>
</AdditionalRelayProvider>
);
}

View File

@@ -1,14 +1,14 @@
import { kinds } from "nostr-tools";
import { useRelaySelectionRelays } from "../../providers/local/relay-selection-provider";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
import GenericNoteTimeline from "../../components/timeline-page/generic-note-timeline";
import { usePeopleListContext } from "../../providers/local/people-list-provider";
import { useAdditionalRelayContext } from "../../providers/local/additional-relay-context";
export default function NoteSearchResults({ search }: { search: string }) {
const searchRelays = useRelaySelectionRelays();
const searchRelays = useAdditionalRelayContext();
const { listId, filter } = usePeopleListContext();
const timeline = useTimelineLoader(

View File

@@ -12,13 +12,13 @@ import { UserDnsIdentityIcon } from "../../components/user-dns-identity-icon";
import { embedNostrLinks, renderGenericUrl } from "../../components/embed-types";
import UserLink from "../../components/user-link";
import trustedUserStatsService, { NostrBandUserStats } from "../../services/trusted-user-stats";
import { useRelaySelectionRelays } from "../../providers/local/relay-selection-provider";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import useSubject from "../../hooks/use-subject";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
import { usePeopleListContext } from "../../providers/local/people-list-provider";
import { useAdditionalRelayContext } from "../../providers/local/additional-relay-context";
function ProfileResult({ profile }: { profile: NostrEvent }) {
const metadata = parseKind0Event(profile);
@@ -51,7 +51,7 @@ function ProfileResult({ profile }: { profile: NostrEvent }) {
}
export default function ProfileSearchResults({ search }: { search: string }) {
const searchRelays = useRelaySelectionRelays();
const searchRelays = useAdditionalRelayContext();
const { listId, filter } = usePeopleListContext();
const timeline = useTimelineLoader(

View File

@@ -1,47 +0,0 @@
import {
Flex,
IconButton,
IconButtonProps,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
useDisclosure,
} from "@chakra-ui/react";
import { CodeIcon } from "../../../components/icons";
import RawValue from "../../../components/debug-modal/raw-value";
import RawJson from "../../../components/debug-modal/raw-json";
import { ParsedStream } from "../../../helpers/nostr/stream";
import useEventNaddr from "../../../hooks/use-event-naddr";
export default function StreamDebugButton({
stream,
...props
}: { stream: ParsedStream } & Omit<IconButtonProps, "icon" | "aria-label">) {
const debugModal = useDisclosure();
const naddr = useEventNaddr(stream.event);
return (
<>
<IconButton icon={<CodeIcon />} aria-label="Show raw event" onClick={debugModal.onOpen} {...props} />
<Modal isOpen={debugModal.isOpen} onClose={debugModal.onClose} size="6xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>Raw event</ModalHeader>
<ModalCloseButton />
<ModalBody p="4">
<Flex gap="2" direction="column">
<RawValue heading="Event Id" value={stream.event.id} />
<RawValue heading="naddr" value={naddr} />
<RawJson heading="Parsed" json={{ ...stream, event: "Omitted, see JSON below" }} />
<RawJson heading="JSON" json={stream.event} />
</Flex>
</ModalBody>
</ModalContent>
</Modal>
</>
);
}

View File

@@ -3,8 +3,9 @@ import { ParsedStream } from "../../../helpers/nostr/stream";
import { LightningIcon } from "../../../components/icons";
import useUserLNURLMetadata from "../../../hooks/use-user-lnurl-metadata";
import ZapModal from "../../../components/event-zap-modal";
import { useRelaySelectionRelays } from "../../../providers/local/relay-selection-provider";
import useStreamGoal from "../../../hooks/use-stream-goal";
import { useReadRelays } from "../../../hooks/use-client-relays";
import { useAdditionalRelayContext } from "../../../providers/local/additional-relay-context";
export default function StreamZapButton({
stream,
@@ -19,7 +20,7 @@ export default function StreamZapButton({
}) {
const zapModal = useDisclosure();
const zapMetadata = useUserLNURLMetadata(stream.host);
const relays = useRelaySelectionRelays();
const relays = useReadRelays(useAdditionalRelayContext());
const goal = useStreamGoal(stream);
const commonProps = {

View File

@@ -2,13 +2,13 @@ import { useMemo } from "react";
import { Card, CardBody, CardHeader, CardProps, Heading, Image, LinkBox, LinkOverlay } from "@chakra-ui/react";
import { useReadRelays } from "../../../hooks/use-client-relays";
import { useRelaySelectionRelays } from "../../../providers/local/relay-selection-provider";
import replaceableEventLoaderService from "../../../services/replaceable-event-requester";
import useSubject from "../../../hooks/use-subject";
import { NoteContents } from "../../../components/note/text-note-contents";
import { isATag } from "../../../types/nostr-event";
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
import OpenGraphCard from "../../../components/open-graph-card";
import { useAdditionalRelayContext } from "../../../providers/local/additional-relay-context";
export const STREAMER_CARDS_TYPE = 17777;
export const STREAMER_CARD_TYPE = 37777;
@@ -24,7 +24,7 @@ function useStreamerCardsCords(pubkey: string, relays: Iterable<string>) {
}
function StreamerCard({ cord, relay, ...props }: { cord: string; relay?: string } & CardProps) {
const contextRelays = useRelaySelectionRelays();
const contextRelays = useAdditionalRelayContext();
const readRelays = useReadRelays(relay ? [...contextRelays, relay] : contextRelays);
const card = useReplaceableEvent(cord, readRelays);
@@ -61,7 +61,7 @@ function StreamerCard({ cord, relay, ...props }: { cord: string; relay?: string
}
export default function StreamerCards({ pubkey, ...props }: Omit<CardProps, "children"> & { pubkey: string }) {
const contextRelays = useRelaySelectionRelays();
const contextRelays = useAdditionalRelayContext();
const readRelays = useReadRelays(contextRelays);
const cardCords = useStreamerCardsCords(pubkey, readRelays);

View File

@@ -15,7 +15,7 @@ import useCurrentAccount from "../../../hooks/use-current-account";
import { getEventUID } from "../../../helpers/nostr/events";
import { useReadRelays } from "../../../hooks/use-client-relays";
import { ChevronLeftIcon } from "../../../components/icons";
import RelaySelectionProvider from "../../../providers/local/relay-selection-provider";
import { AdditionalRelayProvider } from "../../../providers/local/additional-relay-context";
import UsersCard from "./users-card";
import ZapsCard from "./zaps-card";
import ChatCard from "./chat-card";
@@ -105,9 +105,9 @@ function StreamModerationPage() {
</Select>
</Flex>
{selected && (
<RelaySelectionProvider additionalDefaults={selected.relays ?? []}>
<AdditionalRelayProvider relays={selected.relays ?? []}>
<StreamModerationDashboard stream={selected} />
</RelaySelectionProvider>
</AdditionalRelayProvider>
)}
</Flex>
);

View File

@@ -1,5 +1,5 @@
import { useCallback, useMemo } from "react";
import { Divider, Flex, Heading, SimpleGrid, Switch } from "@chakra-ui/react";
import { Flex, Heading, SimpleGrid, Switch } from "@chakra-ui/react";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
@@ -7,8 +7,6 @@ import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-
import useSubject from "../../hooks/use-subject";
import StreamCard from "./components/stream-card";
import { STREAM_KIND } from "../../helpers/nostr/stream";
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
import RelaySelectionProvider, { useRelaySelectionRelays } from "../../providers/local/relay-selection-provider";
import useRelaysChanged from "../../hooks/use-relays-changed";
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
@@ -20,10 +18,12 @@ import { NostrEvent } from "../../types/nostr-event";
import VerticalPageLayout from "../../components/vertical-page-layout";
import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter";
import { useRouteStateBoolean } from "../../hooks/use-route-state-value";
import { useReadRelays } from "../../hooks/use-client-relays";
import { AdditionalRelayProvider, useAdditionalRelayContext } from "../../providers/local/additional-relay-context";
function StreamsPage() {
useAppTitle("Streams");
const relays = useRelaySelectionRelays();
const relays = useReadRelays(useAdditionalRelayContext()).urls;
const userMuteFilter = useClientSideMuteFilter();
const showEnded = useRouteStateBoolean("ended", false);
@@ -63,7 +63,6 @@ function StreamsPage() {
<Switch checked={showEnded.isOpen} onChange={showEnded.onToggle}>
Show Ended
</Switch>
<RelaySelectionButton ml="auto" />
</Flex>
<IntersectionObserverProvider callback={callback}>
<Heading size="lg" mt="2">
@@ -93,12 +92,12 @@ function StreamsPage() {
}
export default function StreamsView() {
return (
<RelaySelectionProvider
additionalDefaults={["wss://nos.lol", "wss://relay.damus.io", "wss://relay.snort.social", "wss://nostr.wine"]}
<AdditionalRelayProvider
relays={["wss://nos.lol", "wss://relay.damus.io", "wss://relay.snort.social", "wss://nostr.wine"]}
>
<PeopleListProvider>
<StreamsPage />
</PeopleListProvider>
</RelaySelectionProvider>
</AdditionalRelayProvider>
);
}

View File

@@ -31,11 +31,8 @@ import StreamSummaryContent from "../components/stream-summary-content";
import { ChevronLeftIcon, ExternalLinkIcon } from "../../../components/icons";
import useSetColorMode from "../../../hooks/use-set-color-mode";
import { CopyIconButton } from "../../../components/copy-icon-button";
import StreamDebugButton from "../components/stream-debug-button";
import replaceableEventLoaderService from "../../../services/replaceable-event-requester";
import useSubject from "../../../hooks/use-subject";
import RelaySelectionButton from "../../../components/relay-selection/relay-selection-button";
import RelaySelectionProvider from "../../../providers/local/relay-selection-provider";
import StreamerCards from "../components/streamer-cards";
import { useAppTitle } from "../../../hooks/use-app-title";
import StreamSatsPerMinute from "../components/stream-sats-per-minute";
@@ -50,6 +47,8 @@ import StreamGoal from "../components/stream-goal";
import StreamShareButton from "../components/stream-share-button";
import VerticalPageLayout from "../../../components/vertical-page-layout";
import { useBreakpointValue } from "../../../providers/global/breakpoint-provider";
import { AdditionalRelayProvider } from "../../../providers/local/additional-relay-context";
import DebugEventButton from "../../../components/debug-modal/debug-event-button";
function DesktopStreamPage({ stream }: { stream: ParsedStream }) {
useAppTitle(stream.title);
@@ -96,8 +95,7 @@ function DesktopStreamPage({ stream }: { stream: ParsedStream }) {
<StreamStatusBadge stream={stream} fontSize="lg" />
<Spacer />
<StreamShareButton stream={stream} title="Share stream" />
<RelaySelectionButton display={{ base: "none", md: "block" }} />
<StreamDebugButton stream={stream} variant="ghost" />
<DebugEventButton event={stream.event} variant="ghost" />
<Button onClick={() => setShowChat((v) => !v)}>{showChat ? "Hide" : "Show"} Chat</Button>
</Flex>
<Flex gap="2" maxH="calc(100vh - 4rem)" overflow="hidden">
@@ -278,10 +276,10 @@ export default function StreamView() {
if (!stream) return <Spinner />;
return (
// add snort and damus relays so zap.stream will always see zaps
<RelaySelectionProvider additionalDefaults={streamRelays}>
<AdditionalRelayProvider relays={streamRelays}>
<UserEmojiProvider pubkey={stream.host}>
{displayMode ? <ChatWidget stream={stream} displayMode={displayMode} /> : <StreamPage stream={stream} />}
</UserEmojiProvider>
</RelaySelectionProvider>
</AdditionalRelayProvider>
);
}

View File

@@ -3,7 +3,6 @@ import { Box, Button, Flex, useToast } from "@chakra-ui/react";
import { useForm } from "react-hook-form";
import { ParsedStream, buildChatMessage } from "../../../../helpers/nostr/stream";
import { useRelaySelectionRelays } from "../../../../providers/local/relay-selection-provider";
import { unique } from "../../../../helpers/array";
import { useSigningContext } from "../../../../providers/global/signing-provider";
import { createEmojiTags, ensureNotifyContentMentions } from "../../../../helpers/nostr/post";
@@ -13,12 +12,14 @@ import StreamZapButton from "../../components/stream-zap-button";
import { nostrBuildUploadImage } from "../../../../helpers/nostr-build";
import { useUserInbox } from "../../../../hooks/use-user-mailboxes";
import { usePublishEvent } from "../../../../providers/global/publish-provider";
import { useReadRelays } from "../../../../hooks/use-client-relays";
import { useAdditionalRelayContext } from "../../../../providers/local/additional-relay-context";
export default function ChatMessageForm({ stream, hideZapButton }: { stream: ParsedStream; hideZapButton?: boolean }) {
const toast = useToast();
const publish = usePublishEvent();
const emojis = useContextEmojis();
const streamRelays = useRelaySelectionRelays();
const streamRelays = useReadRelays(useAdditionalRelayContext());
const hostReadRelays = useUserInbox(stream.host);
const relays = useMemo(() => unique([...streamRelays, ...hostReadRelays]), [hostReadRelays, streamRelays]);

View File

@@ -5,14 +5,15 @@ import { getEventUID } from "../../../../helpers/nostr/events";
import { ParsedStream, STREAM_CHAT_MESSAGE_KIND, getATag } from "../../../../helpers/nostr/stream";
import useTimelineLoader from "../../../../hooks/use-timeline-loader";
import { NostrEvent } from "../../../../types/nostr-event";
import { useRelaySelectionRelays } from "../../../../providers/local/relay-selection-provider";
import useStreamGoal from "../../../../hooks/use-stream-goal";
import { NostrQuery } from "../../../../types/nostr-query";
import useUserMuteFilter from "../../../../hooks/use-user-mute-filter";
import useClientSideMuteFilter from "../../../../hooks/use-client-side-mute-filter";
import { useReadRelays } from "../../../../hooks/use-client-relays";
import { useAdditionalRelayContext } from "../../../../providers/local/additional-relay-context";
export default function useStreamChatTimeline(stream: ParsedStream) {
const streamRelays = useRelaySelectionRelays();
const streamRelays = useReadRelays(useAdditionalRelayContext());
const hostMuteFilter = useUserMuteFilter(stream.host, [], { alwaysRequest: true });
const muteFilter = useClientSideMuteFilter();

View File

@@ -1,56 +0,0 @@
import { Button, Flex, SimpleGrid } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import useSubject from "../../hooks/use-subject";
import { FILE_KIND } from "../../helpers/nostr/files";
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
import RelaySelectionProvider, { useRelaySelectionContext } from "../../providers/local/relay-selection-provider";
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
import VerticalPageLayout from "../../components/vertical-page-layout";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
import Upload01 from "../../components/icons/upload-01";
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
function FilesPage() {
const { listId, filter } = usePeopleListContext();
const { relays } = useRelaySelectionContext();
const timeline = useTimelineLoader(
`${listId}-files`,
relays,
filter && { kinds: [FILE_KIND], "#m": ["model/stl"], ...filter },
);
const files = useSubject(timeline.timeline);
const callback = useTimelineCurserIntersectionCallback(timeline);
return (
<VerticalPageLayout>
<Flex gap="2">
<PeopleListSelection />
<RelaySelectionButton />
<Button as={RouterLink} colorScheme="primary" ml="auto" leftIcon={<Upload01 />} to="/things/upload">
New Thing
</Button>
</Flex>
<IntersectionObserverProvider callback={callback}>
<SimpleGrid minChildWidth="20rem" spacing="2"></SimpleGrid>
</IntersectionObserverProvider>
<TimelineActionAndStatus timeline={timeline} />
</VerticalPageLayout>
);
}
export default function FilesView() {
return (
<PeopleListProvider initList="global">
<RelaySelectionProvider>
<FilesPage />
</RelaySelectionProvider>
</PeopleListProvider>
);
}

View File

@@ -1,43 +0,0 @@
import { useState } from "react";
import { Button, ButtonGroup, Flex, Heading, Image, Text } from "@chakra-ui/react";
import useObjectURL from "../../../hooks/use-object-url";
import BackButton from "../../../components/back-button";
export default function ConfirmStep({
screenshot,
name,
hash,
summary,
onConfirm,
}: {
screenshot: Blob;
name: string;
summary: string;
hash: string;
onConfirm: () => Promise<void>;
}) {
const [loading, setLoading] = useState(false);
const objectURL = useObjectURL(screenshot);
const confirm = async () => {
setLoading(true);
await onConfirm();
setLoading(false);
};
return (
<Flex gap="2" direction="column" maxW="40rem" w="full" mx="auto">
<Image src={objectURL} maxW="2xl" />
<Heading size="md">{name}</Heading>
<Text>File Hash: {hash}</Text>
<Text whiteSpace="pre-line">{summary}</Text>
<ButtonGroup ml="auto">
<BackButton />
<Button onClick={confirm} isLoading={loading} colorScheme="primary">
Upload
</Button>
</ButtonGroup>
</Flex>
);
}

View File

@@ -1,46 +0,0 @@
import { Button, ButtonGroup, Flex, FormControl, FormLabel, Input, Textarea } from "@chakra-ui/react";
import { useForm } from "react-hook-form";
import BackButton from "../../../components/back-button";
type FormValues = {
name: string;
summary: string;
};
export default function DetailsStep({ onSubmit }: { onSubmit: (values: FormValues) => void }) {
const { register, getValues, setValue, handleSubmit, watch } = useForm<FormValues>({
defaultValues: {
name: "",
summary: "",
},
mode: "all",
});
const submit = handleSubmit(onSubmit);
return (
<Flex as="form" onSubmit={submit} gap="2" direction="column" maxW="40rem" w="full" mx="auto">
<FormControl isRequired>
<FormLabel>Name</FormLabel>
<Input {...register("name", { required: true })} placeholder="Thing name" autoComplete="off" />
</FormControl>
<FormControl isRequired>
<FormLabel>Summary</FormLabel>
<Textarea
rows={3}
isRequired
placeholder="A short summary of the thing"
autoComplete="off"
{...register("summary", { required: true })}
/>
</FormControl>
<ButtonGroup ml="auto">
<BackButton />
<Button type="submit" colorScheme="primary">
Next
</Button>
</ButtonGroup>
</Flex>
);
}

View File

@@ -1,99 +0,0 @@
import { useState } from "react";
import {
Box,
Step,
StepIcon,
StepIndicator,
StepNumber,
StepSeparator,
StepStatus,
StepTitle,
Stepper,
} from "@chakra-ui/react";
import VerticalPageLayout from "../../../components/vertical-page-layout";
import { useNavigate } from "react-router-dom";
import useRouteStateValue from "../../../hooks/use-route-state-value";
import SelectFileStep from "./select-file-step";
import DetailsStep from "./details-step";
import PreviewStep from "./preview-step";
import ConfirmStep from "./confirm-step";
const steps = [{ title: "Select File" }, { title: "Details" }, { title: "Upload" }];
export default function ThingUploadView() {
const navigate = useNavigate();
const step = useRouteStateValue("step", 0);
const [file, setFile] = useState<Blob>();
const [fileURL, setFileURL] = useState<string>();
const [hash, setHash] = useState<string>();
const [name, setName] = useState<string>();
const [summary, setSummary] = useState<string>();
const [screenshot, setScreenshot] = useState<Blob>();
const upload = async () => {
// await publish("Post", draft);
};
const renderContent = () => {
switch (step.value) {
case 0:
return (
<SelectFileStep
onSubmit={(values) => {
setFile(values.file);
if (values.fileURL) setFileURL(values.fileURL);
setHash(values.hash);
step.setValue(1, false);
}}
/>
);
case 1:
return (
<DetailsStep
onSubmit={(values) => {
setName(values.name);
setSummary(values.summary);
step.setValue(2, false);
}}
/>
);
case 2:
return (
<PreviewStep
file={file!}
onSubmit={(values) => {
setScreenshot(values.screenshot);
step.setValue(3, false);
}}
/>
);
case 3:
return <ConfirmStep name={name!} hash={hash!} summary={summary!} screenshot={screenshot!} onConfirm={upload} />;
}
};
return (
<VerticalPageLayout>
<Stepper index={step.value}>
{steps.map((step, index) => (
<Step key={index}>
<StepIndicator>
<StepStatus complete={<StepIcon />} incomplete={<StepNumber />} active={<StepNumber />} />
</StepIndicator>
<Box flexShrink="0">
<StepTitle>{step.title}</StepTitle>
{/* {step.description && <StepDescription>{step.description}</StepDescription>} */}
</Box>
<StepSeparator />
</Step>
))}
</Stepper>
{renderContent()}
</VerticalPageLayout>
);
}

View File

@@ -1,54 +0,0 @@
import { useRef } from "react";
import { Button, ButtonGroup, Flex, useToast } from "@chakra-ui/react";
import STLViewer from "../../../components/stl-viewer";
import useObjectURL from "../../../hooks/use-object-url";
import BackButton from "../../../components/back-button";
type FormValues = {
screenshot: Blob;
};
function canvasToBlob(canvas: HTMLCanvasElement, type?: string): Promise<Blob> {
return new Promise((res, rej) => {
canvas.toBlob((blob) => {
if (blob) res(blob);
else rej(new Error("Failed to get blob"));
}, type);
});
}
export default function PreviewStep({ file, onSubmit }: { file: Blob; onSubmit: (values: FormValues) => void }) {
const toast = useToast();
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const takeScreenshot = async () => {
if (!canvasRef.current) return;
try {
const blob = await canvasToBlob(canvasRef.current, "image/jpeg");
onSubmit({ screenshot: blob });
} catch (e) {
if (e instanceof Error) {
toast({ description: e.message, status: "error" });
}
}
};
const previewURL = useObjectURL(file);
return (
<Flex gap="2" direction="column">
{previewURL && (
<>
<STLViewer aspectRatio={16 / 10} url={previewURL} ref={canvasRef} />
<ButtonGroup ml="auto">
<BackButton />
<Button onClick={takeScreenshot} colorScheme="primary">
Take Screenshot
</Button>
</ButtonGroup>
</>
)}
</Flex>
);
}

View File

@@ -1,79 +0,0 @@
import { ChangeEventHandler, useCallback } from "react";
import { Button, ButtonGroup, Divider, Flex, FormControl, FormLabel, Heading, Input } from "@chakra-ui/react";
import { useForm } from "react-hook-form";
import useObjectURL from "../../../hooks/use-object-url";
import { arrayBufferToHex } from "../../../helpers/array";
import BackButton from "../../../components/back-button";
type FormValues = {
file: Blob;
fileURL?: string;
hash: string;
};
// example file https://tonybox.net/objects/keystone/keystone.stl
export default function SelectFileStep({ onSubmit }: { onSubmit: (values: FormValues) => void }) {
const { register, getValues, setValue, handleSubmit, watch, resetField } = useForm<FormValues>({
defaultValues: {
fileURL: "",
},
mode: "all",
});
watch("file");
watch("fileURL");
const handleFileChange = useCallback<ChangeEventHandler<HTMLInputElement>>(
(e) => {
const file = e.target.files?.[0];
if (file) setValue("file", file);
else resetField("file");
},
[setValue, resetField],
);
const submit = handleSubmit(async (values) => {
let file: Blob | undefined = values.file;
if (!file && values.fileURL) file = await fetch(values.fileURL).then((res) => res.blob());
if (!file) throw new Error("Cant access file");
// get file hash
const buffer = await file.arrayBuffer();
const hash = await window.crypto.subtle.digest("SHA-256", buffer);
onSubmit({ hash: arrayBufferToHex(hash), file, fileURL: values.fileURL });
// const pub = await publish("Post", draft);
});
const fileObjectURL = useObjectURL(getValues().file);
const stlURL = fileObjectURL || getValues().fileURL;
let step = 0;
if (getValues().file) step = 1;
return (
<Flex as="form" onSubmit={submit} direction="column" gap="2" maxW="40rem" w="full" mx="auto">
<Heading size="md">Upload File</Heading>
<Input type="file" accept="model/stl" placeholder="Select STL file" onChange={handleFileChange} isDisabled />
<Flex gap="4" alignItems="center" my="4">
<Divider />
<Heading size="sm">OR</Heading>
<Divider />
</Flex>
<Heading size="md">Use Remote File</Heading>
<Input
type="url"
{...register("fileURL", { validate: (str) => str && str.endsWith(".stl"), required: true })}
placeholder="https://example.com/files/things/cube.stl"
onChange={handleFileChange}
autoComplete="off"
/>
<ButtonGroup ml="auto">
<BackButton />
<Button type="submit" colorScheme="primary">
Next
</Button>
</ButtonGroup>
</Flex>
);
}

View File

@@ -25,18 +25,17 @@ import { useUserMetadata } from "../../hooks/use-user-metadata";
import EventStore from "../../classes/event-store";
import NostrRequest from "../../classes/nostr-request";
import { isPTag } from "../../types/nostr-event";
import RelaySelectionProvider, { useRelaySelectionContext } from "../../providers/local/relay-selection-provider";
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
import { useDebounce } from "react-use";
import useSubject from "../../hooks/use-subject";
import { ChevronLeftIcon } from "../../components/icons";
import { useReadRelays } from "../../hooks/use-client-relays";
type NodeType = { id: string; image?: string; name?: string };
function NetworkDMGraphPage() {
const navigate = useNavigate();
const account = useCurrentAccount()!;
const { relays } = useRelaySelectionContext();
const relays = useReadRelays();
const contacts = useUserContactList(account.pubkey);
const contactsPubkeys = useMemo(
@@ -123,7 +122,6 @@ function NetworkDMGraphPage() {
onChange={(e) => setSince(dayjs(e.target.value).unix())}
/>
<Text>Showing all direct messages between contacts in the last {dayjs.unix(since).fromNow(true)}</Text>
<RelaySelectionButton ml="auto" />
</Flex>
<Box overflow="hidden" flex={1}>
<AutoSizer>
@@ -170,9 +168,7 @@ function NetworkDMGraphPage() {
export default function NetworkDMGraphView() {
return (
<RequireCurrentAccount>
<RelaySelectionProvider>
<NetworkDMGraphPage />
</RelaySelectionProvider>
<NetworkDMGraphPage />
</RequireCurrentAccount>
);
}

View File

@@ -6,9 +6,7 @@ import { bytesToHex } from "@noble/hashes/utils";
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
import VerticalPageLayout from "../../components/vertical-page-layout";
import RelaySelectionProvider, { useRelaySelectionContext } from "../../providers/local/relay-selection-provider";
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter";
import { NostrEvent } from "../../types/nostr-event";
@@ -23,6 +21,7 @@ import accountService from "../../services/account";
import signingService from "../../services/signing";
import CategorySelect from "./components/category-select";
import useRouteSearchValue from "../../hooks/use-route-search-value";
import { useReadRelays } from "../../hooks/use-client-relays";
function Warning() {
const navigate = useNavigate();
@@ -60,7 +59,7 @@ function Warning() {
function TorrentsPage() {
const { filter, listId } = usePeopleListContext();
const { relays } = useRelaySelectionContext();
const relays = useReadRelays();
const tagsParam = useRouteSearchValue("tags");
const tags = tagsParam.value?.split(",") ?? [];
@@ -100,7 +99,6 @@ function TorrentsPage() {
<VerticalPageLayout>
{!!account && <Warning />}
<Flex gap="2">
<RelaySelectionButton />
<PeopleListSelection />
<CategorySelect maxW="xs" value={tags.join(",")} onChange={handleTagsChange} />
<Spacer />
@@ -135,10 +133,8 @@ function TorrentsPage() {
export default function TorrentsView() {
return (
<RelaySelectionProvider>
<PeopleListProvider>
<TorrentsPage />
</PeopleListProvider>
</RelaySelectionProvider>
<PeopleListProvider>
<TorrentsPage />
</PeopleListProvider>
);
}

View File

@@ -6,17 +6,18 @@ import { STEMSTR_RELAY, STEMSTR_TRACK_KIND } from "../../helpers/nostr/stemstr";
import useSubject from "../../hooks/use-subject";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
import RelaySelectionProvider, { useRelaySelectionContext } from "../../providers/local/relay-selection-provider";
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
import TrackCard from "./components/track-card";
import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter";
import { NostrEvent } from "../../types/nostr-event";
import { AdditionalRelayProvider, useAdditionalRelayContext } from "../../providers/local/additional-relay-context";
import { useReadRelays } from "../../hooks/use-client-relays";
function TracksPage() {
const { listId, filter } = usePeopleListContext();
const { relays } = useRelaySelectionContext();
const relays = useReadRelays(useAdditionalRelayContext());
const clientMuteFilter = useClientSideMuteFilter();
const eventFilter = useCallback(
@@ -50,9 +51,9 @@ function TracksPage() {
export default function TracksView() {
return (
<PeopleListProvider>
<RelaySelectionProvider additionalDefaults={[STEMSTR_RELAY]}>
<AdditionalRelayProvider relays={[STEMSTR_RELAY]}>
<TracksPage />
</RelaySelectionProvider>
</AdditionalRelayProvider>
</PeopleListProvider>
);
}

View File

@@ -4,9 +4,7 @@ import { Link as RouterLink } from "react-router-dom";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import useSubject from "../../hooks/use-subject";
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
import RelaySelectionProvider, { useRelaySelectionContext } from "../../providers/local/relay-selection-provider";
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
import VerticalPageLayout from "../../components/vertical-page-layout";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
@@ -16,10 +14,11 @@ import { FLARE_VIDEO_KIND } from "../../helpers/nostr/flare";
import VideoCard from "./components/video-card";
import { getEventUID } from "../../helpers/nostr/events";
import { ErrorBoundary } from "../../components/error-boundary";
import { useReadRelays } from "../../hooks/use-client-relays";
function VideosPage() {
const { listId, filter } = usePeopleListContext();
const { relays } = useRelaySelectionContext();
const relays = useReadRelays();
const timeline = useTimelineLoader(`${listId}-videos`, relays, filter && { kinds: [FLARE_VIDEO_KIND], ...filter });
@@ -30,10 +29,6 @@ function VideosPage() {
<VerticalPageLayout>
<Flex gap="2">
<PeopleListSelection />
<RelaySelectionButton />
<Button as={RouterLink} colorScheme="primary" ml="auto" leftIcon={<Upload01 />} to="/things/upload">
New Thing
</Button>
</Flex>
<IntersectionObserverProvider callback={callback}>
@@ -53,9 +48,7 @@ function VideosPage() {
export default function VideosView() {
return (
<PeopleListProvider initList="following">
<RelaySelectionProvider>
<VideosPage />
</RelaySelectionProvider>
<VideosPage />
</PeopleListProvider>
);
}