diff --git a/package.json b/package.json
index 82a5d39..54f00a2 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
+ "@react-hook/resize-observer": "^1.2.6",
"@snort/system-react": "^1.0.8",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0",
diff --git a/src/element/emoji.css b/src/element/emoji.css
index bf193f2..35e29bc 100644
--- a/src/element/emoji.css
+++ b/src/element/emoji.css
@@ -2,5 +2,4 @@
width: 21px;
height: 21px;
display: inline-block;
- margin-bottom: -5px;
}
diff --git a/src/element/live-chat.css b/src/element/live-chat.css
index 4f4a6a6..ddf9e19 100644
--- a/src/element/live-chat.css
+++ b/src/element/live-chat.css
@@ -1,13 +1,23 @@
.live-chat {
- height: calc(100vh - 72px - 96px);
+ grid-area: chat;
display: flex;
flex-direction: column;
- padding: 24px 16px 8px 24px;
- border: 1px solid #171717;
- border-radius: 24px;
- gap: 16px;
+ padding: 8px 16px;
+ border: none;
+ height: unset;
}
+@media (min-width: 1020px) {
+ .live-chat {
+ height: calc(100vh - 72px - 96px);
+ padding: 24px 16px 8px 24px;
+ border: 1px solid #171717;
+ border-radius: 24px;
+ gap: 16px;
+ }
+}
+
+
.live-chat>.header {
font-weight: 600;
font-size: 24px;
@@ -15,12 +25,19 @@
}
.live-chat>.messages {
- flex-grow: 1;
display: flex;
gap: 12px;
flex-direction: column-reverse;
overflow-y: auto;
overflow-x: hidden;
+ padding-bottom: 8px;
+ border-bottom: 1px solid var(--border, #171717);
+}
+
+@media (min-width: 1020px){
+ .live-chat > .messages {
+ flex-grow: 1;
+ }
}
.live-chat>.write-message {
@@ -42,6 +59,10 @@
flex-grow: 1;
}
+.live-chat .message {
+ word-wrap: break-word;
+}
+
.live-chat .message .profile {
gap: 8px;
font-weight: 600;
@@ -69,6 +90,10 @@
color: white;
}
+.live-chat .messages .pill:hover {
+ cursor: default;
+}
+
.live-chat .zap {
display: flex;
align-items: center;
@@ -84,14 +109,21 @@
.top-zappers-container {
display: flex;
- gap: 8px;
- justify-content: space-between;
- padding-top: 12px;
- padding-bottom: 20px;
- border-bottom: 1px solid var(--border, #171717);
+ padding-top: 8px;
+ padding-bottom: 8px;
overflow-y: scroll;
}
+@media (min-width: 1020px) {
+ .top-zappers-container {
+ display: flex;
+ gap: 8px;
+ padding-top: 12px;
+ padding-bottom: 20px;
+ border-bottom: 1px solid var(--border, #171717);
+ }
+}
+
.top-zapper {
display: flex;
padding: 4px 8px 4px 4px;
@@ -114,6 +146,6 @@
margin: 0;
}
-.top-zapper-icon {
+.zap-icon {
color: #FFCB44;
}
diff --git a/src/element/live-chat.tsx b/src/element/live-chat.tsx
index c563dbe..7ac1404 100644
--- a/src/element/live-chat.tsx
+++ b/src/element/live-chat.tsx
@@ -42,7 +42,8 @@ function totalZapped(pubkey: string, zaps: ParsedZap[]) {
function TopZappers({ zaps }: { zaps: ParsedZap[] }) {
const zappers = zaps
.map((z) => (z.anonZap ? "anon" : z.sender))
- .map((p) => p as string);
+ .map((p) => p as string)
+ .slice(0, 3);
const sortedZappers = useMemo(() => {
const sorted = [...new Set([...zappers])];
@@ -63,7 +64,7 @@ function TopZappers({ zaps }: { zaps: ParsedZap[] }) {
) : (
)}
-
+
{formatSats(total)}
);
@@ -76,9 +77,11 @@ function TopZappers({ zaps }: { zaps: ParsedZap[] }) {
export function LiveChat({
link,
options,
+ height,
}: {
link: NostrLink;
options?: LiveChatOptions;
+ height?: number;
}) {
const messages = useLiveChatFeed(link);
const login = useLogin();
@@ -88,7 +91,7 @@ export function LiveChat({
.map((ev) => parseZap(ev, System.ProfileLoader.Cache))
.filter((z) => z && z.valid);
return (
-
+
{(options?.showHeader ?? true) && (
Stream Chat
)}
@@ -157,7 +160,7 @@ function ChatZap({ ev }: { ev: TaggedRawEvent }) {
return (
-
+
- zapped
{formatSats(parsed.amount)}
sats
diff --git a/src/element/live-video-player.tsx b/src/element/live-video-player.tsx
index c3964d7..15da4b5 100644
--- a/src/element/live-video-player.tsx
+++ b/src/element/live-video-player.tsx
@@ -3,16 +3,23 @@ import { HTMLProps, useEffect, useMemo, useRef, useState } from "react";
export enum VideoStatus {
Online = "online",
- Offline = "offline"
+ Offline = "offline",
}
-export function LiveVideoPlayer(props: HTMLProps
& { stream?: string }) {
+export function LiveVideoPlayer(
+ props: HTMLProps & { stream?: string }
+) {
const video = useRef(null);
const streamCached = useMemo(() => props.stream, [props.stream]);
const [status, setStatus] = useState();
useEffect(() => {
- if (streamCached && video.current && !video.current.src && Hls.isSupported()) {
+ if (
+ streamCached &&
+ video.current &&
+ !video.current.src &&
+ Hls.isSupported()
+ ) {
try {
const hls = new Hls();
hls.loadSource(streamCached);
@@ -25,10 +32,10 @@ export function LiveVideoPlayer(props: HTMLProps & { stream?:
hls.detachMedia();
setStatus(VideoStatus.Offline);
}
- })
+ });
hls.on(Hls.Events.MANIFEST_PARSED, () => {
setStatus(VideoStatus.Online);
- })
+ });
return () => hls.destroy();
} catch (e) {
console.error(e);
@@ -37,9 +44,11 @@ export function LiveVideoPlayer(props: HTMLProps & { stream?:
}
}, [video, streamCached]);
return (
-
+ >
);
}
diff --git a/src/element/profile.tsx b/src/element/profile.tsx
index 2ccaf18..8fc9204 100644
--- a/src/element/profile.tsx
+++ b/src/element/profile.tsx
@@ -24,16 +24,24 @@ export function getName(pk: string, user?: UserMetadata) {
export function Profile({
pubkey,
+ avatarClassname,
options,
}: {
pubkey: string;
+ avatarClassname?: string;
options?: ProfileOptions;
}) {
const profile = useUserProfile(System, pubkey);
return (
- {(options?.showAvatar ?? true) &&

}
+ {(options?.showAvatar ?? true) && (
+

+ )}
{(options?.showName ?? true) &&
(options?.overrideName ?? getName(pubkey, profile))}
diff --git a/src/element/textarea.css b/src/element/textarea.css
index df253bf..7614ab5 100644
--- a/src/element/textarea.css
+++ b/src/element/textarea.css
@@ -1,3 +1,7 @@
+.rta__textarea {
+ resize: none;
+}
+
.rta__list {
border: none;
}
diff --git a/src/hooks/event-feed.ts b/src/hooks/event-feed.ts
index 001b763..d9e05e8 100644
--- a/src/hooks/event-feed.ts
+++ b/src/hooks/event-feed.ts
@@ -1,5 +1,10 @@
import { useMemo } from "react";
-import { NostrPrefix, RequestBuilder, ReplaceableNoteStore, NostrLink } from "@snort/system";
+import {
+ NostrPrefix,
+ RequestBuilder,
+ ReplaceableNoteStore,
+ NostrLink,
+} from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import { System } from "index";
@@ -8,8 +13,8 @@ export default function useEventFeed(link: NostrLink, leaveOpen = false) {
const sub = useMemo(() => {
const b = new RequestBuilder(`event:${link.id.slice(0, 12)}`);
b.withOptions({
- leaveOpen
- })
+ leaveOpen,
+ });
if (link.type === NostrPrefix.Address) {
const f = b.withFilter().tag("d", [link.id]);
if (link.author) {
@@ -21,14 +26,18 @@ export default function useEventFeed(link: NostrLink, leaveOpen = false) {
} else {
const f = b.withFilter().ids([link.id]);
if (link.relays) {
- link.relays.slice(0, 2).forEach(r => f.relay(r));
+ link.relays.slice(0, 2).forEach((r) => f.relay(r));
}
if (link.author) {
f.authors([link.author]);
}
}
return b;
- }, [link]);
+ }, [link, leaveOpen]);
- return useRequestBuilder(System, ReplaceableNoteStore, sub);
+ return useRequestBuilder(
+ System,
+ ReplaceableNoteStore,
+ sub
+ );
}
diff --git a/src/pages/layout.css b/src/pages/layout.css
index d3b6546..1ca015f 100644
--- a/src/pages/layout.css
+++ b/src/pages/layout.css
@@ -1,4 +1,74 @@
+.page {
+ display: grid;
+ grid-template-areas:
+ "header"
+ "video-content"
+ "profile"
+ "chat";
+ grid-template-rows: 64px 230px 56px min-content;
+ grid-template-columns: 1fr;
+ height: 100vh;
+ gap: 0;
+}
+
+
+.live-chat {
+ max-height: calc(100vh - 385px);
+}
+
+@media (min-width: 768px) {
+ .info {
+ display: none;
+ }
+
+ .video-content video {
+ height: calc(100vh - 64px);
+ }
+
+ .live-chat {
+ width: fit-content;
+ max-height: calc(100vh - 82px);
+ }
+
+ .page {
+ display: grid;
+ grid-template-areas:
+ "header header"
+ "video-content chat";
+ grid-template-rows: 64px min-content;
+ grid-template-columns: calc(min(600px, 1fr)) 1fr;
+ gap: 0;
+ }
+}
+
+@media (min-width: 1020px) {
+ .video-content video {
+ height: unset;
+ }
+
+ .page {
+ width: unset;
+ display: grid;
+ height: calc(100vh - 72px - 32px - 32px);
+ padding: 0 40px;
+ grid-template-columns: auto 376px;
+ grid-template-rows: unset;
+ grid-template-areas:
+ "header header"
+ "video-content chat"
+ "profile chat";
+ gap: 32px;
+ }
+}
+
+@media (min-width: 2000px) {
+ .page {
+ grid-template-columns: auto 450px;
+ }
+}
+
header {
+ grid-area: header;
display: grid;
grid-template-columns: min-content min-content auto;
gap: 24px;
@@ -6,7 +76,7 @@ header {
padding: 24px 40px 0 40px;
}
-header>div:nth-child(1) {
+header .logo {
background: #171717;
border-radius: 16px;
width: 48px;
@@ -19,12 +89,12 @@ header>div:nth-child(1) {
cursor: pointer;
}
-header>div:nth-child(2) {
+header .input {
min-width: 300px;
height: 32px;
}
-header>div:nth-child(3) {
+header .header-right {
justify-self: end;
display: flex;
gap: 24px;
@@ -44,4 +114,31 @@ header button {
header .profile img {
width: 48px;
height: 48px;
-}
\ No newline at end of file
+}
+
+@media (max-width: 1020px) {
+ header {
+ padding: 8px 16px 8px 16px;
+ gap: 8px;
+ }
+
+ header .header-right {
+ gap: 8px;
+ }
+
+ header .input {
+ min-width: unset;
+ }
+
+ header .input .search-input {
+ display: none;
+ }
+
+ header .new-stream-button-text {
+ display: none;
+ }
+
+ header .profile img {
+ border-radius: 12px;
+ }
+}
diff --git a/src/pages/layout.tsx b/src/pages/layout.tsx
index 1fa21b0..c23d625 100644
--- a/src/pages/layout.tsx
+++ b/src/pages/layout.tsx
@@ -37,10 +37,11 @@ export function LayoutPage() {
className="btn btn-primary"
onClick={() => setNewStream(true)}
>
- New Stream
+ New Stream
+