mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-17 14:04:21 +01:00
feat: show thread
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import type { Component, JSX } from 'solid-js';
|
||||
import { Show, type JSX, type Component } from 'solid-js';
|
||||
import { useHandleCommand } from '@/hooks/useCommandBus';
|
||||
import { ColumnContext, useColumnState } from '@/components/ColumnContext';
|
||||
import ColumnContentDisplay from '@/components/ColumnContentDisplay';
|
||||
|
||||
export type ColumnProps = {
|
||||
name: string;
|
||||
@@ -12,6 +14,8 @@ export type ColumnProps = {
|
||||
const Column: Component<ColumnProps> = (props) => {
|
||||
let columnDivRef: HTMLDivElement | undefined;
|
||||
|
||||
const columnState = useColumnState();
|
||||
|
||||
const width = () => props.width ?? 'medium';
|
||||
|
||||
useHandleCommand(() => ({
|
||||
@@ -33,9 +37,10 @@ const Column: Component<ColumnProps> = (props) => {
|
||||
}));
|
||||
|
||||
return (
|
||||
<ColumnContext.Provider value={columnState}>
|
||||
<div
|
||||
ref={columnDivRef}
|
||||
class="flex w-[80vw] shrink-0 snap-center snap-always flex-col border-r sm:snap-align-none"
|
||||
class="relative flex w-[80vw] shrink-0 snap-center snap-always flex-col border-r sm:snap-align-none"
|
||||
classList={{
|
||||
'sm:w-[500px]': width() === 'widest',
|
||||
'sm:w-[350px]': width() === 'wide',
|
||||
@@ -48,7 +53,22 @@ const Column: Component<ColumnProps> = (props) => {
|
||||
<span class="column-name">{props.name}</span>
|
||||
</div>
|
||||
<ul class="flex flex-col overflow-y-scroll scroll-smooth">{props.children}</ul>
|
||||
<Show when={columnState.columnState.content} keyed>
|
||||
{(columnContent) => (
|
||||
<div class="absolute h-full w-full bg-white">
|
||||
<div class="flex h-8 shrink-0 items-center border-b bg-white px-2">
|
||||
<button class="w-full text-left" onClick={() => columnState?.clearColumnContext()}>
|
||||
<ホームに戻る
|
||||
</button>
|
||||
</div>
|
||||
<ul class="flex h-full flex-col overflow-y-scroll scroll-smooth">
|
||||
<ColumnContentDisplay columnContent={columnContent} />
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
</ColumnContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
33
src/components/ColumnContentDisplay.tsx
Normal file
33
src/components/ColumnContentDisplay.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Switch, Match, type Component } from 'solid-js';
|
||||
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
|
||||
import { type ColumnContent } from '@/components/ColumnContext';
|
||||
import Timeline from '@/components/Timeline';
|
||||
import useSubscription from '@/nostr/useSubscription';
|
||||
|
||||
const RepliesDisplay: Component<{ eventId: string }> = (props) => {
|
||||
const { config } = useConfig();
|
||||
|
||||
const { events } = useSubscription(() => ({
|
||||
relayUrls: config().relayUrls,
|
||||
filters: [
|
||||
{ kinds: [1], ids: [props.eventId], limit: 25 },
|
||||
{ kinds: [1], '#e': [props.eventId], limit: 25 },
|
||||
],
|
||||
}));
|
||||
|
||||
return <Timeline events={[...events()].reverse()} />;
|
||||
};
|
||||
|
||||
const ColumnContentDisplay: Component<{ columnContent: ColumnContent }> = (props) => {
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={props.columnContent.type === 'Replies' && props.columnContent} keyed>
|
||||
{(replies) => <RepliesDisplay eventId={replies.eventId} />}
|
||||
</Match>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColumnContentDisplay;
|
||||
31
src/components/ColumnContext.tsx
Normal file
31
src/components/ColumnContext.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { createContext, useContext, type JSX } from 'solid-js';
|
||||
import { createStore } from 'solid-js/store';
|
||||
|
||||
export type ColumnContent = {
|
||||
type: 'Replies';
|
||||
eventId: string;
|
||||
};
|
||||
|
||||
export type ColumnState = {
|
||||
content?: ColumnContent;
|
||||
};
|
||||
|
||||
export type UseColumnState = {
|
||||
columnState: ColumnState;
|
||||
setColumnContent: (content: ColumnContent) => void;
|
||||
clearColumnContext: () => void;
|
||||
};
|
||||
|
||||
export const ColumnContext = createContext<UseColumnState>();
|
||||
|
||||
export const useColumnContext = () => useContext(ColumnContext);
|
||||
|
||||
export const useColumnState = (): UseColumnState => {
|
||||
const [columnState, setColumnState] = createStore<ColumnState>({});
|
||||
|
||||
return {
|
||||
columnState,
|
||||
setColumnContent: (content: ColumnContent) => setColumnState('content', content),
|
||||
clearColumnContext: () => setColumnState('content', undefined),
|
||||
};
|
||||
};
|
||||
@@ -186,7 +186,7 @@ const ConfigUI = (props: ConfigProps) => {
|
||||
<div class="relative">
|
||||
<div class="flex flex-col gap-1">
|
||||
<h2 class="flex-1 text-center font-bold">設定</h2>
|
||||
<button class="absolute top-1 right-0 h-4 w-4" onClick={() => props.onClose?.()}>
|
||||
<button class="absolute top-1 right-0 z-0 h-4 w-4" onClick={() => props.onClose?.()}>
|
||||
<XMark />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ const Modal: Component<ModalProps> = (props) => {
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
class="absolute top-0 left-0 flex h-screen w-screen cursor-default place-content-center place-items-center bg-black/30"
|
||||
class="absolute top-0 left-0 z-10 flex h-screen w-screen cursor-default place-content-center place-items-center bg-black/30"
|
||||
onClick={handleClickContainer}
|
||||
>
|
||||
{props.children}
|
||||
|
||||
@@ -29,11 +29,11 @@ export type ProfileDisplayProps = {
|
||||
};
|
||||
|
||||
const FollowersCount: Component<{ pubkey: string }> = (props) => {
|
||||
const { followersPubkeys } = useFollowers(() => ({
|
||||
const { count } = useFollowers(() => ({
|
||||
pubkey: props.pubkey,
|
||||
}));
|
||||
|
||||
return <span>{followersPubkeys().length}</span>;
|
||||
return <>{count()}</>;
|
||||
};
|
||||
|
||||
const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
||||
@@ -66,9 +66,11 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
||||
);
|
||||
const following = () => myFollowingPubkeys().includes(props.pubkey);
|
||||
|
||||
const { followingPubkeys: userFollowingPubkeys } = useFollowings(() => ({
|
||||
const { followingPubkeys: userFollowingPubkeys, query: userFollowingQuery } = useFollowings(
|
||||
() => ({
|
||||
pubkey: props.pubkey,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
const followed = () => {
|
||||
const p = pubkey();
|
||||
return p != null && userFollowingPubkeys().includes(p);
|
||||
@@ -86,6 +88,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
||||
until: epoch(),
|
||||
},
|
||||
],
|
||||
continuous: false,
|
||||
}));
|
||||
|
||||
return (
|
||||
@@ -210,14 +213,28 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
||||
<div class="flex border-t px-4 py-2">
|
||||
<div class="flex flex-1 flex-col items-start">
|
||||
<div class="text-sm">フォロー</div>
|
||||
<div class="text-xl">{userFollowingPubkeys().length}</div>
|
||||
<div class="text-xl">
|
||||
<Show
|
||||
when={userFollowingQuery.isFetched}
|
||||
fallback={<span class="text-sm">読み込み中</span>}
|
||||
>
|
||||
{userFollowingPubkeys().length}
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col items-start">
|
||||
<div class="text-sm">フォロワー</div>
|
||||
<div class="text-xl">
|
||||
<Show
|
||||
when={showFollowers()}
|
||||
fallback={<button onClick={() => setShowFollowers(true)}>読み込む</button>}
|
||||
fallback={
|
||||
<button
|
||||
class="text-sm hover:text-stone-800 hover:underline"
|
||||
onClick={() => setShowFollowers(true)}
|
||||
>
|
||||
読み込む
|
||||
</button>
|
||||
}
|
||||
keyed
|
||||
>
|
||||
<FollowersCount pubkey={props.pubkey} />
|
||||
|
||||
@@ -8,11 +8,6 @@ import ArrowPathRoundedSquare from 'heroicons/24/outline/arrow-path-rounded-squa
|
||||
import ChatBubbleLeft from 'heroicons/24/outline/chat-bubble-left.svg';
|
||||
import EllipsisHorizontal from 'heroicons/24/outline/ellipsis-horizontal.svg';
|
||||
|
||||
import GeneralUserMentionDisplay from '@/components/textNote/GeneralUserMentionDisplay';
|
||||
import ContentWarningDisplay from '@/components/textNote/ContentWarningDisplay';
|
||||
import TextNoteContentDisplay from '@/components/textNote/TextNoteContentDisplay';
|
||||
import NotePostForm from '@/components/NotePostForm';
|
||||
|
||||
import eventWrapper from '@/core/event';
|
||||
|
||||
import useProfile from '@/nostr/useProfile';
|
||||
@@ -23,12 +18,19 @@ import useReactions from '@/nostr/useReactions';
|
||||
import useDeprecatedReposts from '@/nostr/useDeprecatedReposts';
|
||||
|
||||
import useFormatDate from '@/hooks/useFormatDate';
|
||||
import useModalState from '@/hooks/useModalState';
|
||||
|
||||
import UserNameDisplay from '@/components/UserDisplayName';
|
||||
import TextNoteDisplayById from '@/components/textNote/TextNoteDisplayById';
|
||||
import { useColumnContext } from '@/components/ColumnContext';
|
||||
import GeneralUserMentionDisplay from '@/components/textNote/GeneralUserMentionDisplay';
|
||||
import ContentWarningDisplay from '@/components/textNote/ContentWarningDisplay';
|
||||
import TextNoteContentDisplay from '@/components/textNote/TextNoteContentDisplay';
|
||||
import NotePostForm from '@/components/NotePostForm';
|
||||
|
||||
import ensureNonNull from '@/utils/ensureNonNull';
|
||||
import npubEncodeFallback from '@/utils/npubEncodeFallback';
|
||||
import useModalState from '@/hooks/useModalState';
|
||||
import UserNameDisplay from '../UserDisplayName';
|
||||
import TextNoteDisplayById from './TextNoteDisplayById';
|
||||
import useSubscription from '@/nostr/useSubscription';
|
||||
|
||||
export type TextNoteDisplayProps = {
|
||||
event: NostrEvent;
|
||||
@@ -43,6 +45,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
const formatDate = useFormatDate();
|
||||
const pubkey = usePubkey();
|
||||
const { showProfile } = useModalState();
|
||||
const columnContext = useColumnContext();
|
||||
|
||||
const [showReplyForm, setShowReplyForm] = createSignal(false);
|
||||
const closeReplyForm = () => setShowReplyForm(false);
|
||||
@@ -60,11 +63,11 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
}));
|
||||
|
||||
const { reactions, isReactedBy, invalidateReactions } = useReactions(() => ({
|
||||
eventId: props.event.id, // TODO いつかなおす
|
||||
eventId: props.event.id,
|
||||
}));
|
||||
|
||||
const { reposts, isRepostedBy, invalidateDeprecatedReposts } = useDeprecatedReposts(() => ({
|
||||
eventId: props.event.id, // TODO いつかなおす
|
||||
eventId: props.event.id,
|
||||
}));
|
||||
|
||||
const commands = useCommands();
|
||||
@@ -118,7 +121,9 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
|
||||
const createdAt = () => formatDate(event().createdAtAsDate());
|
||||
|
||||
const handleRepost: JSX.EventHandler<HTMLButtonElement, MouseEvent> = () => {
|
||||
const handleRepost: JSX.EventHandler<HTMLButtonElement, MouseEvent> = (ev) => {
|
||||
ev.stopPropagation();
|
||||
|
||||
if (isRepostedByMe()) {
|
||||
// TODO remove reaction
|
||||
return;
|
||||
@@ -134,7 +139,9 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleReaction: JSX.EventHandler<HTMLButtonElement, MouseEvent> = () => {
|
||||
const handleReaction: JSX.EventHandler<HTMLButtonElement, MouseEvent> = (ev) => {
|
||||
ev.stopPropagation();
|
||||
|
||||
if (isReactedByMe()) {
|
||||
// TODO remove reaction
|
||||
return;
|
||||
@@ -158,11 +165,22 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="nostr-textnote flex flex-col">
|
||||
<div
|
||||
class="nostr-textnote flex flex-col"
|
||||
onClick={() => {
|
||||
columnContext?.setColumnContent({
|
||||
type: 'Replies',
|
||||
eventId: event().rootEvent()?.id ?? props.event.id,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div class="flex w-full gap-1">
|
||||
<button
|
||||
class="author-icon h-10 w-10 shrink-0 overflow-hidden"
|
||||
onClick={() => showProfile(event().pubkey)}
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
showProfile(event().pubkey);
|
||||
}}
|
||||
>
|
||||
<Show when={author()?.picture}>
|
||||
{/* TODO 画像は脆弱性回避のためにimgじゃない方法で読み込みたい */}
|
||||
@@ -173,7 +191,10 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
<div class="flex justify-between gap-1 text-xs">
|
||||
<button
|
||||
class="author flex min-w-0 truncate hover:text-blue-500"
|
||||
onClick={() => showProfile(event().pubkey)}
|
||||
onClick={() => {
|
||||
ev.stopPropagation();
|
||||
showProfile(event().pubkey);
|
||||
}}
|
||||
>
|
||||
{/* TODO link to author */}
|
||||
<Show when={(author()?.display_name?.length ?? 0) > 0}>
|
||||
@@ -211,7 +232,10 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
{(replyToPubkey: string) => (
|
||||
<button
|
||||
class="pr-1 text-blue-500 hover:underline"
|
||||
onClick={() => showProfile(replyToPubkey)}
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
showProfile(replyToPubkey);
|
||||
}}
|
||||
>
|
||||
<GeneralUserMentionDisplay pubkey={replyToPubkey} />
|
||||
</button>
|
||||
@@ -229,7 +253,10 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
<Show when={overflow()}>
|
||||
<button
|
||||
class="text-xs text-stone-600 hover:text-stone-800"
|
||||
onClick={() => setShowOverflow((current) => !current)}
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
setShowOverflow((current) => !current);
|
||||
}}
|
||||
>
|
||||
<Show when={!showOverflow()} fallback="隠す">
|
||||
続きを読む
|
||||
@@ -240,7 +267,10 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
<div class="actions flex w-48 items-center justify-between gap-8 pt-1">
|
||||
<button
|
||||
class="h-4 w-4 shrink-0 text-zinc-400"
|
||||
onClick={() => setShowReplyForm((current) => !current)}
|
||||
onClick={() => {
|
||||
stopPropagation();
|
||||
setShowReplyForm((current) => !current);
|
||||
}}
|
||||
>
|
||||
<ChatBubbleLeft />
|
||||
</button>
|
||||
@@ -285,7 +315,10 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
<div>
|
||||
<button
|
||||
class="h-4 w-4 text-zinc-400"
|
||||
onClick={() => setShowMenu((current) => !current)}
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
setShowMenu((current) => !current);
|
||||
}}
|
||||
>
|
||||
<EllipsisHorizontal />
|
||||
</button>
|
||||
|
||||
@@ -8,7 +8,7 @@ export type Task<TaskArgs, TaskResult> = {
|
||||
};
|
||||
|
||||
export type UseBatchProps<TaskArgs, TaskResult> = {
|
||||
executor: (task: Task<TaskArgs, TaskResult>[]) => void;
|
||||
executor: (tasks: Task<TaskArgs, TaskResult>[]) => void;
|
||||
interval?: number;
|
||||
batchSize?: number;
|
||||
};
|
||||
|
||||
@@ -9,11 +9,14 @@ import {
|
||||
import { type Event as NostrEvent, type Filter, Kind } from 'nostr-tools';
|
||||
import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query';
|
||||
|
||||
import timeout from '@/utils/timeout';
|
||||
import useBatch, { type Task } from '@/nostr/useBatch';
|
||||
import eventWrapper from '@/core/event';
|
||||
import useConfig from './useConfig';
|
||||
import usePool from './usePool';
|
||||
|
||||
import useBatch, { type Task } from '@/nostr/useBatch';
|
||||
import useStats from '@/nostr/useStats';
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
import usePool from '@/nostr/usePool';
|
||||
|
||||
import timeout from '@/utils/timeout';
|
||||
|
||||
type TaskArg =
|
||||
| { type: 'Profile'; pubkey: string }
|
||||
@@ -63,7 +66,7 @@ export type UseTextNoteProps = {
|
||||
};
|
||||
|
||||
export type UseTextNote = {
|
||||
event: Accessor<NostrEvent | null>;
|
||||
event: () => NostrEvent | null;
|
||||
query: CreateQueryResult<NostrEvent | null>;
|
||||
};
|
||||
|
||||
@@ -73,8 +76,8 @@ export type UseReactionsProps = {
|
||||
};
|
||||
|
||||
export type UseReactions = {
|
||||
reactions: Accessor<NostrEvent[]>;
|
||||
reactionsGroupedByContent: Accessor<Map<string, NostrEvent[]>>;
|
||||
reactions: () => NostrEvent[];
|
||||
reactionsGroupedByContent: () => Map<string, NostrEvent[]>;
|
||||
isReactedBy: (pubkey: string) => boolean;
|
||||
invalidateReactions: () => Promise<void>;
|
||||
query: CreateQueryResult<NostrEvent[]>;
|
||||
@@ -86,7 +89,7 @@ export type UseDeprecatedRepostsProps = {
|
||||
};
|
||||
|
||||
export type UseDeprecatedReposts = {
|
||||
reposts: Accessor<NostrEvent[]>;
|
||||
reposts: () => NostrEvent[];
|
||||
isRepostedBy: (pubkey: string) => boolean;
|
||||
invalidateDeprecatedReposts: () => Promise<void>;
|
||||
query: CreateQueryResult<NostrEvent[]>;
|
||||
@@ -104,18 +107,22 @@ type Following = {
|
||||
};
|
||||
|
||||
export type UseFollowings = {
|
||||
followings: Accessor<Following[]>;
|
||||
followingPubkeys: Accessor<string[]>;
|
||||
followings: () => Following[];
|
||||
followingPubkeys: () => string[];
|
||||
query: CreateQueryResult<NostrEvent | null>;
|
||||
};
|
||||
|
||||
let count = 0;
|
||||
|
||||
setInterval(() => console.log('batchSub', count), 1000);
|
||||
const { setActiveBatchSubscriptions } = useStats();
|
||||
|
||||
setInterval(() => {
|
||||
setActiveBatchSubscriptions(count);
|
||||
}, 1000);
|
||||
|
||||
const { exec } = useBatch<TaskArg, TaskRes>(() => ({
|
||||
interval: 2000,
|
||||
batchSize: 100,
|
||||
batchSize: 150,
|
||||
executor: (tasks) => {
|
||||
const profileTasks = new Map<string, Task<TaskArg, TaskRes>[]>();
|
||||
const textNoteTasks = new Map<string, Task<TaskArg, TaskRes>[]>();
|
||||
@@ -201,7 +208,7 @@ const { exec } = useBatch<TaskArg, TaskRes>(() => ({
|
||||
const { config } = useConfig();
|
||||
const pool = usePool();
|
||||
|
||||
const sub = pool().sub(config().relayUrls, filters);
|
||||
const sub = pool().sub(config().relayUrls, filters, {});
|
||||
|
||||
count += 1;
|
||||
|
||||
@@ -300,7 +307,6 @@ export const useProfile = (propsProvider: () => UseProfileProps | null): UseProf
|
||||
|
||||
export const useTextNote = (propsProvider: () => UseTextNoteProps | null): UseTextNote => {
|
||||
const props = createMemo(propsProvider);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const query = createQuery(
|
||||
() => ['useTextNote', props()] as const,
|
||||
@@ -317,8 +323,11 @@ export const useTextNote = (propsProvider: () => UseTextNoteProps | null): UseTe
|
||||
},
|
||||
{
|
||||
// Text notes never change, so they can be stored for a long time.
|
||||
// However, events tend to be unreferenced as time passes.
|
||||
staleTime: 4 * 60 * 60 * 1000, // 4 hour
|
||||
cacheTime: 4 * 60 * 60 * 1000, // 4 hour
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -449,6 +458,7 @@ export const useFollowings = (propsProvider: () => UseFollowingsProps | null): U
|
||||
staleTime: 5 * 60 * 1000, // 5 min
|
||||
cacheTime: 24 * 60 * 60 * 1000, // 24 hour
|
||||
refetchOnWindowFocus: false,
|
||||
refetchInterval: 5 * 60 * 1000, // 5 min
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -22,5 +22,7 @@ export default function useFollowers(propsProvider: () => UseFollowersProps) {
|
||||
|
||||
const followersPubkeys = () => uniq(events()?.map((ev) => ev.pubkey));
|
||||
|
||||
return { followersPubkeys };
|
||||
const count = () => followersPubkeys().length;
|
||||
|
||||
return { followersPubkeys, count };
|
||||
}
|
||||
|
||||
26
src/nostr/useStats.ts
Normal file
26
src/nostr/useStats.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createStore } from 'solid-js/store';
|
||||
|
||||
export type Stats = {
|
||||
activeSubscriptions: number;
|
||||
activeBatchSubscriptions: number;
|
||||
};
|
||||
|
||||
const [stats, setStats] = createStore<Stats>({
|
||||
activeSubscriptions: 0,
|
||||
activeBatchSubscriptions: 0,
|
||||
});
|
||||
|
||||
const useStats = () => {
|
||||
const setActiveSubscriptions = (count: number) => setStats('activeSubscriptions', count);
|
||||
|
||||
const setActiveBatchSubscriptions = (count: number) =>
|
||||
setStats('activeBatchSubscriptions', count);
|
||||
|
||||
return {
|
||||
stats,
|
||||
setActiveSubscriptions,
|
||||
setActiveBatchSubscriptions,
|
||||
};
|
||||
};
|
||||
|
||||
export default useStats;
|
||||
@@ -2,6 +2,7 @@ import { createSignal, createEffect, onCleanup } from 'solid-js';
|
||||
import type { Event as NostrEvent, Filter, SubscriptionOptions } from 'nostr-tools';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import usePool from '@/nostr/usePool';
|
||||
import useStats from './useStats';
|
||||
|
||||
export type UseSubscriptionProps = {
|
||||
relayUrls: string[];
|
||||
@@ -27,7 +28,10 @@ const sortEvents = (events: NostrEvent[]) =>
|
||||
|
||||
let count = 0;
|
||||
|
||||
setInterval(() => console.log('sub', count), 1000);
|
||||
const { setActiveSubscriptions } = useStats();
|
||||
setInterval(() => {
|
||||
setActiveSubscriptions(count);
|
||||
}, 1000);
|
||||
|
||||
const useSubscription = (propsProvider: () => UseSubscriptionProps | null) => {
|
||||
const pool = usePool();
|
||||
|
||||
Reference in New Issue
Block a user