forked from janek/mostr
1
0
Fork 0

feat: integrate progress and dependencies into state property

This commit is contained in:
xeruf 2024-08-10 21:25:46 +03:00
parent dcf333353b
commit dda969e08b
3 changed files with 48 additions and 26 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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