⭐
Log in to mark this repository.
calendar. default nick
f7cb98f · gmorell · 2025-01-12 05:25
Files changed
modified
Cargo.lock
+13
−0
@@ -1703,6 +1703,15 @@ dependencies = [
| 1703 | 1703 | "libm", |
| 1704 | 1704 | ] |
| 1705 | 1705 | |
| 1706 | +[[package]] | |
| 1707 | +name = "num_threads" | |
| 1708 | +version = "0.1.7" | |
| 1709 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1710 | +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" | |
| 1711 | +dependencies = [ | |
| 1712 | + "libc", | |
| 1713 | +] | |
| 1714 | + | |
| 1706 | 1715 | [[package]] |
| 1707 | 1716 | name = "object" |
| 1708 | 1717 | version = "0.32.2" |
@@ -1886,6 +1895,7 @@ dependencies = [
| 1886 | 1895 | "ratatui", |
| 1887 | 1896 | "sea-orm", |
| 1888 | 1897 | "sea-orm-migration", |
| 1898 | + "time", | |
| 1889 | 1899 | "tokio", |
| 1890 | 1900 | "tracing", |
| 1891 | 1901 | "tracing-error", |
@@ -2047,6 +2057,7 @@ dependencies = [
| 2047 | 2057 | "lru 0.12.1", |
| 2048 | 2058 | "paste", |
| 2049 | 2059 | "strum", |
| 2060 | + "time", | |
| 2050 | 2061 | "unicode-segmentation", |
| 2051 | 2062 | "unicode-truncate", |
| 2052 | 2063 | "unicode-width 0.2.0", |
@@ -2957,7 +2968,9 @@ checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
| 2957 | 2968 | dependencies = [ |
| 2958 | 2969 | "deranged", |
| 2959 | 2970 | "itoa", |
| 2971 | + "libc", | |
| 2960 | 2972 | "num-conv", |
| 2973 | + "num_threads", | |
| 2961 | 2974 | "powerfmt", |
| 2962 | 2975 | "serde", |
| 2963 | 2976 | "time-core", |
modified
Cargo.toml
+2
−1
@@ -9,7 +9,7 @@ edition = "2021"
| 9 | 9 | clap = { version = "4.4.18", features = ["derive"] } |
| 10 | 10 | color-eyre = "0.6" |
| 11 | 11 | crossterm = "0.28.1" |
| 12 | −ratatui = "0.29.0" | |
| 12 | +ratatui = { version = "0.29.0", features = ["widget-calendar"] } | |
| 13 | 13 | sea-orm = { version = "1.1.2" , features = ["sqlx-sqlite", "runtime-tokio", "macros"]} |
| 14 | 14 | wealthy = "0.2.0" |
| 15 | 15 | futures = "0.3.30" |
@@ -29,3 +29,4 @@ tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
| 29 | 29 | directories = "5.0.1" |
| 30 | 30 | lazy_static = "1.5.0" |
| 31 | 31 | colored = { version = "2.2.0" } |
| 32 | +time = "0.3.37" |
modified
src/data.rs
+13
−6
@@ -13,7 +13,7 @@ use color_eyre::owo_colors::OwoColorize;
| 13 | 13 | use log::warn; |
| 14 | 14 | use ratatui::style::palette::tailwind::PURPLE; |
| 15 | 15 | use sea_orm::ActiveValue::Set; |
| 16 | −use sea_orm::{ActiveModelTrait, DatabaseConnection, DbErr, EntityTrait}; | |
| 16 | +use sea_orm::{ActiveModelTrait, DatabaseConnection, DbErr, EntityTrait, NotSet}; | |
| 17 | 17 | |
| 18 | 18 | fn bool_from_string(input: Option<String>) -> bool { |
| 19 | 19 | if input.is_none() { |
@@ -118,6 +118,7 @@ impl ContactOps {
| 118 | 118 | am.designator = Set(designator); |
| 119 | 119 | am.r#type = Set(typ); |
| 120 | 120 | am.value = Set(value); |
| 121 | + am.date_up = Set(Local::today().naive_local()); | |
| 121 | 122 | am.update(db).await?; |
| 122 | 123 | Ok(()) |
| 123 | 124 | } |
@@ -128,13 +129,11 @@ impl ContactOps {
| 128 | 129 | pub struct PplOps {} |
| 129 | 130 | |
| 130 | 131 | impl PplOps { |
| 131 | − pub async fn create_me( | |
| 132 | − db: &DatabaseConnection, | |
| 133 | − name: String, | |
| 134 | − ) -> Result<ppl::Model, PplError> { | |
| 132 | + pub async fn create_me(db: &DatabaseConnection, name: String) -> Result<ppl::Model, PplError> { | |
| 135 | 133 | let p = ppl::ActiveModel { |
| 136 | 134 | id: Default::default(), |
| 137 | 135 | name: Set(name), |
| 136 | + nick: NotSet, | |
| 138 | 137 | me: Set(true), |
| 139 | 138 | date_ins: Set(Local::today().naive_local()), |
| 140 | 139 | date_up: Set(Local::today().naive_local()), |
@@ -156,6 +155,7 @@ impl PplOps {
| 156 | 155 | id: Default::default(), |
| 157 | 156 | name: Set(name), |
| 158 | 157 | me: Set(false), |
| 158 | + nick: NotSet, | |
| 159 | 159 | date_ins: Set(Local::today().naive_local()), |
| 160 | 160 | date_up: Set(Local::today().naive_local()), |
| 161 | 161 | }; |
@@ -190,6 +190,7 @@ impl PplOps {
| 190 | 190 | None => {} |
| 191 | 191 | Some(i) => am.name = Set(i), |
| 192 | 192 | } |
| 193 | + am.date_up = Set(Local::today().naive_local()); | |
| 193 | 194 | am.update(db).await?; |
| 194 | 195 | Ok(()) |
| 195 | 196 | } |
@@ -247,7 +248,9 @@ impl RelationOps {
| 247 | 248 | None, |
| 248 | 249 | option_naive_date_from_option_string(editable.second), |
| 249 | 250 | option_naive_date_from_option_string(editable.third), |
| 250 | − ).await.expect("huh"); | |
| 251 | + ) | |
| 252 | + .await | |
| 253 | + .expect("huh"); | |
| 251 | 254 | } |
| 252 | 255 | |
| 253 | 256 | pub async fn update( |
@@ -286,6 +289,7 @@ impl RelationOps {
| 286 | 289 | Some(date) => am.date_ended = Set(Option::from(date)), |
| 287 | 290 | } |
| 288 | 291 | am.r#type = Set(typ); |
| 292 | + am.date_up = Set(Local::today().naive_local()); | |
| 289 | 293 | am.update(db).await?; |
| 290 | 294 | Ok(()) |
| 291 | 295 | } |
@@ -362,6 +366,7 @@ impl SigDateOps {
| 362 | 366 | am.event = Set(event); |
| 363 | 367 | am.date = Set(date.unwrap_or_default()); |
| 364 | 368 | am.do_remind = Set(do_remind); |
| 369 | + am.date_up = Set(Local::today().naive_local()); | |
| 365 | 370 | am.update(db).await?; |
| 366 | 371 | Ok(()) |
| 367 | 372 | } |
@@ -431,6 +436,7 @@ impl TierOps {
| 431 | 436 | am.name = Set(name); |
| 432 | 437 | am.color = Set(color); |
| 433 | 438 | am.symbol = Set(symbol); |
| 439 | + am.date_up = Set(Local::today().naive_local()); | |
| 434 | 440 | am.update(db).await?; |
| 435 | 441 | Ok(()) |
| 436 | 442 | } |
@@ -506,6 +512,7 @@ impl TraitOps {
| 506 | 512 | Some(j) => am.value = Set(j), |
| 507 | 513 | } |
| 508 | 514 | am.hidden = Set(hidden); |
| 515 | + am.date_up = Set(Local::today().naive_local()); | |
| 509 | 516 | am.update(db).await?; |
| 510 | 517 | Ok(()) |
| 511 | 518 | } |
modified
src/do_add.rs
+4
−26
@@ -115,16 +115,7 @@ pub async fn do_add(args: &AddArgs, db: DatabaseConnection) -> Result<(), PplErr
| 115 | 115 | match &args.relation { |
| 116 | 116 | None => Ok(()), |
| 117 | 117 | Some(relation) => { |
| 118 | − RelationOps::create( | |
| 119 | − &db, | |
| 120 | − me.id, | |
| 121 | − pp.id, | |
| 122 | − relation.to_owned(), | |
| 123 | − false, | |
| 124 | − None, | |
| 125 | − None, | |
| 126 | − ) | |
| 127 | − .await | |
| 118 | + RelationOps::create(&db, me.id, pp.id, relation.to_owned(), false, None, None).await | |
| 128 | 119 | } |
| 129 | 120 | }?; |
| 130 | 121 |
@@ -134,14 +125,8 @@ pub async fn do_add(args: &AddArgs, db: DatabaseConnection) -> Result<(), PplErr
| 134 | 125 | let date = parse_date_string(met, Local::now(), Dialect::Us); |
| 135 | 126 | match date { |
| 136 | 127 | Ok(parsed) => { |
| 137 | − SigDateOps::create( | |
| 138 | − &db, | |
| 139 | − pp.id, | |
| 140 | − parsed.date_naive(), | |
| 141 | − "met".to_string(), | |
| 142 | − false, | |
| 143 | − ) | |
| 144 | − .await | |
| 128 | + SigDateOps::create(&db, pp.id, parsed.date_naive(), "met".to_string(), false) | |
| 129 | + .await | |
| 145 | 130 | } |
| 146 | 131 | Err(e) => { |
| 147 | 132 | warn!("failed to parse metdate {:?}", e.red()); |
@@ -154,14 +139,7 @@ pub async fn do_add(args: &AddArgs, db: DatabaseConnection) -> Result<(), PplErr
| 154 | 139 | match &args.from { |
| 155 | 140 | None => Ok(()), |
| 156 | 141 | Some(from) => { |
| 157 | − TraitOps::create( | |
| 158 | − &db, | |
| 159 | − pp.id, | |
| 160 | − "from".to_string(), | |
| 161 | − from.to_string(), | |
| 162 | − false, | |
| 163 | − ) | |
| 164 | − .await | |
| 142 | + TraitOps::create(&db, pp.id, "from".to_string(), from.to_string(), false).await | |
| 165 | 143 | } |
| 166 | 144 | }?; |
| 167 | 145 |
modified
src/do_init.rs
+4
−2
@@ -17,7 +17,7 @@ use ratatui::{
| 17 | 17 | use sea_orm::sqlx::types::chrono; |
| 18 | 18 | use sea_orm::sqlx::types::chrono::Local; |
| 19 | 19 | use sea_orm::ActiveValue::Set; |
| 20 | −use sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait}; | |
| 20 | +use sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait, NotSet}; | |
| 21 | 21 | use std::fmt; |
| 22 | 22 | use std::string::String; |
| 23 | 23 | use tui_input::backend::crossterm::EventHandler; |
@@ -438,6 +438,7 @@ pub async fn run_init(
| 438 | 438 | id: Default::default(), |
| 439 | 439 | name: Set(ofppl.clone()), |
| 440 | 440 | me: Set(false), |
| 441 | + nick: NotSet, | |
| 441 | 442 | date_ins: Set(Local::today().naive_local()), |
| 442 | 443 | date_up: Set(Local::today().naive_local()), |
| 443 | 444 | }; |
@@ -491,6 +492,7 @@ pub async fn run_init(
| 491 | 492 | id: Default::default(), |
| 492 | 493 | name: Set(ofppl.clone()), |
| 493 | 494 | me: Set(false), |
| 495 | + nick: NotSet, | |
| 494 | 496 | date_ins: Set(Local::today().naive_local()), |
| 495 | 497 | date_up: Set(Local::today().naive_local()), |
| 496 | 498 | }; |
@@ -1020,7 +1022,7 @@ const TEXT_FG_COLOR: Color = SLATE.c200;
| 1020 | 1022 | const COMPLETED_TEXT_FG_COLOR: Color = GREEN.c500; |
| 1021 | 1023 | const BLUEBLUE: Color = BLUE.c500; |
| 1022 | 1024 | |
| 1023 | −fn lcolor(input: &String) -> Color { | |
| 1025 | +pub fn lcolor(input: &String) -> Color { | |
| 1024 | 1026 | match input.to_lowercase().as_str() { |
| 1025 | 1027 | "red" => RED.c500, |
| 1026 | 1028 | "gold" => AMBER.c500, |
modified
src/do_tui.rs
+383
−60
@@ -1,29 +1,35 @@
| 1 | 1 | use crate::data::{ |
| 2 | 2 | ContactOps, PplOps, RelationOps, SigDateOps, TierDefaultOps, TierOps, TraitDefaultOps, TraitOps, |
| 3 | 3 | }; |
| 4 | +use crate::do_init::lcolor; | |
| 4 | 5 | use crate::entities::ppl::Model; |
| 6 | +use crate::entities::prelude::{TierDefaults, Traits}; | |
| 5 | 7 | use crate::entities::{ |
| 6 | 8 | contact, ppl, relation, sig_date, tier, tier_defaults, trait_defaults, traits, |
| 7 | 9 | }; |
| 8 | 10 | use crate::PplError; |
| 11 | +use chrono::{Datelike, NaiveDate}; | |
| 9 | 12 | use color_eyre::owo_colors::OwoColorize; |
| 10 | 13 | use crossterm::event; |
| 11 | 14 | use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind}; |
| 12 | 15 | use enum_iterator::{next, Sequence}; |
| 13 | 16 | use ratatui::prelude::{Constraint, Direction, Layout, Line, Modifier, Span, Style, Stylize, Text}; |
| 14 | 17 | use ratatui::style::palette::material::AMBER; |
| 15 | −use ratatui::style::palette::tailwind::{ORANGE, SLATE, WHITE}; | |
| 18 | +use ratatui::style::palette::tailwind::{GREEN, ORANGE, PINK, SLATE, TEAL, WHITE}; | |
| 16 | 19 | use ratatui::style::{Color, Styled}; |
| 20 | +use ratatui::widgets::calendar::{CalendarEventStore, Monthly}; | |
| 17 | 21 | use ratatui::widgets::{Block, Borders, List, ListDirection, ListItem, ListState, Paragraph}; |
| 18 | 22 | use ratatui::{DefaultTerminal, Frame}; |
| 19 | 23 | use sea_orm::DatabaseConnection; |
| 20 | 24 | use std::ops::Index; |
| 25 | +use time::{Date, Month, OffsetDateTime}; | |
| 21 | 26 | use tui_input::backend::crossterm::EventHandler; |
| 22 | 27 | use tui_input::Input; |
| 23 | 28 | |
| 24 | 29 | struct Tui { |
| 25 | 30 | current_tab: Tabs, |
| 26 | 31 | sigdate_list: Vec<sig_date::Model>, |
| 32 | + sigdate_cal_state: ListState, | |
| 27 | 33 | trait_list: Vec<traits::Model>, |
| 28 | 34 | tier_list: Vec<tier::Model>, |
| 29 | 35 | rel_list: Vec<relation::Model>, |
@@ -108,6 +114,7 @@ impl Default for Tui {
| 108 | 114 | Tui { |
| 109 | 115 | current_tab: Tabs::Ppl, |
| 110 | 116 | sigdate_list: vec![], |
| 117 | + sigdate_cal_state: ListState::default(), | |
| 111 | 118 | trait_list: vec![], |
| 112 | 119 | tier_list: vec![], |
| 113 | 120 | rel_list: vec![], |
@@ -138,6 +145,21 @@ fn get_style(mine: u8, cursor: u8, total: u8) -> Color {
| 138 | 145 | } |
| 139 | 146 | } |
| 140 | 147 | |
| 148 | +fn get_cal<'a>( | |
| 149 | + month: Month, | |
| 150 | + year: i32, | |
| 151 | + events: &CalendarEventStore, | |
| 152 | +) -> Monthly<'a, &CalendarEventStore> { | |
| 153 | + let default_style = Style::default() | |
| 154 | + .add_modifier(Modifier::BOLD) | |
| 155 | + .fg(AMBER.c500) | |
| 156 | + .bg(SLATE.c800); | |
| 157 | + | |
| 158 | + Monthly::new(Date::from_calendar_date(year, month, 1).unwrap(), events) | |
| 159 | + .show_month_header(Style::default()) | |
| 160 | + .default_style(default_style) | |
| 161 | +} | |
| 162 | + | |
| 141 | 163 | pub async fn run_tui( |
| 142 | 164 | mut terminal: DefaultTerminal, |
| 143 | 165 | db: DatabaseConnection, |
@@ -178,66 +200,68 @@ pub async fn run_tui(
| 178 | 200 | true => { |
| 179 | 201 | app.ppl_field_idx += 1; |
| 180 | 202 | } |
| 181 | − false => { | |
| 182 | − app.current_tab = next(&app.current_tab).unwrap_or(Tabs::Ppl) | |
| 183 | − } | |
| 203 | + false => app.current_tab = next(&app.current_tab).unwrap_or(Tabs::Ppl), | |
| 184 | 204 | }, |
| 185 | 205 | KeyCode::Esc => break Ok(()), |
| 186 | − KeyCode::Char('e') => if app.current_tab == Tabs::Ppl { match app.ppl_field_editing { | |
| 187 | − true => { | |
| 188 | − default_editing_handler(&mut app, &key_event); | |
| 189 | − } | |
| 190 | − false => { | |
| 191 | − app.ppl_editing = !app.ppl_editing; | |
| 206 | + KeyCode::Char('e') => { | |
| 207 | + if app.current_tab == Tabs::Ppl { | |
| 208 | + match app.ppl_field_editing { | |
| 209 | + true => { | |
| 210 | + default_editing_handler(&mut app, &key_event); | |
| 211 | + } | |
| 212 | + false => { | |
| 213 | + app.ppl_editing = !app.ppl_editing; | |
| 214 | + } | |
| 215 | + } | |
| 192 | 216 | } |
| 193 | − } }, | |
| 217 | + } | |
| 194 | 218 | KeyCode::Char('f') => { |
| 195 | 219 | default_editing_handler(&mut app, &key_event); |
| 196 | 220 | } |
| 197 | − KeyCode::Enter => if app.current_tab == Tabs::Ppl { if app.ppl_editing && app.ppl_detail_state.selected().is_some() { | |
| 198 | − let idx = &app.ppl_detail_state.selected().unwrap(); | |
| 199 | − let e = app.ppl_editables.get(*idx).unwrap(); | |
| 200 | − if !app.ppl_field_editing { | |
| 201 | − // let k | |
| 202 | − app.ppl_input_a = e.first.clone().into(); | |
| 203 | − app.ppl_input_b = | |
| 204 | − e.second.clone().unwrap_or("".to_string()).into(); | |
| 205 | − app.ppl_input_c = | |
| 206 | − e.third.clone().unwrap_or("".to_string()).into(); | |
| 207 | − } else { | |
| 208 | − let new_editable = Editable { | |
| 209 | − tgt: e.tgt.clone(), | |
| 210 | − id: e.id, | |
| 211 | − first: app.ppl_input_a.value().parse().unwrap(), | |
| 212 | − second: Option::from( | |
| 213 | − app.ppl_input_b.value().to_string(), | |
| 214 | − ), | |
| 215 | − third: Option::from( | |
| 216 | − app.ppl_input_c.value().to_string(), | |
| 217 | − ), | |
| 218 | − }; | |
| 221 | + KeyCode::Enter => { | |
| 222 | + if app.current_tab == Tabs::Ppl { | |
| 223 | + if app.ppl_editing && app.ppl_detail_state.selected().is_some() { | |
| 224 | + let idx = &app.ppl_detail_state.selected().unwrap(); | |
| 225 | + let e = app.ppl_editables.get(*idx).unwrap(); | |
| 226 | + if !app.ppl_field_editing { | |
| 227 | + // let k | |
| 228 | + app.ppl_input_a = e.first.clone().into(); | |
| 229 | + app.ppl_input_b = | |
| 230 | + e.second.clone().unwrap_or("".to_string()).into(); | |
| 231 | + app.ppl_input_c = | |
| 232 | + e.third.clone().unwrap_or("".to_string()).into(); | |
| 233 | + } else { | |
| 234 | + let new_editable = Editable { | |
| 235 | + tgt: e.tgt.clone(), | |
| 236 | + id: e.id, | |
| 237 | + first: app.ppl_input_a.value().parse().unwrap(), | |
| 238 | + second: Option::from(app.ppl_input_b.value().to_string()), | |
| 239 | + third: Option::from(app.ppl_input_c.value().to_string()), | |
| 240 | + }; | |
| 219 | 241 | |
| 220 | − match e.tgt { | |
| 221 | − Editablez::Trait => { | |
| 222 | − TraitOps::updatee(&db, new_editable).await; | |
| 223 | − } | |
| 224 | − Editablez::Tier => { | |
| 225 | − TierOps::updatee(&db, new_editable).await; | |
| 226 | − } | |
| 227 | − Editablez::Contact => { | |
| 228 | − ContactOps::updatee(&db, new_editable).await; | |
| 229 | − } | |
| 230 | − Editablez::Relation => { | |
| 231 | − RelationOps::updatee(&db, new_editable).await; | |
| 242 | + match e.tgt { | |
| 243 | + Editablez::Trait => { | |
| 244 | + TraitOps::updatee(&db, new_editable).await; | |
| 245 | + } | |
| 246 | + Editablez::Tier => { | |
| 247 | + TierOps::updatee(&db, new_editable).await; | |
| 248 | + } | |
| 249 | + Editablez::Contact => { | |
| 250 | + ContactOps::updatee(&db, new_editable).await; | |
| 251 | + } | |
| 252 | + Editablez::Relation => { | |
| 253 | + RelationOps::updatee(&db, new_editable).await; | |
| 254 | + } | |
| 255 | + Editablez::SigDate => { | |
| 256 | + SigDateOps::updatee(&db, new_editable).await; | |
| 257 | + } | |
| 258 | + }; | |
| 259 | + app.reload(&db).await?; | |
| 232 | 260 | } |
| 233 | − Editablez::SigDate => { | |
| 234 | − SigDateOps::updatee(&db, new_editable).await; | |
| 235 | − } | |
| 236 | − }; | |
| 237 | − app.reload(&db).await?; | |
| 261 | + app.ppl_field_editing = !app.ppl_field_editing; | |
| 262 | + } | |
| 238 | 263 | } |
| 239 | − app.ppl_field_editing = !app.ppl_field_editing; | |
| 240 | − } }, | |
| 264 | + } | |
| 241 | 265 | _ => { |
| 242 | 266 | default_editing_handler(&mut app, &key_event); |
| 243 | 267 | } |
@@ -320,7 +344,14 @@ fn render(f: &mut Frame, app: &mut Tui) {
| 320 | 344 | let chunks = Layout::default() |
| 321 | 345 | .direction(Direction::Vertical) |
| 322 | 346 | .margin(2) |
| 323 | − .constraints([Constraint::Length(1), Constraint::Length(15)].as_ref()) | |
| 347 | + .constraints( | |
| 348 | + [ | |
| 349 | + Constraint::Length(1), | |
| 350 | + Constraint::Percentage(100), | |
| 351 | + Constraint::Length(1), | |
| 352 | + ] | |
| 353 | + .as_ref(), | |
| 354 | + ) | |
| 324 | 355 | .split(f.area()); |
| 325 | 356 | |
| 326 | 357 | let msgw = vec![ |
@@ -337,6 +368,45 @@ fn render(f: &mut Frame, app: &mut Tui) {
| 337 | 368 | |
| 338 | 369 | match app.current_tab { |
| 339 | 370 | Tabs::Ppl => { |
| 371 | + match app.ppl_editing { | |
| 372 | + true => { | |
| 373 | + let msgw = vec![ | |
| 374 | + Span::styled( | |
| 375 | + "Enter", | |
| 376 | + Style::default().add_modifier(Modifier::BOLD).fg(WHITE), | |
| 377 | + ), | |
| 378 | + Span::raw(" to modify a ppl entry. "), | |
| 379 | + Span::styled( | |
| 380 | + "Shift+Enter", | |
| 381 | + Style::default().add_modifier(Modifier::BOLD).fg(WHITE), | |
| 382 | + ), | |
| 383 | + Span::raw(" to add a ppl entry"), | |
| 384 | + ]; | |
| 385 | + let textw = Text::from(Line::from(msgw)).style(Style::default()); | |
| 386 | + let welcome = Paragraph::new(textw); | |
| 387 | + f.render_widget(welcome, chunks[2]); | |
| 388 | + } | |
| 389 | + false => { | |
| 390 | + let msgw = vec![ | |
| 391 | + Span::styled( | |
| 392 | + "Tab", | |
| 393 | + Style::default().add_modifier(Modifier::BOLD).fg(WHITE), | |
| 394 | + ), | |
| 395 | + Span::raw(" to switch views"), | |
| 396 | + Span::styled( | |
| 397 | + "Enter", | |
| 398 | + Style::default().add_modifier(Modifier::BOLD).fg(WHITE), | |
| 399 | + ), | |
| 400 | + Span::raw(" to add ppl. "), | |
| 401 | + Span::styled("E", Style::default().add_modifier(Modifier::BOLD).fg(WHITE)), | |
| 402 | + Span::raw(" to edit the selected ppl. "), | |
| 403 | + ]; | |
| 404 | + let textw = Text::from(Line::from(msgw)).style(Style::default()); | |
| 405 | + let welcome = Paragraph::new(textw); | |
| 406 | + f.render_widget(welcome, chunks[2]); | |
| 407 | + } | |
| 408 | + } | |
| 409 | + | |
| 340 | 410 | let layout_ppl = Layout::default() |
| 341 | 411 | .direction(Direction::Horizontal) |
| 342 | 412 | .margin(0) |
@@ -353,7 +423,22 @@ fn render(f: &mut Frame, app: &mut Tui) {
| 353 | 423 | Constraint::Length(3), |
| 354 | 424 | ]) |
| 355 | 425 | .split(layout_ppl[1]); |
| 356 | − let ppl = List::new(&app.ppl_list) | |
| 426 | + let ppls: Vec<PplAndProps> = app | |
| 427 | + .ppl_list | |
| 428 | + .iter() | |
| 429 | + .map(|p| PplAndProps { | |
| 430 | + ppl: p.clone(), | |
| 431 | + tiers: app | |
| 432 | + .tier_list | |
| 433 | + .iter() | |
| 434 | + .filter(|t| t.ppl_id == p.id) | |
| 435 | + .map(|t| t.to_owned()) | |
| 436 | + .collect::<Vec<tier::Model>>(), | |
| 437 | + tier_defaults: app.def_tier_list.clone(), | |
| 438 | + }) | |
| 439 | + .collect::<Vec<PplAndProps>>(); | |
| 440 | + | |
| 441 | + let ppl = List::new(ppls) | |
| 357 | 442 | .block(Block::bordered().title("ppl")) |
| 358 | 443 | .style(Style::new().white()) |
| 359 | 444 | .highlight_style(Style::new().italic()) |
@@ -717,17 +802,32 @@ fn render(f: &mut Frame, app: &mut Tui) {
| 717 | 802 | f.render_widget(input_c, layout_edit[3]); |
| 718 | 803 | } |
| 719 | 804 | } |
| 720 | − | |
| 805 | + let title = match &curr.nick { | |
| 806 | + None => { | |
| 807 | + format!("Editing {}", curr.name.clone()) | |
| 808 | + } | |
| 809 | + Some(n) => { | |
| 810 | + format!("Editing {} ({})", curr.name.clone(), n) | |
| 811 | + } | |
| 812 | + }; | |
| 721 | 813 | let block = Block::new() |
| 722 | 814 | .borders(Borders::LEFT) |
| 723 | − .title(format!("Editing {}", curr.name.clone())) | |
| 815 | + .title(title) | |
| 724 | 816 | .style(Style::default().fg(ORANGE.c500)); |
| 725 | 817 | f.render_widget(paragraph.clone().block(block), layout_edit[0]) |
| 726 | 818 | } |
| 727 | 819 | false => { |
| 820 | + let title = match &curr.nick { | |
| 821 | + None => { | |
| 822 | + format!("Editing {}", curr.name.clone()) | |
| 823 | + } | |
| 824 | + Some(n) => { | |
| 825 | + format!("Editing {} ({})", curr.name.clone(), n) | |
| 826 | + } | |
| 827 | + }; | |
| 728 | 828 | let block = Block::new() |
| 729 | 829 | .borders(Borders::LEFT) |
| 730 | − .title(format!("Editing {}", curr.name.clone())) | |
| 830 | + .title(title) | |
| 731 | 831 | .style(Style::default().fg(AMBER.c500)); |
| 732 | 832 | f.render_stateful_widget( |
| 733 | 833 | list_contact.block(block), |
@@ -737,15 +837,123 @@ fn render(f: &mut Frame, app: &mut Tui) {
| 737 | 837 | } |
| 738 | 838 | }, |
| 739 | 839 | false => { |
| 740 | − let block = Block::new() | |
| 741 | − .borders(Borders::ALL) | |
| 742 | − .title(curr.name.clone().to_string()); | |
| 840 | + let title = match &curr.nick { | |
| 841 | + None => { | |
| 842 | + format!("{}", curr.name.clone()) | |
| 843 | + } | |
| 844 | + Some(n) => { | |
| 845 | + format!("{} ({})", curr.name.clone(), n) | |
| 846 | + } | |
| 847 | + }; | |
| 848 | + let block = Block::new().borders(Borders::ALL).title(title); | |
| 743 | 849 | f.render_widget(paragraph.clone().block(block), layout_ppl[1]); |
| 744 | 850 | } |
| 745 | 851 | }; |
| 746 | 852 | } |
| 747 | 853 | } |
| 748 | 854 | } |
| 855 | + Tabs::Calendar => { | |
| 856 | + let layout_cal = Layout::default() | |
| 857 | + .direction(Direction::Horizontal) | |
| 858 | + .margin(0) | |
| 859 | + .constraints([Constraint::Percentage(26), Constraint::Percentage(74)]) | |
| 860 | + .split(chunks[1]); | |
| 861 | + | |
| 862 | + let mut start = OffsetDateTime::now_local() | |
| 863 | + .unwrap() | |
| 864 | + .date() | |
| 865 | + .replace_month(Month::January) | |
| 866 | + .unwrap() | |
| 867 | + .replace_day(1) | |
| 868 | + .unwrap(); | |
| 869 | + &app.sigdate_list.sort_by(|a, b| a.date.cmp(&b.date)); | |
| 870 | + let as_upcoming = &app | |
| 871 | + .sigdate_list | |
| 872 | + .iter() | |
| 873 | + .map(|mut d| { | |
| 874 | + let mut n = d.clone(); | |
| 875 | + n.date = NaiveDate::from_ymd(start.year(), n.date.month(), n.date.day()); | |
| 876 | + let p = app | |
| 877 | + .ppl_list | |
| 878 | + .iter() | |
| 879 | + .find(|p| p.id == n.ppl_id) | |
| 880 | + .unwrap() | |
| 881 | + .clone(); | |
| 882 | + let tr = app | |
| 883 | + .trait_list | |
| 884 | + .iter() | |
| 885 | + .filter(|p| p.id == n.ppl_id) | |
| 886 | + .map(|t| t.to_owned()) | |
| 887 | + .collect::<Vec<traits::Model>>(); | |
| 888 | + crate::do_tui::SigDateAndProps { | |
| 889 | + date: n, | |
| 890 | + ppl: p, | |
| 891 | + traits: tr, | |
| 892 | + trait_defaults: app.def_trait_list.clone(), | |
| 893 | + } | |
| 894 | + }) | |
| 895 | + .collect::<Vec<SigDateAndProps>>(); | |
| 896 | + let mut event_list = CalendarEventStore::today( | |
| 897 | + Style::default() | |
| 898 | + .add_modifier(Modifier::BOLD) | |
| 899 | + .bg(Color::Blue), | |
| 900 | + ); | |
| 901 | + | |
| 902 | + for u in as_upcoming { | |
| 903 | + event_list.add( | |
| 904 | + // Date::from_calendar_date(start.year(), Month::try_from(4 as u8).expect("oops"), 15 as u8).expect("doubleoops"), | |
| 905 | + Date::from_calendar_date( | |
| 906 | + start.year(), | |
| 907 | + Month::try_from(u.date.date.month() as u8).expect("oops"), | |
| 908 | + u.date.date.day() as u8, | |
| 909 | + ) | |
| 910 | + .expect("doubleoops"), | |
| 911 | + Style::default() | |
| 912 | + .add_modifier(Modifier::BOLD) | |
| 913 | + .fg(GREEN.c300) | |
| 914 | + .bg(PINK.c500), | |
| 915 | + ); | |
| 916 | + } | |
| 917 | + | |
| 918 | + // let _ = as_upcoming.iter().map(|u| { | |
| 919 | + // let _ = event_list.add( | |
| 920 | + // Date::from_calendar_date(start.year(), Month::try_from(4 as u8).expect("oops"), 15 as u8).expect("doubleoops"), | |
| 921 | + // // Date::from_calendar_date(start.year(), Month::try_from(u.date.month() as u8).expect("oops"), u.date.day() as u8).expect("doubleoops"), | |
| 922 | + // Style::default() | |
| 923 | + // .add_modifier(Modifier::BOLD) | |
| 924 | + // .fg(GREEN.c300) | |
| 925 | + // .bg(PINK.c500), | |
| 926 | + // ); | |
| 927 | + // }); | |
| 928 | + | |
| 929 | + let rows = Layout::vertical([Constraint::Ratio(1, 2); 2]).split(layout_cal[1]); | |
| 930 | + let cols = rows.iter().flat_map(|row| { | |
| 931 | + Layout::horizontal([Constraint::Ratio(1, 6); 6]) | |
| 932 | + .split(*row) | |
| 933 | + .to_vec() | |
| 934 | + }); | |
| 935 | + for col in cols { | |
| 936 | + let cal = get_cal(start.month(), start.year(), &event_list); | |
| 937 | + f.render_widget(cal, col); | |
| 938 | + start = start.replace_month(start.month().next()).unwrap(); | |
| 939 | + } | |
| 940 | + | |
| 941 | + let dates = List::new(as_upcoming) | |
| 942 | + .block(Block::bordered().title("upcoming")) | |
| 943 | + .style(Style::new().white()) | |
| 944 | + .highlight_style(Style::new().italic()) | |
| 945 | + .highlight_symbol(">>") | |
| 946 | + .repeat_highlight_symbol(true) | |
| 947 | + .direction(ListDirection::TopToBottom); | |
| 948 | + | |
| 949 | + f.render_stateful_widget(dates, layout_cal[0], &mut app.sigdate_cal_state); | |
| 950 | + | |
| 951 | + // let layout_cal_detail = Layout::default() | |
| 952 | + // .direction(Direction::Horizontal) | |
| 953 | + // .margin(0) | |
| 954 | + // .constraints([Constraint::Percentage(25), Constraint::Percentage(75)]) | |
| 955 | + // .split(chunks[1]); | |
| 956 | + } | |
| 749 | 957 | Tabs::TierSettings => { |
| 750 | 958 | let ppl = List::new(&app.def_tier_list) |
| 751 | 959 | .block(Block::bordered().title("tiers")) |
@@ -772,8 +980,106 @@ fn render(f: &mut Frame, app: &mut Tui) {
| 772 | 980 | } |
| 773 | 981 | } |
| 774 | 982 | |
| 983 | +struct PplAndProps { | |
| 984 | + ppl: ppl::Model, | |
| 985 | + tier_defaults: Vec<tier_defaults::Model>, | |
| 986 | + tiers: Vec<tier::Model>, | |
| 987 | +} | |
| 988 | + | |
| 989 | +struct SigDateAndProps { | |
| 990 | + date: sig_date::Model, | |
| 991 | + ppl: ppl::Model, | |
| 992 | + traits: Vec<traits::Model>, | |
| 993 | + trait_defaults: Vec<trait_defaults::Model>, | |
| 994 | +} | |
| 995 | + | |
| 996 | +impl From<PplAndProps> for ListItem<'_> { | |
| 997 | + fn from(value: PplAndProps) -> Self { | |
| 998 | + // value.date_up; | |
| 999 | + let line = match value.ppl.me { | |
| 1000 | + true => Line::styled( | |
| 1001 | + format!(" 🌟 {}", value.ppl.nick.unwrap_or(value.ppl.name)), | |
| 1002 | + WHITE, | |
| 1003 | + ), | |
| 1004 | + false => match value.tiers.len() { | |
| 1005 | + 0 => Line::styled( | |
| 1006 | + format!(" {}", value.ppl.nick.unwrap_or(value.ppl.name)), | |
| 1007 | + WHITE, | |
| 1008 | + ), | |
| 1009 | + _ => { | |
| 1010 | + let t = value.tiers.first().unwrap(); | |
| 1011 | + match value.tier_defaults.iter().find(|q| q.key == t.name) { | |
| 1012 | + None => Line::styled( | |
| 1013 | + format!(" {}", value.ppl.nick.unwrap_or(value.ppl.name)), | |
| 1014 | + WHITE, | |
| 1015 | + ), | |
| 1016 | + Some(tierd) => match t.symbol.clone().unwrap_or("".to_string()).as_str() { | |
| 1017 | + "" => Line::styled( | |
| 1018 | + format!( | |
| 1019 | + " {} {}", | |
| 1020 | + tierd.symbol.clone().unwrap_or("".to_string()), | |
| 1021 | + value.ppl.nick.unwrap_or(value.ppl.name) | |
| 1022 | + ), | |
| 1023 | + lcolor( | |
| 1024 | + &t.color | |
| 1025 | + .clone() | |
| 1026 | + .unwrap_or(t.color.clone().unwrap_or("".to_string())), | |
| 1027 | + ), | |
| 1028 | + ), | |
| 1029 | + otherwise => Line::styled( | |
| 1030 | + format!( | |
| 1031 | + " {} {}", | |
| 1032 | + otherwise, | |
| 1033 | + value.ppl.nick.unwrap_or(value.ppl.name) | |
| 1034 | + ), | |
| 1035 | + lcolor( | |
| 1036 | + &t.color | |
| 1037 | + .clone() | |
| 1038 | + .unwrap_or(tierd.color.clone().unwrap_or("".to_string())), | |
| 1039 | + ), | |
| 1040 | + ), | |
| 1041 | + }, | |
| 1042 | + } | |
| 1043 | + } | |
| 1044 | + }, | |
| 1045 | + }; | |
| 1046 | + ListItem::new(line) | |
| 1047 | + } | |
| 1048 | +} | |
| 1049 | + | |
| 1050 | +impl From<&SigDateAndProps> for ListItem<'_> { | |
| 1051 | + fn from(value: &SigDateAndProps) -> Self { | |
| 1052 | + let n = value.ppl.nick.clone().unwrap_or(value.ppl.name.clone()); | |
| 1053 | + let default = value | |
| 1054 | + .trait_defaults | |
| 1055 | + .iter() | |
| 1056 | + .find(|q| q.key == value.date.event && q.is_date == true); | |
| 1057 | + let line = match (value.date.do_remind, default) { | |
| 1058 | + (true, Some(t)) => { | |
| 1059 | + Line::styled(format!(" ✓ {} {} {}", t.symbol, n, value.date.date), WHITE) | |
| 1060 | + } | |
| 1061 | + (true, None) => Line::styled( | |
| 1062 | + format!(" ✓ 📅 {} {} ({})", n, value.date.date, value.date.event), | |
| 1063 | + WHITE, | |
| 1064 | + ), | |
| 1065 | + (false, Some(t)) => Line::styled( | |
| 1066 | + format!(" ☐ {} {} {}", t.symbol, n, value.date.date), | |
| 1067 | + SLATE.c500, | |
| 1068 | + ), | |
| 1069 | + | |
| 1070 | + (false, None) => Line::styled( | |
| 1071 | + format!(" ☐ 📅 {} {} ({})", n, value.date.date, value.date.event), | |
| 1072 | + SLATE.c500, | |
| 1073 | + ), | |
| 1074 | + }; | |
| 1075 | + | |
| 1076 | + ListItem::new(line) | |
| 1077 | + } | |
| 1078 | +} | |
| 1079 | + | |
| 775 | 1080 | impl From<&ppl::Model> for ListItem<'_> { |
| 776 | 1081 | fn from(value: &Model) -> Self { |
| 1082 | + value.date_up; | |
| 777 | 1083 | let line = match value.me { |
| 778 | 1084 | true => Line::styled(format!(" 🌟 {}", value.name), WHITE), |
| 779 | 1085 | false => Line::styled(format!(" {}", value.name), WHITE), |
@@ -802,3 +1108,20 @@ impl From<&trait_defaults::Model> for ListItem<'_> {
| 802 | 1108 | ListItem::new(line) |
| 803 | 1109 | } |
| 804 | 1110 | } |
| 1111 | + | |
| 1112 | +impl From<&sig_date::Model> for ListItem<'_> { | |
| 1113 | + fn from(value: &sig_date::Model) -> Self { | |
| 1114 | + let line = match value.do_remind { | |
| 1115 | + true => Line::styled( | |
| 1116 | + format!(" ✓ {} {} {}", value.ppl_id, value.event, value.date), | |
| 1117 | + WHITE, | |
| 1118 | + ), | |
| 1119 | + false => Line::styled( | |
| 1120 | + format!(" ☐ {} {} {}", value.ppl_id, value.event, value.date), | |
| 1121 | + SLATE.c500, | |
| 1122 | + ), | |
| 1123 | + }; | |
| 1124 | + | |
| 1125 | + ListItem::new(line) | |
| 1126 | + } | |
| 1127 | +} |
modified
src/entities/ppl.rs
+1
−0
@@ -9,6 +9,7 @@ pub struct Model {
| 9 | 9 | pub id: i32, |
| 10 | 10 | pub name: String, |
| 11 | 11 | pub me: bool, |
| 12 | + pub nick: Option<String>, | |
| 12 | 13 | pub date_ins: Date, |
| 13 | 14 | pub date_up: Date, |
| 14 | 15 | } |
modified
src/migrator/m20241219_000001_create_ppl.rs
+3
−2
@@ -25,6 +25,7 @@ impl MigrationTrait for Migration {
| 25 | 25 | ) |
| 26 | 26 | .col(ColumnDef::new(Ppl::Name).string().not_null()) |
| 27 | 27 | .col(ColumnDef::new(Ppl::Me).boolean().not_null()) |
| 28 | + .col(ColumnDef::new(Ppl::Nick).string().not_null()) | |
| 28 | 29 | .col(ColumnDef::new(Ppl::DateIns).date().not_null()) |
| 29 | 30 | .col(ColumnDef::new(Ppl::DateUp).date().not_null()) |
| 30 | 31 | .to_owned(), |
@@ -45,8 +46,8 @@ pub enum Ppl {
| 45 | 46 | Table, |
| 46 | 47 | Id, |
| 47 | 48 | Name, |
| 48 | − Me, // boolean for self to wish you a happy bday | |
| 49 | − | |
| 49 | + Me, // boolean for self to wish you a happy bday | |
| 50 | + Nick, // default-alias | |
| 50 | 51 | DateIns, |
| 51 | 52 | DateUp, |
| 52 | 53 | } |
Parents: 19f8d9b