diff --git a/public/anime.jpg b/public/anime.jpg new file mode 100644 index 00000000..f7f8eeab Binary files /dev/null and b/public/anime.jpg differ diff --git a/public/art.jpg b/public/art.jpg new file mode 100644 index 00000000..efc36ab3 Binary files /dev/null and b/public/art.jpg differ diff --git a/public/gaming.jpg b/public/gaming.jpg new file mode 100644 index 00000000..de58aeeb Binary files /dev/null and b/public/gaming.jpg differ diff --git a/public/movie.jpg b/public/movie.jpg new file mode 100644 index 00000000..bcb36809 Binary files /dev/null and b/public/movie.jpg differ diff --git a/public/music.jpg b/public/music.jpg new file mode 100644 index 00000000..1c533c15 Binary files /dev/null and b/public/music.jpg differ diff --git a/public/nsfw.jpg b/public/nsfw.jpg new file mode 100644 index 00000000..f9e183d2 Binary files /dev/null and b/public/nsfw.jpg differ diff --git a/public/photography.jpg b/public/photography.jpg new file mode 100644 index 00000000..6f117830 Binary files /dev/null and b/public/photography.jpg differ diff --git a/public/technology.jpg b/public/technology.jpg new file mode 100644 index 00000000..cfd733f0 Binary files /dev/null and b/public/technology.jpg differ diff --git a/src/app/home/index.tsx b/src/app/home/index.tsx index ce43131c..1de49ec7 100644 --- a/src/app/home/index.tsx +++ b/src/app/home/index.tsx @@ -6,9 +6,18 @@ import { useStorage } from '@libs/storage/provider'; import { LoaderIcon } from '@shared/icons'; import { + ArticleWidget, + FileWidget, + GroupWidget, + HashtagWidget, NewsfeedWidget, NotificationWidget, + ThreadWidget, ToggleWidgetList, + TopicWidget, + TrendingAccountsWidget, + TrendingNotesWidget, + UserWidget, WidgetList, } from '@shared/widgets'; @@ -54,6 +63,24 @@ export function HomeScreen() { return ; case WIDGET_KIND.newsfeed: return ; + case WIDGET_KIND.topic: + return ; + case WIDGET_KIND.user: + return ; + case WIDGET_KIND.thread: + return ; + case WIDGET_KIND.article: + return ; + case WIDGET_KIND.file: + return ; + case WIDGET_KIND.hashtag: + return ; + case WIDGET_KIND.group: + return ; + case WIDGET_KIND.trendingNotes: + return ; + case WIDGET_KIND.trendingAccounts: + return ; case WIDGET_KIND.list: return ; default: diff --git a/src/shared/widgets/nostrBand/trendingAccounts.tsx b/src/shared/widgets/nostrBand/trendingAccounts.tsx index ecb214af..f8ea6f68 100644 --- a/src/shared/widgets/nostrBand/trendingAccounts.tsx +++ b/src/shared/widgets/nostrBand/trendingAccounts.tsx @@ -15,7 +15,7 @@ interface Response { profiles: Array<{ pubkey: string }>; } -export function TrendingAccountsWidget({ params }: { params: Widget }) { +export function TrendingAccountsWidget({ widget }: { widget: Widget }) { const { status, data } = useQuery({ queryKey: ['trending-profiles-widget'], queryFn: async () => { @@ -35,7 +35,7 @@ export function TrendingAccountsWidget({ params }: { params: Widget }) { return ( - +
{status === 'pending' ? (
diff --git a/src/shared/widgets/other/toggleWidgetList.tsx b/src/shared/widgets/other/toggleWidgetList.tsx index c657b067..dca0863f 100644 --- a/src/shared/widgets/other/toggleWidgetList.tsx +++ b/src/shared/widgets/other/toggleWidgetList.tsx @@ -16,7 +16,7 @@ export function ToggleWidgetList() { onClick={() => 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" >

Add widget

diff --git a/src/shared/widgets/other/widgetList.tsx b/src/shared/widgets/other/widgetList.tsx index 875fe22c..8394791a 100644 --- a/src/shared/widgets/other/widgetList.tsx +++ b/src/shared/widgets/other/widgetList.tsx @@ -1,27 +1,196 @@ +import { + ArticleIcon, + BellIcon, + GroupFeedsIcon, + HashtagIcon, + MediaIcon, + PlusIcon, + TrendingIcon, +} from '@shared/icons'; import { TitleBar } from '@shared/titleBar'; import { WidgetWrapper } from '@shared/widgets'; +import { TOPICS, WIDGET_KIND } from '@stores/constants'; + +import { useWidget } from '@utils/hooks/useWidget'; import { Widget } from '@utils/types'; export function WidgetList({ widget }: { widget: Widget }) { + const { replaceWidget } = useWidget(); + return (
-
- +
+ ) + )} +
+
+
+

+ Newsfeed +

+
+
+
+
+ +
+

Article

+
+
- +
+
+
+ +
+

Media

+
+ +
+
+
+
+ +
+

Group feeds

+
+ +
+
+
+
+ +
+

Hashtag

+
+ +
+
+
+
+

+ Nostr Band +

+
+
+
+
+ +
+

Trending posts

+
+ +
+
+
+
+ +
+

Trending users

+
+ +
+
+
+
+

+ Other +

+
+
+
+
+ +
+

Notification

+
+ +
+
diff --git a/src/shared/widgets/topic.tsx b/src/shared/widgets/topic.tsx index 80fe3e92..18c030fd 100644 --- a/src/shared/widgets/topic.tsx +++ b/src/shared/widgets/topic.tsx @@ -1,5 +1,6 @@ import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; import { useInfiniteQuery } from '@tanstack/react-query'; +import { FetchFilter } from 'nostr-fetch'; import { useCallback, useMemo } from 'react'; import { VList } from 'virtua'; @@ -33,18 +34,18 @@ export function TopicWidget({ widget }: { widget: Widget }) { pageParam: number; }) => { 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 dedupQueue = new Set(); - const events = await fetcher.fetchLatestEvents( - relayUrls, - { - kinds: [NDKKind.Text, NDKKind.Repost], - '#t': hashtags, - }, - FETCH_LIMIT, - { asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal } - ); + const events = await fetcher.fetchLatestEvents(relayUrls, filter, FETCH_LIMIT, { + asOf: pageParam === 0 ? undefined : pageParam, + abortSignal: signal, + }); const ndkEvents = events.map((event) => { return new NDKEvent(ndk, event); diff --git a/src/stores/constants.ts b/src/stores/constants.ts index f3d48a2e..e666a95f 100644 --- a/src/stores/constants.ts +++ b/src/stores/constants.ts @@ -1,8 +1,5 @@ -import { WidgetGroup } from '@utils/types'; - export const FULL_RELAYS = [ 'wss://relay.damus.io', - 'wss://relay.primal.net', 'wss://relayable.org', 'wss://relay.nostr.band/all', 'wss://nostr.mutinywallet.com', @@ -59,8 +56,6 @@ export const WIDGET_KIND = { export const TOPICS = [ { title: 'Gaming', - description: '', - kind: WIDGET_KIND.global.topic, content: [ '#gamestr', '#gaming', @@ -74,31 +69,23 @@ export const TOPICS = [ '#twitch', '#fortnite', '#pc', - '#memes', '#pcgaming', '#gamers', '#gamingcommunity', - '#youtube', '#switch', '#gamergirl', '#nintendo', '#gta', '#callofduty', - '#streamer', - '#follow', '#pubg', '#videogame', '#esports', - '#bhfyp', - '#meme', - '#twitchstreamer', - '#art', '#genshinimpact', '#honkaiimpact', '#warthunder', - '#hovoverse', + '#hoyoverse', '#arknights', - '#soul', + '#soullike', '#eldenring', '#steam', '#pubg', @@ -108,12 +95,17 @@ export const TOPICS = [ '#starfield', '#gta6', '#gameoftheyear', + '#darksoul', + '#batterfield', + '#dota', + '#rpg', + '#thewitcher', + '#rogally', + '#rog', ], }, { title: 'Music', - description: '', - kind: WIDGET_KIND.global.topic, content: [ '#audiostr', '#musicstr', @@ -125,18 +117,12 @@ export const TOPICS = [ '#musician', '#artist', '#musica', - '#instagood', '#singer', '#dj', - '#follow', '#rock', - '#like', '#dance', '#guitar', - '#s', - '#photography', '#song', - '#bhfyp', '#newmusic', '#producer', '#life', @@ -146,12 +132,14 @@ export const TOPICS = [ '#explorepage', '#viral', '#beats', + '#dvd', + '#amass', + '#bluray', + '#Blu_Ray', ], }, { title: 'Photography', - description: '', - kind: WIDGET_KIND.global.topic, content: [ '#photography', '#photooftheday', @@ -159,57 +147,38 @@ export const TOPICS = [ '#photo', '#nature', '#picoftheday', - '#like', '#photographer', '#beautiful', - '#follow', - '#art', '#fashion', '#travel', - '#bhfyp', '#photoshoot', - '#likeforlikes', - '#instadaily', '#naturephotography', '#model', '#me', '#smile', '#style', - '#instalike', '#happy', '#likes', '#myself', - '#followme', - '#followforfollowback', ], }, { title: 'Art', - description: '', - kind: WIDGET_KIND.global.topic, content: [ + '#nostrdesign', '#artstr', '#art', '#artist', - '#love', '#drawing', - '#photography', '#artwork', - '#instagood', - '#photooftheday', '#painting', '#fashion', - '#like', - '#artistsoninstagram', '#beautiful', '#illustration', '#digitalart', - '#follow', '#design', '#nature', - '#picoftheday', '#photo', - '#bhfyp', '#sketch', '#style', '#arte', @@ -221,8 +190,6 @@ export const TOPICS = [ }, { title: 'Movie', - description: '', - kind: WIDGET_KIND.global.topic, content: [ '#filmstr', '#moviestr', @@ -233,9 +200,6 @@ export const TOPICS = [ '#films', '#hollywood', '#actor', - '#love', - '#s', - '#art', '#cinematography', '#actress', '#netflix', @@ -243,7 +207,6 @@ export const TOPICS = [ '#music', '#filmmaking', '#horror', - '#instagood', '#bollywood', '#movienight', '#photography', @@ -259,8 +222,6 @@ export const TOPICS = [ }, { title: 'Technology', - description: '', - kind: WIDGET_KIND.global.topic, content: [ '#apple', '#xiaomi', @@ -276,30 +237,27 @@ export const TOPICS = [ '#iphone', '#technews', '#science', - '#design', '#gadgets', '#electronics', '#android', '#software', '#programming', '#smartphone', - '#bhfyp', '#samsung', - '#instagood', '#coding', '#computer', - '#pro', - '#education', '#security', '#gadget', '#mobile', '#technologynews', + '#opensource', + '#tor', + '#bitcoin', + '#lightning', ], }, { title: 'Anime', - description: '', - kind: WIDGET_KIND.global.topic, content: [ '#animestr', '#anime', @@ -317,78 +275,23 @@ export const TOPICS = [ '#aot', '#hentai', '#fanart', - ], - }, -]; - -export const DEFAULT_WIDGETS: Array = [ - { - title: 'Topics', - data: TOPICS, - }, - { - title: 'Local', - data: [ - { - kind: WIDGET_KIND.tmp.xfeed, - title: 'Group feeds', - description: 'All posts from specific people you want to keep up with', - }, - { - 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', - }, + '#loli', + '#vocaloid', + '#vtuber', + ], + }, + { + title: 'NSFW', + content: [ + '#pornstr', + '#porn', + '#nsfw', + '#bdsm', + '#lewd', + '#kink', + '#sexy', + '#loli', + '#hentai', ], }, ]; diff --git a/src/utils/hooks/useWidget.ts b/src/utils/hooks/useWidget.ts index 7cf6017c..e8c902a8 100644 --- a/src/utils/hooks/useWidget.ts +++ b/src/utils/hooks/useWidget.ts @@ -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({ mutationFn: async (id: string) => { // Cancel any outgoing refetches @@ -41,5 +67,5 @@ export function useWidget() { }, }); - return { addWidget, removeWidget }; + return { addWidget, replaceWidget, removeWidget }; }