chore: formatting

This commit is contained in:
Kieran 2024-03-06 16:32:21 +00:00
parent a385ca3271
commit 5e58af119d
29 changed files with 264 additions and 224 deletions

View File

@ -48,12 +48,14 @@ export function NostrEvent({ ev }: { ev: TaggedNostrEvent }) {
}
case EventKind.LiveEvent: {
const info = extractStreamInfo(ev);
return <LiveVideoPlayer
title={info.title}
status={info.status}
stream={info.status === StreamState.Live ? info.stream : info.recording}
poster={info.image}
/>;
return (
<LiveVideoPlayer
title={info.title}
status={info.status}
stream={info.status === StreamState.Live ? info.stream : info.recording}
poster={info.image}
/>
);
}
default: {
const link = NostrLink.fromEvent(ev);

View File

@ -4,33 +4,37 @@ import classNames from "classnames";
import { Link } from "react-router-dom";
interface GameInfoCardProps {
gameId?: string,
gameInfo?: GameInfo,
imageSize?: number,
showImage?: boolean,
link?: boolean
gameId?: string;
gameInfo?: GameInfo;
imageSize?: number;
showImage?: boolean;
link?: boolean;
}
export default function GameInfoCard({
gameId,
gameInfo,
imageSize,
showImage,
link
}: GameInfoCardProps) {
const game = useGameInfo(gameId, gameInfo);
if (!game) return;
export default function GameInfoCard({ gameId, gameInfo, imageSize, showImage, link }: GameInfoCardProps) {
const game = useGameInfo(gameId, gameInfo);
if (!game) return;
const inner = <div className="flex gap-2 items-center">
{(showImage ?? true) && <img src={game.cover} style={{ height: imageSize ?? 20 }} className={classNames("object-contain", game.className)} />}
{game.name}
</div>;
const inner = (
<div className="flex gap-2 items-center">
{(showImage ?? true) && (
<img
src={game.cover}
style={{ height: imageSize ?? 20 }}
className={classNames("object-contain", game.className)}
/>
)}
{game.name}
</div>
);
if (link) {
return <Link to={`/category/${gameId}`} className="text-primary">
{inner}
</Link>
} else {
return inner;
}
}
if (link) {
return (
<Link to={`/category/${gameId}`} className="text-primary">
{inner}
</Link>
);
} else {
return inner;
}
}

View File

@ -21,16 +21,12 @@ export function HyperText({ link, children }: HyperTextProps) {
case "png":
case "bmp":
case "webp": {
return (
<img src={url.toString()} alt={url.toString()} style={{ objectFit: "contain" }} />
);
return <img src={url.toString()} alt={url.toString()} style={{ objectFit: "contain" }} />;
}
case "wav":
case "mp3":
case "ogg": {
return (
<audio key={url.toString()} src={url.toString()} controls />
);
return <audio key={url.toString()} src={url.toString()} controls />;
}
case "mp4":
case "mov":
@ -38,9 +34,7 @@ export function HyperText({ link, children }: HyperTextProps) {
case "avi":
case "m4v":
case "webm": {
return (
<video key={url.toString()} src={url.toString()} controls />
);
return <video key={url.toString()} src={url.toString()} controls />;
}
default:
return <ExternalLink href={url.toString()}>{children || url.toString()}</ExternalLink>;

View File

@ -67,16 +67,18 @@ export function NewStream({ ev, onFinish }: Omit<StreamEditorProps, "onFinish">
return (
<>
{!ev && <>
<FormattedMessage defaultMessage="Stream Providers" id="6Z2pvJ" />
<div className="flex gap-2">
{providers.map(v => (
<Pill className={`${v === currentProvider ? " text-bold" : ""}`} onClick={() => setCurrentProvider(v)}>
{v.name}
</Pill>
))}
</div>
</>}
{!ev && (
<>
<FormattedMessage defaultMessage="Stream Providers" id="6Z2pvJ" />
<div className="flex gap-2">
{providers.map(v => (
<Pill className={`${v === currentProvider ? " text-bold" : ""}`} onClick={() => setCurrentProvider(v)}>
{v.name}
</Pill>
))}
</div>
</>
)}
<div className="flex flex-col gap-4">{providerDialog()}</div>
</>
);

View File

@ -2,6 +2,9 @@ import { useSyncExternalStore } from "react";
import { NSFWStore } from "./store";
export function useContentWarning() {
const v = useSyncExternalStore(c => NSFWStore.hook(c), () => NSFWStore.snapshot());
return v;
}
const v = useSyncExternalStore(
c => NSFWStore.hook(c),
() => NSFWStore.snapshot()
);
return v;
}

View File

@ -36,4 +36,4 @@ export function ContentWarningOverlay() {
);
}
export { useContentWarning }
export { useContentWarning };

View File

@ -1,23 +1,22 @@
import { ExternalStore } from "@snort/shared";
class Store extends ExternalStore<boolean> {
#value: boolean;
#value: boolean;
constructor() {
super();
this.#value = Boolean(window.localStorage.getItem("accepted-content-warning"));
}
constructor() {
super();
this.#value = Boolean(window.localStorage.getItem("accepted-content-warning"));
}
setValue(v: boolean) {
this.#value = v;
window.localStorage.setItem("accepted-content-warning", String(v));
this.notifyChange();
}
takeSnapshot(): boolean {
return this.#value;
}
setValue(v: boolean) {
this.#value = v;
window.localStorage.setItem("accepted-content-warning", String(v));
this.notifyChange();
}
takeSnapshot(): boolean {
return this.#value;
}
}
export const NSFWStore = new Store();
export const NSFWStore = new Store();

View File

@ -6,7 +6,7 @@ import { AllCategories } from "@/pages/category";
import GameDatabase, { GameInfo } from "@/service/game-database";
import GameInfoCard from "@/element/game-info";
export function SearchCategory({ onSelect }: { onSelect?: (game: GameInfo) => void; }) {
export function SearchCategory({ onSelect }: { onSelect?: (game: GameInfo) => void }) {
const ref = useRef<HTMLInputElement | null>(null);
const { formatMessage } = useIntl();
const [search, setSearch] = useState("");
@ -25,7 +25,7 @@ export function SearchCategory({ onSelect }: { onSelect?: (game: GameInfo) => vo
id: `internal:${a.id}`,
name: a.name,
genres: a.tags,
className: a.className
className: a.className,
}));
}
@ -42,29 +42,35 @@ export function SearchCategory({ onSelect }: { onSelect?: (game: GameInfo) => vo
}
}, [search]);
return <>
<input
ref={ref}
type="text"
value={search}
onChange={e => setSearch(e.target.value)}
placeholder={formatMessage({
defaultMessage: "Gaming"
})} />
<ControlledMenu
gap={2}
menuClassName="ctx-menu gap-1"
state={categoryResults.length > 0 ? "open" : "closed"}
anchorRef={ref}
captureFocus={false}
>
{categoryResults.map(a => <MenuItem className="!px-2 !py-0" onClick={() => {
setCategoryResults([]);
setSearch("");
onSelect?.(a);
}}>
<GameInfoCard gameInfo={a} imageSize={40} />
</MenuItem>)}
</ControlledMenu>
</>;
return (
<>
<input
ref={ref}
type="text"
value={search}
onChange={e => setSearch(e.target.value)}
placeholder={formatMessage({
defaultMessage: "Gaming",
})}
/>
<ControlledMenu
gap={2}
menuClassName="ctx-menu gap-1"
state={categoryResults.length > 0 ? "open" : "closed"}
anchorRef={ref}
captureFocus={false}>
{categoryResults.map(a => (
<MenuItem
className="!px-2 !py-0"
onClick={() => {
setCategoryResults([]);
setSearch("");
onSelect?.(a);
}}>
<GameInfoCard gameInfo={a} imageSize={40} />
</MenuItem>
))}
</ControlledMenu>
</>
);
}

View File

@ -12,7 +12,7 @@ export function GoalSelector({ goal, pubkey, onGoalSelect }: GoalSelectorProps)
const { formatMessage } = useIntl();
return (
<select value={goal} onChange={ev => onGoalSelect(ev.target.value)}>
<option >{formatMessage({ defaultMessage: "Select a goal..." })}</option>
<option>{formatMessage({ defaultMessage: "Select a goal..." })}</option>
{goals?.map(x => (
<option key={x.id} value={x.id}>
{x.content}

View File

@ -1,15 +1,14 @@
.rti--container {
background-color: unset !important;
border: 0 !important;
border-radius: 0 !important;
padding: 0 !important;
box-shadow: unset !important;
}
.rti--tag {
color: black !important;
padding: 4px 10px !important;
border-radius: 12px !important;
display: unset !important;
}
background-color: unset !important;
border: 0 !important;
border-radius: 0 !important;
padding: 0 !important;
box-shadow: unset !important;
}
.rti--tag {
color: black !important;
padding: 4px 10px !important;
border-radius: 12px !important;
display: unset !important;
}

View File

@ -62,7 +62,7 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
setTags(tags ?? []);
setContentWarning(contentWarning !== undefined);
setGoal(goal);
setGameId(gameId)
setGameId(gameId);
if (gameInfo) {
setGame(gameInfo);
} else if (gameId) {
@ -209,21 +209,28 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
{(options?.canSetTags ?? true) && (
<>
<StreamInput label={<FormattedMessage defaultMessage="Category" />}>
{!game && <SearchCategory onSelect={g => {
setGame(g);
setGameId(g.id);
}} />}
{game && <div className="flex justify-between rounded-xl px-3 py-2 border border-layer-2">
<GameInfoCard gameInfo={game} gameId={gameId} imageSize={80} />
<IconButton iconName="x"
iconSize={12}
className="text-layer-4"
onClick={() => {
setGame(undefined);
setGameId(undefined);
{!game && (
<SearchCategory
onSelect={g => {
setGame(g);
setGameId(g.id);
}}
/>
</div>}
)}
{game && (
<div className="flex justify-between rounded-xl px-3 py-2 border border-layer-2">
<GameInfoCard gameInfo={game} gameId={gameId} imageSize={80} />
<IconButton
iconName="x"
iconSize={12}
className="text-layer-4"
onClick={() => {
setGame(undefined);
setGameId(undefined);
}}
/>
</div>
)}
</StreamInput>
<StreamInput label={<FormattedMessage defaultMessage="Tags" />}>
<TagsInput value={tags} onChange={setTags} placeHolder="Music,DJ,English" separators={["Enter", ","]} />
@ -261,4 +268,4 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
</div>
</>
);
}
}

View File

@ -1,12 +1,10 @@
import { ReactNode } from "react";
export function StreamInput({ label, children }: { label: ReactNode; children?: ReactNode; }) {
return <div>
<div className="mb-1 text-layer-4 text-sm font-medium">
{label}
</div>
export function StreamInput({ label, children }: { label: ReactNode; children?: ReactNode }) {
return (
<div>
{children}
<div className="mb-1 text-layer-4 text-sm font-medium">{label}</div>
<div>{children}</div>
</div>
</div>;
);
}

View File

@ -42,9 +42,7 @@ export function Text({ content, tags, eventComponent, className }: TextProps) {
}
}
}
return (
<HyperText link={f.content}>{f.content}</HyperText>
);
return <HyperText link={f.content}>{f.content}</HyperText>;
}
case "mention":
return <Mention pubkey={f.content} />;

View File

@ -30,10 +30,16 @@ export function VideoTile({
const hasImg = (image?.length ?? 0) > 0;
return (
<div className="flex flex-col gap-2">
<Link to={`/${link.encode()}`} className={classNames({
"blur transition": contentWarning,
"hover:blur-none": isGrownUp,
}, "h-full")} state={ev}>
<Link
to={`/${link.encode()}`}
className={classNames(
{
"blur transition": contentWarning,
"hover:blur-none": isGrownUp,
},
"h-full"
)}
state={ev}>
<div className="relative mb-2 aspect-video">
{hasImg ? (
<img loading="lazy" className="aspect-video object-cover rounded-xl" src={image} />

View File

@ -3,26 +3,26 @@ import GameDatabase, { GameInfo } from "@/service/game-database";
import { useEffect, useState } from "react";
export default function useGameInfo(gameId?: string, gameInfo?: GameInfo) {
const [game, setGame] = useState<GameInfo | undefined>(gameInfo);
const [game, setGame] = useState<GameInfo | undefined>(gameInfo);
useEffect(() => {
if (!gameInfo && gameId) {
const [prefix, id] = gameId.split(":");
if (prefix === "internal" || !gameId.includes(":")) {
const ix = AllCategories.find(a => a.id === id || a.id === gameId);
if (ix) {
setGame({
id: `internal:${ix.id}`,
name: ix.name,
genres: ix.tags,
className: ix.className
});
}
} else {
new GameDatabase().getGame(gameId).then(setGame);
}
useEffect(() => {
if (!gameInfo && gameId) {
const [prefix, id] = gameId.split(":");
if (prefix === "internal" || !gameId.includes(":")) {
const ix = AllCategories.find(a => a.id === id || a.id === gameId);
if (ix) {
setGame({
id: `internal:${ix.id}`,
name: ix.name,
genres: ix.tags,
className: ix.className,
});
}
}, [gameInfo, gameId]);
} else {
new GameDatabase().getGame(gameId).then(setGame);
}
}
}, [gameInfo, gameId]);
return game;
}
return game;
}

View File

@ -22,4 +22,4 @@ export function getCurrentStreamProvider(ev?: NostrEvent) {
}
}
return providers.at(0);
}
}

View File

@ -92,6 +92,9 @@
"5JcXdV": {
"defaultMessage": "Create Account"
},
"5LXWMX": {
"defaultMessage": "New Goal"
},
"5QYdPU": {
"defaultMessage": "Start Time"
},
@ -155,6 +158,9 @@
"AukrPM": {
"defaultMessage": "No viewer data available"
},
"Axo/o5": {
"defaultMessage": "Science & Technology"
},
"AyGauy": {
"defaultMessage": "Login"
},
@ -263,6 +269,9 @@
"JEsxDw": {
"defaultMessage": "Uploading..."
},
"JO0kq9": {
"defaultMessage": "Edit Stream Info"
},
"JkLHGw": {
"defaultMessage": "Website"
},
@ -371,12 +380,12 @@
"TP/cMX": {
"defaultMessage": "Ended"
},
"TaTRKo": {
"defaultMessage": "Start Stream"
},
"TwyMau": {
"defaultMessage": "Account"
},
"UGFYV8": {
"defaultMessage": "Welcome to zap.stream!"
},
"UJBFYK": {
"defaultMessage": "Add Card"
},
@ -395,9 +404,6 @@
"VKb1MS": {
"defaultMessage": "Categories"
},
"Vn2WiP": {
"defaultMessage": "Get Stream Key"
},
"W7DNWx": {
"defaultMessage": "Stream Forwarding"
},
@ -475,6 +481,9 @@
"cPIKU2": {
"defaultMessage": "Following"
},
"ccXLVi": {
"defaultMessage": "Category"
},
"cvAsEh": {
"defaultMessage": "Streamed on {date}"
},
@ -614,6 +623,9 @@
"oZrFyI": {
"defaultMessage": "Stream type should be HLS"
},
"p4N05H": {
"defaultMessage": "Upload"
},
"q+zTWM": {
"defaultMessage": "<s>{person}</s> zapped <s>{amount}</s> sats"
},
@ -690,9 +702,6 @@
"wMKVFz": {
"defaultMessage": "Select voice..."
},
"wOy57k": {
"defaultMessage": "Add stream goal"
},
"wRGjPp": {
"defaultMessage": "A nostr extension simply saves your keys so you can safely log in without having to re-enter them every time. ZapStream uses the extension to authorize actions on your behalf without ever seeing your key information. This has a significant advantage over having to trust that websites handle your credentials safely."
},

View File

@ -76,7 +76,9 @@ export default function Category() {
const cat = AllCategories.find(a => a.id === id);
const rb = new RequestBuilder(`category:${id}`);
rb.withFilter().kinds([EventKind.LiveEvent]).tag("t", cat?.tags ?? [id]);
rb.withFilter()
.kinds([EventKind.LiveEvent])
.tag("t", cat?.tags ?? [id]);
return rb;
}, [id]);
@ -92,11 +94,13 @@ export default function Category() {
{game?.cover && <img src={game?.cover} className="h-[250px]" />}
<div className="flex flex-col gap-4">
<h1>{game?.name}</h1>
{game?.genres && <div className="flex gap-2">
{game?.genres?.map(a => <Pill>
{a}
</Pill>)}
</div>}
{game?.genres && (
<div className="flex gap-2">
{game?.genres?.map(a => (
<Pill>{a}</Pill>
))}
</div>
)}
</div>
</div>
<VideoGridSorted evs={results} showAll={true} />

View File

@ -5,7 +5,7 @@ import { DashboardRaidMenu } from "./raid-menu";
import { DefaultButton } from "@/element/buttons";
import Modal from "@/element/modal";
export function DashboardRaidButton({ link }: { link: NostrLink; }) {
export function DashboardRaidButton({ link }: { link: NostrLink }) {
const [show, setShow] = useState(false);
return (
<>

View File

@ -6,7 +6,7 @@ import { useMemo } from "react";
import { FormattedMessage } from "react-intl";
import { DefaultButton } from "@/element/buttons";
export function DashboardChatList({ feed }: { feed: Array<TaggedNostrEvent>; }) {
export function DashboardChatList({ feed }: { feed: Array<TaggedNostrEvent> }) {
const pubkeys = useMemo(() => {
return dedupe(feed.map(a => a.pubkey));
}, [feed]);
@ -16,7 +16,7 @@ export function DashboardChatList({ feed }: { feed: Array<TaggedNostrEvent>; })
<Profile pubkey={a} avatarSize={32} gap={4} />
<div className="flex gap-2">
<MuteButton pubkey={a} />
<DefaultButton onClick={() => { }} className="font-bold">
<DefaultButton onClick={() => {}} className="font-bold">
<FormattedMessage defaultMessage="Zap" id="fBI91o" />
</DefaultButton>
</div>

View File

@ -6,7 +6,7 @@ import { FormattedMessage } from "react-intl";
import { DashboardCard } from "./card";
import { DashboardHighlightZap } from "./zap-highlight";
export function DashboardZapColumn({ link, feed }: { link: NostrLink; feed: Array<TaggedNostrEvent>; }) {
export function DashboardZapColumn({ link, feed }: { link: NostrLink; feed: Array<TaggedNostrEvent> }) {
const reactions = useEventReactions(link, feed);
const sortedZaps = useMemo(

View File

@ -17,7 +17,7 @@ import { NewStreamDialog } from "@/element/new-stream";
import { DashboardSettingsButton } from "./button-settings";
import DashboardIntro from "./intro";
export function DashboardForLink({ link }: { link: NostrLink; }) {
export function DashboardForLink({ link }: { link: NostrLink }) {
const streamEvent = useCurrentStreamFeed(link, true);
const streamLink = streamEvent ? NostrLink.fromEvent(streamEvent) : undefined;
const { stream, status, image, participants } = extractStreamInfo(streamEvent);
@ -34,9 +34,7 @@ export function DashboardForLink({ link }: { link: NostrLink; }) {
streamLink ? [streamLink] : [],
rb => {
if (streamLink) {
rb.withFilter()
.kinds([LIVE_STREAM_CHAT, LIVE_STREAM_RAID, LIVE_STREAM_CLIP])
.replyToLink([streamLink]);
rb.withFilter().kinds([LIVE_STREAM_CHAT, LIVE_STREAM_RAID, LIVE_STREAM_CLIP]).replyToLink([streamLink]);
}
},
true
@ -54,11 +52,13 @@ export function DashboardForLink({ link }: { link: NostrLink; }) {
<div className="flex gap-4">
<DashboardStatsCard
name={<FormattedMessage defaultMessage="Stream Time" id="miQKuZ" />}
value={<StreamTimer ev={streamEvent} />} />
value={<StreamTimer ev={streamEvent} />}
/>
<DashboardStatsCard name={<FormattedMessage defaultMessage="Viewers" id="37mth/" />} value={participants} />
<DashboardStatsCard
name={<FormattedMessage defaultMessage="Highest Viewers" id="jctiUc" />}
value={maxParticipants} />
value={maxParticipants}
/>
</div>
<div className="grid gap-2 grid-cols-3">
<DashboardRaidButton link={streamLink} />

View File

@ -7,4 +7,4 @@ export default function DashboardPage() {
if (!login) return;
return <DashboardForLink link={new NostrLink(NostrPrefix.PublicKey, login.pubkey)} />;
}
}

View File

@ -1,9 +1,11 @@
import { FormattedMessage } from "react-intl";
export default function DashboardIntro() {
return <>
<h1>
<FormattedMessage defaultMessage="Welcome to zap.stream!" />
</h1>
return (
<>
<h1>
<FormattedMessage defaultMessage="Welcome to zap.stream!" />
</h1>
</>
}
);
}

View File

@ -2,8 +2,10 @@ import classNames from "classnames";
import { HTMLProps, ReactNode } from "react";
export function DashboardStatsCard({
name, value, ...props
}: { name: ReactNode; value: ReactNode; } & Omit<HTMLProps<HTMLDivElement>, "children" | "name" | "value">) {
name,
value,
...props
}: { name: ReactNode; value: ReactNode } & Omit<HTMLProps<HTMLDivElement>, "children" | "name" | "value">) {
return (
<div
{...props}

View File

@ -3,7 +3,7 @@ import { ParsedZap } from "@snort/system";
import { FormattedMessage, FormattedNumber } from "react-intl";
import { Text } from "@/element/text";
export function DashboardHighlightZap({ zap }: { zap: ParsedZap; }) {
export function DashboardHighlightZap({ zap }: { zap: ParsedZap }) {
return (
<div className="px-4 py-6 bg-layer-1 flex flex-col gap-4 rounded-xl animate-flash">
<div className="flex justify-between items-center text-zap text-2xl font-semibold">
@ -11,14 +11,16 @@ export function DashboardHighlightZap({ zap }: { zap: ParsedZap; }) {
pubkey={zap.sender ?? "anon"}
options={{
showAvatar: false,
}} />
}}
/>
<span>
<FormattedMessage
defaultMessage="{n} sats"
id="CsCUYo"
values={{
n: <FormattedNumber value={zap.amount} />,
}} />
}}
/>
</span>
</div>
{zap.content && (

View File

@ -1,30 +1,30 @@
export default class GameDatabase {
readonly url = "https://api.zap.stream/api/v1";
readonly url = "https://api.zap.stream/api/v1";
async searchGames(search: string, limit = 10) {
const rsp = await fetch(`${this.url}/games/search?q=${encodeURIComponent(search)}&limit=${limit}`);
if (rsp.ok) {
const games = await rsp.json() as Array<GameInfo>;
return games.map(a => ({
...a,
genres: [...a.genres, "gaming"]
}));
}
return [];
async searchGames(search: string, limit = 10) {
const rsp = await fetch(`${this.url}/games/search?q=${encodeURIComponent(search)}&limit=${limit}`);
if (rsp.ok) {
const games = (await rsp.json()) as Array<GameInfo>;
return games.map(a => ({
...a,
genres: [...a.genres, "gaming"],
}));
}
return [];
}
async getGame(id: string) {
const rsp = await fetch(`${this.url}/games/${id}`);
if (rsp.ok) {
return await rsp.json() as GameInfo | undefined;
}
async getGame(id: string) {
const rsp = await fetch(`${this.url}/games/${id}`);
if (rsp.ok) {
return (await rsp.json()) as GameInfo | undefined;
}
}
}
export interface GameInfo {
id: string;
name: string | JSX.Element;
cover?: string;
genres: Array<string>;
className?: string;
id: string;
name: string | JSX.Element;
cover?: string;
genres: Array<string>;
className?: string;
}

View File

@ -30,6 +30,7 @@
"4l6vz1": "Copy",
"50+/JW": "Stream Key is required",
"5JcXdV": "Create Account",
"5LXWMX": "New Goal",
"5QYdPU": "Start Time",
"5kx+2v": "Server Url",
"5tM0VD": "Stream Started",
@ -51,6 +52,7 @@
"ALdW69": "Note by {name}",
"Atr2p4": "NSFW Content",
"AukrPM": "No viewer data available",
"Axo/o5": "Science & Technology",
"AyGauy": "Login",
"BD0vyn": "{name} created a clip",
"BGxpTN": "Stream Chat",
@ -87,6 +89,7 @@
"J/+m9y": "Stream Duration {duration} mins",
"JCIgkj": "Username",
"JEsxDw": "Uploading...",
"JO0kq9": "Edit Stream Info",
"JkLHGw": "Website",
"Jq3FDz": "Content",
"K3r6DQ": "Delete",
@ -123,15 +126,14 @@
"S39ba6": "What is OBS?",
"SC2nJT": "Audio Codec",
"TP/cMX": "Ended",
"TaTRKo": "Start Stream",
"TwyMau": "Account",
"UGFYV8": "Welcome to zap.stream!",
"UJBFYK": "Add Card",
"UfSot5": "Past Streams",
"Uo/DWG": "About too long",
"VA/Z1S": "Hide",
"VDOpia": "What are zaps?",
"VKb1MS": "Categories",
"Vn2WiP": "Get Stream Key",
"W7DNWx": "Stream Forwarding",
"W8nHSd": "FAQ",
"W9355R": "Unmute",
@ -157,6 +159,7 @@
"bbUGS7": "Recommended Stream Settings",
"bfvyfs": "Anon",
"cPIKU2": "Following",
"ccXLVi": "Category",
"cvAsEh": "Streamed on {date}",
"cyR7Kh": "Back",
"d5zWyh": "Test voice",
@ -203,6 +206,7 @@
"o8pHw3": "AUTO",
"oHPB8Q": "Zap {name}",
"oZrFyI": "Stream type should be HLS",
"p4N05H": "Upload",
"q+zTWM": "<s>{person}</s> zapped <s>{amount}</s> sats",
"r2Jjms": "Log In",
"rWBFZA": "Sexually explicit material ahead!",
@ -228,7 +232,6 @@
"wCIL7o": "Broadcast on Nostr",
"wEQDC6": "Edit",
"wMKVFz": "Select voice...",
"wOy57k": "Add stream goal",
"wRGjPp": "A nostr extension simply saves your keys so you can safely log in without having to re-enter them every time. ZapStream uses the extension to authorize actions on your behalf without ever seeing your key information. This has a significant advantage over having to trust that websites handle your credentials safely.",
"wTwfnv": "Invalid nostr address",
"wzWWzV": "Top zappers",

View File

@ -141,8 +141,8 @@ export function extractStreamInfo(ev?: NostrEvent) {
id: internal?.id,
name: internal.name,
genres: internal.tags,
className: internal.className
}
className: internal.className,
};
}
} else {
ret.gameId = game;