diff --git a/crates/recent_projects/src/dev_servers.rs b/crates/recent_projects/src/dev_servers.rs index 62cfeab830..8fe501258a 100644 --- a/crates/recent_projects/src/dev_servers.rs +++ b/crates/recent_projects/src/dev_servers.rs @@ -35,7 +35,7 @@ use task::RevealStrategy; use task::SpawnInTerminal; use terminal_view::terminal_panel::TerminalPanel; use ui::Section; -use ui::{prelude::*, List, ListItem, ListSeparator, Modal, ModalHeader, Tooltip}; +use ui::{prelude::*, IconButtonShape, List, ListItem, ListSeparator, Modal, ModalHeader, Tooltip}; use util::ResultExt; use workspace::notifications::NotificationId; use workspace::OpenOptions; @@ -604,19 +604,16 @@ impl DevServerProjects { }; v_flex() .w_full() - .border_b_1() - .border_color(cx.theme().colors().border_variant) - .mb_1() + .child(ListSeparator) .child( h_flex() .group("ssh-server") .w_full() .pt_0p5() - .px_2p5() + .px_3() .gap_1() .overflow_hidden() .whitespace_nowrap() - .w_full() .child( Label::new(main_label) .size(LabelSize::Small) @@ -630,68 +627,63 @@ impl DevServerProjects { ), ) .child( - v_flex().w_full().gap_1().mb_1().child( - List::new() - .empty_message("No projects.") - .children(ssh_connection.projects.iter().enumerate().map(|(pix, p)| { - v_flex().gap_0p5().child(self.render_ssh_project( - ix, - &ssh_connection, - pix, - p, - cx, - )) - })) - .child(h_flex().map(|this| { - self.selectable_items.add_item(Box::new({ - let ssh_connection = ssh_connection.clone(); - move |this, cx| { - this.create_ssh_project(ix, ssh_connection.clone(), cx); - } - })); - let is_selected = self.selectable_items.is_selected(); - this.child( - ListItem::new(("new-remote-project", ix)) - .selected(is_selected) - .inset(true) - .spacing(ui::ListItemSpacing::Sparse) - .start_slot(Icon::new(IconName::Plus).color(Color::Muted)) - .child(Label::new("Open Folder")) - .on_click(cx.listener({ - let ssh_connection = ssh_connection.clone(); - move |this, _, cx| { - this.create_ssh_project(ix, ssh_connection.clone(), cx); - } - })), - ) - })) - .child(h_flex().map(|this| { - self.selectable_items.add_item(Box::new({ - let ssh_connection = ssh_connection.clone(); - move |this, cx| { - this.view_server_options((ix, ssh_connection.clone()), cx); - } - })); - let is_selected = self.selectable_items.is_selected(); - this.child( - ListItem::new(("server-options", ix)) - .selected(is_selected) - .inset(true) - .spacing(ui::ListItemSpacing::Sparse) - .start_slot(Icon::new(IconName::Settings).color(Color::Muted)) - .child(Label::new("View Server Options")) - .on_click(cx.listener({ - let ssh_connection = ssh_connection.clone(); - move |this, _, cx| { - this.view_server_options( - (ix, ssh_connection.clone()), - cx, - ); - } - })), - ) - })), - ), + List::new() + .empty_message("No projects.") + .children(ssh_connection.projects.iter().enumerate().map(|(pix, p)| { + v_flex().gap_0p5().child(self.render_ssh_project( + ix, + &ssh_connection, + pix, + p, + cx, + )) + })) + .child(h_flex().map(|this| { + self.selectable_items.add_item(Box::new({ + let ssh_connection = ssh_connection.clone(); + move |this, cx| { + this.create_ssh_project(ix, ssh_connection.clone(), cx); + } + })); + let is_selected = self.selectable_items.is_selected(); + this.child( + ListItem::new(("new-remote-project", ix)) + .selected(is_selected) + .inset(true) + .spacing(ui::ListItemSpacing::Sparse) + .start_slot(Icon::new(IconName::Plus).color(Color::Muted)) + .child(Label::new("Open Folder")) + .on_click(cx.listener({ + let ssh_connection = ssh_connection.clone(); + move |this, _, cx| { + this.create_ssh_project(ix, ssh_connection.clone(), cx); + } + })), + ) + })) + .child(h_flex().map(|this| { + self.selectable_items.add_item(Box::new({ + let ssh_connection = ssh_connection.clone(); + move |this, cx| { + this.view_server_options((ix, ssh_connection.clone()), cx); + } + })); + let is_selected = self.selectable_items.is_selected(); + this.child( + ListItem::new(("server-options", ix)) + .selected(is_selected) + .inset(true) + .spacing(ui::ListItemSpacing::Sparse) + .start_slot(Icon::new(IconName::Settings).color(Color::Muted)) + .child(Label::new("View Server Options")) + .on_click(cx.listener({ + let ssh_connection = ssh_connection.clone(); + move |this, _, cx| { + this.view_server_options((ix, ssh_connection.clone()), cx); + } + })), + ) + })), ) } @@ -762,6 +754,7 @@ impl DevServerProjects { .end_hover_slot::(Some( IconButton::new("remove-remote-project", IconName::TrashAlt) .icon_size(IconSize::Small) + .shape(IconButtonShape::Square) .on_click( cx.listener(move |this, _, cx| this.delete_ssh_project(server_ix, ix, cx)), ) @@ -1117,6 +1110,7 @@ impl DevServerProjects { })); let is_selected = self.selectable_items.is_selected(); + let connect_button = ListItem::new("register-dev-server-button") .selected(is_selected) .inset(true) @@ -1130,16 +1124,21 @@ impl DevServerProjects { cx.notify(); })); - let footer = format!("Servers: {}", ssh_connections.len() + dev_servers.len()); let mut modal_section = v_flex() .id("ssh-server-list") .overflow_y_scroll() .size_full() .child(connect_button) - .child(ListSeparator) .child( List::new() - .empty_message("No dev servers registered yet.") + .empty_message( + v_flex() + .child(ListSeparator) + .child(div().px_3().child( + Label::new("No dev servers registered yet.").color(Color::Muted), + )) + .into_any_element(), + ) .children(ssh_connections.iter().cloned().enumerate().map( |(ix, connection)| { self.render_ssh_connection(ix, connection, cx) @@ -1149,23 +1148,25 @@ impl DevServerProjects { ) .into_any_element(); + let server_count = format!("Servers: {}", ssh_connections.len() + dev_servers.len()); + Modal::new("remote-projects", Some(self.scroll_handle.clone())) .header( ModalHeader::new().child( h_flex() + .items_center() .justify_between() .child(Headline::new("Remote Projects (alpha)").size(HeadlineSize::XSmall)) - .child(Label::new(footer).size(LabelSize::Small)), + .child(Label::new(server_count).size(LabelSize::Small)), ), ) .section( Section::new().padded(false).child( v_flex() - .min_h(rems(28.)) + .min_h(rems(20.)) + .flex_1() .size_full() - .pt_1p5() - .border_y_1() - .border_color(cx.theme().colors().border_variant) + .child(ListSeparator) .child( canvas( |bounds, cx| { @@ -1180,9 +1181,7 @@ impl DevServerProjects { modal_section.paint(cx); }, ) - .size_full() - .min_h_full() - .flex_1(), + .size_full(), ), ), ) diff --git a/crates/ui/src/components/list/list.rs b/crates/ui/src/components/list/list.rs index e112a558ee..e4cc74b2c0 100644 --- a/crates/ui/src/components/list/list.rs +++ b/crates/ui/src/components/list/list.rs @@ -5,11 +5,16 @@ use smallvec::SmallVec; use crate::{prelude::*, v_flex, Label, ListHeader}; +pub enum EmptyMessage { + Text(SharedString), + Element(AnyElement), +} + #[derive(IntoElement)] pub struct List { /// Message to display when the list is empty /// Defaults to "No items" - empty_message: SharedString, + empty_message: EmptyMessage, header: Option, toggle: Option, children: SmallVec<[AnyElement; 2]>, @@ -24,15 +29,15 @@ impl Default for List { impl List { pub fn new() -> Self { Self { - empty_message: "No items".into(), + empty_message: EmptyMessage::Text("No items".into()), header: None, toggle: None, children: SmallVec::new(), } } - pub fn empty_message(mut self, empty_message: impl Into) -> Self { - self.empty_message = empty_message.into(); + pub fn empty_message(mut self, message: impl Into) -> Self { + self.empty_message = message.into(); self } @@ -53,6 +58,24 @@ impl ParentElement for List { } } +impl From for EmptyMessage { + fn from(s: String) -> Self { + EmptyMessage::Text(SharedString::from(s)) + } +} + +impl From<&str> for EmptyMessage { + fn from(s: &str) -> Self { + EmptyMessage::Text(SharedString::from(s.to_owned())) + } +} + +impl From for EmptyMessage { + fn from(e: AnyElement) -> Self { + EmptyMessage::Element(e) + } +} + impl RenderOnce for List { fn render(self, cx: &mut WindowContext) -> impl IntoElement { v_flex() @@ -62,7 +85,10 @@ impl RenderOnce for List { .map(|this| match (self.children.is_empty(), self.toggle) { (false, _) => this.children(self.children), (true, Some(false)) => this, - (true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)), + (true, _) => match self.empty_message { + EmptyMessage::Text(text) => this.child(Label::new(text).color(Color::Muted)), + EmptyMessage::Element(element) => this.child(element), + }, }) } } diff --git a/crates/ui/src/components/modal.rs b/crates/ui/src/components/modal.rs index 512f9601a8..f8fdaede19 100644 --- a/crates/ui/src/components/modal.rs +++ b/crates/ui/src/components/modal.rs @@ -77,6 +77,7 @@ impl RenderOnce for Modal { v_flex() .id(self.container_id.clone()) .w_full() + .flex_1() .gap(Spacing::Large.rems(cx)) .when_some( self.container_scroll_handler, @@ -344,6 +345,7 @@ impl RenderOnce for Section { } else { v_flex() .w_full() + .flex_1() .gap_y(Spacing::Small.rems(cx)) .when(self.padded, |this| { this.px(Spacing::Medium.rems(cx) + Spacing::Medium.rems(cx))