From 18e1ac0e6ca5da8fce7be0bdf8f1a161dda7ce44 Mon Sep 17 00:00:00 2001 From: reya Date: Fri, 1 Nov 2024 09:25:12 +0700 Subject: [PATCH] feat: add relay feeds --- src-tauri/src/commands/event.rs | 38 +++++ src-tauri/src/commands/metadata.rs | 44 +---- src-tauri/src/commands/relay.rs | 61 +++++-- src-tauri/src/main.rs | 9 +- src/commands.gen.ts | 29 ++-- src/components/column.tsx | 2 +- src/components/repost.tsx | 2 + src/routes.gen.ts | 67 ++++++++ src/routes/bootstrap-relays.lazy.tsx | 2 +- .../columns/_layout/discover-relays.lazy.tsx | 154 ++++++++++++++++++ .../columns/_layout/groups.$id.lazy.tsx | 2 +- .../columns/_layout/interests.$id.lazy.tsx | 2 +- .../columns/_layout/launchpad.$id.lazy.tsx | 20 ++- .../columns/_layout/newsfeed.$id.lazy.tsx | 17 +- .../columns/_layout/relays.$url.lazy.tsx | 144 ++++++++++++++++ 15 files changed, 500 insertions(+), 93 deletions(-) create mode 100644 src/routes/columns/_layout/discover-relays.lazy.tsx create mode 100644 src/routes/columns/_layout/relays.$url.lazy.tsx diff --git a/src-tauri/src/commands/event.rs b/src-tauri/src/commands/event.rs index 8f35bd0b..15ae4b56 100644 --- a/src-tauri/src/commands/event.rs +++ b/src-tauri/src/commands/event.rs @@ -201,6 +201,44 @@ pub async fn get_all_events_by_hashtags( Ok(alt_events) } +#[tauri::command] +#[specta::specta] +pub async fn get_all_events_from( + url: String, + until: Option, + state: State<'_, Nostr>, +) -> Result, String> { + let client = &state.client; + + let _ = client.add_read_relay(&url).await; + let _ = client.connect_relay(&url).await; + + let as_of = match until { + Some(until) => Timestamp::from_str(&until).map_err(|err| err.to_string())?, + None => Timestamp::now(), + }; + + let filter = Filter::new() + .kinds(vec![Kind::TextNote, Kind::Repost]) + .limit(FETCH_LIMIT) + .until(as_of); + + let mut events = Events::new(&[filter.clone()]); + + let mut rx = client + .stream_events_from(vec![url], vec![filter], Some(Duration::from_secs(3))) + .await + .map_err(|e| e.to_string())?; + + while let Some(event) = rx.next().await { + events.insert(event); + } + + let alt_events = process_event(client, events, false).await; + + Ok(alt_events) +} + #[tauri::command] #[specta::specta] pub async fn get_local_events( diff --git a/src-tauri/src/commands/metadata.rs b/src-tauri/src/commands/metadata.rs index 202c6577..1b1e33e4 100644 --- a/src-tauri/src/commands/metadata.rs +++ b/src-tauri/src/commands/metadata.rs @@ -3,7 +3,7 @@ use nostr_sdk::prelude::*; use serde::{Deserialize, Serialize}; use specta::Type; use std::{str::FromStr, time::Duration}; -use tauri::{Emitter, Manager, State}; +use tauri::State; use crate::{common::process_event, Nostr, RichEvent, FETCH_LIMIT}; @@ -147,7 +147,6 @@ pub async fn set_group( image: Option, users: Vec, state: State<'_, Nostr>, - handle: tauri::AppHandle, ) -> Result { let client = &state.client; let public_keys: Vec = users @@ -181,25 +180,7 @@ pub async fn set_group( .map_err(|err| err.to_string())?; match client.send_event(event).await { - Ok(output) => { - // Sync event - tauri::async_runtime::spawn(async move { - let state = handle.state::(); - let client = &state.client; - - let filter = Filter::new() - .kinds(vec![Kind::TextNote, Kind::Repost]) - .authors(public_keys) - .limit(500); - - if let Ok(report) = client.sync(filter, &SyncOptions::default()).await { - println!("Received: {}", report.received.len()); - handle.emit("synchronized", ()).unwrap(); - }; - }); - - Ok(output.to_hex()) - } + Ok(output) => Ok(output.to_hex()), Err(err) => Err(err.to_string()), } } @@ -296,7 +277,6 @@ pub async fn set_interest( image: Option, hashtags: Vec, state: State<'_, Nostr>, - handle: tauri::AppHandle, ) -> Result { let client = &state.client; let label = title.to_lowercase().replace(" ", "-"); @@ -320,25 +300,7 @@ pub async fn set_interest( .map_err(|err| err.to_string())?; match client.send_event(event).await { - Ok(output) => { - // Sync event - tauri::async_runtime::spawn(async move { - let state = handle.state::(); - let client = &state.client; - - let filter = Filter::new() - .kinds(vec![Kind::TextNote, Kind::Repost]) - .hashtags(hashtags) - .limit(500); - - if let Ok(report) = client.sync(filter, &SyncOptions::default()).await { - println!("Received: {}", report.received.len()); - handle.emit("synchronized", ()).unwrap(); - }; - }); - - Ok(output.to_hex()) - } + Ok(output) => Ok(output.to_hex()), Err(err) => Err(err.to_string()), } } diff --git a/src-tauri/src/commands/relay.rs b/src-tauri/src/commands/relay.rs index eb86b83f..d7cc8528 100644 --- a/src-tauri/src/commands/relay.rs +++ b/src-tauri/src/commands/relay.rs @@ -1,4 +1,3 @@ -use crate::Nostr; use nostr_sdk::prelude::*; use serde::Serialize; use specta::Type; @@ -9,6 +8,8 @@ use std::{ }; use tauri::{path::BaseDirectory, Manager, State}; +use crate::{Nostr, FETCH_LIMIT}; + #[derive(Serialize, Type)] pub struct Relays { connected: Vec, @@ -94,31 +95,59 @@ pub async fn get_relays(id: String, state: State<'_, Nostr>) -> Result) -> Result { +pub async fn get_all_relays( + until: Option, + state: State<'_, Nostr>, +) -> Result, String> { let client = &state.client; - let status = client.add_relay(relay).await.map_err(|e| e.to_string())?; - if status { - client - .connect_relay(relay) - .await - .map_err(|e| e.to_string())?; - } + let as_of = match until { + Some(until) => Timestamp::from_str(&until).unwrap_or(Timestamp::now()), + None => Timestamp::now(), + }; + + let filter = Filter::new() + .kind(Kind::RelayList) + .limit(FETCH_LIMIT) + .until(as_of); + + let events = client + .database() + .query(vec![filter]) + .await + .map_err(|e| e.to_string())?; + + let alt_events: Vec = events.iter().map(|ev| ev.as_json()).collect(); + + Ok(alt_events) +} + +#[tauri::command] +#[specta::specta] +pub async fn is_relay_connected(relay: String, state: State<'_, Nostr>) -> Result { + let client = &state.client; + let status = client.add_relay(&relay).await.map_err(|e| e.to_string())?; Ok(status) } #[tauri::command] #[specta::specta] -pub async fn remove_relay(relay: &str, state: State<'_, Nostr>) -> Result { +pub async fn connect_relay(relay: String, state: State<'_, Nostr>) -> Result<(), String> { let client = &state.client; + let _ = client.add_relay(&relay).await; + let _ = client.connect_relay(&relay).await; - client - .force_remove_relay(relay) - .await - .map_err(|e| e.to_string())?; + Ok(()) +} + +#[tauri::command] +#[specta::specta] +pub async fn remove_relay(relay: String, state: State<'_, Nostr>) -> Result<(), String> { + let client = &state.client; + let _ = client.force_remove_relay(relay).await; - Ok(true) + Ok(()) } #[tauri::command] @@ -140,7 +169,7 @@ pub fn get_bootstrap_relays(app: tauri::AppHandle) -> Result, String #[tauri::command] #[specta::specta] -pub fn save_bootstrap_relays(relays: &str, app: tauri::AppHandle) -> Result<(), String> { +pub fn set_bootstrap_relays(relays: String, app: tauri::AppHandle) -> Result<(), String> { let relays_path = app .path() .resolve("resources/relays.txt", BaseDirectory::Resource) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 95f3297c..59e346f3 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -5,7 +5,7 @@ #[cfg(target_os = "macos")] use border::WebviewWindowExt as BorderWebviewWindowExt; -use commands::{account::*, event::*, metadata::*, relay::*, sync::*, window::*}; +use commands::{account::*, event::*, metadata::*, relay::*, window::*}; use common::{get_all_accounts, parse_event}; use nostr_sdk::prelude::{Profile as DatabaseProfile, *}; use serde::{Deserialize, Serialize}; @@ -71,13 +71,13 @@ fn main() { tracing_subscriber::fmt::init(); let builder = Builder::::new().commands(collect_commands![ - sync_account, - is_account_sync, get_relays, + get_all_relays, + is_relay_connected, connect_relay, remove_relay, get_bootstrap_relays, - save_bootstrap_relays, + set_bootstrap_relays, get_accounts, watch_account, import_account, @@ -116,6 +116,7 @@ fn main() { get_all_events_by_author, get_all_events_by_authors, get_all_events_by_hashtags, + get_all_events_from, get_local_events, get_global_events, search, diff --git a/src/commands.gen.ts b/src/commands.gen.ts index c4f7d368..fdb2ac32 100644 --- a/src/commands.gen.ts +++ b/src/commands.gen.ts @@ -5,31 +5,31 @@ export const commands = { -async syncAccount(id: string, reader: TAURI_CHANNEL) : Promise> { +async getRelays(id: string) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("sync_account", { id, reader }) }; + return { status: "ok", data: await TAURI_INVOKE("get_relays", { id }) }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; } }, -async isAccountSync(id: string) : Promise> { +async getAllRelays(until: string | null) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("is_account_sync", { id }) }; + return { status: "ok", data: await TAURI_INVOKE("get_all_relays", { until }) }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; } }, -async getRelays(id: string) : Promise> { +async isRelayConnected(relay: string) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("get_relays", { id }) }; + return { status: "ok", data: await TAURI_INVOKE("is_relay_connected", { relay }) }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; } }, -async connectRelay(relay: string) : Promise> { +async connectRelay(relay: string) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("connect_relay", { relay }) }; } catch (e) { @@ -37,7 +37,7 @@ async connectRelay(relay: string) : Promise> { else return { status: "error", error: e as any }; } }, -async removeRelay(relay: string) : Promise> { +async removeRelay(relay: string) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("remove_relay", { relay }) }; } catch (e) { @@ -53,9 +53,9 @@ async getBootstrapRelays() : Promise> { else return { status: "error", error: e as any }; } }, -async saveBootstrapRelays(relays: string) : Promise> { +async setBootstrapRelays(relays: string) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("save_bootstrap_relays", { relays }) }; + return { status: "ok", data: await TAURI_INVOKE("set_bootstrap_relays", { relays }) }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; @@ -360,6 +360,14 @@ async getAllEventsByHashtags(hashtags: string[], until: string | null) : Promise else return { status: "error", error: e as any }; } }, +async getAllEventsFrom(url: string, until: string | null) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("get_all_events_from", { url, until }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, async getLocalEvents(until: string | null) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("get_local_events", { until }) }; @@ -523,7 +531,6 @@ export type NewWindow = { label: string; title: string; url: string; width: numb export type Relays = { connected: string[]; read: string[] | null; write: string[] | null; both: string[] | null } export type RichEvent = { raw: string; parsed: Meta | null } export type Settings = { resize_service: boolean; content_warning: boolean; display_avatar: boolean; display_zap_button: boolean; display_repost_button: boolean; display_media: boolean } -export type TAURI_CHANNEL = null /** tauri-specta globals **/ diff --git a/src/components/column.tsx b/src/components/column.tsx index 0b353ab3..057a437f 100644 --- a/src/components/column.tsx +++ b/src/components/column.tsx @@ -40,7 +40,7 @@ export function Column({ column }: { column: LumeColumn }) { />
-
+
{error?.length ? error : null}
diff --git a/src/components/repost.tsx b/src/components/repost.tsx index bbac35f3..afd980ea 100644 --- a/src/components/repost.tsx +++ b/src/components/repost.tsx @@ -14,6 +14,8 @@ export const RepostNote = memo(function RepostNote({ }) { const { isLoading, isError, data } = useEvent(event.repostId, event.content); + console.log("Repost: ", event); + return ( {isLoading ? ( diff --git a/src/routes.gen.ts b/src/routes.gen.ts index ebc67a2d..ee23b5d1 100644 --- a/src/routes.gen.ts +++ b/src/routes.gen.ts @@ -51,6 +51,9 @@ const ColumnsLayoutSearchLazyImport = createFileRoute( const ColumnsLayoutOnboardingLazyImport = createFileRoute( '/columns/_layout/onboarding', )() +const ColumnsLayoutDiscoverRelaysLazyImport = createFileRoute( + '/columns/_layout/discover-relays', +)() const ColumnsLayoutDiscoverNewsfeedsLazyImport = createFileRoute( '/columns/_layout/discover-newsfeeds', )() @@ -63,6 +66,9 @@ const ColumnsLayoutUsersIdLazyImport = createFileRoute( const ColumnsLayoutRepliesIdLazyImport = createFileRoute( '/columns/_layout/replies/$id', )() +const ColumnsLayoutRelaysUrlLazyImport = createFileRoute( + '/columns/_layout/relays/$url', +)() const ColumnsLayoutNotificationIdLazyImport = createFileRoute( '/columns/_layout/notification/$id', )() @@ -202,6 +208,17 @@ const ColumnsLayoutOnboardingLazyRoute = import('./routes/columns/_layout/onboarding.lazy').then((d) => d.Route), ) +const ColumnsLayoutDiscoverRelaysLazyRoute = + ColumnsLayoutDiscoverRelaysLazyImport.update({ + id: '/discover-relays', + path: '/discover-relays', + getParentRoute: () => ColumnsLayoutRoute, + } as any).lazy(() => + import('./routes/columns/_layout/discover-relays.lazy').then( + (d) => d.Route, + ), + ) + const ColumnsLayoutDiscoverNewsfeedsLazyRoute = ColumnsLayoutDiscoverNewsfeedsLazyImport.update({ id: '/discover-newsfeeds', @@ -279,6 +296,16 @@ const ColumnsLayoutRepliesIdLazyRoute = ColumnsLayoutRepliesIdLazyImport.update( import('./routes/columns/_layout/replies.$id.lazy').then((d) => d.Route), ) +const ColumnsLayoutRelaysUrlLazyRoute = ColumnsLayoutRelaysUrlLazyImport.update( + { + id: '/relays/$url', + path: '/relays/$url', + getParentRoute: () => ColumnsLayoutRoute, + } as any, +).lazy(() => + import('./routes/columns/_layout/relays.$url.lazy').then((d) => d.Route), +) + const ColumnsLayoutNotificationIdLazyRoute = ColumnsLayoutNotificationIdLazyImport.update({ id: '/notification/$id', @@ -511,6 +538,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ColumnsLayoutDiscoverNewsfeedsLazyImport parentRoute: typeof ColumnsLayoutImport } + '/columns/_layout/discover-relays': { + id: '/columns/_layout/discover-relays' + path: '/discover-relays' + fullPath: '/columns/discover-relays' + preLoaderRoute: typeof ColumnsLayoutDiscoverRelaysLazyImport + parentRoute: typeof ColumnsLayoutImport + } '/columns/_layout/onboarding': { id: '/columns/_layout/onboarding' path: '/onboarding' @@ -595,6 +629,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ColumnsLayoutNotificationIdLazyImport parentRoute: typeof ColumnsLayoutImport } + '/columns/_layout/relays/$url': { + id: '/columns/_layout/relays/$url' + path: '/relays/$url' + fullPath: '/columns/relays/$url' + preLoaderRoute: typeof ColumnsLayoutRelaysUrlLazyImport + parentRoute: typeof ColumnsLayoutImport + } '/columns/_layout/replies/$id': { id: '/columns/_layout/replies/$id' path: '/replies/$id' @@ -646,6 +687,7 @@ interface ColumnsLayoutRouteChildren { ColumnsLayoutGlobalRoute: typeof ColumnsLayoutGlobalRoute ColumnsLayoutDiscoverInterestsLazyRoute: typeof ColumnsLayoutDiscoverInterestsLazyRoute ColumnsLayoutDiscoverNewsfeedsLazyRoute: typeof ColumnsLayoutDiscoverNewsfeedsLazyRoute + ColumnsLayoutDiscoverRelaysLazyRoute: typeof ColumnsLayoutDiscoverRelaysLazyRoute ColumnsLayoutOnboardingLazyRoute: typeof ColumnsLayoutOnboardingLazyRoute ColumnsLayoutSearchLazyRoute: typeof ColumnsLayoutSearchLazyRoute ColumnsLayoutTrendingLazyRoute: typeof ColumnsLayoutTrendingLazyRoute @@ -656,6 +698,7 @@ interface ColumnsLayoutRouteChildren { ColumnsLayoutEventsIdLazyRoute: typeof ColumnsLayoutEventsIdLazyRoute ColumnsLayoutLaunchpadIdLazyRoute: typeof ColumnsLayoutLaunchpadIdLazyRoute ColumnsLayoutNotificationIdLazyRoute: typeof ColumnsLayoutNotificationIdLazyRoute + ColumnsLayoutRelaysUrlLazyRoute: typeof ColumnsLayoutRelaysUrlLazyRoute ColumnsLayoutRepliesIdLazyRoute: typeof ColumnsLayoutRepliesIdLazyRoute ColumnsLayoutUsersIdLazyRoute: typeof ColumnsLayoutUsersIdLazyRoute } @@ -668,6 +711,7 @@ const ColumnsLayoutRouteChildren: ColumnsLayoutRouteChildren = { ColumnsLayoutDiscoverInterestsLazyRoute, ColumnsLayoutDiscoverNewsfeedsLazyRoute: ColumnsLayoutDiscoverNewsfeedsLazyRoute, + ColumnsLayoutDiscoverRelaysLazyRoute: ColumnsLayoutDiscoverRelaysLazyRoute, ColumnsLayoutOnboardingLazyRoute: ColumnsLayoutOnboardingLazyRoute, ColumnsLayoutSearchLazyRoute: ColumnsLayoutSearchLazyRoute, ColumnsLayoutTrendingLazyRoute: ColumnsLayoutTrendingLazyRoute, @@ -678,6 +722,7 @@ const ColumnsLayoutRouteChildren: ColumnsLayoutRouteChildren = { ColumnsLayoutEventsIdLazyRoute: ColumnsLayoutEventsIdLazyRoute, ColumnsLayoutLaunchpadIdLazyRoute: ColumnsLayoutLaunchpadIdLazyRoute, ColumnsLayoutNotificationIdLazyRoute: ColumnsLayoutNotificationIdLazyRoute, + ColumnsLayoutRelaysUrlLazyRoute: ColumnsLayoutRelaysUrlLazyRoute, ColumnsLayoutRepliesIdLazyRoute: ColumnsLayoutRepliesIdLazyRoute, ColumnsLayoutUsersIdLazyRoute: ColumnsLayoutUsersIdLazyRoute, } @@ -735,6 +780,7 @@ export interface FileRoutesByFullPath { '/settings/$id/wallet': typeof SettingsIdWalletRoute '/columns/discover-interests': typeof ColumnsLayoutDiscoverInterestsLazyRoute '/columns/discover-newsfeeds': typeof ColumnsLayoutDiscoverNewsfeedsLazyRoute + '/columns/discover-relays': typeof ColumnsLayoutDiscoverRelaysLazyRoute '/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute '/columns/search': typeof ColumnsLayoutSearchLazyRoute '/columns/trending': typeof ColumnsLayoutTrendingLazyRoute @@ -747,6 +793,7 @@ export interface FileRoutesByFullPath { '/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute '/columns/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute '/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute + '/columns/relays/$url': typeof ColumnsLayoutRelaysUrlLazyRoute '/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute '/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute } @@ -772,6 +819,7 @@ export interface FileRoutesByTo { '/settings/$id/wallet': typeof SettingsIdWalletRoute '/columns/discover-interests': typeof ColumnsLayoutDiscoverInterestsLazyRoute '/columns/discover-newsfeeds': typeof ColumnsLayoutDiscoverNewsfeedsLazyRoute + '/columns/discover-relays': typeof ColumnsLayoutDiscoverRelaysLazyRoute '/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute '/columns/search': typeof ColumnsLayoutSearchLazyRoute '/columns/trending': typeof ColumnsLayoutTrendingLazyRoute @@ -784,6 +832,7 @@ export interface FileRoutesByTo { '/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute '/columns/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute '/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute + '/columns/relays/$url': typeof ColumnsLayoutRelaysUrlLazyRoute '/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute '/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute } @@ -812,6 +861,7 @@ export interface FileRoutesById { '/settings/$id/wallet': typeof SettingsIdWalletRoute '/columns/_layout/discover-interests': typeof ColumnsLayoutDiscoverInterestsLazyRoute '/columns/_layout/discover-newsfeeds': typeof ColumnsLayoutDiscoverNewsfeedsLazyRoute + '/columns/_layout/discover-relays': typeof ColumnsLayoutDiscoverRelaysLazyRoute '/columns/_layout/onboarding': typeof ColumnsLayoutOnboardingLazyRoute '/columns/_layout/search': typeof ColumnsLayoutSearchLazyRoute '/columns/_layout/trending': typeof ColumnsLayoutTrendingLazyRoute @@ -824,6 +874,7 @@ export interface FileRoutesById { '/columns/_layout/events/$id': typeof ColumnsLayoutEventsIdLazyRoute '/columns/_layout/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute '/columns/_layout/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute + '/columns/_layout/relays/$url': typeof ColumnsLayoutRelaysUrlLazyRoute '/columns/_layout/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute '/columns/_layout/users/$id': typeof ColumnsLayoutUsersIdLazyRoute } @@ -852,6 +903,7 @@ export interface FileRouteTypes { | '/settings/$id/wallet' | '/columns/discover-interests' | '/columns/discover-newsfeeds' + | '/columns/discover-relays' | '/columns/onboarding' | '/columns/search' | '/columns/trending' @@ -864,6 +916,7 @@ export interface FileRouteTypes { | '/columns/events/$id' | '/columns/launchpad/$id' | '/columns/notification/$id' + | '/columns/relays/$url' | '/columns/replies/$id' | '/columns/users/$id' fileRoutesByTo: FileRoutesByTo @@ -888,6 +941,7 @@ export interface FileRouteTypes { | '/settings/$id/wallet' | '/columns/discover-interests' | '/columns/discover-newsfeeds' + | '/columns/discover-relays' | '/columns/onboarding' | '/columns/search' | '/columns/trending' @@ -900,6 +954,7 @@ export interface FileRouteTypes { | '/columns/events/$id' | '/columns/launchpad/$id' | '/columns/notification/$id' + | '/columns/relays/$url' | '/columns/replies/$id' | '/columns/users/$id' id: @@ -926,6 +981,7 @@ export interface FileRouteTypes { | '/settings/$id/wallet' | '/columns/_layout/discover-interests' | '/columns/_layout/discover-newsfeeds' + | '/columns/_layout/discover-relays' | '/columns/_layout/onboarding' | '/columns/_layout/search' | '/columns/_layout/trending' @@ -938,6 +994,7 @@ export interface FileRouteTypes { | '/columns/_layout/events/$id' | '/columns/_layout/launchpad/$id' | '/columns/_layout/notification/$id' + | '/columns/_layout/relays/$url' | '/columns/_layout/replies/$id' | '/columns/_layout/users/$id' fileRoutesById: FileRoutesById @@ -1037,6 +1094,7 @@ export const routeTree = rootRoute "/columns/_layout/global", "/columns/_layout/discover-interests", "/columns/_layout/discover-newsfeeds", + "/columns/_layout/discover-relays", "/columns/_layout/onboarding", "/columns/_layout/search", "/columns/_layout/trending", @@ -1047,6 +1105,7 @@ export const routeTree = rootRoute "/columns/_layout/events/$id", "/columns/_layout/launchpad/$id", "/columns/_layout/notification/$id", + "/columns/_layout/relays/$url", "/columns/_layout/replies/$id", "/columns/_layout/users/$id" ] @@ -1110,6 +1169,10 @@ export const routeTree = rootRoute "filePath": "columns/_layout/discover-newsfeeds.lazy.tsx", "parent": "/columns/_layout" }, + "/columns/_layout/discover-relays": { + "filePath": "columns/_layout/discover-relays.lazy.tsx", + "parent": "/columns/_layout" + }, "/columns/_layout/onboarding": { "filePath": "columns/_layout/onboarding.lazy.tsx", "parent": "/columns/_layout" @@ -1158,6 +1221,10 @@ export const routeTree = rootRoute "filePath": "columns/_layout/notification.$id.lazy.tsx", "parent": "/columns/_layout" }, + "/columns/_layout/relays/$url": { + "filePath": "columns/_layout/relays.$url.lazy.tsx", + "parent": "/columns/_layout" + }, "/columns/_layout/replies/$id": { "filePath": "columns/_layout/replies.$id.lazy.tsx", "parent": "/columns/_layout" diff --git a/src/routes/bootstrap-relays.lazy.tsx b/src/routes/bootstrap-relays.lazy.tsx index 8d56ca31..f70afd45 100644 --- a/src/routes/bootstrap-relays.lazy.tsx +++ b/src/routes/bootstrap-relays.lazy.tsx @@ -53,7 +53,7 @@ function Screen() { } const merged = relays.join("\r\n"); - const res = await commands.saveBootstrapRelays(merged); + const res = await commands.setBootstrapRelays(merged); if (res.status === "ok") { return await relaunch(); diff --git a/src/routes/columns/_layout/discover-relays.lazy.tsx b/src/routes/columns/_layout/discover-relays.lazy.tsx new file mode 100644 index 00000000..85000193 --- /dev/null +++ b/src/routes/columns/_layout/discover-relays.lazy.tsx @@ -0,0 +1,154 @@ +import { commands } from "@/commands.gen"; +import { Spinner, User } from "@/components"; +import { LumeWindow } from "@/system"; +import type { NostrEvent } from "@/types"; +import { ArrowDown } from "@phosphor-icons/react"; +import * as ScrollArea from "@radix-ui/react-scroll-area"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { createLazyFileRoute } from "@tanstack/react-router"; +import { type RefObject, useCallback, useRef } from "react"; +import { Virtualizer } from "virtua"; + +export const Route = createLazyFileRoute("/columns/_layout/discover-relays")({ + component: Screen, +}); + +function Screen() { + const { + isLoading, + isError, + error, + isFetchingNextPage, + hasNextPage, + fetchNextPage, + data, + } = useInfiniteQuery({ + queryKey: ["discover-relays"], + initialPageParam: 0, + queryFn: async ({ pageParam }: { pageParam: number }) => { + const until = pageParam > 0 ? pageParam.toString() : null; + const res = await commands.getAllRelays(until); + + if (res.status === "ok") { + const data: NostrEvent[] = res.data.map((item) => JSON.parse(item)); + return data; + } else { + throw new Error(res.error); + } + }, + getNextPageParam: (lastPage) => { + const lastEvent = lastPage.at(-1); + + if (lastEvent) { + return lastEvent.created_at - 1; + } + }, + select: (data) => data?.pages.flat(), + refetchOnWindowFocus: false, + refetchOnReconnect: false, + refetchOnMount: false, + }); + + const ref = useRef(null); + + const renderItem = useCallback( + (item: NostrEvent) => { + return ( +
+
+ {item.tags.map((tag) => + tag[1]?.startsWith("wss://") ? ( +
+
+ {tag[1]} +
+ +
+ ) : null, + )} +
+
+ + + + + + +
+
+ ); + }, + [data], + ); + + return ( + + + }> + {isLoading ? ( +
+ + Loading... +
+ ) : isError ? ( +
+

{error?.message ?? "Error"}

+
+ ) : !data?.length ? ( +
+

Empty.

+
+ ) : ( + data?.map((item) => renderItem(item)) + )} + {hasNextPage ? ( + + ) : null} +
+
+ + + + +
+ ); +} diff --git a/src/routes/columns/_layout/groups.$id.lazy.tsx b/src/routes/columns/_layout/groups.$id.lazy.tsx index 16b8a2b2..bd7243d7 100644 --- a/src/routes/columns/_layout/groups.$id.lazy.tsx +++ b/src/routes/columns/_layout/groups.$id.lazy.tsx @@ -26,7 +26,7 @@ export function Screen() { hasNextPage, fetchNextPage, } = useInfiniteQuery({ - queryKey: ["events", "groups", params.id], + queryKey: ["groups", params.id], initialPageParam: 0, queryFn: async ({ pageParam }: { pageParam: number }) => { const until = pageParam > 0 ? pageParam.toString() : null; diff --git a/src/routes/columns/_layout/interests.$id.lazy.tsx b/src/routes/columns/_layout/interests.$id.lazy.tsx index 03593320..dec2de9f 100644 --- a/src/routes/columns/_layout/interests.$id.lazy.tsx +++ b/src/routes/columns/_layout/interests.$id.lazy.tsx @@ -26,7 +26,7 @@ export function Screen() { hasNextPage, fetchNextPage, } = useInfiniteQuery({ - queryKey: ["events", "hashtags", params.id], + queryKey: ["hashtags", params.id], initialPageParam: 0, queryFn: async ({ pageParam }: { pageParam: number }) => { const tags = hashtags.map((tag) => tag.toLowerCase().replace("#", "")); diff --git a/src/routes/columns/_layout/launchpad.$id.lazy.tsx b/src/routes/columns/_layout/launchpad.$id.lazy.tsx index 4169d0b7..3962a899 100644 --- a/src/routes/columns/_layout/launchpad.$id.lazy.tsx +++ b/src/routes/columns/_layout/launchpad.$id.lazy.tsx @@ -42,7 +42,7 @@ function Screen() { function Newsfeeds() { const { id } = Route.useParams(); const { isLoading, isError, error, data, refetch, isRefetching } = useQuery({ - queryKey: ["others", "newsfeeds", id], + queryKey: ["newsfeeds", id], queryFn: async () => { const res = await commands.getAllNewsfeeds(id); @@ -204,7 +204,7 @@ function Newsfeeds() { function Interests() { const { id } = Route.useParams(); const { isLoading, isError, error, data, refetch, isRefetching } = useQuery({ - queryKey: ["others", "interests", id], + queryKey: ["interests", id], queryFn: async () => { const res = await commands.getAllInterests(id); @@ -399,6 +399,22 @@ function Core() { Add
+
+
Relays
+ +
{data?.map((column) => (
{ - const unlisten = listen("synchronized", async () => { - await queryClient.refetchQueries({ - queryKey: ["events", "newsfeed", search.label], - }); - }); - - return () => { - unlisten.then((f) => f()); - }; - }, []); - return ( { + const until = pageParam > 0 ? pageParam.toString() : null; + const relay = decodeURIComponent(url); + const res = await commands.getAllEventsFrom(relay, until); + + if (res.status === "error") { + throw new Error(res.error); + } + + return toLumeEvents(res.data); + }, + getNextPageParam: (lastPage) => { + const lastEvent = lastPage.at(-1); + + if (lastEvent) { + return lastEvent.created_at - 1; + } + }, + select: (data) => data?.pages.flat(), + }); + + const ref = useRef(null); + + const renderItem = useCallback( + (event: LumeEvent) => { + if (!event) { + return; + } + + switch (event.kind) { + case Kind.Repost: { + const repostId = event.repostId; + + return ( + + ); + } + default: + return ( + + ); + } + }, + [data], + ); + + return ( + + + }> + {isFetching && !isLoading && !isFetchingNextPage ? ( +
+
+ + Getting new notes... +
+
+ ) : null} + {isLoading ? ( +
+ + Loading... +
+ ) : !data?.length ? ( +
+ 🎉 Yo. You're catching up on all latest notes. +
+ ) : ( + data.map((item) => renderItem(item)) + )} + {hasNextPage ? ( +
+ +
+ ) : null} +
+
+ + + + +
+ ); +}