onboarding_modal.rs

  1use std::sync::Arc;
  2
  3use crate::{ZedPredictUpsell, onboarding_event};
  4use ai_onboarding::EditPredictionOnboarding;
  5use client::{Client, UserStore};
  6use db::kvp::Dismissable;
  7use fs::Fs;
  8use gpui::{
  9    ClickEvent, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, MouseDownEvent, Render,
 10    linear_color_stop, linear_gradient,
 11};
 12use language::language_settings::EditPredictionProvider;
 13use settings::update_settings_file;
 14use ui::{Vector, VectorName, prelude::*};
 15use workspace::{ModalView, Workspace};
 16
 17/// Introduces user to Zed's Edit Prediction feature
 18pub struct ZedPredictModal {
 19    onboarding: Entity<EditPredictionOnboarding>,
 20    focus_handle: FocusHandle,
 21}
 22
 23pub(crate) fn set_edit_prediction_provider(provider: EditPredictionProvider, cx: &mut App) {
 24    let fs = <dyn Fs>::global(cx);
 25    update_settings_file(fs, cx, move |settings, _| {
 26        settings
 27            .project
 28            .all_languages
 29            .features
 30            .get_or_insert(Default::default())
 31            .edit_prediction_provider = Some(provider);
 32    });
 33}
 34
 35impl ZedPredictModal {
 36    pub fn toggle(
 37        workspace: &mut Workspace,
 38        user_store: Entity<UserStore>,
 39        client: Arc<Client>,
 40        window: &mut Window,
 41        cx: &mut Context<Workspace>,
 42    ) {
 43        workspace.toggle_modal(window, cx, |_window, cx| {
 44            let weak_entity = cx.weak_entity();
 45            Self {
 46                onboarding: cx.new(|cx| {
 47                    EditPredictionOnboarding::new(
 48                        user_store.clone(),
 49                        client.clone(),
 50                        copilot::Copilot::global(cx)
 51                            .is_some_and(|copilot| copilot.read(cx).status().is_configured()),
 52                        Arc::new({
 53                            let this = weak_entity.clone();
 54                            move |_window, cx| {
 55                                ZedPredictUpsell::set_dismissed(true, cx);
 56                                set_edit_prediction_provider(EditPredictionProvider::Zed, cx);
 57                                this.update(cx, |_, cx| cx.emit(DismissEvent)).ok();
 58                            }
 59                        }),
 60                        Arc::new({
 61                            let this = weak_entity.clone();
 62                            move |window, cx| {
 63                                ZedPredictUpsell::set_dismissed(true, cx);
 64                                set_edit_prediction_provider(EditPredictionProvider::Copilot, cx);
 65                                this.update(cx, |_, cx| cx.emit(DismissEvent)).ok();
 66                                copilot::initiate_sign_in(window, cx);
 67                            }
 68                        }),
 69                        cx,
 70                    )
 71                }),
 72                focus_handle: cx.focus_handle(),
 73            }
 74        });
 75    }
 76
 77    fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
 78        ZedPredictUpsell::set_dismissed(true, cx);
 79        cx.emit(DismissEvent);
 80    }
 81}
 82
 83impl EventEmitter<DismissEvent> for ZedPredictModal {}
 84
 85impl Focusable for ZedPredictModal {
 86    fn focus_handle(&self, _cx: &App) -> FocusHandle {
 87        self.focus_handle.clone()
 88    }
 89}
 90
 91impl ModalView for ZedPredictModal {
 92    fn on_before_dismiss(
 93        &mut self,
 94        _window: &mut Window,
 95        cx: &mut Context<Self>,
 96    ) -> workspace::DismissDecision {
 97        ZedPredictUpsell::set_dismissed(true, cx);
 98        workspace::DismissDecision::Dismiss(true)
 99    }
100}
101
102impl Render for ZedPredictModal {
103    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
104        let window_height = window.viewport_size().height;
105        let max_height = window_height - px(200.);
106
107        v_flex()
108            .id("edit-prediction-onboarding")
109            .key_context("ZedPredictModal")
110            .relative()
111            .w(px(550.))
112            .h_full()
113            .max_h(max_height)
114            .p_4()
115            .gap_2()
116            .elevation_3(cx)
117            .track_focus(&self.focus_handle(cx))
118            .overflow_hidden()
119            .on_action(cx.listener(Self::cancel))
120            .on_action(cx.listener(|_, _: &menu::Cancel, _window, cx| {
121                onboarding_event!("Cancelled", trigger = "Action");
122                cx.emit(DismissEvent);
123            }))
124            .on_any_mouse_down(cx.listener(|this, _: &MouseDownEvent, window, _cx| {
125                this.focus_handle.focus(window);
126            }))
127            .child(
128                div()
129                    .opacity(0.5)
130                    .absolute()
131                    .top(px(-8.0))
132                    .right_0()
133                    .w(px(400.))
134                    .h(px(92.))
135                    .child(
136                        Vector::new(VectorName::AiGrid, rems_from_px(400.), rems_from_px(92.))
137                            .color(Color::Custom(cx.theme().colors().text.alpha(0.32))),
138                    ),
139            )
140            .child(
141                div()
142                    .absolute()
143                    .top_0()
144                    .right_0()
145                    .w(px(660.))
146                    .h(px(401.))
147                    .overflow_hidden()
148                    .bg(linear_gradient(
149                        75.,
150                        linear_color_stop(cx.theme().colors().panel_background.alpha(0.01), 1.0),
151                        linear_color_stop(cx.theme().colors().panel_background, 0.45),
152                    )),
153            )
154            .child(h_flex().absolute().top_2().right_2().child(
155                IconButton::new("cancel", IconName::Close).on_click(cx.listener(
156                    |_, _: &ClickEvent, _window, cx| {
157                        onboarding_event!("Cancelled", trigger = "X click");
158                        cx.emit(DismissEvent);
159                    },
160                )),
161            ))
162            .child(self.onboarding.clone())
163    }
164}