feat: integrate progress and dependencies into state property
This commit is contained in:
parent
dcf333353b
commit
dda969e08b
17
README.md
17
README.md
|
@ -121,18 +121,19 @@ Property Filters:
|
|||
- `#TAG` - set tag filter (empty: list all used tags)
|
||||
- `+TAG` - add tag filter
|
||||
- `-TAG` - remove tag filters
|
||||
- `?STATE` - filter by state (type or description) - plain `?` to reset, `??` to show all
|
||||
- `?STATUS` - filter by status (type or description) - plain `?` to reset, `??` to show all
|
||||
|
||||
State descriptions can be used for example for Kanban columns or review flows.
|
||||
An active tag or state filter will also set that attribute for newly created tasks.
|
||||
Status descriptions can be used for example for Kanban columns or review flows.
|
||||
An active tag or status filter will also set that attribute for newly created tasks.
|
||||
|
||||
### Available Columns
|
||||
|
||||
- `id`
|
||||
- `parentid`
|
||||
- `name`
|
||||
- `state`
|
||||
- `hashtags`
|
||||
- `state` - indicator of current progress
|
||||
- `status` - pure task status
|
||||
- `hashtags` - list of hashtags set for the task
|
||||
- `tags` - values of all nostr tags associated with the event, except event tags
|
||||
- `desc` - last note on the task
|
||||
- `description` - accumulated notes on the task
|
||||
|
@ -180,6 +181,7 @@ The following features are not ready to be implemented
|
|||
because they need conceptualization.
|
||||
Suggestions welcome!
|
||||
|
||||
- What if I want to postpone a procedure, i.e. make it pending, or move it across kanban, does this make sense?
|
||||
- Priorities
|
||||
- Dependencies (change from tags to properties so they can be added later? or maybe as a state?)
|
||||
- Templates
|
||||
|
@ -194,3 +196,8 @@ Suggestions welcome!
|
|||
- TUI: Clear terminal? Refresh on empty prompt after timeout?
|
||||
- Kanban, GANTT, Calendar
|
||||
- Web Interface, Messenger integrations
|
||||
|
||||
## Notes
|
||||
|
||||
- TBI = To Be Implemented
|
||||
- `. TASK` - create and enter a new task even if the name matches an existing one
|
34
src/task.rs
34
src/task.rs
|
@ -4,11 +4,11 @@ use std::collections::{BTreeSet, HashSet};
|
|||
use std::fmt;
|
||||
use std::string::ToString;
|
||||
|
||||
use colored::Colorize;
|
||||
use colored::{ColoredString, Colorize};
|
||||
use itertools::Either::{Left, Right};
|
||||
use itertools::Itertools;
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use nostr_sdk::{Event, EventBuilder, EventId, Kind, Tag, TagStandard, Timestamp};
|
||||
use nostr_sdk::{Event, EventId, Kind, Tag, TagStandard, Timestamp};
|
||||
|
||||
use crate::helpers::some_non_empty;
|
||||
use crate::kinds::{is_hashtag, PROCEDURE_KIND};
|
||||
|
@ -72,7 +72,6 @@ impl Task {
|
|||
}
|
||||
|
||||
pub(crate) fn get_dependendees(&self) -> Vec<&EventId> {
|
||||
// TODO honor properly
|
||||
self.find_refs(MARKER_DEPENDS).collect()
|
||||
}
|
||||
|
||||
|
@ -142,17 +141,7 @@ impl Task {
|
|||
match property {
|
||||
"id" => Some(self.event.id.to_string()),
|
||||
"parentid" => self.parent_id().map(|i| i.to_string()),
|
||||
"state" => Some({
|
||||
let state = self.state_or_default();
|
||||
let label = state.get_label();
|
||||
match state.state {
|
||||
State::Open => label.green(),
|
||||
State::Done => label.bright_black(),
|
||||
State::Closed => label.magenta(),
|
||||
State::Pending => label.yellow(),
|
||||
State::Procedure => label.blue(),
|
||||
}.to_string()
|
||||
}),
|
||||
"status" => Some(self.state_or_default().get_colored_label().to_string()),
|
||||
"name" => Some(self.event.content.clone()),
|
||||
"desc" => self.descriptions().last().cloned(),
|
||||
"description" => Some(self.descriptions().join(" ")),
|
||||
|
@ -180,7 +169,7 @@ impl Task {
|
|||
}
|
||||
|
||||
pub(crate) struct TaskState {
|
||||
state: State,
|
||||
pub(crate) state: State,
|
||||
name: Option<String>,
|
||||
pub(crate) time: Timestamp,
|
||||
}
|
||||
|
@ -191,6 +180,9 @@ impl TaskState {
|
|||
pub(crate) fn get_label(&self) -> String {
|
||||
self.name.clone().unwrap_or_else(|| self.state.to_string())
|
||||
}
|
||||
pub(crate) fn get_colored_label(&self) -> ColoredString {
|
||||
self.state.colorize(&self.get_label())
|
||||
}
|
||||
pub(crate) fn matches_label(&self, label: &str) -> bool {
|
||||
self.name.as_ref().is_some_and(|n| n.eq_ignore_ascii_case(label))
|
||||
|| self.state.to_string().eq_ignore_ascii_case(label)
|
||||
|
@ -211,7 +203,7 @@ impl Display for TaskState {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq)]
|
||||
pub(crate) enum State {
|
||||
Open,
|
||||
Done,
|
||||
|
@ -261,6 +253,16 @@ impl State {
|
|||
State::Procedure => PROCEDURE_KIND,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn colorize(&self, str: &str) -> ColoredString {
|
||||
match self {
|
||||
State::Open => str.green(),
|
||||
State::Done => str.bright_black(),
|
||||
State::Closed => str.magenta(),
|
||||
State::Pending => str.yellow(),
|
||||
State::Procedure => str.blue(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<State> for Kind {
|
||||
fn from(value: State) -> Self {
|
||||
|
|
23
src/tasks.rs
23
src/tasks.rs
|
@ -116,7 +116,6 @@ impl Tasks {
|
|||
history: Default::default(),
|
||||
properties: vec![
|
||||
"state".into(),
|
||||
"progress".into(),
|
||||
"rtime".into(),
|
||||
"hashtags".into(),
|
||||
"rpath".into(),
|
||||
|
@ -400,9 +399,15 @@ impl Tasks {
|
|||
writeln!(lock, "{}", t.descriptions().join("\n"))?;
|
||||
}
|
||||
// TODO proper column alignment
|
||||
// TODO hide empty columns and sorting
|
||||
writeln!(lock, "{}", self.properties.join("\t").bold())?;
|
||||
let mut total_time = 0;
|
||||
for task in self.current_tasks() {
|
||||
let progress =
|
||||
self
|
||||
.total_progress(task.get_id())
|
||||
.filter(|_| task.children.len() > 0);
|
||||
let prog_string = progress.map_or(String::new(), |p| format!("{:2.0}%", p * 100.0));
|
||||
writeln!(
|
||||
lock,
|
||||
"{}",
|
||||
|
@ -424,10 +429,18 @@ impl Tasks {
|
|||
"".to_string()
|
||||
}
|
||||
}
|
||||
"progress" => self
|
||||
.total_progress(task.get_id())
|
||||
.filter(|_| task.children.len() > 0)
|
||||
.map_or(String::new(), |p| format!("{:2.0}%", p * 100.0)),
|
||||
"state" => {
|
||||
if let Some(task) = task.get_dependendees().iter().filter_map(|id| self.get_by_id(id)).find(|t| t.pure_state().is_open()) {
|
||||
return format!("Blocked by \"{}\"", task.get_title()).bright_red().to_string()
|
||||
}
|
||||
let state = task.state_or_default();
|
||||
if state.state.is_open() && progress.is_some_and(|p| p > 0.1) {
|
||||
state.state.colorize(&prog_string)
|
||||
} else {
|
||||
state.get_colored_label()
|
||||
}.to_string()
|
||||
}
|
||||
"progress" => prog_string.clone(),
|
||||
"path" => self.get_task_path(Some(task.event.id)),
|
||||
"rpath" => self.relative_path(task.event.id),
|
||||
// TODO format strings configurable
|
||||
|
|
Loading…
Reference in New Issue