forked from Kieran/zap.stream
Add planned start time
This commit is contained in:
parent
ec2ef2142b
commit
602b4c5d0d
@ -11,6 +11,7 @@
|
|||||||
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
|
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
|
||||||
"hls.js": "^1.4.6",
|
"hls.js": "^1.4.6",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"moment": "^2.29.4",
|
||||||
"qr-code-styling": "^1.6.0-rc.1",
|
"qr-code-styling": "^1.6.0-rc.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
@ -5,15 +5,9 @@ import { EventPublisher, NostrEvent } from "@snort/system";
|
|||||||
import { unixNow } from "@snort/shared";
|
import { unixNow } from "@snort/shared";
|
||||||
|
|
||||||
import AsyncButton from "./async-button";
|
import AsyncButton from "./async-button";
|
||||||
import { System } from "index";
|
import { StreamState, System } from "index";
|
||||||
import { findTag } from "utils";
|
import { findTag } from "utils";
|
||||||
|
|
||||||
enum StreamState {
|
|
||||||
Live = "live",
|
|
||||||
Ended = "ended",
|
|
||||||
Planned = "planned"
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NewStream({
|
export function NewStream({
|
||||||
ev,
|
ev,
|
||||||
onFinish,
|
onFinish,
|
||||||
@ -26,6 +20,7 @@ export function NewStream({
|
|||||||
const [image, setImage] = useState(findTag(ev, "image") ?? "");
|
const [image, setImage] = useState(findTag(ev, "image") ?? "");
|
||||||
const [stream, setStream] = useState(findTag(ev, "streaming") ?? "");
|
const [stream, setStream] = useState(findTag(ev, "streaming") ?? "");
|
||||||
const [status, setStatus] = useState(findTag(ev, "status") ?? StreamState.Live);
|
const [status, setStatus] = useState(findTag(ev, "status") ?? StreamState.Live);
|
||||||
|
const [start, setStart] = useState(findTag(ev, "starts"));
|
||||||
const [isValid, setIsValid] = useState(false);
|
const [isValid, setIsValid] = useState(false);
|
||||||
|
|
||||||
function validate() {
|
function validate() {
|
||||||
@ -51,7 +46,7 @@ export function NewStream({
|
|||||||
const evNew = await pub.generic((eb) => {
|
const evNew = await pub.generic((eb) => {
|
||||||
const now = unixNow();
|
const now = unixNow();
|
||||||
const dTag = findTag(ev, "d") ?? now.toString();
|
const dTag = findTag(ev, "d") ?? now.toString();
|
||||||
const starts = findTag(ev, "starts") ?? now.toString();
|
const starts = start ?? now.toString();
|
||||||
const ends = findTag(ev, "ends") ?? now.toString();
|
const ends = findTag(ev, "ends") ?? now.toString();
|
||||||
eb
|
eb
|
||||||
.kind(30_311)
|
.kind(30_311)
|
||||||
@ -73,6 +68,16 @@ export function NewStream({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toDateTimeString(n: number) {
|
||||||
|
console.debug(n);
|
||||||
|
return new Date(n * 1000).toISOString().substring(0, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromDateTimeString(s: string) {
|
||||||
|
console.debug(s);
|
||||||
|
return Math.floor(new Date(s).getTime() / 1000)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="new-stream">
|
<div className="new-stream">
|
||||||
<h3>{ev ? "Edit Stream" : "New Stream"}</h3>
|
<h3>{ev ? "Edit Stream" : "New Stream"}</h3>
|
||||||
@ -129,6 +134,12 @@ export function NewStream({
|
|||||||
</span>)}
|
</span>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{status === StreamState.Planned && <div>
|
||||||
|
<p>Start Time</p>
|
||||||
|
<div className="input">
|
||||||
|
<input type="datetime-local" value={toDateTimeString(Number(start ?? "0"))} onChange={e => setStart(fromDateTimeString(e.target.value).toString())} />
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
<div>
|
<div>
|
||||||
<AsyncButton
|
<AsyncButton
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -4,9 +4,12 @@ import { useRequestBuilder } from "@snort/system-react";
|
|||||||
|
|
||||||
import { System } from "index";
|
import { System } from "index";
|
||||||
|
|
||||||
export default function useEventFeed(link: NostrLink) {
|
export default function useEventFeed(link: NostrLink, leaveOpen = false) {
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
const b = new RequestBuilder(`event:${link.id.slice(0, 12)}`);
|
const b = new RequestBuilder(`event:${link.id.slice(0, 12)}`);
|
||||||
|
b.withOptions({
|
||||||
|
leaveOpen
|
||||||
|
})
|
||||||
if (link.type === NostrPrefix.Address) {
|
if (link.type === NostrPrefix.Address) {
|
||||||
const f = b.withFilter().tag("d", [link.id]);
|
const f = b.withFilter().tag("d", [link.id]);
|
||||||
if (link.author) {
|
if (link.author) {
|
||||||
|
@ -83,7 +83,7 @@ a {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"], textarea {
|
input[type="text"], textarea, input[type="datetime-local"] {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
border: unset;
|
border: unset;
|
||||||
background-color: unset;
|
background-color: unset;
|
||||||
|
@ -11,6 +11,12 @@ import { StreamPage } from "pages/stream-page";
|
|||||||
import { ChatPopout } from "pages/chat-popout";
|
import { ChatPopout } from "pages/chat-popout";
|
||||||
import { LoginStore } from "login";
|
import { LoginStore } from "login";
|
||||||
|
|
||||||
|
export enum StreamState {
|
||||||
|
Live = "live",
|
||||||
|
Ended = "ended",
|
||||||
|
Planned = "planned"
|
||||||
|
}
|
||||||
|
|
||||||
export const System = new NostrSystem({});
|
export const System = new NostrSystem({});
|
||||||
export const Login = new LoginStore();
|
export const Login = new LoginStore();
|
||||||
|
|
||||||
|
@ -15,4 +15,10 @@
|
|||||||
.video-grid {
|
.video-grid {
|
||||||
grid-template-columns: repeat(8, 1fr);
|
grid-template-columns: repeat(8, 1fr);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.homepage h2 {
|
||||||
|
|
||||||
|
background: #171717;
|
||||||
|
padding: 40px;
|
||||||
}
|
}
|
@ -4,14 +4,16 @@ import { useMemo } from "react";
|
|||||||
import { unixNow } from "@snort/shared";
|
import { unixNow } from "@snort/shared";
|
||||||
import { EventKind, ParameterizedReplaceableNoteStore, RequestBuilder } from "@snort/system";
|
import { EventKind, ParameterizedReplaceableNoteStore, RequestBuilder } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
import { System } from "..";
|
import { StreamState, System } from "..";
|
||||||
import { VideoTile } from "../element/video-tile";
|
import { VideoTile } from "../element/video-tile";
|
||||||
import { findTag } from "utils";
|
import { findTag } from "utils";
|
||||||
|
|
||||||
export function RootPage() {
|
export function RootPage() {
|
||||||
const rb = useMemo(() => {
|
const rb = useMemo(() => {
|
||||||
const rb = new RequestBuilder("root");
|
const rb = new RequestBuilder("root");
|
||||||
rb.withFilter()
|
rb.withOptions({
|
||||||
|
leaveOpen: true
|
||||||
|
}).withFilter()
|
||||||
.kinds([30_311 as EventKind])
|
.kinds([30_311 as EventKind])
|
||||||
.since(unixNow() - 86400);
|
.since(unixNow() - 86400);
|
||||||
return rb;
|
return rb;
|
||||||
@ -32,7 +34,21 @@ export function RootPage() {
|
|||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}, [feed.data])
|
}, [feed.data])
|
||||||
return <div className="video-grid">
|
|
||||||
{feedSorted.map(e => <VideoTile ev={e} key={e.id} />)}
|
const live = feedSorted.filter(a => findTag(a, "status") === StreamState.Live);
|
||||||
|
const planned = feedSorted.filter(a => findTag(a, "status") === StreamState.Planned);
|
||||||
|
const ended = feedSorted.filter(a => findTag(a, "status") === StreamState.Ended);
|
||||||
|
return <div className="homepage">
|
||||||
|
<div className="video-grid">
|
||||||
|
{live.map(e => <VideoTile ev={e} key={e.id} />)}
|
||||||
|
</div>
|
||||||
|
{planned.length > 0 && <><h2>Planned</h2>
|
||||||
|
<div className="video-grid">
|
||||||
|
{planned.map(e => <VideoTile ev={e} key={e.id} />)}
|
||||||
|
</div></>}
|
||||||
|
{ended.length > 0 && <><h2>Ended</h2>
|
||||||
|
<div className="video-grid">
|
||||||
|
{ended.map(e => <VideoTile ev={e} key={e.id} />)}
|
||||||
|
</div></>}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
@ -26,11 +26,11 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
color: #A7A7A7;
|
color: #A7A7A7;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.live-page .pill.live {
|
.live-page .pill.live {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.live-page .info {
|
.live-page .info {
|
||||||
|
@ -2,6 +2,7 @@ import "./stream-page.css";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { parseNostrLink, EventPublisher } from "@snort/system";
|
import { parseNostrLink, EventPublisher } from "@snort/system";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
import useEventFeed from "hooks/event-feed";
|
import useEventFeed from "hooks/event-feed";
|
||||||
import { LiveVideoPlayer } from "element/live-video-player";
|
import { LiveVideoPlayer } from "element/live-video-player";
|
||||||
@ -11,7 +12,7 @@ import { LiveChat } from "element/live-chat";
|
|||||||
import AsyncButton from "element/async-button";
|
import AsyncButton from "element/async-button";
|
||||||
import { Icon } from "element/icon";
|
import { Icon } from "element/icon";
|
||||||
import { useLogin } from "hooks/login";
|
import { useLogin } from "hooks/login";
|
||||||
import { System } from "index";
|
import { StreamState, System } from "index";
|
||||||
import Modal from "element/modal";
|
import Modal from "element/modal";
|
||||||
import { SendZaps } from "element/send-zap";
|
import { SendZaps } from "element/send-zap";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
@ -20,7 +21,7 @@ import { NewStream } from "element/new-stream";
|
|||||||
export function StreamPage() {
|
export function StreamPage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const link = parseNostrLink(params.id!);
|
const link = parseNostrLink(params.id!);
|
||||||
const thisEvent = useEventFeed(link);
|
const thisEvent = useEventFeed(link, true);
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [zap, setZap] = useState(false);
|
const [zap, setZap] = useState(false);
|
||||||
@ -30,6 +31,7 @@ export function StreamPage() {
|
|||||||
const stream = findTag(thisEvent.data, "streaming");
|
const stream = findTag(thisEvent.data, "streaming");
|
||||||
const status = findTag(thisEvent.data, "status");
|
const status = findTag(thisEvent.data, "status");
|
||||||
const image = findTag(thisEvent.data, "image");
|
const image = findTag(thisEvent.data, "image");
|
||||||
|
const start = findTag(thisEvent.data, "starts");
|
||||||
const isLive = status === "live";
|
const isLive = status === "live";
|
||||||
const isMine = link.author === login?.pubkey;
|
const isMine = link.author === login?.pubkey;
|
||||||
const zapTarget = profile?.lud16 ?? profile?.lud06;
|
const zapTarget = profile?.lud16 ?? profile?.lud06;
|
||||||
@ -54,6 +56,7 @@ export function StreamPage() {
|
|||||||
<p>{findTag(thisEvent.data, "summary")}</p>
|
<p>{findTag(thisEvent.data, "summary")}</p>
|
||||||
<div className="tags">
|
<div className="tags">
|
||||||
<span className={`pill${isLive ? " live" : ""}`}>{status}</span>
|
<span className={`pill${isLive ? " live" : ""}`}>{status}</span>
|
||||||
|
{status === StreamState.Planned && <span className="pill">Starts {moment(Number(start) * 1000).fromNow()}</span>}
|
||||||
{thisEvent.data?.tags
|
{thisEvent.data?.tags
|
||||||
.filter((a) => a[0] === "t")
|
.filter((a) => a[0] === "t")
|
||||||
.map((a) => a[1])
|
.map((a) => a[1])
|
||||||
@ -111,7 +114,7 @@ export function StreamPage() {
|
|||||||
<Modal onClose={() => setEdit(false)}>
|
<Modal onClose={() => setEdit(false)}>
|
||||||
<NewStream
|
<NewStream
|
||||||
ev={thisEvent.data}
|
ev={thisEvent.data}
|
||||||
onFinish={() => window.location.reload()}
|
onFinish={() => { }}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
|
@ -6574,6 +6574,11 @@ mkdirp@~0.5.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimist "^1.2.6"
|
minimist "^1.2.6"
|
||||||
|
|
||||||
|
moment@^2.29.4:
|
||||||
|
version "2.29.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
|
||||||
|
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
Loading…
Reference in New Issue
Block a user