askpass_modal.rs

  1use editor::Editor;
  2use futures::channel::oneshot;
  3use gpui::{AppContext, DismissEvent, Entity, EventEmitter, Focusable, Styled};
  4use ui::{
  5    ActiveTheme, AnyElement, App, Button, Clickable, Color, Context, DynamicSpacing, Headline,
  6    HeadlineSize, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon,
  7    LabelSize, ParentElement, Render, SharedString, StyledExt, StyledTypography, Window, div,
  8    h_flex, v_flex,
  9};
 10use workspace::ModalView;
 11
 12pub(crate) struct AskPassModal {
 13    operation: SharedString,
 14    prompt: SharedString,
 15    editor: Entity<Editor>,
 16    tx: Option<oneshot::Sender<String>>,
 17}
 18
 19impl EventEmitter<DismissEvent> for AskPassModal {}
 20impl ModalView for AskPassModal {}
 21impl Focusable for AskPassModal {
 22    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
 23        self.editor.focus_handle(cx)
 24    }
 25}
 26
 27impl AskPassModal {
 28    pub fn new(
 29        operation: SharedString,
 30        prompt: SharedString,
 31        tx: oneshot::Sender<String>,
 32        window: &mut Window,
 33        cx: &mut Context<Self>,
 34    ) -> Self {
 35        let editor = cx.new(|cx| {
 36            let mut editor = Editor::single_line(window, cx);
 37            if prompt.contains("yes/no") || prompt.contains("Username") {
 38                editor.set_masked(false, cx);
 39            } else {
 40                editor.set_masked(true, cx);
 41            }
 42            editor
 43        });
 44        Self {
 45            operation,
 46            prompt,
 47            editor,
 48            tx: Some(tx),
 49        }
 50    }
 51
 52    fn cancel(&mut self, _: &menu::Cancel, _window: &mut Window, cx: &mut Context<Self>) {
 53        cx.emit(DismissEvent);
 54    }
 55
 56    fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
 57        if let Some(tx) = self.tx.take() {
 58            tx.send(self.editor.read(cx).text(cx)).ok();
 59        }
 60        cx.emit(DismissEvent);
 61    }
 62
 63    fn render_hint(&mut self, cx: &mut Context<Self>) -> Option<AnyElement> {
 64        let color = cx.theme().status().info_background;
 65        if (self.prompt.contains("Password") || self.prompt.contains("Username"))
 66            && self.prompt.contains("github.com")
 67        {
 68            return Some(
 69            div()
 70                .p_2()
 71                .bg(color)
 72                .border_t_1()
 73                .border_color(cx.theme().status().info_border)
 74                .child(
 75                    h_flex().gap_2()
 76                        .child(
 77                            Icon::new(IconName::Github).size(IconSize::Small)
 78                        )
 79                        .child(
 80                            Label::new("You may need to configure git for Github.")
 81                                .size(LabelSize::Small),
 82                        )
 83                        .child(Button::new("learn-more", "Learn more").color(Color::Accent).label_size(LabelSize::Small).on_click(|_, _, cx| {
 84                            cx.open_url("https://docs.github.com/en/get-started/git-basics/set-up-git#authenticating-with-github-from-git")
 85                        })),
 86                )
 87                .into_any_element(),
 88        );
 89        }
 90        None
 91    }
 92}
 93
 94impl Render for AskPassModal {
 95    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 96        v_flex()
 97            .key_context("PasswordPrompt")
 98            .on_action(cx.listener(Self::cancel))
 99            .on_action(cx.listener(Self::confirm))
100            .elevation_2(cx)
101            .size_full()
102            .child(
103                h_flex()
104                    .font_buffer(cx)
105                    .px(DynamicSpacing::Base12.rems(cx))
106                    .pt(DynamicSpacing::Base08.rems(cx))
107                    .pb(DynamicSpacing::Base04.rems(cx))
108                    .rounded_t_sm()
109                    .w_full()
110                    .gap_1p5()
111                    .child(Icon::new(IconName::GitBranch).size(IconSize::XSmall))
112                    .child(h_flex().gap_1().overflow_x_hidden().child(
113                        div().max_w_96().overflow_x_hidden().text_ellipsis().child(
114                            Headline::new(self.operation.clone()).size(HeadlineSize::XSmall),
115                        ),
116                    )),
117            )
118            .child(
119                div()
120                    .font_buffer(cx)
121                    .text_buffer(cx)
122                    .py_2()
123                    .px_3()
124                    .bg(cx.theme().colors().editor_background)
125                    .border_t_1()
126                    .border_color(cx.theme().colors().border_variant)
127                    .size_full()
128                    .overflow_hidden()
129                    .child(self.prompt.clone())
130                    .child(self.editor.clone()),
131            )
132            .children(self.render_hint(cx))
133    }
134}