mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-24 19:10:24 +00:00
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:
parent
d8ee4378c9
commit
0597c662e4
6 changed files with 95 additions and 33 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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"] }
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue