diff --git a/Cargo.lock b/Cargo.lock index 0d3271c0662eaa689cb7af97ff748b51e9df60fd..35e48ed20039fead4d5555dcedbf41489fbeae03 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 68ea7ee996617bf316a7f7feeddf7f32a993fb6f..9e10180e806860fbb3eb9d72de9288cd91e005ef 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 a7954938dcc3e4806d4305440a783b033b806ade..85edbbc23f609ea7ac5ed3a5986994b311308130 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 d1070d6cadbcd2cb740b265d5684226e33ba73eb..da9cff04d0f2d598c824b79376f898d78181d0be 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 d79bc79f386389746b8bb3674ec12dd969c5b4b3..c4c0a72bfae2e82d5a929a13ab8e053cacf2e7c1 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 01b217e8e5fc6c381f69f745d394e0f3a2ee8101..fbaf1fc5b5ebd11b3ca1fe5d20ddfc961f9ce863 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 6d21fda5dbdd6778145cda0b92d3b5f3ec1aa7d1..f04c8832c5a5a1a5c94189b6833ad992267cdb16 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 9711ff8f9f5c69ff272fc7351f3757bda7fbc597..58764eb7ec55ada69fc7292d9b35640d909ce813 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 17ec4fe62a5fac523a7169435d46c3696be3e745..9a2aeb1b05334573ff912792382ae6abb64f5ae2 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 69cef3177f099bc0901f109f6d0429a6cbf35090..619bcad3385255a232103efad30b954170e3f0da 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 ed5281380c9f9ab77b5d2e8c3d57a28bc9646a79..7626971eaf832e9e76cdde78eced238279de6501 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,149 +36,150 @@ 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) - }); +impl PickerDelegate for ContactFinder { + fn match_count(&self) -> usize { + self.potential_contacts.len() + } - cx.subscribe(&query_editor, |this, _, event, cx| { - if let editor::Event::BufferEdited = event { - this.query_changed(cx) + fn selected_index(&self) -> usize { + self.selected_index + } + + 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)); + + 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(); + cx.notify(); + }); + Ok(()) } + .log_err() + .await; }) - .detach(); - Self { - query_editor, - list_state: Default::default(), - potential_contacts: Arc::from([]), - user_store, - contacts_search_task: None, - } } - fn render_potential_contact( - contact: &User, - user_store: &ModelHandle, - theme: &theme::ContactFinder, - cx: &mut LayoutContext, - ) -> ElementBox { - enum RequestContactButton {} + 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(); + } + _ => {} + } + } + } - let contact_id = contact.id; - let request_status = user_store.read(cx).contact_request_status(&contact); + 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_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( - contact.github_login.clone(), - theme.contact_username.text.clone(), + label.to_string(), + theme.contact_finder.contact_button.text.clone(), ) .contained() - .with_style(theme.contact_username.container) + .with_style(theme.contact_finder.contact_button.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) + .flex_float() .boxed(), ) + .contained() + .with_style(style.container) .constrained() - .with_height(theme.row_height) + .with_height(theme.contact_finder.row_height) .boxed() } +} - fn query_changed(&mut self, cx: &mut ViewContext) { - let query = self.query_editor.read(cx).text(cx); - let search_users = self - .user_store - .update(cx, |store, cx| store.fuzzy_search_users(query, cx)); +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 + }); + } - self.contacts_search_task = Some(cx.spawn(|this, mut cx| { - async move { - let potential_contacts = search_users.await?; - this.update(&mut cx, |this, cx| { - this.potential_contacts = potential_contacts.into(); - cx.notify(); - }); - Ok(()) + 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); } - .log_err() - })); + } } } diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index 6ff2ea7aff8827e2e1dd0404131edcc5ee460989..c86d0f45a3ecc1adbcce895eccdb828174b851f9 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 5a47bc504df76476ab06fc1a7554080db83ca052..0c44a95ba9f8b217afe44852980721d7c05d18c5 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 1f0fb65776cc634092ffbf39e4207241d0689a67..668363076c0e59e9203e17de2f4023cf12791ebb 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, },