wip: rework widget

This commit is contained in:
2023-11-09 18:02:25 +07:00
parent cb9006abb2
commit 0cdf199cb5
31 changed files with 155 additions and 509 deletions

View File

@@ -2,16 +2,12 @@ import { message } from '@tauri-apps/plugin-dialog';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useStorage } from '@libs/storage/provider';
import { ArrowLeftIcon, CheckCircleIcon, LoaderIcon } from '@shared/icons';
import { HASHTAGS, WIDGET_KIND } from '@stores/constants';
import { HASHTAGS } from '@stores/constants';
import { useOnboarding } from '@stores/onboarding';
export function OnboardHashtagScreen() {
const { db } = useStorage();
const [loading, setLoading] = useState(false);
const [tags, setTags] = useState(new Set<string>());
@@ -34,10 +30,6 @@ export function OnboardHashtagScreen() {
try {
setLoading(true);
for (const tag of tags) {
await db.createWidget(WIDGET_KIND.global.hashtag, tag, tag.replace('#', ''));
}
setHashtag();
navigate(-1);
} catch (e) {

View File

@@ -2,34 +2,21 @@ import { useQuery } from '@tanstack/react-query';
import { useCallback, useRef, useState } from 'react';
import { VList, VListHandle } from 'virtua';
import { ToggleWidgetList } from '@app/space/components/toggle';
import { WidgetList } from '@app/space/components/widgetList';
import { useStorage } from '@libs/storage/provider';
import { LoaderIcon } from '@shared/icons';
import {
GlobalArticlesWidget,
GlobalFilesWidget,
GlobalHashtagWidget,
LocalArticlesWidget,
LocalFeedsWidget,
LocalFilesWidget,
LocalThreadWidget,
LocalUserWidget,
NewsfeedWidget,
NotificationWidget,
TrendingAccountsWidget,
TrendingNotesWidget,
XfeedsWidget,
XhashtagWidget,
ToggleWidgetList,
WidgetList,
} from '@shared/widgets';
import { WIDGET_KIND } from '@stores/constants';
import { Widget } from '@utils/types';
export function SpaceScreen() {
export function HomeScreen() {
const ref = useRef<VListHandle>(null);
const [selectedIndex, setSelectedIndex] = useState(-1);
@@ -40,16 +27,16 @@ export function SpaceScreen() {
const dbWidgets = await db.getWidgets();
const defaultWidgets = [
{
id: '9998',
title: 'Notification',
content: '',
kind: WIDGET_KIND.local.notification,
},
{
id: '9999',
id: '99999',
title: 'Newsfeed',
content: '',
kind: WIDGET_KIND.local.network,
kind: WIDGET_KIND.newsfeed,
},
{
id: '99998',
title: 'Notification',
content: '',
kind: WIDGET_KIND.notification,
},
];
@@ -63,36 +50,12 @@ export function SpaceScreen() {
const renderItem = useCallback((widget: Widget) => {
switch (widget.kind) {
case WIDGET_KIND.local.feeds:
return <LocalFeedsWidget key={widget.id} params={widget} />;
case WIDGET_KIND.local.files:
return <LocalFilesWidget key={widget.id} params={widget} />;
case WIDGET_KIND.local.articles:
return <LocalArticlesWidget key={widget.id} params={widget} />;
case WIDGET_KIND.local.user:
return <LocalUserWidget key={widget.id} params={widget} />;
case WIDGET_KIND.local.thread:
return <LocalThreadWidget key={widget.id} params={widget} />;
case WIDGET_KIND.global.hashtag:
return <GlobalHashtagWidget key={widget.id} params={widget} />;
case WIDGET_KIND.global.articles:
return <GlobalArticlesWidget key={widget.id} params={widget} />;
case WIDGET_KIND.global.files:
return <GlobalFilesWidget key={widget.id} params={widget} />;
case WIDGET_KIND.nostrBand.trendingAccounts:
return <TrendingAccountsWidget key={widget.id} params={widget} />;
case WIDGET_KIND.nostrBand.trendingNotes:
return <TrendingNotesWidget key={widget.id} params={widget} />;
case WIDGET_KIND.tmp.xfeed:
return <XfeedsWidget key={widget.id} params={widget} />;
case WIDGET_KIND.tmp.xhashtag:
return <XhashtagWidget key={widget.id} params={widget} />;
case WIDGET_KIND.tmp.list:
return <WidgetList key={widget.id} params={widget} />;
case WIDGET_KIND.local.notification:
case WIDGET_KIND.notification:
return <NotificationWidget key={widget.id} />;
case WIDGET_KIND.local.network:
case WIDGET_KIND.newsfeed:
return <NewsfeedWidget key={widget.id} />;
case WIDGET_KIND.list:
return <WidgetList key={widget.id} widget={widget} />;
default:
return null;
}
@@ -108,14 +71,15 @@ export function SpaceScreen() {
return (
<VList
className="h-full w-full flex-nowrap overflow-x-auto !overflow-y-hidden scrollbar-none focus:outline-none"
horizontal
ref={ref}
className="h-full w-full flex-nowrap overflow-x-auto !overflow-y-hidden scrollbar-none focus:outline-none"
initialItemSize={420}
tabIndex={0}
horizontal
onKeyDown={(e) => {
if (!ref.current) return;
switch (e.code) {
case 'ArrowUp':
case 'ArrowLeft': {
e.preventDefault();
const prevIndex = Math.max(selectedIndex - 1, 0);
@@ -126,6 +90,7 @@ export function SpaceScreen() {
});
break;
}
case 'ArrowDown':
case 'ArrowRight': {
e.preventDefault();
const nextIndex = Math.min(selectedIndex + 1, data.length - 1);
@@ -136,6 +101,8 @@ export function SpaceScreen() {
});
break;
}
default:
break;
}
}}
>

View File

@@ -1,27 +0,0 @@
import { PlusIcon } from '@shared/icons';
import { WidgetWrapper } from '@shared/widgets';
import { WIDGET_KIND } from '@stores/constants';
import { useWidget } from '@utils/hooks/useWidget';
export function ToggleWidgetList() {
const { addWidget } = useWidget();
return (
<WidgetWrapper>
<div className="relative flex h-full w-full flex-col items-center justify-center">
<button
type="button"
onClick={() =>
addWidget.mutate({ kind: WIDGET_KIND.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"
>
<PlusIcon className="h-4 w-4 text-neutral-900 dark:text-zinc-100" />
<p className="text-sm font-semibold leading-none">Add widget</p>
</button>
</div>
</WidgetWrapper>
);
}

View File

@@ -1,128 +0,0 @@
import { useCallback } from 'react';
import {
ArticleIcon,
BellIcon,
FileIcon,
FollowsIcon,
GroupFeedsIcon,
HashtagIcon,
ThreadsIcon,
TrendingIcon,
} from '@shared/icons';
import { TitleBar } from '@shared/titleBar';
import { WidgetWrapper } from '@shared/widgets';
import { DEFAULT_WIDGETS, WIDGET_KIND } from '@stores/constants';
import { useWidget } from '@utils/hooks/useWidget';
import { Widget, WidgetGroup, WidgetGroupItem } from '@utils/types';
export function WidgetList({ params }: { params: Widget }) {
const { addWidget, removeWidget } = useWidget();
const open = (item: WidgetGroupItem) => {
addWidget.mutate({
kind: item.kind,
title: item.title,
content: JSON.stringify(item.content),
});
removeWidget.mutate(params.id);
};
const renderIcon = useCallback((kind: number) => {
switch (kind) {
case WIDGET_KIND.tmp.xfeed:
return (
<GroupFeedsIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />
);
case WIDGET_KIND.local.follows:
return <FollowsIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
case WIDGET_KIND.local.files:
case WIDGET_KIND.global.files:
return <FileIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
case WIDGET_KIND.local.articles:
case WIDGET_KIND.global.articles:
return <ArticleIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
case WIDGET_KIND.tmp.xhashtag:
return <HashtagIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
case WIDGET_KIND.nostrBand.trendingAccounts:
case WIDGET_KIND.nostrBand.trendingNotes:
return (
<TrendingIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />
);
case WIDGET_KIND.local.notification:
return <BellIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
case WIDGET_KIND.other.learnNostr:
return <ThreadsIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
default:
return null;
}
}, []);
const renderItem = useCallback((row: WidgetGroup, index: number) => {
return (
<div key={index} className="flex flex-col gap-2">
<h3 className="font-semibold">{row.title}</h3>
<div className="flex flex-col divide-y divide-neutral-200 overflow-hidden rounded-xl bg-neutral-100 dark:divide-neutral-800 dark:bg-neutral-900">
{row.data.map((item, index) => (
<button
onClick={() => open(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-lg">
<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-lg 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-600 dark:text-neutral-400">
{item.description}
</p>
</div>
</button>
))}
</div>
</div>
);
}, []);
return (
<WidgetWrapper>
<TitleBar id={params.id} title="Add widget" />
<div className="flex-1 overflow-y-auto pb-10 scrollbar-none">
<div className="flex flex-col gap-6 px-3">
{DEFAULT_WIDGETS.map((row: WidgetGroup, index: number) =>
renderItem(row, index)
)}
<div className="border-t border-neutral-200 pt-6 dark:border-neutral-800">
<button
type="button"
disabled
className="inline-flex h-14 w-full items-center justify-center gap-2.5 rounded-xl bg-neutral-50 text-sm font-medium text-neutral-900 dark:bg-neutral-950 dark:text-neutral-100"
>
Build your own widget{' '}
<div className="-rotate-3 transform-gpu rounded-md border border-neutral-200 bg-neutral-100 px-1.5 py-1 dark:border-neutral-800 dark:bg-neutral-900">
<span className="bg-gradient-to-r from-blue-400 via-red-400 to-orange-500 bg-clip-text text-xs text-transparent dark:from-blue-200 dark:via-red-200 dark:to-orange-300">
Coming soon
</span>
</div>
</button>
</div>
</div>
</div>
</WidgetWrapper>
);
}