feat: wait for processing to complete (#66)
* wait instead of check eose * refactor * refactor * refactor * improve extend rooms function * .
This commit is contained in:
@@ -151,6 +151,11 @@ impl ChatSpace {
|
||||
if !state.read(cx).has_profile() {
|
||||
this.open_onboarding(window, cx);
|
||||
} else {
|
||||
// Load all chat rooms from database
|
||||
ChatRegistry::global(cx).update(cx, |this, cx| {
|
||||
this.load_rooms(window, cx);
|
||||
});
|
||||
// Open chat panels
|
||||
this.open_chats(window, cx);
|
||||
}
|
||||
},
|
||||
@@ -273,19 +278,14 @@ impl ChatSpace {
|
||||
|
||||
fn verify_messaging_relays(&self, cx: &App) -> Task<Result<bool, Error>> {
|
||||
cx.background_spawn(async move {
|
||||
let signer = shared_state().client.signer().await?;
|
||||
let client = shared_state().client();
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::InboxRelays)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
let is_exist = shared_state()
|
||||
.client
|
||||
.database()
|
||||
.query(filter)
|
||||
.await?
|
||||
.first()
|
||||
.is_some();
|
||||
let is_exist = client.database().query(filter).await?.first().is_some();
|
||||
|
||||
Ok(is_exist)
|
||||
})
|
||||
|
||||
@@ -3,9 +3,9 @@ use std::sync::Arc;
|
||||
use asset::Assets;
|
||||
use auto_update::AutoUpdater;
|
||||
use chats::ChatRegistry;
|
||||
use global::constants::APP_ID;
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
use global::constants::APP_NAME;
|
||||
use global::constants::{ALL_MESSAGES_SUB_ID, APP_ID};
|
||||
use global::{shared_state, NostrSignal};
|
||||
use gpui::{
|
||||
actions, px, size, App, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem,
|
||||
@@ -15,6 +15,7 @@ use gpui::{
|
||||
use gpui::{point, SharedString, TitlebarOptions};
|
||||
#[cfg(target_os = "linux")]
|
||||
use gpui::{WindowBackgroundAppearance, WindowDecorations};
|
||||
use nostr_sdk::SubscriptionId;
|
||||
use theme::Theme;
|
||||
use ui::Root;
|
||||
|
||||
@@ -28,17 +29,19 @@ fn main() {
|
||||
// Initialize logging
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Initialize the Global State and process events in a separate thread.
|
||||
// Must be run under async utility runtime
|
||||
nostr_sdk::async_utility::task::spawn(async move {
|
||||
shared_state().start().await;
|
||||
});
|
||||
|
||||
// Initialize the Application
|
||||
let app = Application::new()
|
||||
.with_assets(Assets)
|
||||
.with_http_client(Arc::new(reqwest_client::ReqwestClient::new()));
|
||||
|
||||
// Initialize the Global State and process events in a separate thread.
|
||||
app.background_executor()
|
||||
.spawn(async move {
|
||||
shared_state().start().await;
|
||||
})
|
||||
.detach();
|
||||
|
||||
// Run application
|
||||
app.run(move |cx| {
|
||||
// Register the `quit` function
|
||||
cx.on_action(quit);
|
||||
@@ -100,42 +103,44 @@ fn main() {
|
||||
// Initialize chat state
|
||||
chats::init(cx);
|
||||
|
||||
// Initialize chatspace (or workspace)
|
||||
let chatspace = chatspace::init(window, cx);
|
||||
let async_chatspace = chatspace.downgrade();
|
||||
|
||||
// Spawn a task to handle events from nostr channel
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
while let Ok(signal) = shared_state().global_receiver.recv().await {
|
||||
let all_messages_sub_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID);
|
||||
|
||||
while let Ok(signal) = shared_state().signal().recv().await {
|
||||
cx.update(|window, cx| {
|
||||
let chats = ChatRegistry::global(cx);
|
||||
let auto_updater = AutoUpdater::global(cx);
|
||||
|
||||
match signal {
|
||||
NostrSignal::SignerUpdated => {
|
||||
async_chatspace
|
||||
.update(cx, |this, cx| {
|
||||
this.open_chats(window, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
NostrSignal::SignerUnset => {
|
||||
async_chatspace
|
||||
.update(cx, |this, cx| {
|
||||
this.open_onboarding(window, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
NostrSignal::Eose => {
|
||||
chats.update(cx, |this, cx| {
|
||||
this.load_rooms(window, cx);
|
||||
});
|
||||
}
|
||||
NostrSignal::Event(event) => {
|
||||
chats.update(cx, |this, cx| {
|
||||
this.event_to_message(event, window, cx);
|
||||
});
|
||||
}
|
||||
// Load chat rooms and stop the loading status
|
||||
NostrSignal::Finish => {
|
||||
chats.update(cx, |this, cx| {
|
||||
this.load_rooms(window, cx);
|
||||
this.set_loading(false, cx);
|
||||
});
|
||||
}
|
||||
// Load chat rooms without setting as finished
|
||||
NostrSignal::PartialFinish => {
|
||||
chats.update(cx, |this, cx| {
|
||||
this.load_rooms(window, cx);
|
||||
});
|
||||
}
|
||||
NostrSignal::Eose(subscription_id) => {
|
||||
if subscription_id == all_messages_sub_id {
|
||||
chats.update(cx, |this, cx| {
|
||||
this.load_rooms(window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
NostrSignal::Notice(_msg) => {
|
||||
// window.push_notification(msg, cx);
|
||||
}
|
||||
NostrSignal::AppUpdate(event) => {
|
||||
auto_updater.update(cx, |this, cx| {
|
||||
this.update(event, cx);
|
||||
@@ -148,7 +153,7 @@ fn main() {
|
||||
})
|
||||
.detach();
|
||||
|
||||
Root::new(chatspace.into(), window, cx)
|
||||
Root::new(chatspace::init(window, cx).into(), window, cx)
|
||||
})
|
||||
})
|
||||
.expect("Failed to open window. Please restart the application.");
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_utility::task::spawn;
|
||||
use chats::message::Message;
|
||||
use chats::room::{Room, RoomKind, SendError};
|
||||
use common::nip96_upload;
|
||||
@@ -391,8 +390,8 @@ impl Chat {
|
||||
let (tx, rx) = oneshot::channel::<Option<Url>>();
|
||||
|
||||
// Spawn task via async utility instead of GPUI context
|
||||
spawn(async move {
|
||||
let url = match nip96_upload(&shared_state().client, nip96, file_data)
|
||||
nostr_sdk::async_utility::task::spawn(async move {
|
||||
let url = match nip96_upload(shared_state().client(), nip96, file_data)
|
||||
.await
|
||||
{
|
||||
Ok(url) => Some(url),
|
||||
|
||||
@@ -69,13 +69,10 @@ impl Compose {
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
let task: Task<Result<BTreeSet<Profile>, Error>> = cx.background_spawn(async move {
|
||||
let signer = shared_state().client.signer().await?;
|
||||
let client = shared_state().client();
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
let profiles = shared_state()
|
||||
.client
|
||||
.database()
|
||||
.contacts(public_key)
|
||||
.await?;
|
||||
let profiles = client.database().contacts(public_key).await?;
|
||||
|
||||
Ok(profiles)
|
||||
});
|
||||
@@ -134,7 +131,7 @@ impl Compose {
|
||||
let tags = Tags::from_list(tag_list);
|
||||
|
||||
let event: Task<Result<Event, anyhow::Error>> = cx.background_spawn(async move {
|
||||
let signer = shared_state().client.signer().await?;
|
||||
let signer = shared_state().client().signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
|
||||
// [IMPORTANT]
|
||||
@@ -184,7 +181,7 @@ impl Compose {
|
||||
let public_key = profile.public_key;
|
||||
|
||||
let metadata = shared_state()
|
||||
.client
|
||||
.client()
|
||||
.fetch_metadata(public_key, Duration::from_secs(2))
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
@@ -200,7 +197,7 @@ impl Compose {
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let metadata = shared_state()
|
||||
.client
|
||||
.client()
|
||||
.fetch_metadata(public_key, Duration::from_secs(2))
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -158,11 +158,9 @@ impl Login {
|
||||
|
||||
subscriptions.push(
|
||||
cx.observe_in(&active_signer, window, |this, entity, window, cx| {
|
||||
if let Some(mut signer) = entity.read(cx).clone() {
|
||||
// Automatically open auth url
|
||||
signer.auth_url_handler(CoopAuthUrlHandler);
|
||||
if let Some(signer) = entity.read(cx).as_ref() {
|
||||
// Wait for connection from remote signer
|
||||
this.wait_for_connection(signer, window, cx);
|
||||
this.wait_for_connection(signer.to_owned(), window, cx);
|
||||
}
|
||||
}),
|
||||
);
|
||||
@@ -284,11 +282,7 @@ impl Login {
|
||||
};
|
||||
|
||||
if let Some(secret_key) = secret_key {
|
||||
// Active signer is no longer needed
|
||||
self.shutdown_active_signer(cx);
|
||||
|
||||
let keys = Keys::new(secret_key);
|
||||
|
||||
Identity::global(cx).update(cx, |this, cx| {
|
||||
this.write_keys(&keys, password, cx);
|
||||
this.set_signer(keys, window, cx);
|
||||
@@ -312,9 +306,6 @@ impl Login {
|
||||
return;
|
||||
};
|
||||
|
||||
// Active signer is no longer needed
|
||||
self.shutdown_active_signer(cx);
|
||||
|
||||
// Automatically open auth url
|
||||
signer.auth_url_handler(CoopAuthUrlHandler);
|
||||
|
||||
@@ -359,10 +350,14 @@ impl Login {
|
||||
let (tx, rx) = oneshot::channel::<Option<(NostrConnectURI, NostrConnect)>>();
|
||||
|
||||
cx.background_spawn(async move {
|
||||
if let Ok(bunker_uri) = signer.bunker_uri().await {
|
||||
tx.send(Some((bunker_uri, signer))).ok();
|
||||
} else {
|
||||
tx.send(None).ok();
|
||||
match signer.bunker_uri().await {
|
||||
Ok(bunker_uri) => {
|
||||
tx.send(Some((bunker_uri, signer))).ok();
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Nostr Connect (Client): {e}");
|
||||
tx.send(None).ok();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
@@ -378,9 +373,9 @@ impl Login {
|
||||
.ok();
|
||||
} else {
|
||||
cx.update(|window, cx| {
|
||||
window.push_notification(Notification::error("Connection failed"), cx);
|
||||
// Refresh the active signer
|
||||
this.update(cx, |this, cx| {
|
||||
window.push_notification(Notification::error("Connection failed"), cx);
|
||||
this.change_relay(window, cx);
|
||||
})
|
||||
.ok();
|
||||
@@ -407,15 +402,6 @@ impl Login {
|
||||
});
|
||||
}
|
||||
|
||||
fn shutdown_active_signer(&self, cx: &Context<Self>) {
|
||||
if let Some(signer) = self.active_signer.read(cx).clone() {
|
||||
cx.background_spawn(async move {
|
||||
signer.shutdown().await;
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
fn set_error(&mut self, message: impl Into<SharedString>, cx: &mut Context<Self>) {
|
||||
self.set_logging_in(false, cx);
|
||||
self.error.update(cx, |this, cx| {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use async_utility::task::spawn;
|
||||
use common::nip96_upload;
|
||||
use global::shared_state;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
@@ -157,9 +156,9 @@ impl NewAccount {
|
||||
if let Ok(file_data) = fs::read(path).await {
|
||||
let (tx, rx) = oneshot::channel::<Url>();
|
||||
|
||||
spawn(async move {
|
||||
nostr_sdk::async_utility::task::spawn(async move {
|
||||
if let Ok(url) =
|
||||
nip96_upload(&shared_state().client, nip96, file_data).await
|
||||
nip96_upload(shared_state().client(), nip96, file_data).await
|
||||
{
|
||||
_ = tx.send(url);
|
||||
}
|
||||
|
||||
@@ -45,18 +45,14 @@ impl Onboarding {
|
||||
let local_account = cx.new(|_| None);
|
||||
|
||||
let task = cx.background_spawn(async move {
|
||||
let database = shared_state().client().database();
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::ApplicationSpecificData)
|
||||
.identifier(ACCOUNT_D)
|
||||
.limit(1);
|
||||
|
||||
if let Some(event) = shared_state()
|
||||
.client
|
||||
.database()
|
||||
.query(filter)
|
||||
.await?
|
||||
.first_owned()
|
||||
{
|
||||
if let Some(event) = database.query(filter).await?.first_owned() {
|
||||
let public_key = event
|
||||
.tags
|
||||
.public_keys()
|
||||
@@ -65,14 +61,7 @@ impl Onboarding {
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap();
|
||||
|
||||
let metadata = shared_state()
|
||||
.client
|
||||
.database()
|
||||
.metadata(public_key)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let metadata = database.metadata(public_key).await?.unwrap_or_default();
|
||||
let profile = Profile::new(public_key, metadata);
|
||||
|
||||
Ok(profile)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_utility::task::spawn;
|
||||
use common::nip96_upload;
|
||||
use global::shared_state;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
@@ -56,10 +55,10 @@ impl Profile {
|
||||
};
|
||||
|
||||
let task: Task<Result<Option<Metadata>, Error>> = cx.background_spawn(async move {
|
||||
let signer = shared_state().client.signer().await?;
|
||||
let client = shared_state().client();
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
let metadata = shared_state()
|
||||
.client
|
||||
let metadata = client
|
||||
.fetch_metadata(public_key, Duration::from_secs(2))
|
||||
.await?;
|
||||
|
||||
@@ -124,9 +123,9 @@ impl Profile {
|
||||
if let Ok(file_data) = fs::read(path).await {
|
||||
let (tx, rx) = oneshot::channel::<Url>();
|
||||
|
||||
spawn(async move {
|
||||
nostr_sdk::async_utility::task::spawn(async move {
|
||||
if let Ok(url) =
|
||||
nip96_upload(&shared_state().client, nip96, file_data).await
|
||||
nip96_upload(shared_state().client(), nip96, file_data).await
|
||||
{
|
||||
_ = tx.send(url);
|
||||
}
|
||||
@@ -193,7 +192,7 @@ impl Profile {
|
||||
}
|
||||
|
||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||
let _ = shared_state().client.set_metadata(&new_metadata).await?;
|
||||
let _ = shared_state().client().set_metadata(&new_metadata).await?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
|
||||
@@ -35,20 +35,15 @@ impl Relays {
|
||||
let input = cx.new(|cx| InputState::new(window, cx).placeholder("wss://example.com"));
|
||||
let relays = cx.new(|cx| {
|
||||
let task: Task<Result<Vec<RelayUrl>, Error>> = cx.background_spawn(async move {
|
||||
let signer = shared_state().client.signer().await?;
|
||||
let client = shared_state().client();
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::InboxRelays)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
if let Some(event) = shared_state()
|
||||
.client
|
||||
.database()
|
||||
.query(filter)
|
||||
.await?
|
||||
.first_owned()
|
||||
{
|
||||
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
||||
let relays = event
|
||||
.tags
|
||||
.filter(TagKind::Relay)
|
||||
@@ -111,23 +106,18 @@ impl Relays {
|
||||
|
||||
let relays = self.relays.read(cx).clone();
|
||||
let task: Task<Result<EventId, Error>> = cx.background_spawn(async move {
|
||||
let signer = shared_state().client.signer().await?;
|
||||
let client = shared_state().client();
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
|
||||
// If user didn't have any NIP-65 relays, add default ones
|
||||
if shared_state()
|
||||
.client
|
||||
.database()
|
||||
.relay_list(public_key)
|
||||
.await?
|
||||
.is_empty()
|
||||
{
|
||||
if client.database().relay_list(public_key).await?.is_empty() {
|
||||
let builder = EventBuilder::relay_list(vec![
|
||||
(RelayUrl::parse("wss://relay.damus.io/").unwrap(), None),
|
||||
(RelayUrl::parse("wss://relay.primal.net/").unwrap(), None),
|
||||
]);
|
||||
|
||||
if let Err(e) = shared_state().client.send_event_builder(builder).await {
|
||||
if let Err(e) = client.send_event_builder(builder).await {
|
||||
log::error!("Failed to send relay list event: {}", e);
|
||||
}
|
||||
}
|
||||
@@ -138,22 +128,21 @@ impl Relays {
|
||||
.collect();
|
||||
|
||||
let builder = EventBuilder::new(Kind::InboxRelays, "").tags(tags);
|
||||
let output = shared_state().client.send_event_builder(builder).await?;
|
||||
let output = client.send_event_builder(builder).await?;
|
||||
|
||||
// Connect to messaging relays
|
||||
for relay in relays.into_iter() {
|
||||
_ = shared_state().client.add_relay(&relay).await;
|
||||
_ = shared_state().client.connect_relay(&relay).await;
|
||||
_ = client.add_relay(&relay).await;
|
||||
_ = client.connect_relay(&relay).await;
|
||||
}
|
||||
|
||||
let sub_id = SubscriptionId::new(NEW_MESSAGE_SUB_ID);
|
||||
|
||||
// Close old subscription
|
||||
shared_state().client.unsubscribe(&sub_id).await;
|
||||
client.unsubscribe(&sub_id).await;
|
||||
|
||||
// Subscribe to new messages
|
||||
if let Err(e) = shared_state()
|
||||
.client
|
||||
if let Err(e) = client
|
||||
.subscribe_with_id(
|
||||
sub_id,
|
||||
Filter::new()
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::collections::BTreeSet;
|
||||
use std::ops::Range;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_utility::task::spawn;
|
||||
use chats::room::{Room, RoomKind};
|
||||
use chats::{ChatRegistry, RoomEmitter};
|
||||
use common::debounced_delay::DebouncedDelay;
|
||||
@@ -12,10 +11,10 @@ use global::constants::{DEFAULT_MODAL_WIDTH, SEARCH_RELAYS};
|
||||
use global::shared_state;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, px, rems, uniform_list, AnyElement, App, AppContext, ClipboardItem, Context, Entity,
|
||||
EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render,
|
||||
RetainAllImageCache, SharedString, StatefulInteractiveElement, Styled, Subscription, Task,
|
||||
Window,
|
||||
div, px, relative, rems, uniform_list, AnyElement, App, AppContext, ClipboardItem, Context,
|
||||
Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement,
|
||||
Render, RetainAllImageCache, SharedString, StatefulInteractiveElement, Styled, Subscription,
|
||||
Task, Window,
|
||||
};
|
||||
use identity::Identity;
|
||||
use itertools::Itertools;
|
||||
@@ -26,6 +25,7 @@ use theme::ActiveTheme;
|
||||
use ui::avatar::Avatar;
|
||||
use ui::button::{Button, ButtonRounded, ButtonVariants};
|
||||
use ui::dock_area::panel::{Panel, PanelEvent};
|
||||
use ui::indicator::Indicator;
|
||||
use ui::input::{InputEvent, InputState, TextInput};
|
||||
use ui::popup_menu::PopupMenu;
|
||||
use ui::skeleton::Skeleton;
|
||||
@@ -145,13 +145,14 @@ impl Sidebar {
|
||||
let query = self.find_input.read(cx).value().clone();
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let client = shared_state().client();
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::Metadata)
|
||||
.search(query.to_lowercase())
|
||||
.limit(FIND_LIMIT);
|
||||
|
||||
let events = shared_state()
|
||||
.client
|
||||
let events = client
|
||||
.fetch_events_from(SEARCH_RELAYS, filter, Duration::from_secs(3))
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -161,12 +162,8 @@ impl Sidebar {
|
||||
let mut rooms = BTreeSet::new();
|
||||
let (tx, rx) = smol::channel::bounded::<Room>(10);
|
||||
|
||||
spawn(async move {
|
||||
let signer = shared_state()
|
||||
.client
|
||||
.signer()
|
||||
.await
|
||||
.expect("signer is required");
|
||||
nostr_sdk::async_utility::task::spawn(async move {
|
||||
let signer = client.signer().await.expect("signer is required");
|
||||
let public_key = signer.get_public_key().await.expect("error");
|
||||
|
||||
for event in events.into_iter() {
|
||||
@@ -349,7 +346,46 @@ impl Sidebar {
|
||||
});
|
||||
}
|
||||
|
||||
fn render_account(&self, profile: &Profile, cx: &Context<Self>) -> impl IntoElement {
|
||||
fn open_loading_modal(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
window.open_modal(cx, move |this, _window, cx| {
|
||||
const BODY_1: &str =
|
||||
"Coop is downloading all your messages from the messaging relays. \
|
||||
Depending on your total number of messages, this process may take up to \
|
||||
15 minutes if you're using Nostr Connect.";
|
||||
const BODY_2: &str =
|
||||
"Please be patient - you only need to do this full download once. \
|
||||
Next time, Coop will only download new messages.";
|
||||
const DESCRIPTION: &str = "You still can use the app normally \
|
||||
while messages are processing in the background";
|
||||
|
||||
this.child(
|
||||
div()
|
||||
.pt_8()
|
||||
.pb_4()
|
||||
.px_4()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.child(BODY_1)
|
||||
.child(BODY_2),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(DESCRIPTION),
|
||||
),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
fn account(&self, profile: &Profile, cx: &Context<Self>) -> impl IntoElement {
|
||||
let proxy = AppSettings::get_global(cx).settings.proxy_user_avatars;
|
||||
|
||||
div()
|
||||
@@ -396,7 +432,7 @@ impl Sidebar {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_skeleton(&self, total: i32) -> impl IntoIterator<Item = impl IntoElement> {
|
||||
fn skeletons(&self, total: i32) -> impl IntoIterator<Item = impl IntoElement> {
|
||||
(0..total).map(|_| {
|
||||
div()
|
||||
.h_9()
|
||||
@@ -406,7 +442,14 @@ impl Sidebar {
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(Skeleton::new().flex_shrink_0().size_6().rounded_full())
|
||||
.child(Skeleton::new().w_40().h_4().rounded_sm())
|
||||
.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()),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -473,7 +516,7 @@ impl Focusable for Sidebar {
|
||||
impl Render for Sidebar {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let chats = ChatRegistry::get_global(cx);
|
||||
|
||||
// Get rooms from either search results or the chat registry
|
||||
let rooms = if let Some(results) = self.local_result.read(cx) {
|
||||
results.to_owned()
|
||||
} else {
|
||||
@@ -488,12 +531,13 @@ impl Render for Sidebar {
|
||||
div()
|
||||
.image_cache(self.image_cache.clone())
|
||||
.size_full()
|
||||
.relative()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
// Account
|
||||
.when_some(Identity::get_global(cx).profile(), |this, profile| {
|
||||
this.child(self.render_account(&profile, cx))
|
||||
this.child(self.account(&profile, cx))
|
||||
})
|
||||
// Search Input
|
||||
.child(
|
||||
@@ -528,6 +572,7 @@ impl Render for Sidebar {
|
||||
items
|
||||
}))
|
||||
})
|
||||
// Chat Rooms
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
@@ -623,13 +668,14 @@ impl Render for Sidebar {
|
||||
)
|
||||
}),
|
||||
)
|
||||
.when(chats.wait_for_eose, |this| {
|
||||
.when(chats.loading, |this| {
|
||||
this.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.children(self.render_skeleton(10)),
|
||||
.children(self.skeletons(1)),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
@@ -643,5 +689,61 @@ impl Render for Sidebar {
|
||||
.h_full(),
|
||||
),
|
||||
)
|
||||
.when(chats.loading, |this| {
|
||||
this.child(
|
||||
div().absolute().bottom_4().px_4().child(
|
||||
div()
|
||||
.p_1()
|
||||
.w_full()
|
||||
.rounded_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.bg(cx.theme().panel_background)
|
||||
.shadow_sm()
|
||||
// Empty div
|
||||
.child(div().size_6().flex_shrink_0())
|
||||
// Loading indicator
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_xs()
|
||||
.text_center()
|
||||
.child(
|
||||
div()
|
||||
.font_semibold()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.line_height(relative(1.2))
|
||||
.child(Indicator::new().xsmall())
|
||||
.child("Retrieving messages..."),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child("This may take some time"),
|
||||
),
|
||||
)
|
||||
// Info button
|
||||
.child(
|
||||
Button::new("help")
|
||||
.icon(IconName::Info)
|
||||
.tooltip("Why you're seeing this")
|
||||
.small()
|
||||
.ghost()
|
||||
.rounded(ButtonRounded::Full)
|
||||
.flex_shrink_0()
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.open_loading_modal(window, cx)
|
||||
})),
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user