Merge branch 'main' into emotes
This commit is contained in:
@ -6,93 +6,115 @@ import AsyncButton from "./async-button";
|
|||||||
import { System } from "index";
|
import { System } from "index";
|
||||||
import { findTag } from "utils";
|
import { findTag } from "utils";
|
||||||
|
|
||||||
export function NewStream({ ev, onFinish }: { ev?: NostrEvent, onFinish: () => void }) {
|
export function NewStream({
|
||||||
const [title, setTitle] = useState(findTag(ev, "title") ?? "");
|
ev,
|
||||||
const [summary, setSummary] = useState(findTag(ev, "summary") ?? "");
|
onFinish,
|
||||||
const [image, setImage] = useState(findTag(ev, "image") ?? "");
|
}: {
|
||||||
const [stream, setStream] = useState(findTag(ev, "streaming") ?? "");
|
ev?: NostrEvent;
|
||||||
const [isValid, setIsValid] = useState(false);
|
onFinish: (ev: NostrEvent) => void;
|
||||||
|
}) {
|
||||||
|
const [title, setTitle] = useState(findTag(ev, "title") ?? "");
|
||||||
|
const [summary, setSummary] = useState(findTag(ev, "summary") ?? "");
|
||||||
|
const [image, setImage] = useState(findTag(ev, "image") ?? "");
|
||||||
|
const [stream, setStream] = useState(findTag(ev, "streaming") ?? "");
|
||||||
|
const [isValid, setIsValid] = useState(false);
|
||||||
|
|
||||||
function validate() {
|
function validate() {
|
||||||
if (title.length < 2) {
|
if (title.length < 2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
if (stream.length < 5 || !stream.match(/^https?:\/\/.*\.m3u8?$/i)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (image.length > 0 && !image.match(/^https?:\/\//i)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
if (stream.length < 5 || !stream.match(/^https?:\/\/.*\.m3u8?$/i)) {
|
||||||
useEffect(() => {
|
return false;
|
||||||
setIsValid(validate());
|
|
||||||
}, [title, summary, image, stream]);
|
|
||||||
|
|
||||||
async function publishStream() {
|
|
||||||
const pub = await EventPublisher.nip7();
|
|
||||||
if (pub) {
|
|
||||||
const evNew = await pub.generic(eb => {
|
|
||||||
const now = unixNow();
|
|
||||||
const dTag = findTag(ev, "d") ?? now.toString();
|
|
||||||
return eb.kind(30_311)
|
|
||||||
.tag(["d", dTag])
|
|
||||||
.tag(["title", title])
|
|
||||||
.tag(["summary", summary])
|
|
||||||
.tag(["image", image])
|
|
||||||
.tag(["streaming", stream])
|
|
||||||
.tag(["status", "live"])
|
|
||||||
});
|
|
||||||
console.debug(evNew);
|
|
||||||
System.BroadcastEvent(evNew);
|
|
||||||
onFinish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (image.length > 0 && !image.match(/^https?:\/\//i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="new-stream">
|
useEffect(() => {
|
||||||
<h3>
|
setIsValid(validate());
|
||||||
{ev ? "Edit Stream" : "New Stream"}
|
}, [title, summary, image, stream]);
|
||||||
</h3>
|
|
||||||
<div>
|
async function publishStream() {
|
||||||
<p>
|
const pub = await EventPublisher.nip7();
|
||||||
Title
|
if (pub) {
|
||||||
</p>
|
const evNew = await pub.generic((eb) => {
|
||||||
<div className="input">
|
const now = unixNow();
|
||||||
<input type="text" placeholder="What are we steaming today?" value={title} onChange={e => setTitle(e.target.value)} />
|
const dTag = findTag(ev, "d") ?? now.toString();
|
||||||
</div>
|
return eb
|
||||||
|
.kind(30_311)
|
||||||
|
.tag(["d", dTag])
|
||||||
|
.tag(["title", title])
|
||||||
|
.tag(["summary", summary])
|
||||||
|
.tag(["image", image])
|
||||||
|
.tag(["streaming", stream])
|
||||||
|
.tag(["status", "live"]);
|
||||||
|
});
|
||||||
|
console.debug(evNew);
|
||||||
|
System.BroadcastEvent(evNew);
|
||||||
|
onFinish(evNew);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="new-stream">
|
||||||
|
<h3>{ev ? "Edit Stream" : "New Stream"}</h3>
|
||||||
|
<div>
|
||||||
|
<p>Title</p>
|
||||||
|
<div className="input">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="What are we steaming today?"
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<p>
|
<div>
|
||||||
Summary
|
<p>Summary</p>
|
||||||
</p>
|
<div className="input">
|
||||||
<div className="input">
|
<input
|
||||||
<input type="text" placeholder="A short description of the content" value={summary} onChange={e => setSummary(e.target.value)} />
|
type="text"
|
||||||
</div>
|
placeholder="A short description of the content"
|
||||||
|
value={summary}
|
||||||
|
onChange={(e) => setSummary(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<p>
|
<div>
|
||||||
Cover image
|
<p>Cover image</p>
|
||||||
</p>
|
<div className="input">
|
||||||
<div className="input">
|
<input
|
||||||
<input type="text" placeholder="https://" value={image} onChange={e => setImage(e.target.value)} />
|
type="text"
|
||||||
</div>
|
placeholder="https://"
|
||||||
|
value={image}
|
||||||
|
onChange={(e) => setImage(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<p>
|
<div>
|
||||||
Stream Url
|
<p>Stream Url</p>
|
||||||
</p>
|
<div className="input">
|
||||||
<div className="input">
|
<input
|
||||||
<input type="text" placeholder="https://" value={stream} onChange={e => setStream(e.target.value)} />
|
type="text"
|
||||||
</div>
|
placeholder="https://"
|
||||||
<small>
|
value={stream}
|
||||||
Stream type should be HLS
|
onChange={(e) => setStream(e.target.value)}
|
||||||
</small>
|
/>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<AsyncButton type="button" className="btn btn-primary" disabled={!isValid} onClick={publishStream}>
|
|
||||||
{ev ? "Save" : "Start Stream"}
|
|
||||||
</AsyncButton>
|
|
||||||
</div>
|
</div>
|
||||||
|
<small>Stream type should be HLS</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<AsyncButton
|
||||||
|
type="button"
|
||||||
|
className="btn btn-primary"
|
||||||
|
disabled={!isValid}
|
||||||
|
onClick={publishStream}
|
||||||
|
>
|
||||||
|
{ev ? "Save" : "Start Stream"}
|
||||||
|
</AsyncButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
@ -1,6 +1,11 @@
|
|||||||
import { Icon } from "element/icon";
|
import { Icon } from "element/icon";
|
||||||
import "./layout.css";
|
import "./layout.css";
|
||||||
import { EventPublisher } from "@snort/system";
|
import {
|
||||||
|
EventPublisher,
|
||||||
|
NostrEvent,
|
||||||
|
encodeTLV,
|
||||||
|
NostrPrefix,
|
||||||
|
} from "@snort/system";
|
||||||
import { Outlet, useNavigate } from "react-router-dom";
|
import { Outlet, useNavigate } from "react-router-dom";
|
||||||
import AsyncButton from "element/async-button";
|
import AsyncButton from "element/async-button";
|
||||||
import { Login } from "index";
|
import { Login } from "index";
|
||||||
@ -58,6 +63,19 @@ export function LayoutPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goToStream(ev: NostrEvent) {
|
||||||
|
const d = ev.tags.find((t) => t.at(0) === "d")?.at(1) || "";
|
||||||
|
const naddr = encodeTLV(
|
||||||
|
NostrPrefix.Address,
|
||||||
|
d,
|
||||||
|
undefined,
|
||||||
|
ev.kind,
|
||||||
|
ev.pubkey
|
||||||
|
);
|
||||||
|
navigate(`/live/${naddr}`);
|
||||||
|
setNewStream(false);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<header>
|
<header>
|
||||||
@ -74,7 +92,7 @@ export function LayoutPage() {
|
|||||||
<Outlet />
|
<Outlet />
|
||||||
{newStream && (
|
{newStream && (
|
||||||
<Modal onClose={() => setNewStream(false)}>
|
<Modal onClose={() => setNewStream(false)}>
|
||||||
<NewStream onFinish={() => navigate("/")} />
|
<NewStream onFinish={goToStream} />
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -9,10 +9,13 @@ import { VideoTile } from "../element/video-tile";
|
|||||||
import { findTag } from "utils";
|
import { findTag } from "utils";
|
||||||
|
|
||||||
export function RootPage() {
|
export function RootPage() {
|
||||||
const rb = new RequestBuilder("root");
|
const rb = useMemo(() => {
|
||||||
rb.withFilter()
|
const rb = new RequestBuilder("root");
|
||||||
.kinds([30_311 as EventKind])
|
rb.withFilter()
|
||||||
.since(unixNow() - 86400);
|
.kinds([30_311 as EventKind])
|
||||||
|
.since(unixNow() - 86400);
|
||||||
|
return rb;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const feed = useRequestBuilder<ParameterizedReplaceableNoteStore>(System, ParameterizedReplaceableNoteStore, rb);
|
const feed = useRequestBuilder<ParameterizedReplaceableNoteStore>(System, ParameterizedReplaceableNoteStore, rb);
|
||||||
const feedSorted = useMemo(() => {
|
const feedSorted = useMemo(() => {
|
||||||
|
Reference in New Issue
Block a user