feat: media server list
This commit is contained in:
parent
090e41a911
commit
662c267da1
66
src/element/upload/server-list.tsx
Normal file
66
src/element/upload/server-list.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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)",
|
||||
|
Loading…
x
Reference in New Issue
Block a user