diff --git a/crates/onboarding_ui/src/onboarding_ui.rs b/crates/onboarding_ui/src/onboarding_ui.rs index ec01bd3ee767239bb61b705fdbc8e77586a57001..17c6851b157b75675c5510d44ee20903bd356eec 100644 --- a/crates/onboarding_ui/src/onboarding_ui.rs +++ b/crates/onboarding_ui/src/onboarding_ui.rs @@ -1,12 +1,16 @@ #![allow(unused, dead_code)] +use client::Client; use command_palette_hooks::CommandPaletteFilter; use feature_flags::FeatureFlagAppExt as _; use gpui::{Entity, EventEmitter, FocusHandle, Focusable, WeakEntity, actions, prelude::*}; use settings_ui::SettingsUiFeatureFlag; -use ui::prelude::*; +use std::sync::Arc; +use ui::{KeyBinding, ListItem, Vector, VectorName, prelude::*}; +use util::ResultExt; use workspace::{ Workspace, WorkspaceId, item::{Item, ItemEvent}, + notifications::NotifyResultExt, }; actions!( @@ -27,41 +31,10 @@ actions!( pub fn init(cx: &mut App) { cx.observe_new(|workspace: &mut Workspace, _, _cx| { workspace.register_action(|workspace, _: &ShowOnboarding, window, cx| { - let onboarding = cx.new(|cx| OnboardingUI::new(workspace, cx)); + let client = workspace.client().clone(); + let onboarding = cx.new(|cx| OnboardingUI::new(workspace, client, cx)); workspace.add_item_to_active_pane(Box::new(onboarding), None, true, window, cx); }); - - workspace.register_action(|_workspace, _: &JumpToBasics, _window, _cx| { - // Jump to basics implementation - }); - - workspace.register_action(|_workspace, _: &JumpToEditing, _window, _cx| { - // Jump to editing implementation - }); - - workspace.register_action(|_workspace, _: &JumpToAiSetup, _window, _cx| { - // Jump to AI setup implementation - }); - - workspace.register_action(|_workspace, _: &JumpToWelcome, _window, _cx| { - // Jump to welcome implementation - }); - - workspace.register_action(|_workspace, _: &NextPage, _window, _cx| { - // Next page implementation - }); - - workspace.register_action(|_workspace, _: &PreviousPage, _window, _cx| { - // Previous page implementation - }); - - workspace.register_action(|_workspace, _: &ToggleFocus, _window, _cx| { - // Toggle focus implementation - }); - - workspace.register_action(|_workspace, _: &ResetOnboarding, _window, _cx| { - // Reset onboarding implementation - }); }) .detach(); @@ -131,6 +104,7 @@ pub struct OnboardingUI { // Workspace reference for Item trait workspace: WeakEntity, + client: Arc, } impl EventEmitter for OnboardingUI {} @@ -152,26 +126,47 @@ impl Render for OnboardingUI { window: &mut gpui::Window, cx: &mut Context, ) -> impl gpui::IntoElement { - h_flex() - .id("onboarding-ui") - .key_context("Onboarding") - .track_focus(&self.focus_handle) - .w(px(904.)) - .h(px(500.)) - .gap(px(48.)) - .child(v_flex().h_full().w(px(256.)).child("nav")) - .child(self.render_active_page(window, cx)) + div() + .bg(cx.theme().colors().editor_background) + .size_full() + .flex() + .items_center() + .justify_center() + .overflow_hidden() + .child( + h_flex() + .id("onboarding-ui") + .key_context("Onboarding") + .track_focus(&self.focus_handle) + .on_action(cx.listener(Self::handle_jump_to_basics)) + .on_action(cx.listener(Self::handle_jump_to_editing)) + .on_action(cx.listener(Self::handle_jump_to_ai_setup)) + .on_action(cx.listener(Self::handle_jump_to_welcome)) + .on_action(cx.listener(Self::handle_next_page)) + .on_action(cx.listener(Self::handle_previous_page)) + .w(px(904.)) + .h(px(500.)) + .gap(px(48.)) + .child(self.render_navigation(window, cx)) + .child( + v_flex() + .h_full() + .flex_1() + .child(div().flex_1().child(self.render_active_page(window, cx))), + ), + ) } } impl OnboardingUI { - pub fn new(workspace: &Workspace, cx: &mut Context) -> Self { + pub fn new(workspace: &Workspace, client: Arc, cx: &mut Context) -> Self { Self { focus_handle: cx.focus_handle(), current_page: OnboardingPage::Basics, current_focus: OnboardingFocus::Page, completed_pages: [false; 4], workspace: workspace.weak_handle(), + client, } } @@ -182,6 +177,7 @@ impl OnboardingUI { cx: &mut Context, ) { self.current_page = page; + cx.emit(ItemEvent::UpdateTab); cx.notify(); } @@ -230,11 +226,142 @@ impl OnboardingUI { cx.notify(); } + fn handle_jump_to_basics( + &mut self, + _: &JumpToBasics, + window: &mut Window, + cx: &mut Context, + ) { + self.jump_to_page(OnboardingPage::Basics, window, cx); + } + + fn handle_jump_to_editing( + &mut self, + _: &JumpToEditing, + window: &mut Window, + cx: &mut Context, + ) { + self.jump_to_page(OnboardingPage::Editing, window, cx); + } + + fn handle_jump_to_ai_setup( + &mut self, + _: &JumpToAiSetup, + window: &mut Window, + cx: &mut Context, + ) { + self.jump_to_page(OnboardingPage::AiSetup, window, cx); + } + + fn handle_jump_to_welcome( + &mut self, + _: &JumpToWelcome, + window: &mut Window, + cx: &mut Context, + ) { + self.jump_to_page(OnboardingPage::Welcome, window, cx); + } + + fn handle_next_page(&mut self, _: &NextPage, window: &mut Window, cx: &mut Context) { + self.next_page(window, cx); + } + + fn handle_previous_page( + &mut self, + _: &PreviousPage, + window: &mut Window, + cx: &mut Context, + ) { + self.previous_page(window, cx); + } + + fn render_navigation( + &mut self, + window: &mut Window, + cx: &mut Context, + ) -> impl gpui::IntoElement { + v_flex() + .h_full() + .w(px(256.)) + .gap_2() + .justify_between() + .child( + v_flex() + .w_full() + .gap_px() + .child( + h_flex() + .w_full() + .justify_between() + .child(Vector::new(VectorName::ZedLogo, rems(2.), rems(2.))) + .child(self.render_sign_in_button(cx)), + ) + .child(self.render_nav_item(OnboardingPage::Basics, "The Basics", "1", cx)) + .child(self.render_nav_item( + OnboardingPage::Editing, + "Editing Experience", + "2", + cx, + )) + .child(self.render_nav_item(OnboardingPage::AiSetup, "AI Setup", "3", cx)) + .child(self.render_nav_item(OnboardingPage::Welcome, "Welcome", "4", cx)), + ) + .child(self.render_bottom_controls(window, cx)) + } + + fn render_nav_item( + &mut self, + page: OnboardingPage, + label: impl Into, + shortcut: impl Into, + cx: &mut Context, + ) -> impl gpui::IntoElement { + let selected = self.current_page == page; + let label = label.into(); + let shortcut = shortcut.into(); + + ListItem::new(label.clone()) + .child( + h_flex() + .w_full() + .justify_between() + .child(Label::new(label.clone())) + .child(Label::new(format!("⌘{}", shortcut.clone())).color(Color::Muted)), + ) + .selectable(true) + .toggle_state(selected) + .on_click(cx.listener(move |this, _, window, cx| { + this.jump_to_page(page, window, cx); + })) + } + + fn render_bottom_controls( + &mut self, + window: &mut gpui::Window, + cx: &mut Context, + ) -> impl gpui::IntoElement { + h_flex().w_full().p_4().child( + Button::new( + "next", + if self.current_page == OnboardingPage::Welcome { + "Get Started" + } else { + "Next" + }, + ) + .style(ButtonStyle::Filled) + .key_binding(KeyBinding::for_action(&NextPage, window, cx)) + .on_click(cx.listener(|this, _, window, cx| { + this.next_page(window, cx); + })), + ) + } + fn render_active_page( &mut self, _window: &mut gpui::Window, _cx: &mut Context, - ) -> impl gpui::IntoElement { + ) -> AnyElement { match self.current_page { OnboardingPage::Basics => self.render_basics_page(), OnboardingPage::Editing => self.render_editing_page(), @@ -243,20 +370,54 @@ impl OnboardingUI { } } - fn render_basics_page(&self) -> impl gpui::IntoElement { - v_flex().h_full().w_full().child("Basics Page") + fn render_basics_page(&self) -> AnyElement { + v_flex() + .h_full() + .w_full() + .child("Basics Page") + .into_any_element() + } + + fn render_editing_page(&self) -> AnyElement { + v_flex() + .h_full() + .w_full() + .child("Editing Page") + .into_any_element() } - fn render_editing_page(&self) -> impl gpui::IntoElement { - v_flex().h_full().w_full().child("Editing Page") + fn render_ai_setup_page(&self) -> AnyElement { + v_flex() + .h_full() + .w_full() + .child("AI Setup Page") + .into_any_element() } - fn render_ai_setup_page(&self) -> impl gpui::IntoElement { - v_flex().h_full().w_full().child("AI Setup Page") + fn render_welcome_page(&self) -> AnyElement { + v_flex() + .h_full() + .w_full() + .child("Welcome Page") + .into_any_element() } - fn render_welcome_page(&self) -> impl gpui::IntoElement { - v_flex().h_full().w_full().child("Welcome Page") + fn render_sign_in_button(&mut self, cx: &mut Context) -> impl IntoElement { + let client = self.client.clone(); + Button::new("sign_in", "Sign in") + .label_size(LabelSize::Small) + .on_click(cx.listener(move |_, _, window, cx| { + let client = client.clone(); + window + .spawn(cx, async move |cx| { + client + .authenticate_and_connect(true, &cx) + .await + .into_response() + .notify_async_err(cx); + }) + .detach(); + })) } } @@ -282,9 +443,10 @@ impl Item for OnboardingUI { cx: &mut Context, ) -> Option> { let weak_workspace = self.workspace.clone(); + let client = self.client.clone(); if let Some(workspace) = weak_workspace.upgrade() { workspace.update(cx, |workspace, cx| { - Some(cx.new(|cx| OnboardingUI::new(workspace, cx))) + Some(cx.new(|cx| OnboardingUI::new(workspace, client, cx))) }) } else { None