From 55298515afa9d9b6223661075147b78aeada2094 Mon Sep 17 00:00:00 2001 From: reya Date: Mon, 18 Dec 2023 13:39:03 +0700 Subject: [PATCH] refactor(widget): migrate widget component to ark lib --- package.json | 1 + pnpm-lock.yaml | 25 ++ src/app.tsx | 264 +++++++++--------- src/app/home/index.tsx | 41 +-- src/libs/ark/components/widget/content.tsx | 5 + src/libs/ark/components/widget/header.tsx | 112 ++++++++ src/libs/ark/components/widget/index.ts | 9 + .../ark/components/widget/root.tsx} | 11 +- src/libs/ark/index.ts | 4 + src/libs/ark/provider.tsx | 2 +- src/shared/icons/announcement.tsx | 18 ++ src/shared/icons/arrowLeft.tsx | 25 +- src/shared/icons/arrowRight.tsx | 25 +- src/shared/icons/home.tsx | 19 +- src/shared/icons/index.ts | 1 + src/shared/icons/refresh.tsx | 24 +- src/shared/icons/timeline.tsx | 20 +- src/shared/icons/trash.tsx | 23 +- src/shared/layouts/auth.tsx | 16 +- src/shared/layouts/settings.tsx | 181 ++++++------ src/shared/widgets/article.tsx | 98 +++---- src/shared/widgets/file.tsx | 98 +++---- src/shared/widgets/group.tsx | 100 ++++--- src/shared/widgets/hashtag.tsx | 122 ++++---- src/shared/widgets/index.ts | 2 - src/shared/widgets/newsfeed.tsx | 74 ++--- .../widgets/nostrBand/trendingAccounts.tsx | 76 +++-- .../widgets/nostrBand/trendingNotes.tsx | 62 ++-- src/shared/widgets/notification.tsx | 95 ++++--- src/shared/widgets/other/toggleWidgetList.tsx | 30 +- src/shared/widgets/other/widgetList.tsx | 211 +++++++------- src/shared/widgets/thread.tsx | 117 ++++---- src/shared/widgets/titleBar.tsx | 64 ----- src/shared/widgets/topic.tsx | 100 ++++--- src/shared/widgets/user.tsx | 92 +++--- src/utils/types.d.ts | 2 +- 36 files changed, 1153 insertions(+), 1016 deletions(-) create mode 100644 src/libs/ark/components/widget/content.tsx create mode 100644 src/libs/ark/components/widget/header.tsx create mode 100644 src/libs/ark/components/widget/index.ts rename src/{shared/widgets/other/wrapper.tsx => libs/ark/components/widget/root.tsx} (71%) create mode 100644 src/shared/icons/announcement.tsx delete mode 100644 src/shared/widgets/titleBar.tsx diff --git a/package.json b/package.json index 4d3651b3..529fcc5e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4bc65519..e97cb79d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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'} diff --git a/src/app.tsx b/src/app.tsx index 485677d5..f42262ef 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -124,144 +124,138 @@ export default function App() { }, ], }, + ], + }, + { + path: 'auth', + element: , + errorElement: , + children: [ { - path: 'auth', - element: , - errorElement: , - children: [ - { - path: 'welcome', - async lazy() { - const { WelcomeScreen } = await import('@app/auth/welcome'); - return { Component: WelcomeScreen }; - }, - }, - { - path: 'create', - async lazy() { - const { CreateAccountScreen } = await import('@app/auth/create'); - return { Component: CreateAccountScreen }; - }, - }, - { - path: 'import', - async lazy() { - const { ImportAccountScreen } = await import('@app/auth/import'); - return { Component: ImportAccountScreen }; - }, - }, - { - path: 'onboarding', - async lazy() { - const { OnboardingScreen } = await import('@app/auth/onboarding'); - return { Component: OnboardingScreen }; - }, - }, - { - path: 'follow', - async lazy() { - const { FollowScreen } = await import('@app/auth/follow'); - return { Component: FollowScreen }; - }, - }, - { - path: 'finish', - async lazy() { - const { FinishScreen } = await import('@app/auth/finish'); - return { Component: FinishScreen }; - }, - }, - { - path: 'tutorials/note', - async lazy() { - const { TutorialNoteScreen } = await import('@app/auth/tutorials/note'); - return { Component: TutorialNoteScreen }; - }, - }, - { - path: 'tutorials/widget', - async lazy() { - const { TutorialWidgetScreen } = await import( - '@app/auth/tutorials/widget' - ); - return { Component: TutorialWidgetScreen }; - }, - }, - { - path: 'tutorials/posting', - async lazy() { - const { TutorialPostingScreen } = await import( - '@app/auth/tutorials/posting' - ); - return { Component: TutorialPostingScreen }; - }, - }, - { - path: 'tutorials/finish', - async lazy() { - const { TutorialFinishScreen } = await import( - '@app/auth/tutorials/finish' - ); - return { Component: TutorialFinishScreen }; - }, - }, - ], + path: 'welcome', + async lazy() { + const { WelcomeScreen } = await import('@app/auth/welcome'); + return { Component: WelcomeScreen }; + }, }, { - path: 'settings', - element: , - errorElement: , - children: [ - { - index: true, - async lazy() { - const { UserSettingScreen } = await import('@app/settings'); - return { Component: UserSettingScreen }; - }, - }, - { - path: 'edit-profile', - async lazy() { - const { EditProfileScreen } = await import('@app/settings/editProfile'); - return { Component: EditProfileScreen }; - }, - }, - { - path: 'edit-contact', - async lazy() { - const { EditContactScreen } = await import('@app/settings/editContact'); - return { Component: EditContactScreen }; - }, - }, - { - path: 'general', - async lazy() { - const { GeneralSettingScreen } = await import('@app/settings/general'); - return { Component: GeneralSettingScreen }; - }, - }, - { - path: 'backup', - async lazy() { - const { BackupSettingScreen } = await import('@app/settings/backup'); - return { Component: BackupSettingScreen }; - }, - }, - { - path: 'advanced', - async lazy() { - const { AdvancedSettingScreen } = await import('@app/settings/advanced'); - return { Component: AdvancedSettingScreen }; - }, - }, - { - path: 'about', - async lazy() { - const { AboutScreen } = await import('@app/settings/about'); - return { Component: AboutScreen }; - }, - }, - ], + path: 'create', + async lazy() { + const { CreateAccountScreen } = await import('@app/auth/create'); + return { Component: CreateAccountScreen }; + }, + }, + { + path: 'import', + async lazy() { + const { ImportAccountScreen } = await import('@app/auth/import'); + return { Component: ImportAccountScreen }; + }, + }, + { + path: 'onboarding', + async lazy() { + const { OnboardingScreen } = await import('@app/auth/onboarding'); + return { Component: OnboardingScreen }; + }, + }, + { + path: 'follow', + async lazy() { + const { FollowScreen } = await import('@app/auth/follow'); + return { Component: FollowScreen }; + }, + }, + { + path: 'finish', + async lazy() { + const { FinishScreen } = await import('@app/auth/finish'); + return { Component: FinishScreen }; + }, + }, + { + path: 'tutorials/note', + async lazy() { + const { TutorialNoteScreen } = await import('@app/auth/tutorials/note'); + return { Component: TutorialNoteScreen }; + }, + }, + { + path: 'tutorials/widget', + async lazy() { + const { TutorialWidgetScreen } = await import('@app/auth/tutorials/widget'); + return { Component: TutorialWidgetScreen }; + }, + }, + { + path: 'tutorials/posting', + async lazy() { + const { TutorialPostingScreen } = await import('@app/auth/tutorials/posting'); + return { Component: TutorialPostingScreen }; + }, + }, + { + path: 'tutorials/finish', + async lazy() { + const { TutorialFinishScreen } = await import('@app/auth/tutorials/finish'); + return { Component: TutorialFinishScreen }; + }, + }, + ], + }, + { + path: 'settings', + element: , + errorElement: , + children: [ + { + index: true, + async lazy() { + const { UserSettingScreen } = await import('@app/settings'); + return { Component: UserSettingScreen }; + }, + }, + { + path: 'edit-profile', + async lazy() { + const { EditProfileScreen } = await import('@app/settings/editProfile'); + return { Component: EditProfileScreen }; + }, + }, + { + path: 'edit-contact', + async lazy() { + const { EditContactScreen } = await import('@app/settings/editContact'); + return { Component: EditContactScreen }; + }, + }, + { + path: 'general', + async lazy() { + const { GeneralSettingScreen } = await import('@app/settings/general'); + return { Component: GeneralSettingScreen }; + }, + }, + { + path: 'backup', + async lazy() { + const { BackupSettingScreen } = await import('@app/settings/backup'); + return { Component: BackupSettingScreen }; + }, + }, + { + path: 'advanced', + async lazy() { + const { AdvancedSettingScreen } = await import('@app/settings/advanced'); + return { Component: AdvancedSettingScreen }; + }, + }, + { + path: 'about', + async lazy() { + const { AboutScreen } = await import('@app/settings/about'); + return { Component: AboutScreen }; + }, }, ], }, diff --git a/src/app/home/index.tsx b/src/app/home/index.tsx index 81655a10..c181a7d7 100644 --- a/src/app/home/index.tsx +++ b/src/app/home/index.tsx @@ -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(null); - const [selectedIndex, setSelectedIndex] = useState(-1); - const ark = useArk(); - const { status, data } = useQuery({ + const ref = useRef(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 ; @@ -80,13 +81,13 @@ export function HomeScreen() { case WIDGET_KIND.list: return ; default: - return null; + return ; } - }, []); + }; - if (status === 'pending') { + if (isLoading) { return ( -
+
); @@ -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, diff --git a/src/libs/ark/components/widget/content.tsx b/src/libs/ark/components/widget/content.tsx new file mode 100644 index 00000000..3d057b39 --- /dev/null +++ b/src/libs/ark/components/widget/content.tsx @@ -0,0 +1,5 @@ +import { ReactNode } from 'react'; + +export function WidgetContent({ children }: { children: ReactNode }) { + return
{children}
; +} diff --git a/src/libs/ark/components/widget/header.tsx b/src/libs/ark/components/widget/header.tsx new file mode 100644 index 00000000..5e65e97b --- /dev/null +++ b/src/libs/ark/components/widget/header.tsx @@ -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 ( +
+
+
+
+ {icon ? icon : } +
{title}
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ ); +} diff --git a/src/libs/ark/components/widget/index.ts b/src/libs/ark/components/widget/index.ts new file mode 100644 index 00000000..11397f42 --- /dev/null +++ b/src/libs/ark/components/widget/index.ts @@ -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, +}; diff --git a/src/shared/widgets/other/wrapper.tsx b/src/libs/ark/components/widget/root.tsx similarity index 71% rename from src/shared/widgets/other/wrapper.tsx rename to src/libs/ark/components/widget/root.tsx index 112b3075..f3f33486 100644 --- a/src/shared/widgets/other/wrapper.tsx +++ b/src/libs/ark/components/widget/root.tsx @@ -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 ( e.preventDefault()} onResizeStop={(_e, _direction, _ref, d) => { - setWidth((prevWidth) => prevWidth + d.width); + width.value = width.peek() + d.width; }} minWidth={420} maxWidth={600} diff --git a/src/libs/ark/index.ts b/src/libs/ark/index.ts index 52b61586..a988345e 100644 --- a/src/libs/ark/index.ts +++ b/src/libs/ark/index.ts @@ -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'; diff --git a/src/libs/ark/provider.tsx b/src/libs/ark/provider.tsx index 70ad0cbb..cf8f740e 100644 --- a/src/libs/ark/provider.tsx +++ b/src/libs/ark/provider.tsx @@ -38,7 +38,7 @@ const ArkProvider = ({ children }: PropsWithChildren) => { // start depot if (_ark.settings.depot) { - await ark.launchDepot(); + await _ark.launchDepot(); await delay(2000); } diff --git a/src/shared/icons/announcement.tsx b/src/shared/icons/announcement.tsx new file mode 100644 index 00000000..2fb7dbab --- /dev/null +++ b/src/shared/icons/announcement.tsx @@ -0,0 +1,18 @@ +export function AnnouncementIcon(props: JSX.IntrinsicElements['svg']) { + return ( + + + + ); +} diff --git a/src/shared/icons/arrowLeft.tsx b/src/shared/icons/arrowLeft.tsx index 73c78f9c..ceb952d9 100644 --- a/src/shared/icons/arrowLeft.tsx +++ b/src/shared/icons/arrowLeft.tsx @@ -1,15 +1,18 @@ -import { SVGProps } from 'react'; - -export function ArrowLeftIcon(props: JSX.IntrinsicAttributes & SVGProps) { +export function ArrowLeftIcon(props: JSX.IntrinsicElements['svg']) { return ( - - + + ); } diff --git a/src/shared/icons/arrowRight.tsx b/src/shared/icons/arrowRight.tsx index 2089fc14..33ebdc07 100644 --- a/src/shared/icons/arrowRight.tsx +++ b/src/shared/icons/arrowRight.tsx @@ -1,15 +1,18 @@ -import { SVGProps } from 'react'; - -export function ArrowRightIcon(props: JSX.IntrinsicAttributes & SVGProps) { +export function ArrowRightIcon(props: JSX.IntrinsicElements['svg']) { return ( - - + + ); } diff --git a/src/shared/icons/home.tsx b/src/shared/icons/home.tsx index 13648efd..ad97d4de 100644 --- a/src/shared/icons/home.tsx +++ b/src/shared/icons/home.tsx @@ -1,21 +1,18 @@ -import { SVGProps } from 'react'; - -export function HomeIcon(props: JSX.IntrinsicAttributes & SVGProps) { +export function HomeIcon(props: JSX.IntrinsicElements['svg']) { return ( - + ); } diff --git a/src/shared/icons/index.ts b/src/shared/icons/index.ts index 2fd28a33..d41e3b2d 100644 --- a/src/shared/icons/index.ts +++ b/src/shared/icons/index.ts @@ -84,3 +84,4 @@ export * from './info'; export * from './light'; export * from './dark'; export * from './system'; +export * from './announcement'; diff --git a/src/shared/icons/refresh.tsx b/src/shared/icons/refresh.tsx index 5faeb6e3..1bf56169 100644 --- a/src/shared/icons/refresh.tsx +++ b/src/shared/icons/refresh.tsx @@ -1,14 +1,18 @@ -import { SVGProps } from 'react'; - -export function RefreshIcon(props: JSX.IntrinsicAttributes & SVGProps) { +export function RefreshIcon(props: JSX.IntrinsicElements['svg']) { return ( - - + + ); } diff --git a/src/shared/icons/timeline.tsx b/src/shared/icons/timeline.tsx index 52ff870e..1373d1ff 100644 --- a/src/shared/icons/timeline.tsx +++ b/src/shared/icons/timeline.tsx @@ -1,22 +1,18 @@ -import { SVGProps } from 'react'; - -export function TimeLineIcon(props: JSX.IntrinsicAttributes & SVGProps) { +export function TimelineIcon(props: JSX.IntrinsicElements['svg']) { return ( - + ); } diff --git a/src/shared/icons/trash.tsx b/src/shared/icons/trash.tsx index 610bd16c..0078ac7b 100644 --- a/src/shared/icons/trash.tsx +++ b/src/shared/icons/trash.tsx @@ -1,19 +1,18 @@ -import { SVGProps } from 'react'; - -export function TrashIcon(props: JSX.IntrinsicAttributes & SVGProps) { +export function TrashIcon(props: JSX.IntrinsicElements['svg']) { return ( - + ); } diff --git a/src/shared/layouts/auth.tsx b/src/shared/layouts/auth.tsx index 21dfecaf..32d4514f 100644 --- a/src/shared/layouts/auth.tsx +++ b/src/shared/layouts/auth.tsx @@ -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 ( -
-
+
+ {platform !== 'macos' ? ( + + ) : ( +
+ )} +
+
); diff --git a/src/shared/layouts/settings.tsx b/src/shared/layouts/settings.tsx index 975800f4..cda41e2b 100644 --- a/src/shared/layouts/settings.tsx +++ b/src/shared/layouts/settings.tsx @@ -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,98 +9,106 @@ import { SettingsIcon, UserIcon, } from '@shared/icons'; +import { WindowTitleBar } from '@shared/titlebar'; -export function SettingsLayout() { +export function SettingsLayout({ platform }: { platform: Platform }) { const navigate = useNavigate(); return ( -
-
-
- +
+ {platform !== 'macos' ? ( + + ) : ( +
+ )} +
+
+
+ +
+
+ + twMerge( + 'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900', + isActive + ? 'bg-neutral-50 text-blue-500 hover:bg-neutral-100 dark:bg-neutral-950 dark:hover:bg-neutral-900' + : '' + ) + } + > + +

User

+
+ + twMerge( + 'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900', + isActive + ? 'bg-neutral-50 text-blue-500 hover:bg-neutral-100 dark:bg-neutral-950 dark:hover:bg-neutral-900' + : '' + ) + } + > + +

General

+
+ + twMerge( + 'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900', + isActive + ? 'bg-neutral-50 text-blue-500 hover:bg-neutral-100 dark:bg-neutral-950 dark:hover:bg-neutral-900' + : '' + ) + } + > + +

Backup

+
+ + twMerge( + 'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900', + isActive + ? 'bg-neutral-50 text-blue-500 hover:bg-neutral-100 dark:bg-neutral-950 dark:hover:bg-neutral-900' + : '' + ) + } + > + +

Advanced

+
+ + twMerge( + 'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900', + isActive + ? 'bg-neutral-50 text-blue-500 hover:bg-neutral-100 dark:bg-neutral-950 dark:hover:bg-neutral-900' + : '' + ) + } + > + +

About

+
+
+
-
- - twMerge( - 'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900', - isActive - ? 'bg-neutral-50 text-blue-500 hover:bg-neutral-100 dark:bg-neutral-950 dark:hover:bg-neutral-900' - : '' - ) - } - > - -

User

-
- - twMerge( - 'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900', - isActive - ? 'bg-neutral-50 text-blue-500 hover:bg-neutral-100 dark:bg-neutral-950 dark:hover:bg-neutral-900' - : '' - ) - } - > - -

General

-
- - twMerge( - 'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900', - isActive - ? 'bg-neutral-50 text-blue-500 hover:bg-neutral-100 dark:bg-neutral-950 dark:hover:bg-neutral-900' - : '' - ) - } - > - -

Backup

-
- - twMerge( - 'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900', - isActive - ? 'bg-neutral-50 text-blue-500 hover:bg-neutral-100 dark:bg-neutral-950 dark:hover:bg-neutral-900' - : '' - ) - } - > - -

Advanced

-
- - twMerge( - 'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900', - isActive - ? 'bg-neutral-50 text-blue-500 hover:bg-neutral-100 dark:bg-neutral-950 dark:hover:bg-neutral-900' - : '' - ) - } - > - -

About

-
-
-
+
-
); } diff --git a/src/shared/widgets/article.tsx b/src/shared/widgets/article.tsx index 1bb2eebb..4457f8eb 100644 --- a/src/shared/widgets/article.tsx +++ b/src/shared/widgets/article.tsx @@ -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,50 +62,52 @@ export function ArticleWidget({ widget }: { widget: Widget }) { ); return ( - - - - {status === 'pending' ? ( -
- -
- ) : allEvents.length === 0 ? ( -
-
- empty feeds -
-

- Oops, it looks like there are no articles. -

-

- You can close this widget -

+ + + + + {status === 'pending' ? ( +
+ +
+ ) : allEvents.length === 0 ? ( +
+
+ empty feeds +
+

+ Oops, it looks like there are no articles. +

+

+ You can close this widget +

+
+ ) : ( + allEvents.map((item) => ) + )} +
+ {hasNextPage ? ( + + ) : null}
- ) : ( - allEvents.map((item) => ) - )} -
- {hasNextPage ? ( - - ) : null} -
-
- + +
+
); } diff --git a/src/shared/widgets/file.tsx b/src/shared/widgets/file.tsx index 9f5fd5fd..05e33a64 100644 --- a/src/shared/widgets/file.tsx +++ b/src/shared/widgets/file.tsx @@ -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,50 +61,52 @@ export function FileWidget({ widget }: { widget: Widget }) { ); return ( - - - - {status === 'pending' ? ( -
- -
- ) : allEvents.length === 0 ? ( -
-
- empty feeds -
-

- Oops, it looks like there are no files. -

-

- You can close this widget -

+ + + + + {status === 'pending' ? ( +
+ +
+ ) : allEvents.length === 0 ? ( +
+
+ empty feeds +
+

+ Oops, it looks like there are no files. +

+

+ You can close this widget +

+
+ ) : ( + allEvents.map((item) => ) + )} +
+ {hasNextPage ? ( + + ) : null}
- ) : ( - allEvents.map((item) => ) - )} -
- {hasNextPage ? ( - - ) : null} -
-
- + +
+
); } diff --git a/src/shared/widgets/group.tsx b/src/shared/widgets/group.tsx index 7a73ec84..563e6fcd 100644 --- a/src/shared/widgets/group.tsx +++ b/src/shared/widgets/group.tsx @@ -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,53 +54,52 @@ export function GroupWidget({ widget }: { widget: Widget }) { [data] ); - const renderItem = useCallback( - (event: NDKEvent) => { - switch (event.kind) { - case NDKKind.Text: - return ; - case NDKKind.Repost: - return ; - default: - return ; - } - }, - [data] - ); + const renderItem = (event: NDKEvent) => { + switch (event.kind) { + case NDKKind.Text: + return ; + case NDKKind.Repost: + return ; + default: + return ; + } + }; return ( - - - - {status === 'pending' ? ( -
-
- + + + + + {status === 'pending' ? ( +
+
+ +
+ ) : ( + allEvents.map((item) => renderItem(item)) + )} +
+ {hasNextPage ? ( + + ) : null}
- ) : ( - allEvents.map((item) => renderItem(item)) - )} -
- {hasNextPage ? ( - - ) : null} -
-
- + +
+
); } diff --git a/src/shared/widgets/hashtag.tsx b/src/shared/widgets/hashtag.tsx index 4b64ddb3..dbb464db 100644 --- a/src/shared/widgets/hashtag.tsx +++ b/src/shared/widgets/hashtag.tsx @@ -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,65 +49,64 @@ export function HashtagWidget({ widget }: { widget: Widget }) { ); // render event match event kind - const renderItem = useCallback( - (event: NDKEvent) => { - switch (event.kind) { - case NDKKind.Text: - return ; - case NDKKind.Repost: - return ; - default: - return ; - } - }, - [data] - ); + const renderItem = (event: NDKEvent) => { + switch (event.kind) { + case NDKKind.Text: + return ; + case NDKKind.Repost: + return ; + default: + return ; + } + }; return ( - - - - {status === 'pending' ? ( -
- -
- ) : allEvents.length === 0 ? ( -
-
- empty feeds -
-

- Oops, it looks like there are no events related to {widget.content}. -

-

- You can close this widget -

+ + + + + {status === 'pending' ? ( +
+ +
+ ) : allEvents.length === 0 ? ( +
+
+ empty feeds +
+

+ Oops, it looks like there are no events related to {props.content}. +

+

+ You can close this widget +

+
+ ) : ( + allEvents.map((item) => renderItem(item)) + )} +
+ {hasNextPage ? ( + + ) : null}
- ) : ( - allEvents.map((item) => renderItem(item)) - )} -
- {hasNextPage ? ( - - ) : null} -
-
- + +
+
); } diff --git a/src/shared/widgets/index.ts b/src/shared/widgets/index.ts index 1b09b219..4ee6964a 100644 --- a/src/shared/widgets/index.ts +++ b/src/shared/widgets/index.ts @@ -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'; diff --git a/src/shared/widgets/newsfeed.tsx b/src/shared/widgets/newsfeed.tsx index 24e6fc24..445b407a 100644 --- a/src/shared/widgets/newsfeed.tsx +++ b/src/shared/widgets/newsfeed.tsx @@ -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,39 +66,44 @@ export function NewsfeedWidget() { }; return ( - - - - - {status === 'pending' ? ( -
-
- + + } + /> + + + {status === 'pending' ? ( +
+
+ +
+ ) : ( + allEvents.map((item) => renderItem(item)) + )} +
+ {hasNextPage ? ( + + ) : null}
- ) : ( - allEvents.map((item) => renderItem(item)) - )} -
- {hasNextPage ? ( - - ) : null} -
-
- + +
+
); } diff --git a/src/shared/widgets/nostrBand/trendingAccounts.tsx b/src/shared/widgets/nostrBand/trendingAccounts.tsx index 666accb9..4137095b 100644 --- a/src/shared/widgets/nostrBand/trendingAccounts.tsx +++ b/src/shared/widgets/nostrBand/trendingAccounts.tsx @@ -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,38 +28,40 @@ export function TrendingAccountsWidget({ widget }: { widget: Widget }) { }); return ( - - -
- {status === 'pending' ? ( -
-
- -

- Loading trending accounts... -

-
-
- ) : status === 'error' ? ( -
-
- empty feeds -
-

- Sorry, an unexpected error has occurred. -

+ + + +
+ {status === 'pending' ? ( +
+
+ +

+ Loading trending accounts... +

-
- ) : ( - - {data.map((item: Profile) => ( - - ))} -
- - )} -
- + ) : status === 'error' ? ( +
+
+ empty feeds +
+

+ Sorry, an unexpected error has occurred. +

+
+
+
+ ) : ( + + {data.map((item: Profile) => ( + + ))} +
+ + )} +
+
+
); } diff --git a/src/shared/widgets/nostrBand/trendingNotes.tsx b/src/shared/widgets/nostrBand/trendingNotes.tsx index 169b4b9b..2043bd7e 100644 --- a/src/shared/widgets/nostrBand/trendingNotes.tsx +++ b/src/shared/widgets/nostrBand/trendingNotes.tsx @@ -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,33 +29,37 @@ export function TrendingNotesWidget({ widget }: { widget: Widget }) { }); return ( - - - - {status === 'pending' ? ( -
-
- -

- Loading trending posts... -

-
-
- ) : status === 'error' ? ( -
-
- empty feeds -
-

- Sorry, an unexpected error has occurred. -

+ + + + + {status === 'pending' ? ( +
+
+ +

+ Loading trending posts... +

-
- ) : ( - data.map((item) => ) - )} - - + ) : status === 'error' ? ( +
+
+ empty feeds +
+

+ Sorry, an unexpected error has occurred. +

+
+
+
+ ) : ( + data.map((item) => ( + + )) + )} + + + ); } diff --git a/src/shared/widgets/notification.tsx b/src/shared/widgets/notification.tsx index 5831ebe5..88795bfc 100644 --- a/src/shared/widgets/notification.tsx +++ b/src/shared/widgets/notification.tsx @@ -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 ; - }, []); + }; useEffect(() => { let sub: NDKSubscription = undefined; @@ -124,45 +123,51 @@ export function NotificationWidget() { }, [status]); return ( - - - - {status === 'pending' ? ( -
-
- + + } + /> + + + {status === 'pending' ? ( +
+
+ +
+ ) : allEvents.length < 1 ? ( +
+

🎉

+

+ Hmm! Nothing new yet. +

+
+ ) : ( + allEvents.map((event) => renderEvent(event)) + )} +
+ {hasNextPage ? ( + + ) : null}
- ) : allEvents.length < 1 ? ( -
-

🎉

-

- Hmm! Nothing new yet. -

-
- ) : ( - allEvents.map((event) => renderEvent(event)) - )} -
- {hasNextPage ? ( - - ) : null} -
-
- + +
+
); } diff --git a/src/shared/widgets/other/toggleWidgetList.tsx b/src/shared/widgets/other/toggleWidgetList.tsx index c7267351..7fe72175 100644 --- a/src/shared/widgets/other/toggleWidgetList.tsx +++ b/src/shared/widgets/other/toggleWidgetList.tsx @@ -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,18 +7,20 @@ export function ToggleWidgetList() { const { addWidget } = useWidget(); return ( - -
- -
-
+ + +
+ +
+
+
); } diff --git a/src/shared/widgets/other/widgetList.tsx b/src/shared/widgets/other/widgetList.tsx index 95f54fd7..798e90a9 100644 --- a/src/shared/widgets/other/widgetList.tsx +++ b/src/shared/widgets/other/widgetList.tsx @@ -1,121 +1,124 @@ +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 ( - - -
-
-
-

- Topics -

-
- {TOPICS.sort((a, b) => a.title.localeCompare(b.title)).map( - (topic, index) => ( -
-
-
- {topic.title} -
-

{topic.title}

-
- -
- ) - )} -
-
-
-

- Newsfeed -

-
- - -
-
-
- -
-

Articles

-
- +
+
+ {topic.title} +
+

{topic.title}

+
+ +
+ ) + )}
-
-
-
- +
+
+

+ Newsfeed +

+
+ + +
+
+
+ +
+

Articles

-

Media

+ +
+
+
+
+ +
+

Media

+
+
-
-
- + + ); } diff --git a/src/shared/widgets/thread.tsx b/src/shared/widgets/thread.tsx index 4eabeb20..fb5cc846 100644 --- a/src/shared/widgets/thread.tsx +++ b/src/shared/widgets/thread.tsx @@ -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,71 +12,69 @@ 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 thread = ark.getEventThread({ tags: event.tags }); - switch (event.kind) { - case NDKKind.Text: - return ( - <> - {thread ? ( -
-
- {thread.rootEventId ? ( - - ) : null} - {thread.replyEventId ? : null} -
+ const renderKind = (event: NDKEvent) => { + const thread = ark.getEventThread({ tags: event.tags }); + switch (event.kind) { + case NDKKind.Text: + return ( + <> + {thread ? ( +
+
+ {thread.rootEventId ? ( + + ) : null} + {thread.replyEventId ? : null}
- ) : null} - - - ); - case NDKKind.Article: - return ; - case 1063: - return ; - default: - return null; - } - }, - [data] - ); +
+ ) : null} + + + ); + case NDKKind.Article: + return ; + case 1063: + return ; + default: + return null; + } + }; return ( - - - - {isFetching ? ( -
- -
- ) : ( - <> -
- {isError ? ( -
Failed to fetch event
- ) : ( - <> - - {renderKind(data)} - - - )} + + + + + {isFetching ? ( +
+
- - - - )} -
- + ) : ( + <> +
+ {isError ? ( +
Failed to fetch event
+ ) : ( + <> + + {renderKind(data)} + + + )} +
+ + + + )} + +
+
); } diff --git a/src/shared/widgets/titleBar.tsx b/src/shared/widgets/titleBar.tsx deleted file mode 100644 index 593454b2..00000000 --- a/src/shared/widgets/titleBar.tsx +++ /dev/null @@ -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 ( -
-
- {isLive ? ( -
- - - - -

Live

-
- ) : null} -
-
- {id === '9999' ? ( -
- {ark.account.contacts - ?.slice(0, 8) - .map((item) => )} - {ark.account.contacts?.length > 8 ? ( -
- - +{ark.account.contacts?.length - 8} - -
- ) : null} -
- ) : ( -

- {title} -

- )} -
-
- {id !== '9999' && id !== '9998' ? ( - - ) : null} -
-
- ); -} diff --git a/src/shared/widgets/topic.tsx b/src/shared/widgets/topic.tsx index 26ef784e..d7a5fe70 100644 --- a/src/shared/widgets/topic.tsx +++ b/src/shared/widgets/topic.tsx @@ -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,53 +54,52 @@ export function TopicWidget({ widget }: { widget: Widget }) { [data] ); - const renderItem = useCallback( - (event: NDKEvent) => { - switch (event.kind) { - case NDKKind.Text: - return ; - case NDKKind.Repost: - return ; - default: - return ; - } - }, - [data] - ); + const renderItem = (event: NDKEvent) => { + switch (event.kind) { + case NDKKind.Text: + return ; + case NDKKind.Repost: + return ; + default: + return ; + } + }; return ( - - - - {status === 'pending' ? ( -
-
- + + + + + {status === 'pending' ? ( +
+
+ +
+ ) : ( + allEvents.map((item) => renderItem(item)) + )} +
+ {hasNextPage ? ( + + ) : null}
- ) : ( - allEvents.map((item) => renderItem(item)) - )} -
- {hasNextPage ? ( - - ) : null} -
-
- + +
+
); } diff --git a/src/shared/widgets/user.tsx b/src/shared/widgets/user.tsx index 84b5a57f..94d10877 100644 --- a/src/shared/widgets/user.tsx +++ b/src/shared/widgets/user.tsx @@ -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,48 +68,50 @@ export function UserWidget({ widget }: { widget: Widget }) { ); return ( - - - -
- -
-
-

- Latest posts -

-
- {status === 'pending' ? ( -
-
- + + + + +
+ +
+
+

+ Latest posts +

+
+ {status === 'pending' ? ( +
+
+ +
+ ) : ( + allEvents.map((item) => renderItem(item)) + )} +
+ {hasNextPage ? ( + + ) : null}
- ) : ( - allEvents.map((item) => renderItem(item)) - )} -
- {hasNextPage ? ( - - ) : null}
-
-
- + +
+
); } diff --git a/src/utils/types.d.ts b/src/utils/types.d.ts index 01ceb2d0..85fca12f 100644 --- a/src/utils/types.d.ts +++ b/src/utils/types.d.ts @@ -30,7 +30,7 @@ export interface WidgetGroupItem { icon?: string; } -export interface Widget { +export interface WidgetProps { id?: string; account_id?: number; kind: number;