diff --git a/Cargo.lock b/Cargo.lock
index 1cac85e0c7..f682d76ad5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9002,7 +9002,6 @@ dependencies = [
"gpui",
"language",
"log",
- "markdown",
"menu",
"ordered-float 2.10.1",
"picker",
diff --git a/assets/icons/trash_alt.svg b/assets/icons/trash_alt.svg
new file mode 100644
index 0000000000..6867b42147
--- /dev/null
+++ b/assets/icons/trash_alt.svg
@@ -0,0 +1 @@
+
diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml
index da4ee210e1..2eea6321a0 100644
--- a/crates/recent_projects/Cargo.toml
+++ b/crates/recent_projects/Cargo.toml
@@ -22,7 +22,6 @@ futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true
log.workspace = true
-markdown.workspace = true
menu.workspace = true
ordered-float.workspace = true
picker.workspace = true
diff --git a/crates/recent_projects/src/dev_servers.rs b/crates/recent_projects/src/dev_servers.rs
index 722743e0ff..7761461ab5 100644
--- a/crates/recent_projects/src/dev_servers.rs
+++ b/crates/recent_projects/src/dev_servers.rs
@@ -8,17 +8,18 @@ use anyhow::Result;
use client::Client;
use dev_server_projects::{DevServer, DevServerId, DevServerProject, DevServerProjectId};
use editor::Editor;
+use gpui::pulsating_between;
use gpui::AsyncWindowContext;
+use gpui::ClipboardItem;
use gpui::PathPromptOptions;
use gpui::Subscription;
use gpui::Task;
use gpui::WeakView;
use gpui::{
- percentage, Animation, AnimationExt, AnyElement, AppContext, DismissEvent, EventEmitter,
- FocusHandle, FocusableView, Model, ScrollHandle, Transformation, View, ViewContext,
+ percentage, Action, Animation, AnimationExt, AnyElement, AppContext, DismissEvent,
+ EventEmitter, FocusHandle, FocusableView, Model, ScrollHandle, Transformation, View,
+ ViewContext,
};
-use markdown::Markdown;
-use markdown::MarkdownStyle;
use project::terminals::wrap_for_ssh;
use project::terminals::SshCommand;
use rpc::proto::RegenerateDevServerTokenResponse;
@@ -35,8 +36,8 @@ use terminal_view::terminal_panel::TerminalPanel;
use ui::ElevationIndex;
use ui::Section;
use ui::{
- prelude::*, Indicator, List, ListHeader, ListItem, Modal, ModalFooter, ModalHeader,
- RadioWithLabel, Tooltip,
+ prelude::*, IconButtonShape, Indicator, List, ListItem, Modal, ModalFooter, ModalHeader,
+ Tooltip,
};
use ui_input::{FieldLabelLayout, TextField};
use util::ResultExt;
@@ -62,7 +63,6 @@ pub struct DevServerProjects {
workspace: WeakView,
project_path_input: View,
dev_server_name_input: View,
- markdown: View,
_dev_server_subscription: Subscription,
}
@@ -132,26 +132,6 @@ impl DevServerProjects {
..Default::default()
});
- let markdown_style = MarkdownStyle {
- base_text_style: base_style,
- code_block: gpui::StyleRefinement {
- text: Some(gpui::TextStyleRefinement {
- font_family: Some("Zed Plex Mono".into()),
- ..Default::default()
- }),
- ..Default::default()
- },
- link: gpui::TextStyleRefinement {
- color: Some(Color::Accent.color(cx)),
- ..Default::default()
- },
- syntax: cx.theme().syntax().clone(),
- selection_background_color: cx.theme().players().local().selection,
- ..Default::default()
- };
- let markdown =
- cx.new_view(|cx| Markdown::new("".to_string(), markdown_style, None, cx, None));
-
Self {
mode: Mode::Default(None),
focus_handle,
@@ -159,7 +139,6 @@ impl DevServerProjects {
dev_server_store,
project_path_input,
dev_server_name_input,
- markdown,
workspace,
_dev_server_subscription: subscription,
}
@@ -845,7 +824,7 @@ impl DevServerProjects {
})
.child({
let dev_server_id = dev_server.id;
- IconButton::new("remove-dev-server", IconName::Trash)
+ IconButton::new("remove-dev-server", IconName::TrashAlt)
.on_click(cx.listener(move |this, _, cx| {
this.delete_dev_server(dev_server_id, cx)
}))
@@ -913,40 +892,73 @@ impl DevServerProjects {
) -> impl IntoElement {
v_flex()
.w_full()
+ .px(Spacing::Small.rems(cx) + Spacing::Small.rems(cx))
.child(
- h_flex().group("ssh-server").justify_between().child(
- h_flex()
- .gap_2()
- .child(
- div()
- .id(("status", ix))
- .relative()
- .child(Icon::new(IconName::Server).size(IconSize::Small)),
- )
- .child(
- div()
- .max_w(rems(26.))
- .overflow_hidden()
- .whitespace_nowrap()
- .child(Label::new(ssh_connection.host.clone())),
- )
- .child(h_flex().visible_on_hover("ssh-server").gap_1().child({
- IconButton::new("remove-dev-server", IconName::Trash)
- .on_click(
- cx.listener(move |this, _, cx| this.delete_ssh_server(ix, cx)),
- )
- .tooltip(|cx| Tooltip::text("Remove Dev Server", cx))
- })),
- ),
+ h_flex()
+ .w_full()
+ .group("ssh-server")
+ .justify_between()
+ .child(
+ h_flex()
+ .gap_2()
+ .w_full()
+ .child(
+ div()
+ .id(("status", ix))
+ .relative()
+ .child(Icon::new(IconName::Server).size(IconSize::Small)),
+ )
+ .child(
+ h_flex()
+ .max_w(rems(26.))
+ .overflow_hidden()
+ .whitespace_nowrap()
+ .child(Label::new(ssh_connection.host.clone())),
+ ),
+ )
+ .child(
+ h_flex()
+ .visible_on_hover("ssh-server")
+ .gap_1()
+ .child({
+ IconButton::new("copy-dev-server-address", IconName::Copy)
+ .icon_size(IconSize::Small)
+ .on_click(cx.listener(move |this, _, cx| {
+ this.update_settings_file(cx, move |servers, cx| {
+ if let Some(content) = servers
+ .ssh_connections
+ .as_ref()
+ .and_then(|connections| {
+ connections
+ .get(ix)
+ .map(|connection| connection.host.clone())
+ })
+ {
+ cx.write_to_clipboard(ClipboardItem::new_string(
+ content,
+ ));
+ }
+ });
+ }))
+ .tooltip(|cx| Tooltip::text("Copy Server Address", cx))
+ })
+ .child({
+ IconButton::new("remove-dev-server", IconName::TrashAlt)
+ .icon_size(IconSize::Small)
+ .on_click(cx.listener(move |this, _, cx| {
+ this.delete_ssh_server(ix, cx)
+ }))
+ .tooltip(|cx| Tooltip::text("Remove Dev Server", cx))
+ }),
+ ),
)
.child(
v_flex()
.w_full()
- .bg(cx.theme().colors().background)
- .border_1()
+ .border_l_1()
.border_color(cx.theme().colors().border_variant)
- .rounded_md()
.my_1()
+ .mx_1p5()
.py_0p5()
.px_3()
.child(
@@ -956,12 +968,17 @@ impl DevServerProjects {
self.render_ssh_project(ix, &ssh_connection, pix, p, cx)
}))
.child(
- ListItem::new("new-remote_project")
- .start_slot(Icon::new(IconName::Plus))
- .child(Label::new("Open folder…"))
- .on_click(cx.listener(move |this, _, cx| {
- this.create_ssh_project(ix, ssh_connection.clone(), cx);
- })),
+ h_flex().child(
+ Button::new("new-remote_project", "Open Folder…")
+ .icon(IconName::Plus)
+ .size(ButtonSize::Default)
+ .style(ButtonStyle::Filled)
+ .layer(ElevationIndex::ModalSurface)
+ .icon_position(IconPosition::Start)
+ .on_click(cx.listener(move |this, _, cx| {
+ this.create_ssh_project(ix, ssh_connection.clone(), cx);
+ })),
+ ),
),
),
)
@@ -978,7 +995,8 @@ impl DevServerProjects {
let project = project.clone();
let server = server.clone();
ListItem::new(("remote-project", ix))
- .start_slot(Icon::new(IconName::FileTree))
+ .spacing(ui::ListItemSpacing::Sparse)
+ .start_slot(Icon::new(IconName::Folder).color(Color::Muted))
.child(Label::new(project.paths.join(", ")))
.on_click(cx.listener(move |this, _, cx| {
let Some(app_state) = this
@@ -1014,7 +1032,7 @@ impl DevServerProjects {
.detach();
}))
.end_hover_slot::(Some(
- IconButton::new("remove-remote-project", IconName::Trash)
+ IconButton::new("remove-remote-project", IconName::TrashAlt)
.on_click(
cx.listener(move |this, _, cx| this.delete_ssh_project(server_ix, ix, cx)),
)
@@ -1026,7 +1044,7 @@ impl DevServerProjects {
fn update_settings_file(
&mut self,
cx: &mut ViewContext,
- f: impl FnOnce(&mut RemoteSettingsContent) + Send + Sync + 'static,
+ f: impl FnOnce(&mut RemoteSettingsContent, &AppContext) + Send + Sync + 'static,
) {
let Some(fs) = self
.workspace
@@ -1035,11 +1053,11 @@ impl DevServerProjects {
else {
return;
};
- update_settings_file::(fs, cx, move |setting, _| f(setting));
+ update_settings_file::(fs, cx, move |setting, cx| f(setting, cx));
}
fn delete_ssh_server(&mut self, server: usize, cx: &mut ViewContext) {
- self.update_settings_file(cx, move |setting| {
+ self.update_settings_file(cx, move |setting, _| {
if let Some(connections) = setting.ssh_connections.as_mut() {
connections.remove(server);
}
@@ -1047,7 +1065,7 @@ impl DevServerProjects {
}
fn delete_ssh_project(&mut self, server: usize, project: usize, cx: &mut ViewContext) {
- self.update_settings_file(cx, move |setting| {
+ self.update_settings_file(cx, move |setting, _| {
if let Some(server) = setting
.ssh_connections
.as_mut()
@@ -1063,7 +1081,7 @@ impl DevServerProjects {
connection_options: remote::SshConnectionOptions,
cx: &mut ViewContext,
) {
- self.update_settings_file(cx, move |setting| {
+ self.update_settings_file(cx, move |setting, _| {
setting
.ssh_connections
.get_or_insert(Default::default())
@@ -1124,7 +1142,7 @@ impl DevServerProjects {
}).detach();
}
}))
- .end_hover_slot::(Some(IconButton::new("remove-remote-project", IconName::Trash)
+ .end_hover_slot::(Some(IconButton::new("remove-remote-project", IconName::TrashAlt)
.on_click(cx.listener(move |this, _, cx| {
this.delete_dev_server_project(dev_server_project_id, cx)
}))
@@ -1148,250 +1166,109 @@ impl DevServerProjects {
kind = NewServerKind::DirectSSH;
}
- let status = dev_server_id
- .map(|id| self.dev_server_store.read(cx).dev_server_status(id))
- .unwrap_or_default();
-
- let name = self.dev_server_name_input.update(cx, |input, cx| {
+ self.dev_server_name_input.update(cx, |input, cx| {
input.editor().update(cx, |editor, cx| {
if editor.text(cx).is_empty() {
- match kind {
- NewServerKind::DirectSSH => editor.set_placeholder_text("ssh host", cx),
- NewServerKind::LegacySSH => editor.set_placeholder_text("ssh host", cx),
- NewServerKind::Manual => editor.set_placeholder_text("example-host", cx),
- }
+ editor.set_placeholder_text("ssh me@my.server / ssh@secret-box:2222", cx);
}
- editor.text(cx)
})
});
-
- const MANUAL_SETUP_MESSAGE: &str =
- "Generate a token for this server and follow the steps to set Zed up on that machine.";
- const SSH_SETUP_MESSAGE: &str =
- "Enter the command you use to SSH into this server.\nFor example: `ssh me@my.server` or `ssh me@secret-box:2222`.";
-
- Modal::new("create-dev-server", Some(self.scroll_handle.clone()))
- .header(
- ModalHeader::new()
- .headline("Create Dev Server")
- .show_back_button(true),
- )
- .section(
- Section::new()
- .header(if kind == NewServerKind::Manual {
- "Server Name".into()
- } else {
- "SSH arguments".into()
- })
- .child(
- div()
- .max_w(rems(16.))
- .child(self.dev_server_name_input.clone()),
- ),
- )
- .section(
- Section::new_contained()
- .header("Connection Method".into())
- .child(
- v_flex()
- .w_full()
- .px_2()
- .gap_y(Spacing::Large.rems(cx))
- .when(ssh_prompt.is_none(), |el| {
- el.child(
- v_flex()
- .when(use_direct_ssh, |el| {
- el.child(RadioWithLabel::new(
- "use-server-name-in-ssh",
- Label::new("Connect via SSH (default)"),
- NewServerKind::DirectSSH == kind,
- cx.listener({
- move |this, _, cx| {
- if let Mode::CreateDevServer(
- CreateDevServer { kind, .. },
- ) = &mut this.mode
- {
- *kind = NewServerKind::DirectSSH;
- }
- cx.notify()
- }
- }),
- ))
- })
- .when(!use_direct_ssh, |el| {
- el.child(RadioWithLabel::new(
- "use-server-name-in-ssh",
- Label::new("Configure over SSH (default)"),
- kind == NewServerKind::LegacySSH,
- cx.listener({
- move |this, _, cx| {
- if let Mode::CreateDevServer(
- CreateDevServer { kind, .. },
- ) = &mut this.mode
- {
- *kind = NewServerKind::LegacySSH;
- }
- cx.notify()
- }
- }),
- ))
- })
- .child(RadioWithLabel::new(
- "use-server-name-in-ssh",
- Label::new("Configure manually"),
- kind == NewServerKind::Manual,
- cx.listener({
- move |this, _, cx| {
- if let Mode::CreateDevServer(
- CreateDevServer { kind, .. },
- ) = &mut this.mode
- {
- *kind = NewServerKind::Manual;
- }
- cx.notify()
- }
- }),
- )),
- )
- })
- .when(dev_server_id.is_none() && ssh_prompt.is_none(), |el| {
- el.child(
- if kind == NewServerKind::Manual {
- Label::new(MANUAL_SETUP_MESSAGE)
- } else {
- Label::new(SSH_SETUP_MESSAGE)
- }
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
- })
- .when_some(ssh_prompt, |el, ssh_prompt| el.child(ssh_prompt))
- .when(dev_server_id.is_some() && access_token.is_none(), |el| {
- el.child(
- if kind == NewServerKind::Manual {
- Label::new(
- "Note: updating the dev server generate a new token",
- )
- } else {
- Label::new(SSH_SETUP_MESSAGE)
- }
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
- })
- .when_some(access_token.clone(), {
- |el, access_token| {
- el.child(self.render_dev_server_token_creating(
- access_token,
- name,
- kind,
- status,
- creating,
- cx,
- ))
- }
- }),
- ),
- )
- .footer(
- ModalFooter::new().end_slot(if status == DevServerStatus::Online {
- Button::new("create-dev-server", "Done")
- .style(ButtonStyle::Filled)
- .layer(ElevationIndex::ModalSurface)
- .on_click(cx.listener(move |this, _, cx| {
- cx.focus(&this.focus_handle);
- this.mode = Mode::Default(None);
- cx.notify();
- }))
- } else {
- Button::new(
- "create-dev-server",
- if kind == NewServerKind::Manual {
- if dev_server_id.is_some() {
- "Update"
- } else {
- "Create"
- }
- } else if dev_server_id.is_some() {
- "Reconnect"
- } else {
- "Connect"
- },
- )
- .style(ButtonStyle::Filled)
- .layer(ElevationIndex::ModalSurface)
- .disabled(creating && dev_server_id.is_none())
- .on_click(cx.listener({
- let access_token = access_token.clone();
- move |this, _, cx| {
- if kind == NewServerKind::DirectSSH {
- this.create_ssh_server(cx);
- return;
- }
- this.create_or_update_dev_server(
- kind,
- dev_server_id,
- access_token.clone(),
- cx,
- );
- }
- }))
- }),
- )
- }
-
- fn render_dev_server_token_creating(
- &self,
- access_token: String,
- dev_server_name: String,
- kind: NewServerKind,
- status: DevServerStatus,
- creating: bool,
- cx: &mut ViewContext,
- ) -> Div {
- self.markdown.update(cx, |markdown, cx| {
- if kind == NewServerKind::Manual {
- markdown.reset(format!("Please log into '{}'. If you don't yet have Zed installed, run:\n```\ncurl https://zed.dev/install.sh | bash\n```\nThen, to start Zed in headless mode:\n```\nzed --dev-server-token {}\n```", dev_server_name, access_token), cx);
- } else {
- markdown.reset("Please wait while we connect over SSH.\n\nIf you run into problems, please [file a bug](https://github.com/zed-industries/zed), and in the meantime try using the manual setup.".to_string(), cx);
- }
- });
-
+ let theme = cx.theme();
v_flex()
- .pl_2()
- .pt_2()
- .gap_2()
- .child(v_flex().w_full().text_sm().child(self.markdown.clone()))
- .map(|el| {
- if status == DevServerStatus::Offline && kind != NewServerKind::Manual && !creating
- {
- el.child(
- h_flex()
- .gap_2()
- .child(Icon::new(IconName::Disconnected).size(IconSize::Medium))
- .child(Label::new("Not connected")),
- )
- } else if status == DevServerStatus::Offline {
- el.child(Self::render_loading_spinner("Waiting for connection…"))
- } else {
- el.child(Label::new("🎊 Connection established!"))
- }
- })
- }
-
- fn render_loading_spinner(label: impl Into) -> Div {
- h_flex()
- .gap_2()
+ .id("create-dev-server")
+ .overflow_hidden()
+ .size_full()
+ .flex_1()
.child(
- Icon::new(IconName::ArrowCircle)
- .size(IconSize::Medium)
- .with_animation(
- "arrow-circle",
- Animation::new(Duration::from_secs(2)).repeat(),
- |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
+ h_flex()
+ .p_2()
+ .gap_2()
+ .items_center()
+ .border_b_1()
+ .border_color(theme.colors().border_variant)
+ .child(
+ IconButton::new("cancel-dev-server-creation", IconName::ArrowLeft)
+ .shape(IconButtonShape::Square)
+ .on_click(|_, cx| {
+ cx.dispatch_action(menu::Cancel.boxed_clone());
+ }),
+ )
+ .child(Label::new("Connect New Dev Server")),
+ )
+ .child(
+ v_flex()
+ .p_3()
+ .border_b_1()
+ .border_color(theme.colors().border_variant)
+ .child(Label::new("SSH Arguments"))
+ .child(
+ Label::new("Enter the command you use to SSH into this server.")
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ )
+ .child(
+ h_flex()
+ .mt_2()
+ .w_full()
+ .gap_2()
+ .child(self.dev_server_name_input.clone())
+ .child(
+ Button::new("create-dev-server", "Connect Server")
+ .style(ButtonStyle::Filled)
+ .layer(ElevationIndex::ModalSurface)
+ .disabled(creating && dev_server_id.is_none())
+ .on_click(cx.listener({
+ let access_token = access_token.clone();
+ move |this, _, cx| {
+ if kind == NewServerKind::DirectSSH {
+ this.create_ssh_server(cx);
+ return;
+ }
+ this.create_or_update_dev_server(
+ kind,
+ dev_server_id,
+ access_token.clone(),
+ cx,
+ );
+ }
+ })),
+ ),
),
)
- .child(Label::new(label))
+ .child(
+ h_flex()
+ .bg(theme.colors().editor_background)
+ .w_full()
+ .map(|this| {
+ if let Some(ssh_prompt) = ssh_prompt {
+ this.child(h_flex().w_full().child(ssh_prompt))
+ } else {
+ let color = Color::Muted.color(cx);
+ this.child(
+ h_flex()
+ .p_2()
+ .w_full()
+ .content_center()
+ .gap_2()
+ .child(h_flex().w_full())
+ .child(
+ div().p_1().rounded_lg().bg(color).with_animation(
+ "pulse-ssh-waiting-for-connection",
+ Animation::new(Duration::from_secs(2))
+ .repeat()
+ .with_easing(pulsating_between(0.2, 0.5)),
+ move |this, progress| this.bg(color.opacity(progress)),
+ ),
+ )
+ .child(
+ Label::new("Waiting for connection…")
+ .size(LabelSize::Small),
+ )
+ .child(h_flex().w_full()),
+ )
+ }
+ }),
+ )
}
fn render_default(&mut self, cx: &mut ViewContext) -> impl IntoElement {
@@ -1416,64 +1293,73 @@ impl DevServerProjects {
creating_dev_server = Some(*dev_server_id);
};
+ let footer = format!("Connections: {}", ssh_connections.len() + dev_servers.len());
Modal::new("remote-projects", Some(self.scroll_handle.clone()))
.header(
- ModalHeader::new()
- .show_dismiss_button(true)
- .child(Headline::new("Remote Projects (alpha)").size(HeadlineSize::Small)),
+ ModalHeader::new().child(
+ h_flex()
+ .justify_between()
+ .child(Headline::new("Remote Projects (alpha)").size(HeadlineSize::XSmall))
+ .child(
+ Button::new("register-dev-server-button", "Connect New Server")
+ .style(ButtonStyle::Filled)
+ .layer(ElevationIndex::ModalSurface)
+ .icon(IconName::Plus)
+ .icon_position(IconPosition::Start)
+ .icon_color(Color::Muted)
+ .on_click(cx.listener(|this, _, cx| {
+ this.mode = Mode::CreateDevServer(CreateDevServer {
+ kind: if SshSettings::get_global(cx).use_direct_ssh() {
+ NewServerKind::DirectSSH
+ } else {
+ NewServerKind::LegacySSH
+ },
+ ..Default::default()
+ });
+ this.dev_server_name_input.update(cx, |text_field, cx| {
+ text_field.editor().update(cx, |editor, cx| {
+ editor.set_text("", cx);
+ });
+ });
+ cx.notify();
+ })),
+ ),
+ ),
)
.section(
- Section::new().child(
- div().child(
- List::new()
- .empty_message("No dev servers registered yet.")
- .header(Some(
- ListHeader::new("Connections").end_slot(
- Button::new("register-dev-server-button", "Connect New Server")
- .icon(IconName::Plus)
- .icon_position(IconPosition::Start)
- .icon_color(Color::Muted)
- .on_click(cx.listener(|this, _, cx| {
- this.mode = Mode::CreateDevServer(CreateDevServer {
- kind: if SshSettings::get_global(cx)
- .use_direct_ssh()
- {
- NewServerKind::DirectSSH
- } else {
- NewServerKind::LegacySSH
- },
- ..Default::default()
- });
- this.dev_server_name_input.update(
- cx,
- |text_field, cx| {
- text_field.editor().update(cx, |editor, cx| {
- editor.set_text("", cx);
- });
- },
- );
- cx.notify();
- })),
- ),
- ))
- .children(ssh_connections.iter().cloned().enumerate().map(
- |(ix, connection)| {
- self.render_ssh_connection(ix, connection, cx)
- .into_any_element()
- },
- ))
- .children(dev_servers.iter().map(|dev_server| {
- let creating = if creating_dev_server == Some(dev_server.id) {
- is_creating
- } else {
- None
- };
- self.render_dev_server(dev_server, creating, cx)
- .into_any_element()
- })),
- ),
+ Section::new().padded(false).child(
+ div()
+ .border_y_1()
+ .border_color(cx.theme().colors().border_variant)
+ .w_full()
+ .child(
+ div().p_2().child(
+ List::new()
+ .empty_message("No dev servers registered yet.")
+ .children(ssh_connections.iter().cloned().enumerate().map(
+ |(ix, connection)| {
+ self.render_ssh_connection(ix, connection, cx)
+ .into_any_element()
+ },
+ ))
+ .children(dev_servers.iter().map(|dev_server| {
+ let creating = if creating_dev_server == Some(dev_server.id)
+ {
+ is_creating
+ } else {
+ None
+ };
+ self.render_dev_server(dev_server, creating, cx)
+ .into_any_element()
+ })),
+ ),
+ ),
),
)
+ .footer(
+ ModalFooter::new()
+ .start_slot(div().child(Label::new(footer).size(LabelSize::Small))),
+ )
}
}
@@ -1501,7 +1387,6 @@ impl Render for DevServerProjects {
fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement {
div()
.track_focus(&self.focus_handle)
- .p_2()
.elevation_3(cx)
.key_context("DevServerModal")
.on_action(cx.listener(Self::cancel))
diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs
index 9e50523773..554146eab2 100644
--- a/crates/recent_projects/src/ssh_connections.rs
+++ b/crates/recent_projects/src/ssh_connections.rs
@@ -5,9 +5,9 @@ use auto_update::AutoUpdater;
use editor::Editor;
use futures::channel::oneshot;
use gpui::{
- percentage, px, Animation, AnimationExt, AnyWindowHandle, AsyncAppContext, DismissEvent,
- EventEmitter, FocusableView, ParentElement as _, Render, SemanticVersion, SharedString, Task,
- Transformation, View,
+ percentage, px, Action, Animation, AnimationExt, AnyWindowHandle, AsyncAppContext,
+ DismissEvent, EventEmitter, FocusableView, ParentElement as _, Render, SemanticVersion,
+ SharedString, Task, Transformation, View,
};
use gpui::{AppContext, Model};
use release_channel::{AppVersion, ReleaseChannel};
@@ -16,9 +16,9 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
use ui::{
- h_flex, v_flex, Color, FluentBuilder as _, Icon, IconName, IconSize, InteractiveElement,
- IntoElement, Label, LabelCommon, Styled, StyledExt as _, ViewContext, VisualContext,
- WindowContext,
+ div, h_flex, v_flex, ActiveTheme, ButtonCommon, Clickable, Color, FluentBuilder as _, Icon,
+ IconButton, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon, Styled,
+ StyledExt as _, Tooltip, ViewContext, VisualContext, WindowContext,
};
use workspace::{AppState, ModalView, Workspace};
@@ -140,47 +140,57 @@ impl SshPrompt {
}
impl Render for SshPrompt {
- fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement {
+ fn render(&mut self, _: &mut ViewContext) -> impl IntoElement {
v_flex()
+ .w_full()
.key_context("PasswordPrompt")
- .p_4()
- .size_full()
+ .justify_start()
.child(
- h_flex()
- .gap_2()
- .child(if self.error_message.is_some() {
- Icon::new(IconName::XCircle)
- .size(IconSize::Medium)
- .color(Color::Error)
- .into_any_element()
- } else {
- Icon::new(IconName::ArrowCircle)
- .size(IconSize::Medium)
- .with_animation(
- "arrow-circle",
- Animation::new(Duration::from_secs(2)).repeat(),
- |icon, delta| {
- icon.transform(Transformation::rotate(percentage(delta)))
- },
- )
- .into_any_element()
- })
+ v_flex()
+ .p_4()
+ .size_full()
.child(
- Label::new(format!("ssh {}…", self.connection_string))
- .size(ui::LabelSize::Large),
- ),
+ h_flex()
+ .gap_2()
+ .justify_between()
+ .child(h_flex().w_full())
+ .child(if self.error_message.is_some() {
+ Icon::new(IconName::XCircle)
+ .size(IconSize::Medium)
+ .color(Color::Error)
+ .into_any_element()
+ } else {
+ Icon::new(IconName::ArrowCircle)
+ .size(IconSize::Medium)
+ .with_animation(
+ "arrow-circle",
+ Animation::new(Duration::from_secs(2)).repeat(),
+ |icon, delta| {
+ icon.transform(Transformation::rotate(percentage(
+ delta,
+ )))
+ },
+ )
+ .into_any_element()
+ })
+ .child(Label::new(format!(
+ "Connecting to {}…",
+ self.connection_string
+ )))
+ .child(h_flex().w_full()),
+ )
+ .when_some(self.error_message.as_ref(), |el, error| {
+ el.child(Label::new(error.clone()))
+ })
+ .when(
+ self.error_message.is_none() && self.status_message.is_some(),
+ |el| el.child(Label::new(self.status_message.clone().unwrap())),
+ )
+ .when_some(self.prompt.as_ref(), |el, prompt| {
+ el.child(Label::new(prompt.0.clone()))
+ .child(self.editor.clone())
+ }),
)
- .when_some(self.error_message.as_ref(), |el, error| {
- el.child(Label::new(error.clone()))
- })
- .when(
- self.error_message.is_none() && self.status_message.is_some(),
- |el| el.child(Label::new(self.status_message.clone().unwrap())),
- )
- .when_some(self.prompt.as_ref(), |el, prompt| {
- el.child(Label::new(prompt.0.clone()))
- .child(self.editor.clone())
- })
}
}
@@ -202,14 +212,41 @@ impl SshConnectionModal {
impl Render for SshConnectionModal {
fn render(&mut self, cx: &mut ui::ViewContext) -> impl ui::IntoElement {
+ let connection_string = self.prompt.read(cx).connection_string.clone();
+ let theme = cx.theme();
+ let header_color = theme.colors().element_background;
+ let body_color = theme.colors().background;
v_flex()
.elevation_3(cx)
- .p_4()
- .gap_2()
.on_action(cx.listener(Self::dismiss))
.on_action(cx.listener(Self::confirm))
.w(px(400.))
- .child(self.prompt.clone())
+ .child(
+ h_flex()
+ .p_1()
+ .border_b_1()
+ .border_color(theme.colors().border)
+ .bg(header_color)
+ .justify_between()
+ .child(
+ IconButton::new("ssh-connection-cancel", IconName::ArrowLeft)
+ .icon_size(IconSize::XSmall)
+ .on_click(|_, cx| cx.dispatch_action(menu::Cancel.boxed_clone()))
+ .tooltip(|cx| Tooltip::for_action("Back", &menu::Cancel, cx)),
+ )
+ .child(
+ h_flex()
+ .gap_2()
+ .child(Icon::new(IconName::Server).size(IconSize::XSmall))
+ .child(
+ Label::new(connection_string)
+ .size(ui::LabelSize::Small)
+ .single_line(),
+ ),
+ )
+ .child(div()),
+ )
+ .child(h_flex().bg(body_color).w_full().child(self.prompt.clone()))
}
}
diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs
index 693caaaafd..8d374ef67c 100644
--- a/crates/ui/src/components/icon.rs
+++ b/crates/ui/src/components/icon.rs
@@ -275,6 +275,7 @@ pub enum IconName {
Tab,
Terminal,
Trash,
+ TrashAlt,
TriangleRight,
Undo,
Unpin,
diff --git a/crates/ui/src/components/modal.rs b/crates/ui/src/components/modal.rs
index 11611f9c0f..512f9601a8 100644
--- a/crates/ui/src/components/modal.rs
+++ b/crates/ui/src/components/modal.rs
@@ -262,6 +262,7 @@ impl RenderOnce for ModalFooter {
#[derive(IntoElement)]
pub struct Section {
contained: bool,
+ padded: bool,
header: Option,
meta: Option,
children: SmallVec<[AnyElement; 2]>,
@@ -277,6 +278,7 @@ impl Section {
pub fn new() -> Self {
Self {
contained: false,
+ padded: true,
header: None,
meta: None,
children: SmallVec::new(),
@@ -286,6 +288,7 @@ impl Section {
pub fn new_contained() -> Self {
Self {
contained: true,
+ padded: true,
header: None,
meta: None,
children: SmallVec::new(),
@@ -306,6 +309,10 @@ impl Section {
self.meta = Some(meta.into());
self
}
+ pub fn padded(mut self, padded: bool) -> Self {
+ self.padded = padded;
+ self
+ }
}
impl ParentElement for Section {
@@ -320,22 +327,27 @@ impl RenderOnce for Section {
section_bg.fade_out(0.96);
let children = if self.contained {
- v_flex().flex_1().px(Spacing::XLarge.rems(cx)).child(
- v_flex()
- .w_full()
- .rounded_md()
- .border_1()
- .border_color(cx.theme().colors().border)
- .bg(section_bg)
- .py(Spacing::Medium.rems(cx))
- .gap_y(Spacing::Small.rems(cx))
- .child(div().flex().flex_1().size_full().children(self.children)),
- )
+ v_flex()
+ .flex_1()
+ .when(self.padded, |this| this.px(Spacing::XLarge.rems(cx)))
+ .child(
+ v_flex()
+ .w_full()
+ .rounded_md()
+ .border_1()
+ .border_color(cx.theme().colors().border)
+ .bg(section_bg)
+ .py(Spacing::Medium.rems(cx))
+ .gap_y(Spacing::Small.rems(cx))
+ .child(div().flex().flex_1().size_full().children(self.children)),
+ )
} else {
v_flex()
.w_full()
.gap_y(Spacing::Small.rems(cx))
- .px(Spacing::Medium.rems(cx) + Spacing::Medium.rems(cx))
+ .when(self.padded, |this| {
+ this.px(Spacing::Medium.rems(cx) + Spacing::Medium.rems(cx))
+ })
.children(self.children)
};
diff --git a/crates/ui/src/traits/styled_ext.rs b/crates/ui/src/traits/styled_ext.rs
index 997e80ca86..09d8a4f74f 100644
--- a/crates/ui/src/traits/styled_ext.rs
+++ b/crates/ui/src/traits/styled_ext.rs
@@ -3,7 +3,7 @@ use gpui::{hsla, Styled, WindowContext};
use crate::prelude::*;
use crate::ElevationIndex;
-fn elevated(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E {
+fn elevated(this: E, cx: &WindowContext, index: ElevationIndex) -> E {
this.bg(cx.theme().colors().elevated_surface_background)
.rounded_lg()
.border_1()
@@ -11,7 +11,7 @@ fn elevated(this: E, cx: &mut WindowContext, index: ElevationIndex) -
.shadow(index.shadow())
}
-fn elevated_borderless(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E {
+fn elevated_borderless(this: E, cx: &WindowContext, index: ElevationIndex) -> E {
this.bg(cx.theme().colors().elevated_surface_background)
.rounded_lg()
.shadow(index.shadow())
@@ -38,14 +38,14 @@ pub trait StyledExt: Styled + Sized {
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
///
/// Example Elements: Title Bar, Panel, Tab Bar, Editor
- fn elevation_1(self, cx: &mut WindowContext) -> Self {
+ fn elevation_1(self, cx: &WindowContext) -> Self {
elevated(self, cx, ElevationIndex::Surface)
}
/// See [`elevation_1`].
///
/// Renders a borderless version [`elevation_1`].
- fn elevation_1_borderless(self, cx: &mut WindowContext) -> Self {
+ fn elevation_1_borderless(self, cx: &WindowContext) -> Self {
elevated_borderless(self, cx, ElevationIndex::Surface)
}
@@ -54,14 +54,14 @@ pub trait StyledExt: Styled + Sized {
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
///
/// Examples: Notifications, Palettes, Detached/Floating Windows, Detached/Floating Panels
- fn elevation_2(self, cx: &mut WindowContext) -> Self {
+ fn elevation_2(self, cx: &WindowContext) -> Self {
elevated(self, cx, ElevationIndex::ElevatedSurface)
}
/// See [`elevation_2`].
///
/// Renders a borderless version [`elevation_2`].
- fn elevation_2_borderless(self, cx: &mut WindowContext) -> Self {
+ fn elevation_2_borderless(self, cx: &WindowContext) -> Self {
elevated_borderless(self, cx, ElevationIndex::ElevatedSurface)
}
@@ -74,24 +74,24 @@ pub trait StyledExt: Styled + Sized {
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
///
/// Examples: Settings Modal, Channel Management, Wizards/Setup UI, Dialogs
- fn elevation_3(self, cx: &mut WindowContext) -> Self {
+ fn elevation_3(self, cx: &WindowContext) -> Self {
elevated(self, cx, ElevationIndex::ModalSurface)
}
/// See [`elevation_3`].
///
/// Renders a borderless version [`elevation_3`].
- fn elevation_3_borderless(self, cx: &mut WindowContext) -> Self {
+ fn elevation_3_borderless(self, cx: &WindowContext) -> Self {
elevated_borderless(self, cx, ElevationIndex::ModalSurface)
}
/// The theme's primary border color.
- fn border_primary(self, cx: &mut WindowContext) -> Self {
+ fn border_primary(self, cx: &WindowContext) -> Self {
self.border_color(cx.theme().colors().border)
}
/// The theme's secondary or muted border color.
- fn border_muted(self, cx: &mut WindowContext) -> Self {
+ fn border_muted(self, cx: &WindowContext) -> Self {
self.border_color(cx.theme().colors().border_variant)
}