refactor: optimize bundle size
This commit is contained in:
parent
6905fb63fd
commit
13edd58987
@ -5,15 +5,14 @@
|
||||
"@emoji-mart/data": "^1.1.2",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
"@getalby/bitcoin-connect-react": "^1.1.0",
|
||||
"@noble/curves": "^1.1.0",
|
||||
"@noble/hashes": "^1.3.1",
|
||||
"@noble/curves": "^1.2.0",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-progress": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-toggle": "^1.0.3",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@scure/base": "^1.1.1",
|
||||
"@scure/base": "^1.1.3",
|
||||
"@snort/shared": "^1.0.10",
|
||||
"@snort/system": "^1.1.5",
|
||||
"@snort/system-react": "^1.1.5",
|
||||
@ -28,7 +27,6 @@
|
||||
"flag-icons": "^6.11.0",
|
||||
"hls.js": "^1.4.6",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash.uniqby": "^4.7.0",
|
||||
"marked": "^9.1.2",
|
||||
"qr-code-styling": "^1.6.0-rc.1",
|
||||
"react": "^18.2.0",
|
||||
@ -99,6 +97,7 @@
|
||||
"postcss": "^8.4.32",
|
||||
"prettier": "^2.8.8",
|
||||
"prop-types": "^15.8.1",
|
||||
"rollup-plugin-visualizer": "^5.10.0",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.0.5",
|
||||
|
5
src/element/alby-button.tsx
Normal file
5
src/element/alby-button.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { Button as AlbyZapsButton } from "@getalby/bitcoin-connect-react";
|
||||
|
||||
export default function AlbyButton() {
|
||||
return <AlbyZapsButton />
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
import { SnortContext, useEventReactions, useUserProfile } from "@snort/system-react";
|
||||
import { EventKind, NostrLink, TaggedNostrEvent } from "@snort/system";
|
||||
import React, { useContext, useMemo, useRef, useState } from "react";
|
||||
import React, { Suspense, lazy, useContext, useMemo, useRef, useState } from "react";
|
||||
import { useHover, useIntersectionObserver, useMediaQuery, useOnClickOutside } from "usehooks-ts";
|
||||
import { dedupe } from "@snort/shared";
|
||||
|
||||
import { EmojiPicker } from "./emoji-picker";
|
||||
const EmojiPicker = lazy(() => import("./emoji-picker"));
|
||||
import { Icon } from "./icon";
|
||||
import { Emoji as EmojiComponent } from "./emoji";
|
||||
import { Profile } from "./profile";
|
||||
@ -176,15 +176,15 @@ export function ChatMessage({
|
||||
style={
|
||||
isTablet
|
||||
? {
|
||||
display: showZapDialog || isHovering ? "flex" : "none",
|
||||
}
|
||||
display: showZapDialog || isHovering ? "flex" : "none",
|
||||
}
|
||||
: {
|
||||
position: "fixed",
|
||||
top: topOffset ? topOffset - 12 : 0,
|
||||
left: leftOffset ? leftOffset - 32 : 0,
|
||||
opacity: showZapDialog || isHovering ? 1 : 0,
|
||||
pointerEvents: showZapDialog || isHovering ? "auto" : "none",
|
||||
}
|
||||
position: "fixed",
|
||||
top: topOffset ? topOffset - 12 : 0,
|
||||
left: leftOffset ? leftOffset - 32 : 0,
|
||||
opacity: showZapDialog || isHovering ? 1 : 0,
|
||||
pointerEvents: showZapDialog || isHovering ? "auto" : "none",
|
||||
}
|
||||
}>
|
||||
{zapTarget && (
|
||||
<SendZapsDialog
|
||||
@ -211,14 +211,16 @@ export function ChatMessage({
|
||||
)}
|
||||
</div>
|
||||
{showEmojiPicker && (
|
||||
<EmojiPicker
|
||||
topOffset={topOffset ?? 0}
|
||||
leftOffset={leftOffset ?? 0}
|
||||
emojiPacks={emojiPacks}
|
||||
onEmojiSelect={onEmojiSelect}
|
||||
onClickOutside={() => setShowEmojiPicker(false)}
|
||||
ref={emojiRef}
|
||||
/>
|
||||
<Suspense>
|
||||
<EmojiPicker
|
||||
topOffset={topOffset ?? 0}
|
||||
leftOffset={leftOffset ?? 0}
|
||||
emojiPacks={emojiPacks}
|
||||
onEmojiSelect={onEmojiSelect}
|
||||
onClickOutside={() => setShowEmojiPicker(false)}
|
||||
ref={emojiRef}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -13,7 +13,7 @@ interface EmojiPickerProps {
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export function EmojiPicker({
|
||||
export default function EmojiPicker({
|
||||
topOffset,
|
||||
leftOffset,
|
||||
onEmojiSelect,
|
||||
|
@ -4,7 +4,6 @@ import { EventKind, NostrEvent, NostrLink, ParsedZap } from "@snort/system";
|
||||
import { useEventReactions } from "@snort/system-react";
|
||||
import { unixNow } from "@snort/shared";
|
||||
import { useMemo } from "react";
|
||||
import uniqBy from "lodash.uniqby";
|
||||
|
||||
import { Icon } from "./icon";
|
||||
import Spinner from "./spinner";
|
||||
@ -22,7 +21,7 @@ import { useLogin } from "@/hooks/login";
|
||||
import { useAddress } from "@/hooks/event";
|
||||
import { formatSats } from "@/number";
|
||||
import { LIVE_STREAM_CHAT, WEEK } from "@/const";
|
||||
import { findTag, getHost, getTagValues } from "@/utils";
|
||||
import { findTag, getHost, getTagValues, uniqBy } from "@/utils";
|
||||
import { TopZappers } from "./top-zappers";
|
||||
|
||||
export interface LiveChatOptions {
|
||||
|
@ -17,7 +17,7 @@ export interface VideoPlayerProps {
|
||||
poster?: string;
|
||||
}
|
||||
|
||||
export function LiveVideoPlayer(props: VideoPlayerProps) {
|
||||
export default function LiveVideoPlayer(props: VideoPlayerProps) {
|
||||
const video = useRef<HTMLVideoElement>(null);
|
||||
const hlsObj = useRef<Hls>(null);
|
||||
const streamCached = useMemo(() => props.stream, [props.stream]);
|
||||
|
@ -82,7 +82,7 @@ function renderToken(t: Token): ReactNode {
|
||||
}
|
||||
}
|
||||
|
||||
export const Markdown = forwardRef<HTMLDivElement, MarkdownProps>((props: MarkdownProps, ref) => {
|
||||
const Markdown = forwardRef<HTMLDivElement, MarkdownProps>((props: MarkdownProps, ref) => {
|
||||
const parsed = useMemo(() => {
|
||||
return marked.lexer(props.content);
|
||||
}, [props.content, props.tags]);
|
||||
@ -93,3 +93,5 @@ export const Markdown = forwardRef<HTMLDivElement, MarkdownProps>((props: Markdo
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default Markdown;
|
||||
|
@ -1,8 +1,9 @@
|
||||
import "./note.css";
|
||||
import { lazy } from "react";
|
||||
import { type NostrEvent, NostrPrefix } from "@snort/system";
|
||||
import { hexToBech32 } from "@snort/shared";
|
||||
|
||||
import { Markdown } from "./markdown";
|
||||
const Markdown = lazy(() => import("./markdown"));
|
||||
import { ExternalIconLink } from "./external-link";
|
||||
import { Profile } from "./profile";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import "./stream-cards.css";
|
||||
|
||||
import { forwardRef, useContext, useState } from "react";
|
||||
import { Suspense, forwardRef, lazy, useContext, useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import * as Dialog from "@radix-ui/react-dialog";
|
||||
import { DndProvider, useDrag, useDrop } from "react-dnd";
|
||||
@ -10,11 +10,11 @@ import { removeUndefined, unwrap } from "@snort/shared";
|
||||
import { NostrLink, TaggedNostrEvent } from "@snort/system";
|
||||
import { SnortContext } from "@snort/system-react";
|
||||
|
||||
const Markdown = lazy(() => import("./markdown"));
|
||||
import { Toggle } from "./toggle";
|
||||
import { Icon } from "./icon";
|
||||
import { ExternalLink } from "./external-link";
|
||||
import { FileUploader } from "./file-uploader";
|
||||
import { Markdown } from "./markdown";
|
||||
import { useLogin } from "@/hooks/login";
|
||||
import { useCards, useUserCards } from "@/hooks/cards";
|
||||
import { CARD, USER_CARDS } from "@/const";
|
||||
@ -57,7 +57,9 @@ const CardPreview = forwardRef(({ style, title, link, image, content }: CardPrev
|
||||
) : (
|
||||
<img className="card-image" src={image} alt={title} />
|
||||
))}
|
||||
<Markdown content={content} />
|
||||
<Suspense>
|
||||
<Markdown content={content} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
227
src/element/summary-chart.tsx
Normal file
227
src/element/summary-chart.tsx
Normal file
@ -0,0 +1,227 @@
|
||||
import { LIVE_STREAM_CHAT } from "@/const";
|
||||
import { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
|
||||
import { useLiveChatFeed } from "@/hooks/live-chat";
|
||||
import { formatSats } from "@/number";
|
||||
import { findTag } from "@/utils";
|
||||
import { unixNow } from "@snort/shared";
|
||||
import { NostrLink, NostrEvent, ParsedZap, EventKind } from "@snort/system";
|
||||
import { useEventReactions } from "@snort/system-react";
|
||||
import { useMemo } from "react";
|
||||
import { FormattedMessage, FormattedNumber, FormattedDate } from "react-intl";
|
||||
import { ResponsiveContainer, BarChart, XAxis, YAxis, Bar, Tooltip } from "recharts";
|
||||
import { StreamState } from "..";
|
||||
import { Profile } from "./profile";
|
||||
import { StatePill } from "./state-pill";
|
||||
|
||||
interface StatSlot {
|
||||
time: number;
|
||||
zaps: number;
|
||||
messages: number;
|
||||
reactions: number;
|
||||
}
|
||||
|
||||
export default function StreamSummary({ link, preload }: { link: NostrLink; preload?: NostrEvent }) {
|
||||
const ev = useCurrentStreamFeed(link, true, preload);
|
||||
const thisLink = ev ? NostrLink.fromEvent(ev) : undefined;
|
||||
const data = useLiveChatFeed(thisLink, undefined, 5_000);
|
||||
const reactions = useEventReactions(thisLink ?? link, data.reactions);
|
||||
|
||||
const chatSummary = useMemo(() => {
|
||||
return Object.entries(
|
||||
data.messages.reduce((acc, v) => {
|
||||
acc[v.pubkey] ??= [];
|
||||
acc[v.pubkey].push(v);
|
||||
return acc;
|
||||
}, {} as Record<string, Array<NostrEvent>>)
|
||||
)
|
||||
.map(([k, v]) => ({
|
||||
pubkey: k,
|
||||
messages: v,
|
||||
}))
|
||||
.sort((a, b) => (a.messages.length > b.messages.length ? -1 : 1));
|
||||
}, [data.messages]);
|
||||
|
||||
const zapsSummary = useMemo(() => {
|
||||
return Object.entries(
|
||||
reactions.zaps.reduce((acc, v) => {
|
||||
if (!v.sender) return acc;
|
||||
acc[v.sender] ??= [];
|
||||
acc[v.sender].push(v);
|
||||
return acc;
|
||||
}, {} as Record<string, Array<ParsedZap>>)
|
||||
)
|
||||
.map(([k, v]) => ({
|
||||
pubkey: k,
|
||||
zaps: v,
|
||||
total: v.reduce((acc, vv) => acc + vv.amount, 0),
|
||||
}))
|
||||
.sort((a, b) => (a.total > b.total ? -1 : 1));
|
||||
}, [reactions.zaps]);
|
||||
|
||||
const title = findTag(ev, "title");
|
||||
const summary = findTag(ev, "summary");
|
||||
const status = findTag(ev, "status");
|
||||
const starts = findTag(ev, "starts");
|
||||
|
||||
const Day = 60 * 60 * 24;
|
||||
const startTime = starts ? Number(starts) : ev?.created_at ?? unixNow();
|
||||
const endTime = status === StreamState.Live ? unixNow() : ev?.created_at ?? unixNow();
|
||||
|
||||
const streamLength = endTime - startTime;
|
||||
const windowSize = streamLength > Day ? Day : 60 * 10;
|
||||
|
||||
const stats = useMemo(() => {
|
||||
let min = unixNow();
|
||||
let max = 0;
|
||||
const ret = [...data.messages, ...data.reactions]
|
||||
.sort((a, b) => (a.created_at > b.created_at ? -1 : 1))
|
||||
.reduce((acc, v) => {
|
||||
const time = Math.floor(v.created_at - (v.created_at % windowSize));
|
||||
if (time < min) {
|
||||
min = time;
|
||||
}
|
||||
if (time > max) {
|
||||
max = time;
|
||||
}
|
||||
const key = time.toString();
|
||||
acc[key] ??= {
|
||||
time,
|
||||
zaps: 0,
|
||||
messages: 0,
|
||||
reactions: 0,
|
||||
};
|
||||
|
||||
if (v.kind === LIVE_STREAM_CHAT) {
|
||||
acc[key].messages++;
|
||||
} else if (v.kind === EventKind.ZapReceipt) {
|
||||
acc[key].zaps++;
|
||||
} else if (v.kind === EventKind.Reaction) {
|
||||
acc[key].reactions++;
|
||||
} else {
|
||||
console.debug("Uncounted stat", v);
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, StatSlot>);
|
||||
|
||||
// fill empty time slots
|
||||
for (let x = min; x < max; x += windowSize) {
|
||||
ret[x.toString()] ??= {
|
||||
time: x,
|
||||
zaps: 0,
|
||||
messages: 0,
|
||||
reactions: 0,
|
||||
};
|
||||
}
|
||||
return ret;
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<div className="stream-summary">
|
||||
<h1>{title}</h1>
|
||||
<p>{summary}</p>
|
||||
<div className="flex gap-1">
|
||||
<StatePill state={status as StreamState} />
|
||||
{streamLength > 0 && (
|
||||
<FormattedMessage
|
||||
defaultMessage="Stream Duration {duration} mins"
|
||||
id="J/+m9y"
|
||||
values={{
|
||||
duration: <FormattedNumber value={streamLength / 60} maximumFractionDigits={2} />,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<h2>
|
||||
<FormattedMessage defaultMessage="Summary" id="RrCui3" />
|
||||
</h2>
|
||||
<ResponsiveContainer height={200}>
|
||||
<BarChart data={Object.values(stats)} margin={{ left: 0, right: 0 }} style={{ userSelect: "none" }}>
|
||||
<XAxis tick={false} />
|
||||
<YAxis />
|
||||
<Bar dataKey="messages" fill="green" stackId="" />
|
||||
<Bar dataKey="zaps" fill="yellow" stackId="" />
|
||||
<Bar dataKey="reactions" fill="red" stackId="" />
|
||||
<Tooltip
|
||||
cursor={{ fill: "rgba(255,255,255,0.2)" }}
|
||||
content={({ active, payload }) => {
|
||||
if (active && payload && payload.length) {
|
||||
const data = payload[0].payload as StatSlot;
|
||||
return (
|
||||
<div className="plain-paper flex flex-col gap-2">
|
||||
<div>
|
||||
<FormattedDate value={data.time * 1000} timeStyle="short" dateStyle="short" />
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<FormattedMessage defaultMessage="Messages" id="hMzcSq" />
|
||||
</div>
|
||||
<div>{data.messages}</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<FormattedMessage defaultMessage="Reactions" id="XgWvGA" />
|
||||
</div>
|
||||
<div>{data.reactions}</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<FormattedMessage defaultMessage="Zaps" id="OEW7yJ" />
|
||||
</div>
|
||||
<div>{data.zaps}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
|
||||
<div className="flex gap-1">
|
||||
<div className="plain-paper flex-1">
|
||||
<h3>
|
||||
<FormattedMessage defaultMessage="Top Chatters" id="GGaJMU" />
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{chatSummary.slice(0, 5).map(a => (
|
||||
<div className="flex justify-between items-center" key={a.pubkey}>
|
||||
<Profile pubkey={a.pubkey} />
|
||||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="{n} messages"
|
||||
id="gzsn7k"
|
||||
values={{
|
||||
n: <FormattedNumber value={a.messages.length} />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="plain-paper flex-1">
|
||||
<h3>
|
||||
<FormattedMessage defaultMessage="Top Zappers" id="dVD/AR" />
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{zapsSummary.slice(0, 5).map(a => (
|
||||
<div className="flex justify-between items-center" key={a.pubkey}>
|
||||
<Profile pubkey={a.pubkey} />
|
||||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="{n} sats"
|
||||
id="CsCUYo"
|
||||
values={{
|
||||
n: formatSats(a.total),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
import { EventKind, NostrLink } from "@snort/system";
|
||||
import React, { useContext, useRef, useState } from "react";
|
||||
import React, { Suspense, lazy, useContext, useRef, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { SnortContext } from "@snort/system-react";
|
||||
import { unixNowMs } from "@snort/shared";
|
||||
|
||||
const EmojiPicker = lazy(() => import("./emoji-picker"));
|
||||
import { useLogin } from "@/hooks/login";
|
||||
import AsyncButton from "./async-button";
|
||||
import { Icon } from "./icon";
|
||||
import { Textarea } from "./textarea";
|
||||
import { EmojiPicker } from "./emoji-picker";
|
||||
import type { Emoji, EmojiPack } from "@/types";
|
||||
import { LIVE_STREAM_CHAT } from "@/const";
|
||||
import { TimeSync } from "@/index";
|
||||
@ -88,14 +88,16 @@ export function WriteMessage({ link, emojiPacks }: { link: NostrLink; emojiPacks
|
||||
<Icon name="face" className="write-emoji-button" />
|
||||
</div>
|
||||
{showEmojiPicker && (
|
||||
<EmojiPicker
|
||||
topOffset={topOffset ?? 0}
|
||||
leftOffset={leftOffset ?? 0}
|
||||
emojiPacks={emojiPacks}
|
||||
onEmojiSelect={onEmojiSelect}
|
||||
onClickOutside={() => setShowEmojiPicker(false)}
|
||||
ref={emojiRef}
|
||||
/>
|
||||
<Suspense>
|
||||
<EmojiPicker
|
||||
topOffset={topOffset ?? 0}
|
||||
leftOffset={leftOffset ?? 0}
|
||||
emojiPacks={emojiPacks}
|
||||
onEmojiSelect={onEmojiSelect}
|
||||
onClickOutside={() => setShowEmojiPicker(false)}
|
||||
ref={emojiRef}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
</div>
|
||||
<AsyncButton onClick={sendChatMessage} className="btn btn-border">
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useMemo } from "react";
|
||||
import uniqBy from "lodash.uniqby";
|
||||
|
||||
import { NostrEvent, NoteCollection, ReplaceableNoteStore, RequestBuilder } from "@snort/system";
|
||||
import { useRequestBuilder } from "@snort/system-react";
|
||||
import { findTag } from "@/utils";
|
||||
import { findTag, uniqBy } from "@/utils";
|
||||
import { EMOJI_PACK, USER_EMOJIS } from "@/const";
|
||||
import type { EmojiPack, EmojiTag, Tags } from "@/types";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import "./layout.css";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useState, useSyncExternalStore } from "react";
|
||||
import * as Dialog from "@radix-ui/react-dialog";
|
||||
import { Outlet, useNavigate } from "react-router-dom";
|
||||
import { Helmet } from "react-helmet";
|
||||
@ -16,6 +16,8 @@ import { LoginSignup } from "@/element/login-signup";
|
||||
import { Login } from "@/index";
|
||||
import { useLang } from "@/hooks/lang";
|
||||
import { AllLocales } from "@/intl";
|
||||
import { NewVersion } from "@/serviceWorker";
|
||||
import AsyncButton from "@/element/async-button";
|
||||
|
||||
export function LayoutPage() {
|
||||
const navigate = useNavigate();
|
||||
@ -137,6 +139,26 @@ export function LayoutPage() {
|
||||
</div>
|
||||
</header>
|
||||
<Outlet />
|
||||
{NewVersion && <NewVersionBanner />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NewVersionBanner() {
|
||||
const newVersion = useSyncExternalStore(c => NewVersion.hook(c), () => NewVersion.snapshot());
|
||||
if (!newVersion) return;
|
||||
|
||||
return <div className="fixed top-0 left-0 w-max flex bg-slate-800 py-2 px-4 opacity-95">
|
||||
<div className="grow">
|
||||
<h1>
|
||||
<FormattedMessage defaultMessage="A new version has been detected" id="RJ2VxG" />
|
||||
</h1>
|
||||
<p>
|
||||
<FormattedMessage defaultMessage="Refresh the page to use the latest version" id="Gmiwnd" />
|
||||
</p>
|
||||
</div>
|
||||
<AsyncButton onClick={() => window.location.reload()} className="btn">
|
||||
<FormattedMessage defaultMessage="Refresh" id="rELDbB" />
|
||||
</AsyncButton>
|
||||
</div>
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Suspense, lazy, useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { Button as AlbyZapsButton } from "@getalby/bitcoin-connect-react";
|
||||
import { hexToBech32, unwrap } from "@snort/shared";
|
||||
|
||||
const AlbyButton = lazy(() => import("@/element/alby-button"));
|
||||
import { useLogin } from "@/hooks/login";
|
||||
import Copy from "@/element/copy";
|
||||
import { NostrProviderDialog } from "@/element/nostr-provider-dialog";
|
||||
@ -53,7 +53,9 @@ export function SettingsPage() {
|
||||
<h1>
|
||||
<FormattedMessage defaultMessage="Zaps" id="OEW7yJ" />
|
||||
</h1>
|
||||
<AlbyZapsButton />
|
||||
<Suspense>
|
||||
<AlbyButton />
|
||||
</Suspense>
|
||||
<h1>
|
||||
<FormattedMessage defaultMessage="Stream Key" id="LknBsU" />
|
||||
</h1>
|
||||
|
@ -5,9 +5,9 @@ import { Helmet } from "react-helmet";
|
||||
import { NostrEvent } from "@snort/system";
|
||||
import { SnortContext, useUserProfile } from "@snort/system-react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useContext } from "react";
|
||||
import { Suspense, lazy, useContext } from "react";
|
||||
|
||||
import { LiveVideoPlayer } from "@/element/live-video-player";
|
||||
const LiveVideoPlayer = lazy(() => import("@/element/live-video-player"));
|
||||
import { findTag, getEventFromLocationState, getHost } from "@/utils";
|
||||
import { Profile, getName } from "@/element/profile";
|
||||
import { LiveChat } from "@/element/live-chat";
|
||||
@ -147,7 +147,9 @@ export function StreamPage({ link, evPreload }: { evPreload?: NostrEvent; link:
|
||||
<meta property="og:image" content={image ?? ""} />
|
||||
</Helmet>
|
||||
<div className="video-content">
|
||||
<LiveVideoPlayer stream={stream} poster={image} status={status} />
|
||||
<Suspense>
|
||||
<LiveVideoPlayer stream={stream} poster={image} status={status} />
|
||||
</Suspense>
|
||||
<ProfileInfo ev={ev} goal={goal} />
|
||||
<StreamCards host={host} />
|
||||
</div>
|
||||
|
@ -1,20 +1,9 @@
|
||||
import { useMemo } from "react";
|
||||
import { unixNow } from "@snort/shared";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { EventKind, NostrEvent, NostrLink, ParsedZap } from "@snort/system";
|
||||
import { useEventReactions } from "@snort/system-react";
|
||||
import { FormattedDate, FormattedMessage, FormattedNumber } from "react-intl";
|
||||
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
|
||||
|
||||
import { LIVE_STREAM_CHAT } from "@/const";
|
||||
import { Profile } from "@/element/profile";
|
||||
import { StatePill } from "@/element/state-pill";
|
||||
import { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
|
||||
import { useLiveChatFeed } from "@/hooks/live-chat";
|
||||
import { useStreamLink } from "@/hooks/stream-link";
|
||||
import { StreamState } from "@/index";
|
||||
import { formatSats } from "@/number";
|
||||
import { findTag, getEventFromLocationState } from "@/utils";
|
||||
import { getEventFromLocationState } from "@/utils";
|
||||
import { lazy } from "react";
|
||||
const StreamSummary = lazy(() => import("@/element/summary-chart"));
|
||||
|
||||
export function StreamSummaryPage() {
|
||||
const location = useLocation();
|
||||
@ -24,216 +13,3 @@ export function StreamSummaryPage() {
|
||||
return <StreamSummary link={link} preload={evPreload} />;
|
||||
}
|
||||
}
|
||||
|
||||
interface StatSlot {
|
||||
time: number;
|
||||
zaps: number;
|
||||
messages: number;
|
||||
reactions: number;
|
||||
}
|
||||
|
||||
export function StreamSummary({ link, preload }: { link: NostrLink; preload?: NostrEvent }) {
|
||||
const ev = useCurrentStreamFeed(link, true, preload);
|
||||
const thisLink = ev ? NostrLink.fromEvent(ev) : undefined;
|
||||
const data = useLiveChatFeed(thisLink, undefined, 5_000);
|
||||
const reactions = useEventReactions(thisLink ?? link, data.reactions);
|
||||
|
||||
const chatSummary = useMemo(() => {
|
||||
return Object.entries(
|
||||
data.messages.reduce((acc, v) => {
|
||||
acc[v.pubkey] ??= [];
|
||||
acc[v.pubkey].push(v);
|
||||
return acc;
|
||||
}, {} as Record<string, Array<NostrEvent>>)
|
||||
)
|
||||
.map(([k, v]) => ({
|
||||
pubkey: k,
|
||||
messages: v,
|
||||
}))
|
||||
.sort((a, b) => (a.messages.length > b.messages.length ? -1 : 1));
|
||||
}, [data.messages]);
|
||||
|
||||
const zapsSummary = useMemo(() => {
|
||||
return Object.entries(
|
||||
reactions.zaps.reduce((acc, v) => {
|
||||
if (!v.sender) return acc;
|
||||
acc[v.sender] ??= [];
|
||||
acc[v.sender].push(v);
|
||||
return acc;
|
||||
}, {} as Record<string, Array<ParsedZap>>)
|
||||
)
|
||||
.map(([k, v]) => ({
|
||||
pubkey: k,
|
||||
zaps: v,
|
||||
total: v.reduce((acc, vv) => acc + vv.amount, 0),
|
||||
}))
|
||||
.sort((a, b) => (a.total > b.total ? -1 : 1));
|
||||
}, [reactions.zaps]);
|
||||
|
||||
const title = findTag(ev, "title");
|
||||
const summary = findTag(ev, "summary");
|
||||
const status = findTag(ev, "status");
|
||||
const starts = findTag(ev, "starts");
|
||||
|
||||
const Day = 60 * 60 * 24;
|
||||
const startTime = starts ? Number(starts) : ev?.created_at ?? unixNow();
|
||||
const endTime = status === StreamState.Live ? unixNow() : ev?.created_at ?? unixNow();
|
||||
|
||||
const streamLength = endTime - startTime;
|
||||
const windowSize = streamLength > Day ? Day : 60 * 10;
|
||||
|
||||
const stats = useMemo(() => {
|
||||
let min = unixNow();
|
||||
let max = 0;
|
||||
const ret = [...data.messages, ...data.reactions]
|
||||
.sort((a, b) => (a.created_at > b.created_at ? -1 : 1))
|
||||
.reduce((acc, v) => {
|
||||
const time = Math.floor(v.created_at - (v.created_at % windowSize));
|
||||
if (time < min) {
|
||||
min = time;
|
||||
}
|
||||
if (time > max) {
|
||||
max = time;
|
||||
}
|
||||
const key = time.toString();
|
||||
acc[key] ??= {
|
||||
time,
|
||||
zaps: 0,
|
||||
messages: 0,
|
||||
reactions: 0,
|
||||
};
|
||||
|
||||
if (v.kind === LIVE_STREAM_CHAT) {
|
||||
acc[key].messages++;
|
||||
} else if (v.kind === EventKind.ZapReceipt) {
|
||||
acc[key].zaps++;
|
||||
} else if (v.kind === EventKind.Reaction) {
|
||||
acc[key].reactions++;
|
||||
} else {
|
||||
console.debug("Uncounted stat", v);
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, StatSlot>);
|
||||
|
||||
// fill empty time slots
|
||||
for (let x = min; x < max; x += windowSize) {
|
||||
ret[x.toString()] ??= {
|
||||
time: x,
|
||||
zaps: 0,
|
||||
messages: 0,
|
||||
reactions: 0,
|
||||
};
|
||||
}
|
||||
return ret;
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<div className="stream-summary">
|
||||
<h1>{title}</h1>
|
||||
<p>{summary}</p>
|
||||
<div className="flex gap-1">
|
||||
<StatePill state={status as StreamState} />
|
||||
{streamLength > 0 && (
|
||||
<FormattedMessage
|
||||
defaultMessage="Stream Duration {duration} mins"
|
||||
id="J/+m9y"
|
||||
values={{
|
||||
duration: <FormattedNumber value={streamLength / 60} maximumFractionDigits={2} />,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<h2>
|
||||
<FormattedMessage defaultMessage="Summary" id="RrCui3" />
|
||||
</h2>
|
||||
<ResponsiveContainer height={200}>
|
||||
<BarChart data={Object.values(stats)} margin={{ left: 0, right: 0 }} style={{ userSelect: "none" }}>
|
||||
<XAxis tick={false} />
|
||||
<YAxis />
|
||||
<Bar dataKey="messages" fill="green" stackId="" />
|
||||
<Bar dataKey="zaps" fill="yellow" stackId="" />
|
||||
<Bar dataKey="reactions" fill="red" stackId="" />
|
||||
<Tooltip
|
||||
cursor={{ fill: "rgba(255,255,255,0.2)" }}
|
||||
content={({ active, payload }) => {
|
||||
if (active && payload && payload.length) {
|
||||
const data = payload[0].payload as StatSlot;
|
||||
return (
|
||||
<div className="plain-paper flex flex-col gap-2">
|
||||
<div>
|
||||
<FormattedDate value={data.time * 1000} timeStyle="short" dateStyle="short" />
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<FormattedMessage defaultMessage="Messages" id="hMzcSq" />
|
||||
</div>
|
||||
<div>{data.messages}</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<FormattedMessage defaultMessage="Reactions" id="XgWvGA" />
|
||||
</div>
|
||||
<div>{data.reactions}</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<FormattedMessage defaultMessage="Zaps" id="OEW7yJ" />
|
||||
</div>
|
||||
<div>{data.zaps}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
|
||||
<div className="flex gap-1">
|
||||
<div className="plain-paper flex-1">
|
||||
<h3>
|
||||
<FormattedMessage defaultMessage="Top Chatters" id="GGaJMU" />
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{chatSummary.slice(0, 5).map(a => (
|
||||
<div className="flex justify-between items-center" key={a.pubkey}>
|
||||
<Profile pubkey={a.pubkey} />
|
||||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="{n} messages"
|
||||
id="gzsn7k"
|
||||
values={{
|
||||
n: <FormattedNumber value={a.messages.length} />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="plain-paper flex-1">
|
||||
<h3>
|
||||
<FormattedMessage defaultMessage="Top Zappers" id="dVD/AR" />
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{zapsSummary.slice(0, 5).map(a => (
|
||||
<div className="flex justify-between items-center" key={a.pubkey}>
|
||||
<Profile pubkey={a.pubkey} />
|
||||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="{n} sats"
|
||||
id="CsCUYo"
|
||||
values={{
|
||||
n: formatSats(a.total),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -14,3 +14,18 @@ self.addEventListener("message", event => {
|
||||
self.skipWaiting();
|
||||
}
|
||||
});
|
||||
self.addEventListener("install", event => {
|
||||
// delete all cache on install
|
||||
event.waitUntil(
|
||||
caches.keys().then(cacheNames => {
|
||||
return Promise.all(
|
||||
cacheNames.map(cacheName => {
|
||||
console.debug("Deleting cache: ", cacheName);
|
||||
return caches.delete(cacheName);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
// always skip waiting
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { ExternalStore } from "@snort/shared";
|
||||
|
||||
export function register() {
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", () => {
|
||||
@ -6,6 +8,25 @@ export function register() {
|
||||
}
|
||||
}
|
||||
|
||||
class BoolStore extends ExternalStore<boolean> {
|
||||
#value = false;
|
||||
|
||||
set value(v: boolean) {
|
||||
this.#value = v;
|
||||
this.notifyChange();
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.#value;
|
||||
}
|
||||
|
||||
takeSnapshot(): boolean {
|
||||
return this.#value;
|
||||
}
|
||||
}
|
||||
|
||||
export const NewVersion = new BoolStore();
|
||||
|
||||
async function registerValidSW(swUrl: string) {
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.register(swUrl);
|
||||
@ -18,6 +39,7 @@ async function registerValidSW(swUrl: string) {
|
||||
if (installingWorker.state === "installed") {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
console.log("Service worker updated, pending reload");
|
||||
NewVersion.value = true;
|
||||
} else {
|
||||
console.log("Content is cached for offline use.");
|
||||
}
|
||||
|
10
src/utils.ts
10
src/utils.ts
@ -68,3 +68,13 @@ export function getEventFromLocationState(state: unknown | undefined | null) {
|
||||
? (state as NostrEvent)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function uniqBy<T>(vals: Array<T>, key: (x: T) => string) {
|
||||
return Object.values(
|
||||
vals.reduce((acc, v) => {
|
||||
const k = key(v);
|
||||
acc[k] ??= v;
|
||||
return acc;
|
||||
}, {} as Record<string, T>)
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import { defineConfig } from "vite";
|
||||
import { visualizer } from "rollup-plugin-visualizer";
|
||||
import { vitePluginVersionMark } from "vite-plugin-version-mark";
|
||||
|
||||
export default defineConfig({
|
||||
@ -21,6 +22,11 @@ export default defineConfig({
|
||||
command: "git describe --always --tags",
|
||||
ifMeta: false,
|
||||
}),
|
||||
visualizer({
|
||||
open: true,
|
||||
gzipSize: true,
|
||||
filename: "build/stats.html",
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
outDir: "build",
|
||||
|
147
yarn.lock
147
yarn.lock
@ -2110,7 +2110,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/curves@npm:^1.1.0, @noble/curves@npm:^1.2.0":
|
||||
"@noble/curves@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "@noble/curves@npm:1.2.0"
|
||||
dependencies:
|
||||
@ -2126,7 +2126,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/hashes@npm:1.3.2, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.1":
|
||||
"@noble/hashes@npm:1.3.2, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.1":
|
||||
version: 1.3.2
|
||||
resolution: "@noble/hashes@npm:1.3.2"
|
||||
checksum: fe23536b436539d13f90e4b9be843cc63b1b17666a07634a2b1259dded6f490be3d050249e6af98076ea8f2ea0d56f578773c2197f2aa0eeaa5fba5bc18ba474
|
||||
@ -2835,13 +2835,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@scure/base@npm:^1.1.1, @scure/base@npm:^1.1.2, @scure/base@npm:~1.1.0":
|
||||
"@scure/base@npm:^1.1.2, @scure/base@npm:~1.1.0":
|
||||
version: 1.1.2
|
||||
resolution: "@scure/base@npm:1.1.2"
|
||||
checksum: f666b09dbd62ecb5fe6d0e7a629c8a86a972a47dc4f4555ebbbd7b09782b10a5f894fed9c3b8c74fd683b1588c064df079a44e9f695c075ccd98c30a8d3e91f7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@scure/base@npm:^1.1.3":
|
||||
version: 1.1.3
|
||||
resolution: "@scure/base@npm:1.1.3"
|
||||
checksum: 1606ab8a4db898cb3a1ada16c15437c3bce4e25854fadc8eb03ae93cbbbac1ed90655af4b0be3da37e12056fef11c0374499f69b9e658c9e5b7b3e06353c630c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@scure/bip32@npm:1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "@scure/bip32@npm:1.3.1"
|
||||
@ -4069,6 +4076,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cliui@npm:^8.0.1":
|
||||
version: 8.0.1
|
||||
resolution: "cliui@npm:8.0.1"
|
||||
dependencies:
|
||||
string-width: ^4.2.0
|
||||
strip-ansi: ^6.0.1
|
||||
wrap-ansi: ^7.0.0
|
||||
checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"color-convert@npm:^1.9.0":
|
||||
version: 1.9.3
|
||||
resolution: "color-convert@npm:1.9.3"
|
||||
@ -4368,6 +4386,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"define-lazy-prop@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "define-lazy-prop@npm:2.0.0"
|
||||
checksum: 0115fdb065e0490918ba271d7339c42453d209d4cb619dfe635870d906731eff3e1ade8028bb461ea27ce8264ec5e22c6980612d332895977e89c1bbc80fcee2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"define-properties@npm:^1.1.3, define-properties@npm:^1.1.4, define-properties@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "define-properties@npm:1.2.0"
|
||||
@ -5144,6 +5169,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-caller-file@npm:^2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "get-caller-file@npm:2.0.5"
|
||||
checksum: b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.0, get-intrinsic@npm:^1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "get-intrinsic@npm:1.2.1"
|
||||
@ -5616,6 +5648,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1":
|
||||
version: 2.2.1
|
||||
resolution: "is-docker@npm:2.2.1"
|
||||
bin:
|
||||
is-docker: cli.js
|
||||
checksum: 3fef7ddbf0be25958e8991ad941901bf5922ab2753c46980b60b05c1bf9c9c2402d35e6dc32e4380b980ef5e1970a5d9d5e5aa2e02d77727c3b6b5e918474c56
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-extglob@npm:^2.1.1":
|
||||
version: 2.1.1
|
||||
resolution: "is-extglob@npm:2.1.1"
|
||||
@ -5790,6 +5831,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-wsl@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "is-wsl@npm:2.2.0"
|
||||
dependencies:
|
||||
is-docker: ^2.0.0
|
||||
checksum: 20849846ae414997d290b75e16868e5261e86ff5047f104027026fd61d8b5a9b0b3ade16239f35e1a067b3c7cc02f70183cb661010ed16f4b6c7c93dad1b19d8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isarray@npm:^2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "isarray@npm:2.0.5"
|
||||
@ -6093,13 +6143,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.uniqby@npm:^4.7.0":
|
||||
version: 4.7.0
|
||||
resolution: "lodash.uniqby@npm:4.7.0"
|
||||
checksum: 659264545a95726d1493123345aad8cbf56e17810fa9a0b029852c6d42bc80517696af09d99b23bef1845d10d95e01b8b4a1da578f22aeba7a30d3e0022a4938
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21":
|
||||
version: 4.17.21
|
||||
resolution: "lodash@npm:4.17.21"
|
||||
@ -6538,6 +6581,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"open@npm:^8.4.0":
|
||||
version: 8.4.2
|
||||
resolution: "open@npm:8.4.2"
|
||||
dependencies:
|
||||
define-lazy-prop: ^2.0.0
|
||||
is-docker: ^2.1.1
|
||||
is-wsl: ^2.2.0
|
||||
checksum: 6388bfff21b40cb9bd8f913f9130d107f2ed4724ea81a8fd29798ee322b361ca31fa2cdfb491a5c31e43a3996cfe9566741238c7a741ada8d7af1cb78d85cf26
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"optionator@npm:^0.9.3":
|
||||
version: 0.9.3
|
||||
resolution: "optionator@npm:0.9.3"
|
||||
@ -7273,6 +7327,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"require-directory@npm:^2.1.1":
|
||||
version: 2.1.1
|
||||
resolution: "require-directory@npm:2.1.1"
|
||||
checksum: fb47e70bf0001fdeabdc0429d431863e9475e7e43ea5f94ad86503d918423c1543361cc5166d713eaa7029dd7a3d34775af04764bebff99ef413111a5af18c80
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"require-from-string@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "require-from-string@npm:2.0.2"
|
||||
@ -7378,6 +7439,25 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rollup-plugin-visualizer@npm:^5.10.0":
|
||||
version: 5.10.0
|
||||
resolution: "rollup-plugin-visualizer@npm:5.10.0"
|
||||
dependencies:
|
||||
open: ^8.4.0
|
||||
picomatch: ^2.3.1
|
||||
source-map: ^0.7.4
|
||||
yargs: ^17.5.1
|
||||
peerDependencies:
|
||||
rollup: 2.x || 3.x || 4.x
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
bin:
|
||||
rollup-plugin-visualizer: dist/bin/cli.js
|
||||
checksum: b60d50bd3d69fadcba2536bcd0f1926bc26f23ad8872108aad005f050f4d379969bfe09c658f9ae81efcf4329aedf3b0b7fcd80d9a650401b065cf514c8ca78b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rollup@npm:^2.43.1":
|
||||
version: 2.79.1
|
||||
resolution: "rollup@npm:2.79.1"
|
||||
@ -7667,6 +7747,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"source-map@npm:^0.7.4":
|
||||
version: 0.7.4
|
||||
resolution: "source-map@npm:0.7.4"
|
||||
checksum: 01cc5a74b1f0e1d626a58d36ad6898ea820567e87f18dfc9d24a9843a351aaa2ec09b87422589906d6ff1deed29693e176194dc88bcae7c9a852dc74b311dbf5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"source-map@npm:^0.8.0-beta.0":
|
||||
version: 0.8.0-beta.0
|
||||
resolution: "source-map@npm:0.8.0-beta.0"
|
||||
@ -7710,15 +7797,14 @@ __metadata:
|
||||
"@formatjs/cli": ^6.1.3
|
||||
"@formatjs/ts-transformer": ^3.13.3
|
||||
"@getalby/bitcoin-connect-react": ^1.1.0
|
||||
"@noble/curves": ^1.1.0
|
||||
"@noble/hashes": ^1.3.1
|
||||
"@noble/curves": ^1.2.0
|
||||
"@radix-ui/react-collapsible": ^1.0.3
|
||||
"@radix-ui/react-dialog": ^1.0.4
|
||||
"@radix-ui/react-progress": ^1.0.3
|
||||
"@radix-ui/react-tabs": ^1.0.4
|
||||
"@radix-ui/react-toggle": ^1.0.3
|
||||
"@react-hook/resize-observer": ^1.2.6
|
||||
"@scure/base": ^1.1.1
|
||||
"@scure/base": ^1.1.3
|
||||
"@snort/shared": ^1.0.10
|
||||
"@snort/system": ^1.1.5
|
||||
"@snort/system-react": ^1.1.5
|
||||
@ -7747,7 +7833,6 @@ __metadata:
|
||||
flag-icons: ^6.11.0
|
||||
hls.js: ^1.4.6
|
||||
lodash: ^4.17.21
|
||||
lodash.uniqby: ^4.7.0
|
||||
marked: ^9.1.2
|
||||
postcss: ^8.4.32
|
||||
prettier: ^2.8.8
|
||||
@ -7764,6 +7849,7 @@ __metadata:
|
||||
react-router-dom: ^6.13.0
|
||||
react-tag-input-component: ^2.0.2
|
||||
recharts: ^2.9.3
|
||||
rollup-plugin-visualizer: ^5.10.0
|
||||
semantic-sdp: ^3.26.3
|
||||
tailwindcss: ^3.3.5
|
||||
typescript: ^5.2.2
|
||||
@ -7780,7 +7866,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.3":
|
||||
"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3":
|
||||
version: 4.2.3
|
||||
resolution: "string-width@npm:4.2.3"
|
||||
dependencies:
|
||||
@ -8787,7 +8873,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "wrap-ansi@npm:7.0.0"
|
||||
dependencies:
|
||||
@ -8831,6 +8917,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"y18n@npm:^5.0.5":
|
||||
version: 5.0.8
|
||||
resolution: "y18n@npm:5.0.8"
|
||||
checksum: 54f0fb95621ee60898a38c572c515659e51cc9d9f787fb109cef6fde4befbe1c4602dc999d30110feee37456ad0f1660fa2edcfde6a9a740f86a290999550d30
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yallist@npm:^3.0.2":
|
||||
version: 3.1.1
|
||||
resolution: "yallist@npm:3.1.1"
|
||||
@ -8852,6 +8945,28 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yargs-parser@npm:^21.1.1":
|
||||
version: 21.1.1
|
||||
resolution: "yargs-parser@npm:21.1.1"
|
||||
checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yargs@npm:^17.5.1":
|
||||
version: 17.7.2
|
||||
resolution: "yargs@npm:17.7.2"
|
||||
dependencies:
|
||||
cliui: ^8.0.1
|
||||
escalade: ^3.1.1
|
||||
get-caller-file: ^2.0.5
|
||||
require-directory: ^2.1.1
|
||||
string-width: ^4.2.3
|
||||
y18n: ^5.0.5
|
||||
yargs-parser: ^21.1.1
|
||||
checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yocto-queue@npm:^0.1.0":
|
||||
version: 0.1.0
|
||||
resolution: "yocto-queue@npm:0.1.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user