feat: media server list

This commit is contained in:
kieran 2024-06-19 10:33:26 +01:00
parent 090e41a911
commit 662c267da1
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
5 changed files with 127 additions and 44 deletions

View File

@ -0,0 +1,66 @@
import { useMediaServerList } from "@/hooks/media-servers";
import { IconButton, PrimaryButton } from "../buttons";
import { FormattedMessage } from "react-intl";
import { useState } from "react";
import { sanitizeRelayUrl } from "@snort/shared";
export function ServerList() {
const [newServer, setNewServer] = useState("");
const defaultServers = [
//"https://media.zap.stream",
"https://nostr.build/",
"https://nostrcheck.me/",
"https://files.v0l.io/",
];
const servers = useMediaServerList();
async function tryAddServer(s: string) {
await servers.addServer(s);
}
async function tryRemoveServer(s: string) {
await servers.removeServer(s);
}
return (
<div className="flex flex-col gap-2">
<h3>
<FormattedMessage defaultMessage="Media Server List" />
</h3>
{servers.servers.map(a => (
<div className="flex items-center justify-between py-2 px-3 bg-layer-2 rounded-xl" key={a}>
{a}
<IconButton iconName="x" className="text-warning" iconSize={16} onClick={() => tryRemoveServer(a)} />
</div>
))}
<h4>
<FormattedMessage defaultMessage="Add server" />
</h4>
<div className="flex gap-2">
<input
type="text"
placeholder="https://my-media-server.com"
value={newServer}
onChange={e => setNewServer(e.target.value)}
/>
<PrimaryButton disabled={sanitizeRelayUrl(newServer) === undefined} onClick={() => tryAddServer(newServer)}>
<FormattedMessage defaultMessage="Add" />
</PrimaryButton>
</div>
<h4>
<FormattedMessage defaultMessage="Suggested Servers" />
</h4>
{defaultServers
.filter(a => !servers.servers.includes(a))
.map(a => (
<div className="flex items-center justify-between py-2 px-3 bg-layer-2 rounded-xl" key={a}>
{a}
<PrimaryButton disabled={sanitizeRelayUrl(a) === undefined} onClick={() => tryAddServer(a)}>
<FormattedMessage defaultMessage="Add" />
</PrimaryButton>
</div>
))}
</div>
);
}

View File

@ -1,18 +1,31 @@
import { RequestBuilder } from "@snort/system";
import { EventKind, UnknownTag } from "@snort/system";
import { useLogin } from "./login";
import { useRequestBuilder } from "@snort/system-react";
import { useMemo } from "react";
import { removeUndefined, sanitizeRelayUrl } from "@snort/shared";
import { Nip96Uploader } from "@/service/upload/nip96";
export function useMediaServerList() {
const login = useLogin();
const sub = useMemo(() => {
if (!login?.pubkey) return;
const servers = login?.state?.getList(EventKind.StorageServerList) ?? [];
const rb = new RequestBuilder(`media-servers:${login.pubkey}`);
rb.withFilter().kinds([10_096]).authors([login.pubkey]);
return rb;
}, [login?.pubkey]);
return {
servers: removeUndefined(servers.map(a => a.toEventTag()))
.filter(a => a[0] === "server")
.map(a => a[1]),
addServer: async (s: string) => {
const pub = login?.publisher();
if (!pub) return;
return useRequestBuilder(sub);
const u = sanitizeRelayUrl(s);
if (!u) return;
const server = new Nip96Uploader(u, pub);
await server.loadInfo();
await login?.state?.addToList(EventKind.StorageServerList, new UnknownTag(["server", u]), true);
},
removeServer: async (s: string) => {
const u = sanitizeRelayUrl(s);
if (!u) return;
await login?.state?.removeFromList(EventKind.StorageServerList, new UnknownTag(["server", u]), true);
},
};
}

View File

@ -318,12 +318,18 @@
"Ht231k": {
"defaultMessage": "Forwarding"
},
"HvUhXt": {
"defaultMessage": "Uploading to {n} servers"
},
"I1kjHI": {
"defaultMessage": "Supports {markdown}"
},
"I2AKfn": {
"defaultMessage": "Gaming"
},
"IDqXmC": {
"defaultMessage": "Media Server List"
},
"IJDKz3": {
"defaultMessage": "Zap amount in {currency}"
},
@ -411,6 +417,9 @@
"OKhRC6": {
"defaultMessage": "Share"
},
"OadZli": {
"defaultMessage": "Manage Servers"
},
"ObZZEz": {
"defaultMessage": "No clips yet"
},
@ -697,6 +706,9 @@
"hzSNj4": {
"defaultMessage": "Dashboard"
},
"iKlloU": {
"defaultMessage": "Suggested Servers"
},
"ieGrWo": {
"defaultMessage": "Follow"
},
@ -751,9 +763,6 @@
"kp0NPF": {
"defaultMessage": "Planned"
},
"lBm7CY": {
"defaultMessage": "Upload to:"
},
"lXbG97": {
"defaultMessage": "Most Zapped Streamers"
},
@ -802,6 +811,9 @@
"p4N05H": {
"defaultMessage": "Upload"
},
"pOeEMx": {
"defaultMessage": "Add server"
},
"pxF+t0": {
"defaultMessage": "Popular"
},

View File

@ -1,8 +1,10 @@
import { VIDEO_KIND } from "@/const";
import { DefaultButton, IconButton, PrimaryButton, WarningButton } from "@/element/buttons";
import { DefaultButton, IconButton, Layer3Button, PrimaryButton, WarningButton } from "@/element/buttons";
import { Icon } from "@/element/icon";
import Modal from "@/element/modal";
import { Profile } from "@/element/profile";
import Spinner from "@/element/spinner";
import { ServerList } from "@/element/upload/server-list";
import useImgProxy from "@/hooks/img-proxy";
import { useLogin } from "@/hooks/login";
import { useMediaServerList } from "@/hooks/media-servers";
@ -166,26 +168,18 @@ export function UploadPage() {
const { formatMessage } = useIntl();
const login = useLogin();
const system = useContext(SnortContext);
const [selectedServers, setSelectedServers] = useState<Array<string>>([]);
const [error, setError] = useState<Array<string>>([]);
const [title, setTitle] = useState("");
const [summary, setSummary] = useState("");
const [thumb, setThumb] = useState("");
const [editServers, setEditServers] = useState(false);
const { proxy } = useImgProxy();
const uploads = useSyncExternalStore(
c => manager.hook(c),
() => manager.snapshot(),
);
const navigate = useNavigate();
const servers = useMediaServerList()?.at(0) ?? {
tags: [
//["server", "https://media.zap.stream"],
["server", "https://files.v0l.io"],
["server", "https://nostrcheck.me"],
["server", "https://nostr.build"],
],
};
const servers = useMediaServerList();
function canPublish() {
return error.length == 0 && uploads.length > 0 && uploads.every(a => a.result !== undefined);
@ -215,7 +209,7 @@ export function UploadPage() {
const pub = login?.publisher();
const f = await openFile();
if (f && pub) {
selectedServers.forEach(b => manager.uploadTo(b, f, pub, "video"));
servers.servers.forEach(b => manager.uploadTo(b, f, pub, "video"));
}
}
@ -233,7 +227,7 @@ export function UploadPage() {
const data = await rsp.blob();
const pub = login?.publisher();
if (pub) {
selectedServers.forEach(b => manager.uploadTo(b, new File([data], "thumb.jpg"), pub, "thumb"));
servers.servers.forEach(b => manager.uploadTo(b, new File([data], "thumb.jpg"), pub, "thumb"));
}
}
}
@ -243,7 +237,7 @@ export function UploadPage() {
if (f) {
const pub = login?.publisher();
if (pub) {
selectedServers.forEach(b => manager.uploadTo(b, f, pub, "thumb"));
servers.servers.forEach(b => manager.uploadTo(b, f, pub, "thumb"));
}
}
}
@ -313,22 +307,11 @@ export function UploadPage() {
return (
<div className="max-xl:w-full xl:w-[1200px] xl:mx-auto grid gap-6 xl:grid-cols-[auto_350px] max-xl:px-4">
<div className="flex flex-col gap-6">
<div>
<p>
<FormattedMessage defaultMessage="Upload to:" />
</p>
<select multiple={true} onChange={e => setSelectedServers([...e.target.selectedOptions].map(a => a.value))}>
{servers?.tags.map(a => {
const url = a[1];
if (url && a[0] === "server") {
return (
<option selected={selectedServers.includes(url)} key={url}>
{url}
</option>
);
}
})}
</select>
<div className="flex justify-between items-center bg-layer-2 rounded-xl px-3 py-2">
<FormattedMessage defaultMessage="Uploading to {n} servers" values={{ n: servers.servers.length }} />
<Layer3Button onClick={() => setEditServers(true)}>
<FormattedMessage defaultMessage="Manage Servers" />
</Layer3Button>
</div>
<div
onClick={() => uploadFile()}
@ -419,6 +402,11 @@ export function UploadPage() {
<pre className="text-xs font-mono overflow-wrap text-pretty">
{JSON.stringify(manager.makeIMeta(), undefined, 2)}
</pre>
{editServers && (
<Modal id="server-list" onClose={() => setEditServers(false)}>
<ServerList />
</Modal>
)}
</div>
);
}

View File

@ -105,8 +105,10 @@
"HDgXO/": "zap.stream is an open source platform powered by the nostr protocol. There are no giant corporations or giant funds available to provide free streaming.",
"HsgeUk": "Come check out my stream on zap.stream! {link}",
"Ht231k": "Forwarding",
"HvUhXt": "Uploading to {n} servers",
"I1kjHI": "Supports {markdown}",
"I2AKfn": "Gaming",
"IDqXmC": "Media Server List",
"IJDKz3": "Zap amount in {currency}",
"INlWvJ": "OR",
"IPXhUW": "Last Stream Summary",
@ -136,6 +138,7 @@
"O7AeYh": "Description..",
"OEW7yJ": "Zaps",
"OKhRC6": "Share",
"OadZli": "Manage Servers",
"ObZZEz": "No clips yet",
"OkXMLE": "Max Audio Bitrate",
"OumUwz": "{m}s {ago}",
@ -229,6 +232,7 @@
"heyxZL": "Enable text to speech",
"hpl4BP": "Chat Widget",
"hzSNj4": "Dashboard",
"iKlloU": "Suggested Servers",
"ieGrWo": "Follow",
"ieKb+k": "What does it cost to stream?",
"ipTKP3": "Chat Popout",
@ -247,7 +251,6 @@
"kc5EOy": "Username is too long",
"khJ51Q": "Stream Earnings",
"kp0NPF": "Planned",
"lBm7CY": "Upload to:",
"lXbG97": "Most Zapped Streamers",
"lZpRMR": "Check here if this stream contains nudity or pornographic content.",
"ljmS5P": "Endpoint",
@ -264,6 +267,7 @@
"oHPB8Q": "Zap {name}",
"oZrFyI": "Stream type should be HLS",
"p4N05H": "Upload",
"pOeEMx": "Add server",
"pxF+t0": "Popular",
"q+zTWM": "<s>{person}</s> zapped <s>{amount}</s> sats",
"q9ryv4": "Cover image URL (optional)",