feat: bind user metadata to per-view directories (#64)
Reviewed-on: reya/coop#64
This commit is contained in:
@@ -10,7 +10,7 @@ use gpui::{
|
||||
Window, div, px, uniform_list,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
use person::Directory;
|
||||
use settings::AppSettings;
|
||||
use state::NostrRegistry;
|
||||
use theme::{ActiveTheme, TextStyle, Typeset as _};
|
||||
@@ -39,6 +39,8 @@ pub struct NewChat {
|
||||
input: Entity<InputState>,
|
||||
/// Current user's contacts
|
||||
contacts: Option<Vec<PublicKey>>,
|
||||
/// Person directory for contacts and selected members
|
||||
directory: Entity<Directory>,
|
||||
/// Public keys selected as members of the chat
|
||||
members: Vec<PublicKey>,
|
||||
/// Whether a NIP-05 address is being resolved
|
||||
@@ -69,6 +71,7 @@ impl NewChat {
|
||||
dock,
|
||||
input,
|
||||
contacts: None,
|
||||
directory: Directory::new(cx),
|
||||
members: Vec::new(),
|
||||
resolving: false,
|
||||
error: None,
|
||||
@@ -112,6 +115,9 @@ impl NewChat {
|
||||
match task.await {
|
||||
Ok(contacts) => {
|
||||
this.update(cx, |this, cx| {
|
||||
this.directory.update(cx, |directory, cx| {
|
||||
directory.track(contacts.iter().copied(), cx)
|
||||
});
|
||||
this.contacts = Some(contacts);
|
||||
cx.notify();
|
||||
})?;
|
||||
@@ -133,7 +139,6 @@ impl NewChat {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let query = self.input.read(cx).value().trim().to_lowercase();
|
||||
|
||||
if query.is_empty() {
|
||||
@@ -146,7 +151,7 @@ impl NewChat {
|
||||
if public_key.to_hex().to_lowercase().contains(&query) {
|
||||
return true;
|
||||
}
|
||||
let name = persons.read(cx).get(public_key, cx).name();
|
||||
let name = self.directory.read(cx).person(public_key, cx).name();
|
||||
name.to_lowercase().contains(&query)
|
||||
})
|
||||
.cloned()
|
||||
@@ -318,7 +323,6 @@ impl NewChat {
|
||||
contacts: &[PublicKey],
|
||||
cx: &mut Context<Self>,
|
||||
) -> Vec<AnyElement> {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let start = range.start;
|
||||
|
||||
contacts
|
||||
@@ -327,7 +331,7 @@ impl NewChat {
|
||||
.flatten()
|
||||
.enumerate()
|
||||
.map(|(offset, public_key)| {
|
||||
let profile = persons.read(cx).get(public_key, cx);
|
||||
let profile = self.directory.read(cx).person(public_key, cx);
|
||||
let public_key = *public_key;
|
||||
|
||||
Nav::new(start + offset)
|
||||
@@ -350,13 +354,11 @@ impl NewChat {
|
||||
}
|
||||
|
||||
fn render_members(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
|
||||
h_flex()
|
||||
.flex_wrap()
|
||||
.gap_1()
|
||||
.children(self.members.iter().map(|public_key| {
|
||||
let profile = persons.read(cx).get(public_key, cx);
|
||||
let profile = self.directory.read(cx).person(public_key, cx);
|
||||
let public_key = *public_key;
|
||||
let id = SharedString::from(format!("m-{}", public_key.to_hex()));
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use gpui::{
|
||||
};
|
||||
use instant::Duration;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::{Person, PersonRegistry, shorten_pubkey};
|
||||
use person::{Directory, Person, PersonRegistry, shorten_pubkey};
|
||||
use settings::AppSettings;
|
||||
use state::{NostrRegistry, upload};
|
||||
use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius};
|
||||
@@ -34,6 +34,8 @@ struct ProfileDialog {
|
||||
public_key: PublicKey,
|
||||
/// Whether the profile belongs to the signed-in user
|
||||
is_current_user: bool,
|
||||
/// Person directory for the profile
|
||||
directory: Entity<Directory>,
|
||||
/// Whether the metadata inputs are shown
|
||||
editing: bool,
|
||||
/// User's name text input
|
||||
@@ -74,9 +76,15 @@ impl ProfileDialog {
|
||||
.placeholder("A short introduce about you.")
|
||||
});
|
||||
|
||||
let directory = Directory::new(cx);
|
||||
directory.update(cx, |directory, cx| {
|
||||
directory.track([public_key], cx);
|
||||
});
|
||||
|
||||
Self {
|
||||
public_key,
|
||||
is_current_user,
|
||||
directory,
|
||||
editing: false,
|
||||
name_input,
|
||||
avatar_input,
|
||||
@@ -90,8 +98,7 @@ impl ProfileDialog {
|
||||
}
|
||||
|
||||
fn profile(&self, cx: &App) -> Person {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
persons.read(cx).get(&self.public_key, cx)
|
||||
self.directory.read(cx).person(&self.public_key, cx)
|
||||
}
|
||||
|
||||
fn set_profile(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -238,9 +245,8 @@ impl ProfileDialog {
|
||||
}
|
||||
|
||||
fn save(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let public_key = self.public_key;
|
||||
let old_metadata = persons.read(cx).get(&public_key, cx).metadata();
|
||||
let old_metadata = self.directory.read(cx).person(&public_key, cx).metadata();
|
||||
|
||||
// Extract all new metadata fields
|
||||
let avatar = self.avatar_input.read(cx).value();
|
||||
@@ -274,9 +280,9 @@ impl ProfileDialog {
|
||||
match task.await {
|
||||
Ok(_) => {
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
// Update the registry
|
||||
persons.update(cx, |this, cx| {
|
||||
this.insert(Person::new(public_key, new_metadata), cx);
|
||||
// Publish the new metadata to every directory
|
||||
PersonRegistry::global(cx).update(cx, |registry, cx| {
|
||||
registry.insert(Person::new(public_key, new_metadata), cx);
|
||||
});
|
||||
|
||||
this.set_updating(false, cx);
|
||||
|
||||
@@ -9,7 +9,7 @@ use gpui::{
|
||||
};
|
||||
use instant::Duration;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::{Person, PersonRegistry, shorten_pubkey};
|
||||
use person::{Directory, Person, shorten_pubkey};
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use state::{BOOTSTRAP_RELAYS, NostrAddress, NostrRegistry, TIMEOUT};
|
||||
use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius};
|
||||
@@ -33,6 +33,8 @@ pub struct Screening {
|
||||
last_active: Option<Timestamp>,
|
||||
/// All mutual contacts of the person being screened.
|
||||
mutual_contacts: Vec<PublicKey>,
|
||||
/// Person directory for the person and their mutual contacts
|
||||
directory: Entity<Directory>,
|
||||
tasks: SmallVec<[Task<()>; 3]>,
|
||||
_subscriptions: SmallVec<[Subscription; 1]>,
|
||||
}
|
||||
@@ -53,12 +55,16 @@ impl Screening {
|
||||
this.verify_identifier(cx);
|
||||
});
|
||||
|
||||
let directory = Directory::new(cx);
|
||||
directory.update(cx, |directory, cx| directory.track([public_key], cx));
|
||||
|
||||
Self {
|
||||
public_key,
|
||||
verified: false,
|
||||
followed: false,
|
||||
last_active: None,
|
||||
mutual_contacts: vec![],
|
||||
directory,
|
||||
tasks: smallvec![],
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
@@ -208,8 +214,7 @@ impl Screening {
|
||||
}
|
||||
|
||||
fn profile(&self, cx: &Context<Self>) -> Person {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
persons.read(cx).get(&self.public_key, cx)
|
||||
self.directory.read(cx).person(&self.public_key, cx)
|
||||
}
|
||||
|
||||
fn address(&self, cx: &Context<Self>) -> Option<Nip05Address> {
|
||||
@@ -260,22 +265,23 @@ impl Screening {
|
||||
|
||||
fn mutual_contacts(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let contacts = self.mutual_contacts.clone();
|
||||
let directory = self.directory.clone();
|
||||
|
||||
window.open_dialog(cx, move |this, _window, _cx| {
|
||||
let contacts = contacts.clone();
|
||||
let directory = directory.clone();
|
||||
let total = contacts.len();
|
||||
|
||||
this.title("Mutual contacts").child(
|
||||
v_flex().gap_1().pb_2().child(
|
||||
uniform_list("contacts", total, move |range, _window, cx| {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let mut items = Vec::with_capacity(total);
|
||||
|
||||
for ix in range {
|
||||
let Some(contact) = contacts.get(ix) else {
|
||||
continue;
|
||||
};
|
||||
let profile = persons.read(cx).get(contact, cx);
|
||||
let profile = directory.read(cx).person(contact, cx);
|
||||
|
||||
items.push(
|
||||
h_flex()
|
||||
|
||||
@@ -9,7 +9,7 @@ use gpui::{
|
||||
};
|
||||
use instant::Duration;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
use person::Directory;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use state::NostrRegistry;
|
||||
use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius, panel_radius};
|
||||
@@ -41,6 +41,9 @@ pub struct ContactListPanel {
|
||||
/// All contacts
|
||||
contacts: HashSet<PublicKey>,
|
||||
|
||||
/// Person directory for the contacts
|
||||
directory: Entity<Directory>,
|
||||
|
||||
/// Event subscriptions
|
||||
_subscriptions: SmallVec<[Subscription; 1]>,
|
||||
|
||||
@@ -73,6 +76,7 @@ impl ContactListPanel {
|
||||
input,
|
||||
updating: false,
|
||||
contacts: HashSet::new(),
|
||||
directory: Directory::new(cx),
|
||||
error: None,
|
||||
_subscriptions: subscriptions,
|
||||
tasks: vec![],
|
||||
@@ -110,6 +114,9 @@ impl ContactListPanel {
|
||||
|
||||
// Update state
|
||||
this.update(cx, |this, cx| {
|
||||
this.directory.update(cx, |directory, cx| {
|
||||
directory.track(public_keys.iter().copied(), cx)
|
||||
});
|
||||
this.contacts.extend(public_keys);
|
||||
cx.notify();
|
||||
})?;
|
||||
@@ -123,6 +130,8 @@ impl ContactListPanel {
|
||||
|
||||
if let Ok(public_key) = PublicKey::parse(&value) {
|
||||
if self.contacts.insert(public_key) {
|
||||
self.directory
|
||||
.update(cx, |directory, cx| directory.track([public_key], cx));
|
||||
self.input.update(cx, |this, cx| {
|
||||
this.set_value("", window, cx);
|
||||
});
|
||||
@@ -218,11 +227,10 @@ impl ContactListPanel {
|
||||
}
|
||||
|
||||
fn render_list_items(&mut self, cx: &mut Context<Self>) -> Vec<impl IntoElement> {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let mut items = Vec::new();
|
||||
|
||||
for (ix, public_key) in self.contacts.iter().enumerate() {
|
||||
let profile = persons.read(cx).get(public_key, cx);
|
||||
let profile = self.directory.read(cx).person(public_key, cx);
|
||||
|
||||
items.push(
|
||||
h_flex()
|
||||
|
||||
@@ -8,7 +8,6 @@ use gpui::{
|
||||
};
|
||||
use nip29::Group;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
use settings::AppSettings;
|
||||
use state::NostrRegistry;
|
||||
use theme::{ActiveTheme, TextStyle, Typeset as _};
|
||||
@@ -51,6 +50,19 @@ impl Sidebar {
|
||||
pub(super) fn show_group(&mut self, group: &Entity<Group>, cx: &mut Context<Self>) {
|
||||
self.group = Some(group.downgrade());
|
||||
self.group_open = true;
|
||||
|
||||
let group = group.read(cx);
|
||||
let mut keys: Vec<PublicKey> = group.members().iter().copied().collect();
|
||||
keys.extend(
|
||||
group
|
||||
.admins()
|
||||
.entries()
|
||||
.iter()
|
||||
.map(|(public_key, _)| *public_key),
|
||||
);
|
||||
self.directory
|
||||
.update(cx, |directory, cx| directory.track(keys, cx));
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -253,9 +265,8 @@ impl Sidebar {
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
||||
let persons = PersonRegistry::global(cx);
|
||||
|
||||
let person = persons.read(cx).get(public_key, cx);
|
||||
let person = self.directory.read(cx).person(public_key, cx);
|
||||
let group = group.clone();
|
||||
|
||||
let public_key = *public_key;
|
||||
|
||||
@@ -5,7 +5,6 @@ use gpui::{
|
||||
Stateful, Styled, div, px,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
use theme::TABBAR_HEIGHT;
|
||||
use ui::avatar::Avatar;
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
@@ -22,8 +21,7 @@ impl Sidebar {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Stateful<Div> {
|
||||
let updater = AutoUpdater::try_global(cx);
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let profile = persons.read(cx).get(current_user, cx);
|
||||
let profile = self.directory.read(cx).person(current_user, cx);
|
||||
let avatar = profile.avatar();
|
||||
let avatar_seed = profile.avatar_seed();
|
||||
|
||||
|
||||
@@ -69,6 +69,9 @@ impl Sidebar {
|
||||
match task.await {
|
||||
Ok(contacts) => {
|
||||
this.update(cx, |this, cx| {
|
||||
this.directory.update(cx, |directory, cx| {
|
||||
directory.track(contacts.iter().copied(), cx)
|
||||
});
|
||||
this.contacts = Some(contacts);
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
@@ -13,6 +13,7 @@ use gpui::{
|
||||
use instant::Duration;
|
||||
use nip29::{Group, GroupKey, GroupsEvent, GroupsRegistry};
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::Directory;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use state::{FIND_DELAY, NostrRegistry, StateEvent};
|
||||
use theme::{ActiveTheme, TABBAR_HEIGHT, TextStyle, Typeset as _};
|
||||
@@ -61,6 +62,8 @@ pub struct Sidebar {
|
||||
contacts_open: bool,
|
||||
/// The signed-in user's contacts, loaded when the contact picker opens.
|
||||
contacts: Option<Vec<PublicKey>>,
|
||||
/// Person directory for contacts, search hits, and group members
|
||||
directory: Entity<Directory>,
|
||||
/// Contacts selected in the contact picker.
|
||||
selected_contacts: HashSet<PublicKey>,
|
||||
/// Scroll state of the contact picker's list.
|
||||
@@ -139,15 +142,27 @@ impl Sidebar {
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
cx.subscribe_in(&nostr, window, |this, _, event, window, cx| {
|
||||
if let StateEvent::NoSigner = event {
|
||||
this.banner = onboarding::pick_banner();
|
||||
if this.search_open {
|
||||
this.toggle_search(window, cx);
|
||||
cx.subscribe_in(
|
||||
&nostr,
|
||||
window,
|
||||
|this, state, event, window, cx| match event {
|
||||
StateEvent::NoSigner => {
|
||||
this.banner = onboarding::pick_banner();
|
||||
if this.search_open {
|
||||
this.toggle_search(window, cx);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}),
|
||||
StateEvent::SignerChanged => {
|
||||
if let Some(current_user) = state.read(cx).current_user() {
|
||||
this.directory.update(cx, |directory, cx| {
|
||||
directory.track([current_user], cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
subscriptions.push(cx.subscribe_in(
|
||||
@@ -196,6 +211,7 @@ impl Sidebar {
|
||||
chats_open: None,
|
||||
contacts_open: false,
|
||||
contacts: None,
|
||||
directory: Directory::new(cx),
|
||||
selected_contacts: HashSet::new(),
|
||||
contacts_scroll: UniformListScrollHandle::new(),
|
||||
requests_open: false,
|
||||
|
||||
@@ -9,7 +9,6 @@ use gpui::{
|
||||
};
|
||||
use nip29::Group;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
use settings::AppSettings;
|
||||
use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius};
|
||||
use ui::avatar::Avatar;
|
||||
@@ -196,8 +195,7 @@ impl Sidebar {
|
||||
.into_any_element()
|
||||
}
|
||||
SidebarRow::Contact { public_key } => {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let person = persons.read(cx).get(public_key, cx);
|
||||
let person = self.directory.read(cx).person(public_key, cx);
|
||||
let selected = self.selected_contacts.contains(public_key);
|
||||
let sidebar = cx.entity().downgrade();
|
||||
let public_key = public_key.to_owned();
|
||||
@@ -224,8 +222,7 @@ impl Sidebar {
|
||||
.into_any_element()
|
||||
}
|
||||
SidebarRow::Profile { public_key } => {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let person = persons.read(cx).get(public_key, cx);
|
||||
let person = self.directory.read(cx).person(public_key, cx);
|
||||
let public_key = public_key.to_owned();
|
||||
|
||||
Nav::new(index)
|
||||
@@ -330,7 +327,7 @@ impl Sidebar {
|
||||
.into_any_element()
|
||||
}
|
||||
SidebarRow::Message { hit } => {
|
||||
let person = PersonRegistry::global(cx).read(cx).get(&hit.author, cx);
|
||||
let person = self.directory.read(cx).person(&hit.author, cx);
|
||||
let name = hit.room.read(cx).display_name(cx);
|
||||
let detail = format!("{} · {}", name, hit.created_at.to_ago());
|
||||
let dock = self.dock.clone();
|
||||
|
||||
@@ -106,6 +106,14 @@ impl Sidebar {
|
||||
.chain(groups.into_iter().map(SearchResult::Group))
|
||||
.chain(messages.into_iter().map(SearchResult::Message))
|
||||
.collect();
|
||||
|
||||
let authors = self.results.iter().filter_map(|result| match result {
|
||||
SearchResult::Message(hit) => Some(hit.author),
|
||||
_ => None,
|
||||
});
|
||||
self.directory
|
||||
.update(cx, |directory, cx| directory.track(authors, cx));
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -133,7 +141,7 @@ impl Sidebar {
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.global_finding = false;
|
||||
this.global_results = Some(match result {
|
||||
let keys: Vec<PublicKey> = match result {
|
||||
Ok(profiles) => {
|
||||
// Relays may return the same profile more than once
|
||||
let mut seen = HashSet::new();
|
||||
@@ -146,7 +154,12 @@ impl Sidebar {
|
||||
log::debug!("Global search failed: {error}");
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
this.directory.update(cx, |directory, cx| {
|
||||
directory.track(keys.iter().copied(), cx)
|
||||
});
|
||||
this.global_results = Some(keys);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
|
||||
Reference in New Issue
Block a user