From 2cc71e32784d6196cdaf585b3c70ff15f368f167 Mon Sep 17 00:00:00 2001 From: reya Date: Wed, 28 Jan 2026 06:35:13 +0700 Subject: [PATCH] add loading indicator --- crates/coop/src/sidebar/list_item.rs | 140 ++++++++++----------------- crates/coop/src/sidebar/mod.rs | 59 ++++++----- 2 files changed, 88 insertions(+), 111 deletions(-) diff --git a/crates/coop/src/sidebar/list_item.rs b/crates/coop/src/sidebar/list_item.rs index e5d3946..5ad52fc 100644 --- a/crates/coop/src/sidebar/list_item.rs +++ b/crates/coop/src/sidebar/list_item.rs @@ -1,7 +1,8 @@ use std::rc::Rc; -use chat::{ChatRegistry, RoomKind}; +use chat::RoomKind; use chat_ui::{CopyPublicKey, OpenPublicKey}; +use dock::ClosePanel; use gpui::prelude::FluentBuilder; use gpui::{ div, rems, App, ClickEvent, InteractiveElement, IntoElement, ParentElement as _, RenderOnce, @@ -13,7 +14,6 @@ use theme::ActiveTheme; use ui::avatar::Avatar; use ui::context_menu::ContextMenuExt; use ui::modal::ModalButtonProps; -use ui::skeleton::Skeleton; use ui::{h_flex, StyledExt, WindowExtension}; use crate::views::screening; @@ -21,7 +21,6 @@ use crate::views::screening; #[derive(IntoElement)] pub struct RoomListItem { ix: usize, - room_id: Option, public_key: Option, name: Option, avatar: Option, @@ -35,7 +34,6 @@ impl RoomListItem { pub fn new(ix: usize) -> Self { Self { ix, - room_id: None, public_key: None, name: None, avatar: None, @@ -45,11 +43,6 @@ impl RoomListItem { } } - pub fn room_id(mut self, room_id: u64) -> Self { - self.room_id = Some(room_id); - self - } - pub fn public_key(mut self, public_key: PublicKey) -> Self { self.public_key = Some(public_key); self @@ -89,41 +82,6 @@ impl RenderOnce for RoomListItem { let hide_avatar = AppSettings::get_hide_avatar(cx); let screening = AppSettings::get_screening(cx); - let ( - Some(public_key), - Some(room_id), - Some(name), - Some(avatar), - Some(created_at), - Some(kind), - Some(handler), - ) = ( - self.public_key, - self.room_id, - self.name, - self.avatar, - self.created_at, - self.kind, - self.handler, - ) - else { - 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(cx.theme().radius)) - .child(Skeleton::new().w_6().h_2p5().rounded(cx.theme().radius)), - ); - }; - h_flex() .id(self.ix) .h_9() @@ -133,14 +91,16 @@ impl RenderOnce for RoomListItem { .text_sm() .rounded(cx.theme().radius) .when(!hide_avatar, |this| { - this.child( - div() - .flex_shrink_0() - .size_6() - .rounded_full() - .overflow_hidden() - .child(Avatar::new(avatar).size(rems(1.5))), - ) + this.when_some(self.avatar, |this, avatar| { + this.child( + div() + .flex_shrink_0() + .size_6() + .rounded_full() + .overflow_hidden() + .child(Avatar::new(avatar).size(rems(1.5))), + ) + }) }) .child( div() @@ -148,22 +108,24 @@ impl RenderOnce for RoomListItem { .flex() .items_center() .justify_between() - .child( - div() - .flex_1() - .line_clamp(1) - .text_ellipsis() - .truncate() - .font_medium() - .child(name), - ) + .when_some(self.name, |this, name| { + this.child( + div() + .flex_1() + .line_clamp(1) + .text_ellipsis() + .truncate() + .font_medium() + .child(name), + ) + }) .child( h_flex() .gap_1p5() .flex_shrink_0() .text_xs() .text_color(cx.theme().text_placeholder) - .child(created_at) + .when_some(self.created_at, |this, created_at| this.child(created_at)) .when_some(self.kind, |this, kind| { this.when(kind == RoomKind::Request, |this| { this.child( @@ -174,34 +136,36 @@ impl RenderOnce for RoomListItem { ), ) .hover(|this| this.bg(cx.theme().elevated_surface_background)) - .context_menu(move |this, _window, _cx| { - this.menu("View Profile", Box::new(OpenPublicKey(public_key))) - .menu("Copy Public Key", Box::new(CopyPublicKey(public_key))) - }) - .on_click(move |event, window, cx| { - handler(event, window, cx); + .when_some(self.public_key, |this, public_key| { + this.context_menu(move |this, _window, _cx| { + this.menu("View Profile", Box::new(OpenPublicKey(public_key))) + .menu("Copy Public Key", Box::new(CopyPublicKey(public_key))) + }) + .when_some(self.handler, |this, handler| { + this.on_click(move |event, window, cx| { + handler(event, window, cx); - if kind != RoomKind::Ongoing && screening { - let screening = screening::init(public_key, window, cx); + if self.kind != Some(RoomKind::Ongoing) && screening { + let screening = screening::init(public_key, window, cx); - window.open_modal(cx, move |this, _window, _cx| { - this.confirm() - .child(screening.clone()) - .button_props( - ModalButtonProps::default() - .cancel_text("Ignore") - .ok_text("Response"), - ) - .on_cancel(move |_event, _window, cx| { - ChatRegistry::global(cx).update(cx, |this, cx| { - this.close_room(room_id, cx); - }); - // false to prevent closing the modal - // modal will be closed after closing panel - false - }) - }); - } + window.open_modal(cx, move |this, _window, _cx| { + this.confirm() + .child(screening.clone()) + .button_props( + ModalButtonProps::default() + .cancel_text("Ignore") + .ok_text("Response"), + ) + .on_cancel(move |_event, window, cx| { + window.dispatch_action(Box::new(ClosePanel), cx); + // Prevent closing the modal on click + // Modal will be automatically closed after closing panel + false + }) + }); + } + }) + }) }) } } diff --git a/crates/coop/src/sidebar/mod.rs b/crates/coop/src/sidebar/mod.rs index a369a84..bf9a142 100644 --- a/crates/coop/src/sidebar/mod.rs +++ b/crates/coop/src/sidebar/mod.rs @@ -20,6 +20,7 @@ use state::{NostrRegistry, GIFTWRAP_SUBSCRIPTION}; use theme::{ActiveTheme, CLIENT_SIDE_DECORATION_ROUNDING, TITLEBAR_HEIGHT}; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; +use ui::indicator::Indicator; use ui::input::{InputEvent, InputState, TextInput}; use ui::{h_flex, v_flex, Icon, IconName, Sizable, StyledExt, WindowExtension}; @@ -539,23 +540,22 @@ impl Sidebar { .flatten() .enumerate() .map(|(ix, item)| { - let this = item.read(cx); - let room_id = this.id; - let member = this.display_member(cx); + let room = item.read(cx); + let id = room.id; + let public_key = room.display_member(cx).public_key(); let handler = cx.listener({ move |this, _ev, window, cx| { - this.open(room_id, window, cx); + this.open(id, window, cx); } }); RoomListItem::new(range.start + ix) - .room_id(room_id) - .name(this.display_name(cx)) - .avatar(this.display_image(cx)) - .public_key(member.public_key()) - .kind(this.kind) - .created_at(this.created_at.to_ago()) + .name(room.display_name(cx)) + .avatar(room.display_image(cx)) + .public_key(public_key) + .kind(room.kind) + .created_at(room.created_at.to_ago()) .on_click(handler) .into_any_element() }) @@ -587,20 +587,12 @@ impl Render for Sidebar { let chat = ChatRegistry::global(cx); let loading = chat.read(cx).loading(); - // Get rooms from either search results or the chat registry - let rooms = match self.search_results.read(cx).as_ref() { - Some(results) => results, - None => chat.read(cx).rooms(cx), + // Get total rooms + let total_rooms = match self.search_results.read(cx).as_ref() { + Some(results) => results.len(), + None => chat.read(cx).rooms(cx).len(), }; - // Get total rooms count - let mut total_rooms = rooms.len(); - - // Add 3 dummy rooms to display as skeletons - if loading { - total_rooms += 3 - } - v_flex() .on_action(cx.listener(Self::on_reload)) .on_action(cx.listener(Self::on_manage)) @@ -710,7 +702,28 @@ impl Render for Sidebar { }), ) .h_full(), - ), + ) + .when(loading, |this| { + this.child( + div().absolute().top_2().left_0().w_full().px_8().child( + h_flex() + .gap_2() + .w_full() + .h_9() + .justify_center() + .bg(cx.theme().background.opacity(0.85)) + .border_color(cx.theme().border_disabled) + .border_1() + .when(cx.theme().shadow, |this| this.shadow_sm()) + .rounded_full() + .text_xs() + .font_semibold() + .text_color(cx.theme().text_muted) + .child(Indicator::new().small().color(cx.theme().icon_accent)) + .child(SharedString::from("Getting messages...")), + ), + ) + }), ) } }