From e9ce93264652ed423866ba02304259a6b5ebb778 Mon Sep 17 00:00:00 2001 From: reya Date: Thu, 22 Feb 2024 08:58:45 +0700 Subject: [PATCH] chore: remove storage layer --- apps/desktop2/src/routes/app/home.lazy.tsx | 9 +- packages/ark/src/ark.ts | 35 ++- packages/ui/src/note/buttons/reaction.tsx | 17 +- packages/ui/src/note/buttons/zap.tsx | 262 +-------------------- packages/ui/src/note/content.tsx | 124 +++------- packages/ui/src/user/avatar.tsx | 91 +++---- packages/ui/src/user/followButton.tsx | 18 +- packages/ui/src/user/provider.tsx | 5 +- src-tauri/src/main.rs | 12 +- src-tauri/src/nostr/keys.rs | 2 - 10 files changed, 150 insertions(+), 425 deletions(-) diff --git a/apps/desktop2/src/routes/app/home.lazy.tsx b/apps/desktop2/src/routes/app/home.lazy.tsx index 051b8212..0dc351af 100644 --- a/apps/desktop2/src/routes/app/home.lazy.tsx +++ b/apps/desktop2/src/routes/app/home.lazy.tsx @@ -15,10 +15,15 @@ function Home() { const ark = useArk(); const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } = useInfiniteQuery({ - queryKey: ["timeline"], + queryKey: ["local_timeline"], initialPageParam: 0, queryFn: async ({ pageParam }: { pageParam: number }) => { - const events = await ark.get_text_events(FETCH_LIMIT, pageParam, true); + const events = await ark.get_events( + "local", + FETCH_LIMIT, + pageParam, + true, + ); return events; }, getNextPageParam: (lastPage) => { diff --git a/packages/ark/src/ark.ts b/packages/ark/src/ark.ts index 36155402..2ca11f2b 100644 --- a/packages/ark/src/ark.ts +++ b/packages/ark/src/ark.ts @@ -106,7 +106,8 @@ export class Ark { } } - public async get_text_events( + public async get_events( + type: "local" | "global", limit: number, asOf?: number, dedup?: boolean, @@ -118,7 +119,7 @@ export class Ark { const seenIds = new Set(); const dedupQueue = new Set(); - const nostrEvents: Event[] = await invoke("get_local_events", { + const nostrEvents: Event[] = await invoke(`get_${type}_events`, { limit, until, }); @@ -287,6 +288,36 @@ export class Ark { } } + public async get_contact_list() { + try { + const cmd: string[] = await invoke("get_contact_list"); + return cmd; + } catch (e) { + console.error(e); + return []; + } + } + + public async follow(id: string, alias?: string) { + try { + const cmd: string = await invoke("follow", { id, alias }); + return cmd; + } catch (e) { + console.error(e); + return false; + } + } + + public async unfollow(id: string) { + try { + const cmd: string = await invoke("unfollow", { id }); + return cmd; + } catch (e) { + console.error(e); + return false; + } + } + public async user_to_bech32(key: string, relays: string[]) { try { const cmd: string = await invoke("user_to_bech32", { diff --git a/packages/ui/src/note/buttons/reaction.tsx b/packages/ui/src/note/buttons/reaction.tsx index b7c2774d..ed0d9f02 100644 --- a/packages/ui/src/note/buttons/reaction.tsx +++ b/packages/ui/src/note/buttons/reaction.tsx @@ -9,15 +9,28 @@ export function NoteReaction() { const event = useNoteContext(); const [reaction, setReaction] = useState<"+" | "-">(null); + const [loading, setLoading] = useState(false); const up = async () => { + // start loading + setLoading(false); + const res = await ark.upvote(event.id, event.pubkey); if (res) setReaction("+"); + + // stop loading + setLoading(true); }; const down = async () => { + // start loading + setLoading(false); + const res = await ark.downvote(event.id, event.pubkey); if (res) setReaction("-"); + + // stop loading + setLoading(true); }; return ( @@ -25,7 +38,7 @@ export function NoteReaction() { - - - - {t("note.zap.tooltip")} - - - - - - ); - } - return ( - - - - - - - - - - - {t("note.zap.tooltip")} - - - - - - - - - -
-
- -
- Esc -
-
-
-
-
- - {t("note.zap.modalTitle")}{" "} - {user?.name || - user?.displayName || - displayNpub(event.pubkey, 16)} - -
- {!invoice ? ( -
-
-
- setAmount(value)} - className="w-full flex-1 border-none bg-transparent text-right text-4xl font-semibold placeholder:text-neutral-600 focus:outline-none focus:ring-0 dark:text-neutral-400" - /> - - sats - -
-
- - - - - -
-
-
- setZapMessage(e.target.value)} - spellCheck={false} - autoComplete="off" - autoCorrect="off" - autoCapitalize="off" - placeholder={t("note.zap.messagePlaceholder")} - className="w-full resize-none rounded-lg border-transparent bg-neutral-100 px-3 py-3 !outline-none placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-950 dark:text-neutral-400" - /> -
- -
-
-
- ) : ( -
-
- -
-
-

- {t("note.zap.invoiceButton")} -

- - {t("note.zap.invoiceFooter")} - -
-
- )} -
- - - + ); } diff --git a/packages/ui/src/note/content.tsx b/packages/ui/src/note/content.tsx index f68f8717..735c115f 100644 --- a/packages/ui/src/note/content.tsx +++ b/packages/ui/src/note/content.tsx @@ -1,4 +1,3 @@ -import { useStorage } from "@lume/storage"; import { Kind } from "@lume/types"; import { AUDIOS, @@ -8,14 +7,11 @@ import { VIDEOS, canPreview, cn, - regionNames, } from "@lume/utils"; -import { fetch } from "@tauri-apps/plugin-http"; import getUrls from "get-urls"; import { nanoid } from "nanoid"; -import { ReactNode, useMemo, useState } from "react"; +import { ReactNode, useMemo } from "react"; import reactStringReplace from "react-string-replace"; -import { toast } from "sonner"; import { stripHtml } from "string-strip-html"; import { Hashtag } from "./mentions/hashtag"; import { MentionNote } from "./mentions/note"; @@ -27,19 +23,12 @@ import { VideoPreview } from "./preview/video"; import { useNoteContext } from "./provider"; export function NoteContent({ className }: { className?: string }) { - const storage = useStorage(); const event = useNoteContext(); - const [content, setContent] = useState(event.content); - const [translate, setTranslate] = useState({ - translatable: false, - translated: false, - }); - const richContent = useMemo(() => { - if (event.kind !== Kind.Text) return content; + if (event.kind !== Kind.Text) return event.content; - let parsedContent: string | ReactNode[] = stripHtml(content).result; + let parsedContent: string | ReactNode[] = stripHtml(event.content).result; let linkPreview: string = undefined; let images: string[] = []; let videos: string[] = []; @@ -50,32 +39,32 @@ export function NoteContent({ className }: { className?: string }) { const words = text.split(/( |\n)/); const urls = [...getUrls(text)]; - if (storage.settings.media && !storage.settings.lowPower) { - images = urls.filter((word) => - IMAGES.some((el) => { - const url = new URL(word); - const extension = url.pathname.split(".")[1]; - if (extension === el) return true; - return false; - }), - ); - videos = urls.filter((word) => - VIDEOS.some((el) => { - const url = new URL(word); - const extension = url.pathname.split(".")[1]; - if (extension === el) return true; - return false; - }), - ); - audios = urls.filter((word) => - AUDIOS.some((el) => { - const url = new URL(word); - const extension = url.pathname.split(".")[1]; - if (extension === el) return true; - return false; - }), - ); - } + images = urls.filter((word) => + IMAGES.some((el) => { + const url = new URL(word); + const extension = url.pathname.split(".")[1]; + if (extension === el) return true; + return false; + }), + ); + + videos = urls.filter((word) => + VIDEOS.some((el) => { + const url = new URL(word); + const extension = url.pathname.split(".")[1]; + if (extension === el) return true; + return false; + }), + ); + + audios = urls.filter((word) => + AUDIOS.some((el) => { + const url = new URL(word); + const extension = url.pathname.split(".")[1]; + if (extension === el) return true; + return false; + }), + ); events = words.filter((word) => NOSTR_EVENTS.some((el) => word.startsWith(el)), @@ -121,9 +110,7 @@ export function NoteContent({ className }: { className?: string }) { for (const hashtag of hashtags) { const regex = new RegExp(`(|^)${hashtag}\\b`, "g"); parsedContent = reactStringReplace(parsedContent, regex, () => { - if (storage.settings.hashtag) - return ; - return null; + return ; }); } } @@ -174,7 +161,7 @@ export function NoteContent({ className }: { className?: string }) { ); parsedContent = reactStringReplace(parsedContent, "\n", () => { - return
; + return
; }); if (typeof parsedContent[0] === "string") { @@ -186,35 +173,7 @@ export function NoteContent({ className }: { className?: string }) { console.warn(event.id, `[parser] parse failed: ${e}`); return parsedContent; } - }, [content]); - - const translateContent = async () => { - try { - if (!translate.translatable) return; - - const res = await fetch("https://translate.nostr.wine/translate", { - method: "POST", - body: JSON.stringify({ - q: event.content, - target: storage.locale.slice(0, 2), - api_key: storage.settings.translateApiKey, - }), - headers: { "Content-Type": "application/json" }, - }); - - if (!res.ok) - toast.error( - "Cannot connect to translate service, please try again later.", - ); - - const data = await res.json(); - - setContent(data.translatedText); - setTranslate((state) => ({ ...state, translated: true })); - } catch (e) { - console.error("translate api: ", String(e)); - } - }; + }, []); if (event.kind !== Kind.Text) { return ; @@ -225,25 +184,6 @@ export function NoteContent({ className }: { className?: string }) {
{richContent}
- {storage.settings.translation && translate.translatable ? ( - translate.translated ? ( - - ) : ( - - ) - ) : null}
); } diff --git a/packages/ui/src/user/avatar.tsx b/packages/ui/src/user/avatar.tsx index f7d5d8c3..f5e0db12 100644 --- a/packages/ui/src/user/avatar.tsx +++ b/packages/ui/src/user/avatar.tsx @@ -1,4 +1,3 @@ -import { useStorage } from "@lume/storage"; import { cn } from "@lume/utils"; import * as Avatar from "@radix-ui/react-avatar"; import { minidenticon } from "minidenticons"; @@ -7,59 +6,45 @@ import { useMemo } from "react"; import { useUserContext } from "./provider"; export function UserAvatar({ className }: { className?: string }) { - const user = useUserContext(); - const storage = useStorage(); + const user = useUserContext(); - const fallbackAvatar = useMemo( - () => - `data:image/svg+xml;utf8,${encodeURIComponent( - minidenticon(user?.pubkey || nanoid(), 90, 50), - )}`, - [user], - ); + const fallbackAvatar = useMemo( + () => + `data:image/svg+xml;utf8,${encodeURIComponent( + minidenticon(user?.pubkey || nanoid(), 90, 50), + )}`, + [user], + ); - if (!user.profile) { - return ( -
-
-
- ); - } + if (!user.profile) { + return ( +
+
+
+ ); + } - return ( - - {storage.settings.lowPower ? ( - - ) : ( - - )} - - {user.pubkey} - - - ); + return ( + + + + {user.pubkey} + + + ); } diff --git a/packages/ui/src/user/followButton.tsx b/packages/ui/src/user/followButton.tsx index e6a89557..f634ca7f 100644 --- a/packages/ui/src/user/followButton.tsx +++ b/packages/ui/src/user/followButton.tsx @@ -3,15 +3,11 @@ import { LoaderIcon } from "@lume/icons"; import { cn } from "@lume/utils"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; +import { useUserContext } from "./provider"; -export function UserFollowButton({ - target, - className, -}: { - target: string; - className?: string; -}) { +export function UserFollowButton({ className }: { className?: string }) { const ark = useArk(); + const user = useUserContext(); const [t] = useTranslation(); const [loading, setLoading] = useState(false); @@ -20,10 +16,10 @@ export function UserFollowButton({ const toggleFollow = async () => { setLoading(true); if (!followed) { - const add = await ark.createContact(target); + const add = await ark.follow(user.pubkey); if (add) setFollowed(true); } else { - const remove = await ark.deleteContact(target); + const remove = await ark.unfollow(user.pubkey); if (remove) setFollowed(false); } setLoading(false); @@ -33,8 +29,8 @@ export function UserFollowButton({ async function status() { setLoading(true); - const contacts = await ark.getUserContacts(); - if (contacts?.includes(target)) { + const contacts = await ark.get_contact_list(); + if (contacts?.includes(user.pubkey)) { setFollowed(true); } diff --git a/packages/ui/src/user/provider.tsx b/packages/ui/src/user/provider.tsx index 205ab5f9..6f0fa93d 100644 --- a/packages/ui/src/user/provider.tsx +++ b/packages/ui/src/user/provider.tsx @@ -1,6 +1,6 @@ +import { useArk } from "@lume/ark"; import { Metadata } from "@lume/types"; import { useQuery } from "@tanstack/react-query"; -import { invoke } from "@tauri-apps/api/core"; import { ReactNode, createContext, useContext } from "react"; const UserContext = createContext<{ pubkey: string; profile: Metadata }>(null); @@ -14,12 +14,13 @@ export function UserProvider({ children: ReactNode; embed?: string; }) { + const ark = useArk(); const { data: profile } = useQuery({ queryKey: ["user", pubkey], queryFn: async () => { if (embed) return JSON.parse(embed) as Metadata; try { - const profile: Metadata = await invoke("get_profile", { id: pubkey }); + const profile: Metadata = await ark.get_profile(pubkey); return profile; } catch (e) { throw new Error(e); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index df7c3051..0077be31 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -47,15 +47,19 @@ fn main() { client .add_relay("wss://nostr.mutinywallet.com") .await - .expect("Failed to add bootstrap relay."); + .unwrap_or_default(); client - .add_relay("wss://bostr.yonle.lecturify.net") + .add_relay("wss://relay.nostr.band") .await - .expect("Failed to add bootstrap relay."); + .unwrap_or_default(); + client + .add_relay("wss://relay.damus.io") + .await + .unwrap_or_default(); client .add_relay("wss://purplepag.es") .await - .expect("Failed to add bootstrap relay."); + .unwrap_or_default(); // Connect client.connect().await; diff --git a/src-tauri/src/nostr/keys.rs b/src-tauri/src/nostr/keys.rs index 441ea446..d0f7baa1 100644 --- a/src-tauri/src/nostr/keys.rs +++ b/src-tauri/src/nostr/keys.rs @@ -48,7 +48,6 @@ pub async fn save_key( .get_contact_list_public_keys(Some(Duration::from_secs(10))) .await { - println!("total contacts: {}", list.len()); *contact_list = Some(list); } @@ -161,7 +160,6 @@ pub async fn load_selected_account( .get_contact_list_public_keys(Some(Duration::from_secs(10))) .await { - println!("total contacts: {}", list.len()); *contact_list = Some(list); }