mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-28 20:43:33 +02:00
attempt to auto unmute
This commit is contained in:
@@ -127,7 +127,14 @@ const router = createHashRouter([
|
|||||||
</PageProviders>
|
</PageProviders>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{ path: "tools/stream-moderation", element: <StreamModerationView /> },
|
{
|
||||||
|
path: "tools/stream-moderation",
|
||||||
|
element: (
|
||||||
|
<PageProviders>
|
||||||
|
<StreamModerationView />
|
||||||
|
</PageProviders>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "map",
|
path: "map",
|
||||||
element: <MapView />,
|
element: <MapView />,
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
|
import { HTMLProps, useEffect, useRef, useState } from "react";
|
||||||
import { Box, BoxProps } from "@chakra-ui/react";
|
import { Box, BoxProps } from "@chakra-ui/react";
|
||||||
import Hls from "hls.js";
|
import Hls from "hls.js";
|
||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
|
|
||||||
export enum VideoStatus {
|
export enum VideoStatus {
|
||||||
Online = "online",
|
Online = "online",
|
||||||
@@ -12,8 +12,14 @@ export function LiveVideoPlayer({
|
|||||||
stream,
|
stream,
|
||||||
autoPlay,
|
autoPlay,
|
||||||
poster,
|
poster,
|
||||||
|
muted,
|
||||||
...props
|
...props
|
||||||
}: Omit<BoxProps, "children"> & { stream?: string; autoPlay?: boolean; poster?: string }) {
|
}: Omit<BoxProps, "children"> & {
|
||||||
|
stream?: string;
|
||||||
|
autoPlay?: boolean;
|
||||||
|
poster?: string;
|
||||||
|
muted?: HTMLProps<HTMLVideoElement>["muted"];
|
||||||
|
}) {
|
||||||
const video = useRef<HTMLVideoElement>(null);
|
const video = useRef<HTMLVideoElement>(null);
|
||||||
const [status, setStatus] = useState<VideoStatus>();
|
const [status, setStatus] = useState<VideoStatus>();
|
||||||
|
|
||||||
@@ -50,6 +56,7 @@ export function LiveVideoPlayer({
|
|||||||
controls={status === VideoStatus.Online}
|
controls={status === VideoStatus.Online}
|
||||||
autoPlay={autoPlay}
|
autoPlay={autoPlay}
|
||||||
poster={poster}
|
poster={poster}
|
||||||
|
muted={muted}
|
||||||
style={{ maxHeight: "100%", maxWidth: "100%", width: "100%" }}
|
style={{ maxHeight: "100%", maxWidth: "100%", width: "100%" }}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Flex,
|
Flex,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuItem,
|
||||||
|
MenuList,
|
||||||
Modal,
|
Modal,
|
||||||
ModalBody,
|
ModalBody,
|
||||||
ModalCloseButton,
|
ModalCloseButton,
|
||||||
@@ -15,6 +19,7 @@ import {
|
|||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { PropsWithChildren, createContext, useCallback, useContext, useMemo, useState } from "react";
|
import { PropsWithChildren, createContext, useCallback, useContext, useMemo, useState } from "react";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { useInterval } from "react-use";
|
||||||
|
|
||||||
import { getUserDisplayName } from "../helpers/user-metadata";
|
import { getUserDisplayName } from "../helpers/user-metadata";
|
||||||
import { useUserMetadata } from "../hooks/use-user-metadata";
|
import { useUserMetadata } from "../hooks/use-user-metadata";
|
||||||
@@ -23,6 +28,7 @@ import {
|
|||||||
createEmptyMuteList,
|
createEmptyMuteList,
|
||||||
getPubkeysExpiration,
|
getPubkeysExpiration,
|
||||||
muteListAddPubkey,
|
muteListAddPubkey,
|
||||||
|
muteListRemovePubkey,
|
||||||
pruneExpiredPubkeys,
|
pruneExpiredPubkeys,
|
||||||
} from "../helpers/nostr/mute-list";
|
} from "../helpers/nostr/mute-list";
|
||||||
import { cloneList } from "../helpers/nostr/lists";
|
import { cloneList } from "../helpers/nostr/lists";
|
||||||
@@ -31,10 +37,10 @@ import NostrPublishAction from "../classes/nostr-publish-action";
|
|||||||
import clientRelaysService from "../services/client-relays";
|
import clientRelaysService from "../services/client-relays";
|
||||||
import replaceableEventLoaderService from "../services/replaceable-event-requester";
|
import replaceableEventLoaderService from "../services/replaceable-event-requester";
|
||||||
import useUserMuteList from "../hooks/use-user-mute-list";
|
import useUserMuteList from "../hooks/use-user-mute-list";
|
||||||
import { useInterval } from "react-use";
|
|
||||||
import { DraftNostrEvent } from "../types/nostr-event";
|
import { DraftNostrEvent } from "../types/nostr-event";
|
||||||
import { UserAvatar } from "../components/user-avatar";
|
import { UserAvatar } from "../components/user-avatar";
|
||||||
import { UserLink } from "../components/user-link";
|
import { UserLink } from "../components/user-link";
|
||||||
|
import { ArrowDownSIcon } from "../components/icons";
|
||||||
|
|
||||||
type MuteModalContextType = {
|
type MuteModalContextType = {
|
||||||
openModal: (pubkey: string) => void;
|
openModal: (pubkey: string) => void;
|
||||||
@@ -119,63 +125,163 @@ function MuteModal({ pubkey, onClose, ...props }: Omit<ModalProps, "children"> &
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function UnmuteModal({}) {
|
function UnmuteHandler() {
|
||||||
|
const toast = useToast();
|
||||||
|
const account = useCurrentAccount()!;
|
||||||
|
const { requestSignature } = useSigningContext();
|
||||||
|
const muteList = useUserMuteList(account?.pubkey, [], { ignoreCache: true });
|
||||||
|
const modal = useDisclosure();
|
||||||
|
|
||||||
|
const unmuteAll = async () => {
|
||||||
|
if (!muteList) return;
|
||||||
|
try {
|
||||||
|
let draft: DraftNostrEvent = cloneList(muteList);
|
||||||
|
draft = pruneExpiredPubkeys(draft);
|
||||||
|
|
||||||
|
const signed = await requestSignature(draft);
|
||||||
|
new NostrPublishAction("Unmute", clientRelaysService.getWriteUrls(), signed);
|
||||||
|
replaceableEventLoaderService.handleEvent(signed);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const check = async () => {
|
||||||
|
if (!muteList) return;
|
||||||
|
const now = dayjs().unix();
|
||||||
|
const expirations = getPubkeysExpiration(muteList);
|
||||||
|
const expired = Object.entries(expirations).filter(([pubkey, ex]) => ex < now);
|
||||||
|
|
||||||
|
if (expired.length > 0) {
|
||||||
|
const accepted = await unmuteAll();
|
||||||
|
if (!accepted) modal.onOpen();
|
||||||
|
} else if (modal.isOpen) modal.onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
useInterval(check, 10 * 1000);
|
||||||
|
|
||||||
|
return modal.isOpen ? <UnmuteModal onClose={modal.onClose} isOpen={modal.isOpen} /> : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function UnmuteModal({ onClose }: Omit<ModalProps, "children">) {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const account = useCurrentAccount()!;
|
const account = useCurrentAccount()!;
|
||||||
const { requestSignature } = useSigningContext();
|
const { requestSignature } = useSigningContext();
|
||||||
const muteList = useUserMuteList(account?.pubkey, [], { ignoreCache: true });
|
const muteList = useUserMuteList(account?.pubkey, [], { ignoreCache: true });
|
||||||
|
|
||||||
const modal = useDisclosure();
|
const getExpiredPubkeys = useCallback(() => {
|
||||||
const removeExpiredMutes = async () => {
|
if (!muteList) return [];
|
||||||
|
const now = dayjs().unix();
|
||||||
|
const expirations = getPubkeysExpiration(muteList);
|
||||||
|
|
||||||
|
return Object.entries(expirations).filter(([pubkey, ex]) => ex < now);
|
||||||
|
}, [muteList]);
|
||||||
|
|
||||||
|
const unmuteAll = async () => {
|
||||||
if (!muteList) return;
|
if (!muteList) return;
|
||||||
try {
|
try {
|
||||||
// unmute users
|
|
||||||
let draft: DraftNostrEvent = cloneList(muteList);
|
let draft: DraftNostrEvent = cloneList(muteList);
|
||||||
draft = pruneExpiredPubkeys(muteList);
|
draft = pruneExpiredPubkeys(draft);
|
||||||
|
|
||||||
const signed = await requestSignature(draft);
|
const signed = await requestSignature(draft);
|
||||||
new NostrPublishAction("Unmute Users", clientRelaysService.getWriteUrls(), signed);
|
new NostrPublishAction("Unmute", clientRelaysService.getWriteUrls(), signed);
|
||||||
replaceableEventLoaderService.handleEvent(signed);
|
replaceableEventLoaderService.handleEvent(signed);
|
||||||
modal.onClose();
|
onClose();
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const extendAll = async (expiration: number) => {
|
||||||
|
if (!muteList) return;
|
||||||
|
try {
|
||||||
|
const expired = getExpiredPubkeys();
|
||||||
|
let draft: DraftNostrEvent = cloneList(muteList);
|
||||||
|
draft = pruneExpiredPubkeys(draft);
|
||||||
|
for (const [pubkey] of expired) {
|
||||||
|
draft = muteListAddPubkey(draft, pubkey, expiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
const signed = await requestSignature(draft);
|
||||||
|
new NostrPublishAction("Extend mute", clientRelaysService.getWriteUrls(), signed);
|
||||||
|
replaceableEventLoaderService.handleEvent(signed);
|
||||||
|
onClose();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getExpiredPubkeys = () => {
|
const unmuteUser = async (pubkey: string) => {
|
||||||
if (!muteList) return [];
|
|
||||||
const now = dayjs().unix();
|
|
||||||
const expirations = getPubkeysExpiration(muteList);
|
|
||||||
return Object.entries(expirations)
|
|
||||||
.filter(([pubkey, ex]) => ex < now)
|
|
||||||
.map(([pubkey]) => pubkey);
|
|
||||||
};
|
|
||||||
useInterval(() => {
|
|
||||||
if (!muteList) return;
|
if (!muteList) return;
|
||||||
if (!modal.isOpen && getExpiredPubkeys().length > 0) {
|
try {
|
||||||
modal.onOpen();
|
let draft: DraftNostrEvent = cloneList(muteList);
|
||||||
}
|
draft = muteListRemovePubkey(draft, pubkey);
|
||||||
}, 30 * 1000);
|
|
||||||
|
|
||||||
|
const signed = await requestSignature(draft);
|
||||||
|
new NostrPublishAction("Unmute", clientRelaysService.getWriteUrls(), signed);
|
||||||
|
replaceableEventLoaderService.handleEvent(signed);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const extendUser = async (pubkey: string, expiration: number) => {
|
||||||
|
if (!muteList) return;
|
||||||
|
try {
|
||||||
|
let draft: DraftNostrEvent = cloneList(muteList);
|
||||||
|
draft = muteListRemovePubkey(draft, pubkey);
|
||||||
|
draft = muteListAddPubkey(draft, pubkey, expiration);
|
||||||
|
|
||||||
|
const signed = await requestSignature(draft);
|
||||||
|
new NostrPublishAction("Extend mute", clientRelaysService.getWriteUrls(), signed);
|
||||||
|
replaceableEventLoaderService.handleEvent(signed);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const expiredPubkeys = getExpiredPubkeys().map(([pubkey]) => pubkey);
|
||||||
return (
|
return (
|
||||||
<Modal onClose={modal.onClose} size="lg" isOpen={modal.isOpen}>
|
<Modal onClose={onClose} isOpen size="xl">
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
<ModalHeader p="4">Unmute temporary muted users</ModalHeader>
|
<ModalHeader p="4">Unmute temporary muted users</ModalHeader>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody display="flex" flexWrap="wrap" gap="2" px="4" py="0">
|
<ModalBody display="flex" flexDirection="column" gap="2" px="4" py="0">
|
||||||
{getExpiredPubkeys().map((pubkey) => (
|
{expiredPubkeys.map((pubkey) => (
|
||||||
<Flex gap="2" key={pubkey} alignItems="center">
|
<Flex gap="2" key={pubkey} alignItems="center">
|
||||||
<UserAvatar pubkey={pubkey} size="sm" />
|
<UserAvatar pubkey={pubkey} size="sm" />
|
||||||
<UserLink pubkey={pubkey} fontWeight="bold" />
|
<UserLink pubkey={pubkey} fontWeight="bold" />
|
||||||
|
<Menu>
|
||||||
|
<MenuButton as={Button} size="sm" ml="auto" rightIcon={<ArrowDownSIcon />}>
|
||||||
|
Extend
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList>
|
||||||
|
<MenuItem onClick={() => extendUser(pubkey, Infinity)}>Forever</MenuItem>
|
||||||
|
<MenuItem onClick={() => extendUser(pubkey, dayjs().add(30, "minutes").unix())}>30 Minutes</MenuItem>
|
||||||
|
<MenuItem onClick={() => extendUser(pubkey, dayjs().add(1, "day").unix())}>1 Day</MenuItem>
|
||||||
|
<MenuItem onClick={() => extendUser(pubkey, dayjs().add(1, "week").unix())}>1 Week</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
<Button onClick={() => unmuteUser(pubkey)} size="sm">
|
||||||
|
Unmute
|
||||||
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
))}
|
))}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter p="4">
|
<ModalFooter p="4">
|
||||||
<Button onClick={modal.onClose} mr="3">
|
<Menu>
|
||||||
Cancel
|
<MenuButton as={Button} mr="2" rightIcon={<ArrowDownSIcon />}>
|
||||||
</Button>
|
Extend
|
||||||
<Button colorScheme="brand" onClick={removeExpiredMutes}>
|
</MenuButton>
|
||||||
|
<MenuList>
|
||||||
|
<MenuItem onClick={() => extendAll(Infinity)}>Forever</MenuItem>
|
||||||
|
<MenuItem onClick={() => extendAll(dayjs().add(30, "minutes").unix())}>30 Minutes</MenuItem>
|
||||||
|
<MenuItem onClick={() => extendAll(dayjs().add(1, "day").unix())}>1 Day</MenuItem>
|
||||||
|
<MenuItem onClick={() => extendAll(dayjs().add(1, "week").unix())}>1 Week</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
<Button colorScheme="brand" onClick={unmuteAll}>
|
||||||
Unmute all
|
Unmute all
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
@@ -199,8 +305,8 @@ export default function MuteModalProvider({ children }: PropsWithChildren) {
|
|||||||
return (
|
return (
|
||||||
<MuteModalContext.Provider value={context}>
|
<MuteModalContext.Provider value={context}>
|
||||||
{children}
|
{children}
|
||||||
<UnmuteModal />
|
|
||||||
{muteUser && <MuteModal isOpen onClose={() => setMuteUser("")} pubkey={muteUser} />}
|
{muteUser && <MuteModal isOpen onClose={() => setMuteUser("")} pubkey={muteUser} />}
|
||||||
|
<UnmuteHandler />
|
||||||
</MuteModalContext.Provider>
|
</MuteModalContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
import { forwardRef, memo, useRef } from "react";
|
import { memo, useRef } from "react";
|
||||||
import { Flex } from "@chakra-ui/react";
|
import { Flex } from "@chakra-ui/react";
|
||||||
|
|
||||||
import { DashboardCardProps } from "./common";
|
|
||||||
import useStreamChatTimeline from "../../streams/stream/stream-chat/use-stream-chat-timeline";
|
import useStreamChatTimeline from "../../streams/stream/stream-chat/use-stream-chat-timeline";
|
||||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||||
import IntersectionObserverProvider from "../../../providers/intersection-observer";
|
import IntersectionObserverProvider from "../../../providers/intersection-observer";
|
||||||
import StreamChatLog from "../../streams/stream/stream-chat/chat-log";
|
import StreamChatLog from "../../streams/stream/stream-chat/chat-log";
|
||||||
import ChatMessageForm from "../../streams/stream/stream-chat/stream-chat-form";
|
import ChatMessageForm from "../../streams/stream/stream-chat/stream-chat-form";
|
||||||
|
import { ParsedStream } from "../../../helpers/nostr/stream";
|
||||||
|
|
||||||
const ChatCard = forwardRef<HTMLDivElement, DashboardCardProps>(({ stream, children, ...props }, ref) => {
|
function ChatCard({ stream }: { stream: ParsedStream }) {
|
||||||
const timeline = useStreamChatTimeline(stream);
|
const timeline = useStreamChatTimeline(stream);
|
||||||
|
|
||||||
const scrollBox = useRef<HTMLDivElement | null>(null);
|
const scrollBox = useRef<HTMLDivElement | null>(null);
|
||||||
@@ -22,6 +22,6 @@ const ChatCard = forwardRef<HTMLDivElement, DashboardCardProps>(({ stream, child
|
|||||||
</IntersectionObserverProvider>
|
</IntersectionObserverProvider>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
export default memo(ChatCard);
|
export default memo(ChatCard);
|
||||||
|
@@ -1,4 +0,0 @@
|
|||||||
import { CardHeader, CardHeaderProps, CardProps, Heading } from "@chakra-ui/react";
|
|
||||||
import { ParsedStream } from "../../../helpers/nostr/stream";
|
|
||||||
|
|
||||||
export type DashboardCardProps = CardProps & { stream: ParsedStream };
|
|
@@ -1,4 +1,4 @@
|
|||||||
import { ReactNode, memo, useMemo } from "react";
|
import { ReactNode, memo, useMemo, useState } from "react";
|
||||||
import { Button, ButtonGroup, Divider, Flex, Heading } from "@chakra-ui/react";
|
import { Button, ButtonGroup, Divider, Flex, Heading } from "@chakra-ui/react";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
@@ -11,9 +11,17 @@ import useUserMuteFunctions from "../../../hooks/use-user-mute-functions";
|
|||||||
import { useMuteModalContext } from "../../../providers/mute-modal-provider";
|
import { useMuteModalContext } from "../../../providers/mute-modal-provider";
|
||||||
import useUserMuteList from "../../../hooks/use-user-mute-list";
|
import useUserMuteList from "../../../hooks/use-user-mute-list";
|
||||||
import { isPubkeyInList } from "../../../helpers/nostr/lists";
|
import { isPubkeyInList } from "../../../helpers/nostr/lists";
|
||||||
import { DashboardCardProps } from "./common";
|
import { ParsedStream } from "../../../helpers/nostr/stream";
|
||||||
|
import { useInterval } from "react-use";
|
||||||
|
|
||||||
const UserCard = ({ pubkey }: { pubkey: string }) => {
|
function Countdown({ time }: { time: number }) {
|
||||||
|
const [now, setNow] = useState(dayjs().unix());
|
||||||
|
useInterval(() => setNow(dayjs().unix()), 1000);
|
||||||
|
|
||||||
|
return <span>{time - now + "s"}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function UserCard({ pubkey }: { pubkey: string }) {
|
||||||
const { isMuted, mute, unmute, expiration } = useUserMuteFunctions(pubkey);
|
const { isMuted, mute, unmute, expiration } = useUserMuteFunctions(pubkey);
|
||||||
const { openModal } = useMuteModalContext();
|
const { openModal } = useMuteModalContext();
|
||||||
|
|
||||||
@@ -22,7 +30,11 @@ const UserCard = ({ pubkey }: { pubkey: string }) => {
|
|||||||
if (expiration === Infinity) {
|
if (expiration === Infinity) {
|
||||||
buttons = <Button onClick={unmute}>Unban</Button>;
|
buttons = <Button onClick={unmute}>Unban</Button>;
|
||||||
} else {
|
} else {
|
||||||
buttons = <Button onClick={unmute}>Unmute ({dayjs.unix(expiration).fromNow()})</Button>;
|
buttons = (
|
||||||
|
<Button onClick={unmute}>
|
||||||
|
Unmute (<Countdown time={expiration} />)
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
buttons = (
|
buttons = (
|
||||||
@@ -42,9 +54,9 @@ const UserCard = ({ pubkey }: { pubkey: string }) => {
|
|||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
function UsersCard({ stream, ...props }: DashboardCardProps) {
|
function UsersCard({ stream }: { stream: ParsedStream }) {
|
||||||
const account = useCurrentAccount()!;
|
const account = useCurrentAccount()!;
|
||||||
const streamChatTimeline = useStreamChatTimeline(stream);
|
const streamChatTimeline = useStreamChatTimeline(stream);
|
||||||
|
|
||||||
|
@@ -1,11 +1,17 @@
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
|
|
||||||
import { DashboardCardProps } from "./common";
|
|
||||||
import { LiveVideoPlayer } from "../../../components/live-video-player";
|
import { LiveVideoPlayer } from "../../../components/live-video-player";
|
||||||
|
import { ParsedStream } from "../../../helpers/nostr/stream";
|
||||||
|
|
||||||
function LiveVideoCard({ stream, children, ...props }: DashboardCardProps) {
|
function LiveVideoCard({ stream }: { stream: ParsedStream }) {
|
||||||
return (
|
return (
|
||||||
<LiveVideoPlayer stream={stream.streaming || stream.recording} autoPlay={false} poster={stream.image} maxH="50vh" />
|
<LiveVideoPlayer
|
||||||
|
stream={stream.streaming || stream.recording}
|
||||||
|
autoPlay={stream.streaming ? true : undefined}
|
||||||
|
poster={stream.image}
|
||||||
|
maxH="50vh"
|
||||||
|
muted
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,15 +4,20 @@ import { Kind } from "nostr-tools";
|
|||||||
|
|
||||||
import useSubject from "../../../hooks/use-subject";
|
import useSubject from "../../../hooks/use-subject";
|
||||||
import useStreamChatTimeline from "../../streams/stream/stream-chat/use-stream-chat-timeline";
|
import useStreamChatTimeline from "../../streams/stream/stream-chat/use-stream-chat-timeline";
|
||||||
import { DashboardCardProps } from "./common";
|
|
||||||
import ZapMessageMemo from "../../streams/stream/stream-chat/zap-message";
|
import ZapMessageMemo from "../../streams/stream/stream-chat/zap-message";
|
||||||
|
import { ParsedStream } from "../../../helpers/nostr/stream";
|
||||||
|
|
||||||
function ZapsCard({ stream, ...props }: DashboardCardProps) {
|
function ZapsCard({ stream }: { stream: ParsedStream }) {
|
||||||
const streamChatTimeline = useStreamChatTimeline(stream);
|
const streamChatTimeline = useStreamChatTimeline(stream);
|
||||||
|
|
||||||
// refresh when a new event
|
// refresh when a new event
|
||||||
useSubject(streamChatTimeline.events.onEvent);
|
useSubject(streamChatTimeline.events.onEvent);
|
||||||
const zapMessages = streamChatTimeline.events.getSortedEvents().filter((event) => event.kind === Kind.Zap);
|
const zapMessages = streamChatTimeline.events.getSortedEvents().filter((event) => {
|
||||||
|
if (stream.starts && event.created_at < stream.starts) return false;
|
||||||
|
if (stream.ends && event.created_at > stream.ends) return false;
|
||||||
|
if (event.kind !== Kind.Zap) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flex={1} p="2" gap="2" overflowY="auto" overflowX="hidden" flexDirection="column">
|
<Flex flex={1} p="2" gap="2" overflowY="auto" overflowX="hidden" flexDirection="column">
|
||||||
|
Reference in New Issue
Block a user