diff --git a/apps/desktop2/src/app.tsx b/apps/desktop2/src/app.tsx index b5007e46..71abfd06 100644 --- a/apps/desktop2/src/app.tsx +++ b/apps/desktop2/src/app.tsx @@ -12,21 +12,13 @@ import { routeTree } from "./router.gen"; // auto generated file import { CancelCircleIcon, CheckCircleIcon, InfoCircleIcon } from "@lume/icons"; import { Ark } from "@lume/ark"; -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - gcTime: 1000 * 60 * 60 * 24, // 24 hours - staleTime: 1000 * 60 * 5, // 5 minutes - }, - }, -}); +const ark = new Ark(); +const queryClient = new QueryClient(); const persister = createSyncStoragePersister({ storage: window.localStorage, }); -const ark = new Ark(); - // Set up a Router instance const router = createRouter({ routeTree, diff --git a/apps/desktop2/src/components/col.tsx b/apps/desktop2/src/components/col.tsx index 45e585f9..feefa269 100644 --- a/apps/desktop2/src/components/col.tsx +++ b/apps/desktop2/src/components/col.tsx @@ -1,8 +1,6 @@ import { useEffect, useRef, useState } from "react"; import { LumeColumn } from "@lume/types"; import { invoke } from "@tauri-apps/api/core"; -import { Spinner } from "@lume/ui"; -import { cn } from "@lume/utils"; export function Col({ column, @@ -81,20 +79,5 @@ export function Col({ }; }, [webview]); - return ( -
- {column.label !== "open" ? ( -
- -
- ) : null} -
- ); + return
; } diff --git a/apps/desktop2/src/routes/__root.tsx b/apps/desktop2/src/routes/__root.tsx index 0ce9c48e..3935c98a 100644 --- a/apps/desktop2/src/routes/__root.tsx +++ b/apps/desktop2/src/routes/__root.tsx @@ -2,7 +2,13 @@ import { Outlet, createRootRouteWithContext } from "@tanstack/react-router"; import { type Ark } from "@lume/ark"; import { type QueryClient } from "@tanstack/react-query"; import { type Platform } from "@tauri-apps/plugin-os"; -import type { Account, Interests, Metadata, Settings } from "@lume/types"; +import type { + Account, + Contact, + Interests, + Metadata, + Settings, +} from "@lume/types"; import { Spinner } from "@lume/ui"; import { type Descendant } from "slate"; @@ -22,6 +28,7 @@ interface RouterContext { accounts?: Account[]; initialValue?: EditorElement[]; profile?: Metadata; + contacts?: Contact[]; } export const Route = createRootRouteWithContext()({ diff --git a/apps/desktop2/src/routes/editor/index.tsx b/apps/desktop2/src/routes/editor/index.tsx index 8b24edb4..ba7c7dac 100644 --- a/apps/desktop2/src/routes/editor/index.tsx +++ b/apps/desktop2/src/routes/editor/index.tsx @@ -1,4 +1,4 @@ -import { ComposeFilledIcon, NsfwIcon, TrashIcon } from "@lume/icons"; +import { ComposeFilledIcon, TrashIcon } from "@lume/icons"; import { Portal, cn, @@ -33,7 +33,6 @@ import { import { Contact } from "@lume/types"; import { Spinner, User } from "@lume/ui"; import { nip19 } from "nostr-tools"; -import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; import { invoke } from "@tauri-apps/api/core"; import { NsfwToggle } from "./-components/nsfw"; @@ -42,14 +41,6 @@ type EditorSearch = { quote: boolean; }; -const contactQueryOptions = queryOptions({ - queryKey: ["contacts"], - queryFn: () => invoke("get_contact_metadata"), - refetchOnMount: false, - refetchOnReconnect: false, - refetchOnWindowFocus: false, -}); - export const Route = createFileRoute("/editor/")({ validateSearch: (search: Record): EditorSearch => { return { @@ -58,7 +49,10 @@ export const Route = createFileRoute("/editor/")({ }; }, beforeLoad: async ({ search }) => { + const contacts: Contact[] = await invoke("get_contact_metadata"); + return { + contacts, initialValue: search.quote ? [ { @@ -83,16 +77,14 @@ export const Route = createFileRoute("/editor/")({ ], }; }, - loader: ({ context }) => { - context.queryClient.ensureQueryData(contactQueryOptions); - }, component: Screen, pendingComponent: Pending, }); function Screen() { + const ref = useRef(); const { reply_to, quote } = Route.useSearch(); - const { ark, initialValue } = Route.useRouteContext(); + const { ark, initialValue, contacts } = Route.useRouteContext(); const [t] = useTranslation(); const [editorValue, setEditorValue] = useState(initialValue); @@ -105,9 +97,6 @@ function Screen() { withMentions(withNostrEvent(withImages(withReact(createEditor())))), ); - const ref = useRef(); - const contacts = useSuspenseQuery(contactQueryOptions).data as Contact[]; - const filters = contacts ?.filter((c) => c?.profile.name?.toLowerCase().startsWith(search.toLowerCase()), diff --git a/apps/desktop2/src/routes/newsfeed.tsx b/apps/desktop2/src/routes/newsfeed.tsx index 37966eec..d2f23e75 100644 --- a/apps/desktop2/src/routes/newsfeed.tsx +++ b/apps/desktop2/src/routes/newsfeed.tsx @@ -27,21 +27,26 @@ export const Route = createFileRoute("/newsfeed")({ export function Screen() { const { label, name, account } = Route.useSearch(); const { ark } = Route.useRouteContext(); - const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = - useInfiniteQuery({ - queryKey: [label, account], - initialPageParam: 0, - queryFn: async ({ pageParam }: { pageParam: number }) => { - const events = await ark.get_events(20, pageParam); - return events; - }, - getNextPageParam: (lastPage) => { - const lastEvent = lastPage?.at(-1); - return lastEvent ? lastEvent.created_at - 1 : null; - }, - select: (data) => data?.pages.flatMap((page) => page), - refetchOnWindowFocus: false, - }); + const { + data, + isLoading, + isFetching, + isFetchingNextPage, + hasNextPage, + fetchNextPage, + } = useInfiniteQuery({ + queryKey: [label, account], + initialPageParam: 0, + queryFn: async ({ pageParam }: { pageParam: number }) => { + const events = await ark.get_events(20, pageParam); + return events; + }, + getNextPageParam: (lastPage) => { + const lastEvent = lastPage?.at(-1); + return lastEvent ? lastEvent.created_at - 1 : null; + }, + select: (data) => data?.pages.flatMap((page) => page), + }); const renderItem = (event: Event) => { if (!event) return; @@ -57,9 +62,18 @@ export function Screen() { + {isFetching && !isLoading && !isFetchingNextPage ? ( +
+
+ + Fetching new notes... +
+
+ ) : null} {isLoading ? ( -
+
+ Loading...
) : !data.length ? ( diff --git a/packages/ark/src/ark.ts b/packages/ark/src/ark.ts index e846e751..7da73882 100644 --- a/packages/ark/src/ark.ts +++ b/packages/ark/src/ark.ts @@ -122,15 +122,15 @@ export class Ark { } } - public async get_events_from(id: string, limit: number, asOf?: number) { + public async get_events_from(pubkey: string, limit: number, asOf?: number) { try { let until: string = undefined; if (asOf && asOf > 0) until = asOf.toString(); const nostrEvents: Event[] = await invoke("get_events_from", { - id, + public_key: pubkey, limit, - until, + as_of: until, }); return nostrEvents.sort((a, b) => b.created_at - a.created_at); diff --git a/packages/ui/src/column/content.tsx b/packages/ui/src/column/content.tsx index 4570043f..716d06d3 100644 --- a/packages/ui/src/column/content.tsx +++ b/packages/ui/src/column/content.tsx @@ -11,7 +11,7 @@ export function ColumnContent({ return (
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 4549ca48..ba57e7d0 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -54,6 +54,10 @@ fn main() { // Add some bootstrap relays // #TODO: Pull bootstrap relays from user's settings + client + .add_relay("wss://relay.damus.io") + .await + .expect("Cannot connect to relay.damus.io, please try again later."); client .add_relay("wss://relayable.org") .await diff --git a/src-tauri/src/nostr/event.rs b/src-tauri/src/nostr/event.rs index 88811478..14ca137e 100644 --- a/src-tauri/src/nostr/event.rs +++ b/src-tauri/src/nostr/event.rs @@ -40,34 +40,33 @@ pub async fn get_event(id: &str, state: State<'_, Nostr>) -> Result, + as_of: Option<&str>, state: State<'_, Nostr>, ) -> Result, String> { let client = &state.client; - let f_until = match until { - Some(until) => Timestamp::from_str(until).unwrap(), - None => Timestamp::now(), - }; - if let Ok(author) = PublicKey::from_str(id) { + if let Ok(author) = PublicKey::from_str(public_key) { + let until = match as_of { + Some(until) => Timestamp::from_str(until).unwrap(), + None => Timestamp::now(), + }; let filter = Filter::new() .kinds(vec![Kind::TextNote, Kind::Repost]) .authors(vec![author]) .limit(limit) - .until(f_until); + .until(until); + let _ = client + .reconcile(filter.clone(), NegentropyOptions::default()) + .await; - if let Ok(events) = client - .get_events_of(vec![filter], Some(Duration::from_secs(10))) - .await - { - Ok(events) - } else { - Err("Get text event failed".into()) + match client.database().query(vec![filter], Order::Asc).await { + Ok(events) => Ok(events), + Err(err) => Err(err.to_string()), } } else { - Err("Parse author failed".into()) + Err("Public Key is not valid, please check again.".into()) } } @@ -92,14 +91,12 @@ pub async fn get_events( .limit(limit) .until(as_of); - if let Ok(events) = client + match client .get_events_of(vec![filter], Some(Duration::from_secs(15))) .await { - println!("total global events: {}", events.len()); - Ok(events) - } else { - Err("Get events failed".into()) + Ok(events) => Ok(events), + Err(err) => Err(err.to_string()), } } false => { @@ -132,15 +129,13 @@ pub async fn get_events( .limit(limit) .authors(val) .until(as_of); + let _ = client + .reconcile(filter.clone(), NegentropyOptions::default()) + .await; - if let Ok(events) = client - .get_events_of(vec![filter], Some(Duration::from_secs(15))) - .await - { - println!("total local events: {}", events.len()); - Ok(events) - } else { - Err("Get events failed".into()) + match client.database().query(vec![filter], Order::Asc).await { + Ok(events) => Ok(events), + Err(err) => Err(err.to_string()), } } } @@ -167,31 +162,36 @@ pub async fn get_events_from_interests( .limit(limit) .until(as_of) .hashtags(hashtags); + let _ = client + .reconcile(filter.clone(), NegentropyOptions::default()) + .await; - if let Ok(events) = client - .get_events_of(vec![filter], Some(Duration::from_secs(15))) - .await - { - println!("total events: {}", events.len()); - Ok(events) - } else { - Err("Get text event failed".into()) + match client.database().query(vec![filter], Order::Asc).await { + Ok(events) => Ok(events), + Err(err) => Err(err.to_string()), } } #[tauri::command] -pub async fn get_event_thread(id: &str, state: State<'_, Nostr>) -> Result, ()> { +pub async fn get_event_thread(id: &str, state: State<'_, Nostr>) -> Result, String> { let client = &state.client; - let event_id = EventId::from_hex(id).unwrap(); - let filter = Filter::new().kinds(vec![Kind::TextNote]).event(event_id); - if let Ok(events) = client - .get_events_of(vec![filter], Some(Duration::from_secs(10))) - .await - { - Ok(events) - } else { - Err(()) + match EventId::from_hex(id) { + Ok(event_id) => { + let filter = Filter::new().kinds(vec![Kind::TextNote]).event(event_id); + let _ = client + .reconcile(filter.clone(), NegentropyOptions::default()) + .await; + + match client + .get_events_of(vec![filter], Some(Duration::from_secs(10))) + .await + { + Ok(events) => Ok(events), + Err(err) => Err(err.to_string()), + } + } + Err(_) => Err("Event ID is not valid".into()), } } diff --git a/src-tauri/src/nostr/keys.rs b/src-tauri/src/nostr/keys.rs index fbc1b82a..383cbf39 100644 --- a/src-tauri/src/nostr/keys.rs +++ b/src-tauri/src/nostr/keys.rs @@ -152,30 +152,29 @@ pub async fn load_selected_account(npub: &str, state: State<'_, Nostr>) -> Resul let client = &state.client; let keyring = Entry::new("Lume Secret Storage", npub).unwrap(); - if let Ok(password) = keyring.get_password() { - if password.starts_with("bunker://") { - let app_keys = Keys::generate(); - let bunker_uri = NostrConnectURI::parse(password).unwrap(); - let signer = Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(60), None) - .await - .unwrap(); + match keyring.get_password() { + Ok(password) => { + if password.starts_with("bunker://") { + let app_keys = Keys::generate(); + let bunker_uri = NostrConnectURI::parse(password).unwrap(); + let signer = Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(60), None) + .await + .unwrap(); - // Update signer - client.set_signer(Some(signer.into())).await; - // Done - Ok(true) - } else { - let secret_key = SecretKey::from_bech32(password).expect("Get secret key failed"); - let keys = Keys::new(secret_key); - let signer = NostrSigner::Keys(keys); + // Update signer + client.set_signer(Some(signer.into())).await; + } else { + let secret_key = SecretKey::from_bech32(password).expect("Get secret key failed"); + let keys = Keys::new(secret_key); + let signer = NostrSigner::Keys(keys); + + // Update signer + client.set_signer(Some(signer)).await; + } - // Update signer - client.set_signer(Some(signer)).await; - // Done Ok(true) } - } else { - Err("nsec not found".into()) + Err(err) => Err(err.to_string()), } }