Add affordance to copy user's invite link if they have a code

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
Nathan Sobo
2022-05-19 17:57:46 -06:00
co-authored by Antonio Scandurra
parent a3bbabaaac
commit eedb8ba59f
21 changed files with 2412 additions and 45 deletions
+35 -5
View File
@@ -65,12 +65,19 @@ pub struct UserStore {
incoming_contact_requests: Vec<Arc<User>>,
outgoing_contact_requests: Vec<Arc<User>>,
pending_contact_requests: HashMap<u64, usize>,
invite_info: Option<InviteInfo>,
client: Weak<Client>,
http: Arc<dyn HttpClient>,
_maintain_contacts: Task<()>,
_maintain_current_user: Task<()>,
}
#[derive(Clone)]
pub struct InviteInfo {
pub count: u32,
pub url: Arc<str>,
}
#[derive(Clone)]
pub struct ContactEvent {
pub user: Arc<User>,
@@ -101,19 +108,22 @@ impl UserStore {
) -> Self {
let (mut current_user_tx, current_user_rx) = watch::channel();
let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
let rpc_subscription =
client.add_message_handler(cx.handle(), Self::handle_update_contacts);
let rpc_subscriptions = vec![
client.add_message_handler(cx.handle(), Self::handle_update_contacts),
client.add_message_handler(cx.handle(), Self::handle_update_invite_info),
];
Self {
users: Default::default(),
current_user: current_user_rx,
contacts: Default::default(),
incoming_contact_requests: Default::default(),
outgoing_contact_requests: Default::default(),
invite_info: None,
client: Arc::downgrade(&client),
update_contacts_tx,
http,
_maintain_contacts: cx.spawn_weak(|this, mut cx| async move {
let _subscription = rpc_subscription;
let _subscriptions = rpc_subscriptions;
while let Some(message) = update_contacts_rx.next().await {
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| this.update_contacts(message, cx))
@@ -154,15 +164,35 @@ impl UserStore {
}
}
async fn handle_update_invite_info(
this: ModelHandle<Self>,
message: TypedEnvelope<proto::UpdateInviteInfo>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
this.invite_info = Some(InviteInfo {
url: Arc::from(message.payload.url),
count: message.payload.count,
});
cx.notify();
});
Ok(())
}
pub fn invite_info(&self) -> Option<&InviteInfo> {
self.invite_info.as_ref()
}
async fn handle_update_contacts(
this: ModelHandle<Self>,
msg: TypedEnvelope<proto::UpdateContacts>,
message: TypedEnvelope<proto::UpdateContacts>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, _| {
this.update_contacts_tx
.unbounded_send(UpdateContacts::Update(msg.payload))
.unbounded_send(UpdateContacts::Update(message.payload))
.unwrap();
});
Ok(())
+7
View File
@@ -1,2 +1,9 @@
DATABASE_URL = "postgres://postgres@localhost/zed"
HTTP_PORT = 8080
API_TOKEN = "secret"
INVITE_LINK_PREFIX = "http://localhost:3000/invites/"
# HONEYCOMB_API_KEY=
# HONEYCOMB_DATASET=
# RUST_LOG=info
# LOG_JSON=true
+3
View File
@@ -20,6 +20,7 @@ pub struct Config {
pub http_port: u16,
pub database_url: String,
pub api_token: String,
pub invite_link_prefix: String,
pub honeycomb_api_key: Option<String>,
pub honeycomb_dataset: Option<String>,
pub rust_log: Option<String>,
@@ -29,6 +30,7 @@ pub struct Config {
pub struct AppState {
db: Arc<dyn Db>,
api_token: String,
invite_link_prefix: String,
}
impl AppState {
@@ -37,6 +39,7 @@ impl AppState {
let this = Self {
db: Arc::new(db),
api_token: config.api_token.clone(),
invite_link_prefix: config.invite_link_prefix.clone(),
};
Ok(Arc::new(this))
}
+17 -2
View File
@@ -23,7 +23,11 @@ use axum::{
Extension, Router, TypedHeader,
};
use collections::HashMap;
use futures::{channel::mpsc, future::BoxFuture, FutureExt, SinkExt, StreamExt, TryStreamExt};
use futures::{
channel::mpsc,
future::{self, BoxFuture},
FutureExt, SinkExt, StreamExt, TryStreamExt,
};
use lazy_static::lazy_static;
use rpc::{
proto::{self, AnyTypedEnvelope, EntityMessage, EnvelopedMessage, RequestMessage},
@@ -276,12 +280,22 @@ impl Server {
let _ = send_connection_id.send(connection_id).await;
}
let contacts = this.app_state.db.get_contacts(user_id).await?;
let (contacts, invite_code) = future::try_join(
this.app_state.db.get_contacts(user_id),
this.app_state.db.get_invite_code_for_user(user_id)
).await?;
{
let mut store = this.store_mut().await;
store.add_connection(connection_id, user_id);
this.peer.send(connection_id, store.build_initial_contacts_update(contacts))?;
if let Some((code, count)) = invite_code {
this.peer.send(connection_id, proto::UpdateInviteInfo {
url: format!("{}{}", this.app_state.invite_link_prefix, code),
count,
})?;
}
}
this.update_user_contacts(user_id).await?;
@@ -6670,6 +6684,7 @@ mod tests {
Arc::new(AppState {
db: test_db.db().clone(),
api_token: Default::default(),
invite_link_prefix: Default::default(),
})
}
+56 -2
View File
@@ -8,12 +8,13 @@ use contact_notification::ContactNotification;
use editor::{Cancel, Editor};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
color::Color,
elements::*,
geometry::{rect::RectF, vector::vec2f},
impl_actions, impl_internal_actions,
platform::CursorStyle,
AppContext, Element, ElementBox, Entity, LayoutContext, ModelHandle, MutableAppContext,
RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
AppContext, ClipboardItem, Element, ElementBox, Entity, LayoutContext, ModelHandle,
MutableAppContext, RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
};
use join_project_notification::JoinProjectNotification;
use serde::Deserialize;
@@ -863,6 +864,59 @@ impl View for ContactsPanel {
.boxed(),
)
.with_child(List::new(self.list_state.clone()).flex(1., false).boxed())
.with_children(
self.user_store
.read(cx)
.invite_info()
.cloned()
.and_then(|info| {
enum InviteLink {}
if info.count > 0 {
Some(
MouseEventHandler::new::<InviteLink, _, _>(
0,
cx,
|state, cx| {
let style =
theme.invite_row.style_for(state, false).clone();
let copied =
cx.read_from_clipboard().map_or(false, |item| {
item.text().as_str() == info.url.as_ref()
});
Label::new(
format!(
"{} invite link ({} left)",
if copied { "Copied" } else { "Copy" },
info.count
),
style.label.clone(),
)
.aligned()
.left()
.constrained()
.with_height(theme.row_height)
.contained()
.with_style(style.container)
.boxed()
},
)
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, cx| {
cx.write_to_clipboard(ClipboardItem::new(
info.url.to_string(),
));
cx.notify();
})
.boxed(),
)
} else {
None
}
}),
)
.boxed(),
)
.with_style(theme.container)
+16 -10
View File
@@ -87,18 +87,19 @@ message Envelope {
GetChannelMessagesResponse get_channel_messages_response = 75;
UpdateContacts update_contacts = 76;
UpdateInviteInfo update_invite_info = 77;
GetUsers get_users = 77;
FuzzySearchUsers fuzzy_search_users = 78;
UsersResponse users_response = 79;
RequestContact request_contact = 80;
RespondToContactRequest respond_to_contact_request = 81;
RemoveContact remove_contact = 82;
GetUsers get_users = 78;
FuzzySearchUsers fuzzy_search_users = 79;
UsersResponse users_response = 80;
RequestContact request_contact = 81;
RespondToContactRequest respond_to_contact_request = 82;
RemoveContact remove_contact = 83;
Follow follow = 83;
FollowResponse follow_response = 84;
UpdateFollowers update_followers = 85;
Unfollow unfollow = 86;
Follow follow = 84;
FollowResponse follow_response = 85;
UpdateFollowers update_followers = 86;
Unfollow unfollow = 87;
}
}
@@ -634,6 +635,11 @@ message UpdateContacts {
repeated uint64 remove_outgoing_requests = 6;
}
message UpdateInviteInfo {
string url = 1;
uint32 count = 2;
}
message IncomingContactRequest {
uint64 requester_id = 1;
bool should_notify = 2;
+1
View File
@@ -155,6 +155,7 @@ messages!(
(UpdateContacts, Foreground),
(UpdateDiagnosticSummary, Foreground),
(UpdateFollowers, Foreground),
(UpdateInviteInfo, Foreground),
(UpdateLanguageServer, Foreground),
(UpdateWorktree, Foreground),
);
+19
View File
@@ -262,6 +262,16 @@ pub struct ContactsPanel {
pub disabled_contact_button: IconButton,
pub tree_branch: Interactive<TreeBranch>,
pub section_icon_size: f32,
pub invite_row: Interactive<ContainedLabel>,
}
#[derive(Deserialize, Default)]
pub struct InviteLink {
#[serde(flatten)]
pub container: ContainerStyle,
#[serde(flatten)]
pub label: LabelStyle,
pub icon: Icon,
}
#[derive(Deserialize, Default, Clone, Copy)]
@@ -279,6 +289,15 @@ pub struct ContactFinder {
pub disabled_contact_button: IconButton,
}
#[derive(Deserialize, Default)]
pub struct Icon {
#[serde(flatten)]
pub container: ContainerStyle,
pub color: Color,
pub width: f32,
pub path: String,
}
#[derive(Deserialize, Default)]
pub struct IconButton {
#[serde(flatten)]