Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
parent
86310c6764
commit
f6a48540e1
@ -5,8 +5,8 @@ use ratatui::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command_parser::CommandParser, commands::IntoCommand, components::GraphExplorer,
|
command_parser::CommandParser, commands::IntoCommand,
|
||||||
state::SharedState, Msg,
|
components::graph_explorer::GraphExplorer, state::SharedState, Msg,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
@ -125,6 +125,10 @@ impl<'a> App<'a> {
|
|||||||
self.command = None;
|
self.command = None;
|
||||||
return Ok(msg.into_command());
|
return Ok(msg.into_command());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if command.is_quit() {
|
||||||
|
return Ok(Msg::QuitApp.into_command());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,35 @@
|
|||||||
use std::ops::Deref;
|
pub(crate) mod movement_graph;
|
||||||
|
pub(crate) mod render_graph;
|
||||||
|
|
||||||
use anyhow::Result;
|
pub(crate) mod graph_explorer {
|
||||||
use hyperlog_core::log::{GraphItem, ItemState};
|
use anyhow::Result;
|
||||||
use itertools::Itertools;
|
use hyperlog_core::log::GraphItem;
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{prelude::*, widgets::*};
|
||||||
|
|
||||||
use crate::{command_parser::Commands, models::Msg, state::SharedState};
|
use crate::{
|
||||||
|
command_parser::Commands, components::movement_graph::GraphItemType, models::Msg,
|
||||||
|
state::SharedState,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct GraphExplorer<'a> {
|
use super::movement_graph::{MovementGraph, MovementGraphItem};
|
||||||
|
use super::render_graph::RenderGraph;
|
||||||
|
|
||||||
|
pub struct GraphExplorer<'a> {
|
||||||
state: SharedState,
|
state: SharedState,
|
||||||
|
|
||||||
pub inner: GraphExplorerState<'a>,
|
pub inner: GraphExplorerState<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GraphExplorerState<'a> {
|
pub struct GraphExplorerState<'a> {
|
||||||
root: String,
|
root: String,
|
||||||
|
|
||||||
current_path: Option<&'a str>,
|
current_path: Option<&'a str>,
|
||||||
current_position: Vec<usize>,
|
current_position: Vec<usize>,
|
||||||
|
|
||||||
graph: Option<GraphItem>,
|
graph: Option<GraphItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GraphExplorer<'a> {
|
impl<'a> GraphExplorer<'a> {
|
||||||
pub fn new(root: String, state: SharedState) -> Self {
|
pub fn new(root: String, state: SharedState) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state,
|
state,
|
||||||
@ -222,149 +229,9 @@ impl<'a> GraphExplorer<'a> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
trait RenderGraph {
|
|
||||||
fn render_graph(&self, items: &[usize]) -> Vec<Line>;
|
|
||||||
fn render_graph_spans(&self, items: &[usize]) -> Vec<Vec<Span>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderGraph for MovementGraph {
|
|
||||||
/// render_graph takes each level of items, renders them, and finally renders a strongly set selector for the current item the user is on
|
|
||||||
/// This is done from buttom up, and composed via. string padding
|
|
||||||
fn render_graph(&self, items: &[usize]) -> Vec<Line> {
|
|
||||||
// Gets the inner content of the strings
|
|
||||||
|
|
||||||
let mut lines = Vec::new();
|
|
||||||
|
|
||||||
for item in &self.items {
|
|
||||||
let prefix = match item.item_type {
|
|
||||||
GraphItemType::Section => "- ",
|
|
||||||
GraphItemType::Item { done } => {
|
|
||||||
if done {
|
|
||||||
"- [x]"
|
|
||||||
} else {
|
|
||||||
"- [ ]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match items.split_first().map(|(first, rest)| {
|
|
||||||
if item.index == *first {
|
|
||||||
(true, rest)
|
|
||||||
} else {
|
|
||||||
(false, rest)
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Some((true, rest)) => {
|
|
||||||
if rest.is_empty() {
|
|
||||||
lines.push(
|
|
||||||
Line::raw(format!("{} {}", prefix, item.name))
|
|
||||||
.style(Style::new().bold().white()),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
lines.push(
|
|
||||||
Line::raw(format!("{} {}", prefix, item.name))
|
|
||||||
.patch_style(Style::new().dark_gray()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push("".into());
|
impl<'a> StatefulWidget for GraphExplorer<'a> {
|
||||||
|
|
||||||
let embedded_sections = item.values.render_graph_spans(rest);
|
|
||||||
for section in &embedded_sections {
|
|
||||||
let mut line = vec![Span::raw(" ".repeat(4))];
|
|
||||||
line.extend_from_slice(section);
|
|
||||||
lines.push(Line::from(line));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
lines.push(
|
|
||||||
Line::raw(format!("{} {}", prefix, item.name))
|
|
||||||
.patch_style(Style::new().dark_gray()),
|
|
||||||
);
|
|
||||||
|
|
||||||
lines.push("".into());
|
|
||||||
|
|
||||||
let embedded_sections = item.values.render_graph_spans(&[]);
|
|
||||||
for section in &embedded_sections {
|
|
||||||
let mut line = vec![Span::raw(" ".repeat(4))];
|
|
||||||
line.extend_from_slice(section);
|
|
||||||
lines.push(Line::from(line));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lines
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_graph_spans(&self, items: &[usize]) -> Vec<Vec<Span>> {
|
|
||||||
let mut lines = Vec::new();
|
|
||||||
|
|
||||||
for item in &self.items {
|
|
||||||
let prefix = match item.item_type {
|
|
||||||
GraphItemType::Section => "-",
|
|
||||||
GraphItemType::Item { done } => {
|
|
||||||
if done {
|
|
||||||
"- [x]"
|
|
||||||
} else {
|
|
||||||
"- [ ]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match items.split_first().map(|(first, rest)| {
|
|
||||||
if item.index == *first {
|
|
||||||
(true, rest)
|
|
||||||
} else {
|
|
||||||
(false, rest)
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Some((true, rest)) => {
|
|
||||||
let mut line = Vec::new();
|
|
||||||
if rest.is_empty() {
|
|
||||||
line.push(
|
|
||||||
Span::raw(format!("{} {}", prefix, item.name))
|
|
||||||
.style(Style::new().bold().white()),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
line.push(
|
|
||||||
Span::raw(format!("{} {}", prefix, item.name))
|
|
||||||
.patch_style(Style::new().dark_gray()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
lines.push(line);
|
|
||||||
lines.push(vec!["".into()]);
|
|
||||||
|
|
||||||
let embedded_sections = item.values.render_graph_spans(rest);
|
|
||||||
for section in &embedded_sections {
|
|
||||||
let mut line = vec![Span::raw(" ".repeat(4))];
|
|
||||||
line.extend_from_slice(section);
|
|
||||||
lines.push(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
lines.push(vec![Span::raw(format!("{prefix} {}", item.name))
|
|
||||||
.patch_style(Style::new().dark_gray())]);
|
|
||||||
|
|
||||||
lines.push(vec!["".into()]);
|
|
||||||
|
|
||||||
let embedded_sections = item.values.render_graph_spans(&[]);
|
|
||||||
for section in &embedded_sections {
|
|
||||||
let mut line = vec![Span::raw(" ".repeat(4))];
|
|
||||||
line.extend_from_slice(section);
|
|
||||||
lines.push(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lines
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> StatefulWidget for GraphExplorer<'a> {
|
|
||||||
type State = GraphExplorerState<'a>;
|
type State = GraphExplorerState<'a>;
|
||||||
|
|
||||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||||
@ -378,520 +245,5 @@ impl<'a> StatefulWidget for GraphExplorer<'a> {
|
|||||||
para.render(area, buf);
|
para.render(area, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
|
||||||
enum GraphItemType {
|
|
||||||
Section,
|
|
||||||
Item { done: bool },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
|
||||||
struct MovementGraphItem {
|
|
||||||
index: usize,
|
|
||||||
name: String,
|
|
||||||
values: MovementGraph,
|
|
||||||
|
|
||||||
item_type: GraphItemType,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Eq, Debug, Clone)]
|
|
||||||
struct MovementGraph {
|
|
||||||
items: Vec<MovementGraphItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MovementGraph {
|
|
||||||
fn next_right(&self, items: &[usize]) -> Option<MovementGraphItem> {
|
|
||||||
match items.split_first() {
|
|
||||||
Some((current_index, rest)) => match self.items.get(*current_index) {
|
|
||||||
Some(next_item) => next_item.values.next_right(rest),
|
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
None => self.items.first().cloned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_up(&self, items: &[usize]) -> Option<Vec<usize>> {
|
|
||||||
match items.split_last() {
|
|
||||||
Some((0, _)) => None,
|
|
||||||
Some((current_index, rest)) => {
|
|
||||||
let mut vec = rest.to_vec();
|
|
||||||
vec.push(current_index - 1);
|
|
||||||
|
|
||||||
Some(vec)
|
|
||||||
}
|
|
||||||
// May need to reduce this to an Some(Vec::default()) instead
|
|
||||||
//None => Some(self.items.iter().map(|i| i.index).collect_vec()),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_down(&self, items: &[usize]) -> Option<Vec<usize>> {
|
|
||||||
match items.split_last() {
|
|
||||||
Some((current_index, rest)) => {
|
|
||||||
if let Some(current_item) = self.get_graph(rest) {
|
|
||||||
if *current_index + 1 < current_item.items.len() {
|
|
||||||
let mut vec = rest.to_vec();
|
|
||||||
vec.push(current_index + 1);
|
|
||||||
|
|
||||||
Some(vec)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// May need to reduce this to an Some(Vec::default()) instead
|
|
||||||
//None => Some(self.items.iter().map(|i| i.index).collect_vec()),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_graph(&self, items: &[usize]) -> Option<&MovementGraph> {
|
|
||||||
match items.split_first() {
|
|
||||||
Some((first, rest)) => match self.items.get(*first).map(|s| &s.values) {
|
|
||||||
Some(next_graph) => next_graph.get_graph(rest),
|
|
||||||
None => Some(self),
|
|
||||||
},
|
|
||||||
None => Some(self),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_graph_item(&self, items: &[usize]) -> Option<&MovementGraphItem> {
|
|
||||||
match items.split_first() {
|
|
||||||
Some((first, rest)) => match self.items.get(*first) {
|
|
||||||
Some(next_graph) => match next_graph.values.get_graph_item(rest) {
|
|
||||||
Some(graph_item) => Some(graph_item),
|
|
||||||
None => Some(next_graph),
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_current_path(&self, position_items: &[usize]) -> Vec<String> {
|
|
||||||
match position_items.split_first() {
|
|
||||||
Some((first, rest)) => match self.items.get(*first) {
|
|
||||||
Some(item) => {
|
|
||||||
let mut current = vec![item.name.clone()];
|
|
||||||
let mut next = item.values.to_current_path(rest);
|
|
||||||
current.append(&mut next);
|
|
||||||
|
|
||||||
current
|
|
||||||
}
|
|
||||||
None => Vec::new(),
|
|
||||||
},
|
|
||||||
None => Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Box<GraphItem>> for MovementGraph {
|
|
||||||
fn from(value: Box<GraphItem>) -> Self {
|
|
||||||
value.deref().clone().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<GraphItem> for MovementGraph {
|
|
||||||
fn from(value: GraphItem) -> Self {
|
|
||||||
let mut graph = MovementGraph::default();
|
|
||||||
|
|
||||||
match value {
|
|
||||||
GraphItem::User(sections) | GraphItem::Section(sections) => {
|
|
||||||
let graph_items = sections
|
|
||||||
.iter()
|
|
||||||
.sorted_by(|(a, _), (b, _)| Ord::cmp(a, b))
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, (key, value))| MovementGraphItem {
|
|
||||||
index: i,
|
|
||||||
name: key.clone(),
|
|
||||||
values: value.clone().into(),
|
|
||||||
item_type: match value {
|
|
||||||
GraphItem::User(_) => GraphItemType::Section,
|
|
||||||
GraphItem::Section(_) => GraphItemType::Section,
|
|
||||||
GraphItem::Item { state, .. } => GraphItemType::Item {
|
|
||||||
done: matches!(state, ItemState::Done),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
graph.items = graph_items;
|
|
||||||
}
|
|
||||||
GraphItem::Item { .. } => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
graph
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use hyperlog_core::log::{GraphItem, ItemState};
|
|
||||||
use similar_asserts::assert_eq;
|
|
||||||
|
|
||||||
use crate::components::{GraphItemType, MovementGraphItem};
|
|
||||||
|
|
||||||
use super::MovementGraph;
|
|
||||||
|
|
||||||
/// Lets say we've got a graph
|
|
||||||
/// ```json
|
|
||||||
/// {
|
|
||||||
/// "type": "user",
|
|
||||||
/// "something": {
|
|
||||||
/// "type": "section",
|
|
||||||
/// "something": {
|
|
||||||
/// "type": "section",
|
|
||||||
/// "something-else": {
|
|
||||||
/// "type": "section",
|
|
||||||
/// "blabla": {
|
|
||||||
/// "type": "section"
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
/// We can get something out like
|
|
||||||
/// [
|
|
||||||
/// 0: {key: something, values: [
|
|
||||||
/// 0: {key: something, values: [
|
|
||||||
/// ...
|
|
||||||
/// ]}
|
|
||||||
/// ]}
|
|
||||||
/// ]
|
|
||||||
#[test]
|
|
||||||
fn test_can_transform_to_movement_graph() {
|
|
||||||
let graph = GraphItem::User(BTreeMap::from([(
|
|
||||||
"0".to_string(),
|
|
||||||
GraphItem::Section(BTreeMap::from([
|
|
||||||
("00".to_string(), GraphItem::Section(BTreeMap::new())),
|
|
||||||
(
|
|
||||||
"01".to_string(),
|
|
||||||
GraphItem::Section(BTreeMap::from([
|
|
||||||
(
|
|
||||||
"010".to_string(),
|
|
||||||
GraphItem::Item {
|
|
||||||
title: "some-title".into(),
|
|
||||||
description: "some-desc".into(),
|
|
||||||
state: ItemState::NotDone,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"011".to_string(),
|
|
||||||
GraphItem::Item {
|
|
||||||
title: "some-title".into(),
|
|
||||||
description: "some-desc".into(),
|
|
||||||
state: ItemState::NotDone,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
])),
|
|
||||||
),
|
|
||||||
])),
|
|
||||||
)]));
|
|
||||||
|
|
||||||
let actual: MovementGraph = graph.into();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
MovementGraph {
|
|
||||||
items: vec![MovementGraphItem {
|
|
||||||
index: 0,
|
|
||||||
name: "0".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph {
|
|
||||||
items: vec![
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 0,
|
|
||||||
name: "00".into(),
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 1,
|
|
||||||
name: "01".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph {
|
|
||||||
items: vec![
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 0,
|
|
||||||
name: "010".into(),
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
item_type: GraphItemType::Item { done: false },
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 1,
|
|
||||||
name: "011".into(),
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
item_type: GraphItemType::Item { done: false },
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
actual
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_graph_item() -> anyhow::Result<()> {
|
|
||||||
let graph = MovementGraph {
|
|
||||||
items: vec![
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 0,
|
|
||||||
name: "0".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph {
|
|
||||||
items: vec![
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 0,
|
|
||||||
name: "0".into(),
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 1,
|
|
||||||
name: "0".into(),
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 1,
|
|
||||||
name: "0".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph {
|
|
||||||
items: vec![
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 0,
|
|
||||||
name: "0".into(),
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 1,
|
|
||||||
name: "0".into(),
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 2,
|
|
||||||
name: "0".into(),
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 2,
|
|
||||||
name: "0".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph {
|
|
||||||
items: vec![
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 0,
|
|
||||||
name: "0".into(),
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 1,
|
|
||||||
name: "0".into(),
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
let actual_default = graph.get_graph(&[]);
|
|
||||||
assert_eq!(Some(&graph), actual_default);
|
|
||||||
|
|
||||||
let actual_first = graph.get_graph(&[0]);
|
|
||||||
assert_eq!(graph.items.first().map(|i| &i.values), actual_first);
|
|
||||||
|
|
||||||
let actual_second = graph.get_graph(&[1]);
|
|
||||||
assert_eq!(graph.items.get(1).map(|i| &i.values), actual_second);
|
|
||||||
|
|
||||||
let actual_nested = graph.get_graph(&[0, 0]);
|
|
||||||
assert_eq!(
|
|
||||||
graph
|
|
||||||
.items
|
|
||||||
.first()
|
|
||||||
.and_then(|i| i.values.items.first())
|
|
||||||
.map(|i| &i.values),
|
|
||||||
actual_nested
|
|
||||||
);
|
|
||||||
|
|
||||||
let actual_nested = graph.get_graph(&[0, 1]);
|
|
||||||
assert_eq!(
|
|
||||||
graph
|
|
||||||
.items
|
|
||||||
.first()
|
|
||||||
.and_then(|i| i.values.items.get(1))
|
|
||||||
.map(|i| &i.values),
|
|
||||||
actual_nested
|
|
||||||
);
|
|
||||||
|
|
||||||
let actual_nested = graph.get_graph(&[1, 2]);
|
|
||||||
assert_eq!(
|
|
||||||
graph
|
|
||||||
.items
|
|
||||||
.get(1)
|
|
||||||
.and_then(|i| i.values.items.get(2))
|
|
||||||
.map(|i| &i.values),
|
|
||||||
actual_nested
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_next_down() -> anyhow::Result<()> {
|
|
||||||
let graph = MovementGraph {
|
|
||||||
items: vec![
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 0,
|
|
||||||
name: "0".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph {
|
|
||||||
items: vec![MovementGraphItem {
|
|
||||||
index: 0,
|
|
||||||
name: "0".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 1,
|
|
||||||
name: "1".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph {
|
|
||||||
items: vec![
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 0,
|
|
||||||
name: "0".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 1,
|
|
||||||
name: "1".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 2,
|
|
||||||
name: "2".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph {
|
|
||||||
items: vec![
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 0,
|
|
||||||
name: "0".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 1,
|
|
||||||
name: "1".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 2,
|
|
||||||
name: "2".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph::default(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
let actual = graph.next_down(&[]);
|
|
||||||
assert_eq!(None, actual);
|
|
||||||
|
|
||||||
let actual = graph.next_down(&[0]);
|
|
||||||
assert_eq!(Some(vec![1]), actual);
|
|
||||||
|
|
||||||
let actual = graph.next_down(&[1]);
|
|
||||||
assert_eq!(Some(vec![2]), actual);
|
|
||||||
|
|
||||||
let actual = graph.next_down(&[2]);
|
|
||||||
assert_eq!(None, actual);
|
|
||||||
|
|
||||||
let graph = MovementGraph {
|
|
||||||
items: vec![
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 0,
|
|
||||||
name: "other".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph {
|
|
||||||
items: vec![MovementGraphItem {
|
|
||||||
index: 0,
|
|
||||||
name: "other".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph {
|
|
||||||
items: vec![MovementGraphItem {
|
|
||||||
index: 0,
|
|
||||||
name: "other".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph { items: vec![] },
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 1,
|
|
||||||
name: "some".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph { items: vec![] },
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 2,
|
|
||||||
name: "something".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph {
|
|
||||||
items: vec![
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 0,
|
|
||||||
name: "else".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph { items: vec![] },
|
|
||||||
},
|
|
||||||
MovementGraphItem {
|
|
||||||
index: 1,
|
|
||||||
name: "third".into(),
|
|
||||||
item_type: GraphItemType::Section,
|
|
||||||
values: MovementGraph { items: vec![] },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
let actual = graph.next_down(&[0]);
|
|
||||||
assert_eq!(Some(vec![1]), actual);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
520
crates/hyperlog-tui/src/components/movement_graph.rs
Normal file
520
crates/hyperlog-tui/src/components/movement_graph.rs
Normal file
@ -0,0 +1,520 @@
|
|||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use hyperlog_core::log::{GraphItem, ItemState};
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||||
|
pub enum GraphItemType {
|
||||||
|
Section,
|
||||||
|
Item { done: bool },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||||
|
pub struct MovementGraphItem {
|
||||||
|
pub index: usize,
|
||||||
|
pub name: String,
|
||||||
|
pub values: MovementGraph,
|
||||||
|
|
||||||
|
pub item_type: GraphItemType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Eq, Debug, Clone)]
|
||||||
|
pub struct MovementGraph {
|
||||||
|
pub items: Vec<MovementGraphItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MovementGraph {
|
||||||
|
pub fn next_right(&self, items: &[usize]) -> Option<MovementGraphItem> {
|
||||||
|
match items.split_first() {
|
||||||
|
Some((current_index, rest)) => match self.items.get(*current_index) {
|
||||||
|
Some(next_item) => next_item.values.next_right(rest),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
None => self.items.first().cloned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_up(&self, items: &[usize]) -> Option<Vec<usize>> {
|
||||||
|
match items.split_last() {
|
||||||
|
Some((0, _)) => None,
|
||||||
|
Some((current_index, rest)) => {
|
||||||
|
let mut vec = rest.to_vec();
|
||||||
|
vec.push(current_index - 1);
|
||||||
|
|
||||||
|
Some(vec)
|
||||||
|
}
|
||||||
|
// May need to reduce this to an Some(Vec::default()) instead
|
||||||
|
//None => Some(self.items.iter().map(|i| i.index).collect_vec()),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_down(&self, items: &[usize]) -> Option<Vec<usize>> {
|
||||||
|
match items.split_last() {
|
||||||
|
Some((current_index, rest)) => {
|
||||||
|
if let Some(current_item) = self.get_graph(rest) {
|
||||||
|
if *current_index + 1 < current_item.items.len() {
|
||||||
|
let mut vec = rest.to_vec();
|
||||||
|
vec.push(current_index + 1);
|
||||||
|
|
||||||
|
Some(vec)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// May need to reduce this to an Some(Vec::default()) instead
|
||||||
|
//None => Some(self.items.iter().map(|i| i.index).collect_vec()),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_graph(&self, items: &[usize]) -> Option<&MovementGraph> {
|
||||||
|
match items.split_first() {
|
||||||
|
Some((first, rest)) => match self.items.get(*first).map(|s| &s.values) {
|
||||||
|
Some(next_graph) => next_graph.get_graph(rest),
|
||||||
|
None => Some(self),
|
||||||
|
},
|
||||||
|
None => Some(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_graph_item(&self, items: &[usize]) -> Option<&MovementGraphItem> {
|
||||||
|
match items.split_first() {
|
||||||
|
Some((first, rest)) => match self.items.get(*first) {
|
||||||
|
Some(next_graph) => match next_graph.values.get_graph_item(rest) {
|
||||||
|
Some(graph_item) => Some(graph_item),
|
||||||
|
None => Some(next_graph),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_current_path(&self, position_items: &[usize]) -> Vec<String> {
|
||||||
|
match position_items.split_first() {
|
||||||
|
Some((first, rest)) => match self.items.get(*first) {
|
||||||
|
Some(item) => {
|
||||||
|
let mut current = vec![item.name.clone()];
|
||||||
|
let mut next = item.values.to_current_path(rest);
|
||||||
|
current.append(&mut next);
|
||||||
|
|
||||||
|
current
|
||||||
|
}
|
||||||
|
None => Vec::new(),
|
||||||
|
},
|
||||||
|
None => Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Box<GraphItem>> for MovementGraph {
|
||||||
|
fn from(value: Box<GraphItem>) -> Self {
|
||||||
|
value.deref().clone().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GraphItem> for MovementGraph {
|
||||||
|
fn from(value: GraphItem) -> Self {
|
||||||
|
let mut graph = MovementGraph::default();
|
||||||
|
|
||||||
|
match value {
|
||||||
|
GraphItem::User(sections) | GraphItem::Section(sections) => {
|
||||||
|
let graph_items = sections
|
||||||
|
.iter()
|
||||||
|
.sorted_by(|(a, _), (b, _)| Ord::cmp(a, b))
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, (key, value))| MovementGraphItem {
|
||||||
|
index: i,
|
||||||
|
name: key.clone(),
|
||||||
|
values: value.clone().into(),
|
||||||
|
item_type: match value {
|
||||||
|
GraphItem::User(_) => GraphItemType::Section,
|
||||||
|
GraphItem::Section(_) => GraphItemType::Section,
|
||||||
|
GraphItem::Item { state, .. } => GraphItemType::Item {
|
||||||
|
done: matches!(state, ItemState::Done),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
graph.items = graph_items;
|
||||||
|
}
|
||||||
|
GraphItem::Item { .. } => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
graph
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use hyperlog_core::log::{GraphItem, ItemState};
|
||||||
|
use similar_asserts::assert_eq;
|
||||||
|
|
||||||
|
use crate::components::movement_graph::{GraphItemType, MovementGraphItem};
|
||||||
|
|
||||||
|
use super::MovementGraph;
|
||||||
|
|
||||||
|
/// Lets say we've got a graph
|
||||||
|
/// ```json
|
||||||
|
/// {
|
||||||
|
/// "type": "user",
|
||||||
|
/// "something": {
|
||||||
|
/// "type": "section",
|
||||||
|
/// "something": {
|
||||||
|
/// "type": "section",
|
||||||
|
/// "something-else": {
|
||||||
|
/// "type": "section",
|
||||||
|
/// "blabla": {
|
||||||
|
/// "type": "section"
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// We can get something out like
|
||||||
|
/// [
|
||||||
|
/// 0: {key: something, values: [
|
||||||
|
/// 0: {key: something, values: [
|
||||||
|
/// ...
|
||||||
|
/// ]}
|
||||||
|
/// ]}
|
||||||
|
/// ]
|
||||||
|
#[test]
|
||||||
|
fn test_can_transform_to_movement_graph() {
|
||||||
|
let graph = GraphItem::User(BTreeMap::from([(
|
||||||
|
"0".to_string(),
|
||||||
|
GraphItem::Section(BTreeMap::from([
|
||||||
|
("00".to_string(), GraphItem::Section(BTreeMap::new())),
|
||||||
|
(
|
||||||
|
"01".to_string(),
|
||||||
|
GraphItem::Section(BTreeMap::from([
|
||||||
|
(
|
||||||
|
"010".to_string(),
|
||||||
|
GraphItem::Item {
|
||||||
|
title: "some-title".into(),
|
||||||
|
description: "some-desc".into(),
|
||||||
|
state: ItemState::NotDone,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"011".to_string(),
|
||||||
|
GraphItem::Item {
|
||||||
|
title: "some-title".into(),
|
||||||
|
description: "some-desc".into(),
|
||||||
|
state: ItemState::NotDone,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
])),
|
||||||
|
),
|
||||||
|
])),
|
||||||
|
)]));
|
||||||
|
|
||||||
|
let actual: MovementGraph = graph.into();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
MovementGraph {
|
||||||
|
items: vec![MovementGraphItem {
|
||||||
|
index: 0,
|
||||||
|
name: "0".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph {
|
||||||
|
items: vec![
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 0,
|
||||||
|
name: "00".into(),
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 1,
|
||||||
|
name: "01".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph {
|
||||||
|
items: vec![
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 0,
|
||||||
|
name: "010".into(),
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
item_type: GraphItemType::Item { done: false },
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 1,
|
||||||
|
name: "011".into(),
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
item_type: GraphItemType::Item { done: false },
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
actual
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_graph_item() -> anyhow::Result<()> {
|
||||||
|
let graph = MovementGraph {
|
||||||
|
items: vec![
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 0,
|
||||||
|
name: "0".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph {
|
||||||
|
items: vec![
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 0,
|
||||||
|
name: "0".into(),
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 1,
|
||||||
|
name: "0".into(),
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 1,
|
||||||
|
name: "0".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph {
|
||||||
|
items: vec![
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 0,
|
||||||
|
name: "0".into(),
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 1,
|
||||||
|
name: "0".into(),
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 2,
|
||||||
|
name: "0".into(),
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 2,
|
||||||
|
name: "0".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph {
|
||||||
|
items: vec![
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 0,
|
||||||
|
name: "0".into(),
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 1,
|
||||||
|
name: "0".into(),
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let actual_default = graph.get_graph(&[]);
|
||||||
|
assert_eq!(Some(&graph), actual_default);
|
||||||
|
|
||||||
|
let actual_first = graph.get_graph(&[0]);
|
||||||
|
assert_eq!(graph.items.first().map(|i| &i.values), actual_first);
|
||||||
|
|
||||||
|
let actual_second = graph.get_graph(&[1]);
|
||||||
|
assert_eq!(graph.items.get(1).map(|i| &i.values), actual_second);
|
||||||
|
|
||||||
|
let actual_nested = graph.get_graph(&[0, 0]);
|
||||||
|
assert_eq!(
|
||||||
|
graph
|
||||||
|
.items
|
||||||
|
.first()
|
||||||
|
.and_then(|i| i.values.items.first())
|
||||||
|
.map(|i| &i.values),
|
||||||
|
actual_nested
|
||||||
|
);
|
||||||
|
|
||||||
|
let actual_nested = graph.get_graph(&[0, 1]);
|
||||||
|
assert_eq!(
|
||||||
|
graph
|
||||||
|
.items
|
||||||
|
.first()
|
||||||
|
.and_then(|i| i.values.items.get(1))
|
||||||
|
.map(|i| &i.values),
|
||||||
|
actual_nested
|
||||||
|
);
|
||||||
|
|
||||||
|
let actual_nested = graph.get_graph(&[1, 2]);
|
||||||
|
assert_eq!(
|
||||||
|
graph
|
||||||
|
.items
|
||||||
|
.get(1)
|
||||||
|
.and_then(|i| i.values.items.get(2))
|
||||||
|
.map(|i| &i.values),
|
||||||
|
actual_nested
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_next_down() -> anyhow::Result<()> {
|
||||||
|
let graph = MovementGraph {
|
||||||
|
items: vec![
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 0,
|
||||||
|
name: "0".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph {
|
||||||
|
items: vec![MovementGraphItem {
|
||||||
|
index: 0,
|
||||||
|
name: "0".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 1,
|
||||||
|
name: "1".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph {
|
||||||
|
items: vec![
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 0,
|
||||||
|
name: "0".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 1,
|
||||||
|
name: "1".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 2,
|
||||||
|
name: "2".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph {
|
||||||
|
items: vec![
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 0,
|
||||||
|
name: "0".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 1,
|
||||||
|
name: "1".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 2,
|
||||||
|
name: "2".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph::default(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let actual = graph.next_down(&[]);
|
||||||
|
assert_eq!(None, actual);
|
||||||
|
|
||||||
|
let actual = graph.next_down(&[0]);
|
||||||
|
assert_eq!(Some(vec![1]), actual);
|
||||||
|
|
||||||
|
let actual = graph.next_down(&[1]);
|
||||||
|
assert_eq!(Some(vec![2]), actual);
|
||||||
|
|
||||||
|
let actual = graph.next_down(&[2]);
|
||||||
|
assert_eq!(None, actual);
|
||||||
|
|
||||||
|
let graph = MovementGraph {
|
||||||
|
items: vec![
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 0,
|
||||||
|
name: "other".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph {
|
||||||
|
items: vec![MovementGraphItem {
|
||||||
|
index: 0,
|
||||||
|
name: "other".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph {
|
||||||
|
items: vec![MovementGraphItem {
|
||||||
|
index: 0,
|
||||||
|
name: "other".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph { items: vec![] },
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 1,
|
||||||
|
name: "some".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph { items: vec![] },
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 2,
|
||||||
|
name: "something".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph {
|
||||||
|
items: vec![
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 0,
|
||||||
|
name: "else".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph { items: vec![] },
|
||||||
|
},
|
||||||
|
MovementGraphItem {
|
||||||
|
index: 1,
|
||||||
|
name: "third".into(),
|
||||||
|
item_type: GraphItemType::Section,
|
||||||
|
values: MovementGraph { items: vec![] },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let actual = graph.next_down(&[0]);
|
||||||
|
assert_eq!(Some(vec![1]), actual);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
143
crates/hyperlog-tui/src/components/render_graph.rs
Normal file
143
crates/hyperlog-tui/src/components/render_graph.rs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
use ratatui::prelude::*;
|
||||||
|
|
||||||
|
use super::movement_graph::{GraphItemType, MovementGraph};
|
||||||
|
|
||||||
|
pub trait RenderGraph {
|
||||||
|
fn render_graph(&self, items: &[usize]) -> Vec<Line>;
|
||||||
|
fn render_graph_spans(&self, items: &[usize]) -> Vec<Vec<Span>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderGraph for MovementGraph {
|
||||||
|
/// render_graph takes each level of items, renders them, and finally renders a strongly set selector for the current item the user is on
|
||||||
|
/// This is done from buttom up, and composed via. string padding
|
||||||
|
fn render_graph(&self, items: &[usize]) -> Vec<Line> {
|
||||||
|
// Gets the inner content of the strings
|
||||||
|
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
|
||||||
|
for item in &self.items {
|
||||||
|
let prefix = match item.item_type {
|
||||||
|
GraphItemType::Section => "- ",
|
||||||
|
GraphItemType::Item { done } => {
|
||||||
|
if done {
|
||||||
|
"- [x]"
|
||||||
|
} else {
|
||||||
|
"- [ ]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match items.split_first().map(|(first, rest)| {
|
||||||
|
if item.index == *first {
|
||||||
|
(true, rest)
|
||||||
|
} else {
|
||||||
|
(false, rest)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Some((true, rest)) => {
|
||||||
|
if rest.is_empty() {
|
||||||
|
lines.push(
|
||||||
|
Line::raw(format!("{} {}", prefix, item.name))
|
||||||
|
.style(Style::new().bold().white()),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
lines.push(
|
||||||
|
Line::raw(format!("{} {}", prefix, item.name))
|
||||||
|
.patch_style(Style::new().dark_gray()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push("".into());
|
||||||
|
|
||||||
|
let embedded_sections = item.values.render_graph_spans(rest);
|
||||||
|
for section in &embedded_sections {
|
||||||
|
let mut line = vec![Span::raw(" ".repeat(4))];
|
||||||
|
line.extend_from_slice(section);
|
||||||
|
lines.push(Line::from(line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
lines.push(
|
||||||
|
Line::raw(format!("{} {}", prefix, item.name))
|
||||||
|
.patch_style(Style::new().dark_gray()),
|
||||||
|
);
|
||||||
|
|
||||||
|
lines.push("".into());
|
||||||
|
|
||||||
|
let embedded_sections = item.values.render_graph_spans(&[]);
|
||||||
|
for section in &embedded_sections {
|
||||||
|
let mut line = vec![Span::raw(" ".repeat(4))];
|
||||||
|
line.extend_from_slice(section);
|
||||||
|
lines.push(Line::from(line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_graph_spans(&self, items: &[usize]) -> Vec<Vec<Span>> {
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
|
||||||
|
for item in &self.items {
|
||||||
|
let prefix = match item.item_type {
|
||||||
|
GraphItemType::Section => "-",
|
||||||
|
GraphItemType::Item { done } => {
|
||||||
|
if done {
|
||||||
|
"- [x]"
|
||||||
|
} else {
|
||||||
|
"- [ ]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match items.split_first().map(|(first, rest)| {
|
||||||
|
if item.index == *first {
|
||||||
|
(true, rest)
|
||||||
|
} else {
|
||||||
|
(false, rest)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Some((true, rest)) => {
|
||||||
|
let mut line = Vec::new();
|
||||||
|
if rest.is_empty() {
|
||||||
|
line.push(
|
||||||
|
Span::raw(format!("{} {}", prefix, item.name))
|
||||||
|
.style(Style::new().bold().white()),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
line.push(
|
||||||
|
Span::raw(format!("{} {}", prefix, item.name))
|
||||||
|
.patch_style(Style::new().dark_gray()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(line);
|
||||||
|
lines.push(vec!["".into()]);
|
||||||
|
|
||||||
|
let embedded_sections = item.values.render_graph_spans(rest);
|
||||||
|
for section in &embedded_sections {
|
||||||
|
let mut line = vec![Span::raw(" ".repeat(4))];
|
||||||
|
line.extend_from_slice(section);
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
lines.push(vec![Span::raw(format!("{prefix} {}", item.name))
|
||||||
|
.patch_style(Style::new().dark_gray())]);
|
||||||
|
|
||||||
|
lines.push(vec!["".into()]);
|
||||||
|
|
||||||
|
let embedded_sections = item.values.render_graph_spans(&[]);
|
||||||
|
for section in &embedded_sections {
|
||||||
|
let mut line = vec![Span::raw(" ".repeat(4))];
|
||||||
|
line.extend_from_slice(section);
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ use std::{io::Stdout, time::Duration};
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use app::{render_app, App};
|
use app::{render_app, App};
|
||||||
use commands::IntoCommand;
|
use commands::IntoCommand;
|
||||||
use components::GraphExplorer;
|
use components::graph_explorer::GraphExplorer;
|
||||||
use crossterm::event::{self, Event, KeyCode};
|
use crossterm::event::{self, Event, KeyCode};
|
||||||
use hyperlog_core::state::State;
|
use hyperlog_core::state::State;
|
||||||
use models::{EditMsg, Msg};
|
use models::{EditMsg, Msg};
|
||||||
@ -77,7 +77,6 @@ fn update(
|
|||||||
let mut cmd = match &app.mode {
|
let mut cmd = match &app.mode {
|
||||||
app::Mode::View => match key.code {
|
app::Mode::View => match key.code {
|
||||||
KeyCode::Enter => app.update(Msg::Interact)?,
|
KeyCode::Enter => app.update(Msg::Interact)?,
|
||||||
KeyCode::Char('q') => return Ok(UpdateConclusion::new(true)),
|
|
||||||
KeyCode::Char('l') => app.update(Msg::MoveRight)?,
|
KeyCode::Char('l') => app.update(Msg::MoveRight)?,
|
||||||
KeyCode::Char('h') => app.update(Msg::MoveLeft)?,
|
KeyCode::Char('h') => app.update(Msg::MoveLeft)?,
|
||||||
KeyCode::Char('j') => app.update(Msg::MoveDown)?,
|
KeyCode::Char('j') => app.update(Msg::MoveDown)?,
|
||||||
@ -109,6 +108,10 @@ fn update(
|
|||||||
let msg = cmd.into_command().execute();
|
let msg = cmd.into_command().execute();
|
||||||
match msg {
|
match msg {
|
||||||
Some(msg) => {
|
Some(msg) => {
|
||||||
|
if let Msg::QuitApp = msg {
|
||||||
|
return Ok(UpdateConclusion(true));
|
||||||
|
}
|
||||||
|
|
||||||
cmd = app.update(msg)?;
|
cmd = app.update(msg)?;
|
||||||
}
|
}
|
||||||
None => break,
|
None => break,
|
||||||
|
@ -8,6 +8,7 @@ pub enum Msg {
|
|||||||
MoveLeft,
|
MoveLeft,
|
||||||
MoveDown,
|
MoveDown,
|
||||||
MoveUp,
|
MoveUp,
|
||||||
|
QuitApp,
|
||||||
OpenCreateItemDialog,
|
OpenCreateItemDialog,
|
||||||
OpenEditItemDialog { item: GraphItem },
|
OpenEditItemDialog { item: GraphItem },
|
||||||
Interact,
|
Interact,
|
||||||
|
Loading…
Reference in New Issue
Block a user