gpui-component/crates/story/src/table_story.rs
2024-08-11 14:46:55 +08:00

426 lines
14 KiB
Rust

use fake::Fake;
use gpui::{
div, img, IntoElement, ParentElement, Pixels, Render, SharedString, Styled, View, ViewContext,
VisualContext as _, WindowContext,
};
use ui::{
checkbox::Checkbox,
h_flex,
label::Label,
table::{ColSort, Table, TableDelegate, TableEvent},
theme::ActiveTheme as _,
v_flex, Icon, IconName, Selectable,
};
struct Customer {
id: usize,
login: String,
first_name: String,
last_name: String,
company: String,
city: String,
country: String,
email: String,
phone: String,
gender: usize,
age: usize,
verified: bool,
confirmed: bool,
}
impl Customer {
fn render_avatar(&self, _: &mut WindowContext) -> impl IntoElement {
let image_id = self.id % 70 + 1;
let avatar_url = format!("https://i.pravatar.cc/40?image={}", image_id);
img(avatar_url).size_5().rounded_full()
}
}
fn randome_customers(size: usize) -> Vec<Customer> {
(0..size)
.map(|id| Customer {
id,
login: fake::faker::internet::en::Username().fake::<String>(),
first_name: fake::faker::name::en::FirstName().fake::<String>(),
last_name: fake::faker::name::en::LastName().fake::<String>(),
company: fake::faker::company::en::CompanyName().fake::<String>(),
city: fake::faker::address::en::CityName().fake::<String>(),
country: fake::faker::address::en::CountryName().fake::<String>(),
email: fake::faker::internet::en::FreeEmail().fake::<String>(),
phone: fake::faker::phone_number::en::PhoneNumber().fake::<String>(),
gender: (0..=1).fake(),
age: (18..80).fake(),
verified: (0..=1).fake::<u8>() == 1,
confirmed: (0..=1).fake::<u8>() == 1,
})
.collect()
}
struct Column {
id: SharedString,
name: SharedString,
sort: Option<ColSort>,
}
impl Column {
fn new(
id: impl Into<SharedString>,
name: impl Into<SharedString>,
sort: Option<ColSort>,
) -> Self {
Self {
id: id.into(),
name: name.into(),
sort,
}
}
}
struct CustomerTableDelegate {
customers: Vec<Customer>,
columns: Vec<Column>,
loop_selection: bool,
col_resize: bool,
col_order: bool,
col_sort: bool,
}
impl CustomerTableDelegate {
fn new(size: usize) -> Self {
Self {
customers: randome_customers(size),
columns: vec![
Column::new("id", "ID", Some(ColSort::Ascending)),
Column::new("login", "Login", Some(ColSort::Default)),
Column::new("first_name", "First Name", Some(ColSort::Default)),
Column::new("last_name", "Last Name", Some(ColSort::Default)),
Column::new("company", "Company", Some(ColSort::Default)),
Column::new("city", "City", Some(ColSort::Default)),
Column::new("country", "Country", Some(ColSort::Default)),
Column::new("email", "Email", Some(ColSort::Default)),
Column::new("phone", "Phone", None),
Column::new("gender", "Gender", None),
Column::new("age", "Age", Some(ColSort::Default)),
Column::new("verified", "Verified", None),
Column::new("confirmed", "Confirmed", None),
Column::new("twitter", "Twitter", None),
],
loop_selection: true,
col_resize: true,
col_order: true,
col_sort: true,
}
}
}
impl TableDelegate for CustomerTableDelegate {
fn cols_count(&self) -> usize {
self.columns.len()
}
fn rows_count(&self) -> usize {
self.customers.len()
}
fn col_name(&self, col_ix: usize) -> SharedString {
if let Some(col) = self.columns.get(col_ix) {
col.name.clone()
} else {
"--".into()
}
}
fn col_width(&self, col_ix: usize) -> Option<Pixels> {
if let Some(col) = self.columns.get(col_ix) {
Some(
match col.id.as_ref() {
"id" => 100.0,
"login" => 220.0,
"first_name" => 150.0,
"last_name" => 150.0,
"company" => 300.0,
"city" => 200.0,
"country" => 200.0,
"email" => 350.0,
"phone" => 240.0,
"gender" => 80.0,
"age" => 90.0,
"verified" => 90.0,
"confirmed" => 90.0,
"twitter" => 90.0,
_ => 200.0,
}
.into(),
)
} else {
None
}
}
fn can_resize_col(&self, col_ix: usize) -> bool {
return self.col_resize && col_ix > 1;
}
fn render_td(
&self,
row_ix: usize,
col_ix: usize,
cx: &mut ViewContext<Table<Self>>,
) -> impl IntoElement {
let customer = self.customers.get(row_ix).unwrap();
let col = self.columns.get(col_ix).unwrap();
match col.id.as_ref() {
"id" => customer.id.to_string().into_any_element(),
"login" => h_flex()
.items_center()
.gap_2()
.child(customer.render_avatar(cx))
.child(customer.login.clone())
.into_any_element(),
"first_name" => customer.first_name.clone().into_any_element(),
"last_name" => customer.last_name.clone().into_any_element(),
"company" => h_flex()
.items_center()
.justify_between()
.gap_1()
.child(customer.company.clone())
.child(IconName::Info)
.into_any_element(),
"city" => customer.city.clone().into_any_element(),
"country" => customer.country.clone().into_any_element(),
"email" => customer.email.clone().into_any_element(),
"phone" => customer.phone.clone().into_any_element(),
"gender" => match customer.gender {
0 => "Male",
1 => "Famale",
_ => "",
}
.into_any_element(),
"age" => customer.age.to_string().into_any_element(),
"verified" => match customer.verified {
true => "Yes".to_string().into_any_element(),
false => "No".to_string().into_any_element(),
},
"confirmed" => match customer.confirmed {
true => Icon::new(IconName::Check).size_4().into_any_element(),
false => div().into_any_element(),
},
_ => Label::new("--")
.text_color(cx.theme().muted_foreground)
.into_any_element(),
}
}
fn can_loop_select(&self) -> bool {
self.loop_selection
}
fn can_move_col(&self, _: usize) -> bool {
self.col_order
}
fn move_col(&mut self, col_ix: usize, to_ix: usize) {
let col = self.columns.remove(col_ix);
self.columns.insert(to_ix, col);
}
fn col_sort(&self, col_ix: usize) -> Option<ColSort> {
if !self.col_sort {
return None;
}
self.columns.get(col_ix).and_then(|c| c.sort)
}
fn perform_sort(&mut self, col_ix: usize, sort: ColSort, _: &mut ViewContext<Table<Self>>) {
if !self.col_sort {
return;
}
if let Some(col) = self.columns.get_mut(col_ix) {
col.sort = Some(sort);
let asc = matches!(sort, ColSort::Ascending);
match col.id.as_ref() {
"id" => self.customers.sort_by(|a, b| {
if asc {
a.id.cmp(&b.id)
} else {
b.id.cmp(&a.id)
}
}),
"login" => self.customers.sort_by(|a, b| {
if asc {
a.login.cmp(&b.login)
} else {
b.login.cmp(&a.login)
}
}),
"first_name" => self.customers.sort_by(|a, b| {
if asc {
a.first_name.cmp(&b.first_name)
} else {
b.first_name.cmp(&a.first_name)
}
}),
"last_name" => self.customers.sort_by(|a, b| {
if asc {
a.last_name.cmp(&b.last_name)
} else {
b.last_name.cmp(&a.last_name)
}
}),
"company" => self.customers.sort_by(|a, b| {
if asc {
a.company.cmp(&b.company)
} else {
b.company.cmp(&a.company)
}
}),
"city" => self.customers.sort_by(|a, b| {
if asc {
a.city.cmp(&b.city)
} else {
b.city.cmp(&a.city)
}
}),
"country" => self.customers.sort_by(|a, b| {
if asc {
a.country.cmp(&b.country)
} else {
b.country.cmp(&a.country)
}
}),
"email" => self.customers.sort_by(|a, b| {
if asc {
a.email.cmp(&b.email)
} else {
b.email.cmp(&a.email)
}
}),
"age" => self.customers.sort_by(|a, b| {
if asc {
a.age.cmp(&b.age)
} else {
b.age.cmp(&a.age)
}
}),
_ => {}
}
for col in self.columns.iter_mut() {
if let Some(ColSort::Ascending) = col.sort {
col.sort = Some(ColSort::Default);
}
}
}
}
}
pub struct TableStory {
table: View<Table<CustomerTableDelegate>>,
}
impl TableStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.new_view(Self::new)
}
fn new(cx: &mut ViewContext<Self>) -> Self {
let delegate = CustomerTableDelegate::new(5000);
let table = cx.new_view(|cx| Table::new(delegate, cx));
cx.subscribe(&table, Self::on_table_event).detach();
Self { table }
}
fn toggle_loop_selection(&mut self, checked: &bool, cx: &mut ViewContext<Self>) {
let table = self.table.clone();
table.update(cx, |table, cx| {
table.delegate_mut().loop_selection = *checked;
cx.notify();
});
}
fn toggle_col_resize(&mut self, checked: &bool, cx: &mut ViewContext<Self>) {
let table = self.table.clone();
table.update(cx, |table, cx| {
table.delegate_mut().col_resize = *checked;
cx.notify();
});
}
fn toggle_col_order(&mut self, checked: &bool, cx: &mut ViewContext<Self>) {
let table = self.table.clone();
table.update(cx, |table, cx| {
table.delegate_mut().col_order = *checked;
cx.notify();
});
}
fn toggle_col_sort(&mut self, checked: &bool, cx: &mut ViewContext<Self>) {
let table = self.table.clone();
table.update(cx, |table, cx| {
println!("- toggle_col_sort: {}", checked);
table.delegate_mut().col_sort = *checked;
cx.notify();
});
}
fn on_table_event(
&mut self,
_: View<Table<CustomerTableDelegate>>,
event: &TableEvent,
_cx: &mut ViewContext<Self>,
) {
match event {
TableEvent::ColWidthsChanged(col_widths) => {
println!("Col widths changed: {:?}", col_widths)
}
TableEvent::SelectCol(ix) => println!("Select col: {}", ix),
TableEvent::SelectRow(ix) => println!("Select row: {}", ix),
}
}
}
impl Render for TableStory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl gpui::IntoElement {
let delegate = self.table.read(cx).delegate();
v_flex()
.size_full()
.gap_2()
.child(
h_flex()
.items_center()
.gap_2()
.child(
Checkbox::new("loop-selection")
.label("Loop Selection")
.selected(delegate.loop_selection)
.on_click(cx.listener(Self::toggle_loop_selection)),
)
.child(
Checkbox::new("col-resize")
.label("Column Resize")
.selected(delegate.col_resize)
.on_click(cx.listener(Self::toggle_col_resize)),
)
.child(
Checkbox::new("col-order")
.label("Column Order")
.selected(delegate.col_order)
.on_click(cx.listener(Self::toggle_col_order)),
)
.child(
Checkbox::new("col-sort")
.label("Column Sort")
.selected(delegate.col_sort)
.on_click(cx.listener(Self::toggle_col_sort)),
),
)
.child(self.table.clone())
}
}