import "./stream-editor.css"; import { useEffect, useState, useCallback } from "react"; import { NostrEvent } from "@snort/system"; import { unixNow } from "@snort/shared"; import { TagsInput } from "react-tag-input-component"; import { FormattedMessage, useIntl } from "react-intl"; import AsyncButton from "./async-button"; import { StreamState } from "../index"; import { findTag } from "../utils"; import { useLogin } from "hooks/login"; import { NewGoalDialog } from "element/new-goal"; import { useGoals } from "hooks/goals"; export interface StreamEditorProps { ev?: NostrEvent; onFinish?: (ev: NostrEvent) => void; options?: { canSetTitle?: boolean; canSetSummary?: boolean; canSetImage?: boolean; canSetStatus?: boolean; canSetStream?: boolean; canSetTags?: boolean; canSetContentWarning?: boolean; }; } interface GoalSelectorProps { goal?: string; pubkey: string; onGoalSelect: (g: string) => void; } function GoalSelector({ goal, pubkey, onGoalSelect }: GoalSelectorProps) { const goals = useGoals(pubkey, true); const { formatMessage } = useIntl(); return ( onGoalSelect(ev.target.value)}> {formatMessage({ defaultMessage: "Select a goal..." })} {goals?.map(goal => ( {goal.content} ))} ); } export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) { const [title, setTitle] = useState(""); const [summary, setSummary] = useState(""); const [image, setImage] = useState(""); const [stream, setStream] = useState(""); const [status, setStatus] = useState(""); const [start, setStart] = useState(); const [tags, setTags] = useState([]); const [contentWarning, setContentWarning] = useState(false); const [isValid, setIsValid] = useState(false); const [goal, setGoal] = useState(); const login = useLogin(); const { formatMessage } = useIntl(); useEffect(() => { setTitle(findTag(ev, "title") ?? ""); setSummary(findTag(ev, "summary") ?? ""); setImage(findTag(ev, "image") ?? ""); setStream(findTag(ev, "streaming") ?? ""); setStatus(findTag(ev, "status") ?? StreamState.Live); setStart(findTag(ev, "starts")); setTags(ev?.tags.filter(a => a[0] === "t").map(a => a[1]) ?? []); setContentWarning(findTag(ev, "content-warning") !== undefined); setGoal(findTag(ev, "goal")); }, [ev?.id]); const validate = useCallback(() => { 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; }, [title, image, stream]); useEffect(() => { setIsValid(ev !== undefined || validate()); }, [validate, title, summary, image, stream]); async function publishStream() { const pub = login?.publisher(); if (pub) { const evNew = await pub.generic(eb => { const now = unixNow(); const dTag = findTag(ev, "d") ?? now.toString(); const starts = start ?? now.toString(); const ends = findTag(ev, "ends") ?? now.toString(); eb.kind(30311) .tag(["d", dTag]) .tag(["title", title]) .tag(["summary", summary]) .tag(["image", image]) .tag(["streaming", stream]) .tag(["status", status]) .tag(["starts", starts]); if (status === StreamState.Ended) { eb.tag(["ends", ends]); } for (const tx of tags) { eb.tag(["t", tx.trim()]); } if (contentWarning) { eb.tag(["content-warning", "nsfw"]); } if (goal && goal.length > 0) { eb.tag(["goal", goal]); } return eb; }); console.debug(evNew); onFinish && onFinish(evNew); } } function toDateTimeString(n: number) { return new Date(n * 1000).toISOString().substring(0, -1); } function fromDateTimeString(s: string) { return Math.floor(new Date(s).getTime() / 1000); } return ( <> {ev ? "Edit Stream" : "New Stream"} {(options?.canSetTitle ?? true) && ( setTitle(e.target.value)} /> )} {(options?.canSetSummary ?? true) && ( setSummary(e.target.value)} /> )} {(options?.canSetImage ?? true) && ( setImage(e.target.value)} /> )} {(options?.canSetStream ?? true) && ( setStream(e.target.value)} /> )} {(options?.canSetStatus ?? true) && ( <> {[StreamState.Live, StreamState.Planned, StreamState.Ended].map(v => ( setStatus(v)} key={v}> {v} ))} {status === StreamState.Planned && ( setStart(fromDateTimeString(e.target.value).toString())} /> )} > )} {(options?.canSetTags ?? true) && ( )} {login?.pubkey && ( <> > )} {(options?.canSetContentWarning ?? true) && ( setContentWarning(e.target.checked)} /> )} {ev ? : } > ); }