wip
This commit is contained in:
@@ -253,6 +253,11 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the loading status of the chat registry
|
||||||
|
pub fn loading(&self) -> bool {
|
||||||
|
self.loading
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the loading status of the chat registry
|
/// Set the loading status of the chat registry
|
||||||
pub fn set_loading(&mut self, loading: bool, cx: &mut Context<Self>) {
|
pub fn set_loading(&mut self, loading: bool, cx: &mut Context<Self>) {
|
||||||
self.loading = loading;
|
self.loading = loading;
|
||||||
@@ -511,7 +516,7 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set this room is ongoing if the new message is from current user
|
// Set this room is ongoing if the new message is from current user
|
||||||
if author == nostr.read(cx).identity(cx).public_key() {
|
if author == nostr.read(cx).identity().read(cx).public_key() {
|
||||||
this.set_ongoing(cx);
|
this.set_ongoing(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ impl Room {
|
|||||||
pub fn display_member(&self, cx: &App) -> Profile {
|
pub fn display_member(&self, cx: &App) -> Profile {
|
||||||
let persons = PersonRegistry::global(cx);
|
let persons = PersonRegistry::global(cx);
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let public_key = nostr.read(cx).identity(cx).public_key();
|
let public_key = nostr.read(cx).identity().read(cx).public_key();
|
||||||
|
|
||||||
let target_member = self
|
let target_member = self
|
||||||
.members
|
.members
|
||||||
@@ -369,7 +369,7 @@ impl Room {
|
|||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
|
||||||
// Get current user
|
// Get current user
|
||||||
let public_key = nostr.read(cx).identity(cx).public_key();
|
let public_key = nostr.read(cx).identity().read(cx).public_key();
|
||||||
|
|
||||||
// Get room's subject
|
// Get room's subject
|
||||||
let subject = self.subject.clone();
|
let subject = self.subject.clone();
|
||||||
@@ -438,7 +438,7 @@ impl Room {
|
|||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
|
|
||||||
// Get current user's public key and relays
|
// Get current user's public key and relays
|
||||||
let current_user = nostr.read(cx).identity(cx).public_key();
|
let current_user = nostr.read(cx).identity().read(cx).public_key();
|
||||||
let current_user_relays = nostr.read(cx).messaging_relays(¤t_user, cx);
|
let current_user_relays = nostr.read(cx).messaging_relays(¤t_user, cx);
|
||||||
|
|
||||||
let rumor = rumor.to_owned();
|
let rumor = rumor.to_owned();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::collections::HashSet;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub use actions::*;
|
pub use actions::*;
|
||||||
use chat::{Message, RenderedMessage, Room, RoomKind, RoomSignal, SendOptions, SendReport};
|
use chat::{Message, RenderedMessage, Room, RoomKind, RoomSignal, SendReport};
|
||||||
use common::{nip96_upload, RenderedProfile, RenderedTimestamp};
|
use common::{nip96_upload, RenderedProfile, RenderedTimestamp};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -59,7 +59,6 @@ pub struct ChatPanel {
|
|||||||
|
|
||||||
// New Message
|
// New Message
|
||||||
input: Entity<InputState>,
|
input: Entity<InputState>,
|
||||||
options: Entity<SendOptions>,
|
|
||||||
replies_to: Entity<HashSet<EventId>>,
|
replies_to: Entity<HashSet<EventId>>,
|
||||||
|
|
||||||
// Media Attachment
|
// Media Attachment
|
||||||
@@ -87,7 +86,6 @@ impl ChatPanel {
|
|||||||
|
|
||||||
let attachments = cx.new(|_| vec![]);
|
let attachments = cx.new(|_| vec![]);
|
||||||
let replies_to = cx.new(|_| HashSet::new());
|
let replies_to = cx.new(|_| HashSet::new());
|
||||||
let options = cx.new(|_| SendOptions::default());
|
|
||||||
|
|
||||||
let id = room.read(cx).id.to_string().into();
|
let id = room.read(cx).id.to_string().into();
|
||||||
let messages = BTreeSet::from([Message::system()]);
|
let messages = BTreeSet::from([Message::system()]);
|
||||||
@@ -145,18 +143,11 @@ impl ChatPanel {
|
|||||||
cx.subscribe_in(&room, window, move |this, _, signal, window, cx| {
|
cx.subscribe_in(&room, window, move |this, _, signal, window, cx| {
|
||||||
match signal {
|
match signal {
|
||||||
RoomSignal::NewMessage((gift_wrap_id, event)) => {
|
RoomSignal::NewMessage((gift_wrap_id, event)) => {
|
||||||
let nostr = NostrRegistry::global(cx);
|
|
||||||
let tracker = nostr.read(cx).tracker();
|
|
||||||
let gift_wrap_id = gift_wrap_id.to_owned();
|
|
||||||
let message = Message::user(event.clone());
|
let message = Message::user(event.clone());
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let tracker = tracker.read().await;
|
|
||||||
|
|
||||||
this.update_in(cx, |this, _window, cx| {
|
this.update_in(cx, |this, _window, cx| {
|
||||||
if !tracker.sent_ids().contains(&gift_wrap_id) {
|
this.insert_message(message, false, cx);
|
||||||
this.insert_message(message, false, cx);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
})
|
})
|
||||||
@@ -189,7 +180,6 @@ impl ChatPanel {
|
|||||||
input,
|
input,
|
||||||
replies_to,
|
replies_to,
|
||||||
attachments,
|
attachments,
|
||||||
options,
|
|
||||||
rendered_texts_by_id: BTreeMap::new(),
|
rendered_texts_by_id: BTreeMap::new(),
|
||||||
reports_by_id: BTreeMap::new(),
|
reports_by_id: BTreeMap::new(),
|
||||||
uploading: false,
|
uploading: false,
|
||||||
@@ -271,14 +261,13 @@ impl ChatPanel {
|
|||||||
|
|
||||||
// Get the current room entity
|
// Get the current room entity
|
||||||
let room = self.room.read(cx);
|
let room = self.room.read(cx);
|
||||||
let opts = self.options.read(cx);
|
|
||||||
|
|
||||||
// Create a temporary message for optimistic update
|
// Create a temporary message for optimistic update
|
||||||
let rumor = room.create_message(&content, replies.as_ref(), cx);
|
let rumor = room.create_message(&content, replies.as_ref(), cx);
|
||||||
let rumor_id = rumor.id.unwrap();
|
let rumor_id = rumor.id.unwrap();
|
||||||
|
|
||||||
// Create a task for sending the message in the background
|
// Create a task for sending the message in the background
|
||||||
let send_message = room.send_message(&rumor, opts, cx);
|
let send_message = room.send_message(&rumor, cx);
|
||||||
|
|
||||||
// Optimistically update message list
|
// Optimistically update message list
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
@@ -440,10 +429,6 @@ impl ChatPanel {
|
|||||||
persons.read(cx).get_person(public_key, cx)
|
persons.read(cx).get_person(public_key, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signer_kind(&self, cx: &App) -> SignerKind {
|
|
||||||
self.options.read(cx).signer_kind
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scroll_to(&self, id: EventId) {
|
fn scroll_to(&self, id: EventId) {
|
||||||
if let Some(ix) = self.messages.iter().position(|m| {
|
if let Some(ix) = self.messages.iter().position(|m| {
|
||||||
if let Message::User(msg) = m {
|
if let Message::User(msg) = m {
|
||||||
@@ -1235,71 +1220,6 @@ impl ChatPanel {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_open_seen_on(&mut self, ev: &SeenOn, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let id = ev.0;
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
|
||||||
let client = nostr.read(cx).client();
|
|
||||||
let tracker = nostr.read(cx).tracker();
|
|
||||||
|
|
||||||
let task: Task<Result<Vec<RelayUrl>, Error>> = cx.background_spawn(async move {
|
|
||||||
let tracker = tracker.read().await;
|
|
||||||
let mut relays: Vec<RelayUrl> = vec![];
|
|
||||||
|
|
||||||
let filter = Filter::new()
|
|
||||||
.kind(Kind::ApplicationSpecificData)
|
|
||||||
.event(id)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
|
||||||
if let Some(Ok(id)) = event.tags.identifier().map(EventId::parse) {
|
|
||||||
if let Some(urls) = tracker.seen_on_relays.get(&id).cloned() {
|
|
||||||
relays.extend(urls);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(relays)
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.spawn_in(window, async move |_, cx| {
|
|
||||||
if let Ok(urls) = task.await {
|
|
||||||
cx.update(|window, cx| {
|
|
||||||
window.open_modal(cx, move |this, _window, cx| {
|
|
||||||
this.show_close(true)
|
|
||||||
.title(SharedString::from("Seen on"))
|
|
||||||
.child(v_flex().pb_4().gap_2().children({
|
|
||||||
let mut items = Vec::with_capacity(urls.len());
|
|
||||||
|
|
||||||
for url in urls.clone().into_iter() {
|
|
||||||
items.push(
|
|
||||||
h_flex()
|
|
||||||
.h_8()
|
|
||||||
.px_2()
|
|
||||||
.bg(cx.theme().elevated_surface_background)
|
|
||||||
.rounded(cx.theme().radius)
|
|
||||||
.font_semibold()
|
|
||||||
.text_xs()
|
|
||||||
.child(SharedString::from(url.to_string())),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
items
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_set_encryption(&mut self, ev: &SetSigner, _: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
self.options.update(cx, move |this, cx| {
|
|
||||||
this.signer_kind = ev.0;
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Panel for ChatPanel {
|
impl Panel for ChatPanel {
|
||||||
@@ -1339,11 +1259,7 @@ impl Focusable for ChatPanel {
|
|||||||
|
|
||||||
impl Render for ChatPanel {
|
impl Render for ChatPanel {
|
||||||
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 {
|
||||||
let kind = self.signer_kind(cx);
|
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.on_action(cx.listener(Self::on_open_seen_on))
|
|
||||||
.on_action(cx.listener(Self::on_set_encryption))
|
|
||||||
.image_cache(self.image_cache.clone())
|
.image_cache(self.image_cache.clone())
|
||||||
.size_full()
|
.size_full()
|
||||||
.child(
|
.child(
|
||||||
@@ -1398,31 +1314,7 @@ impl Render for ChatPanel {
|
|||||||
.large(),
|
.large(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(TextInput::new(&self.input))
|
.child(TextInput::new(&self.input)),
|
||||||
.child(
|
|
||||||
Button::new("encryptions")
|
|
||||||
.icon(IconName::Encryption)
|
|
||||||
.ghost()
|
|
||||||
.large()
|
|
||||||
.popup_menu(move |this, _window, _cx| {
|
|
||||||
this.label("Encrypt by:")
|
|
||||||
.menu_with_check(
|
|
||||||
"Encryption Key",
|
|
||||||
matches!(kind, SignerKind::Encryption),
|
|
||||||
Box::new(SetSigner(SignerKind::Encryption)),
|
|
||||||
)
|
|
||||||
.menu_with_check(
|
|
||||||
"User's Identity",
|
|
||||||
matches!(kind, SignerKind::User),
|
|
||||||
Box::new(SetSigner(SignerKind::User)),
|
|
||||||
)
|
|
||||||
.menu_with_check(
|
|
||||||
"Auto",
|
|
||||||
matches!(kind, SignerKind::Auto),
|
|
||||||
Box::new(SetSigner(SignerKind::Auto)),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use account::Account;
|
|
||||||
use auto_update::{AutoUpdateStatus, AutoUpdater};
|
use auto_update::{AutoUpdateStatus, AutoUpdater};
|
||||||
use chat::{ChatEvent, ChatRegistry};
|
use chat::{ChatEvent, ChatRegistry};
|
||||||
use chat_ui::{CopyPublicKey, OpenPublicKey};
|
use chat_ui::{CopyPublicKey, OpenPublicKey};
|
||||||
use common::{RenderedProfile, DEFAULT_SIDEBAR_WIDTH};
|
use common::{RenderedProfile, DEFAULT_SIDEBAR_WIDTH};
|
||||||
use encryption::Encryption;
|
|
||||||
use encryption_ui::EncryptionPanel;
|
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
deferred, div, px, relative, rems, App, AppContext, Axis, ClipboardItem, Context, Entity,
|
deferred, div, px, relative, rems, App, AppContext, Axis, ClipboardItem, Context, Entity,
|
||||||
@@ -19,6 +16,7 @@ use person::PersonRegistry;
|
|||||||
use relay_auth::RelayAuth;
|
use relay_auth::RelayAuth;
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use state::NostrRegistry;
|
||||||
use theme::{ActiveTheme, Theme, ThemeMode, ThemeRegistry};
|
use theme::{ActiveTheme, Theme, ThemeMode, ThemeRegistry};
|
||||||
use title_bar::TitleBar;
|
use title_bar::TitleBar;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
@@ -27,7 +25,6 @@ use ui::dock_area::dock::DockPlacement;
|
|||||||
use ui::dock_area::panel::PanelView;
|
use ui::dock_area::panel::PanelView;
|
||||||
use ui::dock_area::{ClosePanel, DockArea, DockItem};
|
use ui::dock_area::{ClosePanel, DockArea, DockItem};
|
||||||
use ui::modal::ModalButtonProps;
|
use ui::modal::ModalButtonProps;
|
||||||
use ui::popover::{Popover, PopoverContent};
|
|
||||||
use ui::popup_menu::PopupMenuExt;
|
use ui::popup_menu::PopupMenuExt;
|
||||||
use ui::{h_flex, v_flex, ContextModal, IconName, Root, Sizable, StyledExt};
|
use ui::{h_flex, v_flex, ContextModal, IconName, Root, Sizable, StyledExt};
|
||||||
|
|
||||||
@@ -61,9 +58,6 @@ pub struct ChatSpace {
|
|||||||
/// App's Dock Area
|
/// App's Dock Area
|
||||||
dock: Entity<DockArea>,
|
dock: Entity<DockArea>,
|
||||||
|
|
||||||
/// App's Encryption Panel
|
|
||||||
encryption_panel: Entity<EncryptionPanel>,
|
|
||||||
|
|
||||||
/// Determines if the chat space is ready to use
|
/// Determines if the chat space is ready to use
|
||||||
ready: bool,
|
ready: bool,
|
||||||
|
|
||||||
@@ -73,13 +67,14 @@ pub struct ChatSpace {
|
|||||||
|
|
||||||
impl ChatSpace {
|
impl ChatSpace {
|
||||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
let keystore = KeyStore::global(cx);
|
let keystore = KeyStore::global(cx);
|
||||||
let account = Account::global(cx);
|
|
||||||
|
|
||||||
let title_bar = cx.new(|_| TitleBar::new());
|
let title_bar = cx.new(|_| TitleBar::new());
|
||||||
let dock = cx.new(|cx| DockArea::new(window, cx));
|
let dock = cx.new(|cx| DockArea::new(window, cx));
|
||||||
let encryption_panel = encryption_ui::init(window, cx);
|
|
||||||
|
let identity = nostr.read(cx).identity();
|
||||||
|
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
|
|
||||||
@@ -92,8 +87,8 @@ impl ChatSpace {
|
|||||||
|
|
||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
// Observe account entity changes
|
// Observe account entity changes
|
||||||
cx.observe_in(&account, window, move |this, state, window, cx| {
|
cx.observe_in(&identity, window, move |this, state, window, cx| {
|
||||||
if !this.ready && state.read(cx).has_account() {
|
if !this.ready && state.read(cx).has_public_key() {
|
||||||
this.set_default_layout(window, cx);
|
this.set_default_layout(window, cx);
|
||||||
|
|
||||||
// Load all chat room in the database if available
|
// Load all chat room in the database if available
|
||||||
@@ -175,7 +170,6 @@ impl ChatSpace {
|
|||||||
Self {
|
Self {
|
||||||
dock,
|
dock,
|
||||||
title_bar,
|
title_bar,
|
||||||
encryption_panel,
|
|
||||||
ready: false,
|
ready: false,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
@@ -447,11 +441,11 @@ impl ChatSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn titlebar_left(&mut self, _window: &mut Window, cx: &Context<Self>) -> impl IntoElement {
|
fn titlebar_left(&mut self, _window: &mut Window, cx: &Context<Self>) -> impl IntoElement {
|
||||||
let account = Account::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
let status = chat.read(cx).loading;
|
let status = chat.read(cx).loading();
|
||||||
|
|
||||||
if !account.read(cx).has_account() {
|
if !nostr.read(cx).identity().read(cx).has_public_key() {
|
||||||
return div();
|
return div();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,10 +473,12 @@ impl ChatSpace {
|
|||||||
fn titlebar_right(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn titlebar_right(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||||
let auto_update = AutoUpdater::global(cx);
|
let auto_update = AutoUpdater::global(cx);
|
||||||
let account = Account::global(cx);
|
|
||||||
let relay_auth = RelayAuth::global(cx);
|
let relay_auth = RelayAuth::global(cx);
|
||||||
let pending_requests = relay_auth.read(cx).pending_requests(cx);
|
let pending_requests = relay_auth.read(cx).pending_requests(cx);
|
||||||
let encryption_panel = self.encryption_panel.downgrade();
|
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
let identity = nostr.read(cx).identity();
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
@@ -542,16 +538,10 @@ impl ChatSpace {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when(account.read(cx).has_account(), |this| {
|
.when_some(identity.read(cx).option_public_key(), |this, public_key| {
|
||||||
let account = Account::global(cx);
|
|
||||||
let public_key = account.read(cx).public_key();
|
|
||||||
|
|
||||||
let persons = PersonRegistry::global(cx);
|
let persons = PersonRegistry::global(cx);
|
||||||
let profile = persons.read(cx).get_person(&public_key, cx);
|
let profile = persons.read(cx).get_person(&public_key, cx);
|
||||||
|
|
||||||
let encryption = Encryption::global(cx);
|
|
||||||
let has_encryption = encryption.read(cx).has_encryption(cx);
|
|
||||||
|
|
||||||
let keystore = KeyStore::global(cx);
|
let keystore = KeyStore::global(cx);
|
||||||
let is_using_file_keystore = keystore.read(cx).is_using_file_keystore();
|
let is_using_file_keystore = keystore.read(cx).is_using_file_keystore();
|
||||||
|
|
||||||
@@ -562,82 +552,38 @@ impl ChatSpace {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.child(
|
this.child(
|
||||||
h_flex()
|
Button::new("user")
|
||||||
.gap_1()
|
.small()
|
||||||
.child(
|
.reverse()
|
||||||
Popover::new("encryption")
|
.transparent()
|
||||||
.trigger(
|
.icon(IconName::CaretDown)
|
||||||
Button::new("encryption-trigger")
|
.child(Avatar::new(profile.avatar(proxy)).size(rems(1.45)))
|
||||||
.tooltip("Manage Encryption Key")
|
.popup_menu(move |this, _window, _cx| {
|
||||||
.icon(IconName::Encryption)
|
this.label(profile.display_name())
|
||||||
.rounded()
|
.menu_with_icon(
|
||||||
.small()
|
"Profile",
|
||||||
.cta()
|
IconName::EmojiFill,
|
||||||
.map(|this| match has_encryption {
|
Box::new(ViewProfile),
|
||||||
true => this.ghost_alt(),
|
|
||||||
false => this.warning(),
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
.content(move |window, cx| {
|
.menu_with_icon(
|
||||||
let encryption_panel = encryption_panel.clone();
|
"Messaging Relays",
|
||||||
|
IconName::Server,
|
||||||
cx.new(|cx| {
|
Box::new(ViewRelays),
|
||||||
PopoverContent::new(window, cx, move |_window, _cx| {
|
)
|
||||||
if let Some(view) = encryption_panel.upgrade() {
|
.separator()
|
||||||
view.clone().into_any_element()
|
.label(SharedString::from("Keyring Service"))
|
||||||
} else {
|
.menu_with_icon_and_disabled(
|
||||||
div().into_any_element()
|
keyring_label.clone(),
|
||||||
}
|
IconName::Encryption,
|
||||||
})
|
Box::new(KeyringPopup),
|
||||||
})
|
!is_using_file_keystore,
|
||||||
}),
|
)
|
||||||
)
|
.separator()
|
||||||
.child(
|
.menu_with_icon("Dark Mode", IconName::Sun, Box::new(DarkMode))
|
||||||
Button::new("user")
|
.menu_with_icon("Themes", IconName::Moon, Box::new(Themes))
|
||||||
.small()
|
.menu_with_icon("Settings", IconName::Settings, Box::new(Settings))
|
||||||
.reverse()
|
.menu_with_icon("Sign Out", IconName::Logout, Box::new(Logout))
|
||||||
.transparent()
|
}),
|
||||||
.icon(IconName::CaretDown)
|
|
||||||
.child(Avatar::new(profile.avatar(proxy)).size(rems(1.45)))
|
|
||||||
.popup_menu(move |this, _window, _cx| {
|
|
||||||
this.label(profile.display_name())
|
|
||||||
.menu_with_icon(
|
|
||||||
"Profile",
|
|
||||||
IconName::EmojiFill,
|
|
||||||
Box::new(ViewProfile),
|
|
||||||
)
|
|
||||||
.menu_with_icon(
|
|
||||||
"Messaging Relays",
|
|
||||||
IconName::Server,
|
|
||||||
Box::new(ViewRelays),
|
|
||||||
)
|
|
||||||
.separator()
|
|
||||||
.label(SharedString::from("Keyring Service"))
|
|
||||||
.menu_with_icon_and_disabled(
|
|
||||||
keyring_label.clone(),
|
|
||||||
IconName::Encryption,
|
|
||||||
Box::new(KeyringPopup),
|
|
||||||
!is_using_file_keystore,
|
|
||||||
)
|
|
||||||
.separator()
|
|
||||||
.menu_with_icon(
|
|
||||||
"Dark Mode",
|
|
||||||
IconName::Sun,
|
|
||||||
Box::new(DarkMode),
|
|
||||||
)
|
|
||||||
.menu_with_icon("Themes", IconName::Moon, Box::new(Themes))
|
|
||||||
.menu_with_icon(
|
|
||||||
"Settings",
|
|
||||||
IconName::Settings,
|
|
||||||
Box::new(Settings),
|
|
||||||
)
|
|
||||||
.menu_with_icon(
|
|
||||||
"Sign Out",
|
|
||||||
IconName::Logout,
|
|
||||||
Box::new(Logout),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,12 +96,6 @@ fn main() {
|
|||||||
// Initialize settings
|
// Initialize settings
|
||||||
settings::init(cx);
|
settings::init(cx);
|
||||||
|
|
||||||
// Initialize account state
|
|
||||||
account::init(cx);
|
|
||||||
|
|
||||||
// Initialize encryption state
|
|
||||||
encryption::init(cx);
|
|
||||||
|
|
||||||
// Initialize app registry
|
// Initialize app registry
|
||||||
chat::init(cx);
|
chat::init(cx);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use account::Account;
|
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use chat::{ChatEvent, ChatRegistry, Room, RoomKind};
|
use chat::{ChatEvent, ChatRegistry, Room, RoomKind};
|
||||||
use common::{DebouncedDelay, RenderedTimestamp, TextUtils, BOOTSTRAP_RELAYS, SEARCH_RELAYS};
|
use common::{DebouncedDelay, RenderedTimestamp, TextUtils, BOOTSTRAP_RELAYS, SEARCH_RELAYS};
|
||||||
@@ -199,11 +198,9 @@ impl Sidebar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_by_nip50(&mut self, query: &str, window: &mut Window, cx: &mut Context<Self>) {
|
fn search_by_nip50(&mut self, query: &str, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let account = Account::global(cx);
|
|
||||||
let public_key = account.read(cx).public_key();
|
|
||||||
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
|
let public_key = nostr.read(cx).identity().read(cx).public_key();
|
||||||
|
|
||||||
let query = query.to_owned();
|
let query = query.to_owned();
|
||||||
|
|
||||||
@@ -597,7 +594,7 @@ 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 {
|
||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
let loading = chat.read(cx).loading;
|
let loading = chat.read(cx).loading();
|
||||||
|
|
||||||
// Get rooms from either search results or the chat registry
|
// Get rooms from either search results or the chat registry
|
||||||
let rooms = if let Some(results) = self.search_results.read(cx).as_ref() {
|
let rooms = if let Some(results) = self.search_results.read(cx).as_ref() {
|
||||||
|
|||||||
@@ -259,17 +259,11 @@ impl UserProfile {
|
|||||||
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let gossip = nostr.read(cx).gossip();
|
let public_key = nostr.read(cx).identity().read(cx).public_key();
|
||||||
|
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
|
||||||
|
|
||||||
let gossip = gossip.read().await;
|
|
||||||
let write_relays = gossip.inbox_relays(&public_key);
|
|
||||||
|
|
||||||
// Ensure connections to the write relays
|
|
||||||
gossip.ensure_connections(&client, &write_relays).await;
|
|
||||||
|
|
||||||
// Sign the new metadata event
|
// Sign the new metadata event
|
||||||
let event = EventBuilder::metadata(&new_metadata).sign(&signer).await?;
|
let event = EventBuilder::metadata(&new_metadata).sign(&signer).await?;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use account::Account;
|
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use chat::{ChatRegistry, Room};
|
use chat::{ChatRegistry, Room};
|
||||||
use common::{nip05_profile, RenderedProfile, TextUtils, BOOTSTRAP_RELAYS};
|
use common::{nip05_profile, RenderedProfile, TextUtils, BOOTSTRAP_RELAYS};
|
||||||
@@ -312,9 +311,8 @@ impl Compose {
|
|||||||
|
|
||||||
fn submit(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn submit(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
let account = Account::global(cx);
|
let public_key = nostr.read(cx).identity().read(cx).public_key();
|
||||||
let public_key = account.read(cx).public_key();
|
|
||||||
|
|
||||||
let receivers: Vec<PublicKey> = self.selected(cx);
|
let receivers: Vec<PublicKey> = self.selected(cx);
|
||||||
let subject_input = self.title_input.read(cx).value();
|
let subject_input = self.title_input.read(cx).value();
|
||||||
|
|||||||
@@ -158,17 +158,13 @@ impl SetupRelay {
|
|||||||
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let gossip = nostr.read(cx).gossip();
|
let public_key = nostr.read(cx).identity().read(cx).public_key();
|
||||||
|
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
|
||||||
|
|
||||||
let relays = self.relays.clone();
|
let relays = self.relays.clone();
|
||||||
|
|
||||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
|
||||||
let gossip = gossip.read().await;
|
|
||||||
let write_relays = gossip.inbox_relays(&public_key);
|
|
||||||
|
|
||||||
// Ensure connections to the write relays
|
|
||||||
gossip.ensure_connections(&client, &write_relays).await;
|
|
||||||
|
|
||||||
let tags: Vec<Tag> = relays
|
let tags: Vec<Tag> = relays
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -29,11 +29,16 @@ pub fn init(cre: Credential, window: &mut Window, cx: &mut App) -> Entity<Startu
|
|||||||
/// Startup
|
/// Startup
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Startup {
|
pub struct Startup {
|
||||||
credential: Credential,
|
|
||||||
loading: bool,
|
|
||||||
|
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
|
|
||||||
|
/// Local user credentials
|
||||||
|
credential: Credential,
|
||||||
|
|
||||||
|
/// Whether the loadng is in progress
|
||||||
|
loading: bool,
|
||||||
|
|
||||||
|
/// Image cache
|
||||||
image_cache: Entity<RetainAllImageCache>,
|
image_cache: Entity<RetainAllImageCache>,
|
||||||
|
|
||||||
/// Event subscriptions
|
/// Event subscriptions
|
||||||
@@ -164,15 +169,12 @@ impl Startup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn login_with_keys(&mut self, secret: SecretKey, cx: &mut Context<Self>) {
|
fn login_with_keys(&mut self, secret: SecretKey, cx: &mut Context<Self>) {
|
||||||
let nostr = NostrRegistry::global(cx);
|
|
||||||
let client = nostr.read(cx).client();
|
|
||||||
let keys = Keys::new(secret);
|
let keys = Keys::new(secret);
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
|
||||||
// Update the signer
|
nostr.update(cx, |this, cx| {
|
||||||
cx.background_spawn(async move {
|
this.set_signer(keys, cx);
|
||||||
client.set_signer(keys).await;
|
|
||||||
})
|
})
|
||||||
.detach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {
|
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {
|
||||||
|
|||||||
@@ -75,6 +75,11 @@ impl Identity {
|
|||||||
self.public_key = None;
|
self.public_key = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the public key of the identity.
|
||||||
|
pub fn option_public_key(&self) -> Option<PublicKey> {
|
||||||
|
self.public_key
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the public key of the identity.
|
/// Returns the public key of the identity.
|
||||||
pub fn public_key(&self) -> PublicKey {
|
pub fn public_key(&self) -> PublicKey {
|
||||||
// This method is safe to unwrap because the public key is always called when the identity is created.
|
// This method is safe to unwrap because the public key is always called when the identity is created.
|
||||||
|
|||||||
@@ -286,8 +286,8 @@ impl NostrRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get current identity
|
/// Get current identity
|
||||||
pub fn identity(&self, cx: &App) -> Identity {
|
pub fn identity(&self) -> Entity<Identity> {
|
||||||
self.identity.read(cx).clone()
|
self.identity.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a relay hint (messaging relay) for a given public key
|
/// Get a relay hint (messaging relay) for a given public key
|
||||||
@@ -299,9 +299,58 @@ impl NostrRegistry {
|
|||||||
.cloned()
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a list of write relays for a given public key
|
||||||
|
pub fn write_relays(&self, public_key: &PublicKey, cx: &App) -> Vec<RelayUrl> {
|
||||||
|
let client = self.client();
|
||||||
|
let relays = self.gossip.read(cx).write_relays(public_key);
|
||||||
|
let async_relays = relays.clone();
|
||||||
|
|
||||||
|
// Ensure relay connections
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
for url in async_relays.iter() {
|
||||||
|
client.add_relay(url).await.ok();
|
||||||
|
client.connect_relay(url).await.ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
relays
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a list of read relays for a given public key
|
||||||
|
pub fn read_relays(&self, public_key: &PublicKey, cx: &App) -> Vec<RelayUrl> {
|
||||||
|
let client = self.client();
|
||||||
|
let relays = self.gossip.read(cx).read_relays(public_key);
|
||||||
|
let async_relays = relays.clone();
|
||||||
|
|
||||||
|
// Ensure relay connections
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
for url in async_relays.iter() {
|
||||||
|
client.add_relay(url).await.ok();
|
||||||
|
client.connect_relay(url).await.ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
relays
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a list of messaging relays for a given public key
|
/// Get a list of messaging relays for a given public key
|
||||||
pub fn messaging_relays(&self, public_key: &PublicKey, cx: &App) -> Vec<RelayUrl> {
|
pub fn messaging_relays(&self, public_key: &PublicKey, cx: &App) -> Vec<RelayUrl> {
|
||||||
self.gossip.read(cx).messaging_relays(public_key)
|
let client = self.client();
|
||||||
|
let relays = self.gossip.read(cx).messaging_relays(public_key);
|
||||||
|
let async_relays = relays.clone();
|
||||||
|
|
||||||
|
// Ensure relay connections
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
for url in async_relays.iter() {
|
||||||
|
client.add_relay(url).await.ok();
|
||||||
|
client.connect_relay(url).await.ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
relays
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the signer for the nostr client and verify the public key
|
/// Set the signer for the nostr client and verify the public key
|
||||||
@@ -370,7 +419,7 @@ impl NostrRegistry {
|
|||||||
fn get_relay_list(&mut self, cx: &mut Context<Self>) {
|
fn get_relay_list(&mut self, cx: &mut Context<Self>) {
|
||||||
let client = self.client();
|
let client = self.client();
|
||||||
let async_identity = self.identity.downgrade();
|
let async_identity = self.identity.downgrade();
|
||||||
let public_key = self.identity(cx).public_key();
|
let public_key = self.identity().read(cx).public_key();
|
||||||
|
|
||||||
let task: Task<Result<RelayState, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<RelayState, Error>> = cx.background_spawn(async move {
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
@@ -415,8 +464,8 @@ impl NostrRegistry {
|
|||||||
fn get_messaging_relays(&mut self, cx: &mut Context<Self>) {
|
fn get_messaging_relays(&mut self, cx: &mut Context<Self>) {
|
||||||
let client = self.client();
|
let client = self.client();
|
||||||
let async_identity = self.identity.downgrade();
|
let async_identity = self.identity.downgrade();
|
||||||
let public_key = self.identity(cx).public_key();
|
let public_key = self.identity().read(cx).public_key();
|
||||||
let write_relays = self.gossip.read(cx).write_relays(&public_key);
|
let write_relays = self.write_relays(&public_key, cx);
|
||||||
|
|
||||||
let task: Task<Result<RelayState, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<RelayState, Error>> = cx.background_spawn(async move {
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
@@ -460,8 +509,8 @@ impl NostrRegistry {
|
|||||||
/// Continuously get gift wrap events for the current user in their messaging relays
|
/// Continuously get gift wrap events for the current user in their messaging relays
|
||||||
fn get_messages(&mut self, cx: &mut Context<Self>) {
|
fn get_messages(&mut self, cx: &mut Context<Self>) {
|
||||||
let client = self.client();
|
let client = self.client();
|
||||||
let public_key = self.identity(cx).public_key();
|
let public_key = self.identity().read(cx).public_key();
|
||||||
let messaging_relays = self.gossip.read(cx).messaging_relays(&public_key);
|
let messaging_relays = self.messaging_relays(&public_key, cx);
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let id = SubscriptionId::new(GIFTWRAP_SUBSCRIPTION);
|
let id = SubscriptionId::new(GIFTWRAP_SUBSCRIPTION);
|
||||||
@@ -480,7 +529,7 @@ impl NostrRegistry {
|
|||||||
/// Publish an event to author's write relays
|
/// Publish an event to author's write relays
|
||||||
pub fn publish(&self, event: Event, cx: &App) -> Task<Result<Output<EventId>, Error>> {
|
pub fn publish(&self, event: Event, cx: &App) -> Task<Result<Output<EventId>, Error>> {
|
||||||
let client = self.client();
|
let client = self.client();
|
||||||
let write_relays = self.gossip.read(cx).write_relays(&event.pubkey);
|
let write_relays = self.write_relays(&event.pubkey, cx);
|
||||||
|
|
||||||
cx.background_spawn(async move { Ok(client.send_event_to(&write_relays, &event).await?) })
|
cx.background_spawn(async move { Ok(client.send_event_to(&write_relays, &event).await?) })
|
||||||
}
|
}
|
||||||
@@ -491,7 +540,7 @@ impl NostrRegistry {
|
|||||||
I: Into<Vec<Kind>>,
|
I: Into<Vec<Kind>>,
|
||||||
{
|
{
|
||||||
let client = self.client();
|
let client = self.client();
|
||||||
let write_relays = self.gossip.read(cx).write_relays(&author);
|
let write_relays = self.write_relays(&author, cx);
|
||||||
|
|
||||||
// Construct filters based on event kinds
|
// Construct filters based on event kinds
|
||||||
let filters: Vec<Filter> = kinds
|
let filters: Vec<Filter> = kinds
|
||||||
|
|||||||
Reference in New Issue
Block a user