wip: update widget list

This commit is contained in:
reya 2023-11-10 16:05:20 +07:00
parent 0cdf199cb5
commit 0710996a0d
15 changed files with 283 additions and 157 deletions

BIN
public/anime.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
public/art.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
public/gaming.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
public/movie.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
public/music.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
public/nsfw.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
public/photography.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/technology.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -6,9 +6,18 @@ import { useStorage } from '@libs/storage/provider';
import { LoaderIcon } from '@shared/icons'; import { LoaderIcon } from '@shared/icons';
import { import {
ArticleWidget,
FileWidget,
GroupWidget,
HashtagWidget,
NewsfeedWidget, NewsfeedWidget,
NotificationWidget, NotificationWidget,
ThreadWidget,
ToggleWidgetList, ToggleWidgetList,
TopicWidget,
TrendingAccountsWidget,
TrendingNotesWidget,
UserWidget,
WidgetList, WidgetList,
} from '@shared/widgets'; } from '@shared/widgets';
@ -54,6 +63,24 @@ export function HomeScreen() {
return <NotificationWidget key={widget.id} />; return <NotificationWidget key={widget.id} />;
case WIDGET_KIND.newsfeed: case WIDGET_KIND.newsfeed:
return <NewsfeedWidget key={widget.id} />; return <NewsfeedWidget key={widget.id} />;
case WIDGET_KIND.topic:
return <TopicWidget key={widget.id} widget={widget} />;
case WIDGET_KIND.user:
return <UserWidget key={widget.id} widget={widget} />;
case WIDGET_KIND.thread:
return <ThreadWidget key={widget.id} widget={widget} />;
case WIDGET_KIND.article:
return <ArticleWidget key={widget.id} widget={widget} />;
case WIDGET_KIND.file:
return <FileWidget key={widget.id} widget={widget} />;
case WIDGET_KIND.hashtag:
return <HashtagWidget key={widget.id} widget={widget} />;
case WIDGET_KIND.group:
return <GroupWidget key={widget.id} widget={widget} />;
case WIDGET_KIND.trendingNotes:
return <TrendingNotesWidget key={widget.id} widget={widget} />;
case WIDGET_KIND.trendingAccounts:
return <TrendingAccountsWidget key={widget.id} widget={widget} />;
case WIDGET_KIND.list: case WIDGET_KIND.list:
return <WidgetList key={widget.id} widget={widget} />; return <WidgetList key={widget.id} widget={widget} />;
default: default:

View File

@ -15,7 +15,7 @@ interface Response {
profiles: Array<{ pubkey: string }>; profiles: Array<{ pubkey: string }>;
} }
export function TrendingAccountsWidget({ params }: { params: Widget }) { export function TrendingAccountsWidget({ widget }: { widget: Widget }) {
const { status, data } = useQuery({ const { status, data } = useQuery({
queryKey: ['trending-profiles-widget'], queryKey: ['trending-profiles-widget'],
queryFn: async () => { queryFn: async () => {
@ -35,7 +35,7 @@ export function TrendingAccountsWidget({ params }: { params: Widget }) {
return ( return (
<WidgetWrapper> <WidgetWrapper>
<TitleBar id={params.id} title="Trending Accounts" /> <TitleBar id={widget.id} title="Trending Accounts" />
<div className="flex-1"> <div className="flex-1">
{status === 'pending' ? ( {status === 'pending' ? (
<div className="flex h-full w-full items-center justify-center "> <div className="flex h-full w-full items-center justify-center ">

View File

@ -16,7 +16,7 @@ export function ToggleWidgetList() {
onClick={() => onClick={() =>
addWidget.mutate({ kind: WIDGET_KIND.list, title: '', content: '' }) addWidget.mutate({ kind: WIDGET_KIND.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" className="inline-flex h-9 items-center gap-2 rounded-full bg-neutral-100 px-3 text-neutral-900 hover:bg-neutral-200 dark:bg-neutral-900 dark:text-neutral-100 dark:hover:bg-neutral-800"
> >
<PlusIcon className="h-4 w-4 text-neutral-900 dark:text-zinc-100" /> <PlusIcon className="h-4 w-4 text-neutral-900 dark:text-zinc-100" />
<p className="text-sm font-semibold leading-none">Add widget</p> <p className="text-sm font-semibold leading-none">Add widget</p>

View File

@ -1,27 +1,196 @@
import {
ArticleIcon,
BellIcon,
GroupFeedsIcon,
HashtagIcon,
MediaIcon,
PlusIcon,
TrendingIcon,
} from '@shared/icons';
import { TitleBar } from '@shared/titleBar'; import { TitleBar } from '@shared/titleBar';
import { WidgetWrapper } from '@shared/widgets'; import { WidgetWrapper } from '@shared/widgets';
import { TOPICS, WIDGET_KIND } from '@stores/constants';
import { useWidget } from '@utils/hooks/useWidget';
import { Widget } from '@utils/types'; import { Widget } from '@utils/types';
export function WidgetList({ widget }: { widget: Widget }) { export function WidgetList({ widget }: { widget: Widget }) {
const { replaceWidget } = useWidget();
return ( return (
<WidgetWrapper> <WidgetWrapper>
<TitleBar id={widget.id} title="Add widgets" /> <TitleBar id={widget.id} title="Add widgets" />
<div className="flex-1 overflow-y-auto pb-10 scrollbar-none"> <div className="flex-1 overflow-y-auto pb-10 scrollbar-none">
<div className="flex flex-col gap-6 px-3"> <div className="flex flex-col gap-6 px-3">
<div className="border-t border-neutral-200 pt-6 dark:border-neutral-800"> <div className="rounded-xl bg-neutral-100 p-3 dark:bg-neutral-900">
<button <h3 className="mb-2.5 text-sm font-semibold uppercase text-neutral-700 dark:text-neutral-300">
type="button" Topics
disabled </h3>
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" <div className="flex flex-col gap-3">
> {TOPICS.sort((a, b) => a.title.localeCompare(b.title)).map(
Build your own widget{' '} (topic, index) => (
<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"> <div
<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"> key={index}
Coming soon className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-white px-3 hover:shadow-md hover:shadow-neutral-200/50 dark:hover:shadow-neutral-800/50"
</span> >
<div className="inline-flex items-center gap-2.5">
<div className="h-9 w-9 shrink-0 rounded-md">
<img
src={`/${topic.title.toLowerCase()}.jpg`}
alt={topic.title}
className="h-9 w-9 rounded-md"
/>
</div>
<p className="font-medium">{topic.title}</p>
</div>
<button
type="button"
onClick={() =>
replaceWidget.mutate({
currentId: widget.id,
widget: {
kind: WIDGET_KIND.topic,
title: topic.title,
content: JSON.stringify(topic.content),
},
})
}
className="inline-flex h-6 items-center gap-1 rounded-md bg-neutral-100 pl-1.5 pr-2.5 text-sm font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-900"
>
<PlusIcon className="h-3 w-3" />
Add
</button>
</div>
)
)}
</div>
</div>
<div className="rounded-xl bg-neutral-100 p-3 dark:bg-neutral-900">
<h3 className="mb-2.5 text-sm font-semibold uppercase text-neutral-700 dark:text-neutral-300">
Newsfeed
</h3>
<div className="flex flex-col gap-3">
<div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-white px-3 hover:shadow-md hover:shadow-neutral-200/50 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">
<ArticleIcon className="h-4 w-4" />
</div>
<p className="font-medium">Article</p>
</div>
<button
type="button"
className="inline-flex h-6 items-center gap-1 rounded-md bg-neutral-100 pl-1.5 pr-2.5 text-sm font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-900"
>
<PlusIcon className="h-3 w-3" />
Add
</button>
</div> </div>
</button> <div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-white px-3 hover:shadow-md hover:shadow-neutral-200/50 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">
<MediaIcon className="h-4 w-4" />
</div>
<p className="font-medium">Media</p>
</div>
<button
type="button"
className="inline-flex h-6 items-center gap-1 rounded-md bg-neutral-100 pl-1.5 pr-2.5 text-sm font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-900"
>
<PlusIcon className="h-3 w-3" />
Add
</button>
</div>
<div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-white px-3 hover:shadow-md hover:shadow-neutral-200/50 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">
<GroupFeedsIcon className="h-4 w-4" />
</div>
<p className="font-medium">Group feeds</p>
</div>
<button
type="button"
className="inline-flex h-6 items-center gap-1 rounded-md bg-neutral-100 pl-1.5 pr-2.5 text-sm font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-900"
>
<PlusIcon className="h-3 w-3" />
Add
</button>
</div>
<div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-white px-3 hover:shadow-md hover:shadow-neutral-200/50 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">
<HashtagIcon className="h-4 w-4" />
</div>
<p className="font-medium">Hashtag</p>
</div>
<button
type="button"
className="inline-flex h-6 items-center gap-1 rounded-md bg-neutral-100 pl-1.5 pr-2.5 text-sm font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-900"
>
<PlusIcon className="h-3 w-3" />
Add
</button>
</div>
</div>
</div>
<div className="rounded-xl bg-neutral-100 p-3 dark:bg-neutral-900">
<h3 className="mb-2.5 text-sm font-semibold uppercase text-neutral-700 dark:text-neutral-300">
Nostr Band
</h3>
<div className="flex flex-col gap-3">
<div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-white px-3 hover:shadow-md hover:shadow-neutral-200/50 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">
<TrendingIcon className="h-4 w-4" />
</div>
<p className="font-medium">Trending posts</p>
</div>
<button
type="button"
className="inline-flex h-6 items-center gap-1 rounded-md bg-neutral-100 pl-1.5 pr-2.5 text-sm font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-900"
>
<PlusIcon className="h-3 w-3" />
Add
</button>
</div>
<div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-white px-3 hover:shadow-md hover:shadow-neutral-200/50 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">
<TrendingIcon className="h-4 w-4" />
</div>
<p className="font-medium">Trending users</p>
</div>
<button
type="button"
className="inline-flex h-6 items-center gap-1 rounded-md bg-neutral-100 pl-1.5 pr-2.5 text-sm font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-900"
>
<PlusIcon className="h-3 w-3" />
Add
</button>
</div>
</div>
</div>
<div className="rounded-xl bg-neutral-100 p-3 dark:bg-neutral-900">
<h3 className="mb-2.5 text-sm font-semibold uppercase text-neutral-700 dark:text-neutral-300">
Other
</h3>
<div className="flex flex-col gap-3">
<div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-white px-3 hover:shadow-md hover:shadow-neutral-200/50 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">
<BellIcon className="h-4 w-4" />
</div>
<p className="font-medium">Notification</p>
</div>
<button
type="button"
className="inline-flex h-6 items-center gap-1 rounded-md bg-neutral-100 pl-1.5 pr-2.5 text-sm font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-900"
>
<PlusIcon className="h-3 w-3" />
Add
</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,6 @@
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
import { useInfiniteQuery } from '@tanstack/react-query'; import { useInfiniteQuery } from '@tanstack/react-query';
import { FetchFilter } from 'nostr-fetch';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { VList } from 'virtua'; import { VList } from 'virtua';
@ -33,18 +34,18 @@ export function TopicWidget({ widget }: { widget: Widget }) {
pageParam: number; pageParam: number;
}) => { }) => {
const hashtags: string[] = JSON.parse(widget.content as string); const hashtags: string[] = JSON.parse(widget.content as string);
const filter: FetchFilter = {
kinds: [NDKKind.Text, NDKKind.Repost],
'#t': hashtags.map((tag) => tag.replace('#', '')),
};
const rootIds = new Set(); const rootIds = new Set();
const dedupQueue = new Set(); const dedupQueue = new Set();
const events = await fetcher.fetchLatestEvents( const events = await fetcher.fetchLatestEvents(relayUrls, filter, FETCH_LIMIT, {
relayUrls, asOf: pageParam === 0 ? undefined : pageParam,
{ abortSignal: signal,
kinds: [NDKKind.Text, NDKKind.Repost], });
'#t': hashtags,
},
FETCH_LIMIT,
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
);
const ndkEvents = events.map((event) => { const ndkEvents = events.map((event) => {
return new NDKEvent(ndk, event); return new NDKEvent(ndk, event);

View File

@ -1,8 +1,5 @@
import { WidgetGroup } from '@utils/types';
export const FULL_RELAYS = [ export const FULL_RELAYS = [
'wss://relay.damus.io', 'wss://relay.damus.io',
'wss://relay.primal.net',
'wss://relayable.org', 'wss://relayable.org',
'wss://relay.nostr.band/all', 'wss://relay.nostr.band/all',
'wss://nostr.mutinywallet.com', 'wss://nostr.mutinywallet.com',
@ -59,8 +56,6 @@ export const WIDGET_KIND = {
export const TOPICS = [ export const TOPICS = [
{ {
title: 'Gaming', title: 'Gaming',
description: '',
kind: WIDGET_KIND.global.topic,
content: [ content: [
'#gamestr', '#gamestr',
'#gaming', '#gaming',
@ -74,31 +69,23 @@ export const TOPICS = [
'#twitch', '#twitch',
'#fortnite', '#fortnite',
'#pc', '#pc',
'#memes',
'#pcgaming', '#pcgaming',
'#gamers', '#gamers',
'#gamingcommunity', '#gamingcommunity',
'#youtube',
'#switch', '#switch',
'#gamergirl', '#gamergirl',
'#nintendo', '#nintendo',
'#gta', '#gta',
'#callofduty', '#callofduty',
'#streamer',
'#follow',
'#pubg', '#pubg',
'#videogame', '#videogame',
'#esports', '#esports',
'#bhfyp',
'#meme',
'#twitchstreamer',
'#art',
'#genshinimpact', '#genshinimpact',
'#honkaiimpact', '#honkaiimpact',
'#warthunder', '#warthunder',
'#hovoverse', '#hoyoverse',
'#arknights', '#arknights',
'#soul', '#soullike',
'#eldenring', '#eldenring',
'#steam', '#steam',
'#pubg', '#pubg',
@ -108,12 +95,17 @@ export const TOPICS = [
'#starfield', '#starfield',
'#gta6', '#gta6',
'#gameoftheyear', '#gameoftheyear',
'#darksoul',
'#batterfield',
'#dota',
'#rpg',
'#thewitcher',
'#rogally',
'#rog',
], ],
}, },
{ {
title: 'Music', title: 'Music',
description: '',
kind: WIDGET_KIND.global.topic,
content: [ content: [
'#audiostr', '#audiostr',
'#musicstr', '#musicstr',
@ -125,18 +117,12 @@ export const TOPICS = [
'#musician', '#musician',
'#artist', '#artist',
'#musica', '#musica',
'#instagood',
'#singer', '#singer',
'#dj', '#dj',
'#follow',
'#rock', '#rock',
'#like',
'#dance', '#dance',
'#guitar', '#guitar',
'#s',
'#photography',
'#song', '#song',
'#bhfyp',
'#newmusic', '#newmusic',
'#producer', '#producer',
'#life', '#life',
@ -146,12 +132,14 @@ export const TOPICS = [
'#explorepage', '#explorepage',
'#viral', '#viral',
'#beats', '#beats',
'#dvd',
'#amass',
'#bluray',
'#Blu_Ray',
], ],
}, },
{ {
title: 'Photography', title: 'Photography',
description: '',
kind: WIDGET_KIND.global.topic,
content: [ content: [
'#photography', '#photography',
'#photooftheday', '#photooftheday',
@ -159,57 +147,38 @@ export const TOPICS = [
'#photo', '#photo',
'#nature', '#nature',
'#picoftheday', '#picoftheday',
'#like',
'#photographer', '#photographer',
'#beautiful', '#beautiful',
'#follow',
'#art',
'#fashion', '#fashion',
'#travel', '#travel',
'#bhfyp',
'#photoshoot', '#photoshoot',
'#likeforlikes',
'#instadaily',
'#naturephotography', '#naturephotography',
'#model', '#model',
'#me', '#me',
'#smile', '#smile',
'#style', '#style',
'#instalike',
'#happy', '#happy',
'#likes', '#likes',
'#myself', '#myself',
'#followme',
'#followforfollowback',
], ],
}, },
{ {
title: 'Art', title: 'Art',
description: '',
kind: WIDGET_KIND.global.topic,
content: [ content: [
'#nostrdesign',
'#artstr', '#artstr',
'#art', '#art',
'#artist', '#artist',
'#love',
'#drawing', '#drawing',
'#photography',
'#artwork', '#artwork',
'#instagood',
'#photooftheday',
'#painting', '#painting',
'#fashion', '#fashion',
'#like',
'#artistsoninstagram',
'#beautiful', '#beautiful',
'#illustration', '#illustration',
'#digitalart', '#digitalart',
'#follow',
'#design', '#design',
'#nature', '#nature',
'#picoftheday',
'#photo', '#photo',
'#bhfyp',
'#sketch', '#sketch',
'#style', '#style',
'#arte', '#arte',
@ -221,8 +190,6 @@ export const TOPICS = [
}, },
{ {
title: 'Movie', title: 'Movie',
description: '',
kind: WIDGET_KIND.global.topic,
content: [ content: [
'#filmstr', '#filmstr',
'#moviestr', '#moviestr',
@ -233,9 +200,6 @@ export const TOPICS = [
'#films', '#films',
'#hollywood', '#hollywood',
'#actor', '#actor',
'#love',
'#s',
'#art',
'#cinematography', '#cinematography',
'#actress', '#actress',
'#netflix', '#netflix',
@ -243,7 +207,6 @@ export const TOPICS = [
'#music', '#music',
'#filmmaking', '#filmmaking',
'#horror', '#horror',
'#instagood',
'#bollywood', '#bollywood',
'#movienight', '#movienight',
'#photography', '#photography',
@ -259,8 +222,6 @@ export const TOPICS = [
}, },
{ {
title: 'Technology', title: 'Technology',
description: '',
kind: WIDGET_KIND.global.topic,
content: [ content: [
'#apple', '#apple',
'#xiaomi', '#xiaomi',
@ -276,30 +237,27 @@ export const TOPICS = [
'#iphone', '#iphone',
'#technews', '#technews',
'#science', '#science',
'#design',
'#gadgets', '#gadgets',
'#electronics', '#electronics',
'#android', '#android',
'#software', '#software',
'#programming', '#programming',
'#smartphone', '#smartphone',
'#bhfyp',
'#samsung', '#samsung',
'#instagood',
'#coding', '#coding',
'#computer', '#computer',
'#pro',
'#education',
'#security', '#security',
'#gadget', '#gadget',
'#mobile', '#mobile',
'#technologynews', '#technologynews',
'#opensource',
'#tor',
'#bitcoin',
'#lightning',
], ],
}, },
{ {
title: 'Anime', title: 'Anime',
description: '',
kind: WIDGET_KIND.global.topic,
content: [ content: [
'#animestr', '#animestr',
'#anime', '#anime',
@ -317,78 +275,23 @@ export const TOPICS = [
'#aot', '#aot',
'#hentai', '#hentai',
'#fanart', '#fanart',
], '#loli',
}, '#vocaloid',
]; '#vtuber',
],
export const DEFAULT_WIDGETS: Array<WidgetGroup> = [ },
{ {
title: 'Topics', title: 'NSFW',
data: TOPICS, content: [
}, '#pornstr',
{ '#porn',
title: 'Local', '#nsfw',
data: [ '#bdsm',
{ '#lewd',
kind: WIDGET_KIND.tmp.xfeed, '#kink',
title: 'Group feeds', '#sexy',
description: 'All posts from specific people you want to keep up with', '#loli',
}, '#hentai',
{
kind: WIDGET_KIND.local.files,
title: 'Files',
description: 'All files shared by people you follow',
},
{
kind: WIDGET_KIND.local.articles,
title: 'Articles',
description: 'All articles shared by people you follow',
},
],
},
{
title: 'Global',
data: [
{
kind: WIDGET_KIND.tmp.xhashtag,
title: 'Hashtag',
description: 'All posts have a specific hashtag',
},
{
kind: WIDGET_KIND.global.files,
title: 'Files',
description: 'All files shared by people in your current relay set',
},
{
kind: WIDGET_KIND.global.articles,
title: 'Articles',
description: 'All articles shared by people in your current relay set',
},
],
},
{
title: 'nostr.band',
data: [
{
kind: WIDGET_KIND.nostrBand.trendingAccounts,
title: 'Accounts',
description: 'Trending accounts from the last 24 hours',
},
{
kind: WIDGET_KIND.nostrBand.trendingNotes,
title: 'Notes',
description: 'Trending notes from the last 24 hours',
},
],
},
{
title: 'Other',
data: [
{
kind: WIDGET_KIND.local.notification,
title: 'Notification',
description: 'Everything happens around you',
},
], ],
}, },
]; ];

View File

@ -17,6 +17,32 @@ export function useWidget() {
}, },
}); });
const replaceWidget = useMutation({
mutationFn: async ({ currentId, widget }: { currentId: string; widget: Widget }) => {
// Cancel any outgoing refetches
await queryClient.cancelQueries({ queryKey: ['widgets'] });
// Snapshot the previous value
const prevWidgets = queryClient.getQueryData(['widgets']);
// create new widget
await db.removeWidget(currentId);
const newWidget = await db.createWidget(widget.kind, widget.title, widget.content);
// Optimistically update to the new value
queryClient.setQueryData(['widgets'], (prev: Widget[]) => [
...prev.filter((t) => t.id !== currentId),
newWidget,
]);
// Return a context object with the snapshotted value
return { prevWidgets };
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['widgets'] });
},
});
const removeWidget = useMutation({ const removeWidget = useMutation({
mutationFn: async (id: string) => { mutationFn: async (id: string) => {
// Cancel any outgoing refetches // Cancel any outgoing refetches
@ -41,5 +67,5 @@ export function useWidget() {
}, },
}); });
return { addWidget, removeWidget }; return { addWidget, replaceWidget, removeWidget };
} }