mirror of
https://github.com/luminous-devs/lume.git
synced 2024-10-01 09:21:07 +00:00
refactor(widget): migrate widget component to ark lib
This commit is contained in:
parent
344bdc0c66
commit
55298515af
@ -22,6 +22,7 @@
|
||||
"@getalby/sdk": "^2.7.0",
|
||||
"@nostr-dev-kit/ndk": "^2.3.0",
|
||||
"@nostr-fetch/adapter-ndk": "^0.14.1",
|
||||
"@preact/signals-react": "^1.3.8",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
"@radix-ui/react-avatar": "^1.0.4",
|
||||
|
@ -17,6 +17,9 @@ dependencies:
|
||||
'@nostr-fetch/adapter-ndk':
|
||||
specifier: ^0.14.1
|
||||
version: 0.14.1(@nostr-dev-kit/ndk@2.3.0)(nostr-fetch@0.14.1)
|
||||
'@preact/signals-react':
|
||||
specifier: ^1.3.8
|
||||
version: 1.3.8(react@18.2.0)
|
||||
'@radix-ui/react-accordion':
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -876,6 +879,20 @@ packages:
|
||||
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
||||
dev: false
|
||||
|
||||
/@preact/signals-core@1.5.1:
|
||||
resolution: {integrity: sha512-dE6f+WCX5ZUDwXzUIWNMhhglmuLpqJhuy3X3xHrhZYI0Hm2LyQwOu0l9mdPiWrVNsE+Q7txOnJPgtIqHCYoBVA==}
|
||||
dev: false
|
||||
|
||||
/@preact/signals-react@1.3.8(react@18.2.0):
|
||||
resolution: {integrity: sha512-i7mVZ/ZiD9WqNH79r+klpQsp8X+/dOd/5AtvDI0HNpgWuHyzyF9WXDViKl+1vXgB767n9VnH1W2azg+w1oyFMQ==}
|
||||
peerDependencies:
|
||||
react: ^16.14.0 || 17.x || 18.x
|
||||
dependencies:
|
||||
'@preact/signals-core': 1.5.1
|
||||
react: 18.2.0
|
||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/primitive@1.0.1:
|
||||
resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==}
|
||||
dependencies:
|
||||
@ -5981,6 +5998,14 @@ packages:
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/use-sync-external-store@1.2.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/utf-8-validate@5.0.10:
|
||||
resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==}
|
||||
engines: {node: '>=6.14.2'}
|
||||
|
20
src/app.tsx
20
src/app.tsx
@ -124,9 +124,11 @@ export default function App() {
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'auth',
|
||||
element: <AuthLayout />,
|
||||
element: <AuthLayout platform={ark.platform} />,
|
||||
errorElement: <ErrorScreen />,
|
||||
children: [
|
||||
{
|
||||
@ -181,27 +183,21 @@ export default function App() {
|
||||
{
|
||||
path: 'tutorials/widget',
|
||||
async lazy() {
|
||||
const { TutorialWidgetScreen } = await import(
|
||||
'@app/auth/tutorials/widget'
|
||||
);
|
||||
const { TutorialWidgetScreen } = await import('@app/auth/tutorials/widget');
|
||||
return { Component: TutorialWidgetScreen };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'tutorials/posting',
|
||||
async lazy() {
|
||||
const { TutorialPostingScreen } = await import(
|
||||
'@app/auth/tutorials/posting'
|
||||
);
|
||||
const { TutorialPostingScreen } = await import('@app/auth/tutorials/posting');
|
||||
return { Component: TutorialPostingScreen };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'tutorials/finish',
|
||||
async lazy() {
|
||||
const { TutorialFinishScreen } = await import(
|
||||
'@app/auth/tutorials/finish'
|
||||
);
|
||||
const { TutorialFinishScreen } = await import('@app/auth/tutorials/finish');
|
||||
return { Component: TutorialFinishScreen };
|
||||
},
|
||||
},
|
||||
@ -209,7 +205,7 @@ export default function App() {
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
element: <SettingsLayout />,
|
||||
element: <SettingsLayout platform={ark.platform} />,
|
||||
errorElement: <ErrorScreen />,
|
||||
children: [
|
||||
{
|
||||
@ -263,8 +259,6 @@ export default function App() {
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useSignal } from '@preact/signals-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { VList, VListHandle } from 'virtua';
|
||||
import { useArk } from '@libs/ark';
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
@ -22,27 +23,27 @@ import { WIDGET_KIND } from '@utils/constants';
|
||||
import { Widget } from '@utils/types';
|
||||
|
||||
export function HomeScreen() {
|
||||
const ref = useRef<VListHandle>(null);
|
||||
const [selectedIndex, setSelectedIndex] = useState(-1);
|
||||
|
||||
const ark = useArk();
|
||||
const { status, data } = useQuery({
|
||||
const ref = useRef<VListHandle>(null);
|
||||
const index = useSignal(-1);
|
||||
|
||||
const { isLoading, data } = useQuery({
|
||||
queryKey: ['widgets'],
|
||||
queryFn: async () => {
|
||||
const dbWidgets = await ark.getWidgets();
|
||||
const defaultWidgets = [
|
||||
{
|
||||
id: '9999',
|
||||
title: 'Newsfeed',
|
||||
content: '',
|
||||
kind: WIDGET_KIND.newsfeed,
|
||||
},
|
||||
{
|
||||
id: '9998',
|
||||
title: 'Notification',
|
||||
content: '',
|
||||
kind: WIDGET_KIND.notification,
|
||||
},
|
||||
{
|
||||
id: '9999',
|
||||
title: 'Newsfeed',
|
||||
content: '',
|
||||
kind: WIDGET_KIND.newsfeed,
|
||||
},
|
||||
];
|
||||
|
||||
return [...defaultWidgets, ...dbWidgets];
|
||||
@ -53,7 +54,7 @@ export function HomeScreen() {
|
||||
staleTime: Infinity,
|
||||
});
|
||||
|
||||
const renderItem = useCallback((widget: Widget) => {
|
||||
const renderItem = (widget: Widget) => {
|
||||
switch (widget.kind) {
|
||||
case WIDGET_KIND.notification:
|
||||
return <NotificationWidget key={widget.id} />;
|
||||
@ -80,13 +81,13 @@ export function HomeScreen() {
|
||||
case WIDGET_KIND.list:
|
||||
return <WidgetList key={widget.id} widget={widget} />;
|
||||
default:
|
||||
return null;
|
||||
return <NewsfeedWidget key={widget.id} />;
|
||||
}
|
||||
}, []);
|
||||
};
|
||||
|
||||
if (status === 'pending') {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center bg-white dark:bg-black">
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<LoaderIcon className="h-5 w-5 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
@ -106,8 +107,8 @@ export function HomeScreen() {
|
||||
case 'ArrowUp':
|
||||
case 'ArrowLeft': {
|
||||
e.preventDefault();
|
||||
const prevIndex = Math.max(selectedIndex - 1, 0);
|
||||
setSelectedIndex(prevIndex);
|
||||
const prevIndex = Math.max(index.peek() - 1, 0);
|
||||
index.value = prevIndex;
|
||||
ref.current.scrollToIndex(prevIndex, {
|
||||
align: 'center',
|
||||
smooth: true,
|
||||
@ -117,8 +118,8 @@ export function HomeScreen() {
|
||||
case 'ArrowDown':
|
||||
case 'ArrowRight': {
|
||||
e.preventDefault();
|
||||
const nextIndex = Math.min(selectedIndex + 1, data.length - 1);
|
||||
setSelectedIndex(nextIndex);
|
||||
const nextIndex = Math.min(index.peek() + 1, data.length - 1);
|
||||
index.value = nextIndex;
|
||||
ref.current.scrollToIndex(nextIndex, {
|
||||
align: 'center',
|
||||
smooth: true,
|
||||
|
5
src/libs/ark/components/widget/content.tsx
Normal file
5
src/libs/ark/components/widget/content.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export function WidgetContent({ children }: { children: ReactNode }) {
|
||||
return <div className="h-full w-full">{children}</div>;
|
||||
}
|
112
src/libs/ark/components/widget/header.tsx
Normal file
112
src/libs/ark/components/widget/header.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { ReactNode } from 'react';
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
ArrowRightIcon,
|
||||
HomeIcon,
|
||||
HorizontalDotsIcon,
|
||||
RefreshIcon,
|
||||
TrashIcon,
|
||||
} from '@shared/icons';
|
||||
import { useWidget } from '@utils/hooks/useWidget';
|
||||
|
||||
export function WidgetHeader({
|
||||
id,
|
||||
title,
|
||||
queryKey,
|
||||
icon,
|
||||
}: {
|
||||
id: string;
|
||||
title: string;
|
||||
queryKey?: string;
|
||||
icon?: ReactNode;
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
const { removeWidget } = useWidget();
|
||||
|
||||
const refresh = async () => {
|
||||
if (queryKey) await queryClient.refetchQueries({ queryKey: [queryKey] });
|
||||
};
|
||||
|
||||
const moveLeft = async () => {
|
||||
removeWidget.mutate(id);
|
||||
};
|
||||
|
||||
const moveRight = async () => {
|
||||
removeWidget.mutate(id);
|
||||
};
|
||||
|
||||
const deleteWidget = async () => {
|
||||
removeWidget.mutate(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-11 w-full shrink-0 items-center justify-between border-b border-neutral-100 px-3 dark:border-neutral-900">
|
||||
<div className="inline-flex items-center gap-4">
|
||||
<div className="h-5 w-1 rounded-full bg-blue-500" />
|
||||
<div className="inline-flex items-center gap-2">
|
||||
{icon ? icon : <HomeIcon className="h-5 w-5" />}
|
||||
<div className="text-sm font-medium">{title}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-6 w-6 items-center justify-center"
|
||||
>
|
||||
<HorizontalDotsIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content className="flex w-[220px] flex-col overflow-hidden rounded-xl border border-neutral-100 bg-white p-2 shadow-lg shadow-neutral-200/50 focus:outline-none dark:border-neutral-900 dark:bg-neutral-950 dark:shadow-neutral-900/50">
|
||||
<DropdownMenu.Item asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={refresh}
|
||||
className="inline-flex h-9 items-center gap-2 rounded-lg px-3 text-sm font-medium text-neutral-700 hover:bg-blue-100 hover:text-blue-500 focus:outline-none dark:text-neutral-300 dark:hover:bg-neutral-900 dark:hover:text-neutral-50"
|
||||
>
|
||||
<RefreshIcon className="h-5 w-5" />
|
||||
Refresh
|
||||
</button>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={moveLeft}
|
||||
className="inline-flex h-9 items-center gap-2 rounded-lg px-3 text-sm font-medium text-neutral-700 hover:bg-blue-100 hover:text-blue-500 focus:outline-none dark:text-neutral-300 dark:hover:bg-neutral-900 dark:hover:text-neutral-50"
|
||||
>
|
||||
<ArrowLeftIcon className="h-5 w-5" />
|
||||
Move left
|
||||
</button>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={moveRight}
|
||||
className="inline-flex h-9 items-center gap-2 rounded-lg px-3 text-sm font-medium text-neutral-700 hover:bg-blue-100 hover:text-blue-500 focus:outline-none dark:text-neutral-300 dark:hover:bg-neutral-900 dark:hover:text-neutral-50"
|
||||
>
|
||||
<ArrowRightIcon className="h-5 w-5" />
|
||||
Move right
|
||||
</button>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator className="my-1 h-px bg-neutral-100 dark:bg-neutral-900" />
|
||||
<DropdownMenu.Item asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={deleteWidget}
|
||||
className="inline-flex h-9 items-center gap-2 rounded-lg px-3 text-sm font-medium text-red-500 hover:bg-red-500 hover:text-red-50 focus:outline-none"
|
||||
>
|
||||
<TrashIcon className="h-5 w-5" />
|
||||
Delete
|
||||
</button>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
9
src/libs/ark/components/widget/index.ts
Normal file
9
src/libs/ark/components/widget/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { WidgetContent } from './content';
|
||||
import { WidgetHeader } from './header';
|
||||
import { WidgetRoot } from './root';
|
||||
|
||||
export const Widget = {
|
||||
Root: WidgetRoot,
|
||||
Header: WidgetHeader,
|
||||
Content: WidgetContent,
|
||||
};
|
@ -1,22 +1,23 @@
|
||||
import { useSignal } from '@preact/signals-react';
|
||||
import { Resizable } from 're-resizable';
|
||||
import { ReactNode, useState } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function WidgetWrapper({
|
||||
export function WidgetRoot({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
const [width, setWidth] = useState(420);
|
||||
const width = useSignal(420);
|
||||
|
||||
return (
|
||||
<Resizable
|
||||
size={{ width: width, height: '100%' }}
|
||||
size={{ width: width.value, height: '100%' }}
|
||||
onResizeStart={(e) => e.preventDefault()}
|
||||
onResizeStop={(_e, _direction, _ref, d) => {
|
||||
setWidth((prevWidth) => prevWidth + d.width);
|
||||
width.value = width.peek() + d.width;
|
||||
}}
|
||||
minWidth={420}
|
||||
maxWidth={600}
|
@ -1,2 +1,6 @@
|
||||
export * from './ark';
|
||||
export * from './provider';
|
||||
export * from './components/widget';
|
||||
export * from './components/widget/content';
|
||||
export * from './components/widget/header';
|
||||
export * from './components/widget/root';
|
||||
|
@ -38,7 +38,7 @@ const ArkProvider = ({ children }: PropsWithChildren<object>) => {
|
||||
|
||||
// start depot
|
||||
if (_ark.settings.depot) {
|
||||
await ark.launchDepot();
|
||||
await _ark.launchDepot();
|
||||
await delay(2000);
|
||||
}
|
||||
|
||||
|
18
src/shared/icons/announcement.tsx
Normal file
18
src/shared/icons/announcement.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
export function AnnouncementIcon(props: JSX.IntrinsicElements['svg']) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M16.36 3.014A27.429 27.429 0 0 1 8.143 8.04l-4.67 1.825a5.126 5.126 0 0 0 1.7 6.34l1.631-.25m9.556-12.94c-.875.234-.824 3.262.114 6.764.938 3.501 2.408 6.15 3.283 5.915M16.36 3.014c.875-.234 2.345 2.414 3.284 5.915.938 3.502.989 6.53.113 6.765m0 0a27.428 27.428 0 0 0-8.595-.382m0 0L13.295 22H8.92l-2.116-6.044m4.358-.644c-.345.04-.69.085-1.034.138l-3.324.506" />
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -1,15 +1,18 @@
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function ArrowLeftIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
export function ArrowLeftIcon(props: JSX.IntrinsicElements['svg']) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M10 18.25L3.75 12M3.75 12L10 5.75M3.75 12H20.25"
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M8.83 6a30.23 30.23 0 0 0-5.62 5.406A.949.949 0 0 0 3 12m5.83 6a30.233 30.233 0 0 1-5.62-5.406A.949.949 0 0 1 3 12m0 0h18" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,15 +1,18 @@
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function ArrowRightIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
export function ArrowRightIcon(props: JSX.IntrinsicElements['svg']) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M14 5.75L20.25 12M20.25 12L14 18.25M20.25 12H3.75"
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M15.17 6a30.23 30.23 0 0 1 5.62 5.406c.14.174.21.384.21.594m-5.83 6a30.232 30.232 0 0 0 5.62-5.406A.949.949 0 0 0 21 12m0 0H3" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,21 +1,18 @@
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function HomeIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
export function HomeIcon(props: JSX.IntrinsicElements['svg']) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M10.108 1.999a3 3 0 013.784 0l6 4.875A3 3 0 0121 9.202V18a3 3 0 01-3 3H6a3 3 0 01-3-3V9.202a3 3 0 011.108-2.328l6-4.875zM8 15a1 1 0 100 2h8a1 1 0 100-2H8z"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
<path d="M9 17h6M7.606 5.65l-2.6 2.456c-.74.698-1.11 1.047-1.374 1.46a4 4 0 0 0-.513 1.191C3 11.233 3 11.742 3 12.76V14.6c0 2.24 0 3.36.436 4.216a4 4 0 0 0 1.748 1.748C6.04 21 7.16 21 9.4 21h5.2c2.24 0 3.36 0 4.216-.436a4 4 0 0 0 1.748-1.748C21 17.96 21 16.84 21 14.6v-1.841c0-1.017 0-1.526-.119-2.002a4 4 0 0 0-.513-1.19c-.265-.414-.634-.763-1.374-1.461l-2.6-2.456c-1.546-1.46-2.32-2.19-3.201-2.466a4 4 0 0 0-2.386 0c-.882.275-1.655 1.006-3.201 2.466Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -84,3 +84,4 @@ export * from './info';
|
||||
export * from './light';
|
||||
export * from './dark';
|
||||
export * from './system';
|
||||
export * from './announcement';
|
||||
|
@ -1,14 +1,18 @@
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function RefreshIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
export function RefreshIcon(props: JSX.IntrinsicElements['svg']) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M11.979 4.5C7.83687 4.5 4.479 7.85786 4.479 12C4.479 16.1421 7.83687 19.5 11.979 19.5C15.2434 19.5 18.0225 17.4141 19.0524 14.5001C19.1905 14.1095 19.619 13.9048 20.0095 14.0429C20.4 14.1809 20.6047 14.6094 20.4667 14.9999C19.2315 18.4945 15.8988 21 11.979 21C7.00844 21 2.979 16.9706 2.979 12C2.979 7.02944 7.00844 3 11.979 3C13.709 3 15.1419 3.42256 16.4191 4.20651C17.1663 4.6651 17.8487 5.24046 18.5 5.90708V4C18.5 3.58579 18.8358 3.25 19.25 3.25C19.6642 3.25 20 3.58579 20 4V8C20 8.41421 19.6642 8.75 19.25 8.75H15.25C14.8358 8.75 14.5 8.41421 14.5 8C14.5 7.58579 14.8358 7.25 15.25 7.25H17.7068C17.0285 6.51595 16.3546 5.92693 15.6345 5.4849C14.6015 4.85088 13.4417 4.5 11.979 4.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M17.5 2.474c.51 1.192.861 2.444 1.049 3.726a.479.479 0 0 1-.298.515l-.181.07M6.5 21.527A15 15 0 0 1 5.451 17.8a.48.48 0 0 1 .298-.515l.181-.07M14.5 7.67a15 15 0 0 0 3.57-.884m0 0a8 8 0 0 0-13.912 6.797m15.75-2.79A8 8 0 0 1 5.93 17.215m3.571-.885a15.002 15.002 0 0 0-3.57.884" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,22 +1,18 @@
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function TimeLineIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
export function TimelineIcon(props: JSX.IntrinsicElements['svg']) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M22.25 15C17.215 15 15 17.215 15 22.25 15 17.215 12.785 15 7.75 15 12.785 15 15 12.785 15 7.75c0 5.035 2.215 7.25 7.25 7.25zM11.25 6.5c-3.299 0-4.75 1.451-4.75 4.75 0-3.299-1.451-4.75-4.75-4.75 3.299 0 4.75-1.451 4.75-4.75 0 3.299 1.451 4.75 4.75 4.75z"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M15 21v-5a3 3 0 1 0-6 0v5M7.606 5.65l-2.6 2.456c-.74.698-1.11 1.047-1.374 1.46a4 4 0 0 0-.513 1.191C3 11.233 3 11.742 3 12.76V14.6c0 2.24 0 3.36.436 4.216a4 4 0 0 0 1.748 1.748C6.04 21 7.16 21 9.4 21h5.2c2.24 0 3.36 0 4.216-.436a4 4 0 0 0 1.748-1.748C21 17.96 21 16.84 21 14.6v-1.841c0-1.017 0-1.526-.119-2.002a4 4 0 0 0-.513-1.19c-.265-.414-.634-.763-1.374-1.461l-2.6-2.456c-1.546-1.46-2.32-2.19-3.201-2.466a4 4 0 0 0-2.386 0c-.882.275-1.655 1.006-3.201 2.466Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,19 +1,18 @@
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function TrashIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
export function TrashIcon(props: JSX.IntrinsicElements['svg']) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path
|
||||
d="M5.75 21.25L5.00156 21.2983C5.02702 21.6929 5.35453 22 5.75 22V21.25ZM18.25 21.25V22C18.6455 22 18.973 21.6929 18.9984 21.2983L18.25 21.25ZM2.75 5C2.33579 5 2 5.33579 2 5.75C2 6.16421 2.33579 6.5 2.75 6.5V5ZM21.25 6.5C21.6642 6.5 22 6.16421 22 5.75C22 5.33579 21.6642 5 21.25 5V6.5ZM10.5 10.75C10.5 10.3358 10.1642 10 9.75 10C9.33579 10 9 10.3358 9 10.75H10.5ZM9 16.25C9 16.6642 9.33579 17 9.75 17C10.1642 17 10.5 16.6642 10.5 16.25H9ZM15 10.75C15 10.3358 14.6642 10 14.25 10C13.8358 10 13.5 10.3358 13.5 10.75H15ZM13.5 16.25C13.5 16.6642 13.8358 17 14.25 17C14.6642 17 15 16.6642 15 16.25H13.5ZM15.1477 5.93694C15.2509 6.33808 15.6598 6.57957 16.0609 6.47633C16.4621 6.37308 16.7036 5.9642 16.6003 5.56306L15.1477 5.93694ZM4.00156 5.79829L5.00156 21.2983L6.49844 21.2017L5.49844 5.70171L4.00156 5.79829ZM5.75 22H18.25V20.5H5.75V22ZM18.9984 21.2983L19.9984 5.79829L18.5016 5.70171L17.5016 21.2017L18.9984 21.2983ZM19.25 5H4.75V6.5H19.25V5ZM2.75 6.5H4.75V5H2.75V6.5ZM19.25 6.5H21.25V5H19.25V6.5ZM9 10.75V16.25H10.5V10.75H9ZM13.5 10.75V16.25H15V10.75H13.5ZM12 3.5C13.5134 3.5 14.7868 4.53504 15.1477 5.93694L16.6003 5.56306C16.0731 3.51451 14.2144 2 12 2V3.5ZM8.85237 5.93694C9.21319 4.53504 10.4867 3.5 12 3.5V2C9.78568 2 7.92697 3.51451 7.39971 5.56306L8.85237 5.93694Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path d="m16 6-1.106-2.211a3.236 3.236 0 0 0-5.788 0L8 6M4 6h16m-10 5v5m4-5v5M6 6h12v9c0 1.864 0 2.796-.305 3.53a4 4 0 0 1-2.164 2.165C14.796 21 13.864 21 12 21s-2.796 0-3.53-.305a4 4 0 0 1-2.166-2.164C6 17.796 6 16.864 6 15V6Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,10 +1,18 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { type Platform } from '@tauri-apps/plugin-os';
|
||||
import { Outlet, ScrollRestoration } from 'react-router-dom';
|
||||
import { WindowTitleBar } from '@shared/titlebar';
|
||||
|
||||
export function AuthLayout() {
|
||||
export function AuthLayout({ platform }: { platform: Platform }) {
|
||||
return (
|
||||
<div className="h-full w-full px-2.5 pb-2.5 pt-1">
|
||||
<div className="flex h-full min-h-0 w-full rounded-lg bg-white p-3 shadow-[rgba(50,_50,_105,_0.15)_0px_2px_5px_0px,_rgba(0,_0,_0,_0.05)_0px_1px_1px_0px] dark:bg-black dark:shadow-[inset_0_0_0.5px_1px_hsla(0,0%,100%,0.075),0_0_0_1px_hsla(0,0%,0%,0.05),0_0.3px_0.4px_hsla(0,0%,0%,0.02),0_0.9px_1.5px_hsla(0,0%,0%,0.045),0_3.5px_6px_hsla(0,0%,0%,0.09)]">
|
||||
<div className="flex h-screen w-screen flex-col bg-neutral-50 dark:bg-neutral-950">
|
||||
{platform !== 'macos' ? (
|
||||
<WindowTitleBar platform={platform} />
|
||||
) : (
|
||||
<div data-tauri-drag-region className="h-9 shrink-0" />
|
||||
)}
|
||||
<div className="h-full w-full">
|
||||
<Outlet />
|
||||
<ScrollRestoration />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Platform } from '@tauri-apps/plugin-os';
|
||||
import { NavLink, Outlet, useNavigate } from 'react-router-dom';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import {
|
||||
@ -8,11 +9,18 @@ import {
|
||||
SettingsIcon,
|
||||
UserIcon,
|
||||
} from '@shared/icons';
|
||||
import { WindowTitleBar } from '@shared/titlebar';
|
||||
|
||||
export function SettingsLayout() {
|
||||
export function SettingsLayout({ platform }: { platform: Platform }) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-screen flex-col bg-neutral-50 dark:bg-neutral-950">
|
||||
{platform !== 'macos' ? (
|
||||
<WindowTitleBar platform={platform} />
|
||||
) : (
|
||||
<div data-tauri-drag-region className="h-9 shrink-0" />
|
||||
)}
|
||||
<div className="flex h-full min-h-0 w-full flex-col gap-8 overflow-y-auto">
|
||||
<div className="flex h-20 w-full items-center justify-between border-b border-neutral-200 px-2 pb-2 dark:border-neutral-900">
|
||||
<div>
|
||||
@ -101,5 +109,6 @@ export function SettingsLayout() {
|
||||
</div>
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -3,18 +3,18 @@ import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { FetchFilter } from 'nostr-fetch';
|
||||
import { useMemo } from 'react';
|
||||
import { VList } from 'virtua';
|
||||
import { useArk } from '@libs/ark';
|
||||
import { Widget, useArk } from '@libs/ark';
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import { MemoizedArticleNote } from '@shared/notes';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
import { Widget } from '@utils/types';
|
||||
import { WidgetProps } from '@utils/types';
|
||||
|
||||
export function ArticleWidget({ widget }: { widget: Widget }) {
|
||||
export function ArticleWidget({ props }: { props: WidgetProps }) {
|
||||
const ark = useArk();
|
||||
|
||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['article', widget.id],
|
||||
queryKey: ['article', props.id],
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({
|
||||
signal,
|
||||
@ -24,7 +24,7 @@ export function ArticleWidget({ widget }: { widget: Widget }) {
|
||||
pageParam: number;
|
||||
}) => {
|
||||
let filter: FetchFilter;
|
||||
const content = JSON.parse(widget.content);
|
||||
const content = JSON.parse(props.content);
|
||||
|
||||
if (content.global) {
|
||||
filter = {
|
||||
@ -62,8 +62,9 @@ export function ArticleWidget({ widget }: { widget: Widget }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<TitleBar id={widget.id} title={widget.title} />
|
||||
<Widget.Root>
|
||||
<Widget.Header id={props.id} title={props.title} />
|
||||
<Widget.Content>
|
||||
<VList className="flex-1">
|
||||
{status === 'pending' ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
@ -106,6 +107,7 @@ export function ArticleWidget({ widget }: { widget: Widget }) {
|
||||
) : null}
|
||||
</div>
|
||||
</VList>
|
||||
</WidgetWrapper>
|
||||
</Widget.Content>
|
||||
</Widget.Root>
|
||||
);
|
||||
}
|
||||
|
@ -2,18 +2,18 @@ import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { FetchFilter } from 'nostr-fetch';
|
||||
import { useMemo } from 'react';
|
||||
import { VList } from 'virtua';
|
||||
import { useArk } from '@libs/ark';
|
||||
import { Widget, useArk } from '@libs/ark';
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import { MemoizedFileNote } from '@shared/notes';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
import { Widget } from '@utils/types';
|
||||
import { WidgetProps } from '@utils/types';
|
||||
|
||||
export function FileWidget({ widget }: { widget: Widget }) {
|
||||
export function FileWidget({ props }: { props: WidgetProps }) {
|
||||
const ark = useArk();
|
||||
|
||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['media', widget.id],
|
||||
queryKey: ['media', props.id],
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({
|
||||
signal,
|
||||
@ -23,7 +23,7 @@ export function FileWidget({ widget }: { widget: Widget }) {
|
||||
pageParam: number;
|
||||
}) => {
|
||||
let filter: FetchFilter;
|
||||
const content = JSON.parse(widget.content);
|
||||
const content = JSON.parse(props.content);
|
||||
|
||||
if (content.global) {
|
||||
filter = {
|
||||
@ -61,8 +61,9 @@ export function FileWidget({ widget }: { widget: Widget }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<TitleBar id={widget.id} title={widget.title} />
|
||||
<Widget.Root>
|
||||
<Widget.Header id={props.id} title={props.title} />
|
||||
<Widget.Content>
|
||||
<VList className="flex-1">
|
||||
{status === 'pending' ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
@ -105,6 +106,7 @@ export function FileWidget({ widget }: { widget: Widget }) {
|
||||
) : null}
|
||||
</div>
|
||||
</VList>
|
||||
</WidgetWrapper>
|
||||
</Widget.Content>
|
||||
</Widget.Root>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { VList } from 'virtua';
|
||||
import { useArk } from '@libs/ark';
|
||||
import { Widget, useArk } from '@libs/ark';
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import {
|
||||
MemoizedRepost,
|
||||
@ -10,15 +10,14 @@ import {
|
||||
NoteSkeleton,
|
||||
UnknownNote,
|
||||
} from '@shared/notes';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
import { Widget } from '@utils/types';
|
||||
import { WidgetProps } from '@utils/types';
|
||||
|
||||
export function GroupWidget({ widget }: { widget: Widget }) {
|
||||
export function GroupWidget({ props }: { props: WidgetProps }) {
|
||||
const ark = useArk();
|
||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['groupfeeds', widget.id],
|
||||
queryKey: ['groupfeeds', props.id],
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({
|
||||
signal,
|
||||
@ -27,7 +26,7 @@ export function GroupWidget({ widget }: { widget: Widget }) {
|
||||
signal: AbortSignal;
|
||||
pageParam: number;
|
||||
}) => {
|
||||
const authors = JSON.parse(widget.content);
|
||||
const authors = JSON.parse(props.content);
|
||||
const events = await ark.getInfiniteEvents({
|
||||
filter: {
|
||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||
@ -55,8 +54,7 @@ export function GroupWidget({ widget }: { widget: Widget }) {
|
||||
[data]
|
||||
);
|
||||
|
||||
const renderItem = useCallback(
|
||||
(event: NDKEvent) => {
|
||||
const renderItem = (event: NDKEvent) => {
|
||||
switch (event.kind) {
|
||||
case NDKKind.Text:
|
||||
return <MemoizedTextNote key={event.id} event={event} />;
|
||||
@ -65,13 +63,12 @@ export function GroupWidget({ widget }: { widget: Widget }) {
|
||||
default:
|
||||
return <UnknownNote key={event.id} event={event} />;
|
||||
}
|
||||
},
|
||||
[data]
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<TitleBar id={widget.id} title={widget.title} />
|
||||
<Widget.Root>
|
||||
<Widget.Header id={props.id} title={props.title} />
|
||||
<Widget.Content>
|
||||
<VList className="flex-1">
|
||||
{status === 'pending' ? (
|
||||
<div className="px-3 py-1.5">
|
||||
@ -102,6 +99,7 @@ export function GroupWidget({ widget }: { widget: Widget }) {
|
||||
) : null}
|
||||
</div>
|
||||
</VList>
|
||||
</WidgetWrapper>
|
||||
</Widget.Content>
|
||||
</Widget.Root>
|
||||
);
|
||||
}
|
||||
|
@ -1,19 +1,18 @@
|
||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { VList } from 'virtua';
|
||||
import { useArk } from '@libs/ark';
|
||||
import { Widget, useArk } from '@libs/ark';
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import { MemoizedRepost, MemoizedTextNote, UnknownNote } from '@shared/notes';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
import { Widget } from '@utils/types';
|
||||
import { WidgetProps } from '@utils/types';
|
||||
|
||||
export function HashtagWidget({ widget }: { widget: Widget }) {
|
||||
export function HashtagWidget({ props }: { props: WidgetProps }) {
|
||||
const ark = useArk();
|
||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['hashtag', widget.id],
|
||||
queryKey: ['hashtag', props.id],
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({
|
||||
signal,
|
||||
@ -25,7 +24,7 @@ export function HashtagWidget({ widget }: { widget: Widget }) {
|
||||
const events = await ark.getInfiniteEvents({
|
||||
filter: {
|
||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||
'#t': [widget.content],
|
||||
'#t': [props.content],
|
||||
},
|
||||
limit: FETCH_LIMIT,
|
||||
pageParam,
|
||||
@ -50,8 +49,7 @@ export function HashtagWidget({ widget }: { widget: Widget }) {
|
||||
);
|
||||
|
||||
// render event match event kind
|
||||
const renderItem = useCallback(
|
||||
(event: NDKEvent) => {
|
||||
const renderItem = (event: NDKEvent) => {
|
||||
switch (event.kind) {
|
||||
case NDKKind.Text:
|
||||
return <MemoizedTextNote key={event.id} event={event} />;
|
||||
@ -60,13 +58,12 @@ export function HashtagWidget({ widget }: { widget: Widget }) {
|
||||
default:
|
||||
return <UnknownNote key={event.id} event={event} />;
|
||||
}
|
||||
},
|
||||
[data]
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<TitleBar id={widget.id} title={widget.title} />
|
||||
<Widget.Root>
|
||||
<Widget.Header id={props.id} title={props.title} />
|
||||
<Widget.Content>
|
||||
<VList className="flex-1">
|
||||
{status === 'pending' ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
@ -78,7 +75,7 @@ export function HashtagWidget({ widget }: { widget: Widget }) {
|
||||
<img src="/ghost.png" alt="empty feeds" className="h-16 w-16" />
|
||||
<div className="text-center">
|
||||
<h3 className="font-semibold leading-tight text-neutral-900 dark:text-neutral-100">
|
||||
Oops, it looks like there are no events related to {widget.content}.
|
||||
Oops, it looks like there are no events related to {props.content}.
|
||||
</h3>
|
||||
<p className="text-neutral-500 dark:text-neutral-400">
|
||||
You can close this widget
|
||||
@ -109,6 +106,7 @@ export function HashtagWidget({ widget }: { widget: Widget }) {
|
||||
) : null}
|
||||
</div>
|
||||
</VList>
|
||||
</WidgetWrapper>
|
||||
</Widget.Content>
|
||||
</Widget.Root>
|
||||
);
|
||||
}
|
||||
|
@ -7,10 +7,8 @@ export * from './file';
|
||||
export * from './hashtag';
|
||||
export * from './thread';
|
||||
export * from './group';
|
||||
export * from './titleBar';
|
||||
export * from './nostrBand/trendingAccounts';
|
||||
export * from './nostrBand/trendingNotes';
|
||||
export * from './other/wrapper';
|
||||
export * from './other/liveUpdater';
|
||||
export * from './other/toggleWidgetList';
|
||||
export * from './other/widgetList';
|
||||
|
@ -2,15 +2,14 @@ import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { VList, VListHandle } from 'virtua';
|
||||
import { useArk } from '@libs/ark';
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import { Widget, useArk } from '@libs/ark';
|
||||
import { ArrowRightCircleIcon, LoaderIcon, TimelineIcon } from '@shared/icons';
|
||||
import {
|
||||
MemoizedRepost,
|
||||
MemoizedTextNote,
|
||||
NoteSkeleton,
|
||||
UnknownNote,
|
||||
} from '@shared/notes';
|
||||
import { LiveUpdater, TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
|
||||
export function NewsfeedWidget() {
|
||||
@ -67,9 +66,13 @@ export function NewsfeedWidget() {
|
||||
};
|
||||
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<TitleBar id="9999" isLive />
|
||||
<LiveUpdater status={status} />
|
||||
<Widget.Root>
|
||||
<Widget.Header
|
||||
id="9999"
|
||||
title="Timeline"
|
||||
icon={<TimelineIcon className="h-5 w-5" />}
|
||||
/>
|
||||
<Widget.Content>
|
||||
<VList ref={ref} overscan={2} className="flex-1">
|
||||
{status === 'pending' ? (
|
||||
<div className="px-3 py-1.5">
|
||||
@ -80,7 +83,7 @@ export function NewsfeedWidget() {
|
||||
) : (
|
||||
allEvents.map((item) => renderItem(item))
|
||||
)}
|
||||
<div className="flex h-16 items-center justify-center px-3 pb-3">
|
||||
<div className="flex h-16 items-center justify-center px-3 py-3">
|
||||
{hasNextPage ? (
|
||||
<button
|
||||
type="button"
|
||||
@ -100,6 +103,7 @@ export function NewsfeedWidget() {
|
||||
) : null}
|
||||
</div>
|
||||
</VList>
|
||||
</WidgetWrapper>
|
||||
</Widget.Content>
|
||||
</Widget.Root>
|
||||
);
|
||||
}
|
||||
|
@ -1,19 +1,15 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { VList } from 'virtua';
|
||||
import { Widget } from '@libs/ark';
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
import {
|
||||
NostrBandUserProfile,
|
||||
type Profile,
|
||||
TitleBar,
|
||||
WidgetWrapper,
|
||||
} from '@shared/widgets';
|
||||
import { Widget } from '@utils/types';
|
||||
import { NostrBandUserProfile, type Profile } from '@shared/widgets';
|
||||
import { WidgetProps } from '@utils/types';
|
||||
|
||||
interface Response {
|
||||
profiles: Array<{ pubkey: string }>;
|
||||
}
|
||||
|
||||
export function TrendingAccountsWidget({ widget }: { widget: Widget }) {
|
||||
export function TrendingAccountsWidget({ props }: { props: WidgetProps }) {
|
||||
const { status, data } = useQuery({
|
||||
queryKey: ['trending-users'],
|
||||
queryFn: async () => {
|
||||
@ -32,8 +28,9 @@ export function TrendingAccountsWidget({ widget }: { widget: Widget }) {
|
||||
});
|
||||
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<TitleBar id={widget.id} title="Trending Accounts" />
|
||||
<Widget.Root>
|
||||
<Widget.Header id={props.id} title="Trending Accounts" />
|
||||
<Widget.Content>
|
||||
<div className="flex-1">
|
||||
{status === 'pending' ? (
|
||||
<div className="flex h-full w-full items-center justify-center ">
|
||||
@ -64,6 +61,7 @@ export function TrendingAccountsWidget({ widget }: { widget: Widget }) {
|
||||
</VList>
|
||||
)}
|
||||
</div>
|
||||
</WidgetWrapper>
|
||||
</Widget.Content>
|
||||
</Widget.Root>
|
||||
);
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { VList } from 'virtua';
|
||||
import { Widget } from '@libs/ark';
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
import { MemoizedTextNote } from '@shared/notes';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
import { Widget } from '@utils/types';
|
||||
import { WidgetProps } from '@utils/types';
|
||||
|
||||
interface Response {
|
||||
notes: Array<{ event: NDKEvent }>;
|
||||
}
|
||||
|
||||
export function TrendingNotesWidget({ widget }: { widget: Widget }) {
|
||||
export function TrendingNotesWidget({ props }: { props: WidgetProps }) {
|
||||
const { status, data } = useQuery({
|
||||
queryKey: ['trending-posts'],
|
||||
queryFn: async () => {
|
||||
@ -29,8 +29,9 @@ export function TrendingNotesWidget({ widget }: { widget: Widget }) {
|
||||
});
|
||||
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<TitleBar id={widget.id} title="Trending Notes" />
|
||||
<Widget.Root>
|
||||
<Widget.Header id={props.id} title="Trending Notes" />
|
||||
<Widget.Content>
|
||||
<VList className="flex-1">
|
||||
{status === 'pending' ? (
|
||||
<div className="flex h-full w-full items-center justify-center ">
|
||||
@ -53,9 +54,12 @@ export function TrendingNotesWidget({ widget }: { widget: Widget }) {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
data.map((item) => <MemoizedTextNote key={item.event.id} event={item.event} />)
|
||||
data.map((item) => (
|
||||
<MemoizedTextNote key={item.event.id} event={item.event} />
|
||||
))
|
||||
)}
|
||||
</VList>
|
||||
</WidgetWrapper>
|
||||
</Widget.Content>
|
||||
</Widget.Root>
|
||||
);
|
||||
}
|
||||
|
@ -1,18 +1,17 @@
|
||||
import { NDKEvent, NDKKind, NDKSubscription } from '@nostr-dev-kit/ndk';
|
||||
import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { VList } from 'virtua';
|
||||
import { useArk } from '@libs/ark';
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import { Widget, useArk } from '@libs/ark';
|
||||
import { AnnouncementIcon, ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import { MemoizedNotifyNote, NoteSkeleton } from '@shared/notes';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
import { sendNativeNotification } from '@utils/notification';
|
||||
|
||||
export function NotificationWidget() {
|
||||
const ark = useArk();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const ark = useArk();
|
||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['notification'],
|
||||
@ -52,10 +51,10 @@ export function NotificationWidget() {
|
||||
[data]
|
||||
);
|
||||
|
||||
const renderEvent = useCallback((event: NDKEvent) => {
|
||||
const renderEvent = (event: NDKEvent) => {
|
||||
if (event.pubkey === ark.account.pubkey) return null;
|
||||
return <MemoizedNotifyNote key={event.id} event={event} />;
|
||||
}, []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let sub: NDKSubscription = undefined;
|
||||
@ -124,8 +123,13 @@ export function NotificationWidget() {
|
||||
}, [status]);
|
||||
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<TitleBar id="9998" title="Notification" isLive />
|
||||
<Widget.Root>
|
||||
<Widget.Header
|
||||
id="9998"
|
||||
title="Notification"
|
||||
icon={<AnnouncementIcon className="h-5 w-5" />}
|
||||
/>
|
||||
<Widget.Content>
|
||||
<VList className="flex-1" overscan={2}>
|
||||
{status === 'pending' ? (
|
||||
<div className="px-3 py-1.5">
|
||||
@ -163,6 +167,7 @@ export function NotificationWidget() {
|
||||
) : null}
|
||||
</div>
|
||||
</VList>
|
||||
</WidgetWrapper>
|
||||
</Widget.Content>
|
||||
</Widget.Root>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Widget } from '@libs/ark';
|
||||
import { PlusIcon } from '@shared/icons';
|
||||
import { WidgetWrapper } from '@shared/widgets';
|
||||
import { WIDGET_KIND } from '@utils/constants';
|
||||
import { useWidget } from '@utils/hooks/useWidget';
|
||||
|
||||
@ -7,7 +7,8 @@ export function ToggleWidgetList() {
|
||||
const { addWidget } = useWidget();
|
||||
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<Widget.Root>
|
||||
<Widget.Content>
|
||||
<div className="relative flex h-full w-full flex-col items-center justify-center">
|
||||
<button
|
||||
type="button"
|
||||
@ -19,6 +20,7 @@ export function ToggleWidgetList() {
|
||||
<PlusIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</WidgetWrapper>
|
||||
</Widget.Content>
|
||||
</Widget.Root>
|
||||
);
|
||||
}
|
||||
|
@ -1,15 +1,17 @@
|
||||
import { Widget } from '@libs/ark';
|
||||
import { ArticleIcon, MediaIcon, PlusIcon } from '@shared/icons';
|
||||
import { AddGroupFeeds, AddHashtagFeeds, TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
import { AddGroupFeeds, AddHashtagFeeds } from '@shared/widgets';
|
||||
import { TOPICS, WIDGET_KIND } from '@utils/constants';
|
||||
import { useWidget } from '@utils/hooks/useWidget';
|
||||
import { Widget } from '@utils/types';
|
||||
import { WidgetProps } from '@utils/types';
|
||||
|
||||
export function WidgetList({ widget }: { widget: Widget }) {
|
||||
export function WidgetList({ props }: { props: WidgetProps }) {
|
||||
const { replaceWidget } = useWidget();
|
||||
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<TitleBar id={widget.id} title="Add widgets" />
|
||||
<Widget.Root>
|
||||
<Widget.Header id={props.id} title="Add widgets" />
|
||||
<Widget.Content>
|
||||
<div className="flex-1 overflow-y-auto pb-10 scrollbar-none">
|
||||
<div className="flex flex-col gap-6 px-3">
|
||||
<div className="rounded-xl bg-neutral-100 p-3 dark:bg-neutral-900">
|
||||
@ -37,7 +39,7 @@ export function WidgetList({ widget }: { widget: Widget }) {
|
||||
type="button"
|
||||
onClick={() =>
|
||||
replaceWidget.mutate({
|
||||
currentId: widget.id,
|
||||
currentId: props.id,
|
||||
widget: {
|
||||
kind: WIDGET_KIND.topic,
|
||||
title: topic.title,
|
||||
@ -60,8 +62,8 @@ export function WidgetList({ widget }: { widget: Widget }) {
|
||||
Newsfeed
|
||||
</h3>
|
||||
<div className="flex flex-col gap-3">
|
||||
<AddGroupFeeds currentWidgetId={widget.id} />
|
||||
<AddHashtagFeeds currentWidgetId={widget.id} />
|
||||
<AddGroupFeeds currentWidgetId={props.id} />
|
||||
<AddHashtagFeeds currentWidgetId={props.id} />
|
||||
<div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-neutral-50 px-3 hover:shadow-md hover:shadow-neutral-200/50 dark:bg-neutral-950 dark:hover:shadow-neutral-800/50">
|
||||
<div className="inline-flex items-center gap-2.5">
|
||||
<div className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-neutral-100 dark:bg-neutral-900">
|
||||
@ -73,7 +75,7 @@ export function WidgetList({ widget }: { widget: Widget }) {
|
||||
type="button"
|
||||
onClick={() =>
|
||||
replaceWidget.mutate({
|
||||
currentId: widget.id,
|
||||
currentId: props.id,
|
||||
widget: {
|
||||
kind: WIDGET_KIND.article,
|
||||
title: 'Articles',
|
||||
@ -98,7 +100,7 @@ export function WidgetList({ widget }: { widget: Widget }) {
|
||||
type="button"
|
||||
onClick={() =>
|
||||
replaceWidget.mutate({
|
||||
currentId: widget.id,
|
||||
currentId: props.id,
|
||||
widget: {
|
||||
kind: WIDGET_KIND.file,
|
||||
title: 'Media',
|
||||
@ -116,6 +118,7 @@ export function WidgetList({ widget }: { widget: Widget }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</WidgetWrapper>
|
||||
</Widget.Content>
|
||||
</Widget.Root>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||
import { useCallback } from 'react';
|
||||
import { WVList } from 'virtua';
|
||||
import { useArk } from '@libs/ark';
|
||||
import { Widget, useArk } from '@libs/ark';
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
import {
|
||||
ChildNote,
|
||||
@ -13,16 +12,14 @@ import {
|
||||
} from '@shared/notes';
|
||||
import { ReplyList } from '@shared/notes/replies/list';
|
||||
import { User } from '@shared/user';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
import { useEvent } from '@utils/hooks/useEvent';
|
||||
import { Widget } from '@utils/types';
|
||||
import { type WidgetProps } from '@utils/types';
|
||||
|
||||
export function ThreadWidget({ widget }: { widget: Widget }) {
|
||||
const { isFetching, isError, data } = useEvent(widget.content);
|
||||
export function ThreadWidget({ props }: { props: WidgetProps }) {
|
||||
const ark = useArk();
|
||||
const { isFetching, isError, data } = useEvent(props.content);
|
||||
|
||||
const renderKind = useCallback(
|
||||
(event: NDKEvent) => {
|
||||
const renderKind = (event: NDKEvent) => {
|
||||
const thread = ark.getEventThread({ tags: event.tags });
|
||||
switch (event.kind) {
|
||||
case NDKKind.Text:
|
||||
@ -48,13 +45,12 @@ export function ThreadWidget({ widget }: { widget: Widget }) {
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[data]
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<TitleBar id={widget.id} title={widget.title} />
|
||||
<Widget.Root>
|
||||
<Widget.Header id={props.id} title={props.title} />
|
||||
<Widget.Content>
|
||||
<WVList className="flex-1 overflow-y-auto px-3 pb-5">
|
||||
{isFetching ? (
|
||||
<div className="flex h-16 items-center justify-center rounded-xl bg-neutral-50 px-3 py-3 dark:bg-neutral-950">
|
||||
@ -78,6 +74,7 @@ export function ThreadWidget({ widget }: { widget: Widget }) {
|
||||
</>
|
||||
)}
|
||||
</WVList>
|
||||
</WidgetWrapper>
|
||||
</Widget.Content>
|
||||
</Widget.Root>
|
||||
);
|
||||
}
|
||||
|
@ -1,64 +0,0 @@
|
||||
import { useArk } from '@libs/ark';
|
||||
import { CancelIcon } from '@shared/icons';
|
||||
import { User } from '@shared/user';
|
||||
import { useWidget } from '@utils/hooks/useWidget';
|
||||
|
||||
export function TitleBar({
|
||||
id,
|
||||
title,
|
||||
isLive,
|
||||
}: {
|
||||
id?: string;
|
||||
title?: string;
|
||||
isLive?: boolean;
|
||||
}) {
|
||||
const ark = useArk();
|
||||
const { removeWidget } = useWidget();
|
||||
|
||||
return (
|
||||
<div className="grid h-11 w-full shrink-0 grid-cols-3 items-center px-3">
|
||||
<div className="col-span-1 flex justify-start">
|
||||
{isLive ? (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-teal-400 opacity-75"></span>
|
||||
<span className="relative inline-flex h-2 w-2 rounded-full bg-teal-500"></span>
|
||||
</span>
|
||||
<p className="text-xs font-medium text-teal-500">Live</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="col-span-1 flex justify-center">
|
||||
{id === '9999' ? (
|
||||
<div className="isolate flex -space-x-2">
|
||||
{ark.account.contacts
|
||||
?.slice(0, 8)
|
||||
.map((item) => <User key={item} pubkey={item} variant="ministacked" />)}
|
||||
{ark.account.contacts?.length > 8 ? (
|
||||
<div className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-neutral-300 text-neutral-900 ring-1 ring-white dark:bg-neutral-700 dark:text-neutral-100 dark:ring-black">
|
||||
<span className="text-[8px] font-medium">
|
||||
+{ark.account.contacts?.length - 8}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<h3 className="text-sm font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{title}
|
||||
</h3>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-span-1 flex justify-end">
|
||||
{id !== '9999' && id !== '9998' ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeWidget.mutate(id)}
|
||||
className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded text-neutral-900 backdrop-blur-xl hover:bg-neutral-100 dark:text-neutral-100 dark:hover:bg-neutral-900"
|
||||
>
|
||||
<CancelIcon className="h-3 w-3" />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import { NDKEvent, NDKFilter, NDKKind } from '@nostr-dev-kit/ndk';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { VList } from 'virtua';
|
||||
import { useArk } from '@libs/ark';
|
||||
import { Widget, useArk } from '@libs/ark';
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import {
|
||||
MemoizedRepost,
|
||||
@ -10,15 +10,14 @@ import {
|
||||
NoteSkeleton,
|
||||
UnknownNote,
|
||||
} from '@shared/notes';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
import { Widget } from '@utils/types';
|
||||
import { type WidgetProps } from '@utils/types';
|
||||
|
||||
export function TopicWidget({ widget }: { widget: Widget }) {
|
||||
export function TopicWidget({ props }: { props: WidgetProps }) {
|
||||
const ark = useArk();
|
||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['topic', widget.id],
|
||||
queryKey: ['topic', props.id],
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({
|
||||
signal,
|
||||
@ -27,7 +26,7 @@ export function TopicWidget({ widget }: { widget: Widget }) {
|
||||
signal: AbortSignal;
|
||||
pageParam: number;
|
||||
}) => {
|
||||
const hashtags: string[] = JSON.parse(widget.content as string);
|
||||
const hashtags: string[] = JSON.parse(props.content as string);
|
||||
const filter: NDKFilter = {
|
||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||
'#t': hashtags.map((tag) => tag.replace('#', '')),
|
||||
@ -55,8 +54,7 @@ export function TopicWidget({ widget }: { widget: Widget }) {
|
||||
[data]
|
||||
);
|
||||
|
||||
const renderItem = useCallback(
|
||||
(event: NDKEvent) => {
|
||||
const renderItem = (event: NDKEvent) => {
|
||||
switch (event.kind) {
|
||||
case NDKKind.Text:
|
||||
return <MemoizedTextNote key={event.id} event={event} />;
|
||||
@ -65,13 +63,12 @@ export function TopicWidget({ widget }: { widget: Widget }) {
|
||||
default:
|
||||
return <UnknownNote key={event.id} event={event} />;
|
||||
}
|
||||
},
|
||||
[data]
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<TitleBar id={widget.id} title={widget.title} />
|
||||
<Widget.Root>
|
||||
<Widget.Header id={props.id} title={props.title} />
|
||||
<Widget.Content>
|
||||
<VList className="flex-1" overscan={2}>
|
||||
{status === 'pending' ? (
|
||||
<div className="px-3 py-1.5">
|
||||
@ -102,6 +99,7 @@ export function TopicWidget({ widget }: { widget: Widget }) {
|
||||
) : null}
|
||||
</div>
|
||||
</VList>
|
||||
</WidgetWrapper>
|
||||
</Widget.Content>
|
||||
</Widget.Root>
|
||||
);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { WVList } from 'virtua';
|
||||
import { useArk } from '@libs/ark';
|
||||
import { Widget, useArk } from '@libs/ark';
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import {
|
||||
MemoizedRepost,
|
||||
@ -10,15 +10,15 @@ import {
|
||||
NoteSkeleton,
|
||||
UnknownNote,
|
||||
} from '@shared/notes';
|
||||
import { TitleBar, UserProfile, WidgetWrapper } from '@shared/widgets';
|
||||
import { UserProfile } from '@shared/widgets';
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
import { Widget } from '@utils/types';
|
||||
import { type WidgetProps } from '@utils/types';
|
||||
|
||||
export function UserWidget({ widget }: { widget: Widget }) {
|
||||
export function UserWidget({ props }: { props: WidgetProps }) {
|
||||
const ark = useArk();
|
||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['user-posts', widget.content],
|
||||
queryKey: ['user-posts', props.content],
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({
|
||||
signal,
|
||||
@ -30,7 +30,7 @@ export function UserWidget({ widget }: { widget: Widget }) {
|
||||
const events = await ark.getInfiniteEvents({
|
||||
filter: {
|
||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||
authors: [widget.content],
|
||||
authors: [props.content],
|
||||
},
|
||||
limit: FETCH_LIMIT,
|
||||
pageParam,
|
||||
@ -68,11 +68,12 @@ export function UserWidget({ widget }: { widget: Widget }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<TitleBar id={widget.id} title={widget.title} />
|
||||
<Widget.Root>
|
||||
<Widget.Header id={props.id} title={props.title} />
|
||||
<Widget.Content>
|
||||
<WVList className="flex-1 overflow-y-auto">
|
||||
<div className="px-3 pt-1.5">
|
||||
<UserProfile pubkey={widget.content} />
|
||||
<UserProfile pubkey={props.content} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="mb-3 mt-4 px-3 text-lg font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
@ -110,6 +111,7 @@ export function UserWidget({ widget }: { widget: Widget }) {
|
||||
</div>
|
||||
</div>
|
||||
</WVList>
|
||||
</WidgetWrapper>
|
||||
</Widget.Content>
|
||||
</Widget.Root>
|
||||
);
|
||||
}
|
||||
|
2
src/utils/types.d.ts
vendored
2
src/utils/types.d.ts
vendored
@ -30,7 +30,7 @@ export interface WidgetGroupItem {
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export interface Widget {
|
||||
export interface WidgetProps {
|
||||
id?: string;
|
||||
account_id?: number;
|
||||
kind: number;
|
||||
|
Loading…
Reference in New Issue
Block a user