use crate::{
app::{ActionRequest, EntityOrTimelineActionRequest},
components::OpenTimelineButton,
config::SharedConfig,
consts::{EDIT_BUTTON_WIDTH, VIEW_BUTTON_WIDTH},
spawn_transaction_no_commit_send_result,
};
use eframe::egui::{self, Align, Context, Layout, ScrollArea, TextEdit, Ui, Vec2};
use egui_extras::{Column, TableBuilder};
use open_timeline_crud::{CrudError, EntityCounts, SortAlphabetically, SortByNumber};
use open_timeline_gui_core::{
Draw, Paginator, Reload, body_text_height, widget_x_spacing, widget_y_spacing,
};
use std::sync::Arc;
use tokio::sync::mpsc::error::TryRecvError;
use tokio::sync::mpsc::{Receiver, UnboundedSender};
const UP_ARROW: &str = "⏶";
const DOWN_ARROW: &str = "⏷";
const UP_DOWN_ARROW: &str = "⏶⏷";
#[derive(Debug, Clone, Copy)]
struct EntityCountsTableSizes {
row_height: f32,
entity_name_width: f32,
entity_date_width: f32,
entity_tag_count_width: f32,
edit_button_width: f32,
view_button_width: f32,
table_body_max_height: f32,
}
#[derive(Debug)]
pub struct EntityCountsGui {
entity_counts: Option<EntityCounts>,
filtered_entity_counts: Option<EntityCounts>,
name_ordering: Option<SortAlphabetically>,
tag_count_ordering: Option<SortByNumber>,
start_ordering: Option<SortByNumber>,
end_ordering: Option<SortByNumber>,
rx_reload: Option<Receiver<Result<EntityCounts, CrudError>>>,
requested_reload: bool,
tx_action_request: UnboundedSender<ActionRequest>,
filter_text: String,
paginator: Paginator,
shared_config: SharedConfig,
}
impl EntityCountsGui {
pub fn new(
shared_config: SharedConfig,
tx_action_request: UnboundedSender<ActionRequest>,
) -> Self {
let mut entity_count_gui = Self {
entity_counts: None,
filtered_entity_counts: None,
name_ordering: None,
start_ordering: None,
end_ordering: None,
tag_count_ordering: None,
rx_reload: None,
requested_reload: false,
tx_action_request,
filter_text: String::new(),
paginator: Paginator::new(0, 0, 100),
shared_config,
};
entity_count_gui.request_reload();
entity_count_gui
}
fn update_sort(&mut self) {
if let Some(entity_counts) = self.filtered_entity_counts.as_mut() {
if let Some(name_ordering) = &self.name_ordering {
entity_counts.sort_by_name(name_ordering);
}
if let Some(tag_count_ordering) = &self.tag_count_ordering {
entity_counts.sort_by_tag_count(tag_count_ordering);
}
if let Some(start_ordering) = &self.start_ordering {
entity_counts.sort_by_start_date(start_ordering);
}
if let Some(end_ordering) = &self.end_ordering {
entity_counts.sort_by_end_date(end_ordering);
}
}
}
fn draw_table_header(
&mut self,
_ctx: &Context,
ui: &mut Ui,
table_sizes: EntityCountsTableSizes,
) {
let mut sort_needs_updating = false;
begin_table(ui, "entity_counts_header", table_sizes).header(
table_sizes.row_height,
|mut row| {
row.col(|ui| {
let arrow = match self.name_ordering {
None => UP_DOWN_ARROW,
Some(SortAlphabetically::AToZ) => UP_ARROW,
Some(SortAlphabetically::ZToA) => DOWN_ARROW,
};
ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
if open_timeline_gui_core::Label::sub_heading(ui, &format!("Name {arrow}"))
.clicked()
{
self.tag_count_ordering = None;
self.start_ordering = None;
self.end_ordering = None;
match self.name_ordering {
None => self.name_ordering = Some(SortAlphabetically::AToZ),
Some(SortAlphabetically::AToZ) => {
self.name_ordering = Some(SortAlphabetically::ZToA)
}
Some(SortAlphabetically::ZToA) => self.name_ordering = None,
}
sort_needs_updating = true;
}
});
});
row.col(|ui| {
let arrow = match self.start_ordering {
None => UP_DOWN_ARROW,
Some(SortByNumber::Ascending) => UP_ARROW,
Some(SortByNumber::Descending) => DOWN_ARROW,
};
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
if open_timeline_gui_core::Label::sub_heading(ui, &format!("Start {arrow}"))
.clicked()
{
self.name_ordering = None;
self.end_ordering = None;
self.tag_count_ordering = None;
match self.start_ordering {
None => self.start_ordering = Some(SortByNumber::Ascending),
Some(SortByNumber::Ascending) => {
self.start_ordering = Some(SortByNumber::Descending)
}
Some(SortByNumber::Descending) => self.start_ordering = None,
}
sort_needs_updating = true;
}
});
});
row.col(|ui| {
let arrow = match self.end_ordering {
None => UP_DOWN_ARROW,
Some(SortByNumber::Ascending) => UP_ARROW,
Some(SortByNumber::Descending) => DOWN_ARROW,
};
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
if open_timeline_gui_core::Label::sub_heading(ui, &format!("End {arrow}"))
.clicked()
{
self.name_ordering = None;
self.start_ordering = None;
self.tag_count_ordering = None;
match self.end_ordering {
None => self.end_ordering = Some(SortByNumber::Ascending),
Some(SortByNumber::Ascending) => {
self.end_ordering = Some(SortByNumber::Descending)
}
Some(SortByNumber::Descending) => self.end_ordering = None,
}
sort_needs_updating = true;
}
});
});
row.col(|ui| {
let arrow = match self.tag_count_ordering {
None => UP_DOWN_ARROW,
Some(SortByNumber::Ascending) => UP_ARROW,
Some(SortByNumber::Descending) => DOWN_ARROW,
};
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
if open_timeline_gui_core::Label::sub_heading(ui, &format!("Tags {arrow}"))
.clicked()
{
self.name_ordering = None;
self.start_ordering = None;
self.end_ordering = None;
match self.tag_count_ordering {
None => self.tag_count_ordering = Some(SortByNumber::Ascending),
Some(SortByNumber::Ascending) => {
self.tag_count_ordering = Some(SortByNumber::Descending)
}
Some(SortByNumber::Descending) => self.tag_count_ordering = None,
}
sort_needs_updating = true;
}
});
});
row.col(|_ui| {});
row.col(|_ui| {});
},
);
if sort_needs_updating {
self.update_sort();
}
}
fn draw_table_body(
&mut self,
_ctx: &Context,
ui: &mut Ui,
table_sizes: EntityCountsTableSizes,
) {
let Some(entity_counts) = self.filtered_entity_counts.as_ref() else {
panic!()
};
let offset = (self.paginator.page_index()) * self.paginator.items_per_page();
let upper_limit = entity_counts
.len()
.min(offset + self.paginator.items_per_page());
let entity_counts = &entity_counts[offset..upper_limit];
let right_to_left = Layout::right_to_left(Align::Center);
let left_to_right = Layout::left_to_right(Align::Center);
ScrollArea::vertical()
.max_height(table_sizes.table_body_max_height)
.show(ui, |ui| {
begin_table(ui, "entity_entity_counts_body", table_sizes).body(|mut body| {
for entity_count in entity_counts {
let name = entity_count.name().as_str();
let start = entity_count.start().as_short_date_format();
let end = entity_count
.end()
.map(|end| end.as_short_date_format())
.unwrap_or_default();
body.row(table_sizes.row_height, |mut row| {
row.col(|ui| {
ui.with_layout(left_to_right, |ui| {
ui.add(egui::Label::new(name).truncate());
});
});
row.col(|ui| {
ui.with_layout(right_to_left, |ui| {
ui.add(egui::Label::new(start).truncate());
});
});
row.col(|ui| {
ui.with_layout(right_to_left, |ui| {
ui.add(egui::Label::new(end).truncate());
});
});
row.col(|ui| {
ui.with_layout(right_to_left, |ui| {
ui.add(
egui::Label::new(format!("{}", entity_count.tag_count()))
.truncate(),
);
});
});
row.col(|ui| {
if OpenTimelineButton::edit(ui).clicked() {
let _ = self.tx_action_request.send(ActionRequest::Entity(
EntityOrTimelineActionRequest::EditExisting(
entity_count.id(),
),
));
}
});
row.col(|ui| {
if OpenTimelineButton::view(ui).clicked() {
let _ = self.tx_action_request.send(ActionRequest::Entity(
EntityOrTimelineActionRequest::ViewExisting(
entity_count.id(),
),
));
}
});
});
}
});
});
}
fn update_filtered_entity_counts(&mut self) {
self.paginator.set_page_index(0);
self.filtered_entity_counts = self.entity_counts.as_ref().map(|entity_counts| {
entity_counts
.into_iter()
.filter(|entity_count| {
entity_count
.name()
.as_str()
.to_ascii_lowercase()
.contains(&self.filter_text.to_ascii_lowercase())
})
.cloned()
.collect()
});
self.filtered_entity_counts = self
.filtered_entity_counts
.take()
.filter(|filtered_entity_counts| !filtered_entity_counts.is_empty());
self.update_sort();
}
}
impl Reload for EntityCountsGui {
fn request_reload(&mut self) {
self.requested_reload = true;
let (tx, rx) = tokio::sync::mpsc::channel(1);
self.rx_reload = Some(rx);
let shared_config = Arc::clone(&self.shared_config);
spawn_transaction_no_commit_send_result!(
shared_config,
bounded,
tx,
|transaction| async move { EntityCounts::fetch_all(transaction).await }
);
}
fn check_reload_response(&mut self) {
if let Some(rx) = self.rx_reload.as_mut() {
match rx.try_recv() {
Ok(msg) => match msg {
Ok(entity_counts) => {
self.entity_counts = Some(entity_counts);
self.paginator.set_page_index(0);
self.update_filtered_entity_counts();
self.rx_reload = None;
self.update_sort();
self.requested_reload = false;
}
Err(error) => eprintln!("Error fetching entity counts: {error}"),
},
Err(TryRecvError::Empty) => (),
Err(TryRecvError::Disconnected) => (),
}
}
}
}
impl Draw for EntityCountsGui {
fn draw(&mut self, ctx: &Context, ui: &mut Ui) {
self.check_reload_response();
let filter_input = ui.add(
TextEdit::singleline(&mut self.filter_text)
.desired_width(f32::INFINITY)
.hint_text("Filter by entity name"),
);
if filter_input.changed() {
self.update_filtered_entity_counts();
}
ui.separator();
if let Some(entity_counts) = &self.filtered_entity_counts {
self.paginator.set_total_count(entity_counts.len());
} else {
open_timeline_gui_core::Label::none(ui);
return;
}
let available_width = ui.available_width();
let available_height = ui.available_height();
let row_height = body_text_height(ui);
let x_spacing = widget_x_spacing(ui);
let y_spacing = widget_y_spacing(ui);
let tag_count_width = 100.0;
let date_width = 100.0;
let table_max_height = available_height - (y_spacing * 3.0) - (row_height * 1.0);
let table_body_max_height = table_max_height - (y_spacing * 1.0) - (row_height * 1.0);
let entity_name_width = available_width
- tag_count_width
- (2.0 * date_width)
- EDIT_BUTTON_WIDTH
- VIEW_BUTTON_WIDTH
- (5.0 * x_spacing);
let table_max_height = table_max_height.max(0.0);
let table_body_max_height = table_body_max_height.max(0.0);
let entity_name_width = entity_name_width.max(0.0);
let table_sizes = EntityCountsTableSizes {
row_height,
entity_name_width,
entity_date_width: tag_count_width,
entity_tag_count_width: tag_count_width,
edit_button_width: EDIT_BUTTON_WIDTH,
view_button_width: VIEW_BUTTON_WIDTH,
table_body_max_height,
};
ui.allocate_ui(Vec2::from([available_width, table_body_max_height]), |ui| {
ui.set_min_size(Vec2::from([available_width, table_max_height]));
self.draw_table_header(ctx, ui, table_sizes);
self.draw_table_body(ctx, ui, table_sizes);
});
ui.separator();
self.paginator.draw(ctx, ui);
}
}
fn begin_table<'a>(
ui: &'a mut Ui,
id: &str,
table_sizes: EntityCountsTableSizes,
) -> TableBuilder<'a> {
TableBuilder::new(ui)
.id_salt(id)
.striped(true)
.column(Column::exact(table_sizes.entity_name_width))
.column(Column::exact(table_sizes.entity_date_width))
.column(Column::exact(table_sizes.entity_date_width))
.column(Column::exact(table_sizes.entity_tag_count_width))
.column(Column::exact(table_sizes.edit_button_width))
.column(Column::exact(table_sizes.view_button_width))
}