Show contacts panel the first time a new user connects to collab

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2022-05-20 17:33:09 +02:00
parent d8ee4378c9
commit 0597c662e4
6 changed files with 95 additions and 33 deletions

1
Cargo.lock generated
View file

@ -935,6 +935,7 @@ checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7"
name = "contacts_panel" name = "contacts_panel"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"client", "client",
"editor", "editor",
"futures", "futures",

View file

@ -78,10 +78,12 @@ pub struct InviteInfo {
pub url: Arc<str>, pub url: Arc<str>,
} }
#[derive(Clone)] pub enum Event {
pub struct ContactEvent { Contact {
pub user: Arc<User>, user: Arc<User>,
pub kind: ContactEventKind, kind: ContactEventKind,
},
ShowContacts,
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -92,7 +94,7 @@ pub enum ContactEventKind {
} }
impl Entity for UserStore { impl Entity for UserStore {
type Event = ContactEvent; type Event = Event;
} }
enum UpdateContacts { enum UpdateContacts {
@ -111,6 +113,7 @@ impl UserStore {
let rpc_subscriptions = vec![ let rpc_subscriptions = vec![
client.add_message_handler(cx.handle(), Self::handle_update_contacts), client.add_message_handler(cx.handle(), Self::handle_update_contacts),
client.add_message_handler(cx.handle(), Self::handle_update_invite_info), client.add_message_handler(cx.handle(), Self::handle_update_invite_info),
client.add_message_handler(cx.handle(), Self::handle_show_contacts),
]; ];
Self { Self {
users: Default::default(), users: Default::default(),
@ -180,6 +183,16 @@ impl UserStore {
Ok(()) Ok(())
} }
async fn handle_show_contacts(
this: ModelHandle<Self>,
_: TypedEnvelope<proto::ShowContacts>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |_, cx| cx.emit(Event::ShowContacts));
Ok(())
}
pub fn invite_info(&self) -> Option<&InviteInfo> { pub fn invite_info(&self) -> Option<&InviteInfo> {
self.invite_info.as_ref() self.invite_info.as_ref()
} }
@ -274,7 +287,7 @@ impl UserStore {
// Update existing contacts and insert new ones // Update existing contacts and insert new ones
for (updated_contact, should_notify) in updated_contacts { for (updated_contact, should_notify) in updated_contacts {
if should_notify { if should_notify {
cx.emit(ContactEvent { cx.emit(Event::Contact {
user: updated_contact.user.clone(), user: updated_contact.user.clone(),
kind: ContactEventKind::Accepted, kind: ContactEventKind::Accepted,
}); });
@ -291,7 +304,7 @@ impl UserStore {
// Remove incoming contact requests // Remove incoming contact requests
this.incoming_contact_requests.retain(|user| { this.incoming_contact_requests.retain(|user| {
if removed_incoming_requests.contains(&user.id) { if removed_incoming_requests.contains(&user.id) {
cx.emit(ContactEvent { cx.emit(Event::Contact {
user: user.clone(), user: user.clone(),
kind: ContactEventKind::Cancelled, kind: ContactEventKind::Cancelled,
}); });
@ -303,7 +316,7 @@ impl UserStore {
// Update existing incoming requests and insert new ones // Update existing incoming requests and insert new ones
for (user, should_notify) in incoming_requests { for (user, should_notify) in incoming_requests {
if should_notify { if should_notify {
cx.emit(ContactEvent { cx.emit(Event::Contact {
user: user.clone(), user: user.clone(),
kind: ContactEventKind::Requested, kind: ContactEventKind::Requested,
}); });

View file

@ -18,6 +18,7 @@ settings = { path = "../settings" }
theme = { path = "../theme" } theme = { path = "../theme" }
util = { path = "../util" } util = { path = "../util" }
workspace = { path = "../workspace" } workspace = { path = "../workspace" }
anyhow = "1.0"
futures = "0.3" futures = "0.3"
log = "0.4" log = "0.4"
postage = { version = "0.4.1", features = ["futures-traits"] } postage = { version = "0.4.1", features = ["futures-traits"] }

View file

@ -1,5 +1,7 @@
use std::sync::Arc;
use crate::notifications::render_user_notification; use crate::notifications::render_user_notification;
use client::{ContactEvent, ContactEventKind, UserStore}; use client::{ContactEventKind, User, UserStore};
use gpui::{ use gpui::{
elements::*, impl_internal_actions, Entity, ModelHandle, MutableAppContext, RenderContext, elements::*, impl_internal_actions, Entity, ModelHandle, MutableAppContext, RenderContext,
View, ViewContext, View, ViewContext,
@ -15,7 +17,8 @@ pub fn init(cx: &mut MutableAppContext) {
pub struct ContactNotification { pub struct ContactNotification {
user_store: ModelHandle<UserStore>, user_store: ModelHandle<UserStore>,
event: ContactEvent, user: Arc<User>,
kind: client::ContactEventKind,
} }
#[derive(Clone)] #[derive(Clone)]
@ -41,27 +44,27 @@ impl View for ContactNotification {
} }
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox { fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
match self.event.kind { match self.kind {
ContactEventKind::Requested => render_user_notification( ContactEventKind::Requested => render_user_notification(
self.event.user.clone(), self.user.clone(),
"wants to add you as a contact", "wants to add you as a contact",
Some("They won't know if you decline."), Some("They won't know if you decline."),
RespondToContactRequest { RespondToContactRequest {
user_id: self.event.user.id, user_id: self.user.id,
accept: false, accept: false,
}, },
vec![ vec![
( (
"Decline", "Decline",
Box::new(RespondToContactRequest { Box::new(RespondToContactRequest {
user_id: self.event.user.id, user_id: self.user.id,
accept: false, accept: false,
}), }),
), ),
( (
"Accept", "Accept",
Box::new(RespondToContactRequest { Box::new(RespondToContactRequest {
user_id: self.event.user.id, user_id: self.user.id,
accept: true, accept: true,
}), }),
), ),
@ -69,10 +72,10 @@ impl View for ContactNotification {
cx, cx,
), ),
ContactEventKind::Accepted => render_user_notification( ContactEventKind::Accepted => render_user_notification(
self.event.user.clone(), self.user.clone(),
"accepted your contact request", "accepted your contact request",
None, None,
Dismiss(self.event.user.id), Dismiss(self.user.id),
vec![], vec![],
cx, cx,
), ),
@ -89,30 +92,35 @@ impl Notification for ContactNotification {
impl ContactNotification { impl ContactNotification {
pub fn new( pub fn new(
event: ContactEvent, user: Arc<User>,
kind: client::ContactEventKind,
user_store: ModelHandle<UserStore>, user_store: ModelHandle<UserStore>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
cx.subscribe(&user_store, move |this, _, event, cx| { cx.subscribe(&user_store, move |this, _, event, cx| {
if let client::ContactEvent { if let client::Event::Contact {
kind: ContactEventKind::Cancelled, kind: ContactEventKind::Cancelled,
user, user,
} = event } = event
{ {
if user.id == this.event.user.id { if user.id == this.user.id {
cx.emit(Event::Dismiss); cx.emit(Event::Dismiss);
} }
} }
}) })
.detach(); .detach();
Self { event, user_store } Self {
user,
kind,
user_store,
}
} }
fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) { fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
self.user_store.update(cx, |store, cx| { self.user_store.update(cx, |store, cx| {
store store
.dismiss_contact_request(self.event.user.id, cx) .dismiss_contact_request(self.user.id, cx)
.detach_and_log_err(cx); .detach_and_log_err(cx);
}); });
cx.emit(Event::Dismiss); cx.emit(Event::Dismiss);

View file

@ -158,16 +158,28 @@ impl ContactsPanel {
if let Some((workspace, user_store)) = if let Some((workspace, user_store)) =
workspace.upgrade(cx).zip(user_store.upgrade(cx)) workspace.upgrade(cx).zip(user_store.upgrade(cx))
{ {
workspace.update(cx, |workspace, cx| match event.kind { workspace.update(cx, |workspace, cx| match event {
ContactEventKind::Requested | ContactEventKind::Accepted => workspace client::Event::Contact { user, kind } => match kind {
.show_notification(event.user.id as usize, cx, |cx| { ContactEventKind::Requested | ContactEventKind::Accepted => workspace
cx.add_view(|cx| { .show_notification(user.id as usize, cx, |cx| {
ContactNotification::new(event.clone(), user_store, cx) cx.add_view(|cx| {
}) ContactNotification::new(
}), user.clone(),
*kind,
user_store,
cx,
)
})
}),
_ => {}
},
_ => {} _ => {}
}); });
} }
if let client::Event::ShowContacts = event {
cx.emit(Event::Activate);
}
} }
}) })
.detach(); .detach();
@ -801,6 +813,10 @@ impl SidebarItem for ContactsPanel {
fn contains_focused_view(&self, cx: &AppContext) -> bool { fn contains_focused_view(&self, cx: &AppContext) -> bool {
self.filter_editor.is_focused(cx) self.filter_editor.is_focused(cx)
} }
fn should_activate_item_on_event(&self, event: &Event, _: &AppContext) -> bool {
matches!(event, Event::Activate)
}
} }
fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Element { fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Element {
@ -816,7 +832,9 @@ fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Elemen
.with_height(style.button_width) .with_height(style.button_width)
} }
pub enum Event {} pub enum Event {
Activate,
}
impl Entity for ContactsPanel { impl Entity for ContactsPanel {
type Event = Event; type Event = Event;

View file

@ -9,6 +9,9 @@ use std::{cell::RefCell, rc::Rc};
use theme::Theme; use theme::Theme;
pub trait SidebarItem: View { pub trait SidebarItem: View {
fn should_activate_item_on_event(&self, _: &Self::Event, _: &AppContext) -> bool {
false
}
fn should_show_badge(&self, cx: &AppContext) -> bool; fn should_show_badge(&self, cx: &AppContext) -> bool;
fn contains_focused_view(&self, _: &AppContext) -> bool { fn contains_focused_view(&self, _: &AppContext) -> bool {
false false
@ -16,6 +19,7 @@ pub trait SidebarItem: View {
} }
pub trait SidebarItemHandle { pub trait SidebarItemHandle {
fn id(&self) -> usize;
fn should_show_badge(&self, cx: &AppContext) -> bool; fn should_show_badge(&self, cx: &AppContext) -> bool;
fn is_focused(&self, cx: &AppContext) -> bool; fn is_focused(&self, cx: &AppContext) -> bool;
fn to_any(&self) -> AnyViewHandle; fn to_any(&self) -> AnyViewHandle;
@ -25,6 +29,10 @@ impl<T> SidebarItemHandle for ViewHandle<T>
where where
T: SidebarItem, T: SidebarItem,
{ {
fn id(&self) -> usize {
self.id()
}
fn should_show_badge(&self, cx: &AppContext) -> bool { fn should_show_badge(&self, cx: &AppContext) -> bool {
self.read(cx).should_show_badge(cx) self.read(cx).should_show_badge(cx)
} }
@ -61,7 +69,7 @@ pub enum Side {
struct Item { struct Item {
icon_path: &'static str, icon_path: &'static str,
view: Rc<dyn SidebarItemHandle>, view: Rc<dyn SidebarItemHandle>,
_observation: Subscription, _subscriptions: [Subscription; 2],
} }
pub struct SidebarButtons { pub struct SidebarButtons {
@ -99,11 +107,24 @@ impl Sidebar {
view: ViewHandle<T>, view: ViewHandle<T>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
let subscription = cx.observe(&view, |_, _, cx| cx.notify()); let subscriptions = [
cx.observe(&view, |_, _, cx| cx.notify()),
cx.subscribe(&view, |this, view, event, cx| {
if view.read(cx).should_activate_item_on_event(event, cx) {
if let Some(ix) = this
.items
.iter()
.position(|item| item.view.id() == view.id())
{
this.activate_item(ix, cx);
}
}
}),
];
self.items.push(Item { self.items.push(Item {
icon_path, icon_path,
view: Rc::new(view), view: Rc::new(view),
_observation: subscription, _subscriptions: subscriptions,
}); });
cx.notify() cx.notify()
} }