use crate::data::{
ContactOps, PplOps, RelationOps, SigDateOps, TierDefaultOps, TierOps, TraitDefaultOps, TraitOps,
};
use crate::entities::ppl::Model;
use crate::entities::prelude::{Ppl, TierDefaults, TraitDefaults};
use crate::entities::{
contact, ppl, relation, sig_date, tier, tier_defaults, trait_defaults, traits,
};
use crate::PplError;
use chrono::NaiveDate;
use color_eyre::owo_colors::OwoColorize;
use crossterm::event;
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use enum_iterator::{next, Sequence};
use ratatui::prelude::{Constraint, Direction, Layout, Line, Modifier, Span, Style, Stylize, Text};
use ratatui::style::palette::material::AMBER;
use ratatui::style::palette::tailwind::{GREEN, ORANGE, SLATE, WHITE};
use ratatui::style::{Color, Styled};
use ratatui::widgets::{Block, Borders, List, ListDirection, ListItem, ListState, Paragraph, Wrap};
use ratatui::{DefaultTerminal, Frame};
use sea_orm::DatabaseConnection;
use std::ops::Index;
use tokio::task::id;
use tui_input::backend::crossterm::EventHandler;
use tui_input::Input;
struct Tui {
current_tab: Tabs,
sigdate_list: Vec<sig_date::Model>,
trait_list: Vec<traits::Model>,
tier_list: Vec<tier::Model>,
rel_list: Vec<relation::Model>,
contact_list: Vec<contact::Model>,
ppl_list: Vec<Model>,
ppl_state: ListState,
ppl_editing: bool,
ppl_field_editing: bool,
ppl_field_idx: u8,
ppl_editables: Vec<Editable>,
ppl_input_a: Input,
ppl_input_b: Input,
ppl_input_c: Input,
ppl_detail_state: ListState,
def_tier_list: Vec<tier_defaults::Model>,
def_tier_state: ListState,
def_trait_list: Vec<trait_defaults::Model>,
def_trait_state: ListState,
}
#[derive(Debug, PartialEq, Sequence, Clone)]
enum Tabs {
Ppl,
Calendar,
TierSettings,
TraitSettings,
}
#[derive(Clone, Debug)]
pub enum Editablez {
Trait,
Tier,
Contact,
Relation,
SigDate,
}
#[derive(Clone, Debug)]
pub struct Editable {
tgt: Editablez,
pub id: i32,
pub first: String,
pub second: Option<String>,
pub third: Option<String>,
}
fn key_in_tier(key: String, tiers: &Vec<tier_defaults::Model>) -> bool {
let t = tiers
.iter()
.map(|t| t.key.to_string())
.collect::<Vec<String>>();
t.contains(&key)
}
fn get_key_in_tier(key: String, tiers: &Vec<tier_defaults::Model>) -> bool {
let t = tiers
.iter()
.map(|t| t.key.to_string())
.collect::<Vec<String>>();
tiers.iter().find(|t| t.key == key);
t.contains(&key)
}
impl Tui {
async fn reload(&mut self, db: &DatabaseConnection) -> Result<(), PplError> {
self.ppl_list = PplOps::list(db).await?;
self.def_trait_list = TraitDefaultOps::list(db).await?;
self.def_tier_list = TierDefaultOps::list(db).await?;
self.tier_list = TierOps::list(db).await?;
self.trait_list = TraitOps::list(db).await?;
self.rel_list = RelationOps::list(db).await?;
self.contact_list = ContactOps::list(db).await?;
self.sigdate_list = SigDateOps::list(db).await?;
Ok(())
}
}
impl Default for Tui {
fn default() -> Self {
Tui {
current_tab: Tabs::Ppl,
sigdate_list: vec![],
trait_list: vec![],
tier_list: vec![],
rel_list: vec![],
contact_list: vec![],
ppl_list: vec![],
ppl_state: ListState::default(),
ppl_editing: false,
ppl_field_editing: false,
ppl_field_idx: 0,
ppl_editables: vec![],
ppl_input_a: Input::default(),
ppl_input_b: Input::default(),
ppl_input_c: Input::default(),
ppl_detail_state: ListState::default(),
def_tier_list: vec![],
def_tier_state: ListState::default(),
def_trait_list: vec![],
def_trait_state: ListState::default(),
}
}
}
fn get_style(mine: u8, cursor: u8, total: u8) -> Color {
if cursor % total == mine {
WHITE
} else {
SLATE.c500
}
}
pub async fn run_tui(
mut terminal: DefaultTerminal,
db: DatabaseConnection,
) -> Result<(), PplError> {
let mut app = Tui::default();
app.reload(&db).await?;
loop {
terminal.draw(|f| render(f, &mut app));
match event::read()? {
Event::Key(key_event) if key_event.kind == KeyEventKind::Press => {
match key_event.code {
KeyCode::Up => match app.current_tab {
Tabs::Ppl => match app.ppl_editing {
true => match app.ppl_field_editing {
true => {}
false => app.ppl_detail_state.scroll_up_by(1),
},
false => app.ppl_state.scroll_up_by(1),
},
Tabs::TierSettings => app.def_tier_state.scroll_up_by(1),
Tabs::TraitSettings => app.def_trait_state.scroll_up_by(1),
_ => {}
},
KeyCode::Down => match app.current_tab {
Tabs::Ppl => match app.ppl_editing {
true => match app.ppl_field_editing {
true => {}
false => app.ppl_detail_state.scroll_down_by(1),
},
false => app.ppl_state.scroll_down_by(1),
},
Tabs::TierSettings => app.def_tier_state.scroll_down_by(1),
Tabs::TraitSettings => app.def_trait_state.scroll_down_by(1),
_ => {}
},
KeyCode::Tab => match app.ppl_field_editing {
true => {
app.ppl_field_idx = app.ppl_field_idx + 1;
}
false => {
app.current_tab = next(&app.current_tab).unwrap_or_else(|| Tabs::Ppl)
}
},
KeyCode::Esc => break Ok(()),
KeyCode::Char('e') => match app.current_tab {
Tabs::Ppl => match app.ppl_field_editing {
true => {
default_editing_handler(&mut app, &key_event);
}
false => {
app.ppl_editing = !app.ppl_editing;
}
},
_ => {}
},
KeyCode::Char('f') => {
default_editing_handler(&mut app, &key_event);
}
KeyCode::Enter => match app.current_tab {
Tabs::Ppl => match app.ppl_editing {
true => {
if app.ppl_detail_state.selected().is_some() {
let idx = &app.ppl_detail_state.selected().unwrap();
let e = app.ppl_editables.get(*idx).unwrap();
if !app.ppl_field_editing {
// let k
app.ppl_input_a = e.first.clone().into();
app.ppl_input_b =
e.second.clone().unwrap_or("".to_string()).into();
app.ppl_input_c =
e.third.clone().unwrap_or("".to_string()).into();
} else {
let new_editable = Editable {
tgt: e.tgt.clone(),
id: e.id,
first: app.ppl_input_a.value().parse().unwrap(),
second: Option::from(
app.ppl_input_b.value().to_string(),
),
third: Option::from(
app.ppl_input_c.value().to_string(),
),
};
match e.tgt {
Editablez::Trait => {
TraitOps::updatee(&db, new_editable).await;
}
Editablez::Tier => {
TierOps::updatee(&db, new_editable).await;
}
Editablez::Contact => {
ContactOps::updatee(&db, new_editable).await;
}
Editablez::Relation => {
RelationOps::updatee(&db, new_editable).await;
}
Editablez::SigDate => {
SigDateOps::updatee(&db, new_editable).await;
}
};
app.reload(&db).await?;
}
app.ppl_field_editing = !app.ppl_field_editing;
}
}
false => {}
},
_ => {}
},
_ => {
default_editing_handler(&mut app, &key_event);
}
}
}
_ => {}
}
}
}
fn default_editing_handler(app: &mut Tui, key_event: &KeyEvent) {
match app.ppl_editing {
true => {
let idx = &app.ppl_detail_state.selected().unwrap();
let e = app.ppl_editables.get(*idx).unwrap();
match e.tgt {
Editablez::Trait => match app.ppl_field_idx % 2 {
0 => {
app.ppl_input_a.handle_event(&Event::Key(*key_event));
}
1 => {
app.ppl_input_b.handle_event(&Event::Key(*key_event));
}
_ => {}
},
Editablez::Tier => match app.ppl_field_idx % 3 {
0 => {
app.ppl_input_a.handle_event(&Event::Key(*key_event));
}
1 => {
app.ppl_input_b.handle_event(&Event::Key(*key_event));
}
2 => {
app.ppl_input_c.handle_event(&Event::Key(*key_event));
}
_ => {}
},
Editablez::Contact => match app.ppl_field_idx % 3 {
0 => {
app.ppl_input_a.handle_event(&Event::Key(*key_event));
}
1 => {
app.ppl_input_b.handle_event(&Event::Key(*key_event));
}
2 => {
app.ppl_input_c.handle_event(&Event::Key(*key_event));
}
_ => {}
},
Editablez::Relation => match app.ppl_field_idx % 3 {
0 => {
app.ppl_input_a.handle_event(&Event::Key(*key_event));
}
1 => {
app.ppl_input_b.handle_event(&Event::Key(*key_event));
}
2 => {
app.ppl_input_c.handle_event(&Event::Key(*key_event));
}
_ => {}
},
Editablez::SigDate => match app.ppl_field_idx % 3 {
0 => {
app.ppl_input_a.handle_event(&Event::Key(*key_event));
}
1 => {
app.ppl_input_b.handle_event(&Event::Key(*key_event));
}
2 => {
app.ppl_input_c.handle_event(&Event::Key(*key_event));
}
_ => {}
},
}
match app.ppl_field_idx {
_ => {}
}
}
false => {}
}
}
fn render(f: &mut Frame, app: &mut Tui) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints([Constraint::Length(1), Constraint::Length(15)].as_ref())
.split(f.area());
let msgw = vec![
Span::raw("welcome to "),
Span::styled(
"ppl",
Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
),
Span::raw(" :)"),
];
let textw = Text::from(Line::from(msgw)).style(Style::default());
let welcome = Paragraph::new(textw);
f.render_widget(welcome, chunks[0]);
match app.current_tab {
Tabs::Ppl => {
let layout_ppl = Layout::default()
.direction(Direction::Horizontal)
.margin(0)
.constraints([Constraint::Percentage(25), Constraint::Percentage(75)])
.split(chunks[1]);
let layout_edit = Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints([
Constraint::Length(1),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
])
.split(layout_ppl[1]);
let ppl = List::new(&app.ppl_list)
.block(Block::bordered().title("ppl"))
.style(Style::new().white())
.highlight_style(Style::new().italic())
.highlight_symbol(">>")
.repeat_highlight_symbol(true)
.direction(ListDirection::TopToBottom);
f.render_stateful_widget(&ppl, layout_ppl[0], &mut app.ppl_state);
let idx = app.ppl_state.selected();
match idx {
None => {}
Some(val) => {
let curr = app.ppl_list.index(val);
let mut msgs: Vec<Line> = vec![];
let mut editables: Vec<Editable> = vec![];
app.trait_list.sort_by_key(|k| {
(
key_in_tier(k.key.clone(), &app.def_tier_list),
k.key.clone(),
k.value.clone(),
)
});
let curr_traits = app
.trait_list
.iter()
.filter(|t| t.ppl_id == curr.id)
.map(|t| t.to_owned())
.collect::<Vec<traits::Model>>();
let trait_defaults = app
.def_trait_list
.iter()
.filter(|t| t.is_date == false && t.is_contact == false)
.collect::<Vec<&trait_defaults::Model>>();
for t in &curr_traits {
editables.push(Editable {
tgt: Editablez::Trait,
id: t.id.clone(),
first: t.key.clone(),
second: Option::from(t.value.clone()),
third: t.hidden.clone().to_string().into(),
});
match trait_defaults.iter().find(|f| f.key == t.key) {
None => msgs.push(Line::from(vec![
Span::styled(
format!("ℹ️ {}: ", t.key.clone()),
Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
),
Span::raw(t.value.clone()),
])),
Some(td) => msgs.push(Line::from(vec![
Span::styled(
format!("{} {}: ", td.symbol, t.key.clone()),
Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
),
Span::raw(t.value.clone()),
])),
}
}
let curr_tiers = app
.tier_list
.iter()
.filter(|t| t.ppl_id == curr.id)
.map(|t| t.to_owned())
.collect::<Vec<tier::Model>>();
for t in &curr_tiers {
let othersym = app.def_tier_list.iter().find(|dt| dt.key == t.name);
editables.push(Editable {
id: t.id.clone(),
tgt: Editablez::Tier,
first: t.name.clone(),
second: Option::from(t.color.clone()),
third: Option::from(t.symbol.clone()),
});
match (&t.symbol, othersym) {
(None, Some(other)) => msgs.push(Line::from(vec![
Span::styled(
format!(
"{} {}: ",
&other.symbol.clone().unwrap(),
t.name.clone()
),
Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
),
Span::raw(t.name.clone()),
])),
(Some(main), None) => msgs.push(Line::from(vec![
Span::styled(
format!("{} {}: ", main, t.name.clone()),
Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
),
Span::raw(t.name.clone()),
])),
(None, None) => msgs.push(Line::from(vec![
Span::styled(
format!("ℹ️ {}: ", t.name.clone()),
Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
),
Span::raw(t.name.clone()),
])),
// _ => {}
(Some(main), Some(other)) => msgs.push(Line::from(vec![
Span::styled(
format!("{} {}: ", main, t.name.clone()),
Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
),
Span::raw(t.name.clone()),
])),
}
}
let curr_rels = app
.rel_list
.iter()
.filter(|t| t.ppl_id_b == curr.id)
.map(|t| t.to_owned())
.collect::<Vec<relation::Model>>();
for t in &curr_rels {
let i2 = match t.date_entered {
None => None,
Some(d) => Some(d.to_string()),
};
let i3 = match t.date_ended {
None => None,
Some(d) => Some(d.to_string()),
};
editables.push(Editable {
id: t.id.clone(),
tgt: Editablez::Relation,
first: t.r#type.clone(),
second: i2,
third: i3,
});
msgs.push(Line::from(vec![
Span::styled(
format!("🫂 {}: ", t.r#type.clone()),
Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
),
Span::raw(format!("{:?} - {:?}", t.date_entered, t.date_ended)),
]))
}
let curr_dates = app
.sigdate_list
.iter()
.filter(|t| t.ppl_id == curr.id)
.map(|t| t.to_owned())
.collect::<Vec<sig_date::Model>>();
let date_traits = app
.def_trait_list
.iter()
.filter(|t| t.is_date == true && t.enabled == true)
.collect::<Vec<&trait_defaults::Model>>();
for t in &curr_dates {
editables.push(Editable {
id: t.id.clone(),
tgt: Editablez::SigDate,
first: t.event.clone(),
second: Option::from(t.date.to_string()),
third: Option::from(t.do_remind.to_string()),
});
match (date_traits.iter().find(|f| f.key == t.event), t.do_remind) {
(Some(dt), _) => msgs.push(Line::from(vec![
Span::styled(
format!("{} {}: ", dt.symbol, t.event.clone()),
Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
),
Span::raw(t.date.clone().to_string()),
])),
(None, true) => msgs.push(Line::from(vec![
Span::styled(
format!("⏰ {}: ", t.event.clone()),
Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
),
Span::raw(t.date.clone().to_string()),
])),
(None, false) => msgs.push(Line::from(vec![
Span::styled(
format!("📅 {}: ", t.event.clone()),
Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
),
Span::raw(t.date.clone().to_string()),
])),
}
}
let contacts = app
.contact_list
.iter()
.filter(|t| t.ppl_id == curr.id)
.map(|t| t.to_owned())
.collect::<Vec<contact::Model>>();
let contact_traits = app
.def_trait_list
.iter()
.filter(|t| t.is_contact == true && t.enabled == true)
.collect::<Vec<&trait_defaults::Model>>();
for t in &contacts {
editables.push(Editable {
id: t.id.clone(),
tgt: Editablez::Contact,
first: t.r#type.clone(),
second: Option::from(t.designator.clone().unwrap_or("".to_string())),
third: Option::from(t.value.clone()),
});
match contact_traits.iter().find(|f| f.key == t.r#type) {
Some(dt) => msgs.push(Line::from(vec![
Span::styled(
format!("{} {}: ", dt.symbol, t.r#type.clone()),
Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
),
Span::raw(t.value.clone().to_string()),
])),
None => msgs.push(Line::from(vec![
Span::styled(
format!("📇 {}: ", t.r#type.clone()),
Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
),
Span::raw(t.value.clone().to_string()),
])),
}
}
app.ppl_editables = editables.clone();
let list_contact = List::new(msgs.clone())
.block(Block::bordered().title("Circles"))
.style(Style::new().white())
.highlight_style(Style::new().italic())
.highlight_symbol(">>")
.repeat_highlight_symbol(true)
.direction(ListDirection::TopToBottom);
let textw = Text::from(msgs.clone()).style(Style::default());
let paragraph = Paragraph::new(textw);
let block = match app.ppl_editing {
true => match app.ppl_field_editing {
true => {
let ced = editables
.get(app.ppl_detail_state.selected().unwrap())
.unwrap();
match { ced.tgt.clone() } {
Editablez::Trait => {
let input_a = Paragraph::new(app.ppl_input_a.value())
.block(
Block::default().borders(Borders::ALL).title("key"),
)
.style(get_style(0, app.ppl_field_idx, 2));
f.render_widget(input_a, layout_edit[1]);
let input_b = Paragraph::new(app.ppl_input_b.value())
.block(
Block::default()
.borders(Borders::ALL)
.title("value"),
)
.style(get_style(1, app.ppl_field_idx, 2));
f.render_widget(input_b, layout_edit[2]);
}
Editablez::Tier => {
let input_b = Paragraph::new(app.ppl_input_a.value())
.block(
Block::default()
.borders(Borders::ALL)
.title("name"),
)
.style(get_style(0, app.ppl_field_idx, 3));
f.render_widget(input_b, layout_edit[1]);
let input_b = Paragraph::new(app.ppl_input_b.value())
.block(
Block::default()
.borders(Borders::ALL)
.title("color"),
)
.style(get_style(1, app.ppl_field_idx, 3));
f.render_widget(input_b, layout_edit[2]);
let input_c = Paragraph::new(app.ppl_input_c.value())
.block(
Block::default()
.borders(Borders::ALL)
.title("symbol"),
)
.style(get_style(2, app.ppl_field_idx, 3));
f.render_widget(input_c, layout_edit[3]);
}
Editablez::Contact => {
let input_a = Paragraph::new(app.ppl_input_a.value())
.block(
Block::default().borders(Borders::ALL).title("typ"),
)
.style(get_style(0, app.ppl_field_idx, 3));
f.render_widget(input_a, layout_edit[1]);
let input_b = Paragraph::new(app.ppl_input_b.value())
.block(
Block::default()
.borders(Borders::ALL)
.title("designator"),
)
.style(get_style(1, app.ppl_field_idx, 3));
f.render_widget(input_b, layout_edit[2]);
let input_c = Paragraph::new(app.ppl_input_c.value())
.block(
Block::default()
.borders(Borders::ALL)
.title("value"),
)
.style(get_style(2, app.ppl_field_idx, 3));
f.render_widget(input_c, layout_edit[3]);
}
Editablez::Relation => {
let input_a = Paragraph::new(app.ppl_input_a.value())
.block(
Block::default().borders(Borders::ALL).title("key"),
)
.style(get_style(0, app.ppl_field_idx, 3));
f.render_widget(input_a, layout_edit[1]);
let input_b = Paragraph::new(app.ppl_input_b.value())
.block(
Block::default()
.borders(Borders::ALL)
.title("start"),
)
.style(get_style(1, app.ppl_field_idx, 3));
f.render_widget(input_b, layout_edit[2]);
let input_c = Paragraph::new(app.ppl_input_c.value())
.block(
Block::default().borders(Borders::ALL).title("end"),
)
.style(get_style(2, app.ppl_field_idx, 3));
f.render_widget(input_c, layout_edit[3]);
}
Editablez::SigDate => {
let input_a = Paragraph::new(app.ppl_input_a.value())
.block(
Block::default()
.borders(Borders::ALL)
.title("date"),
)
.style(get_style(0, app.ppl_field_idx, 3));
f.render_widget(input_a, layout_edit[1]);
let input_b = Paragraph::new(app.ppl_input_b.value())
.block(
Block::default()
.borders(Borders::ALL)
.title("date"),
)
.style(get_style(1, app.ppl_field_idx, 3));
f.render_widget(input_b, layout_edit[2]);
let input_c = Paragraph::new(app.ppl_input_c.value())
.block(
Block::default()
.borders(Borders::ALL)
.title("do_remind"),
)
.style(get_style(2, app.ppl_field_idx, 3));
f.render_widget(input_c, layout_edit[3]);
}
}
let block = Block::new()
.borders(Borders::LEFT)
.title(format!("Editing {}", curr.name.clone()))
.style(Style::default().fg(ORANGE.c500));
f.render_widget(paragraph.clone().block(block), layout_edit[0])
}
false => {
let block = Block::new()
.borders(Borders::LEFT)
.title(format!("Editing {}", curr.name.clone()))
.style(Style::default().fg(AMBER.c500));
f.render_stateful_widget(
list_contact.block(block),
layout_ppl[1],
&mut app.ppl_detail_state,
);
}
},
false => {
let block = Block::new()
.borders(Borders::ALL)
.title(format!("{}", curr.name.clone()));
f.render_widget(paragraph.clone().block(block), layout_ppl[1]);
}
};
}
}
}
Tabs::TierSettings => {
let ppl = List::new(&app.def_tier_list)
.block(Block::bordered().title("tiers"))
.style(Style::new().white())
.highlight_style(Style::new().italic())
.highlight_symbol(">>")
.repeat_highlight_symbol(true)
.direction(ListDirection::TopToBottom);
f.render_stateful_widget(ppl, chunks[1], &mut app.def_tier_state);
}
Tabs::TraitSettings => {
let ppl = List::new(&app.def_trait_list)
.block(Block::bordered().title("traits"))
.style(Style::new().white())
.highlight_style(Style::new().italic())
.highlight_symbol(">>")
.repeat_highlight_symbol(true)
.direction(ListDirection::TopToBottom);
f.render_stateful_widget(ppl, chunks[1], &mut app.def_trait_state);
}
_ => {}
}
}
impl From<&ppl::Model> for ListItem<'_> {
fn from(value: &Model) -> Self {
let line = match value.me {
true => Line::styled(format!(" 🌟 {}", value.name), WHITE),
false => Line::styled(format!(" {}", value.name), WHITE),
};
ListItem::new(line)
}
}
impl From<&tier_defaults::Model> for ListItem<'_> {
fn from(value: &tier_defaults::Model) -> Self {
let line = match value.enabled {
true => Line::styled(format!(" ✓ {:?} {}", value.symbol, value.key), WHITE),
false => Line::styled(format!(" ☐ {:?} {}", value.symbol, value.key), SLATE.c500),
};
ListItem::new(line)
}
}
impl From<&trait_defaults::Model> for ListItem<'_> {
fn from(value: &trait_defaults::Model) -> Self {
let line = match value.enabled {
true => Line::styled(format!(" ✓ {} {}", value.symbol, value.key), WHITE),
false => Line::styled(format!(" ☐ {} {}", value.symbol, value.key), SLATE.c500),
};
ListItem::new(line)
}
}
⭐
Log in to mark this repository.
37.5 KiB · text
History
429aa90