onboarding: Add fast-follow adjustments (#35814)

Release Notes:

- N/A
This commit is contained in:
Danilo Leal 2025-08-07 19:01:52 -03:00 committed by GitHub
parent 106d4cfce9
commit 070f7dbe1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 155 additions and 72 deletions

View file

@ -1,7 +1,7 @@
use std::sync::Arc;
use ai_onboarding::{AiUpsellCard, SignInStatus};
use client::UserStore;
use ai_onboarding::AiUpsellCard;
use client::{Client, UserStore};
use fs::Fs;
use gpui::{
Action, AnyView, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, WeakEntity,
@ -12,8 +12,8 @@ use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageMod
use project::DisableAiSettings;
use settings::{Settings, update_settings_file};
use ui::{
Badge, ButtonLike, Divider, Modal, ModalFooter, ModalHeader, Section, SwitchField, ToggleState,
prelude::*, tooltip_container,
Badge, ButtonLike, Divider, KeyBinding, Modal, ModalFooter, ModalHeader, Section, SwitchField,
ToggleState, prelude::*, tooltip_container,
};
use util::ResultExt;
use workspace::{ModalView, Workspace};
@ -88,7 +88,7 @@ fn render_privacy_card(tab_index: &mut isize, disabled: bool, cx: &mut App) -> i
h_flex()
.gap_2()
.justify_between()
.child(Label::new("We don't train models using your data"))
.child(Label::new("Privacy is the default for Zed"))
.child(
h_flex().gap_1().child(privacy_badge()).child(
Button::new("learn_more", "Learn More")
@ -109,7 +109,7 @@ fn render_privacy_card(tab_index: &mut isize, disabled: bool, cx: &mut App) -> i
)
.child(
Label::new(
"Feel confident in the security and privacy of your projects using Zed.",
"Any use or storage of your data is with your explicit, single-use, opt-in consent.",
)
.size(LabelSize::Small)
.color(Color::Muted),
@ -240,6 +240,7 @@ fn render_llm_provider_card(
pub(crate) fn render_ai_setup_page(
workspace: WeakEntity<Workspace>,
user_store: Entity<UserStore>,
client: Arc<Client>,
window: &mut Window,
cx: &mut App,
) -> impl IntoElement {
@ -283,15 +284,16 @@ pub(crate) fn render_ai_setup_page(
v_flex()
.mt_2()
.gap_6()
.child(AiUpsellCard {
sign_in_status: SignInStatus::SignedIn,
sign_in: Arc::new(|_, _| {}),
account_too_young: user_store.read(cx).account_too_young(),
user_plan: user_store.read(cx).plan(),
tab_index: Some({
.child({
let mut ai_upsell_card =
AiUpsellCard::new(client, &user_store, user_store.read(cx).plan(), cx);
ai_upsell_card.tab_index = Some({
tab_index += 1;
tab_index - 1
}),
});
ai_upsell_card
})
.child(render_llm_provider_section(
&mut tab_index,
@ -336,6 +338,10 @@ impl AiConfigurationModal {
selected_provider,
}
}
fn cancel(&mut self, _: &menu::Cancel, cx: &mut Context<Self>) {
cx.emit(DismissEvent);
}
}
impl ModalView for AiConfigurationModal {}
@ -349,11 +355,15 @@ impl Focusable for AiConfigurationModal {
}
impl Render for AiConfigurationModal {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.key_context("OnboardingAiConfigurationModal")
.w(rems(34.))
.elevation_3(cx)
.track_focus(&self.focus_handle)
.on_action(
cx.listener(|this, _: &menu::Cancel, _window, cx| this.cancel(&menu::Cancel, cx)),
)
.child(
Modal::new("onboarding-ai-setup-modal", None)
.header(
@ -368,18 +378,19 @@ impl Render for AiConfigurationModal {
.section(Section::new().child(self.configuration_view.clone()))
.footer(
ModalFooter::new().end_slot(
h_flex()
.gap_1()
.child(
Button::new("onboarding-closing-cancel", "Cancel")
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
Button::new("ai-onb-modal-Done", "Done")
.key_binding(
KeyBinding::for_action_in(
&menu::Cancel,
&self.focus_handle.clone(),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.child(Button::new("save-btn", "Done").on_click(cx.listener(
|_, _, window, cx| {
window.dispatch_action(menu::Confirm.boxed_clone(), cx);
cx.emit(DismissEvent);
},
))),
.on_click(cx.listener(|this, _event, _window, cx| {
this.cancel(&menu::Cancel, cx)
})),
),
),
)
@ -396,7 +407,7 @@ impl AiPrivacyTooltip {
impl Render for AiPrivacyTooltip {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
const DESCRIPTION: &'static str = "One of Zed's most important principles is transparency. This is why we are and value open-source so much. And it wouldn't be any different with AI.";
const DESCRIPTION: &'static str = "We believe in opt-in data sharing as the default for building AI products, rather than opt-out. We'll only use or store your data if you affirmatively send it to us. ";
tooltip_container(window, cx, move |this, _, _| {
this.child(
@ -407,7 +418,7 @@ impl Render for AiPrivacyTooltip {
.size(IconSize::Small)
.color(Color::Muted),
)
.child(Label::new("Privacy Principle")),
.child(Label::new("Privacy First")),
)
.child(
div().max_w_64().child(

View file

@ -201,12 +201,15 @@ fn render_telemetry_section(tab_index: &mut isize, cx: &App) -> impl IntoElement
let fs = <dyn Fs>::global(cx);
v_flex()
.pt_6()
.gap_4()
.border_t_1()
.border_color(cx.theme().colors().border_variant.opacity(0.5))
.child(Label::new("Telemetry").size(LabelSize::Large))
.child(SwitchField::new(
"onboarding-telemetry-metrics",
"Help Improve Zed",
Some("Sending anonymous usage data helps us build the right features and create the best experience.".into()),
Some("Anonymous usage data helps us build the right features and improve your experience.".into()),
if TelemetrySettings::get_global(cx).metrics {
ui::ToggleState::Selected
} else {
@ -294,7 +297,7 @@ fn render_base_keymap_section(tab_index: &mut isize, cx: &mut App) -> impl IntoE
ToggleButtonWithIcon::new("Emacs", IconName::EditorEmacs, |_, _, cx| {
write_keymap_base(BaseKeymap::Emacs, cx);
}),
ToggleButtonWithIcon::new("Cursor (Beta)", IconName::EditorCursor, |_, _, cx| {
ToggleButtonWithIcon::new("Cursor", IconName::EditorCursor, |_, _, cx| {
write_keymap_base(BaseKeymap::Cursor, cx);
}),
],
@ -326,10 +329,7 @@ fn render_vim_mode_switch(tab_index: &mut isize, cx: &mut App) -> impl IntoEleme
SwitchField::new(
"onboarding-vim-mode",
"Vim Mode",
Some(
"Coming from Neovim? Zed's first-class implementation of Vim Mode has got your back."
.into(),
),
Some("Coming from Neovim? Use our first-class implementation of Vim Mode.".into()),
toggle_state,
{
let fs = <dyn Fs>::global(cx);

View file

@ -584,11 +584,15 @@ fn render_popular_settings_section(
window: &mut Window,
cx: &mut App,
) -> impl IntoElement {
const LIGATURE_TOOLTIP: &'static str = "Ligatures are when a font creates a special character out of combining two characters into one. For example, with ligatures turned on, =/= would become ≠.";
const LIGATURE_TOOLTIP: &'static str =
"Font ligatures combine two characters into one. For example, turning =/= into ≠.";
v_flex()
.gap_5()
.child(Label::new("Popular Settings").size(LabelSize::Large).mt_8())
.pt_6()
.gap_4()
.border_t_1()
.border_color(cx.theme().colors().border_variant.opacity(0.5))
.child(Label::new("Popular Settings").size(LabelSize::Large))
.child(render_font_customization_section(tab_index, window, cx))
.child(
SwitchField::new(
@ -683,7 +687,10 @@ fn render_popular_settings_section(
[
ToggleButtonSimple::new("Auto", |_, _, cx| {
write_show_mini_map(ShowMinimap::Auto, cx);
}),
})
.tooltip(Tooltip::text(
"Show the minimap if the editor's scrollbar is visible.",
)),
ToggleButtonSimple::new("Always", |_, _, cx| {
write_show_mini_map(ShowMinimap::Always, cx);
}),
@ -707,7 +714,7 @@ fn render_popular_settings_section(
pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
let mut tab_index = 0;
v_flex()
.gap_4()
.gap_6()
.child(render_import_settings_section(&mut tab_index, cx))
.child(render_popular_settings_section(&mut tab_index, window, cx))
}

View file

@ -77,6 +77,8 @@ actions!(
ActivateAISetupPage,
/// Finish the onboarding process.
Finish,
/// Sign in while in the onboarding flow.
SignIn
]
);
@ -376,6 +378,7 @@ impl Onboarding {
cx,
)
.map(|kb| kb.size(rems_from_px(12.)));
if ai_setup_page {
this.child(
ButtonLike::new("start_building")
@ -387,14 +390,7 @@ impl Onboarding {
.w_full()
.justify_between()
.child(Label::new("Start Building"))
.child(keybinding.map_or_else(
|| {
Icon::new(IconName::Check)
.size(IconSize::Small)
.into_any_element()
},
IntoElement::into_any_element,
)),
.children(keybinding),
)
.on_click(|_, window, cx| {
window.dispatch_action(Finish.boxed_clone(), cx);
@ -409,11 +405,10 @@ impl Onboarding {
.ml_1()
.w_full()
.justify_between()
.child(Label::new("Skip All"))
.child(keybinding.map_or_else(
|| gpui::Empty.into_any_element(),
IntoElement::into_any_element,
)),
.child(
Label::new("Skip All").color(Color::Muted),
)
.children(keybinding),
)
.on_click(|_, window, cx| {
window.dispatch_action(Finish.boxed_clone(), cx);
@ -435,23 +430,39 @@ impl Onboarding {
Button::new("sign_in", "Sign In")
.full_width()
.style(ButtonStyle::Outlined)
.size(ButtonSize::Medium)
.key_binding(
KeyBinding::for_action_in(&SignIn, &self.focus_handle, window, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(|_, window, cx| {
let client = Client::global(cx);
window
.spawn(cx, async move |cx| {
client
.sign_in_with_optional_connect(true, &cx)
.await
.notify_async_err(cx);
})
.detach();
window.dispatch_action(SignIn.boxed_clone(), cx);
})
.into_any_element()
},
)
}
fn on_finish(_: &Finish, _: &mut Window, cx: &mut App) {
go_to_welcome_page(cx);
}
fn handle_sign_in(_: &SignIn, window: &mut Window, cx: &mut App) {
let client = Client::global(cx);
window
.spawn(cx, async move |cx| {
client
.sign_in_with_optional_connect(true, &cx)
.await
.notify_async_err(cx);
})
.detach();
}
fn render_page(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
let client = Client::global(cx);
match self.selected_page {
SelectedPage::Basics => crate::basics_page::render_basics_page(cx).into_any_element(),
SelectedPage::Editing => {
@ -460,16 +471,13 @@ impl Onboarding {
SelectedPage::AiSetup => crate::ai_setup_page::render_ai_setup_page(
self.workspace.clone(),
self.user_store.clone(),
client,
window,
cx,
)
.into_any_element(),
}
}
fn on_finish(_: &Finish, _: &mut Window, cx: &mut App) {
go_to_welcome_page(cx);
}
}
impl Render for Onboarding {
@ -486,6 +494,7 @@ impl Render for Onboarding {
.size_full()
.bg(cx.theme().colors().editor_background)
.on_action(Self::on_finish)
.on_action(Self::handle_sign_in)
.on_action(cx.listener(|this, _: &ActivateBasicsPage, _, cx| {
this.set_page(SelectedPage::Basics, cx);
}))