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"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"auto_update2",
|
||||||
"call2",
|
"call2",
|
||||||
"channel2",
|
"channel2",
|
||||||
"client2",
|
"client2",
|
||||||
|
|
|
@ -6,7 +6,7 @@ use db::kvp::KEY_VALUE_STORE;
|
||||||
use db::RELEASE_CHANNEL;
|
use db::RELEASE_CHANNEL;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, AppContext, AsyncAppContext, Context as _, Model, ModelContext, SemanticVersion, Task,
|
actions, AppContext, AsyncAppContext, Context as _, Model, ModelContext, SemanticVersion, Task,
|
||||||
ViewContext, VisualContext,
|
ViewContext, VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
use isahc::AsyncBody;
|
use isahc::AsyncBody;
|
||||||
use serde::Deserialize;
|
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) {
|
if let Some(updater) = AutoUpdater::get(cx) {
|
||||||
updater.update(cx, |updater, cx| updater.poll(cx));
|
updater.update(cx, |updater, cx| updater.poll(cx));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -11,8 +11,8 @@ use async_tungstenite::tungstenite::{
|
||||||
http::{Request, StatusCode},
|
http::{Request, StatusCode},
|
||||||
};
|
};
|
||||||
use futures::{
|
use futures::{
|
||||||
future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _,
|
channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt,
|
||||||
TryStreamExt,
|
TryFutureExt as _, TryStreamExt,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, serde_json, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Model,
|
actions, serde_json, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Model,
|
||||||
|
@ -1020,91 +1020,116 @@ impl Client {
|
||||||
) -> Task<Result<Credentials>> {
|
) -> Task<Result<Credentials>> {
|
||||||
let http = self.http.clone();
|
let http = self.http.clone();
|
||||||
cx.spawn(|cx| async move {
|
cx.spawn(|cx| async move {
|
||||||
// Generate a pair of asymmetric encryption keys. The public key will be used by the
|
let background = cx.background_executor().clone();
|
||||||
// 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");
|
|
||||||
|
|
||||||
if let Some((login, token)) = IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref()) {
|
let (open_url_tx, open_url_rx) = oneshot::channel::<String>();
|
||||||
return Self::authenticate_as_admin(http, login.clone(), token.clone()).await;
|
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 credentials = background
|
||||||
let server = tiny_http::Server::http("127.0.0.1:0").expect("failed to find open port");
|
.clone()
|
||||||
let port = server.server_addr().port();
|
.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
|
if let Some((login, token)) =
|
||||||
// that the user is signing in from a Zed app running on the same device.
|
IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref())
|
||||||
let mut url = format!(
|
{
|
||||||
"{}/native_app_signin?native_app_port={}&native_app_public_key={}",
|
return Self::authenticate_as_admin(http, login.clone(), token.clone())
|
||||||
*ZED_SERVER_URL, port, public_key_string
|
.await;
|
||||||
);
|
}
|
||||||
|
|
||||||
if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() {
|
// Start an HTTP server to receive the redirect from Zed's sign-in page.
|
||||||
log::info!("impersonating user @{}", impersonate_login);
|
let server =
|
||||||
write!(&mut url, "&impersonate={}", impersonate_login).unwrap();
|
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
|
if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() {
|
||||||
// access token from the query params.
|
log::info!("impersonating user @{}", impersonate_login);
|
||||||
//
|
write!(&mut url, "&impersonate={}", impersonate_login).unwrap();
|
||||||
// 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
|
open_url_tx.send(url).log_err();
|
||||||
.spawn(|_| async move {
|
|
||||||
for _ in 0..100 {
|
// Receive the HTTP request from the user's browser. Retrieve the user id and encrypted
|
||||||
if let Some(req) = server.recv_timeout(Duration::from_secs(1))? {
|
// access token from the query params.
|
||||||
let path = req.url();
|
//
|
||||||
let mut user_id = None;
|
// TODO - Avoid ever starting more than one HTTP server. Maybe switch to using a
|
||||||
let mut access_token = None;
|
// custom URL scheme instead of this local HTTP server.
|
||||||
let url = Url::parse(&format!("http://example.com{}", path))
|
let (user_id, access_token) = background
|
||||||
.context("failed to parse login notification url")?;
|
.spawn(async move {
|
||||||
for (key, value) in url.query_pairs() {
|
for _ in 0..100 {
|
||||||
if key == "access_token" {
|
if let Some(req) = server.recv_timeout(Duration::from_secs(1))? {
|
||||||
access_token = Some(value.to_string());
|
let path = req.url();
|
||||||
} else if key == "user_id" {
|
let mut user_id = None;
|
||||||
user_id = Some(value.to_string());
|
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 =
|
Err(anyhow!("didn't receive login redirect"))
|
||||||
format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL);
|
})
|
||||||
req.respond(
|
.await?;
|
||||||
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"))
|
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?;
|
.await?;
|
||||||
|
|
||||||
let access_token = private_key
|
|
||||||
.decrypt_string(&access_token)
|
|
||||||
.context("failed to decrypt access token")?;
|
|
||||||
cx.update(|cx| cx.activate(true))?;
|
cx.update(|cx| cx.activate(true))?;
|
||||||
|
Ok(credentials)
|
||||||
Ok(Credentials {
|
|
||||||
user_id: user_id.parse()?,
|
|
||||||
access_token,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ test-support = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# auto_update = { path = "../auto_update" }
|
auto_update = { package = "auto_update2", path = "../auto_update2" }
|
||||||
db = { package = "db2", path = "../db2" }
|
db = { package = "db2", path = "../db2" }
|
||||||
call = { package = "call2", path = "../call2" }
|
call = { package = "call2", path = "../call2" }
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { package = "client2", path = "../client2" }
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::face_pile::FacePile;
|
use crate::face_pile::FacePile;
|
||||||
|
use auto_update::AutoUpdateStatus;
|
||||||
use call::{ActiveCall, ParticipantLocation, Room};
|
use call::{ActiveCall, ParticipantLocation, Room};
|
||||||
use client::{proto::PeerId, Client, ParticipantIndex, SignOut, User, UserStore};
|
use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, div, overlay, point, px, rems, Action, AppContext, DismissEvent, Div, Element,
|
actions, canvas, div, overlay, point, px, rems, Action, AnyElement, AppContext, DismissEvent,
|
||||||
FocusableView, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path, Render,
|
Div, Element, FocusableView, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path,
|
||||||
Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext,
|
Render, Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext,
|
||||||
WeakView, WindowBounds,
|
WeakView, WindowBounds,
|
||||||
};
|
};
|
||||||
use project::{Project, RepositoryEntry};
|
use project::{Project, RepositoryEntry};
|
||||||
|
@ -16,7 +17,7 @@ use ui::{
|
||||||
IconButton, IconElement, KeyBinding, Tooltip,
|
IconButton, IconElement, KeyBinding, Tooltip,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
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_PROJECT_NAME_LENGTH: usize = 40;
|
||||||
const MAX_BRANCH_NAME_LENGTH: usize = 40;
|
const MAX_BRANCH_NAME_LENGTH: usize = 40;
|
||||||
|
@ -52,7 +53,6 @@ pub struct CollabTitlebarItem {
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
//branch_popover: Option<ViewHandle<BranchList>>,
|
//branch_popover: Option<ViewHandle<BranchList>>,
|
||||||
project_popover: Option<recent_projects::RecentProjects>,
|
project_popover: Option<recent_projects::RecentProjects>,
|
||||||
//user_menu: ViewHandle<ContextMenu>,
|
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,88 +232,17 @@ impl Render for CollabTitlebarItem {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.child(h_stack().px_1p5().map(|this| {
|
.map(|el| {
|
||||||
if let Some(user) = current_user {
|
let status = self.client.status();
|
||||||
// TODO: Finish implementing user menu popover
|
let status = &*status.borrow();
|
||||||
//
|
if matches!(status, client::Status::Connected { .. }) {
|
||||||
this.child(
|
el.child(self.render_user_menu_button(cx))
|
||||||
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 {
|
} else {
|
||||||
this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| {
|
el.children(self.render_connection_status(status, cx))
|
||||||
let client = client.clone();
|
.child(self.render_sign_in_button(cx))
|
||||||
cx.spawn(move |mut cx| async move {
|
.child(self.render_user_menu_button(cx))
|
||||||
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)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -355,12 +284,6 @@ impl CollabTitlebarItem {
|
||||||
project,
|
project,
|
||||||
user_store,
|
user_store,
|
||||||
client,
|
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,
|
// branch_popover: None,
|
||||||
project_popover: None,
|
project_popover: None,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
|
@ -535,34 +458,6 @@ impl CollabTitlebarItem {
|
||||||
.log_err();
|
.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>(
|
// fn render_branches_popover_host<'a>(
|
||||||
// &'a self,
|
// &'a self,
|
||||||
// _theme: &'a theme::Titlebar,
|
// _theme: &'a theme::Titlebar,
|
||||||
|
@ -696,154 +591,113 @@ impl CollabTitlebarItem {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn render_user_menu_button(
|
fn render_connection_status(
|
||||||
// &self,
|
&self,
|
||||||
// theme: &Theme,
|
status: &client::Status,
|
||||||
// avatar: Option<Arc<ImageData>>,
|
cx: &mut ViewContext<Self>,
|
||||||
// cx: &mut ViewContext<Self>,
|
) -> Option<AnyElement> {
|
||||||
// ) -> AnyElement<Self> {
|
match status {
|
||||||
// let tooltip = theme.tooltip.clone();
|
client::Status::ConnectionError
|
||||||
// let user_menu_button_style = if avatar.is_some() {
|
| client::Status::ConnectionLost
|
||||||
// &theme.titlebar.user_menu.user_menu_button_online
|
| client::Status::Reauthenticating { .. }
|
||||||
// } else {
|
| client::Status::Reconnecting { .. }
|
||||||
// &theme.titlebar.user_menu.user_menu_button_offline
|
| 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;
|
Some(
|
||||||
// Stack::new()
|
div()
|
||||||
// .with_child(
|
.bg(gpui::red()) // todo!() @nate
|
||||||
// MouseEventHandler::new::<ToggleUserMenu, _>(0, cx, |state, _| {
|
.child(Button::new("connection-status", label).on_click(|_, cx| {
|
||||||
// let style = user_menu_button_style
|
if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) {
|
||||||
// .user_menu
|
if auto_updater.read(cx).status() == AutoUpdateStatus::Updated {
|
||||||
// .inactive_state()
|
workspace::restart(&Default::default(), cx);
|
||||||
// .style_for(state);
|
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 {
|
pub fn render_user_menu_button(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
|
||||||
// dropdown = dropdown.with_child(Self::render_face(
|
if let Some(user) = self.user_store.read(cx).current_user() {
|
||||||
// avatar_img,
|
popover_menu("user-menu")
|
||||||
// *avatar_style,
|
.menu(|cx| {
|
||||||
// Color::transparent_black(),
|
ContextMenu::build(cx, |menu, _| {
|
||||||
// None,
|
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
||||||
// ));
|
.action("Theme", theme_selector::Toggle.boxed_clone())
|
||||||
// };
|
.separator()
|
||||||
|
.action("Share Feedback", feedback::GiveFeedback.boxed_clone())
|
||||||
// dropdown
|
.action("Sign Out", client::SignOut.boxed_clone())
|
||||||
// .with_child(
|
})
|
||||||
// Svg::new("icons/caret_down.svg")
|
})
|
||||||
// .with_color(user_menu_button_style.icon.color)
|
.trigger(
|
||||||
// .constrained()
|
ButtonLike::new("user-menu")
|
||||||
// .with_width(user_menu_button_style.icon.width)
|
.child(
|
||||||
// .contained()
|
h_stack()
|
||||||
// .into_any(),
|
.gap_0p5()
|
||||||
// )
|
.child(Avatar::new(user.avatar_uri.clone()))
|
||||||
// .aligned()
|
.child(IconElement::new(Icon::ChevronDown).color(Color::Muted)),
|
||||||
// .constrained()
|
)
|
||||||
// .with_height(style.width)
|
.style(ButtonStyle::Subtle)
|
||||||
// .contained()
|
.tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
|
||||||
// .with_style(style.container)
|
)
|
||||||
// .into_any()
|
.anchor(gpui::AnchorCorner::TopRight)
|
||||||
// })
|
} else {
|
||||||
// .with_cursor_style(CursorStyle::PointingHand)
|
popover_menu("user-menu")
|
||||||
// .on_down(MouseButton::Left, move |_, this, cx| {
|
.menu(|cx| {
|
||||||
// this.user_menu.update(cx, |menu, _| menu.delay_cancel());
|
ContextMenu::build(cx, |menu, _| {
|
||||||
// })
|
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
||||||
// .on_click(MouseButton::Left, move |_, this, cx| {
|
.action("Theme", theme_selector::Toggle.boxed_clone())
|
||||||
// this.toggle_user_menu(&Default::default(), cx)
|
.separator()
|
||||||
// })
|
.action("Share Feedback", feedback::GiveFeedback.boxed_clone())
|
||||||
// .with_tooltip::<ToggleUserMenu>(
|
})
|
||||||
// 0,
|
})
|
||||||
// "Toggle User Menu".to_owned(),
|
.trigger(
|
||||||
// Some(Box::new(ToggleUserMenu)),
|
ButtonLike::new("user-menu")
|
||||||
// tooltip,
|
.child(
|
||||||
// cx,
|
h_stack()
|
||||||
// )
|
.gap_0p5()
|
||||||
// .contained(),
|
.child(IconElement::new(Icon::ChevronDown).color(Color::Muted)),
|
||||||
// )
|
)
|
||||||
// .with_child(
|
.style(ButtonStyle::Subtle)
|
||||||
// ChildView::new(&self.user_menu, cx)
|
.tooltip(move |cx| Tooltip::text("Toggle 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,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ pub enum Icon {
|
||||||
CopilotError,
|
CopilotError,
|
||||||
CopilotDisabled,
|
CopilotDisabled,
|
||||||
Dash,
|
Dash,
|
||||||
|
Disconnected,
|
||||||
Envelope,
|
Envelope,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
ExclamationTriangle,
|
ExclamationTriangle,
|
||||||
|
@ -129,6 +130,7 @@ impl Icon {
|
||||||
Icon::CopilotError => "icons/copilot_error.svg",
|
Icon::CopilotError => "icons/copilot_error.svg",
|
||||||
Icon::CopilotDisabled => "icons/copilot_disabled.svg",
|
Icon::CopilotDisabled => "icons/copilot_disabled.svg",
|
||||||
Icon::Dash => "icons/dash.svg",
|
Icon::Dash => "icons/dash.svg",
|
||||||
|
Icon::Disconnected => "icons/disconnected.svg",
|
||||||
Icon::Envelope => "icons/feedback.svg",
|
Icon::Envelope => "icons/feedback.svg",
|
||||||
Icon::ExclamationTriangle => "icons/warning.svg",
|
Icon::ExclamationTriangle => "icons/warning.svg",
|
||||||
Icon::ExternalLink => "icons/external_link.svg",
|
Icon::ExternalLink => "icons/external_link.svg",
|
||||||
|
|
Loading…
Reference in a new issue