Pull out contact finder as a picker
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
b721f0064a
commit
f81edb88fe
14 changed files with 151 additions and 437 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -935,6 +935,7 @@ dependencies = [
|
||||||
"fuzzy",
|
"fuzzy",
|
||||||
"gpui",
|
"gpui",
|
||||||
"log",
|
"log",
|
||||||
|
"picker",
|
||||||
"postage",
|
"postage",
|
||||||
"serde",
|
"serde",
|
||||||
"settings",
|
"settings",
|
||||||
|
|
|
@ -1466,45 +1466,12 @@
|
||||||
2
|
2
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"max_width": 540,
|
|
||||||
"max_height": 420,
|
|
||||||
"query_editor": {
|
|
||||||
"background": "#19171c",
|
|
||||||
"corner_radius": 6,
|
|
||||||
"text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#e2dfe7",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"placeholder_text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#7e7887",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"selection": {
|
|
||||||
"cursor": "#576ddb",
|
|
||||||
"selection": "#576ddb3d"
|
|
||||||
},
|
|
||||||
"border": {
|
|
||||||
"color": "#26232a",
|
|
||||||
"width": 1
|
|
||||||
},
|
|
||||||
"padding": {
|
|
||||||
"bottom": 4,
|
|
||||||
"left": 8,
|
|
||||||
"right": 8,
|
|
||||||
"top": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"row_height": 28,
|
"row_height": 28,
|
||||||
"contact_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"contact_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#e2dfe7",
|
|
||||||
"size": 14,
|
|
||||||
"padding": {
|
"padding": {
|
||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
|
|
|
@ -1466,45 +1466,12 @@
|
||||||
2
|
2
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"max_width": 540,
|
|
||||||
"max_height": 420,
|
|
||||||
"query_editor": {
|
|
||||||
"background": "#efecf4",
|
|
||||||
"corner_radius": 6,
|
|
||||||
"text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#26232a",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"placeholder_text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#655f6d",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"selection": {
|
|
||||||
"cursor": "#576ddb",
|
|
||||||
"selection": "#576ddb3d"
|
|
||||||
},
|
|
||||||
"border": {
|
|
||||||
"color": "#e2dfe7",
|
|
||||||
"width": 1
|
|
||||||
},
|
|
||||||
"padding": {
|
|
||||||
"bottom": 4,
|
|
||||||
"left": 8,
|
|
||||||
"right": 8,
|
|
||||||
"top": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"row_height": 28,
|
"row_height": 28,
|
||||||
"contact_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"contact_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#26232a",
|
|
||||||
"size": 14,
|
|
||||||
"padding": {
|
"padding": {
|
||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
|
|
|
@ -1466,45 +1466,12 @@
|
||||||
2
|
2
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"max_width": 540,
|
|
||||||
"max_height": 420,
|
|
||||||
"query_editor": {
|
|
||||||
"background": "#000000",
|
|
||||||
"corner_radius": 6,
|
|
||||||
"text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#f1f1f1",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"placeholder_text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#474747",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"selection": {
|
|
||||||
"cursor": "#2472f2",
|
|
||||||
"selection": "#2472f23d"
|
|
||||||
},
|
|
||||||
"border": {
|
|
||||||
"color": "#232323",
|
|
||||||
"width": 1
|
|
||||||
},
|
|
||||||
"padding": {
|
|
||||||
"bottom": 4,
|
|
||||||
"left": 8,
|
|
||||||
"right": 8,
|
|
||||||
"top": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"row_height": 28,
|
"row_height": 28,
|
||||||
"contact_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"contact_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#f1f1f1",
|
|
||||||
"size": 14,
|
|
||||||
"padding": {
|
"padding": {
|
||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
|
|
|
@ -1466,45 +1466,12 @@
|
||||||
2
|
2
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"max_width": 540,
|
|
||||||
"max_height": 420,
|
|
||||||
"query_editor": {
|
|
||||||
"background": "#ffffff",
|
|
||||||
"corner_radius": 6,
|
|
||||||
"text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#2b2b2b",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"placeholder_text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#808080",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"selection": {
|
|
||||||
"cursor": "#2472f2",
|
|
||||||
"selection": "#2472f23d"
|
|
||||||
},
|
|
||||||
"border": {
|
|
||||||
"color": "#d5d5d5",
|
|
||||||
"width": 1
|
|
||||||
},
|
|
||||||
"padding": {
|
|
||||||
"bottom": 4,
|
|
||||||
"left": 8,
|
|
||||||
"right": 8,
|
|
||||||
"top": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"row_height": 28,
|
"row_height": 28,
|
||||||
"contact_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"contact_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#2b2b2b",
|
|
||||||
"size": 14,
|
|
||||||
"padding": {
|
"padding": {
|
||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
|
|
|
@ -1466,45 +1466,12 @@
|
||||||
2
|
2
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"max_width": 540,
|
|
||||||
"max_height": 420,
|
|
||||||
"query_editor": {
|
|
||||||
"background": "#002b36",
|
|
||||||
"corner_radius": 6,
|
|
||||||
"text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#eee8d5",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"placeholder_text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#839496",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"selection": {
|
|
||||||
"cursor": "#268bd2",
|
|
||||||
"selection": "#268bd23d"
|
|
||||||
},
|
|
||||||
"border": {
|
|
||||||
"color": "#073642",
|
|
||||||
"width": 1
|
|
||||||
},
|
|
||||||
"padding": {
|
|
||||||
"bottom": 4,
|
|
||||||
"left": 8,
|
|
||||||
"right": 8,
|
|
||||||
"top": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"row_height": 28,
|
"row_height": 28,
|
||||||
"contact_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"contact_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#eee8d5",
|
|
||||||
"size": 14,
|
|
||||||
"padding": {
|
"padding": {
|
||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
|
|
|
@ -1466,45 +1466,12 @@
|
||||||
2
|
2
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"max_width": 540,
|
|
||||||
"max_height": 420,
|
|
||||||
"query_editor": {
|
|
||||||
"background": "#fdf6e3",
|
|
||||||
"corner_radius": 6,
|
|
||||||
"text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#073642",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"placeholder_text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#657b83",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"selection": {
|
|
||||||
"cursor": "#268bd2",
|
|
||||||
"selection": "#268bd23d"
|
|
||||||
},
|
|
||||||
"border": {
|
|
||||||
"color": "#eee8d5",
|
|
||||||
"width": 1
|
|
||||||
},
|
|
||||||
"padding": {
|
|
||||||
"bottom": 4,
|
|
||||||
"left": 8,
|
|
||||||
"right": 8,
|
|
||||||
"top": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"row_height": 28,
|
"row_height": 28,
|
||||||
"contact_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"contact_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#073642",
|
|
||||||
"size": 14,
|
|
||||||
"padding": {
|
"padding": {
|
||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
|
|
|
@ -1466,45 +1466,12 @@
|
||||||
2
|
2
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"max_width": 540,
|
|
||||||
"max_height": 420,
|
|
||||||
"query_editor": {
|
|
||||||
"background": "#202746",
|
|
||||||
"corner_radius": 6,
|
|
||||||
"text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#dfe2f1",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"placeholder_text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#898ea4",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"selection": {
|
|
||||||
"cursor": "#3d8fd1",
|
|
||||||
"selection": "#3d8fd13d"
|
|
||||||
},
|
|
||||||
"border": {
|
|
||||||
"color": "#293256",
|
|
||||||
"width": 1
|
|
||||||
},
|
|
||||||
"padding": {
|
|
||||||
"bottom": 4,
|
|
||||||
"left": 8,
|
|
||||||
"right": 8,
|
|
||||||
"top": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"row_height": 28,
|
"row_height": 28,
|
||||||
"contact_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"contact_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#dfe2f1",
|
|
||||||
"size": 14,
|
|
||||||
"padding": {
|
"padding": {
|
||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
|
|
|
@ -1466,45 +1466,12 @@
|
||||||
2
|
2
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"max_width": 540,
|
|
||||||
"max_height": 420,
|
|
||||||
"query_editor": {
|
|
||||||
"background": "#f5f7ff",
|
|
||||||
"corner_radius": 6,
|
|
||||||
"text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#293256",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"placeholder_text": {
|
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#6b7394",
|
|
||||||
"size": 14
|
|
||||||
},
|
|
||||||
"selection": {
|
|
||||||
"cursor": "#3d8fd1",
|
|
||||||
"selection": "#3d8fd13d"
|
|
||||||
},
|
|
||||||
"border": {
|
|
||||||
"color": "#dfe2f1",
|
|
||||||
"width": 1
|
|
||||||
},
|
|
||||||
"padding": {
|
|
||||||
"bottom": 4,
|
|
||||||
"left": 8,
|
|
||||||
"right": 8,
|
|
||||||
"top": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"row_height": 28,
|
"row_height": 28,
|
||||||
"contact_avatar": {
|
"contact_avatar": {
|
||||||
"corner_radius": 10,
|
"corner_radius": 10,
|
||||||
"width": 18
|
"width": 18
|
||||||
},
|
},
|
||||||
"contact_username": {
|
"contact_username": {
|
||||||
"family": "Zed Mono",
|
|
||||||
"color": "#293256",
|
|
||||||
"size": 14,
|
|
||||||
"padding": {
|
"padding": {
|
||||||
"left": 8
|
"left": 8
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ client = { path = "../client" }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
fuzzy = { path = "../fuzzy" }
|
fuzzy = { path = "../fuzzy" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
|
picker = { path = "../picker" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
|
|
@ -1,25 +1,34 @@
|
||||||
use client::{ContactRequestStatus, User, UserStore};
|
use client::{ContactRequestStatus, User, UserStore};
|
||||||
use editor::Editor;
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
color::Color, elements::*, platform::CursorStyle, Entity, LayoutContext, ModelHandle,
|
actions, elements::*, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View,
|
||||||
RenderContext, Task, View, ViewContext, ViewHandle,
|
ViewContext, ViewHandle,
|
||||||
};
|
};
|
||||||
|
use picker::{Picker, PickerDelegate};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::{RemoveContact, RequestContact};
|
actions!(contact_finder, [Toggle]);
|
||||||
|
|
||||||
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
|
Picker::<ContactFinder>::init(cx);
|
||||||
|
cx.add_action(ContactFinder::toggle);
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ContactFinder {
|
pub struct ContactFinder {
|
||||||
query_editor: ViewHandle<Editor>,
|
picker: ViewHandle<Picker<Self>>,
|
||||||
list_state: UniformListState,
|
|
||||||
potential_contacts: Arc<[Arc<User>]>,
|
potential_contacts: Arc<[Arc<User>]>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: ModelHandle<UserStore>,
|
||||||
contacts_search_task: Option<Task<Option<()>>>,
|
selected_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Event {
|
||||||
|
Dismissed,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for ContactFinder {
|
impl Entity for ContactFinder {
|
||||||
type Event = ();
|
type Event = Event;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for ContactFinder {
|
impl View for ContactFinder {
|
||||||
|
@ -27,141 +36,35 @@ impl View for ContactFinder {
|
||||||
"ContactFinder"
|
"ContactFinder"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
|
||||||
let theme = cx.global::<Settings>().theme.clone();
|
ChildView::new(self.picker.clone()).boxed()
|
||||||
let user_store = self.user_store.clone();
|
}
|
||||||
let potential_contacts = self.potential_contacts.clone();
|
|
||||||
Flex::column()
|
fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
.with_child(
|
cx.focus(&self.picker);
|
||||||
ChildView::new(self.query_editor.clone())
|
|
||||||
.contained()
|
|
||||||
.with_style(theme.contact_finder.query_editor.container)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_child(
|
|
||||||
UniformList::new(self.list_state.clone(), self.potential_contacts.len(), {
|
|
||||||
let theme = theme.clone();
|
|
||||||
move |range, items, cx| {
|
|
||||||
items.extend(range.map(|ix| {
|
|
||||||
Self::render_potential_contact(
|
|
||||||
&potential_contacts[ix],
|
|
||||||
&user_store,
|
|
||||||
&theme.contact_finder,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.flex(1., false)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.contained()
|
|
||||||
.with_style(theme.contact_finder.container)
|
|
||||||
.constrained()
|
|
||||||
.with_max_width(theme.contact_finder.max_width)
|
|
||||||
.with_max_height(theme.contact_finder.max_height)
|
|
||||||
.boxed()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContactFinder {
|
impl PickerDelegate for ContactFinder {
|
||||||
pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
|
fn match_count(&self) -> usize {
|
||||||
let query_editor = cx.add_view(|cx| {
|
self.potential_contacts.len()
|
||||||
Editor::single_line(Some(|theme| theme.contact_finder.query_editor.clone()), cx)
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.subscribe(&query_editor, |this, _, event, cx| {
|
|
||||||
if let editor::Event::BufferEdited = event {
|
|
||||||
this.query_changed(cx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
Self {
|
|
||||||
query_editor,
|
|
||||||
list_state: Default::default(),
|
|
||||||
potential_contacts: Arc::from([]),
|
|
||||||
user_store,
|
|
||||||
contacts_search_task: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_potential_contact(
|
fn selected_index(&self) -> usize {
|
||||||
contact: &User,
|
self.selected_index
|
||||||
user_store: &ModelHandle<UserStore>,
|
|
||||||
theme: &theme::ContactFinder,
|
|
||||||
cx: &mut LayoutContext,
|
|
||||||
) -> ElementBox {
|
|
||||||
enum RequestContactButton {}
|
|
||||||
|
|
||||||
let contact_id = contact.id;
|
|
||||||
let request_status = user_store.read(cx).contact_request_status(&contact);
|
|
||||||
|
|
||||||
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(),
|
|
||||||
)
|
|
||||||
.with_child(
|
|
||||||
MouseEventHandler::new::<RequestContactButton, _, _>(
|
|
||||||
contact.id as usize,
|
|
||||||
cx,
|
|
||||||
|_, _| {
|
|
||||||
let label = match request_status {
|
|
||||||
ContactRequestStatus::None | ContactRequestStatus::RequestReceived => {
|
|
||||||
"+"
|
|
||||||
}
|
|
||||||
ContactRequestStatus::RequestSent => "-",
|
|
||||||
ContactRequestStatus::Pending
|
|
||||||
| ContactRequestStatus::RequestAccepted => "…",
|
|
||||||
};
|
|
||||||
|
|
||||||
Label::new(label.to_string(), theme.contact_button.text.clone())
|
|
||||||
.contained()
|
|
||||||
.with_style(theme.contact_button.container)
|
|
||||||
.aligned()
|
|
||||||
.flex_float()
|
|
||||||
.boxed()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.on_click(move |_, cx| match request_status {
|
|
||||||
ContactRequestStatus::None => {
|
|
||||||
cx.dispatch_action(RequestContact(contact_id));
|
|
||||||
}
|
|
||||||
ContactRequestStatus::RequestSent => {
|
|
||||||
cx.dispatch_action(RemoveContact(contact_id));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
})
|
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.constrained()
|
|
||||||
.with_height(theme.row_height)
|
|
||||||
.boxed()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn query_changed(&mut self, cx: &mut ViewContext<Self>) {
|
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
|
||||||
let query = self.query_editor.read(cx).text(cx);
|
self.selected_index = ix;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
|
||||||
let search_users = self
|
let search_users = self
|
||||||
.user_store
|
.user_store
|
||||||
.update(cx, |store, cx| store.fuzzy_search_users(query, cx));
|
.update(cx, |store, cx| store.fuzzy_search_users(query, cx));
|
||||||
|
|
||||||
self.contacts_search_task = Some(cx.spawn(|this, mut cx| {
|
cx.spawn(|this, mut cx| async move {
|
||||||
async move {
|
async {
|
||||||
let potential_contacts = search_users.await?;
|
let potential_contacts = search_users.await?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.potential_contacts = potential_contacts.into();
|
this.potential_contacts = potential_contacts.into();
|
||||||
|
@ -170,6 +73,113 @@ impl ContactFinder {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
.log_err()
|
.log_err()
|
||||||
}));
|
.await;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn confirm(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
if let Some(user) = self.potential_contacts.get(self.selected_index) {
|
||||||
|
let user_store = self.user_store.read(cx);
|
||||||
|
match user_store.contact_request_status(user) {
|
||||||
|
ContactRequestStatus::None | ContactRequestStatus::RequestReceived => {
|
||||||
|
self.user_store
|
||||||
|
.update(cx, |store, cx| store.request_contact(user.id, cx))
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
ContactRequestStatus::RequestSent => {
|
||||||
|
self.user_store
|
||||||
|
.update(cx, |store, cx| store.remove_contact(user.id, cx))
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
cx.emit(Event::Dismissed);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_match(
|
||||||
|
&self,
|
||||||
|
ix: usize,
|
||||||
|
mouse_state: &MouseState,
|
||||||
|
selected: bool,
|
||||||
|
cx: &gpui::AppContext,
|
||||||
|
) -> ElementBox {
|
||||||
|
let theme = &cx.global::<Settings>().theme;
|
||||||
|
let contact = &self.potential_contacts[ix];
|
||||||
|
let request_status = self.user_store.read(cx).contact_request_status(&contact);
|
||||||
|
let label = match request_status {
|
||||||
|
ContactRequestStatus::None | ContactRequestStatus::RequestReceived => "+",
|
||||||
|
ContactRequestStatus::RequestSent => "-",
|
||||||
|
ContactRequestStatus::Pending | ContactRequestStatus::RequestAccepted => "…",
|
||||||
|
};
|
||||||
|
let style = theme.picker.item.style_for(mouse_state, selected);
|
||||||
|
Flex::row()
|
||||||
|
.with_children(contact.avatar.clone().map(|avatar| {
|
||||||
|
Image::new(avatar)
|
||||||
|
.with_style(theme.contact_finder.contact_avatar)
|
||||||
|
.aligned()
|
||||||
|
.left()
|
||||||
|
.boxed()
|
||||||
|
}))
|
||||||
|
.with_child(
|
||||||
|
Label::new(contact.github_login.clone(), style.label.clone())
|
||||||
|
.contained()
|
||||||
|
.with_style(theme.contact_finder.contact_username)
|
||||||
|
.aligned()
|
||||||
|
.left()
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
Label::new(
|
||||||
|
label.to_string(),
|
||||||
|
theme.contact_finder.contact_button.text.clone(),
|
||||||
|
)
|
||||||
|
.contained()
|
||||||
|
.with_style(theme.contact_finder.contact_button.container)
|
||||||
|
.aligned()
|
||||||
|
.flex_float()
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.contained()
|
||||||
|
.with_style(style.container)
|
||||||
|
.constrained()
|
||||||
|
.with_height(theme.contact_finder.row_height)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContactFinder {
|
||||||
|
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||||
|
workspace.toggle_modal(cx, |cx, workspace| {
|
||||||
|
let finder = cx.add_view(|cx| Self::new(workspace.user_store().clone(), cx));
|
||||||
|
cx.subscribe(&finder, Self::on_event).detach();
|
||||||
|
finder
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
|
||||||
|
let this = cx.weak_handle();
|
||||||
|
Self {
|
||||||
|
picker: cx.add_view(|cx| Picker::new(this, cx)),
|
||||||
|
potential_contacts: Arc::from([]),
|
||||||
|
user_store,
|
||||||
|
selected_index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
_: ViewHandle<Self>,
|
||||||
|
event: &Event,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) {
|
||||||
|
match event {
|
||||||
|
Event::Dismissed => {
|
||||||
|
workspace.dismiss_modal(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
mod contact_finder;
|
mod contact_finder;
|
||||||
|
|
||||||
use client::{Contact, ContactRequestStatus, User, UserStore};
|
use client::{Contact, ContactRequestStatus, User, UserStore};
|
||||||
use contact_finder::ContactFinder;
|
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use fuzzy::{match_strings, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions,
|
|
||||||
elements::*,
|
elements::*,
|
||||||
geometry::{rect::RectF, vector::vec2f},
|
geometry::{rect::RectF, vector::vec2f},
|
||||||
impl_actions,
|
impl_actions,
|
||||||
|
@ -16,9 +14,8 @@ use gpui::{
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use workspace::{AppState, JoinProject, Workspace};
|
use workspace::{AppState, JoinProject};
|
||||||
|
|
||||||
actions!(contacts_panel, [FindNewContacts]);
|
|
||||||
impl_actions!(
|
impl_actions!(
|
||||||
contacts_panel,
|
contacts_panel,
|
||||||
[RequestContact, RemoveContact, RespondToContactRequest]
|
[RequestContact, RemoveContact, RespondToContactRequest]
|
||||||
|
@ -54,10 +51,10 @@ pub struct RespondToContactRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
|
contact_finder::init(cx);
|
||||||
cx.add_action(ContactsPanel::request_contact);
|
cx.add_action(ContactsPanel::request_contact);
|
||||||
cx.add_action(ContactsPanel::remove_contact);
|
cx.add_action(ContactsPanel::remove_contact);
|
||||||
cx.add_action(ContactsPanel::respond_to_contact_request);
|
cx.add_action(ContactsPanel::respond_to_contact_request);
|
||||||
cx.add_action(ContactsPanel::find_new_contacts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContactsPanel {
|
impl ContactsPanel {
|
||||||
|
@ -588,16 +585,6 @@ impl ContactsPanel {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_new_contacts(
|
|
||||||
workspace: &mut Workspace,
|
|
||||||
_: &FindNewContacts,
|
|
||||||
cx: &mut ViewContext<Workspace>,
|
|
||||||
) {
|
|
||||||
workspace.toggle_modal(cx, |cx, workspace| {
|
|
||||||
cx.add_view(|cx| ContactFinder::new(workspace.user_store().clone(), cx))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Event {}
|
pub enum Event {}
|
||||||
|
@ -638,7 +625,8 @@ impl View for ContactsPanel {
|
||||||
.aligned()
|
.aligned()
|
||||||
.boxed()
|
.boxed()
|
||||||
})
|
})
|
||||||
.on_click(|_, cx| cx.dispatch_action(FindNewContacts))
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
.on_click(|_, cx| cx.dispatch_action(contact_finder::Toggle))
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.constrained()
|
.constrained()
|
||||||
|
|
|
@ -252,14 +252,9 @@ pub struct ContactsPanel {
|
||||||
|
|
||||||
#[derive(Deserialize, Default)]
|
#[derive(Deserialize, Default)]
|
||||||
pub struct ContactFinder {
|
pub struct ContactFinder {
|
||||||
#[serde(flatten)]
|
|
||||||
pub container: ContainerStyle,
|
|
||||||
pub max_width: f32,
|
|
||||||
pub max_height: f32,
|
|
||||||
pub query_editor: FieldEditor,
|
|
||||||
pub row_height: f32,
|
pub row_height: f32,
|
||||||
pub contact_avatar: ImageStyle,
|
pub contact_avatar: ImageStyle,
|
||||||
pub contact_username: ContainedText,
|
pub contact_username: ContainerStyle,
|
||||||
pub contact_button: ContainedText,
|
pub contact_button: ContainedText,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,33 +1,16 @@
|
||||||
import Theme from "../themes/theme";
|
import Theme from "../themes/theme";
|
||||||
import picker from "./picker";
|
import picker from "./picker";
|
||||||
import { backgroundColor, border, player, text } from "./components";
|
import { backgroundColor, text } from "./components";
|
||||||
|
|
||||||
export default function contactFinder(theme: Theme) {
|
export default function contactFinder(theme: Theme) {
|
||||||
return {
|
return {
|
||||||
...picker(theme),
|
...picker(theme),
|
||||||
maxWidth: 540.,
|
|
||||||
maxHeight: 420.,
|
|
||||||
queryEditor: {
|
|
||||||
background: backgroundColor(theme, 500),
|
|
||||||
cornerRadius: 6,
|
|
||||||
text: text(theme, "mono", "primary"),
|
|
||||||
placeholderText: text(theme, "mono", "placeholder", { size: "sm" }),
|
|
||||||
selection: player(theme, 1).selection,
|
|
||||||
border: border(theme, "secondary"),
|
|
||||||
padding: {
|
|
||||||
bottom: 4,
|
|
||||||
left: 8,
|
|
||||||
right: 8,
|
|
||||||
top: 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rowHeight: 28,
|
rowHeight: 28,
|
||||||
contactAvatar: {
|
contactAvatar: {
|
||||||
cornerRadius: 10,
|
cornerRadius: 10,
|
||||||
width: 18,
|
width: 18,
|
||||||
},
|
},
|
||||||
contactUsername: {
|
contactUsername: {
|
||||||
...text(theme, "mono", "primary", { size: "sm" }),
|
|
||||||
padding: {
|
padding: {
|
||||||
left: 8,
|
left: 8,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue