Update live stream page

This commit is contained in:
Kieran 2023-06-19 14:16:02 +01:00
parent 9f00157bdc
commit fc2304c9fa
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
7 changed files with 152 additions and 20 deletions

View File

@ -1,11 +1,46 @@
.live-chat {
height: calc(100vh - 73px);
height: 100%;
display: flex;
flex-direction: column;
}
.live-chat > div:nth-child(1) {
font-size: 24px;
line-height: 29px;
font-weight: bold;
}
.live-chat > div:nth-child(2) {
flex-grow: 1;
display: flex;
gap: 16px;
flex-direction: column-reverse;
margin-block-end: 20px;
}
.live-chat > div:nth-child(3) {
display: flex;
gap: 10px;
}
.live-chat .message {
display: inline-flex;
align-items: center;
gap: 8px;
}
.live-chat .message .name {
display: inline-flex;
align-items: center;
color: var(--highlight);
font-weight: 600;
cursor: pointer;
user-select: none;
}
.live-chat .message .avatar {
width: 24px;
height: 24px;
display: inline-block;
margin-right: 8px;
}

View File

@ -1,30 +1,47 @@
import "./LiveChat.css";
import { NostrLink, TaggedRawEvent } from "@snort/system";
import { EventKind, NostrLink, TaggedRawEvent } from "@snort/system";
import { useUserProfile } from "@snort/system-react";
import { useState } from "react";
import Textarea from "./Textarea";
import { useNavigate } from "react-router-dom";
import { FormattedMessage, useIntl } from "react-intl";
import Textarea from "Element/Textarea";
import { useLiveChatFeed } from "Feed/LiveChatFeed";
import useEventPublisher from "Feed/EventPublisher";
import { System } from "index";
import { getDisplayName } from "Element/ProfileImage";
import Avatar from "Element/Avatar";
import AsyncButton from "Element/AsyncButton";
import Text from "Element/Text";
import { System } from "index";
import { profileLink } from "SnortUtils";
export function LiveChat({ ev, link }: { ev: TaggedRawEvent; link: NostrLink }) {
const [chat, setChat] = useState("");
const messages = useLiveChatFeed(link);
const pub = useEventPublisher();
const { formatMessage } = useIntl();
async function sendChatMessage() {
const reply = await pub?.note(chat, eb => {
return eb.tag(["a", `${link.kind}:${link.author}:${link.id}`]);
});
if (reply) {
console.debug(reply);
System.BroadcastEvent(reply);
if (chat.length > 1) {
const reply = await pub?.generic(eb => {
return eb
.kind(1311 as EventKind)
.content(chat)
.tag(["a", `${link.kind}:${link.author}:${link.id}`])
.processContent();
});
if (reply) {
console.debug(reply);
System.BroadcastEvent(reply);
}
setChat("");
}
setChat("");
}
return (
<div className="live-chat">
<div>
<FormattedMessage defaultMessage="Stream Chat" />
</div>
<div>
{[...(messages.data ?? [])]
.sort((a, b) => b.created_at - a.created_at)
@ -39,13 +56,19 @@ export function LiveChat({ ev, link }: { ev: TaggedRawEvent; link: NostrLink })
onChange={v => setChat(v.target.value)}
value={chat}
onFocus={() => {}}
placeholder=""
placeholder={formatMessage({
defaultMessage: "Message...",
})}
onKeyDown={async e => {
if (e.code === "Enter") {
e.preventDefault();
await sendChatMessage();
}
}}
/>
<AsyncButton onClick={sendChatMessage}>
<FormattedMessage defaultMessage="Send" />
</AsyncButton>
</div>
</div>
);
@ -53,11 +76,17 @@ export function LiveChat({ ev, link }: { ev: TaggedRawEvent; link: NostrLink })
function ChatMessage({ ev }: { ev: TaggedRawEvent }) {
const profile = useUserProfile(System, ev.pubkey);
const navigate = useNavigate();
return (
<div>
<b>{getDisplayName(profile, ev.pubkey)}</b>
:&nbsp;
{ev.content}
<div className="message">
<div className="name" onClick={() => navigate(profileLink(ev.pubkey, ev.relays))}>
<Avatar user={profile} />
{getDisplayName(profile, ev.pubkey)}:
</div>
<span>
<Text disableMedia={true} content={ev.content} creator={ev.pubkey} tags={ev.tags} />
</span>
</div>
);
}

View File

@ -50,7 +50,7 @@ export default function Text({ content, tags, creator, disableMedia, depth }: Te
};
if (validateLink()) {
if (disableMedia ?? false) {
if ((disableMedia ?? false) && !a.startsWith("nostr:")) {
return (
<a href={a} onClick={e => e.stopPropagation()} target="_blank" rel="noreferrer" className="ext">
{a}

View File

@ -1,4 +1,4 @@
import { FlatNoteStore, NostrLink, RequestBuilder } from "@snort/system";
import { EventKind, FlatNoteStore, NostrLink, RequestBuilder } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import { System } from "index";
import { useMemo } from "react";
@ -10,6 +10,7 @@ export function useLiveChatFeed(link: NostrLink) {
leaveOpen: true,
});
rb.withFilter()
.kinds([EventKind.ZapReceipt, 1311 as EventKind])
.tag("a", [`${link.kind}:${link.author}:${link.id}`])
.limit(100);
return rb;

View File

@ -1,8 +1,31 @@
.live-page {
display: grid;
grid-template-columns: auto 250px;
height: calc(100% - 105px);
padding: 24px;
grid-template-columns: auto 350px;
gap: 16px;
}
@media (min-width: 2000px) {
.live-page {
grid-template-columns: auto 450px;
}
}
.live-page > div:nth-child(1) {
overflow-y: auto;
}
.live-page video {
width: 100%;
aspect-ratio: 16/9;
}
.live-page .pill {
padding: 4px 8px;
border-radius: 20px;
font-size: 12px;
line-height: 16px;
font-weight: 600;
color: var(--font-secondary-color);
}

View File

@ -7,6 +7,10 @@ import { findTag, unwrap } from "SnortUtils";
import PageSpinner from "Element/PageSpinner";
import { LiveChat } from "Element/LiveChat";
import useEventFeed from "Feed/EventFeed";
import ProfilePreview from "Element/ProfilePreview";
import AsyncButton from "Element/AsyncButton";
import { FormattedMessage } from "react-intl";
import Icon from "Icons/Icon";
export function LivePage() {
const params = useParams();
@ -20,8 +24,40 @@ export function LivePage() {
return (
<div className="live-page main-content">
<div>
<h3>{findTag(thisEvent.data, "title")}</h3>
<LiveVideoPlayer stream={unwrap(findTag(thisEvent.data, "streaming"))} autoPlay={true} />
<div className="flex">
<div className="f-grow">
<h1>{findTag(thisEvent.data, "title")}</h1>
<p>{findTag(thisEvent.data, "summary")}</p>
<div>
{thisEvent.data?.tags
.filter(a => a[0] === "t")
.map(a => a[1])
.map(a => (
<div className="pill" key={a}>
{a}
</div>
))}
</div>
</div>
<div>
<ProfilePreview
pubkey={thisEvent.data.pubkey}
className="g10"
options={{
about: false,
}}
actions={
<div className="flex">
<AsyncButton onClick={() => {}}>
<Icon name="zap" size={16} className="mr5" />
<FormattedMessage defaultMessage="Zap" />
</AsyncButton>
</div>
}
/>
</div>
</div>
</div>
<LiveChat ev={thisEvent.data} link={link} />
</div>

View File

@ -498,6 +498,14 @@ small.xs {
margin-right: auto;
}
.g5 {
gap: 5px;
}
.g10 {
gap: 10px;
}
.error {
color: var(--error);
}