diff --git a/crates/remote_connection/src/remote_connection.rs b/crates/remote_connection/src/remote_connection.rs index d4df85d7b94b52c6f6bef0f052e515797b4f79c3..e0bb70165e9adc6e2c8c81e933cf88b2273da79f 100644 --- a/crates/remote_connection/src/remote_connection.rs +++ b/crates/remote_connection/src/remote_connection.rs @@ -15,8 +15,8 @@ use semver::Version; use settings::Settings; use theme::ThemeSettings; use ui::{ - ActiveTheme, Color, CommonAnimationExt, Context, InteractiveElement, IntoElement, KeyBinding, - LabelCommon, ListItem, Styled, Window, prelude::*, + ActiveTheme, CommonAnimationExt, Context, InteractiveElement, KeyBinding, ListItem, Tooltip, + prelude::*, }; use ui_input::{ERASED_EDITOR_FACTORY, ErasedEditor}; use workspace::{DismissDecision, ModalView}; @@ -30,6 +30,8 @@ pub struct RemoteConnectionPrompt { prompt: Option<(Entity, oneshot::Sender)>, cancellation: Option>, editor: Arc, + is_password_prompt: bool, + is_masked: bool, } impl Drop for RemoteConnectionPrompt { @@ -70,6 +72,8 @@ impl RemoteConnectionPrompt { status_message: None, cancellation: None, prompt: None, + is_password_prompt: false, + is_masked: true, } } @@ -85,7 +89,9 @@ impl RemoteConnectionPrompt { cx: &mut Context, ) { let is_yes_no = prompt.contains("yes/no"); - self.editor.set_masked(!is_yes_no, window, cx); + self.is_password_prompt = !is_yes_no; + self.is_masked = !is_yes_no; + self.editor.set_masked(self.is_masked, window, cx); let markdown = cx.new(|cx| Markdown::new_text(prompt.into(), cx)); self.prompt = Some((markdown, tx)); @@ -133,40 +139,87 @@ impl Render for RemoteConnectionPrompt { ..Default::default() }; + let is_password_prompt = self.is_password_prompt; + let is_masked = self.is_masked; + let (masked_password_icon, masked_password_tooltip) = if is_masked { + (IconName::Eye, "Toggle to Unmask Password") + } else { + (IconName::EyeOff, "Toggle to Mask Password") + }; + v_flex() .key_context("PasswordPrompt") .p_2() .size_full() - .text_buffer(cx) - .when_some(self.status_message.clone(), |el, status_message| { - el.child( + .when_some(self.prompt.as_ref(), |this, prompt| { + this.child( + v_flex() + .text_sm() + .size_full() + .overflow_hidden() + .child( + h_flex() + .w_full() + .justify_between() + .child(MarkdownElement::new(prompt.0.clone(), markdown_style)) + .when(is_password_prompt, |this| { + this.child( + IconButton::new("toggle_mask", masked_password_icon) + .icon_size(IconSize::Small) + .tooltip(Tooltip::text(masked_password_tooltip)) + .on_click(cx.listener(|this, _, window, cx| { + this.is_masked = !this.is_masked; + this.editor.set_masked(this.is_masked, window, cx); + window.focus(&this.editor.focus_handle(cx), cx); + cx.notify(); + })), + ) + }), + ) + .child(div().flex_1().child(self.editor.render(window, cx))), + ) + .when(window.capslock().on, |this| { + this.child( + h_flex() + .py_0p5() + .min_w_0() + .w_full() + .gap_1() + .child( + Icon::new(IconName::Warning) + .size(IconSize::Small) + .color(Color::Muted), + ) + .child( + Label::new("Caps lock is on.") + .size(LabelSize::Small) + .color(Color::Muted), + ), + ) + }) + }) + .when_some(self.status_message.clone(), |this, status_message| { + this.child( h_flex() - .gap_2() + .min_w_0() + .w_full() + .mt_1() + .gap_1() .child( - Icon::new(IconName::ArrowCircle) + Icon::new(IconName::LoadCircle) + .size(IconSize::Small) .color(Color::Muted) .with_rotate_animation(2), ) .child( - div() - .text_ellipsis() - .overflow_x_hidden() - .child(format!("{}…", status_message)), + Label::new(format!("{}…", status_message)) + .size(LabelSize::Small) + .color(Color::Muted) + .truncate() + .flex_1(), ), ) }) - .when_some(self.prompt.as_ref(), |el, prompt| { - el.child( - div() - .size_full() - .overflow_hidden() - .child(MarkdownElement::new(prompt.0.clone(), markdown_style)) - .child(self.editor.render(window, cx)), - ) - .when(window.capslock().on, |el| { - el.child(Label::new("⚠️ ⇪ is on")) - }) - }) } }