diff --git a/Cargo.lock b/Cargo.lock index 0d3271c066..35e48ed200 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -935,6 +935,7 @@ dependencies = [ "fuzzy", "gpui", "log", + "picker", "postage", "serde", "settings", diff --git a/assets/themes/cave-dark.json b/assets/themes/cave-dark.json index 68ea7ee996..9e10180e80 100644 --- a/assets/themes/cave-dark.json +++ b/assets/themes/cave-dark.json @@ -1466,45 +1466,12 @@ 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, "contact_avatar": { "corner_radius": 10, "width": 18 }, "contact_username": { - "family": "Zed Mono", - "color": "#e2dfe7", - "size": 14, "padding": { "left": 8 } diff --git a/assets/themes/cave-light.json b/assets/themes/cave-light.json index a7954938dc..85edbbc23f 100644 --- a/assets/themes/cave-light.json +++ b/assets/themes/cave-light.json @@ -1466,45 +1466,12 @@ 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, "contact_avatar": { "corner_radius": 10, "width": 18 }, "contact_username": { - "family": "Zed Mono", - "color": "#26232a", - "size": 14, "padding": { "left": 8 } diff --git a/assets/themes/dark.json b/assets/themes/dark.json index d1070d6cad..da9cff04d0 100644 --- a/assets/themes/dark.json +++ b/assets/themes/dark.json @@ -1466,45 +1466,12 @@ 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, "contact_avatar": { "corner_radius": 10, "width": 18 }, "contact_username": { - "family": "Zed Mono", - "color": "#f1f1f1", - "size": 14, "padding": { "left": 8 } diff --git a/assets/themes/light.json b/assets/themes/light.json index d79bc79f38..c4c0a72bfa 100644 --- a/assets/themes/light.json +++ b/assets/themes/light.json @@ -1466,45 +1466,12 @@ 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, "contact_avatar": { "corner_radius": 10, "width": 18 }, "contact_username": { - "family": "Zed Mono", - "color": "#2b2b2b", - "size": 14, "padding": { "left": 8 } diff --git a/assets/themes/solarized-dark.json b/assets/themes/solarized-dark.json index 01b217e8e5..fbaf1fc5b5 100644 --- a/assets/themes/solarized-dark.json +++ b/assets/themes/solarized-dark.json @@ -1466,45 +1466,12 @@ 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, "contact_avatar": { "corner_radius": 10, "width": 18 }, "contact_username": { - "family": "Zed Mono", - "color": "#eee8d5", - "size": 14, "padding": { "left": 8 } diff --git a/assets/themes/solarized-light.json b/assets/themes/solarized-light.json index 6d21fda5db..f04c8832c5 100644 --- a/assets/themes/solarized-light.json +++ b/assets/themes/solarized-light.json @@ -1466,45 +1466,12 @@ 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, "contact_avatar": { "corner_radius": 10, "width": 18 }, "contact_username": { - "family": "Zed Mono", - "color": "#073642", - "size": 14, "padding": { "left": 8 } diff --git a/assets/themes/sulphurpool-dark.json b/assets/themes/sulphurpool-dark.json index 9711ff8f9f..58764eb7ec 100644 --- a/assets/themes/sulphurpool-dark.json +++ b/assets/themes/sulphurpool-dark.json @@ -1466,45 +1466,12 @@ 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, "contact_avatar": { "corner_radius": 10, "width": 18 }, "contact_username": { - "family": "Zed Mono", - "color": "#dfe2f1", - "size": 14, "padding": { "left": 8 } diff --git a/assets/themes/sulphurpool-light.json b/assets/themes/sulphurpool-light.json index 17ec4fe62a..9a2aeb1b05 100644 --- a/assets/themes/sulphurpool-light.json +++ b/assets/themes/sulphurpool-light.json @@ -1466,45 +1466,12 @@ 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, "contact_avatar": { "corner_radius": 10, "width": 18 }, "contact_username": { - "family": "Zed Mono", - "color": "#293256", - "size": 14, "padding": { "left": 8 } diff --git a/crates/contacts_panel/Cargo.toml b/crates/contacts_panel/Cargo.toml index 69cef3177f..619bcad338 100644 --- a/crates/contacts_panel/Cargo.toml +++ b/crates/contacts_panel/Cargo.toml @@ -12,6 +12,7 @@ client = { path = "../client" } editor = { path = "../editor" } fuzzy = { path = "../fuzzy" } gpui = { path = "../gpui" } +picker = { path = "../picker" } settings = { path = "../settings" } theme = { path = "../theme" } util = { path = "../util" } diff --git a/crates/contacts_panel/src/contact_finder.rs b/crates/contacts_panel/src/contact_finder.rs index ed5281380c..7626971eaf 100644 --- a/crates/contacts_panel/src/contact_finder.rs +++ b/crates/contacts_panel/src/contact_finder.rs @@ -1,25 +1,34 @@ use client::{ContactRequestStatus, User, UserStore}; -use editor::Editor; use gpui::{ - color::Color, elements::*, platform::CursorStyle, Entity, LayoutContext, ModelHandle, - RenderContext, Task, View, ViewContext, ViewHandle, + actions, elements::*, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View, + ViewContext, ViewHandle, }; +use picker::{Picker, PickerDelegate}; use settings::Settings; use std::sync::Arc; use util::TryFutureExt; +use workspace::Workspace; -use crate::{RemoveContact, RequestContact}; +actions!(contact_finder, [Toggle]); + +pub fn init(cx: &mut MutableAppContext) { + Picker::::init(cx); + cx.add_action(ContactFinder::toggle); +} pub struct ContactFinder { - query_editor: ViewHandle, - list_state: UniformListState, + picker: ViewHandle>, potential_contacts: Arc<[Arc]>, user_store: ModelHandle, - contacts_search_task: Option>>, + selected_index: usize, +} + +pub enum Event { + Dismissed, } impl Entity for ContactFinder { - type Event = (); + type Event = Event; } impl View for ContactFinder { @@ -27,141 +36,35 @@ impl View for ContactFinder { "ContactFinder" } - fn render(&mut self, cx: &mut RenderContext) -> ElementBox { - let theme = cx.global::().theme.clone(); - let user_store = self.user_store.clone(); - let potential_contacts = self.potential_contacts.clone(); - Flex::column() - .with_child( - 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() + fn render(&mut self, _: &mut RenderContext) -> ElementBox { + ChildView::new(self.picker.clone()).boxed() + } + + fn on_focus(&mut self, cx: &mut ViewContext) { + cx.focus(&self.picker); } } -impl ContactFinder { - pub fn new(user_store: ModelHandle, cx: &mut ViewContext) -> Self { - let query_editor = cx.add_view(|cx| { - 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, - } +impl PickerDelegate for ContactFinder { + fn match_count(&self) -> usize { + self.potential_contacts.len() } - fn render_potential_contact( - contact: &User, - user_store: &ModelHandle, - 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::( - 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 selected_index(&self) -> usize { + self.selected_index } - fn query_changed(&mut self, cx: &mut ViewContext) { - let query = self.query_editor.read(cx).text(cx); + fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext) { + self.selected_index = ix; + } + + fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { let search_users = 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 { + cx.spawn(|this, mut cx| async move { + async { let potential_contacts = search_users.await?; this.update(&mut cx, |this, cx| { this.potential_contacts = potential_contacts.into(); @@ -170,6 +73,113 @@ impl ContactFinder { Ok(()) } .log_err() - })); + .await; + }) + } + + fn confirm(&mut self, cx: &mut ViewContext) { + 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) { + cx.emit(Event::Dismissed); + } + + fn render_match( + &self, + ix: usize, + mouse_state: &MouseState, + selected: bool, + cx: &gpui::AppContext, + ) -> ElementBox { + let theme = &cx.global::().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.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, cx: &mut ViewContext) -> 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, + event: &Event, + cx: &mut ViewContext, + ) { + match event { + Event::Dismissed => { + workspace.dismiss_modal(cx); + } + } } } diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index 6ff2ea7aff..c86d0f45a3 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -1,11 +1,9 @@ mod contact_finder; use client::{Contact, ContactRequestStatus, User, UserStore}; -use contact_finder::ContactFinder; use editor::Editor; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ - actions, elements::*, geometry::{rect::RectF, vector::vec2f}, impl_actions, @@ -16,9 +14,8 @@ use gpui::{ use serde::Deserialize; use settings::Settings; use std::sync::Arc; -use workspace::{AppState, JoinProject, Workspace}; +use workspace::{AppState, JoinProject}; -actions!(contacts_panel, [FindNewContacts]); impl_actions!( contacts_panel, [RequestContact, RemoveContact, RespondToContactRequest] @@ -54,10 +51,10 @@ pub struct RespondToContactRequest { } pub fn init(cx: &mut MutableAppContext) { + contact_finder::init(cx); cx.add_action(ContactsPanel::request_contact); cx.add_action(ContactsPanel::remove_contact); cx.add_action(ContactsPanel::respond_to_contact_request); - cx.add_action(ContactsPanel::find_new_contacts); } impl ContactsPanel { @@ -588,16 +585,6 @@ impl ContactsPanel { }) .detach(); } - - fn find_new_contacts( - workspace: &mut Workspace, - _: &FindNewContacts, - cx: &mut ViewContext, - ) { - workspace.toggle_modal(cx, |cx, workspace| { - cx.add_view(|cx| ContactFinder::new(workspace.user_store().clone(), cx)) - }); - } } pub enum Event {} @@ -638,7 +625,8 @@ impl View for ContactsPanel { .aligned() .boxed() }) - .on_click(|_, cx| cx.dispatch_action(FindNewContacts)) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(|_, cx| cx.dispatch_action(contact_finder::Toggle)) .boxed(), ) .constrained() diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 5a47bc504d..0c44a95ba9 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -252,14 +252,9 @@ pub struct ContactsPanel { #[derive(Deserialize, Default)] 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 contact_avatar: ImageStyle, - pub contact_username: ContainedText, + pub contact_username: ContainerStyle, pub contact_button: ContainedText, } diff --git a/styles/src/styleTree/contactFinder.ts b/styles/src/styleTree/contactFinder.ts index 1f0fb65776..668363076c 100644 --- a/styles/src/styleTree/contactFinder.ts +++ b/styles/src/styleTree/contactFinder.ts @@ -1,33 +1,16 @@ import Theme from "../themes/theme"; import picker from "./picker"; -import { backgroundColor, border, player, text } from "./components"; +import { backgroundColor, text } from "./components"; export default function contactFinder(theme: Theme) { return { ...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, contactAvatar: { cornerRadius: 10, width: 18, }, contactUsername: { - ...text(theme, "mono", "primary", { size: "sm" }), padding: { left: 8, },