From 0ef9cefe0fd15f9093851fb36a9805dd93d21b73 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 27 Mar 2023 23:16:30 -0700 Subject: [PATCH] Finish shape of copilot auth UI --- crates/copilot/src/copilot.rs | 8 +- crates/copilot/src/sign_in.rs | 227 ++++++++++++++++++++++---------- crates/theme/src/theme.rs | 24 +++- crates/theme/src/ui.rs | 62 ++++++++- styles/src/styleTree/copilot.ts | 144 +++++++++++++++----- 5 files changed, 354 insertions(+), 111 deletions(-) diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 2763eea0fd363fe175d7761db94efa943ee1b7f7..5ad32ed3a571512261a2e6efdb2b87d3d710e1ec 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -51,10 +51,10 @@ enum CopilotServer { #[derive(Clone, Debug)] enum SignInStatus { Authorized { - user: String, + _user: String, }, Unauthorized { - user: String, + _user: String, }, SigningIn { prompt: Option, @@ -321,10 +321,10 @@ impl Copilot { if let CopilotServer::Started { status, .. } = &mut self.server { *status = match lsp_status { request::SignInStatus::Ok { user } | request::SignInStatus::MaybeOk { user } => { - SignInStatus::Authorized { user } + SignInStatus::Authorized { _user: user } } request::SignInStatus::NotAuthorized { user } => { - SignInStatus::Unauthorized { user } + SignInStatus::Unauthorized { _user: user } } _ => SignInStatus::SignedOut, }; diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index cdec0b8963cb1ce5a04875dbe3e3da6c1db24353..80411f18dac4743df0c36eb54391ebf0b3e9a1cf 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -26,13 +26,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.remove_window(window_id); } - let window_size = cx - .global::() - .theme - .copilot - .auth - .popup_dimensions - .to_vec(); + let window_size = cx.global::().theme.copilot.modal.dimensions(); let (window_id, _) = cx.add_window( WindowOptions { @@ -43,13 +37,15 @@ pub fn init(cx: &mut MutableAppContext) { titlebar: None, center: true, focus: false, - kind: WindowKind::PopUp, + kind: WindowKind::Normal, is_movable: true, screen: None, }, |_| CopilotCodeVerification::new(prompt), ); code_verification_window_id = Some(window_id); + + cx.activate_window(window_id); } _ => { if let Some(window_id) = code_verification_window_id.take() { @@ -59,6 +55,26 @@ pub fn init(cx: &mut MutableAppContext) { } }) .detach(); + + // let window_size = cx.global::().theme.copilot.modal.dimensions(); + + // let (_window_id, _) = cx.add_window( + // WindowOptions { + // bounds: gpui::WindowBounds::Fixed(RectF::new(Default::default(), window_size)), + // titlebar: None, + // center: true, + // focus: false, + // kind: WindowKind::PopUp, + // is_movable: true, + // screen: None, + // }, + // |_| { + // CopilotCodeVerification::new(PromptUserDeviceFlow { + // user_code: "ABCD-1234".to_string(), + // verification_uri: "https://github.com/login/device".to_string(), + // }) + // }, + // ); } pub struct CopilotCodeVerification { @@ -74,71 +90,146 @@ impl View for CopilotCodeVerification { "CopilotCodeVerification" } + fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut gpui::ViewContext) { + cx.notify() + } + fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox { let style = cx.global::().theme.copilot.clone(); - let instruction_text = style.auth.instruction_text; - let user_code_text = style.auth.user_code; - let button = style.auth.button; - let button_width = style.auth.button_width; - let height = style.auth.popup_dimensions.height; - - let user_code = self.prompt.user_code.replace("-", " - "); - - Flex::column() - .with_child( - MouseEventHandler::::new(0, cx, |state, _cx| { - let style = style.auth.close_icon.style_for(state, false); - theme::ui::icon(style).boxed() - }) - .on_click(gpui::MouseButton::Left, move |_, cx| { - let window_id = cx.window_id(); - cx.remove_window(window_id); - }) - .with_cursor_style(gpui::CursorStyle::PointingHand) - .aligned() - .right() - .boxed(), - ) - .with_child( - Flex::column() - .align_children_center() - .with_children([ - theme::ui::svg(&style.auth.copilot_icon).boxed(), - Label::new( - "Here is your code to authenticate with github", - instruction_text.clone(), - ) + let copied = cx + .read_from_clipboard() + .map(|item| item.text() == &self.prompt.user_code) + .unwrap_or(false); + + theme::ui::modal("Authenticate Copilot", &style.modal, cx, |cx| { + Flex::column() + .align_children_center() + .with_children([ + Flex::column() + .with_children([ + Flex::row() + .with_children([ + theme::ui::svg(&style.auth.copilot_icon).boxed(), + theme::ui::svg(&style.auth.plus_icon).boxed(), + theme::ui::svg(&style.auth.zed_icon).boxed(), + ]) + .boxed(), + Label::new("Copilot for Zed", style.auth.header_text.clone()).boxed(), + ]) + .align_children_center() + .contained() + .with_style(style.auth.header_group) + .aligned() + .boxed(), + Flex::column() + .with_children([ + Label::new( + "Here is your code to authenticate with github", + style.auth.instruction_text.clone(), + ) + .boxed(), + MouseEventHandler::::new(0, cx, |state, _cx| { + Flex::row() + .with_children([ + Label::new( + self.prompt.user_code.clone(), + style.auth.device_code.clone(), + ) + .aligned() + .contained() + .with_style(style.auth.device_code_left_container) + .constrained() + .with_width(style.auth.device_code_left) + .boxed(), + Empty::new() + .constrained() + .with_width(1.) + .with_height(style.auth.device_code_seperator_height) + .contained() + .with_background_color( + style + .auth + .cta_button + .style_for(state, false) + .container + .border + .color, + ) + .boxed(), + Label::new( + if copied { "Copied!" } else { "Copy" }, + style + .auth + .cta_button + .style_for(state, false) + .text + .clone(), + ) + .aligned() + .contained() + .with_style(style.auth.device_code_right_container) + .constrained() + .with_width(style.auth.device_code_right) + .boxed(), + ]) + .contained() + .with_style( + style + .auth + .device_code_cta + .style_for(state, false) + .container, + ) + .constrained() + .with_width(style.auth.content_width) + .boxed() + }) + .on_click(gpui::MouseButton::Left, { + let user_code = self.prompt.user_code.clone(); + move |_, cx| { + cx.platform() + .write_to_clipboard(ClipboardItem::new(user_code.clone())); + cx.notify(); + } + }) + .with_cursor_style(gpui::CursorStyle::PointingHand) + .boxed(), + ]) + .align_children_center() + .contained() + .with_style(style.auth.device_code_group) + .aligned() .boxed(), - Label::new(user_code, user_code_text.clone()).boxed(), - theme::ui::cta_button_with_click("Copy Code", button_width, &button, cx, { - let user_code = self.prompt.user_code.clone(); - move |_, cx| { - cx.platform() - .write_to_clipboard(ClipboardItem::new(user_code.clone())) - } - }), - Label::new("Copy it and enter it on GitHub", instruction_text.clone()) + Flex::column() + .with_children([ + Label::new( + "Copy it and enter it on GitHub", + style.auth.instruction_text.clone(), + ) .boxed(), - theme::ui::cta_button_with_click( - "Go to Github", - button_width, - &button, - cx, - { - let verification_uri = self.prompt.verification_uri.clone(); - move |_, cx| cx.platform().open_url(&verification_uri) - }, - ), - ]) - .aligned() - .boxed(), - ) - .contained() - .with_style(style.auth.popup_container) - .constrained() - .with_height(height) - .boxed() + theme::ui::cta_button_with_click( + "Go to Github", + style.auth.content_width, + &style.auth.cta_button, + cx, + { + let verification_uri = self.prompt.verification_uri.clone(); + move |_, cx| cx.platform().open_url(&verification_uri) + }, + ), + ]) + .align_children_center() + .contained() + .with_style(style.auth.github_group) + .aligned() + .boxed(), + ]) + .constrained() + .with_width(style.auth.content_width) + .aligned() + .boxed() + }) } } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index ce4d8a04fba102ca1f9bffb42c82aa9d35360832..ae072eca32d1e3bc00ca0511b3f550a6f9385585 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -9,7 +9,7 @@ use gpui::{ use serde::{de::DeserializeOwned, Deserialize}; use serde_json::Value; use std::{collections::HashMap, sync::Arc}; -use ui::{ButtonStyle, CheckboxStyle, Dimensions, IconStyle, SvgStyle}; +use ui::{ButtonStyle, CheckboxStyle, ModalStyle, SvgStyle}; pub mod ui; @@ -118,19 +118,29 @@ pub struct AvatarStyle { #[derive(Deserialize, Default, Clone)] pub struct Copilot { + pub modal: ModalStyle, pub auth: CopilotAuth, } #[derive(Deserialize, Default, Clone)] pub struct CopilotAuth { - pub popup_container: ContainerStyle, - pub popup_dimensions: Dimensions, pub instruction_text: TextStyle, - pub user_code: TextStyle, - pub button: ButtonStyle, - pub button_width: f32, + pub cta_button: ButtonStyle, + pub content_width: f32, pub copilot_icon: SvgStyle, - pub close_icon: Interactive, + pub plus_icon: SvgStyle, + pub zed_icon: SvgStyle, + pub header_text: TextStyle, + pub device_code_group: ContainerStyle, + pub github_group: ContainerStyle, + pub header_group: ContainerStyle, + pub device_code: TextStyle, + pub device_code_cta: ButtonStyle, + pub device_code_left: f32, + pub device_code_left_container: ContainerStyle, + pub device_code_right: f32, + pub device_code_right_container: ContainerStyle, + pub device_code_seperator_height: f32, } #[derive(Deserialize, Default)] diff --git a/crates/theme/src/ui.rs b/crates/theme/src/ui.rs index 392b1134a68d9a810861e6a5b41f71301c449cce..50239bdea57dbefde47c1863fa91190ca8353d6c 100644 --- a/crates/theme/src/ui.rs +++ b/crates/theme/src/ui.rs @@ -4,11 +4,12 @@ use gpui::{ color::Color, elements::{ ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label, - MouseEventHandler, ParentElement, Svg, + MouseEventHandler, ParentElement, Stack, Svg, }, + fonts::TextStyle, geometry::vector::{vec2f, Vector2F}, scene::MouseClick, - Action, Element, ElementBox, EventContext, MouseButton, MouseState, RenderContext, View, + Action, Element, ElementBox, EventContext, MouseButton, RenderContext, View, }; use serde::Deserialize; @@ -213,3 +214,60 @@ where .with_cursor_style(gpui::CursorStyle::PointingHand) .boxed() } + +#[derive(Clone, Deserialize, Default)] +pub struct ModalStyle { + close_icon: Interactive, + container: ContainerStyle, + titlebar: ContainerStyle, + title_text: TextStyle, + dimensions: Dimensions, +} + +impl ModalStyle { + pub fn dimensions(&self) -> Vector2F { + self.dimensions.to_vec() + } +} + +pub fn modal( + title: I, + style: &ModalStyle, + cx: &mut RenderContext, + build_modal: F, +) -> ElementBox +where + V: View, + I: Into>, + F: FnOnce(&mut gpui::RenderContext) -> ElementBox, +{ + Flex::column() + .with_child( + Stack::new() + .with_children([ + Label::new(title, style.title_text.clone()).boxed(), + // FIXME: Get a better tag type + MouseEventHandler::::new(999999, cx, |state, _cx| { + let style = style.close_icon.style_for(state, false); + icon(style).boxed() + }) + .on_click(gpui::MouseButton::Left, move |_, cx| { + let window_id = cx.window_id(); + cx.remove_window(window_id); + }) + .with_cursor_style(gpui::CursorStyle::PointingHand) + .aligned() + .right() + .boxed(), + ]) + .contained() + .with_style(style.titlebar) + .boxed(), + ) + .with_child(build_modal(cx)) + .contained() + .with_style(style.container) + .constrained() + .with_height(style.dimensions().y()) + .boxed() +} diff --git a/styles/src/styleTree/copilot.ts b/styles/src/styleTree/copilot.ts index 4772a2f673ea86bb2cf71620380e64271b28856c..75fc99b591581854206b77f1b2241c96298b8cff 100644 --- a/styles/src/styleTree/copilot.ts +++ b/styles/src/styleTree/copilot.ts @@ -5,41 +5,52 @@ import { background, border, foreground, svg, text } from "./components"; export default function copilot(colorScheme: ColorScheme) { let layer = colorScheme.highest; + let content_width = 304; + + let ctaButton = { // Copied from welcome screen. FIXME: Move this into a ZDS component + background: background(layer), + border: border(layer, "active"), + cornerRadius: 4, + margin: { + top: 4, + bottom: 4, + }, + padding: { + top: 3, + bottom: 3, + left: 7, + right: 7, + }, + ...text(layer, "sans", "default", { size: "sm" }), + hover: { + ...text(layer, "sans", "default", { size: "sm" }), + background: background(layer, "hovered"), + border: border(layer, "active"), + }, + }; + return { - auth: { - popupContainer: { - background: background(colorScheme.highest), - }, - popupDimensions: { - width: 336, - height: 256, - }, - instructionText: text(layer, "sans"), - userCode: - text(layer, "sans", { size: "lg" }), - button: { // Copied from welcome screen. FIXME: Move this into a ZDS component - background: background(layer), + modal: { + titleText: text(layer, "sans", { size: "md" }), + titlebar: { border: border(layer, "active"), - cornerRadius: 4, - margin: { + padding: { top: 4, bottom: 4, + left: 8, + right: 8, }, - padding: { - top: 3, - bottom: 3, - left: 7, - right: 7, - }, - ...text(layer, "sans", "default", { size: "sm" }), - hover: { - ...text(layer, "sans", "default", { size: "sm" }), - background: background(layer, "hovered"), - border: border(layer, "active"), - }, + margin: { + top: 0, + left: 0, + right: 0, + bottom: 8 + } + }, + container: { + background: background(colorScheme.highest), + }, - buttonWidth: 320, - copilotIcon: svg(foreground(layer, "default"), "icons/github-copilot-dummy.svg", 64, 64), closeIcon: { icon: svg(background(layer, "on"), "icons/x_mark_16.svg", 16, 16), container: { @@ -47,13 +58,86 @@ export default function copilot(colorScheme: ColorScheme) { top: 3, bottom: 3, left: 7, - right: 7, + right: 0, } }, hover: { icon: svg(foreground(layer, "on"), "icons/x_mark_16.svg", 16, 16), } }, + dimensions: { + width: 400, + height: 500, + }, + }, + auth: { + content_width, + + headerGroup: { + margin: { + top: 5, + bottom: 5, + left: 0, + right: 0 + } + }, + headerText: text(layer, "sans", { size: "lg" }), + copilotIcon: svg(foreground(layer, "default"), "icons/github-copilot-dummy.svg", 36, 36), + plusIcon: svg(foreground(layer, "default"), "icons/plus_16.svg", 36, 36), + zedIcon: svg(foreground(layer, "default"), "icons/logo_96.svg", 36, 36), + + instructionText: text(layer, "sans"), + + deviceCodeGroup: { + margin: { + top: 5, + bottom: 5, + left: 0, + right: 0 + } + }, + deviceCode: + text(layer, "mono", { size: "md" }), + deviceCodeCta: { + ...ctaButton, + padding: { + top: 0, + bottom: 0, + left: 0, + right: 0, + }, + }, + deviceCodeLeft: content_width * 2 / 3, + deviceCodeLeftContainer: { + padding: { + top: 3, + bottom: 3, + left: 0, + right: 0, + }, + }, + deviceCodeRight: content_width * 1 / 3, + deviceCodeRightContainer: { + border: border(layer, "active", { bottom: false, right: false, top: false, left: true }), + padding: { + top: 3, + bottom: 5, + left: 0, + right: 0, + }, + }, + deviceCodeSeperatorHeight: 0, + + githubGroup: { + margin: { + top: 3, + bottom: 3, + left: 0, + right: 0 + } + }, + + ctaButton } } }