Skip to content

Commit

Permalink
ssh: Refine the modal UI (zed-industries#19256)
Browse files Browse the repository at this point in the history
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.

<img width="700" alt="Screenshot 2024-10-16 at 1 20 54 AM"
src="https://github.com/user-attachments/assets/f2e0586b-4c9f-4497-b4cb-e90c8157512b">


Release Notes:

- N/A
  • Loading branch information
danilo-leal authored Oct 15, 2024
1 parent b752548 commit b64919a
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 84 deletions.
157 changes: 78 additions & 79 deletions crates/recent_projects/src/dev_servers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand All @@ -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);
}
})),
)
})),
)
}

Expand Down Expand Up @@ -762,6 +754,7 @@ impl DevServerProjects {
.end_hover_slot::<AnyElement>(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)),
)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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| {
Expand All @@ -1180,9 +1181,7 @@ impl DevServerProjects {
modal_section.paint(cx);
},
)
.size_full()
.min_h_full()
.flex_1(),
.size_full(),
),
),
)
Expand Down
36 changes: 31 additions & 5 deletions crates/ui/src/components/list/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ListHeader>,
toggle: Option<bool>,
children: SmallVec<[AnyElement; 2]>,
Expand All @@ -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<SharedString>) -> Self {
self.empty_message = empty_message.into();
pub fn empty_message(mut self, message: impl Into<EmptyMessage>) -> Self {
self.empty_message = message.into();
self
}

Expand All @@ -53,6 +58,24 @@ impl ParentElement for List {
}
}

impl From<String> 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<AnyElement> for EmptyMessage {
fn from(e: AnyElement) -> Self {
EmptyMessage::Element(e)
}
}

impl RenderOnce for List {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
v_flex()
Expand All @@ -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),
},
})
}
}
2 changes: 2 additions & 0 deletions crates/ui/src/components/modal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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))
Expand Down

0 comments on commit b64919a

Please sign in to comment.