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:
co-authored by
Antonio Scandurra
parent
a3bbabaaac
commit
eedb8ba59f
@@ -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(())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -155,6 +155,7 @@ messages!(
|
||||
(UpdateContacts, Foreground),
|
||||
(UpdateDiagnosticSummary, Foreground),
|
||||
(UpdateFollowers, Foreground),
|
||||
(UpdateInviteInfo, Foreground),
|
||||
(UpdateLanguageServer, Foreground),
|
||||
(UpdateWorktree, Foreground),
|
||||
);
|
||||
|
||||
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user