wip: handle events
This commit is contained in:
61
Cargo.lock
generated
61
Cargo.lock
generated
@@ -1131,7 +1131,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collections"
|
name = "collections"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
@@ -1520,7 +1520,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_refineable"
|
name = "derive_refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2402,7 +2402,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui"
|
name = "gpui"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"as-raw-xcb-connection",
|
"as-raw-xcb-connection",
|
||||||
@@ -2562,7 +2562,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_macros"
|
name = "gpui_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -2573,7 +2573,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_tokio"
|
name = "gpui_tokio"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -2800,7 +2800,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client"
|
name = "http_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
@@ -2825,7 +2825,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client_tls"
|
name = "http_client_tls"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
@@ -3081,7 +3081,7 @@ dependencies = [
|
|||||||
"rgb",
|
"rgb",
|
||||||
"tiff",
|
"tiff",
|
||||||
"zune-core 0.5.0",
|
"zune-core 0.5.0",
|
||||||
"zune-jpeg 0.5.6",
|
"zune-jpeg 0.5.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3526,6 +3526,7 @@ dependencies = [
|
|||||||
"nostr-connect",
|
"nostr-connect",
|
||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
"oneshot",
|
"oneshot",
|
||||||
|
"person",
|
||||||
"reqwest_client",
|
"reqwest_client",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -3670,7 +3671,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "media"
|
name = "media"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bindgen",
|
"bindgen",
|
||||||
@@ -3773,9 +3774,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "moxcms"
|
name = "moxcms"
|
||||||
version = "0.7.10"
|
version = "0.7.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608"
|
checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"pxfm",
|
"pxfm",
|
||||||
@@ -4540,13 +4541,29 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "perf"
|
name = "perf"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "person"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"common",
|
||||||
|
"gpui",
|
||||||
|
"log",
|
||||||
|
"nostr-sdk",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"smallvec",
|
||||||
|
"smol",
|
||||||
|
"state",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf"
|
name = "phf"
|
||||||
version = "0.11.3"
|
version = "0.11.3"
|
||||||
@@ -5158,7 +5175,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "refineable"
|
name = "refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_refineable",
|
"derive_refineable",
|
||||||
]
|
]
|
||||||
@@ -5239,7 +5256,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest_client"
|
name = "reqwest_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -6218,7 +6235,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "sum_tree"
|
name = "sum_tree"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
@@ -7174,7 +7191,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "util"
|
name = "util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-fs",
|
"async-fs",
|
||||||
@@ -7210,7 +7227,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "util_macros"
|
name = "util_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"perf",
|
"perf",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -8693,7 +8710,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "zlog"
|
name = "zlog"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -8704,7 +8721,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ztracing"
|
name = "ztracing"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@@ -8715,7 +8732,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ztracing_macro"
|
name = "ztracing_macro"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#0283bfb04949295086b5ce6c892defa9c3ecc008"
|
source = "git+https://github.com/zed-industries/zed#a51585d2daaa975409a835f43574c8bb5bcc9d5b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-core"
|
name = "zune-core"
|
||||||
@@ -8749,9 +8766,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-jpeg"
|
name = "zune-jpeg"
|
||||||
version = "0.5.6"
|
version = "0.5.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f520eebad972262a1dde0ec455bce4f8b298b1e5154513de58c114c4c54303e8"
|
checksum = "51d915729b0e7d5fe35c2f294c5dc10b30207cc637920e5b59077bfa3da63f28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zune-core 0.5.0",
|
"zune-core 0.5.0",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
@@ -15,9 +16,12 @@ struct GlobalAccount(Entity<Account>);
|
|||||||
impl Global for GlobalAccount {}
|
impl Global for GlobalAccount {}
|
||||||
|
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
/// The public key of the account
|
/// Public Key of the account
|
||||||
public_key: Option<PublicKey>,
|
public_key: Option<PublicKey>,
|
||||||
|
|
||||||
|
/// Contact List of the account
|
||||||
|
pub contacts: Entity<HashSet<PublicKey>>,
|
||||||
|
|
||||||
/// Event subscriptions
|
/// Event subscriptions
|
||||||
_subscriptions: SmallVec<[Subscription; 1]>,
|
_subscriptions: SmallVec<[Subscription; 1]>,
|
||||||
|
|
||||||
@@ -48,6 +52,8 @@ impl Account {
|
|||||||
|
|
||||||
/// Create a new account instance
|
/// Create a new account instance
|
||||||
fn new(cx: &mut Context<Self>) -> Self {
|
fn new(cx: &mut Context<Self>) -> Self {
|
||||||
|
let contacts = cx.new(|_| HashSet::default());
|
||||||
|
|
||||||
// Collect command line arguments
|
// Collect command line arguments
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
let account = args.get(1).and_then(|s| Keys::parse(s).ok());
|
let account = args.get(1).and_then(|s| Keys::parse(s).ok());
|
||||||
@@ -56,11 +62,11 @@ impl Account {
|
|||||||
let mut tasks = smallvec![];
|
let mut tasks = smallvec![];
|
||||||
|
|
||||||
if let Some(keys) = account {
|
if let Some(keys) = account {
|
||||||
|
let public_key = keys.public_key();
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
// Set signer in background
|
// Set signer in background
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
let public_key = keys.public_key();
|
|
||||||
|
|
||||||
// Set the signer
|
// Set the signer
|
||||||
cx.background_executor()
|
cx.background_executor()
|
||||||
.await_on_background(async move {
|
.await_on_background(async move {
|
||||||
@@ -91,22 +97,12 @@ impl Account {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
public_key: None,
|
public_key: None,
|
||||||
|
contacts,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
_tasks: tasks,
|
_tasks: tasks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the account entity has a public key
|
|
||||||
pub fn has_account(&self) -> bool {
|
|
||||||
self.public_key.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the public key of the account
|
|
||||||
pub fn public_key(&self) -> PublicKey {
|
|
||||||
// This method is only called when user is logged in, so unwrap safely
|
|
||||||
self.public_key.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(&mut self, public_key: PublicKey, cx: &mut Context<Self>) {
|
fn init(&mut self, public_key: PublicKey, cx: &mut Context<Self>) {
|
||||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||||
let client = client();
|
let client = client();
|
||||||
@@ -137,4 +133,41 @@ impl Account {
|
|||||||
|
|
||||||
self._tasks.push(task);
|
self._tasks.push(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if the account entity has a public key
|
||||||
|
pub fn has_account(&self) -> bool {
|
||||||
|
self.public_key.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the public key of the account
|
||||||
|
pub fn public_key(&self) -> PublicKey {
|
||||||
|
// This method is only called when user is logged in, so unwrap safely
|
||||||
|
self.public_key.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the contacts of the account from the database
|
||||||
|
pub fn load_contacts(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let task: Task<Result<HashSet<PublicKey>, Error>> = cx.background_spawn(async move {
|
||||||
|
let client = client();
|
||||||
|
let signer = client.signer().await?;
|
||||||
|
let public_key = signer.get_public_key().await?;
|
||||||
|
let contacts = client.database().contacts_public_keys(public_key).await?;
|
||||||
|
|
||||||
|
Ok(contacts)
|
||||||
|
});
|
||||||
|
|
||||||
|
self._tasks.push(cx.spawn(async move |this, cx| {
|
||||||
|
if let Ok(contacts) = task.await {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.contacts.update(cx, |this, cx| {
|
||||||
|
this.extend(contacts);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ common = { path = "../common" }
|
|||||||
assets = { path = "../assets" }
|
assets = { path = "../assets" }
|
||||||
state = { path = "../state" }
|
state = { path = "../state" }
|
||||||
account = { path = "../account" }
|
account = { path = "../account" }
|
||||||
|
person = { path = "../person" }
|
||||||
|
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
gpui-component.workspace = true
|
gpui-component.workspace = true
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ fn main() {
|
|||||||
// Initialize account
|
// Initialize account
|
||||||
account::init(cx);
|
account::init(cx);
|
||||||
|
|
||||||
|
// Initialize person registry
|
||||||
|
person::init(cx);
|
||||||
|
|
||||||
let workspace = cx.new(|cx| Workspace::new(window, cx));
|
let workspace = cx.new(|cx| Workspace::new(window, cx));
|
||||||
cx.new(|cx| Root::new(workspace, window, cx))
|
cx.new(|cx| Root::new(workspace, window, cx))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
|
use account::Account;
|
||||||
|
use common::BOOTSTRAP_RELAYS;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
|
div, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
ParentElement, Render, Window,
|
InteractiveElement, IntoElement, ParentElement, Render, SharedString,
|
||||||
|
StatefulInteractiveElement, Styled, Window,
|
||||||
};
|
};
|
||||||
use gpui_component::dock::{Panel, PanelEvent};
|
use gpui_component::dock::{Panel, PanelEvent};
|
||||||
|
use gpui_component::scroll::ScrollableElement;
|
||||||
|
use gpui_component::{h_flex, v_flex, ActiveTheme, StyledExt};
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use person::PersonRegistry;
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Sidebar> {
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Sidebar> {
|
||||||
cx.new(|cx| Sidebar::new(window, cx))
|
cx.new(|cx| Sidebar::new(window, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum SidebarEvent {
|
||||||
|
OpenPublicKey(PublicKey),
|
||||||
|
OpenRelay(RelayUrl),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Sidebar {
|
pub struct Sidebar {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
}
|
}
|
||||||
@@ -27,6 +40,7 @@ impl Panel for Sidebar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<PanelEvent> for Sidebar {}
|
impl EventEmitter<PanelEvent> for Sidebar {}
|
||||||
|
impl EventEmitter<SidebarEvent> for Sidebar {}
|
||||||
|
|
||||||
impl Focusable for Sidebar {
|
impl Focusable for Sidebar {
|
||||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||||
@@ -35,7 +49,87 @@ impl Focusable for Sidebar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for Sidebar {
|
impl Render for Sidebar {
|
||||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
div().child("Sidebar")
|
let person = PersonRegistry::global(cx);
|
||||||
|
let account = Account::global(cx);
|
||||||
|
let contacts = account.read(cx).contacts.read(cx);
|
||||||
|
|
||||||
|
v_flex()
|
||||||
|
.id("sidebar-wrapper")
|
||||||
|
.size_full()
|
||||||
|
.relative()
|
||||||
|
.px_2()
|
||||||
|
.overflow_y_scrollbar()
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_1p5()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.mt_4()
|
||||||
|
.mb_2()
|
||||||
|
.font_semibold()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(cx.theme().muted_foreground)
|
||||||
|
.child("Relays"),
|
||||||
|
)
|
||||||
|
.child(v_flex().gap_2().children({
|
||||||
|
let mut items = Vec::with_capacity(BOOTSTRAP_RELAYS.len());
|
||||||
|
|
||||||
|
for (ix, relay) in BOOTSTRAP_RELAYS.into_iter().enumerate() {
|
||||||
|
items.push(
|
||||||
|
h_flex()
|
||||||
|
.id(SharedString::from(format!("relay-{ix}")))
|
||||||
|
.h_7()
|
||||||
|
.px_2()
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
.hover(|this| this.bg(cx.theme().list_hover))
|
||||||
|
.child(div().text_sm().child(SharedString::from(relay)))
|
||||||
|
.on_click(cx.listener(move |_this, _ev, _window, cx| {
|
||||||
|
if let Ok(url) = RelayUrl::parse(relay) {
|
||||||
|
cx.emit(SidebarEvent::OpenRelay(url));
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_1p5()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.mt_4()
|
||||||
|
.mb_2()
|
||||||
|
.font_semibold()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(cx.theme().muted_foreground)
|
||||||
|
.child("Contacts"),
|
||||||
|
)
|
||||||
|
.child(v_flex().gap_2().children({
|
||||||
|
let mut items = Vec::with_capacity(contacts.len());
|
||||||
|
|
||||||
|
for (ix, contact) in contacts.iter().enumerate() {
|
||||||
|
let profile = person.read(cx).get(contact, cx);
|
||||||
|
let name = SharedString::from(profile.name());
|
||||||
|
|
||||||
|
items.push(
|
||||||
|
h_flex()
|
||||||
|
.id(SharedString::from(format!("contact-{ix}")))
|
||||||
|
.h_7()
|
||||||
|
.px_2()
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
.hover(|this| this.bg(cx.theme().list_hover))
|
||||||
|
.child(div().text_sm().child(name.clone()))
|
||||||
|
.on_click(cx.listener(move |_this, _ev, _window, cx| {
|
||||||
|
cx.emit(SidebarEvent::OpenPublicKey(profile.public_key()));
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
|
})),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use account::Account;
|
use account::Account;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use common::{CLIENT_NAME, DEFAULT_SIDEBAR_WIDTH};
|
use common::{BOOTSTRAP_RELAYS, CLIENT_NAME, DEFAULT_SIDEBAR_WIDTH};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, AppContext, Axis, Context, Entity, InteractiveElement, IntoElement, ParentElement,
|
div, px, AppContext, Axis, Context, Entity, InteractiveElement, IntoElement, ParentElement,
|
||||||
Render, Styled, Subscription, Task, Window,
|
Render, Styled, Subscription, Task, Window,
|
||||||
@@ -10,11 +11,12 @@ use gpui::{
|
|||||||
use gpui_component::dock::{DockArea, DockItem};
|
use gpui_component::dock::{DockArea, DockItem};
|
||||||
use gpui_component::{v_flex, Root, Theme};
|
use gpui_component::{v_flex, Root, Theme};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
use person::PersonRegistry;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use state::{client, StateEvent};
|
use state::{client, StateEvent};
|
||||||
|
|
||||||
use crate::panels::startup;
|
use crate::panels::startup;
|
||||||
use crate::sidebar;
|
use crate::sidebar::{self, SidebarEvent};
|
||||||
use crate::title_bar::AppTitleBar;
|
use crate::title_bar::AppTitleBar;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -60,37 +62,90 @@ impl Workspace {
|
|||||||
// Handle nostr notifications
|
// Handle nostr notifications
|
||||||
tasks.push(cx.background_spawn(async move {
|
tasks.push(cx.background_spawn(async move {
|
||||||
let client = client();
|
let client = client();
|
||||||
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
|
let mut processed_events: HashSet<EventId> = HashSet::default();
|
||||||
|
|
||||||
while let Ok(notification) = notifications.recv().await {
|
while let Ok(notification) = notifications.recv().await {
|
||||||
let RelayPoolNotification::Message { message, relay_url } = notification else {
|
let RelayPoolNotification::Message { message, .. } = notification else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
RelayMessage::Event { event, .. } => {
|
RelayMessage::Event { event, .. } => {
|
||||||
// TODO
|
// Skip if already processed
|
||||||
|
if !processed_events.insert(event.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match event.kind {
|
||||||
|
Kind::TextNote => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
Kind::ContactList => {
|
||||||
|
// Get all public keys from the event
|
||||||
|
let public_keys: Vec<PublicKey> =
|
||||||
|
event.tags.public_keys().copied().collect();
|
||||||
|
|
||||||
|
// Construct a filter to get metadata for each public key
|
||||||
|
let filter = Filter::new()
|
||||||
|
.kind(Kind::Metadata)
|
||||||
|
.limit(public_keys.len())
|
||||||
|
.authors(public_keys);
|
||||||
|
|
||||||
|
// Subscribe to metadata events in the bootstrap relays
|
||||||
|
client
|
||||||
|
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Notify GPUI of received contact list
|
||||||
|
tx.send_async(StateEvent::ReceivedContactList).await.ok();
|
||||||
|
}
|
||||||
|
Kind::Metadata => {
|
||||||
|
// Parse metadata from event, default if invalid
|
||||||
|
let metadata =
|
||||||
|
Metadata::from_json(&event.content).unwrap_or_default();
|
||||||
|
|
||||||
|
// Construct nostr profile with metadata and public key
|
||||||
|
let profile = Box::new(Profile::new(event.pubkey, metadata));
|
||||||
|
|
||||||
|
// Notify GPUI of received profile
|
||||||
|
tx.send_async(StateEvent::ReceivedProfile(profile))
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RelayMessage::EndOfStoredEvents(subscription_id) => {
|
RelayMessage::EndOfStoredEvents(_) => {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Handle state events
|
// Handle state events
|
||||||
tasks.push(cx.spawn_in(window, async move |this, cx| {
|
tasks.push(cx.spawn_in(window, async move |_this, cx| {
|
||||||
while let Ok(event) = rx.recv_async().await {
|
while let Ok(event) = rx.recv_async().await {
|
||||||
cx.update(|window, cx| {
|
cx.update(|_window, cx| match event {
|
||||||
// TODO
|
StateEvent::ReceivedContactList => {
|
||||||
|
let account = Account::global(cx);
|
||||||
|
account.update(cx, |this, cx| {
|
||||||
|
this.load_contacts(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
StateEvent::ReceivedProfile(profile) => {
|
||||||
|
let person = PersonRegistry::global(cx);
|
||||||
|
person.update(cx, |this, cx| {
|
||||||
|
this.insert_or_update(&profile, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
// Entity has been released, ignore any errors
|
// Entity has been released, ignore any errors
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -102,14 +157,29 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_app_layout(&self, window: &mut Window, cx: &mut Context<Self>) {
|
fn init_app_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let weak_dock = self.dock.downgrade();
|
let weak_dock = self.dock.downgrade();
|
||||||
|
|
||||||
let sidebar = Arc::new(sidebar::init(window, cx));
|
let sidebar = sidebar::init(window, cx);
|
||||||
let startup = Arc::new(startup::init(window, cx));
|
let startup = Arc::new(startup::init(window, cx));
|
||||||
|
|
||||||
|
self._subscriptions.push(cx.subscribe_in(
|
||||||
|
&sidebar,
|
||||||
|
window,
|
||||||
|
|_this, _sidebar, event: &SidebarEvent, _window, _cx| {
|
||||||
|
match event {
|
||||||
|
SidebarEvent::OpenPublicKey(public_key) => {
|
||||||
|
log::info!("Open public key: {public_key}");
|
||||||
|
}
|
||||||
|
SidebarEvent::OpenRelay(relay) => {
|
||||||
|
log::info!("Open relay url: {relay}")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
// Construct left dock (sidebar)
|
// Construct left dock (sidebar)
|
||||||
let left = DockItem::panel(sidebar);
|
let left = DockItem::panel(Arc::new(sidebar));
|
||||||
|
|
||||||
// Construct center dock
|
// Construct center dock
|
||||||
let center = DockItem::split_with_sizes(
|
let center = DockItem::split_with_sizes(
|
||||||
|
|||||||
19
crates/person/Cargo.toml
Normal file
19
crates/person/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "person"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
common = { path = "../common" }
|
||||||
|
state = { path = "../state" }
|
||||||
|
|
||||||
|
gpui.workspace = true
|
||||||
|
nostr-sdk.workspace = true
|
||||||
|
|
||||||
|
anyhow.workspace = true
|
||||||
|
smallvec.workspace = true
|
||||||
|
smol.workspace = true
|
||||||
|
log.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
117
crates/person/src/lib.rs
Normal file
117
crates/person/src/lib.rs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use gpui::{App, AppContext, AsyncApp, Context, Entity, Global, Task};
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use state::client;
|
||||||
|
|
||||||
|
pub fn init(cx: &mut App) {
|
||||||
|
PersonRegistry::set_global(cx.new(PersonRegistry::new), cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GlobalPersonRegistry(Entity<PersonRegistry>);
|
||||||
|
|
||||||
|
impl Global for GlobalPersonRegistry {}
|
||||||
|
|
||||||
|
/// Person Registry
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PersonRegistry {
|
||||||
|
/// Collection of all profiels
|
||||||
|
pub persons: HashMap<PublicKey, Entity<Profile>>,
|
||||||
|
|
||||||
|
/// Tasks for asynchronous operations
|
||||||
|
_tasks: SmallVec<[Task<()>; 2]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PersonRegistry {
|
||||||
|
/// Retrieve the global person registry state
|
||||||
|
pub fn global(cx: &App) -> Entity<Self> {
|
||||||
|
cx.global::<GlobalPersonRegistry>().0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the global person registry instance
|
||||||
|
pub(crate) fn set_global(state: Entity<Self>, cx: &mut App) {
|
||||||
|
cx.set_global(GlobalPersonRegistry(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new person registry instance
|
||||||
|
pub(crate) fn new(cx: &mut Context<Self>) -> Self {
|
||||||
|
let mut tasks = smallvec![];
|
||||||
|
|
||||||
|
tasks.push(
|
||||||
|
// Load all user profiles from the database
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
let task = Self::init(cx);
|
||||||
|
|
||||||
|
match task.await {
|
||||||
|
Ok(profiles) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.bulk_insert(profiles, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to load user profiles from database: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
persons: HashMap::new(),
|
||||||
|
_tasks: tasks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load all user profiles from the database
|
||||||
|
fn init(cx: &AsyncApp) -> Task<Result<Vec<Profile>, Error>> {
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
let client = client();
|
||||||
|
let filter = Filter::new().kind(Kind::Metadata).limit(200);
|
||||||
|
let events = client.database().query(filter).await?;
|
||||||
|
|
||||||
|
let mut profiles = vec![];
|
||||||
|
|
||||||
|
for event in events.into_iter() {
|
||||||
|
let metadata = Metadata::from_json(event.content).unwrap_or_default();
|
||||||
|
let profile = Profile::new(event.pubkey, metadata);
|
||||||
|
profiles.push(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(profiles)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert batch of persons
|
||||||
|
fn bulk_insert(&mut self, profiles: Vec<Profile>, cx: &mut Context<Self>) {
|
||||||
|
for profile in profiles.into_iter() {
|
||||||
|
self.persons
|
||||||
|
.insert(profile.public_key(), cx.new(|_| profile));
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert or update a person
|
||||||
|
pub fn insert_or_update(&mut self, profile: &Profile, cx: &mut App) {
|
||||||
|
let public_key = profile.public_key();
|
||||||
|
|
||||||
|
if let Some(person) = self.persons.get(&public_key) {
|
||||||
|
person.update(cx, |this, cx| {
|
||||||
|
*this = profile.to_owned();
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.persons
|
||||||
|
.insert(public_key, cx.new(|_| profile.to_owned()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get person
|
||||||
|
pub fn get(&self, public_key: &PublicKey, cx: &App) -> Profile {
|
||||||
|
self.persons
|
||||||
|
.get(public_key)
|
||||||
|
.map(|e| e.read(cx))
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(Profile::new(public_key.to_owned(), Metadata::default()))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum StateEvent {
|
pub enum StateEvent {
|
||||||
//
|
ReceivedContactList,
|
||||||
|
ReceivedProfile(Box<Profile>),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user