zed2 notification panel (#3603)

Release Notes:

- N/A
This commit is contained in:
Julia 2023-12-12 18:04:47 -05:00 committed by GitHub
commit 2e00da5a79
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 874 additions and 1098 deletions

1
Cargo.lock generated
View file

@ -12071,6 +12071,7 @@ dependencies = [
"lsp2", "lsp2",
"menu2", "menu2",
"node_runtime", "node_runtime",
"notifications2",
"num_cpus", "num_cpus",
"outline2", "outline2",
"parking_lot 0.11.2", "parking_lot 0.11.2",

View file

@ -345,7 +345,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
fn init_test(cx: &mut AppContext) -> Model<ChannelStore> { fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
let http = FakeHttpClient::with_404_response(); let http = FakeHttpClient::with_404_response();
let client = Client::new(http.clone(), cx); let client = Client::new(http.clone(), cx);
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http, cx)); let user_store = cx.build_model(|cx| UserStore::new(client.clone(), cx));
let settings_store = SettingsStore::test(cx); let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store); cx.set_global(settings_store);

View file

@ -8,7 +8,6 @@ use rpc::{
ConnectionId, Peer, Receipt, TypedEnvelope, ConnectionId, Peer, Receipt, TypedEnvelope,
}; };
use std::sync::Arc; use std::sync::Arc;
use util::http::FakeHttpClient;
pub struct FakeServer { pub struct FakeServer {
peer: Arc<Peer>, peer: Arc<Peer>,
@ -195,8 +194,7 @@ impl FakeServer {
client: Arc<Client>, client: Arc<Client>,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) -> Model<UserStore> { ) -> Model<UserStore> {
let http_client = FakeHttpClient::with_404_response(); let user_store = cx.build_model(|cx| UserStore::new(client, cx));
let user_store = cx.build_model(|cx| UserStore::new(client, http_client, cx));
assert_eq!( assert_eq!(
self.receive::<proto::GetUsers>() self.receive::<proto::GetUsers>()
.await .await

View file

@ -2,13 +2,12 @@ use super::{proto, Client, Status, TypedEnvelope};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use collections::{hash_map::Entry, HashMap, HashSet}; use collections::{hash_map::Entry, HashMap, HashSet};
use feature_flags::FeatureFlagAppExt; use feature_flags::FeatureFlagAppExt;
use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt}; use futures::{channel::mpsc, Future, StreamExt};
use gpui::{AsyncAppContext, EventEmitter, ImageData, Model, ModelContext, Task}; use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, Task};
use postage::{sink::Sink, watch}; use postage::{sink::Sink, watch};
use rpc::proto::{RequestMessage, UsersResponse}; use rpc::proto::{RequestMessage, UsersResponse};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use text::ReplicaId; use text::ReplicaId;
use util::http::HttpClient;
use util::TryFutureExt as _; use util::TryFutureExt as _;
pub type UserId = u64; pub type UserId = u64;
@ -20,7 +19,7 @@ pub struct ParticipantIndex(pub u32);
pub struct User { pub struct User {
pub id: UserId, pub id: UserId,
pub github_login: String, pub github_login: String,
pub avatar: Option<Arc<ImageData>>, pub avatar_uri: SharedString,
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@ -76,7 +75,6 @@ pub struct UserStore {
pending_contact_requests: HashMap<u64, usize>, pending_contact_requests: HashMap<u64, usize>,
invite_info: Option<InviteInfo>, invite_info: Option<InviteInfo>,
client: Weak<Client>, client: Weak<Client>,
http: Arc<dyn HttpClient>,
_maintain_contacts: Task<()>, _maintain_contacts: Task<()>,
_maintain_current_user: Task<Result<()>>, _maintain_current_user: Task<Result<()>>,
} }
@ -112,11 +110,7 @@ enum UpdateContacts {
} }
impl UserStore { impl UserStore {
pub fn new( pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
client: Arc<Client>,
http: Arc<dyn HttpClient>,
cx: &mut ModelContext<Self>,
) -> Self {
let (mut current_user_tx, current_user_rx) = watch::channel(); let (mut current_user_tx, current_user_rx) = watch::channel();
let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded(); let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
let rpc_subscriptions = vec![ let rpc_subscriptions = vec![
@ -134,7 +128,6 @@ impl UserStore {
invite_info: None, invite_info: None,
client: Arc::downgrade(&client), client: Arc::downgrade(&client),
update_contacts_tx, update_contacts_tx,
http,
_maintain_contacts: cx.spawn(|this, mut cx| async move { _maintain_contacts: cx.spawn(|this, mut cx| async move {
let _subscriptions = rpc_subscriptions; let _subscriptions = rpc_subscriptions;
while let Some(message) = update_contacts_rx.next().await { while let Some(message) = update_contacts_rx.next().await {
@ -445,6 +438,12 @@ impl UserStore {
self.perform_contact_request(user_id, proto::RemoveContact { user_id }, cx) self.perform_contact_request(user_id, proto::RemoveContact { user_id }, cx)
} }
pub fn has_incoming_contact_request(&self, user_id: u64) -> bool {
self.incoming_contact_requests
.iter()
.any(|user| user.id == user_id)
}
pub fn respond_to_contact_request( pub fn respond_to_contact_request(
&mut self, &mut self,
requester_id: u64, requester_id: u64,
@ -616,17 +615,14 @@ impl UserStore {
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<Arc<User>>>> { ) -> Task<Result<Vec<Arc<User>>>> {
let client = self.client.clone(); let client = self.client.clone();
let http = self.http.clone();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
if let Some(rpc) = client.upgrade() { if let Some(rpc) = client.upgrade() {
let response = rpc.request(request).await.context("error loading users")?; let response = rpc.request(request).await.context("error loading users")?;
let users = future::join_all( let users = response
response .users
.users .into_iter()
.into_iter() .map(|user| User::new(user))
.map(|user| User::new(user, http.as_ref())), .collect::<Vec<_>>();
)
.await;
this.update(&mut cx, |this, _| { this.update(&mut cx, |this, _| {
for user in &users { for user in &users {
@ -659,11 +655,11 @@ impl UserStore {
} }
impl User { impl User {
async fn new(message: proto::User, http: &dyn HttpClient) -> Arc<Self> { fn new(message: proto::User) -> Arc<Self> {
Arc::new(User { Arc::new(User {
id: message.id, id: message.id,
github_login: message.github_login, github_login: message.github_login,
avatar: fetch_avatar(http, &message.avatar_url).warn_on_err().await, avatar_uri: message.avatar_url.into(),
}) })
} }
} }
@ -696,25 +692,3 @@ impl Collaborator {
}) })
} }
} }
// todo!("we probably don't need this now that we fetch")
async fn fetch_avatar(http: &dyn HttpClient, url: &str) -> Result<Arc<ImageData>> {
let mut response = http
.get(url, Default::default(), true)
.await
.map_err(|e| anyhow!("failed to send user avatar request: {}", e))?;
if !response.status().is_success() {
return Err(anyhow!("avatar request failed {:?}", response.status()));
}
let mut body = Vec::new();
response
.body_mut()
.read_to_end(&mut body)
.await
.map_err(|e| anyhow!("failed to read user avatar response body: {}", e))?;
let format = image::guess_format(&body)?;
let image = image::load_from_memory_with_format(&body, format)?.into_bgra8();
Ok(Arc::new(ImageData::new(image)))
}

View file

@ -1823,7 +1823,7 @@ async fn test_active_call_events(
owner: Arc::new(User { owner: Arc::new(User {
id: client_a.user_id().unwrap(), id: client_a.user_id().unwrap(),
github_login: "user_a".to_string(), github_login: "user_a".to_string(),
avatar: None, avatar_uri: "avatar_a".into(),
}), }),
project_id: project_a_id, project_id: project_a_id,
worktree_root_names: vec!["a".to_string()], worktree_root_names: vec!["a".to_string()],
@ -1841,7 +1841,7 @@ async fn test_active_call_events(
owner: Arc::new(User { owner: Arc::new(User {
id: client_b.user_id().unwrap(), id: client_b.user_id().unwrap(),
github_login: "user_b".to_string(), github_login: "user_b".to_string(),
avatar: None, avatar_uri: "avatar_b".into(),
}), }),
project_id: project_b_id, project_id: project_b_id,
worktree_root_names: vec!["b".to_string()] worktree_root_names: vec!["b".to_string()]

View file

@ -209,7 +209,7 @@ impl TestServer {
}); });
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http, cx)); let user_store = cx.build_model(|cx| UserStore::new(client.clone(), cx));
let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx)); let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
let mut language_registry = LanguageRegistry::test(); let mut language_registry = LanguageRegistry::test();
language_registry.set_executor(cx.executor()); language_registry.set_executor(cx.executor());

View file

@ -364,13 +364,7 @@ impl ChatPanel {
if !is_continuation { if !is_continuation {
result = result.child( result = result.child(
h_stack() h_stack()
.children( .child(Avatar::new(message.sender.avatar_uri.clone()))
message
.sender
.avatar
.clone()
.map(|avatar| Avatar::data(avatar)),
)
.child(Label::new(message.sender.github_login.clone())) .child(Label::new(message.sender.github_login.clone()))
.child(Label::new(format_timestamp( .child(Label::new(format_timestamp(
message.timestamp, message.timestamp,
@ -659,7 +653,7 @@ mod tests {
timestamp: OffsetDateTime::now_utc(), timestamp: OffsetDateTime::now_utc(),
sender: Arc::new(client::User { sender: Arc::new(client::User {
github_login: "fgh".into(), github_login: "fgh".into(),
avatar: None, avatar_uri: "avatar_fgh".into(),
id: 103, id: 103,
}), }),
nonce: 5, nonce: 5,

View file

@ -234,7 +234,7 @@ mod tests {
user: Arc::new(User { user: Arc::new(User {
github_login: "a-b".into(), github_login: "a-b".into(),
id: 101, id: 101,
avatar: None, avatar_uri: "avatar_a-b".into(),
}), }),
kind: proto::channel_member::Kind::Member, kind: proto::channel_member::Kind::Member,
role: proto::ChannelRole::Member, role: proto::ChannelRole::Member,
@ -243,7 +243,7 @@ mod tests {
user: Arc::new(User { user: Arc::new(User {
github_login: "C_D".into(), github_login: "C_D".into(),
id: 102, id: 102,
avatar: None, avatar_uri: "avatar_C_D".into(),
}), }),
kind: proto::channel_member::Kind::Member, kind: proto::channel_member::Kind::Member,
role: proto::ChannelRole::Member, role: proto::ChannelRole::Member,
@ -275,7 +275,7 @@ mod tests {
cx.update(|cx| { cx.update(|cx| {
let http = FakeHttpClient::with_404_response(); let http = FakeHttpClient::with_404_response();
let client = Client::new(http.clone(), cx); let client = Client::new(http.clone(), cx);
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http, cx)); let user_store = cx.build_model(|cx| UserStore::new(client.clone(), cx));
let settings = SettingsStore::test(cx); let settings = SettingsStore::test(cx);
cx.set_global(settings); cx.set_global(settings);
theme::init(theme::LoadThemes::JustBase, cx); theme::init(theme::LoadThemes::JustBase, cx);

View file

@ -19,6 +19,7 @@ mod contact_finder;
use contact_finder::ContactFinder; use contact_finder::ContactFinder;
use menu::{Cancel, Confirm, SelectNext, SelectPrev}; use menu::{Cancel, Confirm, SelectNext, SelectPrev};
use rpc::proto::{self, PeerId}; use rpc::proto::{self, PeerId};
use smallvec::SmallVec;
use theme::{ActiveTheme, ThemeSettings}; use theme::{ActiveTheme, ThemeSettings};
// use context_menu::{ContextMenu, ContextMenuItem}; // use context_menu::{ContextMenu, ContextMenuItem};
// use db::kvp::KEY_VALUE_STORE; // use db::kvp::KEY_VALUE_STORE;
@ -1155,7 +1156,7 @@ impl CollabPanel {
let tooltip = format!("Follow {}", user.github_login); let tooltip = format!("Follow {}", user.github_login);
ListItem::new(SharedString::from(user.github_login.clone())) ListItem::new(SharedString::from(user.github_login.clone()))
.left_child(Avatar::data(user.avatar.clone().unwrap())) .left_child(Avatar::new(user.avatar_uri.clone()))
.child( .child(
h_stack() h_stack()
.w_full() .w_full()
@ -2349,44 +2350,45 @@ impl CollabPanel {
let busy = contact.busy || calling; let busy = contact.busy || calling;
let user_id = contact.user.id; let user_id = contact.user.id;
let github_login = SharedString::from(contact.user.github_login.clone()); let github_login = SharedString::from(contact.user.github_login.clone());
let mut item = ListItem::new(github_login.clone()) let mut item =
.on_click(cx.listener(move |this, _, cx| this.call(user_id, cx))) ListItem::new(github_login.clone())
.child( .on_click(cx.listener(move |this, _, cx| this.call(user_id, cx)))
h_stack() .child(
.w_full() h_stack()
.justify_between() .w_full()
.child(Label::new(github_login.clone())) .justify_between()
.when(calling, |el| { .child(Label::new(github_login.clone()))
el.child(Label::new("Calling").color(Color::Muted)) .when(calling, |el| {
}) el.child(Label::new("Calling").color(Color::Muted))
.when(!calling, |el| { })
el.child( .when(!calling, |el| {
div() el.child(
.id("remove_contact") div()
.invisible() .id("remove_contact")
.group_hover("", |style| style.visible()) .invisible()
.child( .group_hover("", |style| style.visible())
IconButton::new("remove_contact", Icon::Close) .child(
.icon_color(Color::Muted) IconButton::new("remove_contact", Icon::Close)
.tooltip(|cx| Tooltip::text("Remove Contact", cx)) .icon_color(Color::Muted)
.on_click(cx.listener({ .tooltip(|cx| Tooltip::text("Remove Contact", cx))
let github_login = github_login.clone(); .on_click(cx.listener({
move |this, _, cx| { let github_login = github_login.clone();
this.remove_contact(user_id, &github_login, cx); move |this, _, cx| {
} this.remove_contact(user_id, &github_login, cx);
})), }
), })),
) ),
}), )
) }),
.left_child( )
// todo!() handle contacts with no avatar .left_child(
Avatar::data(contact.user.avatar.clone().unwrap()) // todo!() handle contacts with no avatar
.availability_indicator(if online { Some(!busy) } else { None }), Avatar::new(contact.user.avatar_uri.clone())
) .availability_indicator(if online { Some(!busy) } else { None }),
.when(online && !busy, |el| { )
el.on_click(cx.listener(move |this, _, cx| this.call(user_id, cx))) .when(online && !busy, |el| {
}); el.on_click(cx.listener(move |this, _, cx| this.call(user_id, cx)))
});
div() div()
.id(github_login.clone()) .id(github_login.clone())
@ -2458,7 +2460,7 @@ impl CollabPanel {
.child(Label::new(github_login.clone())) .child(Label::new(github_login.clone()))
.child(h_stack().children(controls)), .child(h_stack().children(controls)),
) )
.when_some(user.avatar.clone(), |el, avatar| el.left_avatar(avatar)) .left_avatar(user.avatar_uri.clone())
} }
fn render_contact_placeholder( fn render_contact_placeholder(
@ -2516,7 +2518,9 @@ impl CollabPanel {
let result = FacePile { let result = FacePile {
faces: participants faces: participants
.iter() .iter()
.filter_map(|user| Some(Avatar::data(user.avatar.clone()?).into_any_element())) .filter_map(|user| {
Some(Avatar::new(user.avatar_uri.clone()).into_any_element())
})
.take(FACEPILE_LIMIT) .take(FACEPILE_LIMIT)
.chain(if extra_count > 0 { .chain(if extra_count > 0 {
// todo!() @nate - this label looks wrong. // todo!() @nate - this label looks wrong.
@ -2524,7 +2528,7 @@ impl CollabPanel {
} else { } else {
None None
}) })
.collect::<Vec<_>>(), .collect::<SmallVec<_>>(),
}; };
Some(result) Some(result)

View file

@ -7,7 +7,7 @@ use gpui::{
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use std::sync::Arc; use std::sync::Arc;
use theme::ActiveTheme as _; use theme::ActiveTheme as _;
use ui::prelude::*; use ui::{prelude::*, Avatar};
use util::{ResultExt as _, TryFutureExt}; use util::{ResultExt as _, TryFutureExt};
use workspace::ModalView; use workspace::ModalView;
@ -187,7 +187,7 @@ impl PickerDelegate for ContactFinderDelegate {
div() div()
.flex_1() .flex_1()
.justify_between() .justify_between()
.children(user.avatar.clone().map(|avatar| img(avatar))) .child(Avatar::new(user.avatar_uri.clone()))
.child(Label::new(user.github_login.clone())) .child(Label::new(user.github_login.clone()))
.children(icon_path.map(|icon_path| svg().path(icon_path))), .children(icon_path.map(|icon_path| svg().path(icon_path))),
) )

View file

@ -232,43 +232,41 @@ impl Render for CollabTitlebarItem {
}) })
.child(h_stack().px_1p5().map(|this| { .child(h_stack().px_1p5().map(|this| {
if let Some(user) = current_user { if let Some(user) = current_user {
this.when_some(user.avatar.clone(), |this, avatar| { // TODO: Finish implementing user menu popover
// TODO: Finish implementing user menu popover //
// this.child(
this.child( popover_menu("user-menu")
popover_menu("user-menu") .menu(|cx| {
.menu(|cx| { ContextMenu::build(cx, |menu, _| menu.header("ADADA"))
ContextMenu::build(cx, |menu, _| menu.header("ADADA")) })
}) .trigger(
.trigger( ButtonLike::new("user-menu")
ButtonLike::new("user-menu") .child(
.child( h_stack()
h_stack() .gap_0p5()
.gap_0p5() .child(Avatar::new(user.avatar_uri.clone()))
.child(Avatar::data(avatar)) .child(
.child( IconElement::new(Icon::ChevronDown)
IconElement::new(Icon::ChevronDown) .color(Color::Muted),
.color(Color::Muted), ),
), )
) .style(ButtonStyle::Subtle)
.style(ButtonStyle::Subtle) .tooltip(move |cx| {
.tooltip(move |cx| { Tooltip::text("Toggle User Menu", cx)
Tooltip::text("Toggle User Menu", cx) }),
}), )
) .anchor(gpui::AnchorCorner::TopRight),
.anchor(gpui::AnchorCorner::TopRight), )
) // this.child(
// this.child( // ButtonLike::new("user-menu")
// ButtonLike::new("user-menu") // .child(
// .child( // h_stack().gap_0p5().child(Avatar::data(avatar)).child(
// h_stack().gap_0p5().child(Avatar::data(avatar)).child( // IconElement::new(Icon::ChevronDown).color(Color::Muted),
// IconElement::new(Icon::ChevronDown).color(Color::Muted), // ),
// ), // )
// ) // .style(ButtonStyle::Subtle)
// .style(ButtonStyle::Subtle) // .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
// .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), // )
// )
})
} else { } else {
this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| { this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| {
let client = client.clone(); let client = client.clone();
@ -424,27 +422,21 @@ impl CollabTitlebarItem {
current_user: &Arc<User>, current_user: &Arc<User>,
) -> Option<FacePile> { ) -> Option<FacePile> {
let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id)); let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id));
let mut pile = FacePile::default();
pile.extend( let pile = FacePile::default().child(
user.avatar div()
.clone() .child(
.map(|avatar| { Avatar::new(user.avatar_uri.clone())
div() .grayscale(!is_present)
.child( .border_color(if is_speaking {
Avatar::data(avatar.clone()) gpui::blue()
.grayscale(!is_present) } else if is_muted {
.border_color(if is_speaking { gpui::red()
gpui::blue() } else {
} else if is_muted { Hsla::default()
gpui::red() }),
} else { )
Hsla::default() .children(followers.iter().filter_map(|follower_peer_id| {
}),
)
.into_any_element()
})
.into_iter()
.chain(followers.iter().filter_map(|follower_peer_id| {
let follower = room let follower = room
.remote_participants() .remote_participants()
.values() .values()
@ -454,12 +446,11 @@ impl CollabTitlebarItem {
.then_some(current_user) .then_some(current_user)
})? })?
.clone(); .clone();
follower
.avatar Some(div().child(Avatar::new(follower.avatar_uri.clone())))
.clone()
.map(|avatar| div().child(Avatar::data(avatar.clone())).into_any_element())
})), })),
); );
Some(pile) Some(pile)
} }

View file

@ -39,6 +39,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
collab_panel::init(cx); collab_panel::init(cx);
channel_view::init(cx); channel_view::init(cx);
chat_panel::init(cx); chat_panel::init(cx);
notification_panel::init(cx);
notifications::init(&app_state, cx); notifications::init(&app_state, cx);
// cx.add_global_action(toggle_screen_sharing); // cx.add_global_action(toggle_screen_sharing);

View file

@ -1,11 +1,11 @@
use gpui::{ use gpui::{
div, AnyElement, Div, ElementId, IntoElement, ParentElement as _, RenderOnce, Styled, div, AnyElement, Div, ElementId, IntoElement, ParentElement, RenderOnce, Styled, WindowContext,
WindowContext,
}; };
use smallvec::SmallVec;
#[derive(Default, IntoElement)] #[derive(Default, IntoElement)]
pub struct FacePile { pub struct FacePile {
pub faces: Vec<AnyElement>, pub faces: SmallVec<[AnyElement; 2]>,
} }
impl RenderOnce for FacePile { impl RenderOnce for FacePile {
@ -25,8 +25,8 @@ impl RenderOnce for FacePile {
} }
} }
impl Extend<AnyElement> for FacePile { impl ParentElement for FacePile {
fn extend<T: IntoIterator<Item = AnyElement>>(&mut self, children: T) { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
self.faces.extend(children); &mut self.faces
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -114,14 +114,7 @@ impl IncomingCallNotification {
} }
fn render_caller(&self, cx: &mut ViewContext<Self>) -> impl Element { fn render_caller(&self, cx: &mut ViewContext<Self>) -> impl Element {
h_stack() h_stack()
.children( .child(Avatar::new(self.state.call.calling_user.avatar_uri.clone()))
self.state
.call
.calling_user
.avatar
.as_ref()
.map(|avatar| Avatar::data(avatar.clone())),
)
.child( .child(
v_stack() v_stack()
.child(Label::new(format!( .child(Label::new(format!(

View file

@ -119,12 +119,7 @@ impl ProjectSharedNotification {
fn render_owner(&self) -> impl Element { fn render_owner(&self) -> impl Element {
h_stack() h_stack()
.children( .child(Avatar::new(self.owner.avatar_uri.clone()))
self.owner
.avatar
.clone()
.map(|avatar| Avatar::data(avatar.clone())),
)
.child( .child(
v_stack() v_stack()
.child(Label::new(self.owner.github_login.clone())) .child(Label::new(self.owner.github_login.clone()))

View file

@ -868,7 +868,7 @@ impl Project {
languages.set_executor(cx.executor()); languages.set_executor(cx.executor());
let http_client = util::http::FakeHttpClient::with_404_response(); let http_client = util::http::FakeHttpClient::with_404_response();
let client = cx.update(|cx| client::Client::new(http_client.clone(), cx)); let client = cx.update(|cx| client::Client::new(http_client.clone(), cx));
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx)); let user_store = cx.build_model(|cx| UserStore::new(client.clone(), cx));
let project = cx.update(|cx| { let project = cx.update(|cx| {
Project::local( Project::local(
client, client,

View file

@ -1,6 +1,5 @@
use crate::prelude::*; use crate::prelude::*;
use gpui::{img, Div, Hsla, ImageData, ImageSource, Img, IntoElement, Styled}; use gpui::{img, Div, Hsla, ImageSource, Img, IntoElement, Styled};
use std::sync::Arc;
#[derive(Debug, Default, PartialEq, Clone)] #[derive(Debug, Default, PartialEq, Clone)]
pub enum Shape { pub enum Shape {
@ -58,16 +57,8 @@ impl RenderOnce for Avatar {
} }
impl Avatar { impl Avatar {
pub fn uri(src: impl Into<SharedString>) -> Self { pub fn new(src: impl Into<ImageSource>) -> Self {
Self::source(src.into().into()) Avatar {
}
pub fn data(src: Arc<ImageData>) -> Self {
Self::source(src.into())
}
pub fn source(src: ImageSource) -> Self {
Self {
image: img(src), image: img(src),
is_available: None, is_available: None,
border_color: None, border_color: None,

View file

@ -111,7 +111,7 @@ impl ListItem {
} }
pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self { pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
self.left_slot = Some(Avatar::source(left_avatar.into()).into_any_element()); self.left_slot = Some(Avatar::new(left_avatar).into_any_element());
self self
} }
} }

View file

@ -13,18 +13,18 @@ impl Render for AvatarStory {
Story::container() Story::container()
.child(Story::title_for::<Avatar>()) .child(Story::title_for::<Avatar>())
.child(Story::label("Default")) .child(Story::label("Default"))
.child(Avatar::uri( .child(Avatar::new(
"https://avatars.githubusercontent.com/u/1714999?v=4", "https://avatars.githubusercontent.com/u/1714999?v=4",
)) ))
.child(Avatar::uri( .child(Avatar::new(
"https://avatars.githubusercontent.com/u/326587?v=4", "https://avatars.githubusercontent.com/u/326587?v=4",
)) ))
.child( .child(
Avatar::uri("https://avatars.githubusercontent.com/u/326587?v=4") Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4")
.availability_indicator(true), .availability_indicator(true),
) )
.child( .child(
Avatar::uri("https://avatars.githubusercontent.com/u/326587?v=4") Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4")
.availability_indicator(false), .availability_indicator(false),
) )
} }

View file

@ -363,7 +363,7 @@ impl AppState {
let languages = Arc::new(LanguageRegistry::test()); let languages = Arc::new(LanguageRegistry::test());
let http_client = util::http::FakeHttpClient::with_404_response(); let http_client = util::http::FakeHttpClient::with_404_response();
let client = Client::new(http_client.clone(), cx); let client = Client::new(http_client.clone(), cx);
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx)); let user_store = cx.build_model(|cx| UserStore::new(client.clone(), cx));
let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx)); let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
theme::init(theme::LoadThemes::JustBase, cx); theme::init(theme::LoadThemes::JustBase, cx);

View file

@ -49,6 +49,7 @@ lsp = { package = "lsp2", path = "../lsp2" }
menu = { package = "menu2", path = "../menu2" } menu = { package = "menu2", path = "../menu2" }
# language_tools = { path = "../language_tools" } # language_tools = { path = "../language_tools" }
node_runtime = { path = "../node_runtime" } node_runtime = { path = "../node_runtime" }
notifications = { package = "notifications2", path = "../notifications2" }
assistant = { package = "assistant2", path = "../assistant2" } assistant = { package = "assistant2", path = "../assistant2" }
outline = { package = "outline2", path = "../outline2" } outline = { package = "outline2", path = "../outline2" }
# plugin_runtime = { path = "../plugin_runtime",optional = true } # plugin_runtime = { path = "../plugin_runtime",optional = true }

View file

@ -143,7 +143,7 @@ fn main() {
language::init(cx); language::init(cx);
languages::init(languages.clone(), node_runtime.clone(), cx); languages::init(languages.clone(), node_runtime.clone(), cx);
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); let user_store = cx.build_model(|cx| UserStore::new(client.clone(), cx));
let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx)); let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
cx.set_global(client.clone()); cx.set_global(client.clone());
@ -220,6 +220,7 @@ fn main() {
// activity_indicator::init(cx); // activity_indicator::init(cx);
// language_tools::init(cx); // language_tools::init(cx);
call::init(app_state.client.clone(), app_state.user_store.clone(), cx); call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
collab_ui::init(&app_state, cx); collab_ui::init(&app_state, cx);
feedback::init(cx); feedback::init(cx);
welcome::init(cx); welcome::init(cx);

View file

@ -164,24 +164,24 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone()); collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone());
let chat_panel = let chat_panel =
collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone()); collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone());
// let notification_panel = collab_ui::notification_panel::NotificationPanel::load( let notification_panel = collab_ui::notification_panel::NotificationPanel::load(
// workspace_handle.clone(), workspace_handle.clone(),
// cx.clone(), cx.clone(),
// ); );
let ( let (
project_panel, project_panel,
terminal_panel, terminal_panel,
assistant_panel, assistant_panel,
channels_panel, channels_panel,
chat_panel, chat_panel,
// notification_panel, notification_panel,
) = futures::try_join!( ) = futures::try_join!(
project_panel, project_panel,
terminal_panel, terminal_panel,
assistant_panel, assistant_panel,
channels_panel, channels_panel,
chat_panel, chat_panel,
// notification_panel, notification_panel,
)?; )?;
workspace_handle.update(&mut cx, |workspace, cx| { workspace_handle.update(&mut cx, |workspace, cx| {
@ -191,7 +191,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
workspace.add_panel(assistant_panel, cx); workspace.add_panel(assistant_panel, cx);
workspace.add_panel(channels_panel, cx); workspace.add_panel(channels_panel, cx);
workspace.add_panel(chat_panel, cx); workspace.add_panel(chat_panel, cx);
// workspace.add_panel(notification_panel, cx); workspace.add_panel(notification_panel, cx);
// if !was_deserialized // if !was_deserialized
// && workspace // && workspace