WIP
This commit is contained in:
parent
428c491542
commit
f0019e3725
8 changed files with 135 additions and 31 deletions
|
@ -2282,6 +2282,16 @@ impl<'a, T: View> ViewContext<'a, T> {
|
||||||
let handle = self.handle();
|
let handle = self.handle();
|
||||||
self.app.spawn(|cx| f(handle, cx))
|
self.app.spawn(|cx| f(handle, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn spawn_weak<F, Fut, S>(&self, f: F) -> Task<S>
|
||||||
|
where
|
||||||
|
F: FnOnce(WeakViewHandle<T>, AsyncAppContext) -> Fut,
|
||||||
|
Fut: 'static + Future<Output = S>,
|
||||||
|
S: 'static,
|
||||||
|
{
|
||||||
|
let handle = self.handle().downgrade();
|
||||||
|
self.app.spawn(|cx| f(handle, cx))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RenderContext<'a, T: View> {
|
pub struct RenderContext<'a, T: View> {
|
||||||
|
|
|
@ -1512,7 +1512,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let user_store_a = Arc::new(UserStore::new(client_a.clone()));
|
let user_store_a = UserStore::new(client_a.clone(), cx_a.background().as_ref());
|
||||||
let channels_a = cx_a.add_model(|cx| ChannelList::new(user_store_a, client_a, cx));
|
let channels_a = cx_a.add_model(|cx| ChannelList::new(user_store_a, client_a, cx));
|
||||||
channels_a
|
channels_a
|
||||||
.condition(&mut cx_a, |list, _| list.available_channels().is_some())
|
.condition(&mut cx_a, |list, _| list.available_channels().is_some())
|
||||||
|
@ -1537,7 +1537,7 @@ mod tests {
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let user_store_b = Arc::new(UserStore::new(client_b.clone()));
|
let user_store_b = UserStore::new(client_b.clone(), cx_b.background().as_ref());
|
||||||
let channels_b = cx_b.add_model(|cx| ChannelList::new(user_store_b, client_b, cx));
|
let channels_b = cx_b.add_model(|cx| ChannelList::new(user_store_b, client_b, cx));
|
||||||
channels_b
|
channels_b
|
||||||
.condition(&mut cx_b, |list, _| list.available_channels().is_some())
|
.condition(&mut cx_b, |list, _| list.available_channels().is_some())
|
||||||
|
@ -1637,7 +1637,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let user_store_a = Arc::new(UserStore::new(client_a.clone()));
|
let user_store_a = UserStore::new(client_a.clone(), cx_a.background().as_ref());
|
||||||
let channels_a = cx_a.add_model(|cx| ChannelList::new(user_store_a, client_a, cx));
|
let channels_a = cx_a.add_model(|cx| ChannelList::new(user_store_a, client_a, cx));
|
||||||
channels_a
|
channels_a
|
||||||
.condition(&mut cx_a, |list, _| list.available_channels().is_some())
|
.condition(&mut cx_a, |list, _| list.available_channels().is_some())
|
||||||
|
@ -1713,7 +1713,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let user_store_a = Arc::new(UserStore::new(client_a.clone()));
|
let user_store_a = UserStore::new(client_a.clone(), cx_a.background().as_ref());
|
||||||
let channels_a = cx_a.add_model(|cx| ChannelList::new(user_store_a, client_a, cx));
|
let channels_a = cx_a.add_model(|cx| ChannelList::new(user_store_a, client_a, cx));
|
||||||
channels_a
|
channels_a
|
||||||
.condition(&mut cx_a, |list, _| list.available_channels().is_some())
|
.condition(&mut cx_a, |list, _| list.available_channels().is_some())
|
||||||
|
@ -1739,7 +1739,7 @@ mod tests {
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let user_store_b = Arc::new(UserStore::new(client_b.clone()));
|
let user_store_b = UserStore::new(client_b.clone(), cx_b.background().as_ref());
|
||||||
let channels_b = cx_b.add_model(|cx| ChannelList::new(user_store_b, client_b, cx));
|
let channels_b = cx_b.add_model(|cx| ChannelList::new(user_store_b, client_b, cx));
|
||||||
channels_b
|
channels_b
|
||||||
.condition(&mut cx_b, |list, _| list.available_channels().is_some())
|
.condition(&mut cx_b, |list, _| list.available_channels().is_some())
|
||||||
|
|
|
@ -118,7 +118,7 @@ impl ChannelList {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
rpc::Status::Disconnected { .. } => {
|
rpc::Status::SignedOut { .. } => {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.available_channels = None;
|
this.available_channels = None;
|
||||||
this.channels.clear();
|
this.channels.clear();
|
||||||
|
@ -503,7 +503,7 @@ mod tests {
|
||||||
let user_id = 5;
|
let user_id = 5;
|
||||||
let mut client = Client::new();
|
let mut client = Client::new();
|
||||||
let server = FakeServer::for_client(user_id, &mut client, &cx).await;
|
let server = FakeServer::for_client(user_id, &mut client, &cx).await;
|
||||||
let user_store = Arc::new(UserStore::new(client.clone()));
|
let user_store = UserStore::new(client.clone(), cx.background().as_ref());
|
||||||
|
|
||||||
let channel_list = cx.add_model(|cx| ChannelList::new(user_store, client.clone(), cx));
|
let channel_list = cx.add_model(|cx| ChannelList::new(user_store, client.clone(), cx));
|
||||||
channel_list.read_with(&cx, |list, _| assert_eq!(list.available_channels(), None));
|
channel_list.read_with(&cx, |list, _| assert_eq!(list.available_channels(), None));
|
||||||
|
|
|
@ -37,7 +37,7 @@ fn main() {
|
||||||
|
|
||||||
app.run(move |cx| {
|
app.run(move |cx| {
|
||||||
let rpc = rpc::Client::new();
|
let rpc = rpc::Client::new();
|
||||||
let user_store = Arc::new(UserStore::new(rpc.clone()));
|
let user_store = UserStore::new(rpc.clone(), cx.background());
|
||||||
let app_state = Arc::new(AppState {
|
let app_state = Arc::new(AppState {
|
||||||
languages: languages.clone(),
|
languages: languages.clone(),
|
||||||
settings_tx: Arc::new(Mutex::new(settings_tx)),
|
settings_tx: Arc::new(Mutex::new(settings_tx)),
|
||||||
|
|
|
@ -39,7 +39,7 @@ pub struct Client {
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
Disconnected,
|
SignedOut,
|
||||||
Authenticating,
|
Authenticating,
|
||||||
Connecting {
|
Connecting {
|
||||||
user_id: u64,
|
user_id: u64,
|
||||||
|
@ -73,7 +73,7 @@ struct ClientState {
|
||||||
impl Default for ClientState {
|
impl Default for ClientState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
status: watch::channel_with(Status::Disconnected),
|
status: watch::channel_with(Status::SignedOut),
|
||||||
entity_id_extractors: Default::default(),
|
entity_id_extractors: Default::default(),
|
||||||
model_handlers: Default::default(),
|
model_handlers: Default::default(),
|
||||||
_maintain_connection: None,
|
_maintain_connection: None,
|
||||||
|
@ -167,7 +167,7 @@ impl Client {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Status::Disconnected => {
|
Status::SignedOut => {
|
||||||
state._maintain_connection.take();
|
state._maintain_connection.take();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -232,7 +232,7 @@ impl Client {
|
||||||
cx: &AsyncAppContext,
|
cx: &AsyncAppContext,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let was_disconnected = match *self.status().borrow() {
|
let was_disconnected = match *self.status().borrow() {
|
||||||
Status::Disconnected => true,
|
Status::SignedOut => true,
|
||||||
Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
|
Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -324,7 +324,7 @@ impl Client {
|
||||||
cx.foreground()
|
cx.foreground()
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
match handle_io.await {
|
match handle_io.await {
|
||||||
Ok(()) => this.set_status(Status::Disconnected, &cx),
|
Ok(()) => this.set_status(Status::SignedOut, &cx),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!("connection error: {:?}", err);
|
log::error!("connection error: {:?}", err);
|
||||||
this.set_status(Status::ConnectionLost, &cx);
|
this.set_status(Status::ConnectionLost, &cx);
|
||||||
|
@ -470,7 +470,7 @@ impl Client {
|
||||||
pub async fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) -> Result<()> {
|
pub async fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) -> Result<()> {
|
||||||
let conn_id = self.connection_id()?;
|
let conn_id = self.connection_id()?;
|
||||||
self.peer.disconnect(conn_id).await;
|
self.peer.disconnect(conn_id).await;
|
||||||
self.set_status(Status::Disconnected, cx);
|
self.set_status(Status::SignedOut, cx);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -164,7 +164,7 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
|
||||||
let languages = Arc::new(LanguageRegistry::new());
|
let languages = Arc::new(LanguageRegistry::new());
|
||||||
let themes = ThemeRegistry::new(Assets, cx.font_cache().clone());
|
let themes = ThemeRegistry::new(Assets, cx.font_cache().clone());
|
||||||
let rpc = rpc::Client::new();
|
let rpc = rpc::Client::new();
|
||||||
let user_store = Arc::new(UserStore::new(rpc.clone()));
|
let user_store = UserStore::new(rpc.clone(), cx.background());
|
||||||
Arc::new(AppState {
|
Arc::new(AppState {
|
||||||
settings_tx: Arc::new(Mutex::new(settings_tx)),
|
settings_tx: Arc::new(Mutex::new(settings_tx)),
|
||||||
settings,
|
settings,
|
||||||
|
|
|
@ -1,22 +1,73 @@
|
||||||
use crate::rpc::Client;
|
use crate::{
|
||||||
|
rpc::{Client, Status},
|
||||||
|
util::TryFutureExt,
|
||||||
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use gpui::{elements::Image, executor, ImageData, Task};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use postage::{prelude::Stream, sink::Sink, watch};
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
use surf::{
|
||||||
|
http::{Method, Request},
|
||||||
|
HttpClient, Url,
|
||||||
|
};
|
||||||
use zrpc::proto;
|
use zrpc::proto;
|
||||||
|
|
||||||
pub use proto::User;
|
pub struct User {
|
||||||
|
id: u64,
|
||||||
|
github_login: String,
|
||||||
|
avatar: Option<ImageData>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct UserStore {
|
pub struct UserStore {
|
||||||
users: Mutex<HashMap<u64, Arc<User>>>,
|
users: Mutex<HashMap<u64, Arc<User>>>,
|
||||||
|
current_user: watch::Receiver<Option<Arc<User>>>,
|
||||||
rpc: Arc<Client>,
|
rpc: Arc<Client>,
|
||||||
|
http: Arc<dyn HttpClient>,
|
||||||
|
_maintain_current_user: Option<Task<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserStore {
|
impl UserStore {
|
||||||
pub fn new(rpc: Arc<Client>) -> Self {
|
pub fn new(
|
||||||
Self {
|
rpc: Arc<Client>,
|
||||||
|
http: Arc<dyn HttpClient>,
|
||||||
|
executor: &executor::Background,
|
||||||
|
) -> Arc<Self> {
|
||||||
|
let (mut current_user_tx, current_user_rx) = watch::channel();
|
||||||
|
|
||||||
|
let mut this = Arc::new(Self {
|
||||||
users: Default::default(),
|
users: Default::default(),
|
||||||
rpc,
|
current_user: current_user_rx,
|
||||||
}
|
rpc: rpc.clone(),
|
||||||
|
http,
|
||||||
|
_maintain_current_user: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let task = {
|
||||||
|
let this = Arc::downgrade(&this);
|
||||||
|
executor.spawn(async move {
|
||||||
|
let mut status = rpc.status();
|
||||||
|
while let Some(status) = status.recv().await {
|
||||||
|
match status {
|
||||||
|
Status::Connected { user_id, .. } => {
|
||||||
|
if let Some(this) = this.upgrade() {
|
||||||
|
current_user_tx
|
||||||
|
.send(this.fetch_user(user_id).log_err().await)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Status::SignedOut => {
|
||||||
|
current_user_tx.send(None).await.ok();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
Arc::get_mut(&mut this).unwrap()._maintain_current_user = Some(task);
|
||||||
|
|
||||||
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_users(&self, mut user_ids: Vec<u64>) -> Result<()> {
|
pub async fn load_users(&self, mut user_ids: Vec<u64>) -> Result<()> {
|
||||||
|
@ -56,4 +107,29 @@ impl UserStore {
|
||||||
Err(anyhow!("server responded with no users"))
|
Err(anyhow!("server responded with no users"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn current_user(&self) -> &watch::Receiver<Option<Arc<User>>> {
|
||||||
|
&self.current_user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
async fn new(message: proto::User, http: &dyn HttpClient) -> Self {
|
||||||
|
let avatar = fetch_avatar(http, &message.avatar_url).await.log_err();
|
||||||
|
User {
|
||||||
|
id: message.id,
|
||||||
|
github_login: message.github_login,
|
||||||
|
avatar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_avatar(http: &dyn HttpClient, url: &str) -> Result<Arc<ImageData>> {
|
||||||
|
let url = Url::parse(url)?;
|
||||||
|
let request = Request::new(Method::Get, url);
|
||||||
|
let response = http.send(request).await?;
|
||||||
|
let bytes = response.body_bytes().await?;
|
||||||
|
let format = image::guess_format(&bytes)?;
|
||||||
|
let image = image::load_from_memory_with_format(&bytes, format)?.into_bgra8();
|
||||||
|
Ok(ImageData::new(image))
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ use gpui::{
|
||||||
use log::error;
|
use log::error;
|
||||||
pub use pane::*;
|
pub use pane::*;
|
||||||
pub use pane_group::*;
|
pub use pane_group::*;
|
||||||
use postage::watch;
|
use postage::{prelude::Stream, watch};
|
||||||
use sidebar::{Side, Sidebar, ToggleSidebarItem};
|
use sidebar::{Side, Sidebar, ToggleSidebarItem};
|
||||||
use smol::prelude::*;
|
use smol::prelude::*;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -356,6 +356,7 @@ pub struct Workspace {
|
||||||
(usize, Arc<Path>),
|
(usize, Arc<Path>),
|
||||||
postage::watch::Receiver<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>,
|
postage::watch::Receiver<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>,
|
||||||
>,
|
>,
|
||||||
|
_observe_current_user: Task<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Workspace {
|
impl Workspace {
|
||||||
|
@ -389,6 +390,18 @@ impl Workspace {
|
||||||
);
|
);
|
||||||
right_sidebar.add_item("icons/user-16.svg", cx.add_view(|_| ProjectBrowser).into());
|
right_sidebar.add_item("icons/user-16.svg", cx.add_view(|_| ProjectBrowser).into());
|
||||||
|
|
||||||
|
let mut current_user = app_state.user_store.current_user().clone();
|
||||||
|
let _observe_current_user = cx.spawn_weak(|this, mut cx| async move {
|
||||||
|
current_user.recv().await;
|
||||||
|
while current_user.recv().await.is_some() {
|
||||||
|
cx.update(|cx| {
|
||||||
|
if let Some(this) = this.upgrade(&cx) {
|
||||||
|
this.update(cx, |_, cx| cx.notify());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Workspace {
|
Workspace {
|
||||||
modal: None,
|
modal: None,
|
||||||
center: PaneGroup::new(pane.id()),
|
center: PaneGroup::new(pane.id()),
|
||||||
|
@ -404,6 +417,7 @@ impl Workspace {
|
||||||
worktrees: Default::default(),
|
worktrees: Default::default(),
|
||||||
items: Default::default(),
|
items: Default::default(),
|
||||||
loading_items: Default::default(),
|
loading_items: Default::default(),
|
||||||
|
_observe_current_user,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -940,17 +954,21 @@ impl Workspace {
|
||||||
&self.active_pane
|
&self.active_pane
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_account_status(&self, cx: &mut RenderContext<Self>) -> ElementBox {
|
fn render_current_user(&self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||||
let theme = &self.settings.borrow().theme;
|
let theme = &self.settings.borrow().theme;
|
||||||
|
let avatar = if let Some(current_user) = self.user_store.current_user().borrow().as_ref() {
|
||||||
|
todo!()
|
||||||
|
} else {
|
||||||
|
Svg::new("icons/signed-out-12.svg")
|
||||||
|
.with_color(theme.workspace.titlebar.icon_signed_out)
|
||||||
|
.boxed()
|
||||||
|
};
|
||||||
|
|
||||||
ConstrainedBox::new(
|
ConstrainedBox::new(
|
||||||
Align::new(
|
Align::new(
|
||||||
ConstrainedBox::new(
|
ConstrainedBox::new(avatar)
|
||||||
Svg::new("icons/signed-out-12.svg")
|
.with_width(theme.workspace.titlebar.icon_width)
|
||||||
.with_color(theme.workspace.titlebar.icon_signed_out)
|
.boxed(),
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_width(theme.workspace.titlebar.icon_width)
|
|
||||||
.boxed(),
|
|
||||||
)
|
)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
|
@ -988,7 +1006,7 @@ impl View for Workspace {
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
Align::new(self.render_account_status(cx)).right().boxed(),
|
Align::new(self.render_current_user(cx)).right().boxed(),
|
||||||
)
|
)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue