mirror of
https://github.com/kdheepak/taskwarrior-tui.git
synced 2025-08-24 05:26:42 +02:00
WIP
This commit is contained in:
parent
ff6c0e60b9
commit
f6c2033b60
2 changed files with 74 additions and 29 deletions
|
@ -10,6 +10,7 @@ use task_hookrs::{import::import, task::Task, uda::UDAValue};
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
use tui_input::backend::crossterm::EventHandler;
|
use tui_input::backend::crossterm::EventHandler;
|
||||||
use unicode_truncate::UnicodeTruncateStr;
|
use unicode_truncate::UnicodeTruncateStr;
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::{Component, Frame};
|
use super::{Component, Frame};
|
||||||
|
@ -120,8 +121,6 @@ impl TaskReport {
|
||||||
|
|
||||||
pub fn generate_rows(&mut self) -> Result<()> {
|
pub fn generate_rows(&mut self) -> Result<()> {
|
||||||
self.rows = vec![];
|
self.rows = vec![];
|
||||||
|
|
||||||
// get all tasks as their string representation
|
|
||||||
for task in self.tasks.iter() {
|
for task in self.tasks.iter() {
|
||||||
if self.columns.is_empty() {
|
if self.columns.is_empty() {
|
||||||
break;
|
break;
|
||||||
|
@ -467,6 +466,44 @@ impl TaskReport {
|
||||||
self.state.select(Some(self.current_selection));
|
self.state.select(Some(self.current_selection));
|
||||||
log::info!("{:?}", self.state);
|
log::info!("{:?}", self.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn calculate_widths(&self, maximum_available_width: u16) -> Vec<usize> {
|
||||||
|
// naive implementation of calculate widths
|
||||||
|
let mut widths = self.labels.iter().map(String::len).collect::<Vec<usize>>();
|
||||||
|
for i in 0..self.labels.len() {
|
||||||
|
let max_width = self.rows.iter().map(|row| row[i].len()).max().unwrap_or(0);
|
||||||
|
if max_width == 0 {
|
||||||
|
widths[i] = 0
|
||||||
|
} else {
|
||||||
|
widths[i] = widths[i].max(max_width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i, header) in self.labels.iter().enumerate() {
|
||||||
|
if header == "Description" || header == "Definition" {
|
||||||
|
// always give description or definition the most room to breath
|
||||||
|
widths[i] = maximum_available_width as usize;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i, header) in self.labels.iter().enumerate() {
|
||||||
|
if i == 0 {
|
||||||
|
// always give ID a couple of extra for indicator
|
||||||
|
widths[i] += self.config.task_report.selection_indicator.as_str().width();
|
||||||
|
// if let TableMode::MultipleSelection = self.task_table_state.mode() {
|
||||||
|
// widths[i] += 2
|
||||||
|
// };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// now start trimming
|
||||||
|
while (widths.iter().sum::<usize>() as u16) >= maximum_available_width - (self.labels.len()) as u16 {
|
||||||
|
let index = widths.iter().position(|i| i == widths.iter().max().unwrap_or(&0)).unwrap_or_default();
|
||||||
|
if widths[index] == 1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
widths[index] -= 1;
|
||||||
|
}
|
||||||
|
widths
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for TaskReport {
|
impl Component for TaskReport {
|
||||||
|
@ -495,34 +532,20 @@ impl Component for TaskReport {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&mut self, f: &mut Frame<'_>, rect: Rect) -> Result<()> {
|
fn draw(&mut self, f: &mut Frame<'_>, rect: Rect) -> Result<()> {
|
||||||
|
let column_spacing = 1;
|
||||||
if self.rows.len() == 0 {
|
if self.rows.len() == 0 {
|
||||||
f.render_widget(Paragraph::new("No data found").block(Block::new().borders(Borders::all())), rect);
|
f.render_widget(Paragraph::new("No data found").block(Block::new().borders(Borders::all())), rect);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let mut total_fixed_widths = 0;
|
let widths = self.calculate_widths(rect.width);
|
||||||
let mut constraints = Vec::with_capacity(self.rows[0].len());
|
let constraints: Vec<Constraint> = widths.iter().map(|i| Constraint::Min(*i as u16)).collect();
|
||||||
|
|
||||||
for i in 0..self.rows[0].len() {
|
|
||||||
if self.columns[i] == "description" {
|
|
||||||
constraints.push(Constraint::Min(0)); // temporary, will update later
|
|
||||||
} else {
|
|
||||||
let max_width = self.rows.iter().map(|row| row[i].len() as u16).max().unwrap_or(0);
|
|
||||||
total_fixed_widths += max_width + 2; // adding 2 for padding
|
|
||||||
constraints.push(Constraint::Length(max_width + 2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(pos) = self.columns.iter().position(|x| x == "description") {
|
|
||||||
let description_width = rect.width.saturating_sub(total_fixed_widths).saturating_sub(4);
|
|
||||||
constraints[pos] = Constraint::Length(description_width);
|
|
||||||
}
|
|
||||||
let rows = self.rows.iter().map(|row| Row::new(row.clone()));
|
let rows = self.rows.iter().map(|row| Row::new(row.clone()));
|
||||||
let table = Table::new(rows)
|
let table = Table::new(rows)
|
||||||
.header(Row::new(self.columns.clone()))
|
.header(Row::new(self.labels.clone()))
|
||||||
.widths(&constraints)
|
.widths(&constraints)
|
||||||
.block(Block::new().borders(Borders::ALL))
|
|
||||||
.highlight_symbol(&self.config.task_report.selection_indicator)
|
.highlight_symbol(&self.config.task_report.selection_indicator)
|
||||||
.highlight_spacing(HighlightSpacing::Always);
|
.highlight_spacing(HighlightSpacing::Always)
|
||||||
|
.column_spacing(column_spacing);
|
||||||
f.render_stateful_widget(table, rect, &mut self.state);
|
f.render_stateful_widget(table, rect, &mut self.state);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -6,13 +6,14 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
use derive_deref::{Deref, DerefMut};
|
use derive_deref::{Deref, DerefMut};
|
||||||
use ratatui::style::{Color, Modifier, Style};
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor};
|
use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor};
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
use crate::{action::Action, app::Mode};
|
use crate::{action::Action, app::Mode};
|
||||||
|
|
||||||
const CONFIG: &str = include_str!("../.config/config.json5");
|
const CONFIG: &str = include_str!("../.config/config.json5");
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Default)]
|
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
||||||
pub struct TaskReportConfig {
|
pub struct TaskReportConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub looping: bool,
|
pub looping: bool,
|
||||||
|
@ -22,11 +23,33 @@ pub struct TaskReportConfig {
|
||||||
|
|
||||||
impl Into<Value> for TaskReportConfig {
|
impl Into<Value> for TaskReportConfig {
|
||||||
fn into(self) -> Value {
|
fn into(self) -> Value {
|
||||||
let mut map = HashMap::new();
|
let json_value = serde_json::to_value(self).unwrap();
|
||||||
map.insert("looping".to_string(), Value::from(self.looping));
|
_convert_json_to_config(json_value)
|
||||||
map.insert("selection_indicator".to_string(), Value::from(self.selection_indicator));
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Value::from(map)
|
fn _convert_json_to_config(json_value: serde_json::Value) -> config::Value {
|
||||||
|
match json_value {
|
||||||
|
JsonValue::Null => config::Value::new(None, config::ValueKind::Nil),
|
||||||
|
JsonValue::Bool(b) => config::Value::from(b),
|
||||||
|
JsonValue::Number(n) => {
|
||||||
|
if let Some(i) = n.as_i64() {
|
||||||
|
config::Value::from(i)
|
||||||
|
} else if let Some(f) = n.as_f64() {
|
||||||
|
config::Value::from(f)
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
JsonValue::String(s) => config::Value::from(s),
|
||||||
|
JsonValue::Array(arr) => {
|
||||||
|
let cv_arr: Vec<_> = arr.into_iter().map(_convert_json_to_config).collect();
|
||||||
|
config::Value::new(None, config::ValueKind::Array(cv_arr))
|
||||||
|
},
|
||||||
|
JsonValue::Object(map) => {
|
||||||
|
let cv_map: HashMap<_, _> = map.into_iter().map(|(k, v)| (k, _convert_json_to_config(v))).collect();
|
||||||
|
config::Value::new(None, config::ValueKind::Table(cv_map))
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +83,6 @@ impl Config {
|
||||||
.set_default("_data_dir", data_dir.to_str().unwrap())?
|
.set_default("_data_dir", data_dir.to_str().unwrap())?
|
||||||
.set_default("_config_dir", config_dir.to_str().unwrap())?;
|
.set_default("_config_dir", config_dir.to_str().unwrap())?;
|
||||||
|
|
||||||
// List of potential configuration files provided by the user
|
|
||||||
let config_files = [
|
let config_files = [
|
||||||
("config.json5", config::FileFormat::Json5),
|
("config.json5", config::FileFormat::Json5),
|
||||||
("config.json", config::FileFormat::Json),
|
("config.json", config::FileFormat::Json),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue