forked from Kieran/zap.stream
Merge pull request 'fix: redirect to stream after starting it' (#2) from verbiricha/stream:fix/redirect-to-stream into main
Reviewed-on: Kieran/stream#2
This commit is contained in:
commit
d29741e3f2
@ -6,93 +6,115 @@ import AsyncButton from "./async-button";
|
||||
import { System } from "index";
|
||||
import { findTag } from "utils";
|
||||
|
||||
export function NewStream({ ev, onFinish }: { ev?: NostrEvent, onFinish: () => 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);
|
||||
export function NewStream({
|
||||
ev,
|
||||
onFinish,
|
||||
}: {
|
||||
ev?: NostrEvent;
|
||||
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() {
|
||||
if (title.length < 2) {
|
||||
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;
|
||||
function validate() {
|
||||
if (title.length < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
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 (stream.length < 5 || !stream.match(/^https?:\/\/.*\.m3u8?$/i)) {
|
||||
return false;
|
||||
}
|
||||
if (image.length > 0 && !image.match(/^https?:\/\//i)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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>
|
||||
useEffect(() => {
|
||||
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(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>
|
||||
<p>
|
||||
Summary
|
||||
</p>
|
||||
<div className="input">
|
||||
<input type="text" placeholder="A short description of the content" value={summary} onChange={e => setSummary(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p>Summary</p>
|
||||
<div className="input">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="A short description of the content"
|
||||
value={summary}
|
||||
onChange={(e) => setSummary(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
Cover image
|
||||
</p>
|
||||
<div className="input">
|
||||
<input type="text" placeholder="https://" value={image} onChange={e => setImage(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p>Cover image</p>
|
||||
<div className="input">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="https://"
|
||||
value={image}
|
||||
onChange={(e) => setImage(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
Stream Url
|
||||
</p>
|
||||
<div className="input">
|
||||
<input type="text" placeholder="https://" value={stream} onChange={e => setStream(e.target.value)} />
|
||||
</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>
|
||||
<p>Stream Url</p>
|
||||
<div className="input">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="https://"
|
||||
value={stream}
|
||||
onChange={(e) => setStream(e.target.value)}
|
||||
/>
|
||||
</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>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Icon } from "element/icon";
|
||||
import "./layout.css";
|
||||
import { EventPublisher } from "@snort/system";
|
||||
import { EventPublisher, NostrEvent, encodeTLV, NostrPrefix } from "@snort/system";
|
||||
import { Outlet, useNavigate } from "react-router-dom";
|
||||
import AsyncButton from "element/async-button";
|
||||
import { Login } from "index";
|
||||
@ -11,59 +11,79 @@ import { NewStream } from "element/new-stream";
|
||||
import { useState } from "react";
|
||||
|
||||
export function LayoutPage() {
|
||||
const navigate = useNavigate();
|
||||
const login = useLogin();
|
||||
const [newStream, setNewStream] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const login = useLogin();
|
||||
const [newStream, setNewStream] = useState(false);
|
||||
|
||||
async function doLogin() {
|
||||
const pub = await EventPublisher.nip7();
|
||||
if (pub) {
|
||||
Login.loginWithPubkey(pub.pubKey);
|
||||
}
|
||||
async function doLogin() {
|
||||
const pub = await EventPublisher.nip7();
|
||||
if (pub) {
|
||||
Login.loginWithPubkey(pub.pubKey);
|
||||
}
|
||||
}
|
||||
|
||||
function loggedIn() {
|
||||
if (!login) return;
|
||||
function loggedIn() {
|
||||
if (!login) return;
|
||||
|
||||
return <>
|
||||
<button type="button" className="btn btn-primary" onClick={() => setNewStream(true)}>
|
||||
New Stream
|
||||
<Icon name="signal" />
|
||||
</button>
|
||||
<Profile pubkey={login.pubkey} options={{
|
||||
showName: false
|
||||
}} />
|
||||
</>
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
onClick={() => setNewStream(true)}
|
||||
>
|
||||
New Stream
|
||||
<Icon name="signal" />
|
||||
</button>
|
||||
<Profile
|
||||
pubkey={login.pubkey}
|
||||
options={{
|
||||
showName: false,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function loggedOut() {
|
||||
if (login) return;
|
||||
function loggedOut() {
|
||||
if (login) return;
|
||||
|
||||
return <>
|
||||
<AsyncButton type="button" className="btn btn-border" onClick={doLogin}>
|
||||
Login
|
||||
<Icon name="login" />
|
||||
</AsyncButton>
|
||||
</>
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<AsyncButton type="button" className="btn btn-border" onClick={doLogin}>
|
||||
Login
|
||||
<Icon name="login" />
|
||||
</AsyncButton>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return <>
|
||||
<header>
|
||||
<div onClick={() => navigate("/")}>
|
||||
S
|
||||
</div>
|
||||
<div className="input">
|
||||
<input type="text" placeholder="Search" />
|
||||
<Icon name="search" size={15} />
|
||||
</div>
|
||||
<div>
|
||||
{loggedIn()}
|
||||
{loggedOut()}
|
||||
</div>
|
||||
</header>
|
||||
<Outlet />
|
||||
{newStream && <Modal onClose={() => setNewStream(false)} >
|
||||
<NewStream onFinish={() => navigate("/")} />
|
||||
</Modal>}
|
||||
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 (
|
||||
<>
|
||||
<header>
|
||||
<div onClick={() => navigate("/")}>S</div>
|
||||
<div className="input">
|
||||
<input type="text" placeholder="Search" />
|
||||
<Icon name="search" size={15} />
|
||||
</div>
|
||||
<div>
|
||||
{loggedIn()}
|
||||
{loggedOut()}
|
||||
</div>
|
||||
</header>
|
||||
<Outlet />
|
||||
{newStream && (
|
||||
<Modal onClose={() => setNewStream(false)}>
|
||||
<NewStream onFinish={goToStream} />
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user