102 lines
2.8 KiB
Rust
102 lines
2.8 KiB
Rust
|
|
use chrono::NaiveDateTime;
|
||
|
|
use color_eyre::Result;
|
||
|
|
use ratatui::widgets::TableState;
|
||
|
|
use ratatui_textarea::TextArea;
|
||
|
|
use unidecode::unidecode;
|
||
|
|
|
||
|
|
use crate::{
|
||
|
|
config::{Config, Location},
|
||
|
|
message::Message,
|
||
|
|
update::update,
|
||
|
|
};
|
||
|
|
|
||
|
|
/// App-wide Elm model.
|
||
|
|
#[derive(Debug)]
|
||
|
|
pub(crate) struct Model<'a> {
|
||
|
|
pub(crate) config: Config,
|
||
|
|
pub(crate) table_state: TableState,
|
||
|
|
pub(crate) table_rows: Vec<Vec<CellData>>,
|
||
|
|
pub(crate) running_state: RunningState,
|
||
|
|
pub(crate) focus_state: FocusState,
|
||
|
|
pub(crate) filter_textarea: TextArea<'a>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl<'a> Model<'a> {
|
||
|
|
/// Create new model from parsed runtime config.
|
||
|
|
pub(crate) fn from_config(config: Config) -> Self {
|
||
|
|
Self {
|
||
|
|
config,
|
||
|
|
table_state: Default::default(),
|
||
|
|
table_rows: Default::default(),
|
||
|
|
running_state: Default::default(),
|
||
|
|
focus_state: Default::default(),
|
||
|
|
filter_textarea: Default::default(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Run Elm updates until they produce no further recursive messages.
|
||
|
|
pub(crate) fn update_loop(&mut self, msg: Option<Message>) -> Result<()> {
|
||
|
|
let mut maybe_msg = msg;
|
||
|
|
while let Some(msg) = maybe_msg {
|
||
|
|
maybe_msg = update(self, msg)?;
|
||
|
|
}
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Compute list of visible locations based on filter value.
|
||
|
|
pub(crate) fn get_visible_locations(&self) -> Vec<Location> {
|
||
|
|
if self.focus_state == FocusState::Filter {
|
||
|
|
let filter_terms = self.get_filter_terms();
|
||
|
|
self.config
|
||
|
|
.locations
|
||
|
|
.iter()
|
||
|
|
.enumerate()
|
||
|
|
.filter(|(i, location)| {
|
||
|
|
*i == 0
|
||
|
|
|| filter_terms.iter().any(|filter_term| {
|
||
|
|
unidecode(&location.name.to_ascii_lowercase())
|
||
|
|
.contains(&unidecode(&filter_term.to_ascii_lowercase()))
|
||
|
|
})
|
||
|
|
})
|
||
|
|
.map(|(_, value)| value.clone())
|
||
|
|
.collect()
|
||
|
|
} else {
|
||
|
|
self.config.locations.to_vec()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn get_filter_terms(&self) -> Vec<String> {
|
||
|
|
self.filter_textarea
|
||
|
|
.clone()
|
||
|
|
.into_lines()
|
||
|
|
.into_iter()
|
||
|
|
.next()
|
||
|
|
.unwrap_or_default()
|
||
|
|
.split('|')
|
||
|
|
.map(|value| value.trim().to_owned())
|
||
|
|
.collect()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Default, Eq, PartialEq)]
|
||
|
|
pub(crate) enum RunningState {
|
||
|
|
#[default]
|
||
|
|
Running,
|
||
|
|
Done,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Default, Eq, PartialEq)]
|
||
|
|
pub(crate) enum FocusState {
|
||
|
|
#[default]
|
||
|
|
Table,
|
||
|
|
Filter,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Computed data required to render a timetable cell.
|
||
|
|
#[derive(Debug)]
|
||
|
|
pub(crate) struct CellData {
|
||
|
|
pub(crate) is_daylight: bool,
|
||
|
|
pub(crate) is_current_time: bool,
|
||
|
|
pub(crate) datetime: NaiveDateTime,
|
||
|
|
}
|