allow user to reorder media servers

This commit is contained in:
hzrd149 2024-04-27 19:27:33 -05:00
parent 289fff2f29
commit 794193b575
6 changed files with 99 additions and 43 deletions

View File

@ -60,8 +60,8 @@ function PublishAction({ pub }: { pub: NostrPublishAction }) {
return (
<>
<Flex gap="2" alignItems="center" cursor="pointer" onClick={details.onOpen}>
<Text>{pub.label}</Text>
<PublishActionStatusTag ml="auto" pub={pub} />
<Text isTruncated>{pub.label}</Text>
<PublishActionStatusTag ml="auto" pub={pub} flexShrink={0} />
</Flex>
{details.isOpen && (
<Modal isOpen onClose={details.onClose} size="2xl">

View File

@ -1,14 +1,5 @@
import { NostrEvent } from "nostr-tools";
import { safeUrl } from "../parse";
import { BlobDescriptor, BlossomClient, Signer } from "blossom-client-sdk";
export function getServersFromEvent(event: NostrEvent) {
return event.tags
.filter((t) => t[0] === "r")
.map((t) => safeUrl(t[1]))
.filter(Boolean) as string[];
}
export async function uploadFileToServers(servers: string[], file: File, signer: Signer) {
const results: BlobDescriptor[] = [];

View File

@ -0,0 +1,19 @@
import { NostrEvent } from "nostr-tools";
import { safeUrl } from "../parse";
export const USER_MEDIA_SERVERS_KIND = 10063;
export function isServerTag(tag: string[]) {
return (tag[0] === "r" || tag[0] === "server") && tag[1];
}
export function serversEqual(a: string, b: string) {
return new URL(a).hostname === new URL(b).hostname;
}
export function getServersFromEvent(event: NostrEvent) {
return event.tags
.filter(isServerTag)
.map((t) => safeUrl(t[1]))
.filter(Boolean) as string[];
}

View File

@ -7,7 +7,7 @@ import { useSigningContext } from "../providers/global/signing-provider";
import { UseFormGetValues, UseFormSetValue } from "react-hook-form";
import useAppSettings from "./use-app-settings";
import useUsersMediaServers from "./use-user-media-servers";
import { getServersFromEvent, uploadFileToServers } from "../helpers/media-upload/blossom";
import { uploadFileToServers } from "../helpers/media-upload/blossom";
import useCurrentAccount from "./use-current-account";
export function useTextAreaUploadFileWithForm(
@ -31,7 +31,7 @@ export default function useTextAreaUploadFile(
const toast = useToast();
const account = useCurrentAccount();
const { mediaUploadService } = useAppSettings();
const mediaServers = useUsersMediaServers(account?.pubkey);
const { servers: mediaServers } = useUsersMediaServers(account?.pubkey);
const { requestSignature } = useSigningContext();
const insertURL = useCallback(
@ -68,8 +68,8 @@ export default function useTextAreaUploadFile(
const response = await nostrBuildUploadImage(file, requestSignature);
const imageUrl = response.url;
insertURL(imageUrl);
} else if (mediaUploadService === "blossom" && mediaServers) {
const blob = await uploadFileToServers(getServersFromEvent(mediaServers), file, requestSignature);
} else if (mediaUploadService === "blossom" && mediaServers.length) {
const blob = await uploadFileToServers(mediaServers, file, requestSignature);
insertURL(blob.url);
}
} catch (e) {

View File

@ -1,10 +1,16 @@
import { useMemo } from "react";
import { USER_MEDIA_SERVERS_KIND, getServersFromEvent } from "../helpers/nostr/blossom";
import replaceableEventsService, { RequestOptions } from "../services/replaceable-events";
import { useReadRelays } from "./use-client-relays";
import useSubject from "./use-subject";
export default function useUsersMediaServers(pubkey?: string, additionalRelays?: string[], opts?: RequestOptions) {
const readRelays = useReadRelays(additionalRelays);
const sub = pubkey ? replaceableEventsService.requestEvent(readRelays, 10063, pubkey, undefined, opts) : undefined;
const value = useSubject(sub);
return value;
const sub = pubkey
? replaceableEventsService.requestEvent(readRelays, USER_MEDIA_SERVERS_KIND, pubkey, undefined, opts)
: undefined;
const event = useSubject(sub);
const servers = useMemo(() => (event ? getServersFromEvent(event) : []), [event?.id]);
return { event, servers };
}

View File

@ -34,29 +34,42 @@ import DebugEventButton from "../../../components/debug-modal/debug-event-button
import { cloneEvent } from "../../../helpers/nostr/event";
import useAppSettings from "../../../hooks/use-app-settings";
import useAsyncErrorHandler from "../../../hooks/use-async-error-handler";
import { getServersFromEvent } from "../../../helpers/media-upload/blossom";
function serversEqual(a: string, b: string) {
return new URL(a).hostname === new URL(b).hostname;
}
import { USER_MEDIA_SERVERS_KIND, isServerTag, serversEqual } from "../../../helpers/nostr/blossom";
function MediaServersPage() {
const toast = useToast();
const account = useCurrentAccount()!;
const publish = usePublishEvent();
const { mediaUploadService, updateSettings } = useAppSettings();
const mediaServers = useUsersMediaServers(account.pubkey, undefined, { alwaysRequest: true, ignoreCache: true });
const servers = mediaServers ? getServersFromEvent(mediaServers) : [];
const { event, servers } = useUsersMediaServers(account.pubkey, undefined, {
alwaysRequest: true,
ignoreCache: true,
});
const addServer = async (server: string) => {
const draft = cloneEvent(10063, mediaServers);
draft.tags = [...draft.tags, ["r", server]];
const draft = cloneEvent(USER_MEDIA_SERVERS_KIND, event);
draft.tags = [
...draft.tags.filter((t) => !isServerTag(t)),
...servers.map((server) => ["server", server]),
["server", server],
];
await publish("Add media server", draft);
};
const removeServer = async (server: string) => {
const draft = cloneEvent(10063, mediaServers);
draft.tags = draft.tags.filter((t) => t[0] === "r" && !serversEqual(t[1], server));
const draft = cloneEvent(USER_MEDIA_SERVERS_KIND, event);
draft.tags = [
...draft.tags.filter((t) => !isServerTag(t)),
...servers.filter((s) => !serversEqual(s, server)).map((server) => ["server", server]),
];
await publish("Remove media server", draft);
};
const makeDefault = async (server: string) => {
const draft = cloneEvent(USER_MEDIA_SERVERS_KIND, event);
draft.tags = [
...draft.tags.filter((t) => !isServerTag(t)),
["server", server],
...servers.filter((s) => !serversEqual(s, server)).map((server) => ["server", server]),
];
await publish("Remove media server", draft);
};
@ -67,12 +80,21 @@ function MediaServersPage() {
const { register, handleSubmit, reset } = useForm({ defaultValues: { server: "" } });
const [confirmServer, setConfirmServer] = useState("");
const submit = handleSubmit((values) => {
if (mediaServers?.tags.some((t) => t[0] === "r" && serversEqual(t[1], values.server)))
const submit = handleSubmit(async (values) => {
let url = new URL(values.server.startsWith("http") ? values.server : "https://" + values.server).toString();
if (event?.tags.some((t) => isServerTag(t) && serversEqual(t[1], url)))
return toast({ status: "error", description: "Server already in list" });
setConfirmServer(new URL(values.server).toString());
reset();
try {
// test server
const res = await fetch(url);
setConfirmServer(url);
reset();
} catch (error) {
toast({ status: "error", description: "Cant reach server" });
}
});
const [loading, setLoading] = useState(false);
@ -88,7 +110,7 @@ function MediaServersPage() {
<Flex gap="2" alignItems="center">
<BackButton hideFrom="lg" size="sm" />
<Heading size="lg">Media Servers</Heading>
{mediaServers && <DebugEventButton event={mediaServers} size="sm" ml="auto" />}
{event && <DebugEventButton event={event} size="sm" ml="auto" />}
</Flex>
<Text fontStyle="italic" mt="-2">
<Link href="https://github.com/hzrd149/blossom" target="_blank" color="blue.500">
@ -136,14 +158,32 @@ function MediaServersPage() {
)}
<Flex direction="column" gap="2">
{servers.map((server) => (
<Flex gap="2" p="2" alignItems="center" borderWidth="1px" borderRadius="lg" key={server}>
{servers.map((server, i) => (
<Flex
gap="2"
p="2"
alignItems="center"
borderWidth="1px"
borderRadius="lg"
key={server}
borderColor={i === 0 ? "primary.500" : undefined}
>
<MediaServerFavicon server={server} size="sm" />
<Link href={server} target="_blank" color="blue.500" fontSize="lg">
{new URL(server).hostname}
</Link>
<CloseButton ml="auto" onClick={() => removeServer(server)} />
<Button
ml="auto"
variant={i === 0 ? "solid" : "outline"}
colorScheme={i === 0 ? "primary" : undefined}
size="sm"
onClick={() => makeDefault(server)}
isDisabled={i === 0}
>
Default
</Button>
<CloseButton onClick={() => removeServer(server)} />
</Flex>
))}
</Flex>
@ -159,16 +199,16 @@ function MediaServersPage() {
</Flex>
{confirmServer && (
<Modal isOpen onClose={() => setConfirmServer("")} size="full">
<Modal isOpen onClose={() => setConfirmServer("")} size="6xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>Add media server</ModalHeader>
<ModalContent h="calc(100vh - var(--chakra-space-32))">
<ModalHeader p="4">Add media server</ModalHeader>
<ModalCloseButton />
<ModalBody display="flex" p="0" flexDirection="column">
<ModalBody p="0" display="flex" flexDirection="column">
<Box as="iframe" src={confirmServer} w="full" h="full" flex={1} />
</ModalBody>
<ModalFooter>
<ModalFooter p="4">
<Button variant="ghost" mr={3} onClick={() => setConfirmServer("")}>
Cancel
</Button>