feat: add support for basic web of trust #65

Merged
reya merged 9 commits from feat/wot into master 2026-10-20 13:43:39 +00:00
Showing only changes of commit dd81b95e36 - Show all commits
+135 -109
View File
@@ -10,6 +10,7 @@ use gpui::{
StatefulInteractiveElement, Styled, Subscription, Task, WeakEntity, Window, div, px, svg, StatefulInteractiveElement, Styled, Subscription, Task, WeakEntity, Window, div, px, svg,
}; };
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use person::PersonRegistry;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use state::{NostrRegistry, StateEvent}; use state::{NostrRegistry, StateEvent};
use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius}; use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius};
@@ -57,13 +58,14 @@ pub struct GreeterPanel {
/// The coalesced reload scheduled after registry updates /// The coalesced reload scheduled after registry updates
refresh_task: Option<Task<Result<(), Error>>>, refresh_task: Option<Task<Result<(), Error>>>,
/// Event subscriptions /// Event subscriptions
_subscriptions: SmallVec<[Subscription; 2]>, _subscriptions: SmallVec<[Subscription; 3]>,
} }
impl GreeterPanel { impl GreeterPanel {
fn new(dock: WeakEntity<DockArea>, window: &mut Window, cx: &mut Context<Self>) -> Self { fn new(dock: WeakEntity<DockArea>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let chat = ChatRegistry::global(cx); let chat = ChatRegistry::global(cx);
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let wot = PersonRegistry::global(cx).read(cx).wot();
let mut subscriptions = smallvec![]; let mut subscriptions = smallvec![];
@@ -76,6 +78,13 @@ impl GreeterPanel {
}), }),
); );
subscriptions.push(
// Redraw when the web of trust loads or changes
cx.subscribe(&wot, |_, _, _, cx| {
cx.notify();
}),
);
subscriptions.push( subscriptions.push(
// Reload soon after rooms load, messages arrive, or requests change // Reload soon after rooms load, messages arrive, or requests change
cx.observe(&chat, |this, _, cx| { cx.observe(&chat, |this, _, cx| {
@@ -176,120 +185,137 @@ impl GreeterPanel {
})); }));
} }
fn render_requests(&mut self, cx: &mut Context<Self>) -> Option<AnyElement> { fn render_requests(&mut self, cx: &mut Context<Self>) -> AnyElement {
if self.requests.is_empty() { // Spam requests stay out of the greeter entirely
return None; let requests: Vec<_> = self
} .requests
.iter()
.filter(|(room, _)| !room.read(cx).is_spam(cx))
.cloned()
.collect();
let dock = self.dock.clone(); let dock = self.dock.clone();
let rows: Vec<AnyElement> = self let rows: Vec<AnyElement> = if requests.is_empty() {
.requests vec![
.iter()
.take(SECTION_SIZE)
.enumerate()
.map(|(index, (room, preview))| {
let room = room.clone();
let dock = dock.clone();
let name = room.read(cx).display_name(cx);
let picture = room.read(cx).display_image(cx);
let seed = room.read(cx).display_image_seed(cx);
let created_at = room.read(cx).created_at.to_ago();
let preview = preview.clone();
h_flex() h_flex()
.id(format!("request-{index}")) .h_16()
.group("")
.w_full() .w_full()
.h_9() .justify_center()
.px_1p5() .border_1()
.gap_2() .border_dashed()
.border_color(cx.theme().border)
.rounded(px(button_radius())) .rounded(px(button_radius()))
.cursor_pointer() .text_style(TextStyle::Caption)
.hover(|this| this.bg(cx.theme().element_hover)) .text_color(cx.theme().text_faint)
.on_click({ .child("No chat requests")
let room = room.clone(); .into_any_element(),
let dock = dock.clone(); ]
move |_, window, cx| { } else {
open_room(&room, &dock, window, cx); requests
} .iter()
}) .enumerate()
.child( .map(|(index, (room, preview))| {
Avatar::from_source(picture) let room = room.clone();
.seed(seed) let dock = dock.clone();
.small() let name = room.read(cx).display_name(cx);
.flex_shrink_0(), let picture = room.read(cx).display_image(cx);
) let seed = room.read(cx).display_image_seed(cx);
.child( let created_at = room.read(cx).created_at.to_ago();
div() let preview = preview.clone();
.min_w_0()
.truncate()
.text_style(TextStyle::Callout)
.child(name),
)
.child(
div()
.flex_1()
.min_w_0()
.truncate()
.text_style(TextStyle::Callout)
.text_color(cx.theme().text_muted)
.italic()
.child(preview),
)
.child(
div()
.relative()
.flex_shrink_0()
.h_6()
.flex()
.items_center()
.justify_end()
.child(
div()
.absolute()
.left_0()
.right_0()
.top_0()
.bottom_0()
.group_hover("", |this| this.invisible())
.child(
div()
.text_align(gpui::TextAlign::Right)
.text_style(TextStyle::Caption)
.text_color(cx.theme().text_faint)
.child(created_at),
),
)
.child(
Button::new(format!("response-{index}"))
.label("Response")
.xsmall()
.ghost()
.invisible()
.group_hover("", |this| this.visible())
.on_click({
let room = room.clone();
let dock = dock.clone();
move |_, window, cx| {
cx.stop_propagation();
open_room(&room, &dock, window, cx);
}
}),
),
)
.into_any_element()
})
.collect();
Some( h_flex()
GroupBox::new() .id(format!("request-{index}"))
.id("requests") .group("")
.title("Requests") .w_full()
.fill() .h_9()
.child(v_flex().gap_0().w_full().children(rows)) .px_1p5()
.into_any_element(), .gap_2()
) .rounded(px(button_radius()))
.cursor_pointer()
.hover(|this| this.bg(cx.theme().element_hover))
.on_click({
let room = room.clone();
let dock = dock.clone();
move |_, window, cx| {
open_room(&room, &dock, window, cx);
}
})
.child(
Avatar::from_source(picture)
.seed(seed)
.small()
.flex_shrink_0(),
)
.child(
div()
.min_w_0()
.truncate()
.text_style(TextStyle::Callout)
.child(name),
)
.child(
div()
.flex_1()
.min_w_0()
.truncate()
.text_style(TextStyle::Callout)
.text_color(cx.theme().text_muted)
.italic()
.child(preview),
)
.child(
div()
.relative()
.flex_shrink_0()
.h_6()
.flex()
.items_center()
.justify_end()
.child(
div()
.absolute()
.left_0()
.right_0()
.top_0()
.bottom_0()
.group_hover("", |this| this.invisible())
.child(
div()
.text_align(gpui::TextAlign::Right)
.text_style(TextStyle::Caption)
.text_color(cx.theme().text_faint)
.child(created_at),
),
)
.child(
Button::new(format!("response-{index}"))
.label("Response")
.xsmall()
.ghost()
.invisible()
.group_hover("", |this| this.visible())
.on_click({
let room = room.clone();
let dock = dock.clone();
move |_, window, cx| {
cx.stop_propagation();
open_room(&room, &dock, window, cx);
}
}),
),
)
.into_any_element()
})
.collect()
};
GroupBox::new()
.id("requests")
.title("Requests")
.fill()
.child(v_flex().gap_0().w_full().children(rows))
.into_any_element()
} }
fn render_follow_ups(&mut self, cx: &mut Context<Self>) -> Option<AnyElement> { fn render_follow_ups(&mut self, cx: &mut Context<Self>) -> Option<AnyElement> {
@@ -503,7 +529,7 @@ impl Render for GreeterPanel {
.size_full() .size_full()
.p_2() .p_2()
.gap_6() .gap_6()
.children(self.render_requests(cx)) .child(self.render_requests(cx))
.children(self.render_follow_ups(cx)) .children(self.render_follow_ups(cx))
.child(self.render_find_friends(cx)), .child(self.render_find_friends(cx)),
) )