chore: improve the event loop (#141)
* improve wait for signer * refactor gift wrap processor * . * . * . * . * .
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -10,9 +11,9 @@ use common::display::ReadableProfile;
|
|||||||
use common::event::EventUtils;
|
use common::event::EventUtils;
|
||||||
use global::constants::{
|
use global::constants::{
|
||||||
ACCOUNT_IDENTIFIER, BOOTSTRAP_RELAYS, DEFAULT_SIDEBAR_WIDTH, METADATA_BATCH_LIMIT,
|
ACCOUNT_IDENTIFIER, BOOTSTRAP_RELAYS, DEFAULT_SIDEBAR_WIDTH, METADATA_BATCH_LIMIT,
|
||||||
METADATA_BATCH_TIMEOUT, RELAY_RETRY, SEARCH_RELAYS, WAIT_FOR_FINISH,
|
METADATA_BATCH_TIMEOUT, SEARCH_RELAYS,
|
||||||
};
|
};
|
||||||
use global::{css, ingester, nostr_client, AuthRequest, IngesterSignal, Notice};
|
use global::{css, ingester, nostr_client, AuthRequest, Notice, Signal};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, rems, App, AppContext, AsyncWindowContext, Axis, Context, Entity, InteractiveElement,
|
div, px, rems, App, AppContext, AsyncWindowContext, Axis, Context, Entity, InteractiveElement,
|
||||||
@@ -28,7 +29,6 @@ use settings::AppSettings;
|
|||||||
use signer_proxy::{BrowserSignerProxy, BrowserSignerProxyOptions};
|
use signer_proxy::{BrowserSignerProxy, BrowserSignerProxyOptions};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use smol::channel::{Receiver, Sender};
|
use smol::channel::{Receiver, Sender};
|
||||||
use smol::lock::Mutex;
|
|
||||||
use theme::{ActiveTheme, Theme, ThemeMode};
|
use theme::{ActiveTheme, Theme, ThemeMode};
|
||||||
use title_bar::TitleBar;
|
use title_bar::TitleBar;
|
||||||
use ui::actions::OpenProfile;
|
use ui::actions::OpenProfile;
|
||||||
@@ -64,20 +64,6 @@ pub fn new_account(window: &mut Window, cx: &mut App) {
|
|||||||
ChatSpace::set_center_panel(panel, window, cx);
|
ChatSpace::set_center_panel(panel, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
enum RelayTrackStatus {
|
|
||||||
#[default]
|
|
||||||
Waiting,
|
|
||||||
NotFound,
|
|
||||||
Found,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
struct RelayTracking {
|
|
||||||
nip17: RelayTrackStatus,
|
|
||||||
nip65: RelayTrackStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ChatSpace {
|
pub struct ChatSpace {
|
||||||
title_bar: Entity<TitleBar>,
|
title_bar: Entity<TitleBar>,
|
||||||
dock: Entity<DockArea>,
|
dock: Entity<DockArea>,
|
||||||
@@ -95,13 +81,7 @@ impl ChatSpace {
|
|||||||
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 relay_tracking = Arc::new(Mutex::new(RelayTracking::default()));
|
|
||||||
let relay_tracking_clone = relay_tracking.clone();
|
|
||||||
|
|
||||||
let (pubkey_tx, pubkey_rx) = smol::channel::bounded::<PublicKey>(1024);
|
let (pubkey_tx, pubkey_rx) = smol::channel::bounded::<PublicKey>(1024);
|
||||||
let (event_tx, event_rx) = smol::channel::bounded::<Event>(2048);
|
|
||||||
|
|
||||||
let pubkey_tx_clone = pubkey_tx.clone();
|
|
||||||
|
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
let mut tasks = smallvec![];
|
let mut tasks = smallvec![];
|
||||||
@@ -132,7 +112,7 @@ impl ChatSpace {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed connect the bootstrap relays. Please restart the application.");
|
.expect("Failed connect the bootstrap relays. Please restart the application.");
|
||||||
|
|
||||||
Self::process_nostr_events(&relay_tracking_clone, &event_tx, &pubkey_tx_clone)
|
Self::process_nostr_events(&pubkey_tx)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to handle nostr events. Please restart the application.");
|
.expect("Failed to handle nostr events. Please restart the application.");
|
||||||
}),
|
}),
|
||||||
@@ -140,9 +120,16 @@ impl ChatSpace {
|
|||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
// Wait for the signer to be set
|
// Wait for the signer to be set
|
||||||
// Also verify NIP65 and NIP17 relays after the signer is set
|
// Also verify NIP-65 and NIP-17 relays after the signer is set
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
Self::wait_for_signer_set(&relay_tracking).await;
|
Self::observe_signer().await;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
tasks.push(
|
||||||
|
// Observe gift wrap process in the background
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
Self::observe_giftwrap().await;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -153,13 +140,6 @@ impl ChatSpace {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
tasks.push(
|
|
||||||
// Process gift wrap event in the background
|
|
||||||
cx.background_spawn(async move {
|
|
||||||
Self::process_gift_wrap(&pubkey_tx, &event_rx).await;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
// Continuously handle signals from the Nostr channel
|
// Continuously handle signals from the Nostr channel
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
@@ -198,47 +178,61 @@ impl ChatSpace {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_for_signer_set(relay_tracking: &Arc<Mutex<RelayTracking>>) {
|
async fn observe_signer() {
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
let ingester = ingester();
|
let ingester = ingester();
|
||||||
|
let loop_duration = Duration::from_millis(500);
|
||||||
let mut signer_set = false;
|
let mut is_sent_signal = false;
|
||||||
let mut retry = 0;
|
let mut identity: Option<PublicKey> = None;
|
||||||
let mut nip65_retry = 0;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if signer_set {
|
if let Some(public_key) = identity {
|
||||||
let state = relay_tracking.lock().await;
|
let nip65 = Filter::new().kind(Kind::RelayList).author(public_key);
|
||||||
|
|
||||||
if state.nip65 == RelayTrackStatus::Found {
|
if client.database().count(nip65).await.unwrap_or(0) > 0 {
|
||||||
if state.nip17 == RelayTrackStatus::Found {
|
let nip17 = Filter::new().kind(Kind::InboxRelays).author(public_key);
|
||||||
break;
|
|
||||||
} else if state.nip17 == RelayTrackStatus::NotFound {
|
match client.database().query(nip17).await {
|
||||||
ingester.send(IngesterSignal::DmRelayNotFound).await;
|
Ok(events) => {
|
||||||
break;
|
if let Some(event) = events.first_owned() {
|
||||||
} else {
|
let relay_urls = Self::extract_relay_list(&event);
|
||||||
retry += 1;
|
|
||||||
if retry == RELAY_RETRY {
|
if relay_urls.is_empty() {
|
||||||
ingester.send(IngesterSignal::DmRelayNotFound).await;
|
if !is_sent_signal {
|
||||||
break;
|
ingester.send(Signal::DmRelayNotFound).await;
|
||||||
|
is_sent_signal = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if !is_sent_signal {
|
||||||
|
ingester.send(Signal::DmRelayNotFound).await;
|
||||||
|
is_sent_signal = true;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
if !is_sent_signal {
|
||||||
|
ingester.send(Signal::DmRelayNotFound).await;
|
||||||
|
is_sent_signal = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if !is_sent_signal {
|
||||||
|
ingester.send(Signal::DmRelayNotFound).await;
|
||||||
|
is_sent_signal = true;
|
||||||
} else {
|
} else {
|
||||||
nip65_retry += 1;
|
break;
|
||||||
if nip65_retry == RELAY_RETRY {
|
|
||||||
ingester.send(IngesterSignal::DmRelayNotFound).await;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
// Wait for signer set
|
||||||
if !signer_set {
|
|
||||||
if let Ok(signer) = client.signer().await {
|
if let Ok(signer) = client.signer().await {
|
||||||
if let Ok(public_key) = signer.get_public_key().await {
|
if let Ok(public_key) = signer.get_public_key().await {
|
||||||
signer_set = true;
|
identity = Some(public_key);
|
||||||
|
|
||||||
// Notify the app that the signer has been set.
|
// Notify the app that the signer has been set.
|
||||||
ingester.send(IngesterSignal::SignerSet(public_key)).await;
|
ingester.send(Signal::SignerSet(public_key)).await;
|
||||||
|
|
||||||
// Subscribe to the NIP-65 relays for the public key.
|
// Subscribe to the NIP-65 relays for the public key.
|
||||||
if let Err(e) = Self::fetch_nip65_relays(public_key).await {
|
if let Err(e) = Self::fetch_nip65_relays(public_key).await {
|
||||||
@@ -248,7 +242,39 @@ impl ChatSpace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
smol::Timer::after(Duration::from_millis(300)).await;
|
smol::Timer::after(loop_duration).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn observe_giftwrap() {
|
||||||
|
let client = nostr_client();
|
||||||
|
let css = css();
|
||||||
|
let ingester = ingester();
|
||||||
|
let loop_duration = Duration::from_secs(10);
|
||||||
|
let mut total_notify = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if client.has_signer().await {
|
||||||
|
if css.gift_wrap_processing.load(Ordering::Acquire) {
|
||||||
|
ingester.send(Signal::EventProcessing).await;
|
||||||
|
|
||||||
|
// Reset gift wrap processing flag
|
||||||
|
let _ = css.gift_wrap_processing.compare_exchange(
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
Ordering::Release,
|
||||||
|
Ordering::Relaxed,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Only send signal to ingester a maximum of three times
|
||||||
|
if total_notify <= 3 {
|
||||||
|
ingester.send(Signal::EventProcessed(true)).await;
|
||||||
|
total_notify += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
smol::Timer::after(loop_duration).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,26 +291,27 @@ impl ChatSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match smol::future::or(
|
let futs = smol::future::or(
|
||||||
async {
|
async move {
|
||||||
if let Ok(public_key) = rx.recv().await {
|
if let Ok(public_key) = rx.recv().await {
|
||||||
BatchEvent::PublicKey(public_key)
|
BatchEvent::PublicKey(public_key)
|
||||||
} else {
|
} else {
|
||||||
BatchEvent::Closed
|
BatchEvent::Closed
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async {
|
async move {
|
||||||
smol::Timer::after(timeout).await;
|
smol::Timer::after(timeout).await;
|
||||||
BatchEvent::Timeout
|
BatchEvent::Timeout
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
.await
|
|
||||||
{
|
match futs.await {
|
||||||
BatchEvent::PublicKey(public_key) => {
|
BatchEvent::PublicKey(public_key) => {
|
||||||
// Prevent duplicate keys from being processed
|
// Prevent duplicate keys from being processed
|
||||||
if processed_pubkeys.insert(public_key) {
|
if processed_pubkeys.insert(public_key) {
|
||||||
batch.insert(public_key);
|
batch.insert(public_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the batch if it's full
|
// Process the batch if it's full
|
||||||
if batch.len() >= METADATA_BATCH_LIMIT {
|
if batch.len() >= METADATA_BATCH_LIMIT {
|
||||||
Self::fetch_metadata_for_pubkeys(std::mem::take(&mut batch)).await;
|
Self::fetch_metadata_for_pubkeys(std::mem::take(&mut batch)).await;
|
||||||
@@ -295,73 +322,21 @@ impl ChatSpace {
|
|||||||
}
|
}
|
||||||
BatchEvent::Closed => {
|
BatchEvent::Closed => {
|
||||||
Self::fetch_metadata_for_pubkeys(std::mem::take(&mut batch)).await;
|
Self::fetch_metadata_for_pubkeys(std::mem::take(&mut batch)).await;
|
||||||
// Exit the current loop
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_gift_wrap(pubkey_tx: &Sender<PublicKey>, event_rx: &Receiver<Event>) {
|
async fn process_nostr_events(pubkey_tx: &Sender<PublicKey>) -> Result<(), Error> {
|
||||||
let client = nostr_client();
|
|
||||||
let ingester = ingester();
|
|
||||||
let timeout = Duration::from_secs(WAIT_FOR_FINISH);
|
|
||||||
|
|
||||||
let mut counter = 0;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// Signer is unset, probably user is not ready to retrieve gift wrap events
|
|
||||||
if client.signer().await.is_err() {
|
|
||||||
smol::Timer::after(Duration::from_secs(1)).await;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let recv = || async {
|
|
||||||
// no inline
|
|
||||||
(event_rx.recv().await).ok()
|
|
||||||
};
|
|
||||||
|
|
||||||
let timeout = || async {
|
|
||||||
smol::Timer::after(timeout).await;
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
match smol::future::or(recv(), timeout()).await {
|
|
||||||
Some(event) => {
|
|
||||||
let cached = Self::unwrap_gift_wrap_event(&event, pubkey_tx).await;
|
|
||||||
|
|
||||||
// Increment the total messages counter if message is not from cache
|
|
||||||
if !cached {
|
|
||||||
counter += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send partial finish signal to GPUI
|
|
||||||
if counter >= 20 {
|
|
||||||
ingester.send(IngesterSignal::PartialFinish).await;
|
|
||||||
// Reset counter
|
|
||||||
counter = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// Notify the UI that the processing is finished
|
|
||||||
ingester.send(IngesterSignal::Finish).await;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn process_nostr_events(
|
|
||||||
relay_tracking: &Arc<Mutex<RelayTracking>>,
|
|
||||||
event_tx: &Sender<Event>,
|
|
||||||
pubkey_tx: &Sender<PublicKey>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
let ingester = ingester();
|
let ingester = ingester();
|
||||||
let css = css();
|
let css = css();
|
||||||
|
|
||||||
let auto_close =
|
let auto_close =
|
||||||
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
|
|
||||||
|
let mut event_counter = 0;
|
||||||
let mut processed_events: HashSet<EventId> = HashSet::new();
|
let mut processed_events: HashSet<EventId> = HashSet::new();
|
||||||
let mut challenges: HashSet<Cow<'_, str>> = HashSet::new();
|
let mut challenges: HashSet<Cow<'_, str>> = HashSet::new();
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
@@ -382,9 +357,6 @@ impl ChatSpace {
|
|||||||
Kind::RelayList => {
|
Kind::RelayList => {
|
||||||
// Get metadata for event's pubkey that matches the current user's pubkey
|
// Get metadata for event's pubkey that matches the current user's pubkey
|
||||||
if let Ok(true) = Self::is_self_event(&event).await {
|
if let Ok(true) = Self::is_self_event(&event).await {
|
||||||
let mut relay_tracking = relay_tracking.lock().await;
|
|
||||||
relay_tracking.nip65 = RelayTrackStatus::Found;
|
|
||||||
|
|
||||||
// Fetch user's metadata event
|
// Fetch user's metadata event
|
||||||
Self::fetch_single_event(Kind::Metadata, event.pubkey).await;
|
Self::fetch_single_event(Kind::Metadata, event.pubkey).await;
|
||||||
|
|
||||||
@@ -397,30 +369,17 @@ impl ChatSpace {
|
|||||||
}
|
}
|
||||||
Kind::InboxRelays => {
|
Kind::InboxRelays => {
|
||||||
if let Ok(true) = Self::is_self_event(&event).await {
|
if let Ok(true) = Self::is_self_event(&event).await {
|
||||||
let relays: Vec<RelayUrl> = event
|
let relays: Vec<RelayUrl> = Self::extract_relay_list(&event);
|
||||||
.tags
|
|
||||||
.filter_standardized(TagKind::Relay)
|
|
||||||
.filter_map(|t| {
|
|
||||||
if let TagStandard::Relay(url) = t {
|
|
||||||
Some(url.to_owned())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if !relays.is_empty() {
|
if !relays.is_empty() {
|
||||||
let mut relay_tracking = relay_tracking.lock().await;
|
|
||||||
relay_tracking.nip17 = RelayTrackStatus::Found;
|
|
||||||
|
|
||||||
for relay in relays.iter() {
|
for relay in relays.iter() {
|
||||||
if client.add_relay(relay).await.is_err() {
|
if client.add_relay(relay).await.is_err() {
|
||||||
let notice = Notice::RelayFailed(relay.clone());
|
let notice = Notice::RelayFailed(relay.clone());
|
||||||
ingester.send(IngesterSignal::Notice(notice)).await;
|
ingester.send(Signal::Notice(notice)).await;
|
||||||
}
|
}
|
||||||
if client.connect_relay(relay).await.is_err() {
|
if client.connect_relay(relay).await.is_err() {
|
||||||
let notice = Notice::RelayFailed(relay.clone());
|
let notice = Notice::RelayFailed(relay.clone());
|
||||||
ingester.send(IngesterSignal::Notice(notice)).await;
|
ingester.send(Signal::Notice(notice)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,23 +403,35 @@ impl ChatSpace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Kind::Metadata => {
|
Kind::Metadata => {
|
||||||
ingester
|
ingester.send(Signal::Metadata(event.into_owned())).await;
|
||||||
.send(IngesterSignal::Metadata(event.into_owned()))
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
Kind::GiftWrap => {
|
Kind::GiftWrap => {
|
||||||
if event_tx.send(event.clone().into_owned()).await.is_err() {
|
// Mark gift wrap event as currently being processed
|
||||||
Self::unwrap_gift_wrap_event(&event, pubkey_tx).await;
|
css.gift_wrap_processing.store(true, Ordering::Release);
|
||||||
|
|
||||||
|
// Process the gift wrap event
|
||||||
|
Self::unwrap_gift_wrap_event(&event, pubkey_tx).await;
|
||||||
|
|
||||||
|
// Trigger a partial UI reload if at least 50 events have been processed
|
||||||
|
if event_counter >= 20 {
|
||||||
|
ingester.send(Signal::EventProcessed(false)).await;
|
||||||
|
event_counter = 0;
|
||||||
}
|
}
|
||||||
|
event_counter += 1;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RelayMessage::EndOfStoredEvents(subscription_id) => {
|
||||||
|
if *subscription_id == css.gift_wrap_sub_id {
|
||||||
|
ingester.send(Signal::EventProcessed(false)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
RelayMessage::Auth { challenge } => {
|
RelayMessage::Auth { challenge } => {
|
||||||
if challenges.insert(challenge.clone()) {
|
if challenges.insert(challenge.clone()) {
|
||||||
let req = AuthRequest::new(challenge, relay_url);
|
let req = AuthRequest::new(challenge, relay_url);
|
||||||
// Send a signal to the ingester to handle the auth request
|
// Send a signal to the ingester to handle the auth request
|
||||||
ingester.send(IngesterSignal::Auth(req)).await;
|
ingester.send(Signal::Auth(req)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RelayMessage::Ok {
|
RelayMessage::Ok {
|
||||||
@@ -498,7 +469,7 @@ impl ChatSpace {
|
|||||||
let settings = AppSettings::global(cx);
|
let settings = AppSettings::global(cx);
|
||||||
|
|
||||||
match signal {
|
match signal {
|
||||||
IngesterSignal::SignerSet(public_key) => {
|
Signal::SignerSet(public_key) => {
|
||||||
window.close_modal(cx);
|
window.close_modal(cx);
|
||||||
|
|
||||||
// Setup the default layout for current workspace
|
// Setup the default layout for current workspace
|
||||||
@@ -515,10 +486,10 @@ impl ChatSpace {
|
|||||||
// Load all chat rooms
|
// Load all chat rooms
|
||||||
registry.update(cx, |this, cx| {
|
registry.update(cx, |this, cx| {
|
||||||
this.set_identity(public_key, cx);
|
this.set_identity(public_key, cx);
|
||||||
this.load_rooms(window, cx);
|
this.load_rooms(false, window, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
IngesterSignal::SignerUnset => {
|
Signal::SignerUnset => {
|
||||||
// Setup the onboarding layout for current workspace
|
// Setup the onboarding layout for current workspace
|
||||||
view.update(cx, |this, cx| {
|
view.update(cx, |this, cx| {
|
||||||
this.set_onboarding_layout(window, cx);
|
this.set_onboarding_layout(window, cx);
|
||||||
@@ -530,7 +501,7 @@ impl ChatSpace {
|
|||||||
this.reset(cx);
|
this.reset(cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
IngesterSignal::Auth(req) => {
|
Signal::Auth(req) => {
|
||||||
let relay_url = &req.url;
|
let relay_url = &req.url;
|
||||||
let challenge = &req.challenge;
|
let challenge = &req.challenge;
|
||||||
let auto_auth = AppSettings::get_auto_auth(cx);
|
let auto_auth = AppSettings::get_auto_auth(cx);
|
||||||
@@ -550,30 +521,27 @@ impl ChatSpace {
|
|||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
IngesterSignal::ProxyDown => {
|
Signal::ProxyDown => {
|
||||||
if !is_open_proxy_modal {
|
if !is_open_proxy_modal {
|
||||||
|
is_open_proxy_modal = true;
|
||||||
|
|
||||||
view.update(cx, |this, cx| {
|
view.update(cx, |this, cx| {
|
||||||
this.render_proxy_modal(window, cx);
|
this.render_proxy_modal(window, cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
is_open_proxy_modal = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Load chat rooms and stop the loading status
|
// Notify the user that the gift wrap still processing
|
||||||
IngesterSignal::Finish => {
|
Signal::EventProcessing => {
|
||||||
registry.update(cx, |this, cx| {
|
registry.update(cx, |this, cx| {
|
||||||
this.load_rooms(window, cx);
|
this.set_loading(true, cx);
|
||||||
this.set_loading(false, cx);
|
|
||||||
// Send a signal to refresh all opened rooms' messages
|
|
||||||
if let Some(ids) = ChatSpace::all_panels(window, cx) {
|
|
||||||
this.refresh_rooms(ids, cx);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Load chat rooms without setting as finished
|
Signal::EventProcessed(finish) => {
|
||||||
IngesterSignal::PartialFinish => {
|
|
||||||
registry.update(cx, |this, cx| {
|
registry.update(cx, |this, cx| {
|
||||||
this.load_rooms(window, cx);
|
// Load all chat rooms in the database
|
||||||
|
this.load_rooms(finish, window, cx);
|
||||||
|
|
||||||
// Send a signal to refresh all opened rooms' messages
|
// Send a signal to refresh all opened rooms' messages
|
||||||
if let Some(ids) = ChatSpace::all_panels(window, cx) {
|
if let Some(ids) = ChatSpace::all_panels(window, cx) {
|
||||||
this.refresh_rooms(ids, cx);
|
this.refresh_rooms(ids, cx);
|
||||||
@@ -581,24 +549,25 @@ impl ChatSpace {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Add the new metadata to the registry or update the existing one
|
// Add the new metadata to the registry or update the existing one
|
||||||
IngesterSignal::Metadata(event) => {
|
Signal::Metadata(event) => {
|
||||||
registry.update(cx, |this, cx| {
|
registry.update(cx, |this, cx| {
|
||||||
this.insert_or_update_person(event, cx);
|
this.insert_or_update_person(event, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Convert the gift wrapped message to a message
|
// Convert the gift wrapped message to a message
|
||||||
IngesterSignal::GiftWrap((gift_wrap_id, event)) => {
|
Signal::Message((gift_wrap_id, event)) => {
|
||||||
registry.update(cx, |this, cx| {
|
registry.update(cx, |this, cx| {
|
||||||
this.event_to_message(gift_wrap_id, event, window, cx);
|
this.event_to_message(gift_wrap_id, event, window, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
IngesterSignal::DmRelayNotFound => {
|
// Notify the user that the DM relay is not set
|
||||||
|
Signal::DmRelayNotFound => {
|
||||||
view.update(cx, |this, cx| {
|
view.update(cx, |this, cx| {
|
||||||
this.set_no_nip17_relays(cx);
|
this.set_no_nip17_relays(cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
IngesterSignal::Notice(msg) => {
|
Signal::Notice(msg) => {
|
||||||
window.push_notification(msg.as_str(), cx);
|
window.push_notification(msg.as_str(), cx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -607,6 +576,20 @@ impl ChatSpace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_relay_list(event: &Event) -> Vec<RelayUrl> {
|
||||||
|
event
|
||||||
|
.tags
|
||||||
|
.filter_standardized(TagKind::Relay)
|
||||||
|
.filter_map(|t| {
|
||||||
|
if let TagStandard::Relay(url) = t {
|
||||||
|
Some(url.to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks if an event is belong to the current user
|
/// Checks if an event is belong to the current user
|
||||||
async fn is_self_event(event: &Event) -> Result<bool, Error> {
|
async fn is_self_event(event: &Event) -> Result<bool, Error> {
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
@@ -617,7 +600,7 @@ impl ChatSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches a single event by kind and public key
|
/// Fetches a single event by kind and public key
|
||||||
async fn fetch_single_event(kind: Kind, public_key: PublicKey) {
|
pub async fn fetch_single_event(kind: Kind, public_key: PublicKey) {
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
let auto_close =
|
let auto_close =
|
||||||
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
@@ -628,13 +611,13 @@ impl ChatSpace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_gift_wrap(relays: &[RelayUrl], public_key: PublicKey) {
|
pub async fn fetch_gift_wrap(relays: &[RelayUrl], public_key: PublicKey) {
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
let subscription_id = SubscriptionId::new("inbox");
|
let sub_id = css().gift_wrap_sub_id.clone();
|
||||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||||
|
|
||||||
if client
|
if client
|
||||||
.subscribe_with_id_to(relays.to_owned(), subscription_id, filter, None)
|
.subscribe_with_id_to(relays.to_owned(), sub_id, filter, None)
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
@@ -643,7 +626,7 @@ impl ChatSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches NIP-65 relay list for a given public key
|
/// Fetches NIP-65 relay list for a given public key
|
||||||
async fn fetch_nip65_relays(public_key: PublicKey) -> Result<(), Error> {
|
pub async fn fetch_nip65_relays(public_key: PublicKey) -> Result<(), Error> {
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
let auto_close =
|
let auto_close =
|
||||||
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
@@ -761,9 +744,7 @@ impl ChatSpace {
|
|||||||
|
|
||||||
// Send a notify to GPUI if this is a new message
|
// Send a notify to GPUI if this is a new message
|
||||||
if event.created_at >= css.init_at {
|
if event.created_at >= css.init_at {
|
||||||
ingester
|
ingester.send(Signal::Message((gift.id, event))).await;
|
||||||
.send(IngesterSignal::GiftWrap((gift.id, event)))
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is_cached
|
is_cached
|
||||||
@@ -1119,7 +1100,7 @@ impl ChatSpace {
|
|||||||
client.reset().await;
|
client.reset().await;
|
||||||
|
|
||||||
// Notify the channel about the signer being unset
|
// Notify the channel about the signer being unset
|
||||||
ingester.send(IngesterSignal::SignerUnset).await;
|
ingester.send(Signal::SignerUnset).await;
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
@@ -1221,7 +1202,7 @@ impl ChatSpace {
|
|||||||
cx: &Context<Self>,
|
cx: &Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let registry = Registry::read_global(cx);
|
let registry = Registry::read_global(cx);
|
||||||
let loading = self.has_nip17_relays && self.auth_requests.is_empty() && registry.loading;
|
let loading = registry.loading;
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
@@ -1376,7 +1357,7 @@ impl ChatSpace {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
ingester.send(IngesterSignal::ProxyDown).await;
|
ingester.send(Signal::ProxyDown).await;
|
||||||
}
|
}
|
||||||
smol::Timer::after(Duration::from_secs(1)).await;
|
smol::Timer::after(Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use client_keys::ClientKeys;
|
|||||||
use common::display::ReadableProfile;
|
use common::display::ReadableProfile;
|
||||||
use common::handle_auth::CoopAuthUrlHandler;
|
use common::handle_auth::CoopAuthUrlHandler;
|
||||||
use global::constants::{ACCOUNT_IDENTIFIER, BUNKER_TIMEOUT};
|
use global::constants::{ACCOUNT_IDENTIFIER, BUNKER_TIMEOUT};
|
||||||
use global::{ingester, nostr_client, IngesterSignal};
|
use global::{ingester, nostr_client, Signal};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, relative, rems, svg, AnyElement, App, AppContext, Context, Entity, EventEmitter,
|
div, relative, rems, svg, AnyElement, App, AppContext, Context, Entity, EventEmitter,
|
||||||
@@ -255,7 +255,7 @@ impl Account {
|
|||||||
client.unset_signer().await;
|
client.unset_signer().await;
|
||||||
|
|
||||||
// Notify the channel about the signer being unset
|
// Notify the channel about the signer being unset
|
||||||
ingester.send(IngesterSignal::SignerUnset).await;
|
ingester.send(Signal::SignerUnset).await;
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use common::nip96::nip96_upload;
|
use common::nip96::nip96_upload;
|
||||||
use global::constants::ACCOUNT_IDENTIFIER;
|
use global::constants::{ACCOUNT_IDENTIFIER, NIP17_RELAYS, NIP65_RELAYS};
|
||||||
use global::nostr_client;
|
use global::nostr_client;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, relative, rems, AnyElement, App, AppContext, AsyncWindowContext, Context, Entity,
|
div, relative, rems, AnyElement, App, AppContext, AsyncWindowContext, Context, Entity,
|
||||||
@@ -70,6 +70,7 @@ impl NewAccount {
|
|||||||
window.open_modal(cx, move |modal, _window, _cx| {
|
window.open_modal(cx, move |modal, _window, _cx| {
|
||||||
let weak_view = weak_view.clone();
|
let weak_view = weak_view.clone();
|
||||||
let current_view = current_view.clone();
|
let current_view = current_view.clone();
|
||||||
|
|
||||||
modal
|
modal
|
||||||
.alert()
|
.alert()
|
||||||
.title(shared_t!("new_account.backup_label"))
|
.title(shared_t!("new_account.backup_label"))
|
||||||
@@ -124,8 +125,44 @@ impl NewAccount {
|
|||||||
// Set the client's signer with the current keys
|
// Set the client's signer with the current keys
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
|
|
||||||
|
// Set the client's signer with the current keys
|
||||||
client.set_signer(keys).await;
|
client.set_signer(keys).await;
|
||||||
client.set_metadata(&metadata).await.ok();
|
|
||||||
|
// Set metadata
|
||||||
|
if let Err(e) = client.set_metadata(&metadata).await {
|
||||||
|
log::error!("Failed to set metadata: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set NIP-65 relays
|
||||||
|
let builder = EventBuilder::new(Kind::RelayList, "").tags(
|
||||||
|
NIP65_RELAYS.into_iter().filter_map(|url| {
|
||||||
|
if let Ok(url) = RelayUrl::parse(url) {
|
||||||
|
Some(Tag::relay_metadata(url, None))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = client.send_event_builder(builder).await {
|
||||||
|
log::error!("Failed to send NIP-65 relay list event: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set NIP-17 relays
|
||||||
|
let builder = EventBuilder::new(Kind::InboxRelays, "").tags(
|
||||||
|
NIP17_RELAYS.into_iter().filter_map(|url| {
|
||||||
|
if let Ok(url) = RelayUrl::parse(url) {
|
||||||
|
Some(Tag::relay(url))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = client.send_event_builder(builder).await {
|
||||||
|
log::error!("Failed to send messaging relay list event: {e}");
|
||||||
|
};
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use smallvec::{smallvec, SmallVec};
|
|||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::dock_area::panel::{Panel, PanelEvent};
|
use ui::dock_area::panel::{Panel, PanelEvent};
|
||||||
|
use ui::notification::Notification;
|
||||||
use ui::popup_menu::PopupMenu;
|
use ui::popup_menu::PopupMenu;
|
||||||
use ui::{divider, h_flex, v_flex, ContextModal, Icon, IconName, Sizable, StyledExt};
|
use ui::{divider, h_flex, v_flex, ContextModal, Icon, IconName, Sizable, StyledExt};
|
||||||
|
|
||||||
@@ -148,7 +149,10 @@ impl Onboarding {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
cx.update(|window, cx| {
|
cx.update(|window, cx| {
|
||||||
window.push_notification(e.to_string(), cx);
|
window.push_notification(
|
||||||
|
Notification::error(e.to_string()).title("Nostr Connect"),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use gpui::{
|
|||||||
TextAlign, UniformList, Window,
|
TextAlign, UniformList, Window,
|
||||||
};
|
};
|
||||||
use i18n::{shared_t, t};
|
use i18n::{shared_t, t};
|
||||||
use itertools::Itertools;
|
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use registry::Registry;
|
use registry::Registry;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
@@ -20,6 +19,8 @@ use ui::input::{InputEvent, InputState, TextInput};
|
|||||||
use ui::modal::ModalButtonProps;
|
use ui::modal::ModalButtonProps;
|
||||||
use ui::{h_flex, v_flex, ContextModal, IconName, Sizable, StyledExt};
|
use ui::{h_flex, v_flex, ContextModal, IconName, Sizable, StyledExt};
|
||||||
|
|
||||||
|
use crate::chatspace::ChatSpace;
|
||||||
|
|
||||||
pub fn init(kind: Kind, window: &mut Window, cx: &mut App) -> Entity<SetupRelay> {
|
pub fn init(kind: Kind, window: &mut Window, cx: &mut App) -> Entity<SetupRelay> {
|
||||||
cx.new(|cx| SetupRelay::new(kind, window, cx))
|
cx.new(|cx| SetupRelay::new(kind, window, cx))
|
||||||
}
|
}
|
||||||
@@ -82,7 +83,7 @@ impl SetupRelay {
|
|||||||
let filter = Filter::new().kind(kind).author(identity).limit(1);
|
let filter = Filter::new().kind(kind).author(identity).limit(1);
|
||||||
|
|
||||||
if let Some(event) = client.database().query(filter).await?.first() {
|
if let Some(event) = client.database().query(filter).await?.first() {
|
||||||
let relays = event
|
let relays: Vec<RelayUrl> = event
|
||||||
.tags
|
.tags
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|tag| tag.as_standardized())
|
.filter_map(|tag| tag.as_standardized())
|
||||||
@@ -95,7 +96,7 @@ impl SetupRelay {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect_vec();
|
.collect();
|
||||||
|
|
||||||
Ok(relays)
|
Ok(relays)
|
||||||
} else {
|
} else {
|
||||||
@@ -195,22 +196,32 @@ impl SetupRelay {
|
|||||||
|
|
||||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
|
let signer = client.signer().await?;
|
||||||
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
let tags: Vec<Tag> = relays
|
let tags: Vec<Tag> = relays
|
||||||
.iter()
|
.iter()
|
||||||
.map(|relay| Tag::relay(relay.clone()))
|
.map(|relay| Tag::relay(relay.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let builder = EventBuilder::new(Kind::InboxRelays, "").tags(tags);
|
let event = EventBuilder::new(Kind::InboxRelays, "")
|
||||||
|
.tags(tags)
|
||||||
|
.build(public_key)
|
||||||
|
.sign(&signer)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Set messaging relays
|
// Set messaging relays
|
||||||
client.send_event_builder(builder).await?;
|
client.send_event(&event).await?;
|
||||||
|
|
||||||
// Connect to messaging relays
|
// Connect to messaging relays
|
||||||
for relay in relays.into_iter() {
|
for relay in relays.iter() {
|
||||||
_ = client.add_relay(&relay).await;
|
_ = client.add_relay(relay).await;
|
||||||
_ = client.connect_relay(&relay).await;
|
_ = client.connect_relay(relay).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch gift wrap events
|
||||||
|
ChatSpace::fetch_gift_wrap(&relays, public_key).await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -223,11 +234,8 @@ impl SetupRelay {
|
|||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
cx.update(|window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.update(cx, |this, cx| {
|
this.set_error(e.to_string(), window, cx);
|
||||||
this.set_error(e.to_string(), window, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use ui::actions::OpenProfile;
|
|||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::context_menu::ContextMenuExt;
|
use ui::context_menu::ContextMenuExt;
|
||||||
use ui::modal::ModalButtonProps;
|
use ui::modal::ModalButtonProps;
|
||||||
|
use ui::skeleton::Skeleton;
|
||||||
use ui::{h_flex, ContextModal, StyledExt};
|
use ui::{h_flex, ContextModal, StyledExt};
|
||||||
|
|
||||||
use crate::views::screening;
|
use crate::views::screening;
|
||||||
@@ -108,7 +109,21 @@ impl RenderOnce for RoomListItem {
|
|||||||
self.handler,
|
self.handler,
|
||||||
)
|
)
|
||||||
else {
|
else {
|
||||||
return div().id(self.ix);
|
return h_flex()
|
||||||
|
.id(self.ix)
|
||||||
|
.h_9()
|
||||||
|
.w_full()
|
||||||
|
.px_1p5()
|
||||||
|
.gap_2()
|
||||||
|
.child(Skeleton::new().flex_shrink_0().size_6().rounded_full())
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex_1()
|
||||||
|
.flex()
|
||||||
|
.justify_between()
|
||||||
|
.child(Skeleton::new().w_32().h_2p5().rounded_sm())
|
||||||
|
.child(Skeleton::new().w_6().h_2p5().rounded_sm()),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ mod list_item;
|
|||||||
|
|
||||||
const FIND_DELAY: u64 = 600;
|
const FIND_DELAY: u64 = 600;
|
||||||
const FIND_LIMIT: usize = 10;
|
const FIND_LIMIT: usize = 10;
|
||||||
|
const TOTAL_SKELETONS: usize = 3;
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Sidebar> {
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Sidebar> {
|
||||||
Sidebar::new(window, cx)
|
Sidebar::new(window, cx)
|
||||||
@@ -586,6 +587,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 registry = Registry::read_global(cx);
|
let registry = Registry::read_global(cx);
|
||||||
|
let loading = registry.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.local_result.read(cx).as_ref() {
|
let rooms = if let Some(results) = self.local_result.read(cx).as_ref() {
|
||||||
@@ -601,6 +603,15 @@ impl Render for Sidebar {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get total rooms count
|
||||||
|
let mut total_rooms = rooms.len();
|
||||||
|
|
||||||
|
// If loading in progress
|
||||||
|
// Add 3 skeletons to the room list
|
||||||
|
if loading {
|
||||||
|
total_rooms += TOTAL_SKELETONS;
|
||||||
|
}
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.image_cache(self.image_cache.clone())
|
.image_cache(self.image_cache.clone())
|
||||||
.size_full()
|
.size_full()
|
||||||
@@ -690,7 +701,7 @@ impl Render for Sidebar {
|
|||||||
.child(
|
.child(
|
||||||
uniform_list(
|
uniform_list(
|
||||||
"rooms",
|
"rooms",
|
||||||
rooms.len(),
|
total_rooms,
|
||||||
cx.processor(move |this, range, _window, cx| {
|
cx.processor(move |this, range, _window, cx| {
|
||||||
this.list_items(&rooms, range, cx)
|
this.list_items(&rooms, range, cx)
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ impl Notice {
|
|||||||
|
|
||||||
/// Signals sent through the global event channel to notify UI
|
/// Signals sent through the global event channel to notify UI
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum IngesterSignal {
|
pub enum Signal {
|
||||||
/// A signal to notify UI that the client's signer has been set
|
/// A signal to notify UI that the client's signer has been set
|
||||||
SignerSet(PublicKey),
|
SignerSet(PublicKey),
|
||||||
|
|
||||||
@@ -64,13 +65,13 @@ pub enum IngesterSignal {
|
|||||||
Metadata(Event),
|
Metadata(Event),
|
||||||
|
|
||||||
/// A signal to notify UI that a new gift wrap event has been received
|
/// A signal to notify UI that a new gift wrap event has been received
|
||||||
GiftWrap((EventId, Event)),
|
Message((EventId, Event)),
|
||||||
|
|
||||||
/// A signal to notify UI that all gift wrap events have been processed
|
/// A signal to notify UI that gift wrap events still processing
|
||||||
Finish,
|
EventProcessing,
|
||||||
|
|
||||||
/// A signal to notify UI that partial processing of gift wrap events has been completed
|
/// A signal to notify UI that gift wrap events have been processed
|
||||||
PartialFinish,
|
EventProcessed(bool),
|
||||||
|
|
||||||
/// A signal to notify UI that no DM relay for current user was found
|
/// A signal to notify UI that no DM relay for current user was found
|
||||||
DmRelayNotFound,
|
DmRelayNotFound,
|
||||||
@@ -81,8 +82,8 @@ pub enum IngesterSignal {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Ingester {
|
pub struct Ingester {
|
||||||
rx: Receiver<IngesterSignal>,
|
rx: Receiver<Signal>,
|
||||||
tx: Sender<IngesterSignal>,
|
tx: Sender<Signal>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Ingester {
|
impl Default for Ingester {
|
||||||
@@ -93,15 +94,15 @@ impl Default for Ingester {
|
|||||||
|
|
||||||
impl Ingester {
|
impl Ingester {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let (tx, rx) = smol::channel::bounded::<IngesterSignal>(2048);
|
let (tx, rx) = smol::channel::bounded::<Signal>(2048);
|
||||||
Self { rx, tx }
|
Self { rx, tx }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn signals(&self) -> &Receiver<IngesterSignal> {
|
pub fn signals(&self) -> &Receiver<Signal> {
|
||||||
&self.rx
|
&self.rx
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send(&self, signal: IngesterSignal) {
|
pub async fn send(&self, signal: Signal) {
|
||||||
if let Err(e) = self.tx.send(signal).await {
|
if let Err(e) = self.tx.send(signal).await {
|
||||||
log::error!("Failed to send signal: {e}");
|
log::error!("Failed to send signal: {e}");
|
||||||
}
|
}
|
||||||
@@ -109,18 +110,28 @@ impl Ingester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A simple storage to store all runtime states that using across the application.
|
/// A simple storage to store all runtime states that using across the application.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
pub struct CoopSimpleStorage {
|
pub struct CoopSimpleStorage {
|
||||||
pub init_at: Timestamp,
|
pub init_at: Timestamp,
|
||||||
|
pub gift_wrap_sub_id: SubscriptionId,
|
||||||
|
pub gift_wrap_processing: AtomicBool,
|
||||||
pub sent_ids: RwLock<HashSet<EventId>>,
|
pub sent_ids: RwLock<HashSet<EventId>>,
|
||||||
pub resent_ids: RwLock<Vec<Output<EventId>>>,
|
pub resent_ids: RwLock<Vec<Output<EventId>>>,
|
||||||
pub resend_queue: RwLock<HashMap<EventId, RelayUrl>>,
|
pub resend_queue: RwLock<HashMap<EventId, RelayUrl>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for CoopSimpleStorage {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CoopSimpleStorage {
|
impl CoopSimpleStorage {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
init_at: Timestamp::now(),
|
init_at: Timestamp::now(),
|
||||||
|
gift_wrap_sub_id: SubscriptionId::new("inbox"),
|
||||||
|
gift_wrap_processing: AtomicBool::new(true),
|
||||||
sent_ids: RwLock::new(HashSet::new()),
|
sent_ids: RwLock::new(HashSet::new()),
|
||||||
resent_ids: RwLock::new(Vec::new()),
|
resent_ids: RwLock::new(Vec::new()),
|
||||||
resend_queue: RwLock::new(HashMap::new()),
|
resend_queue: RwLock::new(HashMap::new()),
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ impl Registry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Load all rooms from the database.
|
/// Load all rooms from the database.
|
||||||
pub fn load_rooms(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn load_rooms(&mut self, finish: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
log::info!("Starting to load chat rooms...");
|
log::info!("Starting to load chat rooms...");
|
||||||
|
|
||||||
// Get the contact bypass setting
|
// Get the contact bypass setting
|
||||||
@@ -340,8 +340,11 @@ impl Registry {
|
|||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
match task.await {
|
match task.await {
|
||||||
Ok(rooms) => {
|
Ok(rooms) => {
|
||||||
this.update_in(cx, |_, window, cx| {
|
this.update_in(cx, move |_, window, cx| {
|
||||||
cx.defer_in(window, |this, _window, cx| {
|
cx.defer_in(window, move |this, _window, cx| {
|
||||||
|
if finish {
|
||||||
|
this.set_loading(false, cx);
|
||||||
|
}
|
||||||
this.extend_rooms(rooms, cx);
|
this.extend_rooms(rooms, cx);
|
||||||
this.sort(cx);
|
this.sort(cx);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ impl RenderOnce for Skeleton {
|
|||||||
.bg(color)
|
.bg(color)
|
||||||
.with_animation(
|
.with_animation(
|
||||||
"skeleton",
|
"skeleton",
|
||||||
Animation::new(Duration::from_secs(2))
|
Animation::new(Duration::from_secs(3))
|
||||||
.repeat()
|
.repeat()
|
||||||
.with_easing(bounce(ease_in_out)),
|
.with_easing(bounce(ease_in_out)),
|
||||||
move |this, delta| {
|
move |this, delta| {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
deferred, div, relative, App, AppContext, Context, Entity, IntoElement, ParentElement, Render,
|
div, relative, App, AppContext, Context, Entity, IntoElement, ParentElement, Render,
|
||||||
SharedString, Styled, Window,
|
SharedString, Styled, Window,
|
||||||
};
|
};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
@@ -16,7 +16,7 @@ impl Tooltip {
|
|||||||
|
|
||||||
impl Render for Tooltip {
|
impl Render for Tooltip {
|
||||||
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(deferred(
|
div().child(
|
||||||
div()
|
div()
|
||||||
.font_family(".SystemUIFont")
|
.font_family(".SystemUIFont")
|
||||||
.m_3()
|
.m_3()
|
||||||
@@ -30,6 +30,6 @@ impl Render for Tooltip {
|
|||||||
.text_color(cx.theme().text_muted)
|
.text_color(cx.theme().text_muted)
|
||||||
.line_height(relative(1.25))
|
.line_height(relative(1.25))
|
||||||
.child(self.text.clone()),
|
.child(self.text.clone()),
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user