wip: handle events

This commit is contained in:
2025-12-14 08:58:33 +07:00
parent af740f462c
commit dd2f940e33
9 changed files with 143 additions and 154 deletions

View File

@@ -4,7 +4,7 @@ use anyhow::Error;
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
use state::NostrRegistry;
use state::client;
pub fn init(cx: &mut App) {
Account::set_global(cx.new(Account::new), cx);
@@ -48,9 +48,6 @@ impl Account {
/// Create a new account instance
fn new(cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
// Collect command line arguments
let args: Vec<String> = env::args().collect();
let account = args.get(1).and_then(|s| Keys::parse(s).ok());
@@ -67,7 +64,9 @@ impl Account {
// Set the signer
cx.background_executor()
.await_on_background(async move {
let client = client();
client.set_signer(keys).await;
log::info!("Signer is set");
})
.await;
@@ -109,10 +108,8 @@ impl Account {
}
fn init(&mut self, public_key: PublicKey, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let client = client();
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
// Construct a filter to get the user's metadata

View File

@@ -34,3 +34,4 @@ futures.workspace = true
oneshot.workspace = true
tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
flume = { version = "0.12", default-features = false, features = ["async", "select"] }

View File

@@ -27,8 +27,3 @@ pub fn load_embedded_fonts(cx: &App) {
.add_fonts(embedded_fonts.into_inner().unwrap())
.unwrap();
}
pub fn quit(_: &Quit, cx: &mut App) {
log::info!("Gracefully quitting the application . . .");
cx.quit();
}

View File

@@ -1,15 +1,15 @@
use std::sync::Arc;
use assets::Assets;
use common::{APP_ID, CLIENT_NAME};
use common::{APP_ID, BOOTSTRAP_RELAYS, CLIENT_NAME, SEARCH_RELAYS};
use gpui::{
point, px, size, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem, SharedString,
TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind,
WindowOptions,
point, px, size, AppContext, Application, Bounds, SharedString, TitlebarOptions,
WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowOptions,
};
use gpui_component::Root;
use state::client;
use crate::actions::{load_embedded_fonts, quit, Quit};
use crate::actions::load_embedded_fonts;
use crate::workspace::Workspace;
mod actions;
@@ -29,28 +29,32 @@ fn main() {
.with_assets(Assets)
.with_http_client(Arc::new(reqwest_client::ReqwestClient::new()));
// Initialize the nostr client
let client = client();
// Establish connection to all bootstrap relays
app.background_executor()
.spawn_with_priority(gpui::Priority::High, async move {
// Add bootstrap relays to the relay pool
for url in BOOTSTRAP_RELAYS {
client.add_relay(url).await.ok();
}
// Add search relays to the relay pool
for url in SEARCH_RELAYS {
client.add_relay(url).await.ok();
}
// Connect to all added relays
client.connect().await;
})
.detach();
// Run application
app.run(move |cx| {
// Load embedded fonts in assets/fonts
load_embedded_fonts(cx);
// Register the `quit` function
cx.on_action(quit);
// Register the `quit` function with CMD+Q (macOS)
#[cfg(target_os = "macos")]
cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
// Register the `quit` function with Super+Q (others)
#[cfg(not(target_os = "macos"))]
cx.bind_keys([KeyBinding::new("super-q", Quit, None)]);
// Set menu items
cx.set_menus(vec![Menu {
name: "Lume".into(),
items: vec![MenuItem::action("Quit", Quit)],
}]);
// Set up the window bounds
let bounds = Bounds::centered(None, size(px(920.0), px(700.0)), cx);
@@ -83,9 +87,6 @@ fn main() {
// Initialize themes
themes::init(cx);
// Initialize app state
state::init(cx);
// Initialize account
account::init(cx);

View File

@@ -1,6 +1,6 @@
use gpui::{
div, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
ParentElement, Render, Window,
ParentElement, Render, SharedString, Window,
};
use gpui_component::dock::{Panel, PanelEvent};
@@ -24,6 +24,10 @@ impl Panel for Startup {
fn panel_name(&self) -> &'static str {
"Startup"
}
fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
SharedString::from("Welcome")
}
}
impl EventEmitter<PanelEvent> for Startup {}

View File

@@ -1,14 +1,17 @@
use std::sync::Arc;
use account::Account;
use anyhow::Error;
use common::{CLIENT_NAME, DEFAULT_SIDEBAR_WIDTH};
use gpui::{
div, px, AppContext, Axis, Context, Entity, InteractiveElement, IntoElement, ParentElement,
Render, Styled, Subscription, Window,
Render, Styled, Subscription, Task, Window,
};
use gpui_component::dock::{DockArea, DockItem};
use gpui_component::{v_flex, Root, Theme};
use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
use state::{client, StateEvent};
use crate::panels::startup;
use crate::sidebar;
@@ -24,6 +27,9 @@ pub struct Workspace {
/// Event subscriptions
_subscriptions: SmallVec<[Subscription; 1]>,
/// Background tasks
_tasks: SmallVec<[Task<Result<(), Error>>; 2]>,
}
impl Workspace {
@@ -34,15 +40,13 @@ impl Workspace {
let mut subscriptions = smallvec![];
subscriptions.push(
// Automatically sync theme with system appearance
window.observe_window_appearance(|window, cx| {
Theme::sync_system_appearance(Some(window), cx);
}),
);
// Automatically sync theme with system appearance
subscriptions.push(window.observe_window_appearance(|window, cx| {
Theme::sync_system_appearance(Some(window), cx);
}));
// Observe account entity changes
subscriptions.push(
// Observe account entity changes
cx.observe_in(&account, window, move |this, state, window, cx| {
if state.read(cx).has_account() {
this.init_app_layout(window, cx);
@@ -50,10 +54,51 @@ impl Workspace {
}),
);
let mut tasks = smallvec![];
let (tx, rx) = flume::bounded::<StateEvent>(2048);
// Handle nostr notifications
tasks.push(cx.background_spawn(async move {
let client = client();
let mut notifications = client.notifications();
while let Ok(notification) = notifications.recv().await {
let RelayPoolNotification::Message { message, relay_url } = notification else {
continue;
};
match message {
RelayMessage::Event { event, .. } => {
// TODO
}
RelayMessage::EndOfStoredEvents(subscription_id) => {
// TODO
}
_ => {}
}
}
Ok(())
}));
// Handle state events
tasks.push(cx.spawn_in(window, async move |this, cx| {
while let Ok(event) = rx.recv_async().await {
cx.update(|window, cx| {
// TODO
})
// Entity has been released, ignore any errors
.ok();
}
Ok(())
}));
Self {
dock,
title_bar,
_subscriptions: subscriptions,
_tasks: tasks,
}
}

View File

@@ -0,0 +1,4 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum StateEvent {
//
}

View File

@@ -1,43 +1,18 @@
use std::sync::OnceLock;
use std::time::Duration;
use common::{config_dir, BOOTSTRAP_RELAYS, SEARCH_RELAYS};
use gpui::{App, AppContext, Context, Entity, Global, Task};
use common::config_dir;
pub use event::*;
use nostr_gossip_memory::prelude::*;
use nostr_lmdb::NostrLmdb;
use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
pub fn init(cx: &mut App) {
NostrRegistry::set_global(cx.new(NostrRegistry::new), cx);
}
mod event;
struct GlobalNostrRegistry(Entity<NostrRegistry>);
static NOSTR_CLIENT: OnceLock<Client> = OnceLock::new();
impl Global for GlobalNostrRegistry {}
/// Nostr Registry
#[derive(Debug)]
pub struct NostrRegistry {
/// Nostr Client
client: Client,
/// Tasks for asynchronous operations
_tasks: SmallVec<[Task<()>; 1]>,
}
impl NostrRegistry {
/// Retrieve the global nostr state
pub fn global(cx: &App) -> Entity<Self> {
cx.global::<GlobalNostrRegistry>().0.clone()
}
/// Set the global nostr instance
fn set_global(state: Entity<Self>, cx: &mut App) {
cx.set_global(GlobalNostrRegistry(state));
}
/// Create a new nostr instance
fn new(cx: &mut Context<Self>) -> Self {
pub fn client() -> &'static Client {
NOSTR_CLIENT.get_or_init(|| {
// rustls uses the `aws_lc_rs` provider by default
// This only errors if the default provider has already
// been installed. We can ignore this `Result`.
@@ -54,7 +29,7 @@ impl NostrRegistry {
});
// Construct the lmdb
let lmdb = cx.background_executor().block(async move {
let lmdb = smol::block_on(async move {
let path = config_dir().join("nostr");
NostrLmdb::open(path)
.await
@@ -62,55 +37,10 @@ impl NostrRegistry {
});
// Construct the nostr client
let client = ClientBuilder::default()
ClientBuilder::default()
.database(lmdb)
.gossip(NostrGossipMemory::unbounded())
.opts(opts)
.build();
let mut tasks = smallvec![];
tasks.push(
// Establish connection to the bootstrap relays
//
// And handle notifications from the nostr relay pool channel
cx.background_spawn({
let client = client.clone();
async move {
// Connect to the bootstrap relays
Self::connect(&client).await;
// Handle notifications from the relay pool
// Self::handle_notifications(&client, &gossip, &tracker).await;
}
}),
);
Self {
client,
_tasks: tasks,
}
}
/// Get the nostr client instance
pub fn client(&self) -> Client {
self.client.clone()
}
/// Establish connection to the bootstrap relays
async fn connect(client: &Client) {
// Get all bootstrapping relays
let mut urls = vec![];
urls.extend(BOOTSTRAP_RELAYS);
urls.extend(SEARCH_RELAYS);
// Add relay to the relay pool
for url in urls.into_iter() {
client.add_relay(url).await.ok();
}
// Connect to all added relays
client.connect().await;
}
.build()
})
}