Rework extension filtering to use a ToggleButton (#8387)

This PR reworks the extension filtering to use a `ToggleButton`, since
the filter states are mutually-exclusive.

<img width="1136" alt="Screenshot 2024-02-25 at 10 04 59 AM"
src="https://github.com/zed-industries/zed/assets/1486634/52c621da-201c-42b9-805d-62e3ab66f94b">

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-02-25 10:17:50 -05:00 committed by GitHub
parent 37a12a366f
commit 053b6cc715
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -11,7 +11,7 @@ use settings::Settings;
use std::time::Duration; use std::time::Duration;
use std::{ops::Range, sync::Arc}; use std::{ops::Range, sync::Arc};
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{prelude::*, CheckboxWithLabel, Tooltip}; use ui::{prelude::*, ToggleButton, Tooltip};
use workspace::{ use workspace::{
item::{Item, ItemEvent}, item::{Item, ItemEvent},
@ -30,12 +30,18 @@ pub fn init(cx: &mut AppContext) {
.detach(); .detach();
} }
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
enum ExtensionFilter {
All,
Installed,
NotInstalled,
}
pub struct ExtensionsPage { pub struct ExtensionsPage {
list: UniformListScrollHandle, list: UniformListScrollHandle,
telemetry: Arc<Telemetry>, telemetry: Arc<Telemetry>,
is_fetching_extensions: bool, is_fetching_extensions: bool,
is_showing_installed_extensions: bool, filter: ExtensionFilter,
is_showing_not_installed_extensions: bool,
extension_entries: Vec<Extension>, extension_entries: Vec<Extension>,
query_editor: View<Editor>, query_editor: View<Editor>,
query_contains_error: bool, query_contains_error: bool,
@ -56,8 +62,7 @@ impl ExtensionsPage {
list: UniformListScrollHandle::new(), list: UniformListScrollHandle::new(),
telemetry: workspace.client().telemetry().clone(), telemetry: workspace.client().telemetry().clone(),
is_fetching_extensions: false, is_fetching_extensions: false,
is_showing_installed_extensions: true, filter: ExtensionFilter::All,
is_showing_not_installed_extensions: true,
extension_entries: Vec::new(), extension_entries: Vec::new(),
query_contains_error: false, query_contains_error: false,
extension_fetch_task: None, extension_fetch_task: None,
@ -74,17 +79,17 @@ impl ExtensionsPage {
self.extension_entries self.extension_entries
.iter() .iter()
.filter(|extension| { .filter(|extension| match self.filter {
let status = extension_store.extension_status(&extension.id); ExtensionFilter::All => true,
ExtensionFilter::Installed => {
let status = extension_store.extension_status(&extension.id);
match [ matches!(status, ExtensionStatus::Installed(_))
self.is_showing_installed_extensions, }
self.is_showing_not_installed_extensions, ExtensionFilter::NotInstalled => {
] { let status = extension_store.extension_status(&extension.id);
[true, true] => true,
[true, false] => matches!(status, ExtensionStatus::Installed(_)), matches!(status, ExtensionStatus::NotInstalled)
[false, true] => matches!(status, ExtensionStatus::NotInstalled),
[false, false] => false,
} }
}) })
.cloned() .cloned()
@ -421,17 +426,33 @@ impl ExtensionsPage {
} }
fn render_empty_state(&self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_empty_state(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let is_filtering = self.search_query(cx).is_some() let has_search = self.search_query(cx).is_some();
|| self.is_showing_installed_extensions
|| self.is_showing_not_installed_extensions;
let message = if self.is_fetching_extensions { let message = if self.is_fetching_extensions {
"Loading extensions..." "Loading extensions..."
} else { } else {
if is_filtering { match self.filter {
"No extensions that match your search criteria." ExtensionFilter::All => {
} else { if has_search {
"No extensions." "No extensions that match your search."
} else {
"No extensions."
}
}
ExtensionFilter::Installed => {
if has_search {
"No installed extensions that match your search."
} else {
"No installed extensions."
}
}
ExtensionFilter::NotInstalled => {
if has_search {
"No not installed extensions that match your search."
} else {
"No not installed extensions."
}
}
} }
}; };
@ -456,38 +477,46 @@ impl Render for ExtensionsPage {
.w_full() .w_full()
.gap_2() .gap_2()
.child(h_flex().child(self.render_search(cx))) .child(h_flex().child(self.render_search(cx)))
.child(CheckboxWithLabel::new( .child(
"Installed", h_flex()
Label::new("Installed"), .child(
if self.is_showing_installed_extensions { ToggleButton::new("filter-all", "All")
Selection::Selected .style(ButtonStyle::Filled)
} else { .size(ButtonSize::Large)
Selection::Unselected .selected(self.filter == ExtensionFilter::All)
}, .on_click(cx.listener(|this, _event, _cx| {
cx.listener(|this, selection, _cx| { this.filter = ExtensionFilter::All;
this.is_showing_installed_extensions = match selection { }))
Selection::Selected => true, .tooltip(move |cx| Tooltip::text("Show all extensions", cx))
Selection::Unselected => false, .first(),
Selection::Indeterminate => return, )
} .child(
}), ToggleButton::new("filter-installed", "Installed")
)) .style(ButtonStyle::Filled)
.child(CheckboxWithLabel::new( .size(ButtonSize::Large)
"not installed", .selected(self.filter == ExtensionFilter::Installed)
Label::new("Not installed"), .on_click(cx.listener(|this, _event, _cx| {
if self.is_showing_not_installed_extensions { this.filter = ExtensionFilter::Installed;
Selection::Selected }))
} else { .tooltip(move |cx| {
Selection::Unselected Tooltip::text("Show installed extensions", cx)
}, })
cx.listener(|this, selection, _cx| { .middle(),
this.is_showing_not_installed_extensions = match selection { )
Selection::Selected => true, .child(
Selection::Unselected => false, ToggleButton::new("filter-not-installed", "Not Installed")
Selection::Indeterminate => return, .style(ButtonStyle::Filled)
} .size(ButtonSize::Large)
}), .selected(self.filter == ExtensionFilter::NotInstalled)
)), .on_click(cx.listener(|this, _event, _cx| {
this.filter = ExtensionFilter::NotInstalled;
}))
.tooltip(move |cx| {
Tooltip::text("Show not installed extensions", cx)
})
.last(),
),
),
) )
.child(v_flex().size_full().overflow_y_hidden().map(|this| { .child(v_flex().size_full().overflow_y_hidden().map(|this| {
let entries = self.filtered_extension_entries(cx); let entries = self.filtered_extension_entries(cx);