chore: add missing ui elements (#153)

* add empty state

* .

* update welcome panel
This commit is contained in:
reya
2025-09-16 19:59:03 +07:00
committed by GitHub
parent 9880a3ed3d
commit c67b223a53
13 changed files with 183 additions and 110 deletions

View File

@@ -26,7 +26,7 @@ use smallvec::{smallvec, SmallVec};
use smol::fs;
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonRounded, ButtonVariants};
use ui::button::{Button, ButtonVariants};
use ui::dock_area::panel::{Panel, PanelEvent};
use ui::emoji_picker::EmojiPicker;
use ui::input::{InputEvent, InputState, TextInput};
@@ -761,7 +761,7 @@ impl Chat {
.label(t!("common.resend"))
.danger()
.xsmall()
.rounded(ButtonRounded::Full)
.rounded()
.on_click(cx.listener(
move |this, _, window, cx| {
this.resend_message(&id, window, cx);
@@ -1031,45 +1031,6 @@ impl Chat {
}
fn render_actions(&self, id: &EventId, cx: &Context<Self>) -> impl IntoElement {
let reply = Button::new("reply")
.icon(IconName::Reply)
.tooltip(t!("chat.reply_button"))
.small()
.ghost()
.on_click({
let id = id.to_owned();
cx.listener(move |this, _event, _window, cx| {
this.reply_to(&id, cx);
})
})
.into_any_element();
let copy = Button::new("copy")
.icon(IconName::Copy)
.tooltip(t!("chat.copy_message_button"))
.small()
.ghost()
.on_click({
let id = id.to_owned();
cx.listener(move |this, _event, _window, cx| {
this.copy_message(&id, cx);
})
})
.into_any_element();
let more = Button::new("seen-on")
.icon(IconName::Ellipsis)
.small()
.ghost()
.popup_menu({
let id = id.to_owned();
move |this, _window, _cx| {
// TODO: add more actions
this.menu(t!("common.seen_on"), Box::new(SeenOn(id)))
}
})
.into_any_element();
h_flex()
.p_0p5()
.gap_1()
@@ -1082,7 +1043,45 @@ impl Chat {
.border_1()
.border_color(cx.theme().border)
.bg(cx.theme().background)
.children(vec![reply, copy, more])
.child(
Button::new("reply")
.icon(IconName::Reply)
.tooltip(t!("chat.reply_button"))
.small()
.ghost()
.on_click({
let id = id.to_owned();
cx.listener(move |this, _event, _window, cx| {
this.reply_to(&id, cx);
})
}),
)
.child(
Button::new("copy")
.icon(IconName::Copy)
.tooltip(t!("chat.copy_message_button"))
.small()
.ghost()
.on_click({
let id = id.to_owned();
cx.listener(move |this, _event, _window, cx| {
this.copy_message(&id, cx);
})
}),
)
.child(div().flex_shrink_0().h_4().w_px().bg(cx.theme().border))
.child(
Button::new("seen-on")
.icon(IconName::Ellipsis)
.small()
.ghost()
.popup_menu({
let id = id.to_owned();
move |this, _window, _cx| {
this.menu(t!("common.seen_on"), Box::new(SeenOn(id)))
}
}),
)
.group_hover("", |this| this.visible())
}

View File

@@ -22,7 +22,7 @@ use smallvec::{smallvec, SmallVec};
use smol::Timer;
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonRounded, ButtonVariants};
use ui::button::{Button, ButtonVariants};
use ui::input::{InputEvent, InputState, TextInput};
use ui::notification::Notification;
use ui::{h_flex, v_flex, ContextModal, Disableable, Icon, IconName, Sizable, StyledExt};
@@ -34,7 +34,7 @@ pub fn compose_button() -> impl IntoElement {
.ghost_alt()
.cta()
.small()
.rounded(ButtonRounded::Full)
.rounded()
.on_click(move |_, window, cx| {
let compose = cx.new(|cx| Compose::new(window, cx));
let title = SharedString::new(t!("sidebar.direct_messages"));

View File

@@ -14,7 +14,7 @@ use settings::AppSettings;
use smol::fs;
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonRounded, ButtonVariants};
use ui::button::{Button, ButtonVariants};
use ui::dock_area::panel::{Panel, PanelEvent};
use ui::input::{InputState, TextInput};
use ui::modal::ModalButtonProps;
@@ -352,7 +352,7 @@ impl Render for NewAccount {
.label(t!("common.upload"))
.ghost()
.small()
.rounded(ButtonRounded::Full)
.rounded()
.disabled(self.submitting || self.uploading)
.loading(self.uploading)
.on_click(cx.listener(move |this, _, window, cx| {

View File

@@ -10,7 +10,7 @@ use registry::Registry;
use settings::AppSettings;
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonRounded, ButtonVariants};
use ui::button::{Button, ButtonVariants};
use ui::input::{InputState, TextInput};
use ui::modal::ModalButtonProps;
use ui::switch::Switch;
@@ -169,7 +169,7 @@ impl Render for Preferences {
.label("Messaging Relays")
.xsmall()
.ghost_alt()
.rounded(ButtonRounded::Full)
.rounded()
.on_click(cx.listener(move |this, _e, window, cx| {
this.open_relays(window, cx);
})),

View File

@@ -17,7 +17,7 @@ use settings::AppSettings;
use smallvec::{smallvec, SmallVec};
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonRounded, ButtonVariants};
use ui::button::{Button, ButtonVariants};
use ui::indicator::Indicator;
use ui::{h_flex, v_flex, ContextModal, Icon, IconName, Sizable, StyledExt};
@@ -268,7 +268,7 @@ impl Render for Screening {
.label(t!("profile.njump"))
.secondary()
.small()
.rounded(ButtonRounded::Full)
.rounded()
.on_click(cx.listener(move |this, _e, window, cx| {
this.open_njump(window, cx);
})),
@@ -278,7 +278,7 @@ impl Render for Screening {
.tooltip(t!("screening.report"))
.icon(IconName::Report)
.danger()
.rounded(ButtonRounded::Full)
.rounded()
.on_click(cx.listener(move |this, _e, window, cx| {
this.report(window, cx);
})),
@@ -330,7 +330,7 @@ impl Render for Screening {
.icon(IconName::Info)
.xsmall()
.ghost()
.rounded(ButtonRounded::Full)
.rounded()
.tooltip(t!("screening.active_tooltip")),
),
)
@@ -402,7 +402,7 @@ impl Render for Screening {
.icon(IconName::Info)
.xsmall()
.ghost()
.rounded(ButtonRounded::Full)
.rounded()
.on_click(cx.listener(
move |this, _, window, cx| {
this.mutual_contacts(window, cx);

View File

@@ -14,7 +14,7 @@ use nostr_sdk::prelude::*;
use registry::Registry;
use smallvec::{smallvec, SmallVec};
use theme::ActiveTheme;
use ui::button::{Button, ButtonRounded, ButtonVariants};
use ui::button::{Button, ButtonVariants};
use ui::input::{InputEvent, InputState, TextInput};
use ui::modal::ModalButtonProps;
use ui::{h_flex, v_flex, ContextModal, IconName, Sizable, StyledExt};
@@ -33,7 +33,7 @@ where
.label(label)
.warning()
.xsmall()
.rounded(ButtonRounded::Full)
.rounded()
.on_click(move |_, window, cx| {
let view = cx.new(|cx| SetupRelay::new(Kind::InboxRelays, window, cx));
let weak_view = view.downgrade();

View File

@@ -9,9 +9,9 @@ use global::constants::{BOOTSTRAP_RELAYS, SEARCH_RELAYS};
use global::{css, nostr_client, UnwrappingStatus};
use gpui::prelude::FluentBuilder;
use gpui::{
div, uniform_list, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle,
Focusable, InteractiveElement, IntoElement, ParentElement, Render, RetainAllImageCache,
SharedString, Styled, Subscription, Task, Window,
div, relative, uniform_list, AnyElement, App, AppContext, Context, Entity, EventEmitter,
FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render,
RetainAllImageCache, SharedString, Styled, Subscription, Task, Window,
};
use gpui_tokio::Tokio;
use i18n::{shared_t, t};
@@ -23,7 +23,7 @@ use registry::{Registry, RegistryEvent};
use settings::AppSettings;
use smallvec::{smallvec, SmallVec};
use theme::ActiveTheme;
use ui::button::{Button, ButtonRounded, ButtonVariants};
use ui::button::{Button, ButtonVariants};
use ui::dock_area::panel::{Panel, PanelEvent};
use ui::input::{InputEvent, InputState, TextInput};
use ui::popup_menu::{PopupMenu, PopupMenuExt};
@@ -669,6 +669,7 @@ impl Focusable for Sidebar {
impl Render for Sidebar {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let registry = Registry::read_global(cx);
let loading = registry.unwrapping_status.read(cx) != &UnwrappingStatus::Complete;
// Get rooms from either search results or the chat registry
let rooms = if let Some(results) = self.local_result.read(cx).as_ref() {
@@ -688,7 +689,7 @@ impl Render for Sidebar {
let mut total_rooms = rooms.len();
// Add 3 dummy rooms to display as skeletons
if registry.unwrapping_status.read(cx) != &UnwrappingStatus::Complete {
if loading {
total_rooms += 3
}
@@ -752,7 +753,7 @@ impl Render for Sidebar {
.cta()
.bold()
.secondary()
.rounded(ButtonRounded::Full)
.rounded()
.selected(self.filter(&RoomKind::Ongoing, cx))
.on_click(cx.listener(|this, _, _, cx| {
this.set_filter(RoomKind::Ongoing, cx);
@@ -773,7 +774,7 @@ impl Render for Sidebar {
.cta()
.bold()
.secondary()
.rounded(ButtonRounded::Full)
.rounded()
.selected(!self.filter(&RoomKind::Ongoing, cx))
.on_click(cx.listener(|this, _, _, cx| {
this.set_filter(RoomKind::default(), cx);
@@ -791,7 +792,7 @@ impl Render for Sidebar {
.icon(IconName::Ellipsis)
.xsmall()
.ghost()
.rounded(ButtonRounded::Full)
.rounded()
.popup_menu(move |this, _window, _cx| {
this.menu(
t!("sidebar.reload_menu"),
@@ -805,6 +806,57 @@ impl Render for Sidebar {
),
),
)
.when(!loading && total_rooms == 0, |this| {
this.map(|this| {
if self.filter(&RoomKind::Ongoing, cx) {
this.child(
v_flex()
.py_2()
.gap_1p5()
.items_center()
.justify_center()
.text_center()
.child(
div()
.text_sm()
.font_semibold()
.line_height(relative(1.25))
.child(shared_t!("sidebar.no_conversations")),
)
.child(
div()
.text_xs()
.text_color(cx.theme().text_muted)
.line_height(relative(1.25))
.child(shared_t!("sidebar.no_conversations_label")),
),
)
} else {
this.child(
v_flex()
.py_2()
.gap_1p5()
.items_center()
.justify_center()
.text_center()
.child(
div()
.text_sm()
.font_semibold()
.line_height(relative(1.25))
.child(shared_t!("sidebar.no_requests")),
)
.child(
div()
.text_xs()
.text_color(cx.theme().text_muted)
.line_height(relative(1.25))
.child(shared_t!("sidebar.no_requests_label")),
),
)
}
})
})
.child(
uniform_list(
"rooms",

View File

@@ -1,12 +1,13 @@
use gpui::{
div, svg, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
IntoElement, ParentElement, Render, SharedString, Styled, Window,
InteractiveElement, IntoElement, ParentElement, Render, SharedString,
StatefulInteractiveElement, Styled, Window,
};
use theme::ActiveTheme;
use ui::button::Button;
use ui::dock_area::panel::{Panel, PanelEvent};
use ui::popup_menu::PopupMenu;
use ui::StyledExt;
use ui::{v_flex, StyledExt};
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Welcome> {
Welcome::new(window, cx)
@@ -14,8 +15,7 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity<Welcome> {
pub struct Welcome {
name: SharedString,
closable: bool,
zoomable: bool,
version: SharedString,
focus_handle: FocusHandle,
}
@@ -25,10 +25,11 @@ impl Welcome {
}
fn view(_window: &mut Window, cx: &mut Context<Self>) -> Self {
let version = SharedString::from(format!("Version: {}", env!("CARGO_PKG_VERSION")));
Self {
version,
name: "Welcome".into(),
closable: true,
zoomable: true,
focus_handle: cx.focus_handle(),
}
}
@@ -39,16 +40,15 @@ impl Panel for Welcome {
self.name.clone()
}
fn title(&self, _cx: &App) -> AnyElement {
"👋".into_any_element()
}
fn closable(&self, _cx: &App) -> bool {
self.closable
}
fn zoomable(&self, _cx: &App) -> bool {
self.zoomable
fn title(&self, cx: &App) -> AnyElement {
div()
.child(
svg()
.path("brand/coop.svg")
.size_4()
.text_color(cx.theme().element_background),
)
.into_any_element()
}
fn popup_menu(&self, menu: PopupMenu, _cx: &App) -> PopupMenu {
@@ -76,11 +76,10 @@ impl Render for Welcome {
.items_center()
.justify_center()
.child(
div()
.flex()
.flex_col()
v_flex()
.gap_2()
.items_center()
.gap_1()
.justify_center()
.child(
svg()
.path("brand/coop.svg")
@@ -88,11 +87,26 @@ impl Render for Welcome {
.text_color(cx.theme().elevated_surface_background),
)
.child(
div()
.child("coop on nostr")
.text_color(cx.theme().text_placeholder)
.font_semibold()
.text_sm(),
v_flex()
.items_center()
.justify_center()
.text_center()
.child(
div()
.font_semibold()
.text_color(cx.theme().text_muted)
.child("coop on nostr"),
)
.child(
div()
.id("version")
.text_color(cx.theme().text_placeholder)
.text_xs()
.child(self.version.clone())
.on_click(|_, _window, cx| {
cx.open_url("https://github.com/lumehq/coop/releases");
}),
),
),
)
}

View File

@@ -3,4 +3,4 @@
///
/// Magic number: There is one extra pixel of padding on the left side due to
/// the 1px border around the window on macOS apps.
pub const TRAFFIC_LIGHT_PADDING: f32 = 71.;
pub const TRAFFIC_LIGHT_PADDING: f32 = 80.;

View File

@@ -10,11 +10,6 @@ use crate::indicator::Indicator;
use crate::tooltip::Tooltip;
use crate::{h_flex, Disableable, Icon, Selectable, Sizable, Size, StyledExt};
pub enum ButtonRounded {
Normal,
Full,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct ButtonCustomVariant {
color: Hsla,
@@ -130,7 +125,7 @@ pub struct Button {
children: Vec<AnyElement>,
variant: ButtonVariant,
rounded: ButtonRounded,
rounded: bool,
size: Size,
disabled: bool,
@@ -163,7 +158,7 @@ impl Button {
disabled: false,
selected: false,
variant: ButtonVariant::default(),
rounded: ButtonRounded::Normal,
rounded: false,
size: Size::Medium,
tooltip: None,
on_click: None,
@@ -177,9 +172,9 @@ impl Button {
}
}
/// Set the border radius of the Button.
pub fn rounded(mut self, rounded: impl Into<ButtonRounded>) -> Self {
self.rounded = rounded.into();
/// Make the button rounded.
pub fn rounded(mut self) -> Self {
self.rounded = true;
self
}
@@ -315,8 +310,8 @@ impl RenderOnce for Button {
.cursor_default()
.overflow_hidden()
.map(|this| match self.rounded {
ButtonRounded::Normal => this.rounded(cx.theme().radius),
ButtonRounded::Full => this.rounded_full(),
false => this.rounded(cx.theme().radius),
true => this.rounded_full(),
})
.map(|this| {
if self.label.is_none() && self.children.is_empty() {

View File

@@ -412,16 +412,15 @@ impl TabPanel {
let is_zoomed = self.is_zoomed && state.zoomable;
let view = cx.entity().clone();
let build_popup_menu = move |this, cx: &App| view.read(cx).popup_menu(this, cx);
let toolbar = self.toolbar_buttons(window, cx);
let has_toolbar = !toolbar.is_empty();
h_flex()
.p_0p5()
.gap_1()
.occlude()
.items_center()
.children(
self.toolbar_buttons(window, cx)
.into_iter()
.map(|btn| btn.small().ghost()),
)
.rounded_full()
.children(toolbar.into_iter().map(|btn| btn.small().ghost().rounded()))
.when(self.is_zoomed, |this| {
this.child(
Button::new("zoom")
@@ -434,11 +433,16 @@ impl TabPanel {
})),
)
})
.when(has_toolbar, |this| {
this.bg(cx.theme().surface_background)
.child(div().flex_shrink_0().h_4().w_px().bg(cx.theme().border))
})
.child(
Button::new("menu")
.icon(IconName::Ellipsis)
.small()
.ghost()
.rounded()
.popup_menu({
let zoomable = state.zoomable;
let closable = state.closable;
@@ -647,7 +651,7 @@ impl TabPanel {
.child(
div()
.size_full()
.rounded_lg()
.rounded_xl()
.shadow_sm()
.when(cx.theme().mode.is_dark(), |this| this.shadow_lg())
.bg(cx.theme().panel_background)
@@ -667,7 +671,7 @@ impl TabPanel {
.p_1()
.child(
div()
.rounded_lg()
.rounded_xl()
.border_1()
.border_color(cx.theme().element_disabled)
.bg(cx.theme().drop_target_background)

View File

@@ -405,13 +405,14 @@ impl Render for ResizablePanel {
return div();
}
let view = cx.entity().clone();
let total_size = self
.group
.as_ref()
.and_then(|group| group.upgrade())
.map(|group| group.read(cx).total_size());
let view = cx.entity();
div()
.flex()
.flex_grow()

View File

@@ -401,6 +401,14 @@ sidebar:
en: "Incoming new conversations"
trusted_contacts_tooltip:
en: "Only show rooms from trusted contacts"
no_requests:
en: "No message requests"
no_requests_label:
en: "New message requests from people you don't know will appear here."
no_conversations:
en: "No conversations"
no_conversations_label:
en: "Start a conversation with someone to get started."
loading:
label: