mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-18 11:13:30 +00:00
refactor widget
This commit is contained in:
parent
abe4d11498
commit
aced6077bd
@ -1,163 +0,0 @@
|
|||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
|
||||||
|
|
||||||
import {
|
|
||||||
AddWidgetIcon,
|
|
||||||
FeedIcon,
|
|
||||||
FileIcon,
|
|
||||||
HashtagIcon,
|
|
||||||
ThreadsIcon,
|
|
||||||
TrendingIcon,
|
|
||||||
} from '@shared/icons';
|
|
||||||
|
|
||||||
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
|
||||||
|
|
||||||
export function AddWidgetButton() {
|
|
||||||
const { db } = useStorage();
|
|
||||||
const setWidget = useWidgets((state) => state.setWidget);
|
|
||||||
|
|
||||||
const setTrendingProfilesWidget = () => {
|
|
||||||
setWidget(db, {
|
|
||||||
kind: WidgetKinds.trendingProfiles,
|
|
||||||
title: 'Trending Profiles',
|
|
||||||
content: 'https://api.nostr.band/v0/trending/profiles',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setTrendingNotesWidget = () => {
|
|
||||||
setWidget(db, {
|
|
||||||
kind: WidgetKinds.trendingNotes,
|
|
||||||
title: 'Trending Notes',
|
|
||||||
content: 'https://api.nostr.band/v0/trending/notes',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setArticleWidget = () => {
|
|
||||||
setWidget(db, {
|
|
||||||
kind: WidgetKinds.article,
|
|
||||||
title: 'Articles',
|
|
||||||
content: '',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setFileWidget = () => {
|
|
||||||
setWidget(db, {
|
|
||||||
kind: WidgetKinds.file,
|
|
||||||
title: 'Files',
|
|
||||||
content: '',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setHashtagWidget = () => {
|
|
||||||
setWidget(db, {
|
|
||||||
kind: WidgetKinds.xhashtag,
|
|
||||||
title: 'New hashtag',
|
|
||||||
content: '',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setGroupFeedWidget = () => {
|
|
||||||
setWidget(db, {
|
|
||||||
kind: WidgetKinds.xfeed,
|
|
||||||
title: 'New user group feed',
|
|
||||||
content: '',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownMenu.Root>
|
|
||||||
<div className="flex h-full shrink-0 grow-0 basis-[400px] flex-col">
|
|
||||||
<div className="inline-flex h-full w-full flex-col items-center justify-center">
|
|
||||||
<DropdownMenu.Trigger asChild>
|
|
||||||
<button type="button" className="flex flex-col items-center gap-2">
|
|
||||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-white/5 backdrop-blur-xl hover:bg-white/10">
|
|
||||||
<AddWidgetIcon className="h-5 w-5 text-white" />
|
|
||||||
</div>
|
|
||||||
<p className="font-medium text-white/50">Add widget</p>
|
|
||||||
</button>
|
|
||||||
</DropdownMenu.Trigger>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<DropdownMenu.Portal>
|
|
||||||
<DropdownMenu.Content
|
|
||||||
sideOffset={-20}
|
|
||||||
className="flex w-[256px] flex-col overflow-hidden rounded-md bg-white/10 p-2 backdrop-blur-3xl focus:outline-none"
|
|
||||||
>
|
|
||||||
<DropdownMenu.Item asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={setHashtagWidget}
|
|
||||||
className="inline-flex h-11 items-center gap-2 rounded-md px-2 text-sm font-medium text-white backdrop-blur-xl hover:bg-white/10"
|
|
||||||
>
|
|
||||||
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10 backdrop-blur-xl">
|
|
||||||
<HashtagIcon className="h-4 w-4 text-white" />
|
|
||||||
</div>
|
|
||||||
Add hashtag feeds
|
|
||||||
</button>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Item asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={setGroupFeedWidget}
|
|
||||||
className="inline-flex h-11 items-center gap-2 rounded-md px-2 text-sm font-medium text-white backdrop-blur-xl hover:bg-white/10"
|
|
||||||
>
|
|
||||||
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10 backdrop-blur-xl">
|
|
||||||
<FeedIcon className="h-4 w-4 text-white" />
|
|
||||||
</div>
|
|
||||||
Add user group feeds
|
|
||||||
</button>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Item asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={setArticleWidget}
|
|
||||||
className="inline-flex h-11 items-center gap-2 rounded-md px-2 text-sm font-medium text-white backdrop-blur-xl hover:bg-white/10"
|
|
||||||
>
|
|
||||||
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10 backdrop-blur-xl">
|
|
||||||
<ThreadsIcon className="h-4 w-4 text-white" />
|
|
||||||
</div>
|
|
||||||
Add article feeds
|
|
||||||
</button>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Item asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={setFileWidget}
|
|
||||||
className="inline-flex h-11 items-center gap-2 rounded-md px-2 text-sm font-medium text-white backdrop-blur-xl hover:bg-white/10"
|
|
||||||
>
|
|
||||||
<div className="flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10 backdrop-blur-xl">
|
|
||||||
<FileIcon className="h-4 w-4 text-white" />
|
|
||||||
</div>
|
|
||||||
Add file feeds
|
|
||||||
</button>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Item asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={setTrendingProfilesWidget}
|
|
||||||
className="inline-flex h-11 items-center gap-2 rounded-md px-2 text-sm font-medium text-white backdrop-blur-xl hover:bg-white/10"
|
|
||||||
>
|
|
||||||
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10 backdrop-blur-xl">
|
|
||||||
<TrendingIcon className="h-4 w-4 text-white" />
|
|
||||||
</div>
|
|
||||||
Add trending accounts
|
|
||||||
</button>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Item asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={setTrendingNotesWidget}
|
|
||||||
className="inline-flex h-11 items-center gap-2 rounded-md px-2 text-sm font-medium text-white backdrop-blur-xl hover:bg-white/10"
|
|
||||||
>
|
|
||||||
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10 backdrop-blur-xl">
|
|
||||||
<TrendingIcon className="h-4 w-4 text-white" />
|
|
||||||
</div>
|
|
||||||
Add trending notes
|
|
||||||
</button>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
</DropdownMenu.Content>
|
|
||||||
</DropdownMenu.Portal>
|
|
||||||
</DropdownMenu.Root>
|
|
||||||
);
|
|
||||||
}
|
|
25
src/app/space/components/toggle.tsx
Normal file
25
src/app/space/components/toggle.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import { PlusIcon } from '@shared/icons';
|
||||||
|
|
||||||
|
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||||
|
|
||||||
|
export function ToggleWidgetList() {
|
||||||
|
const { db } = useStorage();
|
||||||
|
const setWidget = useWidgets((state) => state.setWidget);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative flex h-full shrink-0 grow-0 basis-[400px] items-center justify-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setWidget(db, { kind: WidgetKinds.tmp.list, title: '', content: '' })
|
||||||
|
}
|
||||||
|
className="inline-flex items-center gap-2 text-white"
|
||||||
|
>
|
||||||
|
<PlusIcon className="h-4 w-4 text-white" />
|
||||||
|
<p className="text-sm font-bold leading-none">Add widget</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
51
src/app/space/components/widgetList.tsx
Normal file
51
src/app/space/components/widgetList.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import { TitleBar } from '@shared/titleBar';
|
||||||
|
|
||||||
|
import { DefaultWidgets, useWidgets } from '@stores/widgets';
|
||||||
|
|
||||||
|
import { Widget, WidgetGroup, WidgetGroupItem } from '@utils/types';
|
||||||
|
|
||||||
|
export function WidgetList({ params }: { params: Widget }) {
|
||||||
|
const { db } = useStorage();
|
||||||
|
const [setWidget, removeWidget] = useWidgets((state) => [
|
||||||
|
state.setWidget,
|
||||||
|
state.removeWidget,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const openWidget = (widget: WidgetGroupItem) => {
|
||||||
|
setWidget(db, { kind: widget.kind, title: widget.title, content: '' });
|
||||||
|
removeWidget(db, params.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderItem = useCallback(
|
||||||
|
(row: WidgetGroup) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<h3 className="font-medium text-white/50">{row.title}</h3>
|
||||||
|
<div className="grid grid-cols-3 gap-6">
|
||||||
|
{row.data.map((item, index) => (
|
||||||
|
<button onClick={() => openWidget(item)} key={index}>
|
||||||
|
<div className="inline-flex aspect-square h-full w-full transform-gpu flex-col items-center justify-center gap-2.5 rounded-2xl bg-white/5 hover:bg-white/10">
|
||||||
|
<h5 className="text-sm font-medium">{item.title}</h5>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[DefaultWidgets]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative h-full shrink-0 grow-0 basis-[400px] overflow-hidden">
|
||||||
|
<TitleBar title="Add widget" />
|
||||||
|
<div className="flex flex-col gap-8 px-3">
|
||||||
|
{DefaultWidgets.map((row: WidgetGroup) => renderItem(row))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,21 +1,26 @@
|
|||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
import { AddWidgetButton } from '@app/space/components/button';
|
import { ToggleWidgetList } from '@app/space/components/toggle';
|
||||||
import { FeedWidgetForm } from '@app/space/components/forms/feed';
|
import { WidgetList } from '@app/space/components/widgetList';
|
||||||
import { HashTagWidgetForm } from '@app/space/components/forms/hashtag';
|
|
||||||
import { ArticleWidget } from '@app/space/components/widgets/article';
|
|
||||||
import { FeedWidget } from '@app/space/components/widgets/feed';
|
|
||||||
import { FileWidget } from '@app/space/components/widgets/file';
|
|
||||||
import { HashtagWidget } from '@app/space/components/widgets/hashtag';
|
|
||||||
import { NetworkWidget } from '@app/space/components/widgets/network';
|
|
||||||
import { ThreadBlock } from '@app/space/components/widgets/thread';
|
|
||||||
import { TrendingNotesWidget } from '@app/space/components/widgets/trendingNotes';
|
|
||||||
import { TrendingProfilesWidget } from '@app/space/components/widgets/trendingProfile';
|
|
||||||
import { UserWidget } from '@app/space/components/widgets/user';
|
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
import {
|
||||||
|
GlobalArticlesWidget,
|
||||||
|
GlobalFilesWidget,
|
||||||
|
GlobalHashtagWidget,
|
||||||
|
LocalArticlesWidget,
|
||||||
|
LocalFeedsWidget,
|
||||||
|
LocalFilesWidget,
|
||||||
|
LocalNetworkWidget,
|
||||||
|
LocalThreadWidget,
|
||||||
|
LocalUserWidget,
|
||||||
|
TrendingAccountsWidget,
|
||||||
|
TrendingNotesWidget,
|
||||||
|
XfeedsWidget,
|
||||||
|
XhashtagWidget,
|
||||||
|
} from '@shared/widgets';
|
||||||
|
|
||||||
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||||
|
|
||||||
@ -33,28 +38,34 @@ export function SpaceScreen() {
|
|||||||
(widget: Widget) => {
|
(widget: Widget) => {
|
||||||
if (!widget) return;
|
if (!widget) return;
|
||||||
switch (widget.kind) {
|
switch (widget.kind) {
|
||||||
case WidgetKinds.feed:
|
case WidgetKinds.local.network:
|
||||||
return <FeedWidget key={widget.id} params={widget} />;
|
return <LocalNetworkWidget key={widget.id} />;
|
||||||
case WidgetKinds.thread:
|
case WidgetKinds.local.feeds:
|
||||||
return <ThreadBlock key={widget.id} params={widget} />;
|
return <LocalFeedsWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.hashtag:
|
case WidgetKinds.local.files:
|
||||||
return <HashtagWidget key={widget.id} params={widget} />;
|
return <LocalFilesWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.user:
|
case WidgetKinds.local.articles:
|
||||||
return <UserWidget key={widget.id} params={widget} />;
|
return <LocalArticlesWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.trendingProfiles:
|
case WidgetKinds.local.user:
|
||||||
return <TrendingProfilesWidget key={widget.id} params={widget} />;
|
return <LocalUserWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.trendingNotes:
|
case WidgetKinds.local.thread:
|
||||||
|
return <LocalThreadWidget key={widget.id} params={widget} />;
|
||||||
|
case WidgetKinds.global.hashtag:
|
||||||
|
return <GlobalHashtagWidget key={widget.id} params={widget} />;
|
||||||
|
case WidgetKinds.global.articles:
|
||||||
|
return <GlobalArticlesWidget key={widget.id} params={widget} />;
|
||||||
|
case WidgetKinds.global.files:
|
||||||
|
return <GlobalFilesWidget key={widget.id} params={widget} />;
|
||||||
|
case WidgetKinds.nostrBand.trendingAccounts:
|
||||||
|
return <TrendingAccountsWidget key={widget.id} params={widget} />;
|
||||||
|
case WidgetKinds.nostrBand.trendingNotes:
|
||||||
return <TrendingNotesWidget key={widget.id} params={widget} />;
|
return <TrendingNotesWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.network:
|
case WidgetKinds.tmp.xfeed:
|
||||||
return <NetworkWidget key={widget.id} />;
|
return <XhashtagWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.article:
|
case WidgetKinds.tmp.xhashtag:
|
||||||
return <ArticleWidget key={widget.id} params={widget} />;
|
return <XfeedsWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.file:
|
case WidgetKinds.tmp.list:
|
||||||
return <FileWidget key={widget.id} params={widget} />;
|
return <WidgetList key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.xhashtag:
|
|
||||||
return <HashTagWidgetForm key={widget.id} params={widget} />;
|
|
||||||
case WidgetKinds.xfeed:
|
|
||||||
return <FeedWidgetForm key={widget.id} params={widget} />;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -77,7 +88,7 @@ export function SpaceScreen() {
|
|||||||
) : (
|
) : (
|
||||||
widgets.map((widget) => renderItem(widget))
|
widgets.map((widget) => renderItem(widget))
|
||||||
)}
|
)}
|
||||||
<AddWidgetButton />
|
<ToggleWidgetList />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -223,6 +223,35 @@ export class LumeStorage {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getAllEventsByKinds(kinds: number[], limit: number, offset: number) {
|
||||||
|
const totalEvents = await this.countTotalEvents();
|
||||||
|
const nextCursor = offset + limit;
|
||||||
|
const authorsArr = `'${kinds.join("','")}'`;
|
||||||
|
|
||||||
|
const events: { data: DBEvent[] | null; nextCursor: number } = {
|
||||||
|
data: null,
|
||||||
|
nextCursor: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const query: DBEvent[] = await this.db.select(
|
||||||
|
`SELECT * FROM events WHERE kinds IN (${authorsArr}) ORDER BY created_at DESC LIMIT $1 OFFSET $2;`,
|
||||||
|
[limit, offset]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (query && query.length > 0) {
|
||||||
|
events['data'] = query;
|
||||||
|
events['nextCursor'] =
|
||||||
|
Math.round(totalEvents / nextCursor) > 1 ? nextCursor : undefined;
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: [],
|
||||||
|
nextCursor: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async isEventsEmpty() {
|
public async isEventsEmpty() {
|
||||||
const results: DBEvent[] = await this.db.select(
|
const results: DBEvent[] = await this.db.select(
|
||||||
'SELECT * FROM events ORDER BY id DESC LIMIT 1;'
|
'SELECT * FROM events ORDER BY id DESC LIMIT 1;'
|
||||||
|
@ -20,7 +20,7 @@ export function Repost({ event }: { event: NDKEvent }) {
|
|||||||
|
|
||||||
if (status === 'loading') {
|
if (status === 'loading') {
|
||||||
return (
|
return (
|
||||||
<div className="h-min w-full px-3 py-1.5">
|
<div className="h-min w-full px-3 pb-3">
|
||||||
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
<NoteSkeleton />
|
<NoteSkeleton />
|
||||||
</div>
|
</div>
|
||||||
@ -30,7 +30,7 @@ export function Repost({ event }: { event: NDKEvent }) {
|
|||||||
|
|
||||||
if (status === 'error') {
|
if (status === 'error') {
|
||||||
return (
|
return (
|
||||||
<div className="h-min w-full px-3 py-1.5">
|
<div className="h-min w-full px-3 pb-3">
|
||||||
<div className="flex flex-col gap-1 overflow-hidden rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
<div className="flex flex-col gap-1 overflow-hidden rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
<p className="select-text break-all text-white/50">
|
<p className="select-text break-all text-white/50">
|
||||||
Failed to get repost with ID
|
Failed to get repost with ID
|
||||||
@ -57,7 +57,7 @@ export function Repost({ event }: { event: NDKEvent }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-min w-full px-3 py-1.5">
|
<div className="h-min w-full px-3 pb-3">
|
||||||
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 pt-3 backdrop-blur-xl">
|
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 pt-3 backdrop-blur-xl">
|
||||||
<div className="relative flex flex-col">
|
<div className="relative flex flex-col">
|
||||||
<div className="isolate flex flex-col -space-y-4">
|
<div className="isolate flex flex-col -space-y-4">
|
||||||
|
@ -11,7 +11,7 @@ export function Hashtag({ tag }: { tag: string }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setWidget(db, {
|
setWidget(db, {
|
||||||
kind: WidgetKinds.hashtag,
|
kind: WidgetKinds.global.hashtag,
|
||||||
title: tag,
|
title: tag,
|
||||||
content: tag.replace('#', ''),
|
content: tag.replace('#', ''),
|
||||||
})
|
})
|
||||||
|
@ -25,7 +25,7 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
|||||||
const openThread = (event, thread: string) => {
|
const openThread = (event, thread: string) => {
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
if (selection.toString().length === 0) {
|
if (selection.toString().length === 0) {
|
||||||
setWidget(db, { kind: WidgetKinds.thread, title: 'Thread', content: thread });
|
setWidget(db, { kind: WidgetKinds.local.thread, title: 'Thread', content: thread });
|
||||||
} else {
|
} else {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ export function MentionUser({ pubkey }: { pubkey: string }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setWidget(db, {
|
setWidget(db, {
|
||||||
kind: WidgetKinds.user,
|
kind: WidgetKinds.local.user,
|
||||||
title: user?.nip05 || user?.name || user?.display_name,
|
title: user?.nip05 || user?.name || user?.display_name,
|
||||||
content: pubkey,
|
content: pubkey,
|
||||||
})
|
})
|
||||||
|
@ -19,7 +19,7 @@ export function TitleBar({ id, title }: { id?: string; title: string }) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => remove(db, id)}
|
onClick={() => remove(db, id)}
|
||||||
className="inline-flex h-6 w-6 shrink-0 transform items-center justify-center rounded backdrop-blur-xl hover:bg-white/10"
|
className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded backdrop-blur-xl hover:bg-white/10"
|
||||||
>
|
>
|
||||||
<CancelIcon className="h-3 w-3 text-white" />
|
<CancelIcon className="h-3 w-3 text-white" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -10,10 +10,10 @@ import { TitleBar } from '@shared/titleBar';
|
|||||||
|
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function ArticleWidget({ params }: { params: Widget }) {
|
export function GlobalArticlesWidget({ params }: { params: Widget }) {
|
||||||
const { ndk } = useNDK();
|
const { ndk } = useNDK();
|
||||||
const { status, data } = useQuery(
|
const { status, data } = useQuery(
|
||||||
['article-widget', params.content],
|
['global-articles-widget'],
|
||||||
async () => {
|
async () => {
|
||||||
const events = await ndk.fetchEvents({
|
const events = await ndk.fetchEvents({
|
||||||
kinds: [NDKKind.Article],
|
kinds: [NDKKind.Article],
|
@ -10,12 +10,13 @@ import { TitleBar } from '@shared/titleBar';
|
|||||||
|
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function FileWidget({ params }: { params: Widget }) {
|
export function GlobalFilesWidget({ params }: { params: Widget }) {
|
||||||
const { ndk } = useNDK();
|
const { ndk } = useNDK();
|
||||||
const { status, data } = useQuery(
|
const { status, data } = useQuery(
|
||||||
['file-widget', params.content],
|
['global-files-widget'],
|
||||||
async () => {
|
async () => {
|
||||||
const events = await ndk.fetchEvents({
|
const events = await ndk.fetchEvents({
|
||||||
|
// @ts-expect-error, NDK not support file metadata yet
|
||||||
kinds: [1063],
|
kinds: [1063],
|
||||||
limit: 100,
|
limit: 100,
|
||||||
});
|
});
|
@ -19,10 +19,10 @@ import { TitleBar } from '@shared/titleBar';
|
|||||||
import { nHoursAgo } from '@utils/date';
|
import { nHoursAgo } from '@utils/date';
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function HashtagWidget({ params }: { params: Widget }) {
|
export function GlobalHashtagWidget({ params }: { params: Widget }) {
|
||||||
const { ndk } = useNDK();
|
const { ndk } = useNDK();
|
||||||
const { status, data } = useQuery(
|
const { status, data } = useQuery(
|
||||||
['hashtag-widget', params.content],
|
['global-hashtag-widget', params.content],
|
||||||
async () => {
|
async () => {
|
||||||
const events = await ndk.fetchEvents({
|
const events = await ndk.fetchEvents({
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Article],
|
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Article],
|
13
src/shared/widgets/index.ts
Normal file
13
src/shared/widgets/index.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export * from './local/feeds';
|
||||||
|
export * from './local/network';
|
||||||
|
export * from './local/user';
|
||||||
|
export * from './local/thread';
|
||||||
|
export * from './local/files';
|
||||||
|
export * from './local/articles';
|
||||||
|
export * from './global/articles';
|
||||||
|
export * from './global/files';
|
||||||
|
export * from './global/hashtag';
|
||||||
|
export * from './nostrBand/trendingNotes';
|
||||||
|
export * from './nostrBand/trendingAccounts';
|
||||||
|
export * from './tmp/feeds';
|
||||||
|
export * from './tmp/hashtag';
|
130
src/shared/widgets/local/articles.tsx
Normal file
130
src/shared/widgets/local/articles.tsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
|
import { useCallback, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||||
|
import { FileNote, NoteSkeleton, NoteWrapper } from '@shared/notes';
|
||||||
|
import { TitleBar } from '@shared/titleBar';
|
||||||
|
|
||||||
|
import { DBEvent, Widget } from '@utils/types';
|
||||||
|
|
||||||
|
export function LocalArticlesWidget({ params }: { params: Widget }) {
|
||||||
|
const { db } = useStorage();
|
||||||
|
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||||
|
useInfiniteQuery({
|
||||||
|
queryKey: ['local-articles-widget'],
|
||||||
|
queryFn: async ({ pageParam = 0 }) => {
|
||||||
|
return await db.getAllEventsByKinds([NDKKind.Article], 20, pageParam);
|
||||||
|
},
|
||||||
|
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||||
|
});
|
||||||
|
|
||||||
|
const dbEvents = useMemo(
|
||||||
|
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
||||||
|
[data]
|
||||||
|
);
|
||||||
|
const parentRef = useRef<HTMLDivElement>();
|
||||||
|
const virtualizer = useVirtualizer({
|
||||||
|
count: hasNextPage ? dbEvents.length : dbEvents.length,
|
||||||
|
getScrollElement: () => parentRef.current,
|
||||||
|
estimateSize: () => 650,
|
||||||
|
overscan: 4,
|
||||||
|
});
|
||||||
|
const items = virtualizer.getVirtualItems();
|
||||||
|
|
||||||
|
// render event match event kind
|
||||||
|
const renderItem = useCallback(
|
||||||
|
(index: string | number) => {
|
||||||
|
const event: NDKEvent = data[index];
|
||||||
|
if (!event) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={event.id} data-index={index} ref={virtualizer.measureElement}>
|
||||||
|
<NoteWrapper event={event}>
|
||||||
|
<FileNote event={event} />
|
||||||
|
</NoteWrapper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[data]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
|
||||||
|
<TitleBar id={params.id} title={params.title} />
|
||||||
|
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
|
||||||
|
{status === 'loading' ? (
|
||||||
|
<div className="px-3 py-1.5">
|
||||||
|
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
|
<NoteSkeleton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : items.length === 0 ? (
|
||||||
|
<div className="px-3 py-1.5">
|
||||||
|
<div className="bbg-white/10 rounded-xl px-3 py-6 backdrop-blur-xl">
|
||||||
|
<div className="flex flex-col items-center gap-4">
|
||||||
|
<p className="text-center text-sm text-white">
|
||||||
|
There have been no new posts.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
height: `${virtualizer.getTotalSize()}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute left-0 top-0 w-full"
|
||||||
|
style={{
|
||||||
|
transform: `translateY(${items[0].start}px)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{items.map((item) => renderItem(item.index))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isFetchingNextPage && (
|
||||||
|
<div className="px-3 py-1.5">
|
||||||
|
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
|
<NoteSkeleton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="px-3 py-1.5">
|
||||||
|
<button
|
||||||
|
onClick={() => fetchNextPage()}
|
||||||
|
disabled={!hasNextPage || isFetchingNextPage}
|
||||||
|
className="inline-flex h-11 w-full items-center justify-between gap-2 rounded-lg bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none"
|
||||||
|
>
|
||||||
|
{isFetchingNextPage ? (
|
||||||
|
<>
|
||||||
|
<span className="w-5" />
|
||||||
|
<span>Loading...</span>
|
||||||
|
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||||
|
</>
|
||||||
|
) : hasNextPage ? (
|
||||||
|
<>
|
||||||
|
<span className="w-5" />
|
||||||
|
<span>Load more</span>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="w-5" />
|
||||||
|
<span>Nothing more to load</span>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -19,11 +19,11 @@ import { TitleBar } from '@shared/titleBar';
|
|||||||
|
|
||||||
import { DBEvent, Widget } from '@utils/types';
|
import { DBEvent, Widget } from '@utils/types';
|
||||||
|
|
||||||
export function FeedWidget({ params }: { params: Widget }) {
|
export function LocalFeedsWidget({ params }: { params: Widget }) {
|
||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||||
useInfiniteQuery({
|
useInfiniteQuery({
|
||||||
queryKey: ['groupfeed-widget', params.content],
|
queryKey: ['local-feeds-widget', params.content],
|
||||||
queryFn: async ({ pageParam = 0 }) => {
|
queryFn: async ({ pageParam = 0 }) => {
|
||||||
const authors = JSON.parse(params.content);
|
const authors = JSON.parse(params.content);
|
||||||
return await db.getAllEventsByAuthors(authors, 20, pageParam);
|
return await db.getAllEventsByAuthors(authors, 20, pageParam);
|
130
src/shared/widgets/local/files.tsx
Normal file
130
src/shared/widgets/local/files.tsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||||
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
|
import { useCallback, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||||
|
import { FileNote, NoteSkeleton, NoteWrapper } from '@shared/notes';
|
||||||
|
import { TitleBar } from '@shared/titleBar';
|
||||||
|
|
||||||
|
import { DBEvent, Widget } from '@utils/types';
|
||||||
|
|
||||||
|
export function LocalFilesWidget({ params }: { params: Widget }) {
|
||||||
|
const { db } = useStorage();
|
||||||
|
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||||
|
useInfiniteQuery({
|
||||||
|
queryKey: ['local-files-widget'],
|
||||||
|
queryFn: async ({ pageParam = 0 }) => {
|
||||||
|
return await db.getAllEventsByKinds([1063], 20, pageParam);
|
||||||
|
},
|
||||||
|
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||||
|
});
|
||||||
|
|
||||||
|
const dbEvents = useMemo(
|
||||||
|
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
||||||
|
[data]
|
||||||
|
);
|
||||||
|
const parentRef = useRef<HTMLDivElement>();
|
||||||
|
const virtualizer = useVirtualizer({
|
||||||
|
count: hasNextPage ? dbEvents.length : dbEvents.length,
|
||||||
|
getScrollElement: () => parentRef.current,
|
||||||
|
estimateSize: () => 650,
|
||||||
|
overscan: 4,
|
||||||
|
});
|
||||||
|
const items = virtualizer.getVirtualItems();
|
||||||
|
|
||||||
|
// render event match event kind
|
||||||
|
const renderItem = useCallback(
|
||||||
|
(index: string | number) => {
|
||||||
|
const event: NDKEvent = data[index];
|
||||||
|
if (!event) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={event.id} data-index={index} ref={virtualizer.measureElement}>
|
||||||
|
<NoteWrapper event={event}>
|
||||||
|
<FileNote event={event} />
|
||||||
|
</NoteWrapper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[data]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
|
||||||
|
<TitleBar id={params.id} title={params.title} />
|
||||||
|
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
|
||||||
|
{status === 'loading' ? (
|
||||||
|
<div className="px-3 py-1.5">
|
||||||
|
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
|
<NoteSkeleton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : items.length === 0 ? (
|
||||||
|
<div className="px-3 py-1.5">
|
||||||
|
<div className="bbg-white/10 rounded-xl px-3 py-6 backdrop-blur-xl">
|
||||||
|
<div className="flex flex-col items-center gap-4">
|
||||||
|
<p className="text-center text-sm text-white">
|
||||||
|
There have been no new posts.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
height: `${virtualizer.getTotalSize()}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute left-0 top-0 w-full"
|
||||||
|
style={{
|
||||||
|
transform: `translateY(${items[0].start}px)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{items.map((item) => renderItem(item.index))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isFetchingNextPage && (
|
||||||
|
<div className="px-3 py-1.5">
|
||||||
|
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
|
<NoteSkeleton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="px-3 py-1.5">
|
||||||
|
<button
|
||||||
|
onClick={() => fetchNextPage()}
|
||||||
|
disabled={!hasNextPage || isFetchingNextPage}
|
||||||
|
className="inline-flex h-11 w-full items-center justify-between gap-2 rounded-lg bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none"
|
||||||
|
>
|
||||||
|
{isFetchingNextPage ? (
|
||||||
|
<>
|
||||||
|
<span className="w-5" />
|
||||||
|
<span>Loading...</span>
|
||||||
|
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||||
|
</>
|
||||||
|
) : hasNextPage ? (
|
||||||
|
<>
|
||||||
|
<span className="w-5" />
|
||||||
|
<span>Load more</span>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="w-5" />
|
||||||
|
<span>Nothing more to load</span>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -21,12 +21,12 @@ import { useNostr } from '@utils/hooks/useNostr';
|
|||||||
import { toRawEvent } from '@utils/rawEvent';
|
import { toRawEvent } from '@utils/rawEvent';
|
||||||
import { DBEvent } from '@utils/types';
|
import { DBEvent } from '@utils/types';
|
||||||
|
|
||||||
export function NetworkWidget() {
|
export function LocalNetworkWidget() {
|
||||||
const { sub } = useNostr();
|
const { sub } = useNostr();
|
||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||||
useInfiniteQuery({
|
useInfiniteQuery({
|
||||||
queryKey: ['network-widget'],
|
queryKey: ['local-network-widget'],
|
||||||
queryFn: async ({ pageParam = 0 }) => {
|
queryFn: async ({ pageParam = 0 }) => {
|
||||||
return await db.getAllEvents(30, pageParam);
|
return await db.getAllEvents(30, pageParam);
|
||||||
},
|
},
|
@ -20,7 +20,7 @@ import { TitleBar } from '@shared/titleBar';
|
|||||||
import { useEvent } from '@utils/hooks/useEvent';
|
import { useEvent } from '@utils/hooks/useEvent';
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function ThreadBlock({ params }: { params: Widget }) {
|
export function LocalThreadWidget({ params }: { params: Widget }) {
|
||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
const { status, data } = useEvent(params.content);
|
const { status, data } = useEvent(params.content);
|
||||||
|
|
@ -20,10 +20,10 @@ import { UserProfile } from '@shared/userProfile';
|
|||||||
import { nHoursAgo } from '@utils/date';
|
import { nHoursAgo } from '@utils/date';
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function UserWidget({ params }: { params: Widget }) {
|
export function LocalUserWidget({ params }: { params: Widget }) {
|
||||||
const { ndk } = useNDK();
|
const { ndk } = useNDK();
|
||||||
const { status, data } = useQuery(
|
const { status, data } = useQuery(
|
||||||
['user-widget', params.content],
|
['local-user-widget', params.content],
|
||||||
async () => {
|
async () => {
|
||||||
const events = await ndk.fetchEvents({
|
const events = await ndk.fetchEvents({
|
||||||
kinds: [1, 6],
|
kinds: [1, 6],
|
@ -1,9 +1,8 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { type Profile, UserProfile } from '@app/space/components/userProfile';
|
|
||||||
|
|
||||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||||
import { TitleBar } from '@shared/titleBar';
|
import { TitleBar } from '@shared/titleBar';
|
||||||
|
import { NostrBandUserProfile, type Profile } from '@shared/widgets/nostrBandUserProfile';
|
||||||
|
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
@ -11,11 +10,11 @@ interface Response {
|
|||||||
profiles: Array<{ pubkey: string }>;
|
profiles: Array<{ pubkey: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TrendingProfilesWidget({ params }: { params: Widget }) {
|
export function TrendingAccountsWidget({ params }: { params: Widget }) {
|
||||||
const { status, data } = useQuery(
|
const { status, data } = useQuery(
|
||||||
['trending-profiles-widget'],
|
['trending-profiles-widget'],
|
||||||
async () => {
|
async () => {
|
||||||
const res = await fetch(params.content);
|
const res = await fetch('https://api.nostr.band/v0/trending/profiles');
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error('Error');
|
throw new Error('Error');
|
||||||
}
|
}
|
||||||
@ -32,9 +31,9 @@ export function TrendingProfilesWidget({ params }: { params: Widget }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="scrollbar-hide relative shrink-0 grow-0 basis-[400px] overflow-y-auto bg-white/10 backdrop-blur-xl">
|
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
|
||||||
<TitleBar id={params.id} title={params.title} />
|
<TitleBar id={params.id} title="Trending Accounts" />
|
||||||
<div className="h-full">
|
<div className="scrollbar-hide h-full max-w-full overflow-y-auto pb-20">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
@ -52,7 +51,7 @@ export function TrendingProfilesWidget({ params }: { params: Widget }) {
|
|||||||
) : (
|
) : (
|
||||||
<div className="relative flex w-full flex-col gap-3 px-3 pt-1.5">
|
<div className="relative flex w-full flex-col gap-3 px-3 pt-1.5">
|
||||||
{data.map((item: Profile) => (
|
{data.map((item: Profile) => (
|
||||||
<UserProfile key={item.pubkey} data={item} />
|
<NostrBandUserProfile key={item.pubkey} data={item} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
@ -14,7 +14,7 @@ export function TrendingNotesWidget({ params }: { params: Widget }) {
|
|||||||
const { status, data } = useQuery(
|
const { status, data } = useQuery(
|
||||||
['trending-notes-widget'],
|
['trending-notes-widget'],
|
||||||
async () => {
|
async () => {
|
||||||
const res = await fetch(params.content);
|
const res = await fetch('https://api.nostr.band/v0/trending/notes');
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error('failed to fecht trending notes');
|
throw new Error('failed to fecht trending notes');
|
||||||
}
|
}
|
||||||
@ -31,9 +31,9 @@ export function TrendingNotesWidget({ params }: { params: Widget }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="scrollbar-hide relative shrink-0 grow-0 basis-[400px] overflow-y-auto bg-white/10 backdrop-blur-xl">
|
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
|
||||||
<TitleBar id={params.id} title={params.title} />
|
<TitleBar id={params.id} title="Trending Notes" />
|
||||||
<div className="h-full">
|
<div className="scrollbar-hide h-full max-w-full overflow-y-auto pb-20">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
@ -15,7 +15,7 @@ export interface Profile {
|
|||||||
profile: { content: string };
|
profile: { content: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserProfile({ data }: { data: Profile }) {
|
export function NostrBandUserProfile({ data }: { data: Profile }) {
|
||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
const { addContact, removeContact } = useNostr();
|
const { addContact, removeContact } = useNostr();
|
||||||
const { status, data: userStats } = useQuery(
|
const { status, data: userStats } = useQuery(
|
@ -10,7 +10,7 @@ import { WidgetKinds, useWidgets } from '@stores/widgets';
|
|||||||
|
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function FeedWidgetForm({ params }: { params: Widget }) {
|
export function XfeedsWidget({ params }: { params: Widget }) {
|
||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
|
|
||||||
const [setWidget, removeWidget] = useWidgets((state) => [
|
const [setWidget, removeWidget] = useWidgets((state) => [
|
||||||
@ -34,7 +34,7 @@ export function FeedWidgetForm({ params }: { params: Widget }) {
|
|||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
setWidget(db, {
|
setWidget(db, {
|
||||||
kind: WidgetKinds.feed,
|
kind: WidgetKinds.local.feeds,
|
||||||
title: title || 'Group',
|
title: title || 'Group',
|
||||||
content: JSON.stringify(groups),
|
content: JSON.stringify(groups),
|
||||||
});
|
});
|
@ -26,7 +26,7 @@ const resolver: Resolver<FormValues> = async (values) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function HashTagWidgetForm({ params }: { params: Widget }) {
|
export function XhashtagWidget({ params }: { params: Widget }) {
|
||||||
const [setWidget, removeWidget] = useWidgets((state) => [
|
const [setWidget, removeWidget] = useWidgets((state) => [
|
||||||
state.setWidget,
|
state.setWidget,
|
||||||
state.removeWidget,
|
state.removeWidget,
|
||||||
@ -47,7 +47,7 @@ export function HashTagWidgetForm({ params }: { params: Widget }) {
|
|||||||
const onSubmit = async (data: FormValues) => {
|
const onSubmit = async (data: FormValues) => {
|
||||||
try {
|
try {
|
||||||
setWidget(db, {
|
setWidget(db, {
|
||||||
kind: WidgetKinds.hashtag,
|
kind: WidgetKinds.global.hashtag,
|
||||||
title: data.hashtag + ' in 24 hours ago',
|
title: data.hashtag + ' in 24 hours ago',
|
||||||
content: data.hashtag.replace('#', ''),
|
content: data.hashtag.replace('#', ''),
|
||||||
});
|
});
|
@ -13,19 +13,81 @@ interface WidgetState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const WidgetKinds = {
|
export const WidgetKinds = {
|
||||||
feed: 1, // NIP-01
|
local: {
|
||||||
thread: 2, // NIP-01
|
network: 100,
|
||||||
hashtag: 3, // NIP-01
|
feeds: 101,
|
||||||
article: 4, // NIP-23
|
files: 102,
|
||||||
user: 5, // NIP-01
|
articles: 103,
|
||||||
trendingProfiles: 6,
|
user: 104,
|
||||||
trendingNotes: 7,
|
thread: 105,
|
||||||
file: 8, // NIP-94
|
},
|
||||||
network: 9999,
|
global: {
|
||||||
xfeed: 10000, // x is temporary state for new feed widget form
|
feeds: 1000,
|
||||||
xhashtag: 10001, // x is temporary state for new hashtag widget form
|
files: 1001,
|
||||||
|
articles: 1002,
|
||||||
|
hashtag: 1003,
|
||||||
|
},
|
||||||
|
nostrBand: {
|
||||||
|
trendingAccounts: 1,
|
||||||
|
trendingNotes: 2,
|
||||||
|
},
|
||||||
|
tmp: {
|
||||||
|
list: 10000,
|
||||||
|
xfeed: 10001,
|
||||||
|
xhashtag: 10002,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DefaultWidgets = [
|
||||||
|
{
|
||||||
|
title: 'Network / Follows',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
kind: WidgetKinds.tmp.xfeed,
|
||||||
|
title: 'Group feeds',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: WidgetKinds.local.files,
|
||||||
|
title: 'Files',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: WidgetKinds.local.articles,
|
||||||
|
title: 'Articles',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Global',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
kind: WidgetKinds.tmp.xhashtag,
|
||||||
|
title: 'Hashtag',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: WidgetKinds.global.files,
|
||||||
|
title: 'Files',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: WidgetKinds.global.articles,
|
||||||
|
title: 'Articles',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Trending (nostr.band)',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
kind: WidgetKinds.nostrBand.trendingAccounts,
|
||||||
|
title: 'Accounts',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: WidgetKinds.nostrBand.trendingNotes,
|
||||||
|
title: 'Notes',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const useWidgets = create<WidgetState>()(
|
export const useWidgets = create<WidgetState>()(
|
||||||
persist(
|
persist(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
@ -39,7 +101,7 @@ export const useWidgets = create<WidgetState>()(
|
|||||||
id: '9999',
|
id: '9999',
|
||||||
title: 'Network',
|
title: 'Network',
|
||||||
content: '',
|
content: '',
|
||||||
kind: WidgetKinds.network,
|
kind: WidgetKinds.local.network,
|
||||||
});
|
});
|
||||||
|
|
||||||
set({ widgets: dbWidgets });
|
set({ widgets: dbWidgets });
|
||||||
|
10
src/utils/types.d.ts
vendored
10
src/utils/types.d.ts
vendored
@ -36,6 +36,16 @@ export interface Profile extends NDKUserProfile {
|
|||||||
pubkey?: string;
|
pubkey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WidgetGroup {
|
||||||
|
title: string;
|
||||||
|
data: WidgetGroupItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WidgetGroupItem {
|
||||||
|
title: string;
|
||||||
|
kind: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Widget {
|
export interface Widget {
|
||||||
id?: string;
|
id?: string;
|
||||||
account_id?: number;
|
account_id?: number;
|
||||||
|
Loading…
Reference in New Issue
Block a user