From b64919aa11560e5d5ae952b797a424b589876212 Mon Sep 17 00:00:00 2001
From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Date: Wed, 16 Oct 2024 01:39:27 +0200
Subject: [PATCH] ssh: Refine the modal UI (#19256)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This PR refines the SSH modal UI, adjusting spacing and alignment. Via
these changes, I'm also introducing the ability for the `empty_message`
on the `List` component to receive not just a string but any element.
The custom way in which the SSH modal was designed made it feel like
this was needed for proper spacing.
Release Notes:
- N/A
---
crates/recent_projects/src/dev_servers.rs | 157 +++++++++++-----------
crates/ui/src/components/list/list.rs | 36 ++++-
crates/ui/src/components/modal.rs | 2 +
3 files changed, 111 insertions(+), 84 deletions(-)
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))