forked from janek/mostr
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` - set tag filter (empty: list all used tags)
|
||||||
- `+TAG` - add tag filter
|
- `+TAG` - add tag filter
|
||||||
- `-TAG` - remove tag filters
|
- `-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.
|
Status 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.
|
An active tag or status filter will also set that attribute for newly created tasks.
|
||||||
|
|
||||||
### Available Columns
|
### Available Columns
|
||||||
|
|
||||||
- `id`
|
- `id`
|
||||||
- `parentid`
|
- `parentid`
|
||||||
- `name`
|
- `name`
|
||||||
- `state`
|
- `state` - indicator of current progress
|
||||||
- `hashtags`
|
- `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
|
- `tags` - values of all nostr tags associated with the event, except event tags
|
||||||
- `desc` - last note on the task
|
- `desc` - last note on the task
|
||||||
- `description` - accumulated notes 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.
|
because they need conceptualization.
|
||||||
Suggestions welcome!
|
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
|
- Priorities
|
||||||
- Dependencies (change from tags to properties so they can be added later? or maybe as a state?)
|
- Dependencies (change from tags to properties so they can be added later? or maybe as a state?)
|
||||||
- Templates
|
- Templates
|
||||||
|
@ -194,3 +196,8 @@ Suggestions welcome!
|
||||||
- TUI: Clear terminal? Refresh on empty prompt after timeout?
|
- TUI: Clear terminal? Refresh on empty prompt after timeout?
|
||||||
- Kanban, GANTT, Calendar
|
- Kanban, GANTT, Calendar
|
||||||
- Web Interface, Messenger integrations
|
- 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::fmt;
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::{ColoredString, Colorize};
|
||||||
use itertools::Either::{Left, Right};
|
use itertools::Either::{Left, Right};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{debug, error, info, trace, warn};
|
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::helpers::some_non_empty;
|
||||||
use crate::kinds::{is_hashtag, PROCEDURE_KIND};
|
use crate::kinds::{is_hashtag, PROCEDURE_KIND};
|
||||||
|
@ -72,7 +72,6 @@ impl Task {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_dependendees(&self) -> Vec<&EventId> {
|
pub(crate) fn get_dependendees(&self) -> Vec<&EventId> {
|
||||||
// TODO honor properly
|
|
||||||
self.find_refs(MARKER_DEPENDS).collect()
|
self.find_refs(MARKER_DEPENDS).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,17 +141,7 @@ impl Task {
|
||||||
match property {
|
match property {
|
||||||
"id" => Some(self.event.id.to_string()),
|
"id" => Some(self.event.id.to_string()),
|
||||||
"parentid" => self.parent_id().map(|i| i.to_string()),
|
"parentid" => self.parent_id().map(|i| i.to_string()),
|
||||||
"state" => Some({
|
"status" => Some(self.state_or_default().get_colored_label().to_string()),
|
||||||
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()
|
|
||||||
}),
|
|
||||||
"name" => Some(self.event.content.clone()),
|
"name" => Some(self.event.content.clone()),
|
||||||
"desc" => self.descriptions().last().cloned(),
|
"desc" => self.descriptions().last().cloned(),
|
||||||
"description" => Some(self.descriptions().join(" ")),
|
"description" => Some(self.descriptions().join(" ")),
|
||||||
|
@ -180,7 +169,7 @@ impl Task {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct TaskState {
|
pub(crate) struct TaskState {
|
||||||
state: State,
|
pub(crate) state: State,
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
pub(crate) time: Timestamp,
|
pub(crate) time: Timestamp,
|
||||||
}
|
}
|
||||||
|
@ -191,6 +180,9 @@ impl TaskState {
|
||||||
pub(crate) fn get_label(&self) -> String {
|
pub(crate) fn get_label(&self) -> String {
|
||||||
self.name.clone().unwrap_or_else(|| self.state.to_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 {
|
pub(crate) fn matches_label(&self, label: &str) -> bool {
|
||||||
self.name.as_ref().is_some_and(|n| n.eq_ignore_ascii_case(label))
|
self.name.as_ref().is_some_and(|n| n.eq_ignore_ascii_case(label))
|
||||||
|| self.state.to_string().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 {
|
pub(crate) enum State {
|
||||||
Open,
|
Open,
|
||||||
Done,
|
Done,
|
||||||
|
@ -261,6 +253,16 @@ impl State {
|
||||||
State::Procedure => PROCEDURE_KIND,
|
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 {
|
impl From<State> for Kind {
|
||||||
fn from(value: State) -> Self {
|
fn from(value: State) -> Self {
|
||||||
|
|
23
src/tasks.rs
23
src/tasks.rs
|
@ -116,7 +116,6 @@ impl Tasks {
|
||||||
history: Default::default(),
|
history: Default::default(),
|
||||||
properties: vec![
|
properties: vec![
|
||||||
"state".into(),
|
"state".into(),
|
||||||
"progress".into(),
|
|
||||||
"rtime".into(),
|
"rtime".into(),
|
||||||
"hashtags".into(),
|
"hashtags".into(),
|
||||||
"rpath".into(),
|
"rpath".into(),
|
||||||
|
@ -400,9 +399,15 @@ impl Tasks {
|
||||||
writeln!(lock, "{}", t.descriptions().join("\n"))?;
|
writeln!(lock, "{}", t.descriptions().join("\n"))?;
|
||||||
}
|
}
|
||||||
// TODO proper column alignment
|
// TODO proper column alignment
|
||||||
|
// TODO hide empty columns and sorting
|
||||||
writeln!(lock, "{}", self.properties.join("\t").bold())?;
|
writeln!(lock, "{}", self.properties.join("\t").bold())?;
|
||||||
let mut total_time = 0;
|
let mut total_time = 0;
|
||||||
for task in self.current_tasks() {
|
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!(
|
writeln!(
|
||||||
lock,
|
lock,
|
||||||
"{}",
|
"{}",
|
||||||
|
@ -424,10 +429,18 @@ impl Tasks {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"progress" => self
|
"state" => {
|
||||||
.total_progress(task.get_id())
|
if let Some(task) = task.get_dependendees().iter().filter_map(|id| self.get_by_id(id)).find(|t| t.pure_state().is_open()) {
|
||||||
.filter(|_| task.children.len() > 0)
|
return format!("Blocked by \"{}\"", task.get_title()).bright_red().to_string()
|
||||||
.map_or(String::new(), |p| format!("{:2.0}%", p * 100.0)),
|
}
|
||||||
|
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)),
|
"path" => self.get_task_path(Some(task.event.id)),
|
||||||
"rpath" => self.relative_path(task.event.id),
|
"rpath" => self.relative_path(task.event.id),
|
||||||
// TODO format strings configurable
|
// TODO format strings configurable
|
||||||
|
|
Loading…
Reference in New Issue