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},
};
use gpui::{
div, img, impl_internal_actions, prelude::FluentBuilder, px, uniform_list, App, AppContext,
Context, Entity, FocusHandle, InteractiveElement, IntoElement, ParentElement, Render,
SharedString, StatefulInteractiveElement, Styled, Window,
div, img, impl_internal_actions, prelude::FluentBuilder, px, relative, uniform_list, App,
AppContext, Context, Entity, FocusHandle, InteractiveElement, IntoElement, ParentElement,
Render, SharedString, StatefulInteractiveElement, Styled, TextAlign, Window,
};
use nostr_sdk::prelude::*;
use serde::Deserialize;
@@ -319,73 +319,104 @@ impl Render for Compose {
)
.map(|this| {
if let Some(contacts) = self.contacts.read(cx).clone() {
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
},
if contacts.is_empty() {
this.child(
div()
.w_full()
.h_24()
.flex()
.flex_col()
.items_center()
.justify_center()
.text_align(TextAlign::Center)
.child(
div()
.text_xs()
.font_semibold()
.line_height(relative(1.2))
.child("No contacts"),
)
.child(
div()
.text_xs()
.text_color(
cx.theme()
.base
.step(cx, ColorScaleStep::ELEVEN),
)
.child("Your recently contacts will appear here."),
),
)
.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 {
this.flex()
.items_center()

View File

@@ -2,8 +2,9 @@ use crate::views::app::{AddPanel, PanelKind};
use chat_state::registry::ChatRegistry;
use common::utils::message_ago;
use gpui::{
div, img, percentage, prelude::FluentBuilder, px, Context, InteractiveElement, IntoElement,
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Window,
div, img, percentage, prelude::FluentBuilder, px, relative, Context, InteractiveElement,
IntoElement, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled,
TextAlign, Window,
};
use ui::{
dock_area::dock::DockPlacement,
@@ -47,6 +48,30 @@ impl Inbox {
if inbox.is_loading {
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 {
this.children(inbox.rooms.iter().map(|model| {
let room = model.read(cx);

View File

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

View File

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