refactor all widgets
This commit is contained in:
@@ -6,8 +6,8 @@ import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { ArrowLeftIcon, CheckCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
|
||||
import { WidgetKinds } from '@stores/constants';
|
||||
import { useOnboarding } from '@stores/onboarding';
|
||||
import { WidgetKinds } from '@stores/widgets';
|
||||
|
||||
const data = [
|
||||
{ hashtag: '#bitcoin' },
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
import { PlusIcon } from '@shared/icons';
|
||||
import { WidgetWrapper } from '@shared/widgets';
|
||||
|
||||
import { HandArrowDownIcon, PlusIcon } from '@shared/icons';
|
||||
import { WidgetKinds } from '@stores/constants';
|
||||
|
||||
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||
import { useWidget } from '@utils/hooks/useWidget';
|
||||
|
||||
export function ToggleWidgetList() {
|
||||
const { db } = useStorage();
|
||||
const setWidget = useWidgets((state) => state.setWidget);
|
||||
const { addWidget } = useWidget();
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-[420px] items-center justify-center border-r border-neutral-100 dark:border-neutral-900">
|
||||
<div className="relative">
|
||||
<div className="absolute -top-44 left-1/2 -translate-x-1/2 transform">
|
||||
<HandArrowDownIcon className="text-neutral-100 dark:text-neutral-900" />
|
||||
</div>
|
||||
<WidgetWrapper>
|
||||
<div className="relative flex h-full w-full flex-col items-center justify-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setWidget(db, { kind: WidgetKinds.tmp.list, title: '', content: '' })
|
||||
addWidget.mutate({ kind: WidgetKinds.tmp.list, title: '', content: '' })
|
||||
}
|
||||
className="inline-flex h-9 items-center gap-2 rounded-full bg-neutral-200 px-3 text-neutral-900 hover:bg-neutral-300 dark:bg-neutral-800 dark:text-neutral-100 dark:hover:bg-neutral-700"
|
||||
>
|
||||
@@ -25,6 +22,6 @@ export function ToggleWidgetList() {
|
||||
<p className="text-sm font-semibold leading-none">Add widget</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</WidgetWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import {
|
||||
ArticleIcon,
|
||||
BellIcon,
|
||||
@@ -13,22 +11,15 @@ import {
|
||||
TrendingIcon,
|
||||
} from '@shared/icons';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
import { WidgetWrapper } from '@shared/widgets';
|
||||
|
||||
import { DefaultWidgets, WidgetKinds, useWidgets } from '@stores/widgets';
|
||||
import { DefaultWidgets, WidgetKinds } from '@stores/constants';
|
||||
|
||||
import { Widget, WidgetGroup, WidgetGroupItem } from '@utils/types';
|
||||
import { useWidget } from '@utils/hooks/useWidget';
|
||||
import { Widget, WidgetGroup } from '@utils/types';
|
||||
|
||||
export function WidgetList({ params }: { params: Widget }) {
|
||||
const { db } = useStorage();
|
||||
const [setWidget, removeWidget] = useWidgets((state) => [
|
||||
state.setWidget,
|
||||
state.removeWidget,
|
||||
]);
|
||||
|
||||
const openWidget = (widget: WidgetGroupItem) => {
|
||||
setWidget(db, { kind: widget.kind, title: widget.title, content: '' });
|
||||
removeWidget(db, params.id);
|
||||
};
|
||||
const { addWidget } = useWidget();
|
||||
|
||||
const renderIcon = useCallback(
|
||||
(kind: number) => {
|
||||
@@ -71,52 +62,51 @@ export function WidgetList({ params }: { params: Widget }) {
|
||||
[DefaultWidgets]
|
||||
);
|
||||
|
||||
const renderItem = useCallback(
|
||||
(row: WidgetGroup, index: number) => {
|
||||
return (
|
||||
<div key={index} className="flex flex-col gap-2">
|
||||
<h3 className="text-sm font-semibold text-neutral-500 dark:text-neutral-300">
|
||||
{row.title}
|
||||
</h3>
|
||||
<div className="flex flex-col divide-y divide-neutral-100 overflow-hidden rounded-xl bg-neutral-50 dark:divide-neutral-900 dark:bg-neutral-950">
|
||||
{row.data.map((item, index) => (
|
||||
<button
|
||||
onClick={() => openWidget(item)}
|
||||
key={index}
|
||||
className="group flex items-center gap-2.5 px-4 hover:bg-neutral-200 dark:hover:bg-neutral-800"
|
||||
>
|
||||
{item.icon ? (
|
||||
<div className="h-10 w-10 shrink-0 rounded-md">
|
||||
<img
|
||||
src={item.icon}
|
||||
alt={item.title}
|
||||
className="h-10 w-10 object-cover"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-neutral-200 group-hover:bg-neutral-300 dark:bg-neutral-800 dark:group-hover:bg-neutral-700">
|
||||
{renderIcon(item.kind)}
|
||||
</div>
|
||||
)}
|
||||
<div className="inline-flex h-16 w-full flex-col items-start justify-center">
|
||||
<h5 className="line-clamp-1 text-sm font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{item.title}
|
||||
</h5>
|
||||
<p className="line-clamp-1 text-sm text-neutral-500 dark:text-neutral-300">
|
||||
{item.description}
|
||||
</p>
|
||||
const renderItem = useCallback((row: WidgetGroup, index: number) => {
|
||||
return (
|
||||
<div key={index} className="flex flex-col gap-2">
|
||||
<h3 className="text-sm font-semibold text-neutral-500 dark:text-neutral-300">
|
||||
{row.title}
|
||||
</h3>
|
||||
<div className="flex flex-col divide-y divide-neutral-100 overflow-hidden rounded-xl bg-neutral-50 dark:divide-neutral-900 dark:bg-neutral-950">
|
||||
{row.data.map((item, index) => (
|
||||
<button
|
||||
onClick={() =>
|
||||
addWidget.mutate({ kind: item.kind, title: item.title, content: '' })
|
||||
}
|
||||
key={index}
|
||||
className="group flex items-center gap-2.5 px-4 hover:bg-neutral-200 dark:hover:bg-neutral-800"
|
||||
>
|
||||
{item.icon ? (
|
||||
<div className="h-10 w-10 shrink-0 rounded-md">
|
||||
<img
|
||||
src={item.icon}
|
||||
alt={item.title}
|
||||
className="h-10 w-10 object-cover"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-neutral-200 group-hover:bg-neutral-300 dark:bg-neutral-800 dark:group-hover:bg-neutral-700">
|
||||
{renderIcon(item.kind)}
|
||||
</div>
|
||||
)}
|
||||
<div className="inline-flex h-16 w-full flex-col items-start justify-center">
|
||||
<h5 className="line-clamp-1 text-sm font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{item.title}
|
||||
</h5>
|
||||
<p className="line-clamp-1 text-sm text-neutral-500 dark:text-neutral-300">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[DefaultWidgets]
|
||||
);
|
||||
</div>
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full w-[420px] border-r border-neutral-100 dark:border-neutral-900">
|
||||
<WidgetWrapper>
|
||||
<TitleBar id={params.id} title="Add widget" />
|
||||
<div className="h-full overflow-y-auto pb-20 scrollbar-none">
|
||||
<div className="flex flex-col gap-6 px-3">
|
||||
@@ -139,6 +129,6 @@ export function WidgetList({ params }: { params: Widget }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</WidgetWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { VList, VListHandle } from 'virtua';
|
||||
|
||||
import { ToggleWidgetList } from '@app/space/components/toggle';
|
||||
@@ -11,99 +12,134 @@ import {
|
||||
GlobalArticlesWidget,
|
||||
GlobalFilesWidget,
|
||||
GlobalHashtagWidget,
|
||||
LearnNostrWidget,
|
||||
LocalArticlesWidget,
|
||||
LocalFeedsWidget,
|
||||
LocalFilesWidget,
|
||||
LocalFollowsWidget,
|
||||
LocalNotificationWidget,
|
||||
LocalThreadWidget,
|
||||
LocalUserWidget,
|
||||
NewsfeedWidget,
|
||||
NotificationWidget,
|
||||
TrendingAccountsWidget,
|
||||
TrendingNotesWidget,
|
||||
XfeedsWidget,
|
||||
XhashtagWidget,
|
||||
} from '@shared/widgets';
|
||||
|
||||
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||
import { WidgetKinds } from '@stores/constants';
|
||||
|
||||
import { Widget } from '@utils/types';
|
||||
|
||||
export function SpaceScreen() {
|
||||
const ref = useRef<VListHandle>(null);
|
||||
const [selectedIndex, setSelectedIndex] = useState(-1);
|
||||
|
||||
const { db } = useStorage();
|
||||
const vlistRef = useRef<VListHandle>(null);
|
||||
const { status, data } = useQuery({
|
||||
queryKey: ['widgets'],
|
||||
queryFn: async () => {
|
||||
const dbWidgets = await db.getWidgets();
|
||||
const defaultWidgets = [
|
||||
{
|
||||
id: '9998',
|
||||
title: 'Notification',
|
||||
content: '',
|
||||
kind: WidgetKinds.local.notification,
|
||||
},
|
||||
{
|
||||
id: '9999',
|
||||
title: 'Newsfeed',
|
||||
content: '',
|
||||
kind: WidgetKinds.local.network,
|
||||
},
|
||||
];
|
||||
|
||||
const [widgets, fetchWidgets] = useWidgets((state) => [
|
||||
state.widgets,
|
||||
state.fetchWidgets,
|
||||
]);
|
||||
|
||||
const renderItem = useCallback(
|
||||
(widget: Widget) => {
|
||||
if (!widget) return;
|
||||
switch (widget.kind) {
|
||||
case WidgetKinds.local.network:
|
||||
return <NewsfeedWidget key={widget.id} />;
|
||||
case WidgetKinds.local.follows:
|
||||
return <LocalFollowsWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.local.feeds:
|
||||
return <LocalFeedsWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.local.files:
|
||||
return <LocalFilesWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.local.articles:
|
||||
return <LocalArticlesWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.local.user:
|
||||
return <LocalUserWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.local.thread:
|
||||
return <LocalThreadWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.global.hashtag:
|
||||
return <GlobalHashtagWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.global.articles:
|
||||
return <GlobalArticlesWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.global.files:
|
||||
return <GlobalFilesWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.nostrBand.trendingAccounts:
|
||||
return <TrendingAccountsWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.nostrBand.trendingNotes:
|
||||
return <TrendingNotesWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.tmp.xfeed:
|
||||
return <XfeedsWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.tmp.xhashtag:
|
||||
return <XhashtagWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.tmp.list:
|
||||
return <WidgetList key={widget.id} params={widget} />;
|
||||
case WidgetKinds.other.learnNostr:
|
||||
return <LearnNostrWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.local.notification:
|
||||
return <LocalNotificationWidget key={widget.id} params={widget} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return [...defaultWidgets, ...dbWidgets];
|
||||
},
|
||||
[widgets]
|
||||
);
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
staleTime: Infinity,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetchWidgets(db);
|
||||
const renderItem = useCallback((widget: Widget) => {
|
||||
switch (widget.kind) {
|
||||
case WidgetKinds.local.feeds:
|
||||
return <LocalFeedsWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.local.files:
|
||||
return <LocalFilesWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.local.articles:
|
||||
return <LocalArticlesWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.local.user:
|
||||
return <LocalUserWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.local.thread:
|
||||
return <LocalThreadWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.global.hashtag:
|
||||
return <GlobalHashtagWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.global.articles:
|
||||
return <GlobalArticlesWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.global.files:
|
||||
return <GlobalFilesWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.nostrBand.trendingAccounts:
|
||||
return <TrendingAccountsWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.nostrBand.trendingNotes:
|
||||
return <TrendingNotesWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.tmp.xfeed:
|
||||
return <XfeedsWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.tmp.xhashtag:
|
||||
return <XhashtagWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.tmp.list:
|
||||
return <WidgetList key={widget.id} params={widget} />;
|
||||
case WidgetKinds.local.notification:
|
||||
return <NotificationWidget key={widget.id} />;
|
||||
case WidgetKinds.local.network:
|
||||
return <NewsfeedWidget key={widget.id} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (status === 'pending') {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<LoaderIcon className="h-5 w-5 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<VList
|
||||
className="h-full w-full flex-nowrap overflow-x-auto !overflow-y-hidden scrollbar-none focus:outline-none"
|
||||
horizontal
|
||||
ref={vlistRef}
|
||||
ref={ref}
|
||||
initialItemSize={420}
|
||||
aria-current="step"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (!ref.current) return;
|
||||
switch (e.code) {
|
||||
case 'ArrowLeft': {
|
||||
e.preventDefault();
|
||||
const prevIndex = Math.max(selectedIndex - 1, 0);
|
||||
setSelectedIndex(prevIndex);
|
||||
ref.current.scrollToIndex(prevIndex, {
|
||||
align: 'center',
|
||||
smooth: true,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'ArrowRight': {
|
||||
e.preventDefault();
|
||||
const nextIndex = Math.min(selectedIndex + 1, data.length - 1);
|
||||
setSelectedIndex(nextIndex);
|
||||
ref.current.scrollToIndex(nextIndex, {
|
||||
align: 'center',
|
||||
smooth: true,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{!widgets ? (
|
||||
<div className="flex h-full w-[420px] flex-col items-center justify-center">
|
||||
<LoaderIcon className="h-5 w-5 animate-spin text-neutral-900 dark:text-neutral-100" />
|
||||
</div>
|
||||
) : (
|
||||
widgets.map((widget) => renderItem(widget))
|
||||
)}
|
||||
{data.map((widget) => renderItem(widget))}
|
||||
<ToggleWidgetList />
|
||||
</VList>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user