kjuulh
20190ac784
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
406 lines
13 KiB
Rust
406 lines
13 KiB
Rust
use anyhow::Result;
|
|
use hyperlog_core::log::GraphItem;
|
|
use itertools::Itertools;
|
|
use ratatui::{prelude::*, widgets::*};
|
|
|
|
use crate::{
|
|
command_parser::Commands,
|
|
commands::{
|
|
archive::ArchiveCommandExt, batch::BatchCommand, create_item::CreateItemCommandExt,
|
|
create_section::CreateSectionCommandExt, open_item::OpenItemCommandExt,
|
|
open_update_item_dialog::OpenUpdateItemDialogCommandExt, toggle_item::ToggleItemCommandExt,
|
|
update_graph::UpdateGraphCommandExt, Command, IntoCommand,
|
|
},
|
|
components::movement_graph::GraphItemType,
|
|
models::{IOEvent, Msg},
|
|
state::SharedState,
|
|
};
|
|
|
|
use super::{
|
|
movement_graph::{MovementGraph, MovementGraphItem},
|
|
render_graph::summarize::SummarizeRenderGraph,
|
|
};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum FilterBy {
|
|
NotDone,
|
|
None,
|
|
}
|
|
|
|
impl Default for FilterBy {
|
|
fn default() -> Self {
|
|
Self::NotDone
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Clone, Debug)]
|
|
pub struct DisplayOptions {
|
|
pub filter_by: FilterBy,
|
|
}
|
|
|
|
pub struct GraphExplorer<'a> {
|
|
state: SharedState,
|
|
|
|
pub inner: GraphExplorerState<'a>,
|
|
}
|
|
|
|
pub struct GraphExplorerState<'a> {
|
|
root: String,
|
|
|
|
current_path: Option<&'a str>,
|
|
current_position: Vec<usize>,
|
|
|
|
display_options: DisplayOptions,
|
|
|
|
graph: Option<GraphItem>,
|
|
}
|
|
|
|
impl<'a> GraphExplorerState<'a> {
|
|
pub fn update(&mut self, msg: &Msg) -> Option<Command> {
|
|
if let Msg::GraphUpdated(graph_update) = msg {
|
|
match graph_update {
|
|
IOEvent::Initialized => {
|
|
tracing::trace!("initialized graph");
|
|
}
|
|
IOEvent::Success(graph) => {
|
|
tracing::trace!("graph updated successfully");
|
|
self.graph = Some(graph.clone());
|
|
}
|
|
IOEvent::Failure(e) => {
|
|
tracing::error!("graph update failed: {}", e);
|
|
}
|
|
IOEvent::Optimistic(graph) => {
|
|
tracing::trace!("graph updated optimistically");
|
|
self.graph = Some(graph.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
}
|
|
|
|
impl<'a> GraphExplorer<'a> {
|
|
pub fn new(root: String, state: SharedState) -> Self {
|
|
Self {
|
|
state,
|
|
inner: GraphExplorerState::<'a> {
|
|
root,
|
|
current_path: None,
|
|
current_position: Vec::new(),
|
|
graph: None,
|
|
display_options: DisplayOptions::default(),
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn new_update_graph(&self) -> Command {
|
|
self.state.update_graph_command().command(
|
|
&self.inner.root,
|
|
&self
|
|
.inner
|
|
.current_path
|
|
.map(|p| p.split(".").collect_vec())
|
|
.unwrap_or_default(),
|
|
)
|
|
}
|
|
|
|
pub async fn update_graph(&mut self) -> Result<&mut Self> {
|
|
let now = std::time::SystemTime::now();
|
|
|
|
let graph = self
|
|
.state
|
|
.querier
|
|
.get_async(
|
|
&self.inner.root,
|
|
self.inner
|
|
.current_path
|
|
.map(|p| p.split('.').collect::<Vec<_>>())
|
|
.unwrap_or_default(),
|
|
)
|
|
.await?
|
|
.ok_or(anyhow::anyhow!("graph should've had an item"))?;
|
|
|
|
self.inner.graph = Some(graph);
|
|
|
|
let elapsed = now.elapsed()?;
|
|
tracing::trace!("Graph.update_graph took: {}nanos", elapsed.as_nanos());
|
|
|
|
Ok(self)
|
|
}
|
|
|
|
fn linearize_graph(&self) -> Option<MovementGraph> {
|
|
tracing::trace!("current display options: {:?}", self.inner.display_options);
|
|
self.inner
|
|
.graph
|
|
.clone()
|
|
.map(|g| MovementGraph::new(g, &self.inner.display_options))
|
|
}
|
|
|
|
/// Will only incrmeent to the next level
|
|
///
|
|
/// Current: 0.1.0
|
|
/// Available: 0.1.0.[0,1,2]
|
|
/// Choses: 0.1.0.0 else nothing
|
|
pub(crate) fn move_right(&mut self) -> Result<()> {
|
|
if let Some(graph) = self.linearize_graph() {
|
|
tracing::trace!("graph: {:?}", graph);
|
|
let position_items = &self.inner.current_position;
|
|
|
|
if let Some(next_item) = graph.next_right(position_items) {
|
|
self.inner.current_position.push(next_item.index);
|
|
tracing::trace!("found next item: {:?}", self.inner.current_position);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Will only incrmeent to the next level
|
|
///
|
|
/// Current: 0.1.0
|
|
/// Available: 0.[0,1,2].0
|
|
/// Choses: 0.1 else nothing
|
|
pub(crate) fn move_left(&mut self) -> Result<()> {
|
|
if let Some(last) = self.inner.current_position.pop() {
|
|
tracing::trace!(
|
|
"found last item: {:?}, popped: {}",
|
|
self.inner.current_position,
|
|
last
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Will move up if a sibling exists, or up to the most common sibling between sections
|
|
///
|
|
/// Current: 0.1.1
|
|
/// Available: 0.[0.[0,1],1.[0,1]]
|
|
/// Chose: 0.1.0 again 0.0 We don't choose a subitem in the next three instead we just find the most common sibling
|
|
pub(crate) fn move_up(&mut self) -> Result<()> {
|
|
if let Some(graph) = self.linearize_graph() {
|
|
let position_items = &self.inner.current_position;
|
|
|
|
if let Some(next_item) = graph.next_up(position_items) {
|
|
self.inner.current_position = next_item;
|
|
tracing::trace!("found next up: {:?}", self.inner.current_position)
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Will move down if a sibling exists, or down to the most common sibling between sections
|
|
///
|
|
/// Current: 0.0.0
|
|
/// Available: 0.[0.[0,1],1.[0,1]]
|
|
/// Chose: 0.0.1 again 0.1
|
|
pub(crate) fn move_down(&mut self) -> Result<()> {
|
|
if let Some(graph) = self.linearize_graph() {
|
|
let position_items = &self.inner.current_position;
|
|
|
|
if let Some(next_item) = graph.next_down(position_items) {
|
|
self.inner.current_position = next_item;
|
|
tracing::trace!("found next down: {:?}", self.inner.current_position)
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn get_current_path(&self) -> Vec<String> {
|
|
let graph = self.linearize_graph();
|
|
let position_items = &self.inner.current_position;
|
|
|
|
if let Some(graph) = graph {
|
|
graph.to_current_path(position_items)
|
|
} else {
|
|
Vec::new()
|
|
}
|
|
}
|
|
|
|
fn get_current_item(&self) -> Option<MovementGraphItem> {
|
|
let graph = self.linearize_graph();
|
|
|
|
if let Some(graph) = graph {
|
|
graph.get_graph_item(&self.inner.current_position).cloned()
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn execute_command(&mut self, command: &Commands) -> anyhow::Result<Option<Command>> {
|
|
let mut batch = BatchCommand::default();
|
|
|
|
match command {
|
|
Commands::Archive => {
|
|
if !self.get_current_path().is_empty() {
|
|
batch.with(
|
|
self.state
|
|
.archive_command()
|
|
.command(
|
|
&self.inner.root,
|
|
&self
|
|
.get_current_path()
|
|
.iter()
|
|
.map(|i| i.as_str())
|
|
.collect_vec(),
|
|
)
|
|
.into_command(),
|
|
);
|
|
}
|
|
}
|
|
Commands::CreateSection { name } => {
|
|
if !name.is_empty() {
|
|
let mut path = self.get_current_path();
|
|
path.push(name.replace(".", "-"));
|
|
|
|
let cmd = self.state.create_section_command().command(
|
|
&self.inner.root,
|
|
&path.iter().map(|i| i.as_str()).collect_vec(),
|
|
);
|
|
|
|
batch.with(cmd.into_command());
|
|
}
|
|
}
|
|
Commands::CreateItem { name } => {
|
|
if !name.is_empty() {
|
|
let mut path = self.get_current_path();
|
|
path.push(name.replace(".", " "));
|
|
let cmd = self.state.create_item_command().command(
|
|
&self.inner.root,
|
|
&path.iter().map(|i| i.as_str()).collect_vec(),
|
|
name,
|
|
"",
|
|
&hyperlog_core::log::ItemState::default(),
|
|
);
|
|
|
|
batch.with(cmd.into_command());
|
|
}
|
|
}
|
|
Commands::CreateBelow { name } => {
|
|
if !name.is_empty() {
|
|
let path = self.get_current_path();
|
|
if let Some((_, path)) = path.split_last() {
|
|
let mut path = path.to_vec();
|
|
path.push(name.replace(".", " "));
|
|
|
|
let cmd = self.state.create_item_command().command(
|
|
&self.inner.root,
|
|
&path.iter().map(|i| i.as_str()).collect_vec(),
|
|
name,
|
|
"",
|
|
&hyperlog_core::log::ItemState::default(),
|
|
);
|
|
|
|
batch.with(cmd.into_command());
|
|
}
|
|
}
|
|
}
|
|
Commands::Edit => {
|
|
if let Some(item) = self.get_current_item() {
|
|
let path = self.get_current_path();
|
|
|
|
tracing::debug!(
|
|
"found item to edit: path: {}, item: {}",
|
|
path.join("."),
|
|
item.name
|
|
);
|
|
match item.item_type {
|
|
GraphItemType::Section => {
|
|
todo!("cannot edit section at the moment")
|
|
}
|
|
GraphItemType::Item { .. } => {
|
|
batch.with(
|
|
self.state
|
|
.open_update_item_dialog_command()
|
|
.command(&self.inner.root, path),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Commands::ShowAll => {
|
|
self.inner.display_options.filter_by = FilterBy::None;
|
|
}
|
|
Commands::HideDone => {
|
|
self.inner.display_options.filter_by = FilterBy::NotDone;
|
|
}
|
|
Commands::Test => {
|
|
return Ok(Some(Command::new(|dispatch| {
|
|
tokio::spawn(async move {
|
|
dispatch.send(Msg::MoveDown);
|
|
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
|
dispatch.send(Msg::EnterViewMode);
|
|
});
|
|
|
|
None
|
|
})));
|
|
}
|
|
Commands::Open => {
|
|
if self.get_current_item().is_some() {
|
|
batch.with(
|
|
self.state
|
|
.open_item_command()
|
|
.command(&self.inner.root, self.get_current_path()),
|
|
);
|
|
}
|
|
}
|
|
|
|
_ => (),
|
|
}
|
|
|
|
//self.update_graph()?;
|
|
|
|
Ok(Some(batch.into_command()))
|
|
}
|
|
|
|
pub(crate) fn interact(&mut self) -> anyhow::Result<Command> {
|
|
let mut batch = BatchCommand::default();
|
|
|
|
if !self.get_current_path().is_empty() {
|
|
tracing::info!("toggling state of items");
|
|
|
|
// self.state
|
|
// .commander
|
|
// .execute(commander::Command::ToggleItem {
|
|
// root: self.inner.root.to_string(),
|
|
// path: self.get_current_path(),
|
|
// })?;
|
|
|
|
let cmd = self.state.toggle_item_command().command(
|
|
&self.inner.root,
|
|
&self
|
|
.get_current_path()
|
|
.iter()
|
|
.map(|i| i.as_str())
|
|
.collect_vec(),
|
|
);
|
|
|
|
batch.with(cmd.into_command());
|
|
}
|
|
|
|
//self.update_graph()?;
|
|
|
|
Ok(batch.into_command())
|
|
}
|
|
}
|
|
|
|
impl<'a> StatefulWidget for GraphExplorer<'a> {
|
|
type State = GraphExplorerState<'a>;
|
|
|
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
|
let Rect { height, .. } = area;
|
|
let _height = height as usize;
|
|
|
|
if let Some(graph) = &state.graph {
|
|
let movement_graph: MovementGraph =
|
|
MovementGraph::new(graph.clone(), &state.display_options);
|
|
let lines = movement_graph.render_graph(&state.current_position);
|
|
let para = Paragraph::new(lines);
|
|
para.render(area, buf);
|
|
}
|
|
}
|
|
}
|