mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-16 15:11:25 +00:00
Don't hang the app when signing in offline
This commit is contained in:
parent
3094cb749e
commit
7899833367
6 changed files with 225 additions and 343 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1949,6 +1949,7 @@ name = "collab_ui2"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"auto_update2",
|
||||
"call2",
|
||||
"channel2",
|
||||
"client2",
|
||||
|
|
|
@ -6,7 +6,7 @@ use db::kvp::KEY_VALUE_STORE;
|
|||
use db::RELEASE_CHANNEL;
|
||||
use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context as _, Model, ModelContext, SemanticVersion, Task,
|
||||
ViewContext, VisualContext,
|
||||
ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
use isahc::AsyncBody;
|
||||
use serde::Deserialize;
|
||||
|
@ -125,7 +125,7 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
|
|||
}
|
||||
}
|
||||
|
||||
pub fn check(_: &Check, cx: &mut ViewContext<Workspace>) {
|
||||
pub fn check(_: &Check, cx: &mut WindowContext) {
|
||||
if let Some(updater) = AutoUpdater::get(cx) {
|
||||
updater.update(cx, |updater, cx| updater.poll(cx));
|
||||
} else {
|
||||
|
|
|
@ -11,8 +11,8 @@ use async_tungstenite::tungstenite::{
|
|||
http::{Request, StatusCode},
|
||||
};
|
||||
use futures::{
|
||||
future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _,
|
||||
TryStreamExt,
|
||||
channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt,
|
||||
TryFutureExt as _, TryStreamExt,
|
||||
};
|
||||
use gpui::{
|
||||
actions, serde_json, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Model,
|
||||
|
@ -1020,91 +1020,116 @@ impl Client {
|
|||
) -> Task<Result<Credentials>> {
|
||||
let http = self.http.clone();
|
||||
cx.spawn(|cx| async move {
|
||||
// Generate a pair of asymmetric encryption keys. The public key will be used by the
|
||||
// zed server to encrypt the user's access token, so that it can'be intercepted by
|
||||
// any other app running on the user's device.
|
||||
let (public_key, private_key) =
|
||||
rpc::auth::keypair().expect("failed to generate keypair for auth");
|
||||
let public_key_string =
|
||||
String::try_from(public_key).expect("failed to serialize public key for auth");
|
||||
let background = cx.background_executor().clone();
|
||||
|
||||
if let Some((login, token)) = IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref()) {
|
||||
return Self::authenticate_as_admin(http, login.clone(), token.clone()).await;
|
||||
}
|
||||
let (open_url_tx, open_url_rx) = oneshot::channel::<String>();
|
||||
cx.update(|cx| {
|
||||
cx.spawn(move |cx| async move {
|
||||
let url = open_url_rx.await?;
|
||||
cx.update(|cx| cx.open_url(&url))
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
.log_err();
|
||||
|
||||
// Start an HTTP server to receive the redirect from Zed's sign-in page.
|
||||
let server = tiny_http::Server::http("127.0.0.1:0").expect("failed to find open port");
|
||||
let port = server.server_addr().port();
|
||||
let credentials = background
|
||||
.clone()
|
||||
.spawn(async move {
|
||||
// Generate a pair of asymmetric encryption keys. The public key will be used by the
|
||||
// zed server to encrypt the user's access token, so that it can'be intercepted by
|
||||
// any other app running on the user's device.
|
||||
let (public_key, private_key) =
|
||||
rpc::auth::keypair().expect("failed to generate keypair for auth");
|
||||
let public_key_string = String::try_from(public_key)
|
||||
.expect("failed to serialize public key for auth");
|
||||
|
||||
// Open the Zed sign-in page in the user's browser, with query parameters that indicate
|
||||
// that the user is signing in from a Zed app running on the same device.
|
||||
let mut url = format!(
|
||||
"{}/native_app_signin?native_app_port={}&native_app_public_key={}",
|
||||
*ZED_SERVER_URL, port, public_key_string
|
||||
);
|
||||
if let Some((login, token)) =
|
||||
IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref())
|
||||
{
|
||||
return Self::authenticate_as_admin(http, login.clone(), token.clone())
|
||||
.await;
|
||||
}
|
||||
|
||||
if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() {
|
||||
log::info!("impersonating user @{}", impersonate_login);
|
||||
write!(&mut url, "&impersonate={}", impersonate_login).unwrap();
|
||||
}
|
||||
// Start an HTTP server to receive the redirect from Zed's sign-in page.
|
||||
let server =
|
||||
tiny_http::Server::http("127.0.0.1:0").expect("failed to find open port");
|
||||
let port = server.server_addr().port();
|
||||
|
||||
cx.update(|cx| cx.open_url(&url))?;
|
||||
// Open the Zed sign-in page in the user's browser, with query parameters that indicate
|
||||
// that the user is signing in from a Zed app running on the same device.
|
||||
let mut url = format!(
|
||||
"{}/native_app_signin?native_app_port={}&native_app_public_key={}",
|
||||
*ZED_SERVER_URL, port, public_key_string
|
||||
);
|
||||
|
||||
// Receive the HTTP request from the user's browser. Retrieve the user id and encrypted
|
||||
// access token from the query params.
|
||||
//
|
||||
// TODO - Avoid ever starting more than one HTTP server. Maybe switch to using a
|
||||
// custom URL scheme instead of this local HTTP server.
|
||||
let (user_id, access_token) = cx
|
||||
.spawn(|_| async move {
|
||||
for _ in 0..100 {
|
||||
if let Some(req) = server.recv_timeout(Duration::from_secs(1))? {
|
||||
let path = req.url();
|
||||
let mut user_id = None;
|
||||
let mut access_token = None;
|
||||
let url = Url::parse(&format!("http://example.com{}", path))
|
||||
.context("failed to parse login notification url")?;
|
||||
for (key, value) in url.query_pairs() {
|
||||
if key == "access_token" {
|
||||
access_token = Some(value.to_string());
|
||||
} else if key == "user_id" {
|
||||
user_id = Some(value.to_string());
|
||||
if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() {
|
||||
log::info!("impersonating user @{}", impersonate_login);
|
||||
write!(&mut url, "&impersonate={}", impersonate_login).unwrap();
|
||||
}
|
||||
|
||||
open_url_tx.send(url).log_err();
|
||||
|
||||
// Receive the HTTP request from the user's browser. Retrieve the user id and encrypted
|
||||
// access token from the query params.
|
||||
//
|
||||
// TODO - Avoid ever starting more than one HTTP server. Maybe switch to using a
|
||||
// custom URL scheme instead of this local HTTP server.
|
||||
let (user_id, access_token) = background
|
||||
.spawn(async move {
|
||||
for _ in 0..100 {
|
||||
if let Some(req) = server.recv_timeout(Duration::from_secs(1))? {
|
||||
let path = req.url();
|
||||
let mut user_id = None;
|
||||
let mut access_token = None;
|
||||
let url = Url::parse(&format!("http://example.com{}", path))
|
||||
.context("failed to parse login notification url")?;
|
||||
for (key, value) in url.query_pairs() {
|
||||
if key == "access_token" {
|
||||
access_token = Some(value.to_string());
|
||||
} else if key == "user_id" {
|
||||
user_id = Some(value.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let post_auth_url =
|
||||
format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL);
|
||||
req.respond(
|
||||
tiny_http::Response::empty(302).with_header(
|
||||
tiny_http::Header::from_bytes(
|
||||
&b"Location"[..],
|
||||
post_auth_url.as_bytes(),
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
)
|
||||
.context("failed to respond to login http request")?;
|
||||
return Ok((
|
||||
user_id
|
||||
.ok_or_else(|| anyhow!("missing user_id parameter"))?,
|
||||
access_token.ok_or_else(|| {
|
||||
anyhow!("missing access_token parameter")
|
||||
})?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let post_auth_url =
|
||||
format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL);
|
||||
req.respond(
|
||||
tiny_http::Response::empty(302).with_header(
|
||||
tiny_http::Header::from_bytes(
|
||||
&b"Location"[..],
|
||||
post_auth_url.as_bytes(),
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
)
|
||||
.context("failed to respond to login http request")?;
|
||||
return Ok((
|
||||
user_id.ok_or_else(|| anyhow!("missing user_id parameter"))?,
|
||||
access_token
|
||||
.ok_or_else(|| anyhow!("missing access_token parameter"))?,
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(anyhow!("didn't receive login redirect"))
|
||||
})
|
||||
.await?;
|
||||
|
||||
Err(anyhow!("didn't receive login redirect"))
|
||||
let access_token = private_key
|
||||
.decrypt_string(&access_token)
|
||||
.context("failed to decrypt access token")?;
|
||||
|
||||
Ok(Credentials {
|
||||
user_id: user_id.parse()?,
|
||||
access_token,
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
|
||||
let access_token = private_key
|
||||
.decrypt_string(&access_token)
|
||||
.context("failed to decrypt access token")?;
|
||||
cx.update(|cx| cx.activate(true))?;
|
||||
|
||||
Ok(Credentials {
|
||||
user_id: user_id.parse()?,
|
||||
access_token,
|
||||
})
|
||||
Ok(credentials)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ test-support = [
|
|||
]
|
||||
|
||||
[dependencies]
|
||||
# auto_update = { path = "../auto_update" }
|
||||
auto_update = { package = "auto_update2", path = "../auto_update2" }
|
||||
db = { package = "db2", path = "../db2" }
|
||||
call = { package = "call2", path = "../call2" }
|
||||
client = { package = "client2", path = "../client2" }
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use crate::face_pile::FacePile;
|
||||
use auto_update::AutoUpdateStatus;
|
||||
use call::{ActiveCall, ParticipantLocation, Room};
|
||||
use client::{proto::PeerId, Client, ParticipantIndex, SignOut, User, UserStore};
|
||||
use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore};
|
||||
use gpui::{
|
||||
actions, canvas, div, overlay, point, px, rems, Action, AppContext, DismissEvent, Div, Element,
|
||||
FocusableView, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path, Render,
|
||||
Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext,
|
||||
actions, canvas, div, overlay, point, px, rems, Action, AnyElement, AppContext, DismissEvent,
|
||||
Div, Element, FocusableView, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path,
|
||||
Render, Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext,
|
||||
WeakView, WindowBounds,
|
||||
};
|
||||
use project::{Project, RepositoryEntry};
|
||||
|
@ -16,7 +17,7 @@ use ui::{
|
|||
IconButton, IconElement, KeyBinding, Tooltip,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use workspace::{notifications::NotifyResultExt, Feedback, Workspace, WORKSPACE_DB};
|
||||
use workspace::{notifications::NotifyResultExt, Workspace, WORKSPACE_DB};
|
||||
|
||||
const MAX_PROJECT_NAME_LENGTH: usize = 40;
|
||||
const MAX_BRANCH_NAME_LENGTH: usize = 40;
|
||||
|
@ -52,7 +53,6 @@ pub struct CollabTitlebarItem {
|
|||
workspace: WeakView<Workspace>,
|
||||
//branch_popover: Option<ViewHandle<BranchList>>,
|
||||
project_popover: Option<recent_projects::RecentProjects>,
|
||||
//user_menu: ViewHandle<ContextMenu>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
|
@ -232,88 +232,17 @@ impl Render for CollabTitlebarItem {
|
|||
}),
|
||||
)
|
||||
})
|
||||
.child(h_stack().px_1p5().map(|this| {
|
||||
if let Some(user) = current_user {
|
||||
// TODO: Finish implementing user menu popover
|
||||
//
|
||||
this.child(
|
||||
popover_menu("user-menu")
|
||||
.menu(|cx| {
|
||||
ContextMenu::build(cx, |menu, _| {
|
||||
menu.action(
|
||||
"Settings",
|
||||
zed_actions::OpenSettings.boxed_clone(),
|
||||
)
|
||||
.action("Theme", theme_selector::Toggle.boxed_clone())
|
||||
.separator()
|
||||
.action(
|
||||
"Share Feedback",
|
||||
feedback::GiveFeedback.boxed_clone(),
|
||||
)
|
||||
.action("Sign Out", client::SignOut.boxed_clone())
|
||||
})
|
||||
})
|
||||
.trigger(
|
||||
ButtonLike::new("user-menu")
|
||||
.child(
|
||||
h_stack()
|
||||
.gap_0p5()
|
||||
.child(Avatar::new(user.avatar_uri.clone()))
|
||||
.child(
|
||||
IconElement::new(Icon::ChevronDown)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text("Toggle User Menu", cx)
|
||||
}),
|
||||
)
|
||||
.anchor(gpui::AnchorCorner::TopRight),
|
||||
)
|
||||
.map(|el| {
|
||||
let status = self.client.status();
|
||||
let status = &*status.borrow();
|
||||
if matches!(status, client::Status::Connected { .. }) {
|
||||
el.child(self.render_user_menu_button(cx))
|
||||
} else {
|
||||
this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| {
|
||||
let client = client.clone();
|
||||
cx.spawn(move |mut cx| async move {
|
||||
client
|
||||
.authenticate_and_connect(true, &cx)
|
||||
.await
|
||||
.notify_async_err(&mut cx);
|
||||
})
|
||||
.detach();
|
||||
}))
|
||||
.child(
|
||||
popover_menu("user-menu")
|
||||
.menu(|cx| {
|
||||
ContextMenu::build(cx, |menu, _| {
|
||||
menu.action(
|
||||
"Settings",
|
||||
zed_actions::OpenSettings.boxed_clone(),
|
||||
)
|
||||
.action("Theme", theme_selector::Toggle.boxed_clone())
|
||||
.separator()
|
||||
.action(
|
||||
"Share Feedback",
|
||||
feedback::GiveFeedback.boxed_clone(),
|
||||
)
|
||||
})
|
||||
})
|
||||
.trigger(
|
||||
ButtonLike::new("user-menu")
|
||||
.child(
|
||||
h_stack().gap_0p5().child(
|
||||
IconElement::new(Icon::ChevronDown)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text("Toggle User Menu", cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
el.children(self.render_connection_status(status, cx))
|
||||
.child(self.render_sign_in_button(cx))
|
||||
.child(self.render_user_menu_button(cx))
|
||||
}
|
||||
})),
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -355,12 +284,6 @@ impl CollabTitlebarItem {
|
|||
project,
|
||||
user_store,
|
||||
client,
|
||||
// user_menu: cx.add_view(|cx| {
|
||||
// let view_id = cx.view_id();
|
||||
// let mut menu = ContextMenu::new(view_id, cx);
|
||||
// menu.set_position_mode(OverlayPositionMode::Local);
|
||||
// menu
|
||||
// }),
|
||||
// branch_popover: None,
|
||||
project_popover: None,
|
||||
_subscriptions: subscriptions,
|
||||
|
@ -535,34 +458,6 @@ impl CollabTitlebarItem {
|
|||
.log_err();
|
||||
}
|
||||
|
||||
// pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
|
||||
// self.user_menu.update(cx, |user_menu, cx| {
|
||||
// let items = if let Some(_) = self.user_store.read(cx).current_user() {
|
||||
// vec![
|
||||
// ContextMenuItem::action("Settings", zed_actions::OpenSettings),
|
||||
// ContextMenuItem::action("Theme", theme_selector::Toggle),
|
||||
// ContextMenuItem::separator(),
|
||||
// ContextMenuItem::action(
|
||||
// "Share Feedback",
|
||||
// feedback::feedback_editor::GiveFeedback,
|
||||
// ),
|
||||
// ContextMenuItem::action("Sign Out", SignOut),
|
||||
// ]
|
||||
// } else {
|
||||
// vec![
|
||||
// ContextMenuItem::action("Settings", zed_actions::OpenSettings),
|
||||
// ContextMenuItem::action("Theme", theme_selector::Toggle),
|
||||
// ContextMenuItem::separator(),
|
||||
// ContextMenuItem::action(
|
||||
// "Share Feedback",
|
||||
// feedback::feedback_editor::GiveFeedback,
|
||||
// ),
|
||||
// ]
|
||||
// };
|
||||
// user_menu.toggle(Default::default(), AnchorCorner::TopRight, items, cx);
|
||||
// });
|
||||
// }
|
||||
|
||||
// fn render_branches_popover_host<'a>(
|
||||
// &'a self,
|
||||
// _theme: &'a theme::Titlebar,
|
||||
|
@ -696,154 +591,113 @@ impl CollabTitlebarItem {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
// fn render_user_menu_button(
|
||||
// &self,
|
||||
// theme: &Theme,
|
||||
// avatar: Option<Arc<ImageData>>,
|
||||
// cx: &mut ViewContext<Self>,
|
||||
// ) -> AnyElement<Self> {
|
||||
// let tooltip = theme.tooltip.clone();
|
||||
// let user_menu_button_style = if avatar.is_some() {
|
||||
// &theme.titlebar.user_menu.user_menu_button_online
|
||||
// } else {
|
||||
// &theme.titlebar.user_menu.user_menu_button_offline
|
||||
// };
|
||||
fn render_connection_status(
|
||||
&self,
|
||||
status: &client::Status,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<AnyElement> {
|
||||
match status {
|
||||
client::Status::ConnectionError
|
||||
| client::Status::ConnectionLost
|
||||
| client::Status::Reauthenticating { .. }
|
||||
| client::Status::Reconnecting { .. }
|
||||
| client::Status::ReconnectionError { .. } => Some(
|
||||
div()
|
||||
.id("disconnected")
|
||||
.bg(gpui::red()) // todo!() @nate
|
||||
.child(IconElement::new(Icon::Disconnected))
|
||||
.tooltip(|cx| Tooltip::text("Disconnected", cx))
|
||||
.into_any_element(),
|
||||
),
|
||||
client::Status::UpgradeRequired => {
|
||||
let auto_updater = auto_update::AutoUpdater::get(cx);
|
||||
let label = match auto_updater.map(|auto_update| auto_update.read(cx).status()) {
|
||||
Some(AutoUpdateStatus::Updated) => "Please restart Zed to Collaborate",
|
||||
Some(AutoUpdateStatus::Installing)
|
||||
| Some(AutoUpdateStatus::Downloading)
|
||||
| Some(AutoUpdateStatus::Checking) => "Updating...",
|
||||
Some(AutoUpdateStatus::Idle) | Some(AutoUpdateStatus::Errored) | None => {
|
||||
"Please update Zed to Collaborate"
|
||||
}
|
||||
};
|
||||
|
||||
// let avatar_style = &user_menu_button_style.avatar;
|
||||
// Stack::new()
|
||||
// .with_child(
|
||||
// MouseEventHandler::new::<ToggleUserMenu, _>(0, cx, |state, _| {
|
||||
// let style = user_menu_button_style
|
||||
// .user_menu
|
||||
// .inactive_state()
|
||||
// .style_for(state);
|
||||
Some(
|
||||
div()
|
||||
.bg(gpui::red()) // todo!() @nate
|
||||
.child(Button::new("connection-status", label).on_click(|_, cx| {
|
||||
if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) {
|
||||
if auto_updater.read(cx).status() == AutoUpdateStatus::Updated {
|
||||
workspace::restart(&Default::default(), cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto_update::check(&Default::default(), cx);
|
||||
}))
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// let mut dropdown = Flex::row().align_children_center();
|
||||
pub fn render_sign_in_button(&mut self, _: &mut ViewContext<Self>) -> Button {
|
||||
let client = self.client.clone();
|
||||
Button::new("sign_in", "Sign in").on_click(move |_, cx| {
|
||||
let client = client.clone();
|
||||
cx.spawn(move |mut cx| async move {
|
||||
client
|
||||
.authenticate_and_connect(true, &cx)
|
||||
.await
|
||||
.notify_async_err(&mut cx);
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
}
|
||||
|
||||
// if let Some(avatar_img) = avatar {
|
||||
// dropdown = dropdown.with_child(Self::render_face(
|
||||
// avatar_img,
|
||||
// *avatar_style,
|
||||
// Color::transparent_black(),
|
||||
// None,
|
||||
// ));
|
||||
// };
|
||||
|
||||
// dropdown
|
||||
// .with_child(
|
||||
// Svg::new("icons/caret_down.svg")
|
||||
// .with_color(user_menu_button_style.icon.color)
|
||||
// .constrained()
|
||||
// .with_width(user_menu_button_style.icon.width)
|
||||
// .contained()
|
||||
// .into_any(),
|
||||
// )
|
||||
// .aligned()
|
||||
// .constrained()
|
||||
// .with_height(style.width)
|
||||
// .contained()
|
||||
// .with_style(style.container)
|
||||
// .into_any()
|
||||
// })
|
||||
// .with_cursor_style(CursorStyle::PointingHand)
|
||||
// .on_down(MouseButton::Left, move |_, this, cx| {
|
||||
// this.user_menu.update(cx, |menu, _| menu.delay_cancel());
|
||||
// })
|
||||
// .on_click(MouseButton::Left, move |_, this, cx| {
|
||||
// this.toggle_user_menu(&Default::default(), cx)
|
||||
// })
|
||||
// .with_tooltip::<ToggleUserMenu>(
|
||||
// 0,
|
||||
// "Toggle User Menu".to_owned(),
|
||||
// Some(Box::new(ToggleUserMenu)),
|
||||
// tooltip,
|
||||
// cx,
|
||||
// )
|
||||
// .contained(),
|
||||
// )
|
||||
// .with_child(
|
||||
// ChildView::new(&self.user_menu, cx)
|
||||
// .aligned()
|
||||
// .bottom()
|
||||
// .right(),
|
||||
// )
|
||||
// .into_any()
|
||||
// }
|
||||
|
||||
// fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
// let titlebar = &theme.titlebar;
|
||||
// MouseEventHandler::new::<SignIn, _>(0, cx, |state, _| {
|
||||
// let style = titlebar.sign_in_button.inactive_state().style_for(state);
|
||||
// Label::new("Sign In", style.text.clone())
|
||||
// .contained()
|
||||
// .with_style(style.container)
|
||||
// })
|
||||
// .with_cursor_style(CursorStyle::PointingHand)
|
||||
// .on_click(MouseButton::Left, move |_, this, cx| {
|
||||
// let client = this.client.clone();
|
||||
// cx.app_context()
|
||||
// .spawn(|cx| async move { client.authenticate_and_connect(true, &cx).await })
|
||||
// .detach_and_log_err(cx);
|
||||
// })
|
||||
// .into_any()
|
||||
// }
|
||||
|
||||
// fn render_connection_status(
|
||||
// &self,
|
||||
// status: &client::Status,
|
||||
// cx: &mut ViewContext<Self>,
|
||||
// ) -> Option<AnyElement<Self>> {
|
||||
// enum ConnectionStatusButton {}
|
||||
|
||||
// let theme = &theme::current(cx).clone();
|
||||
// match status {
|
||||
// client::Status::ConnectionError
|
||||
// | client::Status::ConnectionLost
|
||||
// | client::Status::Reauthenticating { .. }
|
||||
// | client::Status::Reconnecting { .. }
|
||||
// | client::Status::ReconnectionError { .. } => Some(
|
||||
// Svg::new("icons/disconnected.svg")
|
||||
// .with_color(theme.titlebar.offline_icon.color)
|
||||
// .constrained()
|
||||
// .with_width(theme.titlebar.offline_icon.width)
|
||||
// .aligned()
|
||||
// .contained()
|
||||
// .with_style(theme.titlebar.offline_icon.container)
|
||||
// .into_any(),
|
||||
// ),
|
||||
// client::Status::UpgradeRequired => {
|
||||
// let auto_updater = auto_update::AutoUpdater::get(cx);
|
||||
// let label = match auto_updater.map(|auto_update| auto_update.read(cx).status()) {
|
||||
// Some(AutoUpdateStatus::Updated) => "Please restart Zed to Collaborate",
|
||||
// Some(AutoUpdateStatus::Installing)
|
||||
// | Some(AutoUpdateStatus::Downloading)
|
||||
// | Some(AutoUpdateStatus::Checking) => "Updating...",
|
||||
// Some(AutoUpdateStatus::Idle) | Some(AutoUpdateStatus::Errored) | None => {
|
||||
// "Please update Zed to Collaborate"
|
||||
// }
|
||||
// };
|
||||
|
||||
// Some(
|
||||
// MouseEventHandler::new::<ConnectionStatusButton, _>(0, cx, |_, _| {
|
||||
// Label::new(label, theme.titlebar.outdated_warning.text.clone())
|
||||
// .contained()
|
||||
// .with_style(theme.titlebar.outdated_warning.container)
|
||||
// .aligned()
|
||||
// })
|
||||
// .with_cursor_style(CursorStyle::PointingHand)
|
||||
// .on_click(MouseButton::Left, |_, _, cx| {
|
||||
// if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) {
|
||||
// if auto_updater.read(cx).status() == AutoUpdateStatus::Updated {
|
||||
// workspace::restart(&Default::default(), cx);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// auto_update::check(&Default::default(), cx);
|
||||
// })
|
||||
// .into_any(),
|
||||
// )
|
||||
// }
|
||||
// _ => None,
|
||||
// }
|
||||
// }
|
||||
pub fn render_user_menu_button(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
|
||||
if let Some(user) = self.user_store.read(cx).current_user() {
|
||||
popover_menu("user-menu")
|
||||
.menu(|cx| {
|
||||
ContextMenu::build(cx, |menu, _| {
|
||||
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
||||
.action("Theme", theme_selector::Toggle.boxed_clone())
|
||||
.separator()
|
||||
.action("Share Feedback", feedback::GiveFeedback.boxed_clone())
|
||||
.action("Sign Out", client::SignOut.boxed_clone())
|
||||
})
|
||||
})
|
||||
.trigger(
|
||||
ButtonLike::new("user-menu")
|
||||
.child(
|
||||
h_stack()
|
||||
.gap_0p5()
|
||||
.child(Avatar::new(user.avatar_uri.clone()))
|
||||
.child(IconElement::new(Icon::ChevronDown).color(Color::Muted)),
|
||||
)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
|
||||
)
|
||||
.anchor(gpui::AnchorCorner::TopRight)
|
||||
} else {
|
||||
popover_menu("user-menu")
|
||||
.menu(|cx| {
|
||||
ContextMenu::build(cx, |menu, _| {
|
||||
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
||||
.action("Theme", theme_selector::Toggle.boxed_clone())
|
||||
.separator()
|
||||
.action("Share Feedback", feedback::GiveFeedback.boxed_clone())
|
||||
})
|
||||
})
|
||||
.trigger(
|
||||
ButtonLike::new("user-menu")
|
||||
.child(
|
||||
h_stack()
|
||||
.gap_0p5()
|
||||
.child(IconElement::new(Icon::ChevronDown).color(Color::Muted)),
|
||||
)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ pub enum Icon {
|
|||
CopilotError,
|
||||
CopilotDisabled,
|
||||
Dash,
|
||||
Disconnected,
|
||||
Envelope,
|
||||
ExternalLink,
|
||||
ExclamationTriangle,
|
||||
|
@ -129,6 +130,7 @@ impl Icon {
|
|||
Icon::CopilotError => "icons/copilot_error.svg",
|
||||
Icon::CopilotDisabled => "icons/copilot_disabled.svg",
|
||||
Icon::Dash => "icons/dash.svg",
|
||||
Icon::Disconnected => "icons/disconnected.svg",
|
||||
Icon::Envelope => "icons/feedback.svg",
|
||||
Icon::ExclamationTriangle => "icons/warning.svg",
|
||||
Icon::ExternalLink => "icons/external_link.svg",
|
||||
|
|
Loading…
Reference in a new issue