feat: add empty state to some elements

This commit is contained in:
2025-02-06 14:38:05 +07:00
parent 193aaa646e
commit cb8a348945
4 changed files with 139 additions and 82 deletions

View File

@@ -5,9 +5,9 @@ use common::{
utils::{random_name, room_hash}, utils::{random_name, room_hash},
}; };
use gpui::{ use gpui::{
div, img, impl_internal_actions, prelude::FluentBuilder, px, uniform_list, App, AppContext, div, img, impl_internal_actions, prelude::FluentBuilder, px, relative, uniform_list, App,
Context, Entity, FocusHandle, InteractiveElement, IntoElement, ParentElement, Render, AppContext, Context, Entity, FocusHandle, InteractiveElement, IntoElement, ParentElement,
SharedString, StatefulInteractiveElement, Styled, Window, Render, SharedString, StatefulInteractiveElement, Styled, TextAlign, Window,
}; };
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use serde::Deserialize; use serde::Deserialize;
@@ -319,73 +319,104 @@ impl Render for Compose {
) )
.map(|this| { .map(|this| {
if let Some(contacts) = self.contacts.read(cx).clone() { if let Some(contacts) = self.contacts.read(cx).clone() {
this.child( if contacts.is_empty() {
uniform_list( this.child(
cx.entity().clone(), div()
"contacts", .w_full()
contacts.len(), .h_24()
move |this, range, _window, cx| { .flex()
let selected = this.selected.read(cx); .flex_col()
let mut items = Vec::new(); .items_center()
.justify_center()
for ix in range { .text_align(TextAlign::Center)
let item = contacts.get(ix).unwrap().clone(); .child(
let is_select = selected.contains(&item.public_key()); div()
.text_xs()
items.push( .font_semibold()
div() .line_height(relative(1.2))
.id(ix) .child("No contacts"),
.w_full() )
.h_9() .child(
.px_2() div()
.flex() .text_xs()
.items_center() .text_color(
.justify_between() cx.theme()
.child( .base
div() .step(cx, ColorScaleStep::ELEVEN),
.flex() )
.items_center() .child("Your recently contacts will appear here."),
.gap_2() ),
.text_xs()
.child(
div().flex_shrink_0().child(
img(item.avatar()).size_6(),
),
)
.child(item.name()),
)
.when(is_select, |this| {
this.child(
Icon::new(IconName::CircleCheck)
.size_3()
.text_color(cx.theme().base.step(
cx,
ColorScaleStep::TWELVE,
)),
)
})
.hover(|this| {
this.bg(cx
.theme()
.base
.step(cx, ColorScaleStep::THREE))
})
.on_click(move |_, window, cx| {
window.dispatch_action(
Box::new(SelectContact(
item.public_key(),
)),
cx,
);
}),
);
}
items
},
) )
.h(px(300.)), } else {
) this.child(
uniform_list(
cx.entity().clone(),
"contacts",
contacts.len(),
move |this, range, _window, cx| {
let selected = this.selected.read(cx);
let mut items = Vec::new();
for ix in range {
let item = contacts.get(ix).unwrap().clone();
let is_select =
selected.contains(&item.public_key());
items.push(
div()
.id(ix)
.w_full()
.h_9()
.px_2()
.flex()
.items_center()
.justify_between()
.child(
div()
.flex()
.items_center()
.gap_2()
.text_xs()
.child(div().flex_shrink_0().child(
img(item.avatar()).size_6(),
))
.child(item.name()),
)
.when(is_select, |this| {
this.child(
Icon::new(IconName::CircleCheck)
.size_3()
.text_color(
cx.theme().base.step(
cx,
ColorScaleStep::TWELVE,
),
),
)
})
.hover(|this| {
this.bg(cx
.theme()
.base
.step(cx, ColorScaleStep::THREE))
})
.on_click(move |_, window, cx| {
window.dispatch_action(
Box::new(SelectContact(
item.public_key(),
)),
cx,
);
}),
);
}
items
},
)
.min_h(px(300.)),
)
}
} else { } else {
this.flex() this.flex()
.items_center() .items_center()

View File

@@ -2,8 +2,9 @@ use crate::views::app::{AddPanel, PanelKind};
use chat_state::registry::ChatRegistry; use chat_state::registry::ChatRegistry;
use common::utils::message_ago; use common::utils::message_ago;
use gpui::{ use gpui::{
div, img, percentage, prelude::FluentBuilder, px, Context, InteractiveElement, IntoElement, div, img, percentage, prelude::FluentBuilder, px, relative, Context, InteractiveElement,
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Window, IntoElement, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled,
TextAlign, Window,
}; };
use ui::{ use ui::{
dock_area::dock::DockPlacement, dock_area::dock::DockPlacement,
@@ -47,6 +48,30 @@ impl Inbox {
if inbox.is_loading { if inbox.is_loading {
this.children(self.render_skeleton(5)) this.children(self.render_skeleton(5))
} else if inbox.rooms.is_empty() {
this.px_1()
.w_full()
.h_20()
.flex()
.flex_col()
.items_center()
.justify_center()
.text_align(TextAlign::Center)
.rounded(px(cx.theme().radius))
.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
.child(
div()
.text_xs()
.font_semibold()
.line_height(relative(1.2))
.child("No chats"),
)
.child(
div()
.text_xs()
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
.child("Recent chats will appear here."),
)
} else { } else {
this.children(inbox.rooms.iter().map(|model| { this.children(inbox.rooms.iter().map(|model| {
let room = model.read(cx); let room = model.read(cx);

View File

@@ -2,15 +2,14 @@ use crate::views::sidebar::inbox::Inbox;
use chat_state::registry::ChatRegistry; use chat_state::registry::ChatRegistry;
use compose::Compose; use compose::Compose;
use gpui::{ use gpui::{
div, px, AnyElement, App, AppContext, BorrowAppContext, Context, Entity, EntityId, div, px, AnyElement, App, AppContext, BorrowAppContext, Context, Entity, EventEmitter,
EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
SharedString, StatefulInteractiveElement, Styled, Window, StatefulInteractiveElement, Styled, Window,
}; };
use ui::{ use ui::{
button::{Button, ButtonRounded, ButtonVariants}, button::{Button, ButtonRounded, ButtonVariants},
dock_area::panel::{Panel, PanelEvent}, dock_area::panel::{Panel, PanelEvent},
popup_menu::PopupMenu, popup_menu::PopupMenu,
scroll::ScrollbarAxis,
theme::{scale::ColorScaleStep, ActiveTheme}, theme::{scale::ColorScaleStep, ActiveTheme},
v_flex, ContextModal, Icon, IconName, Sizable, StyledExt, v_flex, ContextModal, Icon, IconName, Sizable, StyledExt,
}; };
@@ -30,7 +29,6 @@ pub struct Sidebar {
focus_handle: FocusHandle, focus_handle: FocusHandle,
// Dock // Dock
inbox: Entity<Inbox>, inbox: Entity<Inbox>,
view_id: EntityId,
} }
impl Sidebar { impl Sidebar {
@@ -46,7 +44,6 @@ impl Sidebar {
closable: true, closable: true,
zoomable: true, zoomable: true,
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
view_id: cx.entity().entity_id(),
inbox, inbox,
} }
} }
@@ -125,7 +122,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 {
v_flex() v_flex()
.scrollable(self.view_id, ScrollbarAxis::Vertical) .w_full()
.py_3() .py_3()
.gap_3() .gap_3()
.child( .child(

View File

@@ -29,10 +29,14 @@ impl NostrProfile {
/// Get contact's avatar /// Get contact's avatar
pub fn avatar(&self) -> String { pub fn avatar(&self) -> String {
if let Some(picture) = &self.metadata.picture { if let Some(picture) = &self.metadata.picture {
format!( if picture.len() > 1 {
"{}/?url={}&w=100&h=100&fit=cover&mask=circle&n=-1", format!(
IMAGE_SERVICE, picture "{}/?url={}&w=100&h=100&fit=cover&mask=circle&n=-1",
) IMAGE_SERVICE, picture
)
} else {
"brand/avatar.png".into()
}
} else { } else {
"brand/avatar.png".into() "brand/avatar.png".into()
} }