mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-28 17:03:26 +03:00
Allow fuzzy-search for potential contacts in the contacts panel
Co-authored-by: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
35fea43089
commit
ea81737a88
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -935,6 +935,7 @@ dependencies = [
|
|||||||
"postage",
|
"postage",
|
||||||
"settings",
|
"settings",
|
||||||
"theme",
|
"theme",
|
||||||
|
"util",
|
||||||
"workspace",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1240,14 +1240,14 @@
|
|||||||
"top": 7
|
"top": 7
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"host_row_height": 28,
|
"row_height": 28,
|
||||||
"tree_branch_color": "#655f6d",
|
"tree_branch_color": "#655f6d",
|
||||||
"tree_branch_width": 1,
|
"tree_branch_width": 1,
|
||||||
"host_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"host_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
"family": "Zed Mono",
|
||||||
"color": "#e2dfe7",
|
"color": "#e2dfe7",
|
||||||
"size": 14,
|
"size": 14,
|
||||||
@ -1255,6 +1255,11 @@
|
|||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"header": {
|
||||||
|
"family": "Zed Mono",
|
||||||
|
"color": "#8b8792",
|
||||||
|
"size": 14
|
||||||
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"guest_avatar_spacing": 4,
|
"guest_avatar_spacing": 4,
|
||||||
"height": 24,
|
"height": 24,
|
||||||
|
@ -1240,14 +1240,14 @@
|
|||||||
"top": 7
|
"top": 7
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"host_row_height": 28,
|
"row_height": 28,
|
||||||
"tree_branch_color": "#7e7887",
|
"tree_branch_color": "#7e7887",
|
||||||
"tree_branch_width": 1,
|
"tree_branch_width": 1,
|
||||||
"host_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"host_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
"family": "Zed Mono",
|
||||||
"color": "#26232a",
|
"color": "#26232a",
|
||||||
"size": 14,
|
"size": 14,
|
||||||
@ -1255,6 +1255,11 @@
|
|||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"header": {
|
||||||
|
"family": "Zed Mono",
|
||||||
|
"color": "#585260",
|
||||||
|
"size": 14
|
||||||
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"guest_avatar_spacing": 4,
|
"guest_avatar_spacing": 4,
|
||||||
"height": 24,
|
"height": 24,
|
||||||
|
@ -1240,14 +1240,14 @@
|
|||||||
"top": 7
|
"top": 7
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"host_row_height": 28,
|
"row_height": 28,
|
||||||
"tree_branch_color": "#404040",
|
"tree_branch_color": "#404040",
|
||||||
"tree_branch_width": 1,
|
"tree_branch_width": 1,
|
||||||
"host_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"host_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
"family": "Zed Mono",
|
||||||
"color": "#f1f1f1",
|
"color": "#f1f1f1",
|
||||||
"size": 14,
|
"size": 14,
|
||||||
@ -1255,6 +1255,11 @@
|
|||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"header": {
|
||||||
|
"family": "Zed Mono",
|
||||||
|
"color": "#9c9c9c",
|
||||||
|
"size": 14
|
||||||
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"guest_avatar_spacing": 4,
|
"guest_avatar_spacing": 4,
|
||||||
"height": 24,
|
"height": 24,
|
||||||
|
@ -1240,14 +1240,14 @@
|
|||||||
"top": 7
|
"top": 7
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"host_row_height": 28,
|
"row_height": 28,
|
||||||
"tree_branch_color": "#e3e3e3",
|
"tree_branch_color": "#e3e3e3",
|
||||||
"tree_branch_width": 1,
|
"tree_branch_width": 1,
|
||||||
"host_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"host_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
"family": "Zed Mono",
|
||||||
"color": "#2b2b2b",
|
"color": "#2b2b2b",
|
||||||
"size": 14,
|
"size": 14,
|
||||||
@ -1255,6 +1255,11 @@
|
|||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"header": {
|
||||||
|
"family": "Zed Mono",
|
||||||
|
"color": "#474747",
|
||||||
|
"size": 14
|
||||||
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"guest_avatar_spacing": 4,
|
"guest_avatar_spacing": 4,
|
||||||
"height": 24,
|
"height": 24,
|
||||||
|
@ -1240,14 +1240,14 @@
|
|||||||
"top": 7
|
"top": 7
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"host_row_height": 28,
|
"row_height": 28,
|
||||||
"tree_branch_color": "#657b83",
|
"tree_branch_color": "#657b83",
|
||||||
"tree_branch_width": 1,
|
"tree_branch_width": 1,
|
||||||
"host_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"host_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
"family": "Zed Mono",
|
||||||
"color": "#eee8d5",
|
"color": "#eee8d5",
|
||||||
"size": 14,
|
"size": 14,
|
||||||
@ -1255,6 +1255,11 @@
|
|||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"header": {
|
||||||
|
"family": "Zed Mono",
|
||||||
|
"color": "#93a1a1",
|
||||||
|
"size": 14
|
||||||
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"guest_avatar_spacing": 4,
|
"guest_avatar_spacing": 4,
|
||||||
"height": 24,
|
"height": 24,
|
||||||
|
@ -1240,14 +1240,14 @@
|
|||||||
"top": 7
|
"top": 7
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"host_row_height": 28,
|
"row_height": 28,
|
||||||
"tree_branch_color": "#839496",
|
"tree_branch_color": "#839496",
|
||||||
"tree_branch_width": 1,
|
"tree_branch_width": 1,
|
||||||
"host_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"host_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
"family": "Zed Mono",
|
||||||
"color": "#073642",
|
"color": "#073642",
|
||||||
"size": 14,
|
"size": 14,
|
||||||
@ -1255,6 +1255,11 @@
|
|||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"header": {
|
||||||
|
"family": "Zed Mono",
|
||||||
|
"color": "#586e75",
|
||||||
|
"size": 14
|
||||||
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"guest_avatar_spacing": 4,
|
"guest_avatar_spacing": 4,
|
||||||
"height": 24,
|
"height": 24,
|
||||||
|
@ -1240,14 +1240,14 @@
|
|||||||
"top": 7
|
"top": 7
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"host_row_height": 28,
|
"row_height": 28,
|
||||||
"tree_branch_color": "#6b7394",
|
"tree_branch_color": "#6b7394",
|
||||||
"tree_branch_width": 1,
|
"tree_branch_width": 1,
|
||||||
"host_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"host_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
"family": "Zed Mono",
|
||||||
"color": "#dfe2f1",
|
"color": "#dfe2f1",
|
||||||
"size": 14,
|
"size": 14,
|
||||||
@ -1255,6 +1255,11 @@
|
|||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"header": {
|
||||||
|
"family": "Zed Mono",
|
||||||
|
"color": "#979db4",
|
||||||
|
"size": 14
|
||||||
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"guest_avatar_spacing": 4,
|
"guest_avatar_spacing": 4,
|
||||||
"height": 24,
|
"height": 24,
|
||||||
|
@ -1240,14 +1240,14 @@
|
|||||||
"top": 7
|
"top": 7
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"host_row_height": 28,
|
"row_height": 28,
|
||||||
"tree_branch_color": "#898ea4",
|
"tree_branch_color": "#898ea4",
|
||||||
"tree_branch_width": 1,
|
"tree_branch_width": 1,
|
||||||
"host_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"host_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
"family": "Zed Mono",
|
||||||
"color": "#293256",
|
"color": "#293256",
|
||||||
"size": 14,
|
"size": 14,
|
||||||
@ -1255,6 +1255,11 @@
|
|||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"header": {
|
||||||
|
"family": "Zed Mono",
|
||||||
|
"color": "#5e6687",
|
||||||
|
"size": 14
|
||||||
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"guest_avatar_spacing": 4,
|
"guest_avatar_spacing": 4,
|
||||||
"height": 24,
|
"height": 24,
|
||||||
|
@ -500,7 +500,7 @@ async fn messages_from_proto(
|
|||||||
.collect();
|
.collect();
|
||||||
user_store
|
user_store
|
||||||
.update(cx, |user_store, cx| {
|
.update(cx, |user_store, cx| {
|
||||||
user_store.load_users(unique_user_ids, cx)
|
user_store.get_users(unique_user_ids, cx)
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -639,7 +639,7 @@ mod tests {
|
|||||||
server
|
server
|
||||||
.respond(
|
.respond(
|
||||||
get_users.receipt(),
|
get_users.receipt(),
|
||||||
proto::GetUsersResponse {
|
proto::UsersResponse {
|
||||||
users: vec![proto::User {
|
users: vec![proto::User {
|
||||||
id: 5,
|
id: 5,
|
||||||
github_login: "nathansobo".into(),
|
github_login: "nathansobo".into(),
|
||||||
@ -690,7 +690,7 @@ mod tests {
|
|||||||
server
|
server
|
||||||
.respond(
|
.respond(
|
||||||
get_users.receipt(),
|
get_users.receipt(),
|
||||||
proto::GetUsersResponse {
|
proto::UsersResponse {
|
||||||
users: vec![proto::User {
|
users: vec![proto::User {
|
||||||
id: 6,
|
id: 6,
|
||||||
github_login: "maxbrunsfeld".into(),
|
github_login: "maxbrunsfeld".into(),
|
||||||
@ -738,7 +738,7 @@ mod tests {
|
|||||||
server
|
server
|
||||||
.respond(
|
.respond(
|
||||||
get_users.receipt(),
|
get_users.receipt(),
|
||||||
proto::GetUsersResponse {
|
proto::UsersResponse {
|
||||||
users: vec![proto::User {
|
users: vec![proto::User {
|
||||||
id: 7,
|
id: 7,
|
||||||
github_login: "as-cii".into(),
|
github_login: "as-cii".into(),
|
||||||
|
@ -3,6 +3,7 @@ use anyhow::{anyhow, Result};
|
|||||||
use futures::{future, AsyncReadExt};
|
use futures::{future, AsyncReadExt};
|
||||||
use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task};
|
use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task};
|
||||||
use postage::{prelude::Stream, sink::Sink, watch};
|
use postage::{prelude::Stream, sink::Sink, watch};
|
||||||
|
use rpc::proto::{RequestMessage, UsersResponse};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
@ -121,7 +122,7 @@ impl UserStore {
|
|||||||
user_ids.extend(contact.projects.iter().flat_map(|w| &w.guests).copied());
|
user_ids.extend(contact.projects.iter().flat_map(|w| &w.guests).copied());
|
||||||
}
|
}
|
||||||
|
|
||||||
let load_users = self.load_users(user_ids.into_iter().collect(), cx);
|
let load_users = self.get_users(user_ids.into_iter().collect(), cx);
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
load_users.await?;
|
load_users.await?;
|
||||||
|
|
||||||
@ -144,37 +145,27 @@ impl UserStore {
|
|||||||
&self.contacts
|
&self.contacts
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_users(
|
pub fn has_contact(&self, user: &Arc<User>) -> bool {
|
||||||
|
self.contacts
|
||||||
|
.binary_search_by_key(&&user.github_login, |contact| &contact.user.github_login)
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_users(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut user_ids: Vec<u64>,
|
mut user_ids: Vec<u64>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<Vec<Arc<User>>>> {
|
||||||
let rpc = self.client.clone();
|
|
||||||
let http = self.http.clone();
|
|
||||||
user_ids.retain(|id| !self.users.contains_key(id));
|
user_ids.retain(|id| !self.users.contains_key(id));
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
self.load_users(proto::GetUsers { user_ids }, cx)
|
||||||
if let Some(rpc) = rpc.upgrade() {
|
}
|
||||||
if !user_ids.is_empty() {
|
|
||||||
let response = rpc.request(proto::GetUsers { user_ids }).await?;
|
|
||||||
let new_users = future::join_all(
|
|
||||||
response
|
|
||||||
.users
|
|
||||||
.into_iter()
|
|
||||||
.map(|user| User::new(user, http.as_ref())),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
pub fn fuzzy_search_users(
|
||||||
this.update(&mut cx, |this, _| {
|
&mut self,
|
||||||
for user in new_users {
|
query: String,
|
||||||
this.users.insert(user.id, Arc::new(user));
|
cx: &mut ModelContext<Self>,
|
||||||
}
|
) -> Task<Result<Vec<Arc<User>>>> {
|
||||||
});
|
self.load_users(proto::FuzzySearchUsers { query }, cx)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_user(
|
pub fn fetch_user(
|
||||||
@ -186,7 +177,7 @@ impl UserStore {
|
|||||||
return cx.foreground().spawn(async move { Ok(user) });
|
return cx.foreground().spawn(async move { Ok(user) });
|
||||||
}
|
}
|
||||||
|
|
||||||
let load_users = self.load_users(vec![user_id], cx);
|
let load_users = self.get_users(vec![user_id], cx);
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
load_users.await?;
|
load_users.await?;
|
||||||
this.update(&mut cx, |this, _| {
|
this.update(&mut cx, |this, _| {
|
||||||
@ -205,15 +196,47 @@ impl UserStore {
|
|||||||
pub fn watch_current_user(&self) -> watch::Receiver<Option<Arc<User>>> {
|
pub fn watch_current_user(&self) -> watch::Receiver<Option<Arc<User>>> {
|
||||||
self.current_user.clone()
|
self.current_user.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_users(
|
||||||
|
&mut self,
|
||||||
|
request: impl RequestMessage<Response = UsersResponse>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Task<Result<Vec<Arc<User>>>> {
|
||||||
|
let client = self.client.clone();
|
||||||
|
let http = self.http.clone();
|
||||||
|
cx.spawn_weak(|this, mut cx| async move {
|
||||||
|
if let Some(rpc) = client.upgrade() {
|
||||||
|
let response = rpc.request(request).await?;
|
||||||
|
let users = future::join_all(
|
||||||
|
response
|
||||||
|
.users
|
||||||
|
.into_iter()
|
||||||
|
.map(|user| User::new(user, http.as_ref())),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Some(this) = this.upgrade(&cx) {
|
||||||
|
this.update(&mut cx, |this, _| {
|
||||||
|
for user in &users {
|
||||||
|
this.users.insert(user.id, user.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(users)
|
||||||
|
} else {
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
async fn new(message: proto::User, http: &dyn HttpClient) -> Self {
|
async fn new(message: proto::User, http: &dyn HttpClient) -> Arc<Self> {
|
||||||
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: fetch_avatar(http, &message.avatar_url).warn_on_err().await,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +136,7 @@ impl Server {
|
|||||||
.add_request_handler(Server::save_buffer)
|
.add_request_handler(Server::save_buffer)
|
||||||
.add_request_handler(Server::get_channels)
|
.add_request_handler(Server::get_channels)
|
||||||
.add_request_handler(Server::get_users)
|
.add_request_handler(Server::get_users)
|
||||||
|
.add_request_handler(Server::fuzzy_search_users)
|
||||||
.add_request_handler(Server::join_channel)
|
.add_request_handler(Server::join_channel)
|
||||||
.add_message_handler(Server::leave_channel)
|
.add_message_handler(Server::leave_channel)
|
||||||
.add_request_handler(Server::send_channel_message)
|
.add_request_handler(Server::send_channel_message)
|
||||||
@ -842,7 +843,7 @@ impl Server {
|
|||||||
async fn get_users(
|
async fn get_users(
|
||||||
self: Arc<Server>,
|
self: Arc<Server>,
|
||||||
request: TypedEnvelope<proto::GetUsers>,
|
request: TypedEnvelope<proto::GetUsers>,
|
||||||
) -> Result<proto::GetUsersResponse> {
|
) -> Result<proto::UsersResponse> {
|
||||||
let user_ids = request
|
let user_ids = request
|
||||||
.payload
|
.payload
|
||||||
.user_ids
|
.user_ids
|
||||||
@ -861,7 +862,33 @@ impl Server {
|
|||||||
github_login: user.github_login,
|
github_login: user.github_login,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Ok(proto::GetUsersResponse { users })
|
Ok(proto::UsersResponse { users })
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fuzzy_search_users(
|
||||||
|
self: Arc<Server>,
|
||||||
|
request: TypedEnvelope<proto::FuzzySearchUsers>,
|
||||||
|
) -> Result<proto::UsersResponse> {
|
||||||
|
let query = request.payload.query;
|
||||||
|
let db = &self.app_state.db;
|
||||||
|
let users = match query.len() {
|
||||||
|
0 => vec![],
|
||||||
|
1 | 2 => db
|
||||||
|
.get_user_by_github_login(&query)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
_ => db.fuzzy_search_users(&query, 10).await?,
|
||||||
|
};
|
||||||
|
let users = users
|
||||||
|
.into_iter()
|
||||||
|
.map(|user| proto::User {
|
||||||
|
id: user.id.to_proto(),
|
||||||
|
avatar_url: format!("https://github.com/{}.png?size=128", user.github_login),
|
||||||
|
github_login: user.github_login,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(proto::UsersResponse { users })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(self, state, user_ids))]
|
#[instrument(skip(self, state, user_ids))]
|
||||||
|
@ -13,5 +13,6 @@ editor = { path = "../editor" }
|
|||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
|
util = { path = "../util" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||||
|
@ -1,71 +1,124 @@
|
|||||||
use client::{Contact, UserStore};
|
use client::{Contact, User, UserStore};
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::*,
|
elements::*,
|
||||||
geometry::{rect::RectF, vector::vec2f},
|
geometry::{rect::RectF, vector::vec2f},
|
||||||
platform::CursorStyle,
|
platform::CursorStyle,
|
||||||
Element, ElementBox, Entity, LayoutContext, ModelHandle, RenderContext, Subscription, View,
|
Element, ElementBox, Entity, LayoutContext, ModelHandle, RenderContext, Subscription, Task,
|
||||||
ViewContext, ViewHandle,
|
View, ViewContext, ViewHandle,
|
||||||
};
|
};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use util::ResultExt;
|
||||||
use workspace::{AppState, JoinProject};
|
use workspace::{AppState, JoinProject};
|
||||||
|
|
||||||
pub struct ContactsPanel {
|
pub struct ContactsPanel {
|
||||||
contacts: ListState,
|
list_state: ListState,
|
||||||
|
potential_contacts: Vec<Arc<User>>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: ModelHandle<UserStore>,
|
||||||
|
contacts_search_task: Option<Task<Option<()>>>,
|
||||||
user_query_editor: ViewHandle<Editor>,
|
user_query_editor: ViewHandle<Editor>,
|
||||||
_maintain_contacts: Subscription,
|
_maintain_contacts: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContactsPanel {
|
impl ContactsPanel {
|
||||||
pub fn new(app_state: Arc<AppState>, cx: &mut ViewContext<Self>) -> Self {
|
pub fn new(app_state: Arc<AppState>, cx: &mut ViewContext<Self>) -> Self {
|
||||||
|
let user_query_editor = cx.add_view(|cx| {
|
||||||
|
Editor::single_line(
|
||||||
|
Some(|theme| theme.contacts_panel.user_query_editor.clone()),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.subscribe(&user_query_editor, |this, _, event, cx| {
|
||||||
|
if let editor::Event::BufferEdited = event {
|
||||||
|
this.user_query_changed(cx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
contacts: ListState::new(
|
list_state: ListState::new(
|
||||||
app_state.user_store.read(cx).contacts().len(),
|
1 + app_state.user_store.read(cx).contacts().len(), // Add 1 for the "Contacts" header
|
||||||
Orientation::Top,
|
Orientation::Top,
|
||||||
1000.,
|
1000.,
|
||||||
{
|
{
|
||||||
|
let this = cx.weak_handle();
|
||||||
let app_state = app_state.clone();
|
let app_state = app_state.clone();
|
||||||
move |ix, cx| {
|
move |ix, cx| {
|
||||||
let user_store = app_state.user_store.read(cx);
|
let this = this.upgrade(cx).unwrap();
|
||||||
|
let this = this.read(cx);
|
||||||
|
let user_store = this.user_store.read(cx);
|
||||||
let contacts = user_store.contacts().clone();
|
let contacts = user_store.contacts().clone();
|
||||||
let current_user_id = user_store.current_user().map(|user| user.id);
|
let current_user_id = user_store.current_user().map(|user| user.id);
|
||||||
Self::render_collaborator(
|
let theme = cx.global::<Settings>().theme.clone();
|
||||||
&contacts[ix],
|
let theme = &theme.contacts_panel;
|
||||||
current_user_id,
|
|
||||||
app_state.clone(),
|
if ix == 0 {
|
||||||
cx,
|
Label::new("contacts".to_string(), theme.header.text.clone())
|
||||||
)
|
.contained()
|
||||||
|
.with_style(theme.header.container)
|
||||||
|
.aligned()
|
||||||
|
.left()
|
||||||
|
.constrained()
|
||||||
|
.with_height(theme.row_height)
|
||||||
|
.boxed()
|
||||||
|
} else if ix < contacts.len() + 1 {
|
||||||
|
let contact_ix = ix - 1;
|
||||||
|
Self::render_contact(
|
||||||
|
&contacts[contact_ix],
|
||||||
|
current_user_id,
|
||||||
|
app_state.clone(),
|
||||||
|
theme,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
} else if ix == contacts.len() + 1 {
|
||||||
|
Label::new("add contacts".to_string(), theme.header.text.clone())
|
||||||
|
.contained()
|
||||||
|
.with_style(theme.header.container)
|
||||||
|
.aligned()
|
||||||
|
.left()
|
||||||
|
.constrained()
|
||||||
|
.with_height(theme.row_height)
|
||||||
|
.boxed()
|
||||||
|
} else {
|
||||||
|
let potential_contact_ix = ix - 2 - contacts.len();
|
||||||
|
Self::render_potential_contact(
|
||||||
|
&this.potential_contacts[potential_contact_ix],
|
||||||
|
theme,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
user_query_editor: cx.add_view(|cx| {
|
potential_contacts: Default::default(),
|
||||||
Editor::single_line(
|
user_query_editor,
|
||||||
Some(|theme| theme.contacts_panel.user_query_editor.clone()),
|
_maintain_contacts: cx.observe(&app_state.user_store, |this, _, cx| {
|
||||||
cx,
|
this.update_contacts(cx)
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
_maintain_contacts: cx.observe(&app_state.user_store, Self::update_contacts),
|
contacts_search_task: None,
|
||||||
user_store: app_state.user_store.clone(),
|
user_store: app_state.user_store.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_contacts(&mut self, _: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) {
|
fn update_contacts(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.contacts
|
let mut list_len = 1 + self.user_store.read(cx).contacts().len();
|
||||||
.reset(self.user_store.read(cx).contacts().len());
|
if !self.potential_contacts.is_empty() {
|
||||||
|
list_len += 1 + self.potential_contacts.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.list_state.reset(list_len);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_collaborator(
|
fn render_contact(
|
||||||
collaborator: &Contact,
|
contact: &Contact,
|
||||||
current_user_id: Option<u64>,
|
current_user_id: Option<u64>,
|
||||||
app_state: Arc<AppState>,
|
app_state: Arc<AppState>,
|
||||||
|
theme: &theme::ContactsPanel,
|
||||||
cx: &mut LayoutContext,
|
cx: &mut LayoutContext,
|
||||||
) -> ElementBox {
|
) -> ElementBox {
|
||||||
let theme = cx.global::<Settings>().theme.clone();
|
let project_count = contact.projects.len();
|
||||||
let theme = &theme.contacts_panel;
|
|
||||||
let project_count = collaborator.projects.len();
|
|
||||||
let font_cache = cx.font_cache();
|
let font_cache = cx.font_cache();
|
||||||
let line_height = theme.unshared_project.name.text.line_height(font_cache);
|
let line_height = theme.unshared_project.name.text.line_height(font_cache);
|
||||||
let cap_height = theme.unshared_project.name.text.cap_height(font_cache);
|
let cap_height = theme.unshared_project.name.text.cap_height(font_cache);
|
||||||
@ -74,162 +127,202 @@ impl ContactsPanel {
|
|||||||
let tree_branch_width = theme.tree_branch_width;
|
let tree_branch_width = theme.tree_branch_width;
|
||||||
let tree_branch_color = theme.tree_branch_color;
|
let tree_branch_color = theme.tree_branch_color;
|
||||||
let host_avatar_height = theme
|
let host_avatar_height = theme
|
||||||
.host_avatar
|
.contact_avatar
|
||||||
.width
|
.width
|
||||||
.or(theme.host_avatar.height)
|
.or(theme.contact_avatar.height)
|
||||||
.unwrap_or(0.);
|
.unwrap_or(0.);
|
||||||
|
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.with_child(
|
.with_child(
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_children(collaborator.user.avatar.clone().map(|avatar| {
|
.with_children(contact.user.avatar.clone().map(|avatar| {
|
||||||
Image::new(avatar)
|
Image::new(avatar)
|
||||||
.with_style(theme.host_avatar)
|
.with_style(theme.contact_avatar)
|
||||||
.aligned()
|
.aligned()
|
||||||
.left()
|
.left()
|
||||||
.boxed()
|
.boxed()
|
||||||
}))
|
}))
|
||||||
.with_child(
|
.with_child(
|
||||||
Label::new(
|
Label::new(
|
||||||
collaborator.user.github_login.clone(),
|
contact.user.github_login.clone(),
|
||||||
theme.host_username.text.clone(),
|
theme.contact_username.text.clone(),
|
||||||
)
|
)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(theme.host_username.container)
|
.with_style(theme.contact_username.container)
|
||||||
.aligned()
|
.aligned()
|
||||||
.left()
|
.left()
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_height(theme.host_row_height)
|
.with_height(theme.row_height)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.with_children(
|
.with_children(contact.projects.iter().enumerate().map(|(ix, project)| {
|
||||||
collaborator
|
let project_id = project.id;
|
||||||
.projects
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(ix, project)| {
|
|
||||||
let project_id = project.id;
|
|
||||||
|
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(
|
.with_child(
|
||||||
Canvas::new(move |bounds, _, cx| {
|
Canvas::new(move |bounds, _, cx| {
|
||||||
let start_x = bounds.min_x() + (bounds.width() / 2.)
|
let start_x =
|
||||||
- (tree_branch_width / 2.);
|
bounds.min_x() + (bounds.width() / 2.) - (tree_branch_width / 2.);
|
||||||
let end_x = bounds.max_x();
|
let end_x = bounds.max_x();
|
||||||
let start_y = bounds.min_y();
|
let start_y = bounds.min_y();
|
||||||
let end_y =
|
let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
|
||||||
bounds.min_y() + baseline_offset - (cap_height / 2.);
|
|
||||||
|
|
||||||
cx.scene.push_quad(gpui::Quad {
|
cx.scene.push_quad(gpui::Quad {
|
||||||
bounds: RectF::from_points(
|
bounds: RectF::from_points(
|
||||||
vec2f(start_x, start_y),
|
vec2f(start_x, start_y),
|
||||||
vec2f(
|
vec2f(
|
||||||
start_x + tree_branch_width,
|
start_x + tree_branch_width,
|
||||||
if ix + 1 == project_count {
|
if ix + 1 == project_count {
|
||||||
end_y
|
end_y
|
||||||
} else {
|
} else {
|
||||||
bounds.max_y()
|
bounds.max_y()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
background: Some(tree_branch_color),
|
background: Some(tree_branch_color),
|
||||||
border: gpui::Border::default(),
|
border: gpui::Border::default(),
|
||||||
corner_radius: 0.,
|
corner_radius: 0.,
|
||||||
});
|
});
|
||||||
cx.scene.push_quad(gpui::Quad {
|
cx.scene.push_quad(gpui::Quad {
|
||||||
bounds: RectF::from_points(
|
bounds: RectF::from_points(
|
||||||
vec2f(start_x, end_y),
|
vec2f(start_x, end_y),
|
||||||
vec2f(end_x, end_y + tree_branch_width),
|
vec2f(end_x, end_y + tree_branch_width),
|
||||||
),
|
),
|
||||||
background: Some(tree_branch_color),
|
background: Some(tree_branch_color),
|
||||||
border: gpui::Border::default(),
|
border: gpui::Border::default(),
|
||||||
corner_radius: 0.,
|
corner_radius: 0.,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_width(host_avatar_height)
|
.with_width(host_avatar_height)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.with_child({
|
.with_child({
|
||||||
let is_host = Some(collaborator.user.id) == current_user_id;
|
let is_host = Some(contact.user.id) == current_user_id;
|
||||||
let is_guest = !is_host
|
let is_guest = !is_host
|
||||||
&& project
|
&& project
|
||||||
.guests
|
.guests
|
||||||
.iter()
|
.iter()
|
||||||
.any(|guest| Some(guest.id) == current_user_id);
|
.any(|guest| Some(guest.id) == current_user_id);
|
||||||
let is_shared = project.is_shared;
|
let is_shared = project.is_shared;
|
||||||
let app_state = app_state.clone();
|
let app_state = app_state.clone();
|
||||||
|
|
||||||
MouseEventHandler::new::<ContactsPanel, _, _>(
|
MouseEventHandler::new::<ContactsPanel, _, _>(
|
||||||
project_id as usize,
|
project_id as usize,
|
||||||
cx,
|
cx,
|
||||||
|mouse_state, _| {
|
|mouse_state, _| {
|
||||||
let style = match (project.is_shared, mouse_state.hovered) {
|
let style = match (project.is_shared, mouse_state.hovered) {
|
||||||
(false, false) => &theme.unshared_project,
|
(false, false) => &theme.unshared_project,
|
||||||
(false, true) => &theme.hovered_unshared_project,
|
(false, true) => &theme.hovered_unshared_project,
|
||||||
(true, false) => &theme.shared_project,
|
(true, false) => &theme.shared_project,
|
||||||
(true, true) => &theme.hovered_shared_project,
|
(true, true) => &theme.hovered_shared_project,
|
||||||
};
|
};
|
||||||
|
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(
|
.with_child(
|
||||||
Label::new(
|
Label::new(
|
||||||
project.worktree_root_names.join(", "),
|
project.worktree_root_names.join(", "),
|
||||||
style.name.text.clone(),
|
style.name.text.clone(),
|
||||||
)
|
)
|
||||||
.aligned()
|
.aligned()
|
||||||
.left()
|
.left()
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(style.name.container)
|
.with_style(style.name.container)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.with_children(project.guests.iter().filter_map(
|
.with_children(project.guests.iter().filter_map(
|
||||||
|participant| {
|
|participant| {
|
||||||
participant.avatar.clone().map(|avatar| {
|
participant.avatar.clone().map(|avatar| {
|
||||||
Image::new(avatar)
|
Image::new(avatar)
|
||||||
.with_style(style.guest_avatar)
|
.with_style(style.guest_avatar)
|
||||||
.aligned()
|
.aligned()
|
||||||
.left()
|
.left()
|
||||||
.contained()
|
.contained()
|
||||||
.with_margin_right(
|
.with_margin_right(style.guest_avatar_spacing)
|
||||||
style.guest_avatar_spacing,
|
.boxed()
|
||||||
)
|
})
|
||||||
.boxed()
|
},
|
||||||
})
|
))
|
||||||
},
|
.contained()
|
||||||
))
|
.with_style(style.container)
|
||||||
.contained()
|
.constrained()
|
||||||
.with_style(style.container)
|
.with_height(style.height)
|
||||||
.constrained()
|
.boxed()
|
||||||
.with_height(style.height)
|
},
|
||||||
.boxed()
|
)
|
||||||
},
|
.with_cursor_style(if is_host || is_shared {
|
||||||
)
|
CursorStyle::PointingHand
|
||||||
.with_cursor_style(if is_host || is_shared {
|
} else {
|
||||||
CursorStyle::PointingHand
|
CursorStyle::Arrow
|
||||||
} else {
|
})
|
||||||
CursorStyle::Arrow
|
.on_click(move |_, cx| {
|
||||||
})
|
if !is_host && !is_guest {
|
||||||
.on_click(move |_, cx| {
|
cx.dispatch_global_action(JoinProject {
|
||||||
if !is_host && !is_guest {
|
project_id,
|
||||||
cx.dispatch_global_action(JoinProject {
|
app_state: app_state.clone(),
|
||||||
project_id,
|
});
|
||||||
app_state: app_state.clone(),
|
}
|
||||||
});
|
})
|
||||||
}
|
.flex(1., true)
|
||||||
})
|
.boxed()
|
||||||
.flex(1., true)
|
})
|
||||||
.boxed()
|
.constrained()
|
||||||
})
|
.with_height(theme.unshared_project.height)
|
||||||
.constrained()
|
.boxed()
|
||||||
.with_height(theme.unshared_project.height)
|
}))
|
||||||
.boxed()
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_potential_contact(contact: &User, theme: &theme::ContactsPanel) -> ElementBox {
|
||||||
|
Flex::row()
|
||||||
|
.with_children(contact.avatar.clone().map(|avatar| {
|
||||||
|
Image::new(avatar)
|
||||||
|
.with_style(theme.contact_avatar)
|
||||||
|
.aligned()
|
||||||
|
.left()
|
||||||
|
.boxed()
|
||||||
|
}))
|
||||||
|
.with_child(
|
||||||
|
Label::new(
|
||||||
|
contact.github_login.clone(),
|
||||||
|
theme.contact_username.text.clone(),
|
||||||
|
)
|
||||||
|
.contained()
|
||||||
|
.with_style(theme.contact_username.container)
|
||||||
|
.aligned()
|
||||||
|
.left()
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.constrained()
|
||||||
|
.with_height(theme.row_height)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_query_changed(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
let query = self.user_query_editor.read(cx).text(cx);
|
||||||
|
if query.is_empty() {
|
||||||
|
self.potential_contacts.clear();
|
||||||
|
self.update_contacts(cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let search = self
|
||||||
|
.user_store
|
||||||
|
.update(cx, |store, cx| store.fuzzy_search_users(query, cx));
|
||||||
|
self.contacts_search_task = Some(cx.spawn(|this, mut cx| async move {
|
||||||
|
let users = search.await.log_err()?;
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
let user_store = this.user_store.read(cx);
|
||||||
|
this.potential_contacts = users;
|
||||||
|
this.potential_contacts
|
||||||
|
.retain(|user| !user_store.has_contact(&user));
|
||||||
|
this.update_contacts(cx);
|
||||||
|
});
|
||||||
|
None
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Event {}
|
pub enum Event {}
|
||||||
@ -252,7 +345,7 @@ impl View for ContactsPanel {
|
|||||||
.with_style(theme.user_query_editor.container)
|
.with_style(theme.user_query_editor.container)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.with_child(List::new(self.contacts.clone()).flex(1., false).boxed())
|
.with_child(List::new(self.list_state.clone()).flex(1., false).boxed())
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.with_style(theme.container)
|
.with_style(theme.container)
|
||||||
|
@ -443,7 +443,7 @@ impl Project {
|
|||||||
.map(|peer| peer.user_id)
|
.map(|peer| peer.user_id)
|
||||||
.collect();
|
.collect();
|
||||||
user_store
|
user_store
|
||||||
.update(cx, |user_store, cx| user_store.load_users(user_ids, cx))
|
.update(cx, |user_store, cx| user_store.get_users(user_ids, cx))
|
||||||
.await?;
|
.await?;
|
||||||
let mut collaborators = HashMap::default();
|
let mut collaborators = HashMap::default();
|
||||||
for message in response.collaborators {
|
for message in response.collaborators {
|
||||||
|
@ -87,12 +87,13 @@ message Envelope {
|
|||||||
UpdateContacts update_contacts = 75;
|
UpdateContacts update_contacts = 75;
|
||||||
|
|
||||||
GetUsers get_users = 76;
|
GetUsers get_users = 76;
|
||||||
GetUsersResponse get_users_response = 77;
|
FuzzySearchUsers fuzzy_search_users = 77;
|
||||||
|
UsersResponse users_response = 78;
|
||||||
|
|
||||||
Follow follow = 78;
|
Follow follow = 79;
|
||||||
FollowResponse follow_response = 79;
|
FollowResponse follow_response = 80;
|
||||||
UpdateFollowers update_followers = 80;
|
UpdateFollowers update_followers = 81;
|
||||||
Unfollow unfollow = 81;
|
Unfollow unfollow = 82;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,7 +539,11 @@ message GetUsers {
|
|||||||
repeated uint64 user_ids = 1;
|
repeated uint64 user_ids = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetUsersResponse {
|
message FuzzySearchUsers {
|
||||||
|
string query = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UsersResponse {
|
||||||
repeated User users = 1;
|
repeated User users = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +155,7 @@ messages!(
|
|||||||
(FollowResponse, Foreground),
|
(FollowResponse, Foreground),
|
||||||
(FormatBuffers, Foreground),
|
(FormatBuffers, Foreground),
|
||||||
(FormatBuffersResponse, Foreground),
|
(FormatBuffersResponse, Foreground),
|
||||||
|
(FuzzySearchUsers, Foreground),
|
||||||
(GetChannelMessages, Foreground),
|
(GetChannelMessages, Foreground),
|
||||||
(GetChannelMessagesResponse, Foreground),
|
(GetChannelMessagesResponse, Foreground),
|
||||||
(GetChannels, Foreground),
|
(GetChannels, Foreground),
|
||||||
@ -172,7 +173,7 @@ messages!(
|
|||||||
(GetProjectSymbols, Background),
|
(GetProjectSymbols, Background),
|
||||||
(GetProjectSymbolsResponse, Background),
|
(GetProjectSymbolsResponse, Background),
|
||||||
(GetUsers, Foreground),
|
(GetUsers, Foreground),
|
||||||
(GetUsersResponse, Foreground),
|
(UsersResponse, Foreground),
|
||||||
(JoinChannel, Foreground),
|
(JoinChannel, Foreground),
|
||||||
(JoinChannelResponse, Foreground),
|
(JoinChannelResponse, Foreground),
|
||||||
(JoinProject, Foreground),
|
(JoinProject, Foreground),
|
||||||
@ -236,7 +237,8 @@ request_messages!(
|
|||||||
(GetDocumentHighlights, GetDocumentHighlightsResponse),
|
(GetDocumentHighlights, GetDocumentHighlightsResponse),
|
||||||
(GetReferences, GetReferencesResponse),
|
(GetReferences, GetReferencesResponse),
|
||||||
(GetProjectSymbols, GetProjectSymbolsResponse),
|
(GetProjectSymbols, GetProjectSymbolsResponse),
|
||||||
(GetUsers, GetUsersResponse),
|
(FuzzySearchUsers, UsersResponse),
|
||||||
|
(GetUsers, UsersResponse),
|
||||||
(JoinChannel, JoinChannelResponse),
|
(JoinChannel, JoinChannelResponse),
|
||||||
(JoinProject, JoinProjectResponse),
|
(JoinProject, JoinProjectResponse),
|
||||||
(OpenBufferById, OpenBufferResponse),
|
(OpenBufferById, OpenBufferResponse),
|
||||||
|
@ -234,20 +234,21 @@ pub struct CommandPalette {
|
|||||||
pub struct ContactsPanel {
|
pub struct ContactsPanel {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub container: ContainerStyle,
|
pub container: ContainerStyle,
|
||||||
|
pub header: ContainedText,
|
||||||
pub user_query_editor: FieldEditor,
|
pub user_query_editor: FieldEditor,
|
||||||
pub host_row_height: f32,
|
pub row_height: f32,
|
||||||
pub host_avatar: ImageStyle,
|
pub contact_avatar: ImageStyle,
|
||||||
pub host_username: ContainedText,
|
pub contact_username: ContainedText,
|
||||||
pub tree_branch_width: f32,
|
pub tree_branch_width: f32,
|
||||||
pub tree_branch_color: Color,
|
pub tree_branch_color: Color,
|
||||||
pub shared_project: WorktreeRow,
|
pub shared_project: ProjectRow,
|
||||||
pub hovered_shared_project: WorktreeRow,
|
pub hovered_shared_project: ProjectRow,
|
||||||
pub unshared_project: WorktreeRow,
|
pub unshared_project: ProjectRow,
|
||||||
pub hovered_unshared_project: WorktreeRow,
|
pub hovered_unshared_project: ProjectRow,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default)]
|
#[derive(Deserialize, Default)]
|
||||||
pub struct WorktreeRow {
|
pub struct ProjectRow {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub container: ContainerStyle,
|
pub container: ContainerStyle,
|
||||||
pub height: f32,
|
pub height: f32,
|
||||||
|
@ -47,19 +47,25 @@ export default function(theme: Theme) {
|
|||||||
top: 7,
|
top: 7,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
hostRowHeight: 28,
|
rowHeight: 28,
|
||||||
treeBranchColor: borderColor(theme, "muted"),
|
treeBranchColor: borderColor(theme, "muted"),
|
||||||
treeBranchWidth: 1,
|
treeBranchWidth: 1,
|
||||||
hostAvatar: {
|
contactAvatar: {
|
||||||
cornerRadius: 10,
|
cornerRadius: 10,
|
||||||
width: 18,
|
width: 18,
|
||||||
},
|
},
|
||||||
hostUsername: {
|
contactUsername: {
|
||||||
...text(theme, "mono", "primary", { size: "sm" }),
|
...text(theme, "mono", "primary", { size: "sm" }),
|
||||||
padding: {
|
padding: {
|
||||||
left: 8,
|
left: 8,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
header: {
|
||||||
|
...text(theme, "mono", "secondary", { size: "sm" }),
|
||||||
|
// padding: {
|
||||||
|
// left: 8,
|
||||||
|
// }
|
||||||
|
},
|
||||||
project,
|
project,
|
||||||
sharedProject,
|
sharedProject,
|
||||||
hoveredSharedProject: {
|
hoveredSharedProject: {
|
||||||
|
Loading…
Reference in New Issue
Block a user