chore: fix test breaking changes
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
@@ -25,6 +25,13 @@ pub enum Command {
|
||||
description: String,
|
||||
state: ItemState,
|
||||
},
|
||||
UpdateItem {
|
||||
root: String,
|
||||
path: Vec<String>,
|
||||
title: String,
|
||||
description: String,
|
||||
state: ItemState,
|
||||
},
|
||||
ToggleItem {
|
||||
root: String,
|
||||
path: Vec<String>,
|
||||
@@ -88,6 +95,21 @@ impl Commander {
|
||||
Command::ToggleItem { root, path } => self
|
||||
.engine
|
||||
.toggle_item(&root, &path.iter().map(|p| p.as_str()).collect::<Vec<_>>())?,
|
||||
Command::UpdateItem {
|
||||
root,
|
||||
path,
|
||||
title,
|
||||
description,
|
||||
state,
|
||||
} => self.engine.update_item(
|
||||
&root,
|
||||
&path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
GraphItem::Item {
|
||||
title,
|
||||
description,
|
||||
state,
|
||||
},
|
||||
)?,
|
||||
}
|
||||
|
||||
self.storage.store(&self.engine)?;
|
||||
|
@@ -44,7 +44,7 @@ impl Engine {
|
||||
match current_item {
|
||||
GraphItem::User(u) => match u.get_mut(section.to_owned()) {
|
||||
Some(graph_item) => {
|
||||
current_item = graph_item.as_mut();
|
||||
current_item = graph_item;
|
||||
}
|
||||
None => anyhow::bail!("path: {} section was not found", section),
|
||||
},
|
||||
@@ -60,10 +60,10 @@ impl Engine {
|
||||
|
||||
match current_item {
|
||||
GraphItem::User(u) => {
|
||||
u.insert(last.to_string(), Box::new(item));
|
||||
u.insert(last.to_string(), item);
|
||||
}
|
||||
GraphItem::Section(s) => {
|
||||
s.insert(last.to_string(), Box::new(item));
|
||||
s.insert(last.to_string(), item);
|
||||
}
|
||||
GraphItem::Item { .. } => anyhow::bail!("cannot insert an item into an item"),
|
||||
}
|
||||
@@ -109,11 +109,11 @@ impl Engine {
|
||||
|
||||
match dest {
|
||||
GraphItem::User(u) => {
|
||||
u.try_insert(src_item.to_string(), Box::new(src))
|
||||
u.try_insert(src_item.to_string(), src)
|
||||
.map_err(|_e| anyhow!("key was already found, aborting: {}", src_item))?;
|
||||
}
|
||||
GraphItem::Section(s) => {
|
||||
s.try_insert(src_item.to_string(), Box::new(src))
|
||||
s.try_insert(src_item.to_string(), src)
|
||||
.map_err(|_e| anyhow!("key was already found, aborting: {}", src_item))?;
|
||||
}
|
||||
GraphItem::Item { .. } => {
|
||||
@@ -145,6 +145,57 @@ impl Engine {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_item(
|
||||
&mut self,
|
||||
root: &str,
|
||||
path: &[&str],
|
||||
item: &GraphItem,
|
||||
) -> anyhow::Result<()> {
|
||||
if let Some((name, dest_last)) = path.split_last() {
|
||||
if let Some(parent) = self.get_mut(root, dest_last) {
|
||||
match parent {
|
||||
GraphItem::User(s) | GraphItem::Section(s) => {
|
||||
if let Some(mut existing) = s.remove(*name) {
|
||||
match (&mut existing, item) {
|
||||
(
|
||||
GraphItem::Item {
|
||||
title: ex_title,
|
||||
description: ex_desc,
|
||||
state: ex_state,
|
||||
},
|
||||
GraphItem::Item {
|
||||
title,
|
||||
description,
|
||||
state,
|
||||
},
|
||||
) => {
|
||||
ex_title.clone_from(title);
|
||||
ex_desc.clone_from(description);
|
||||
ex_state.clone_from(state);
|
||||
|
||||
let title = title.replace(".", "-");
|
||||
s.insert(title, existing.clone());
|
||||
}
|
||||
_ => {
|
||||
anyhow::bail!(
|
||||
"path: {}.{} found is not an item",
|
||||
root,
|
||||
path.join(".")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
GraphItem::Item { .. } => {
|
||||
anyhow::bail!("cannot rename when item is placed in an item")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Engine {
|
||||
|
@@ -13,13 +13,19 @@ pub enum ItemState {
|
||||
Done,
|
||||
}
|
||||
|
||||
impl Default for ItemState {
|
||||
fn default() -> Self {
|
||||
Self::NotDone
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum GraphItem {
|
||||
#[serde(rename = "user")]
|
||||
User(BTreeMap<String, Box<GraphItem>>),
|
||||
User(BTreeMap<String, GraphItem>),
|
||||
#[serde(rename = "section")]
|
||||
Section(BTreeMap<String, Box<GraphItem>>),
|
||||
Section(BTreeMap<String, GraphItem>),
|
||||
#[serde(rename = "item")]
|
||||
Item {
|
||||
title: String,
|
||||
@@ -58,8 +64,7 @@ impl GraphItem {
|
||||
Some((first, rest)) => match self {
|
||||
GraphItem::User(section) | GraphItem::Section(section) => {
|
||||
if rest.is_empty() {
|
||||
let val = section.remove(*first);
|
||||
val.map(|v| *v)
|
||||
section.remove(*first)
|
||||
} else {
|
||||
section.get_mut(*first)?.take(rest)
|
||||
}
|
||||
@@ -131,7 +136,7 @@ mod test {
|
||||
let mut user = BTreeMap::new();
|
||||
user.insert(
|
||||
"some-project".into(),
|
||||
Box::new(GraphItem::Section(BTreeMap::default())),
|
||||
GraphItem::Section(BTreeMap::default()),
|
||||
);
|
||||
|
||||
expected.insert("kjuulh".into(), GraphItem::User(user));
|
||||
@@ -160,13 +165,10 @@ mod test {
|
||||
let mut some_project = BTreeMap::default();
|
||||
some_project.insert(
|
||||
"some-nested-project".into(),
|
||||
Box::new(GraphItem::Section(BTreeMap::default())),
|
||||
GraphItem::Section(BTreeMap::default()),
|
||||
);
|
||||
let mut user = BTreeMap::new();
|
||||
user.insert(
|
||||
"some-project".into(),
|
||||
Box::new(GraphItem::Section(some_project)),
|
||||
);
|
||||
user.insert("some-project".into(), GraphItem::Section(some_project));
|
||||
|
||||
expected.insert("kjuulh".into(), GraphItem::User(user));
|
||||
|
||||
@@ -200,23 +202,20 @@ mod test {
|
||||
let mut nested_project = BTreeMap::default();
|
||||
nested_project.insert(
|
||||
"some-todo".into(),
|
||||
Box::new(GraphItem::Item {
|
||||
GraphItem::Item {
|
||||
title: "some title".into(),
|
||||
description: "some description".into(),
|
||||
state: ItemState::NotDone,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
let mut some_project = BTreeMap::default();
|
||||
some_project.insert(
|
||||
"some-nested-project".into(),
|
||||
Box::new(GraphItem::Section(nested_project)),
|
||||
GraphItem::Section(nested_project),
|
||||
);
|
||||
let mut user = BTreeMap::new();
|
||||
user.insert(
|
||||
"some-project".into(),
|
||||
Box::new(GraphItem::Section(some_project)),
|
||||
);
|
||||
user.insert("some-project".into(), GraphItem::Section(some_project));
|
||||
|
||||
expected.insert("kjuulh".into(), GraphItem::User(user));
|
||||
|
||||
|
@@ -49,4 +49,18 @@ impl SharedEngine {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn update_item(
|
||||
&self,
|
||||
root: &str,
|
||||
path: &[&str],
|
||||
state: GraphItem,
|
||||
) -> anyhow::Result<()> {
|
||||
self.inner
|
||||
.write()
|
||||
.unwrap()
|
||||
.update_item(root, path, &state)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
use hyperlog_core::log::GraphItem;
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, Padding, Paragraph},
|
||||
@@ -10,7 +11,10 @@ use crate::{
|
||||
|
||||
use self::{
|
||||
command_bar::{CommandBar, CommandBarState},
|
||||
dialog::{CreateItem, CreateItemState},
|
||||
dialog::{
|
||||
create_item::{CreateItem, CreateItemState},
|
||||
edit_item::{EditItem, EditItemState},
|
||||
},
|
||||
};
|
||||
|
||||
mod command_bar;
|
||||
@@ -18,12 +22,14 @@ pub mod dialog;
|
||||
|
||||
pub enum Dialog {
|
||||
CreateItem { state: CreateItemState },
|
||||
EditItem { state: EditItemState },
|
||||
}
|
||||
|
||||
impl Dialog {
|
||||
pub fn get_command(&self) -> Option<hyperlog_core::commander::Command> {
|
||||
match self {
|
||||
Dialog::CreateItem { state } => state.get_command(),
|
||||
Dialog::EditItem { state } => state.get_command(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,12 +82,13 @@ impl<'a> App<'a> {
|
||||
pub fn update(&mut self, msg: Msg) -> anyhow::Result<impl IntoCommand> {
|
||||
tracing::trace!("handling msg: {:?}", msg);
|
||||
|
||||
match msg {
|
||||
match &msg {
|
||||
Msg::MoveRight => self.graph_explorer.move_right()?,
|
||||
Msg::MoveLeft => self.graph_explorer.move_left()?,
|
||||
Msg::MoveDown => self.graph_explorer.move_down()?,
|
||||
Msg::MoveUp => self.graph_explorer.move_up()?,
|
||||
Msg::OpenCreateItemDialog => self.open_dialog(),
|
||||
Msg::OpenEditItemDialog { item } => self.open_edit_item_dialog(item),
|
||||
Msg::EnterInsertMode => self.mode = Mode::Insert,
|
||||
Msg::EnterViewMode => self.mode = Mode::View,
|
||||
Msg::EnterCommandMode => {
|
||||
@@ -95,7 +102,7 @@ impl<'a> App<'a> {
|
||||
Msg::SubmitCommand { command } => {
|
||||
tracing::info!("submitting command");
|
||||
|
||||
if let Some(command) = CommandParser::parse(&command) {
|
||||
if let Some(command) = CommandParser::parse(command) {
|
||||
match self.focus {
|
||||
AppFocus::Dialog => {
|
||||
if command.is_write() {
|
||||
@@ -113,7 +120,12 @@ impl<'a> App<'a> {
|
||||
self.dialog = None;
|
||||
}
|
||||
}
|
||||
AppFocus::Graph => self.graph_explorer.execute_command(&command)?,
|
||||
AppFocus::Graph => {
|
||||
if let Some(msg) = self.graph_explorer.execute_command(&command)? {
|
||||
self.command = None;
|
||||
return Ok(msg.into_command());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.command = None;
|
||||
@@ -128,6 +140,7 @@ impl<'a> App<'a> {
|
||||
} else if let Some(dialog) = &mut self.dialog {
|
||||
match dialog {
|
||||
Dialog::CreateItem { state } => state.update(&msg)?,
|
||||
Dialog::EditItem { state } => state.update(&msg)?,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +158,20 @@ impl<'a> App<'a> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn open_edit_item_dialog(&mut self, item: &GraphItem) {
|
||||
if self.dialog.is_none() {
|
||||
let root = self.root.clone();
|
||||
let path = self.graph_explorer.get_current_path();
|
||||
|
||||
self.dialog = Some(Dialog::EditItem {
|
||||
state: EditItemState::new(root, path, item),
|
||||
});
|
||||
self.command = None;
|
||||
self.focus = AppFocus::Dialog;
|
||||
self.mode = Mode::Insert;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for &mut App<'a> {
|
||||
@@ -176,6 +203,7 @@ pub fn render_app(frame: &mut Frame, state: &mut App) {
|
||||
|
||||
match dialog {
|
||||
Dialog::CreateItem { .. } => heading_parts.push(Span::raw("create item")),
|
||||
Dialog::EditItem { .. } => heading_parts.push(Span::raw("edit item")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +266,9 @@ pub fn render_app(frame: &mut Frame, state: &mut App) {
|
||||
Dialog::CreateItem { state } => {
|
||||
frame.render_stateful_widget(&mut CreateItem::default(), chunks[1], state)
|
||||
}
|
||||
Dialog::EditItem { state } => {
|
||||
frame.render_stateful_widget(&mut EditItem::default(), chunks[1], state)
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
@@ -1,4 +1,3 @@
|
||||
use itertools::Itertools;
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
|
||||
use crate::models::{EditMsg, Msg};
|
||||
@@ -74,7 +73,16 @@ pub struct InputBuffer {
|
||||
}
|
||||
|
||||
impl InputBuffer {
|
||||
fn transform_focused(&mut self) {
|
||||
pub fn new(input: String) -> Self {
|
||||
Self {
|
||||
state: BufferState::Static {
|
||||
content: input,
|
||||
position: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transform_focused(&mut self) {
|
||||
match &mut self.state {
|
||||
BufferState::Focused { .. } => {}
|
||||
BufferState::Static { content, position } => {
|
||||
@@ -136,6 +144,14 @@ impl InputBuffer {
|
||||
BufferState::Static { content, .. } => content.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_position(&mut self, title_len: usize) {
|
||||
match &mut self.state {
|
||||
BufferState::Focused { position, .. } | BufferState::Static { position, .. } => {
|
||||
*position = title_len
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InputField<'a> {
|
||||
@@ -189,110 +205,5 @@ fn clamp_x(area: &Rect, x: u16) -> u16 {
|
||||
}
|
||||
}
|
||||
|
||||
enum CreateItemFocused {
|
||||
Title,
|
||||
Description,
|
||||
}
|
||||
impl Default for CreateItemFocused {
|
||||
fn default() -> Self {
|
||||
Self::Title
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CreateItemState {
|
||||
root: String,
|
||||
path: Vec<String>,
|
||||
|
||||
title: InputBuffer,
|
||||
description: InputBuffer,
|
||||
|
||||
focused: CreateItemFocused,
|
||||
}
|
||||
|
||||
impl CreateItemState {
|
||||
pub fn new(root: impl Into<String>, path: impl IntoIterator<Item = impl Into<String>>) -> Self {
|
||||
let root = root.into();
|
||||
let path = path.into_iter().map(|p| p.into()).collect_vec();
|
||||
|
||||
Self {
|
||||
root,
|
||||
path,
|
||||
|
||||
title: Default::default(),
|
||||
description: Default::default(),
|
||||
focused: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, msg: &Msg) -> anyhow::Result<()> {
|
||||
match &msg {
|
||||
Msg::MoveDown | Msg::MoveUp => match self.focused {
|
||||
CreateItemFocused::Title => self.focused = CreateItemFocused::Description,
|
||||
CreateItemFocused::Description => self.focused = CreateItemFocused::Title,
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match self.focused {
|
||||
CreateItemFocused::Title => {
|
||||
self.title.update(msg)?;
|
||||
}
|
||||
CreateItemFocused::Description => {
|
||||
self.description.update(msg)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_command(&self) -> Option<hyperlog_core::commander::Command> {
|
||||
let title = self.title.string();
|
||||
let description = self.description.string();
|
||||
|
||||
if !title.is_empty() {
|
||||
let mut path = self.path.clone();
|
||||
path.push(title.replace([' ', '.'], "-"));
|
||||
|
||||
Some(hyperlog_core::commander::Command::CreateItem {
|
||||
root: self.root.clone(),
|
||||
path,
|
||||
title: title.trim().into(),
|
||||
description: description.trim().into(),
|
||||
state: hyperlog_core::log::ItemState::NotDone,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CreateItem {}
|
||||
|
||||
impl StatefulWidget for &mut CreateItem {
|
||||
type State = CreateItemState;
|
||||
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let chunks = Layout::vertical(vec![
|
||||
Constraint::Length(2),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
])
|
||||
.split(area);
|
||||
|
||||
let path = format!("path: {}.{}", state.root, state.path.join("."));
|
||||
let path_header = Paragraph::new(path).dark_gray();
|
||||
path_header.render(chunks[0], buf);
|
||||
|
||||
let mut title_input = InputField::new("title");
|
||||
let mut description_input = InputField::new("description");
|
||||
|
||||
match state.focused {
|
||||
CreateItemFocused::Title => title_input.focused = true,
|
||||
CreateItemFocused::Description => description_input.focused = true,
|
||||
}
|
||||
|
||||
title_input.render(chunks[1], buf, &mut state.title);
|
||||
description_input.render(chunks[2], buf, &mut state.description);
|
||||
}
|
||||
}
|
||||
pub mod create_item;
|
||||
pub mod edit_item;
|
||||
|
114
crates/hyperlog-tui/src/app/dialog/create_item.rs
Normal file
114
crates/hyperlog-tui/src/app/dialog/create_item.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use itertools::Itertools;
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
|
||||
use crate::models::Msg;
|
||||
|
||||
use super::{InputBuffer, InputField};
|
||||
|
||||
enum CreateItemFocused {
|
||||
Title,
|
||||
Description,
|
||||
}
|
||||
impl Default for CreateItemFocused {
|
||||
fn default() -> Self {
|
||||
Self::Title
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CreateItemState {
|
||||
root: String,
|
||||
path: Vec<String>,
|
||||
|
||||
title: InputBuffer,
|
||||
description: InputBuffer,
|
||||
|
||||
focused: CreateItemFocused,
|
||||
}
|
||||
|
||||
impl CreateItemState {
|
||||
pub fn new(root: impl Into<String>, path: impl IntoIterator<Item = impl Into<String>>) -> Self {
|
||||
let root = root.into();
|
||||
let path = path.into_iter().map(|p| p.into()).collect_vec();
|
||||
|
||||
Self {
|
||||
root,
|
||||
path,
|
||||
|
||||
title: Default::default(),
|
||||
description: Default::default(),
|
||||
focused: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, msg: &Msg) -> anyhow::Result<()> {
|
||||
match &msg {
|
||||
Msg::MoveDown | Msg::MoveUp => match self.focused {
|
||||
CreateItemFocused::Title => self.focused = CreateItemFocused::Description,
|
||||
CreateItemFocused::Description => self.focused = CreateItemFocused::Title,
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match self.focused {
|
||||
CreateItemFocused::Title => {
|
||||
self.title.update(msg)?;
|
||||
}
|
||||
CreateItemFocused::Description => {
|
||||
self.description.update(msg)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_command(&self) -> Option<hyperlog_core::commander::Command> {
|
||||
let title = self.title.string();
|
||||
let description = self.description.string();
|
||||
|
||||
if !title.is_empty() {
|
||||
let mut path = self.path.clone();
|
||||
path.push(title.replace([' ', '.'], "-"));
|
||||
|
||||
Some(hyperlog_core::commander::Command::CreateItem {
|
||||
root: self.root.clone(),
|
||||
path,
|
||||
title: title.trim().into(),
|
||||
description: description.trim().into(),
|
||||
state: hyperlog_core::log::ItemState::NotDone,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CreateItem {}
|
||||
|
||||
impl StatefulWidget for &mut CreateItem {
|
||||
type State = CreateItemState;
|
||||
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let chunks = Layout::vertical(vec![
|
||||
Constraint::Length(2),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
])
|
||||
.split(area);
|
||||
|
||||
let path = format!("path: {}.{}", state.root, state.path.join("."));
|
||||
let path_header = Paragraph::new(path).dark_gray();
|
||||
path_header.render(chunks[0], buf);
|
||||
|
||||
let mut title_input = InputField::new("title");
|
||||
let mut description_input = InputField::new("description");
|
||||
|
||||
match state.focused {
|
||||
CreateItemFocused::Title => title_input.focused = true,
|
||||
CreateItemFocused::Description => description_input.focused = true,
|
||||
}
|
||||
|
||||
title_input.render(chunks[1], buf, &mut state.title);
|
||||
description_input.render(chunks[2], buf, &mut state.description);
|
||||
}
|
||||
}
|
138
crates/hyperlog-tui/src/app/dialog/edit_item.rs
Normal file
138
crates/hyperlog-tui/src/app/dialog/edit_item.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
use hyperlog_core::log::GraphItem;
|
||||
use itertools::Itertools;
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
|
||||
use crate::models::Msg;
|
||||
|
||||
use super::{InputBuffer, InputField};
|
||||
|
||||
enum EditItemFocused {
|
||||
Title,
|
||||
Description,
|
||||
}
|
||||
impl Default for EditItemFocused {
|
||||
fn default() -> Self {
|
||||
Self::Title
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EditItemState {
|
||||
root: String,
|
||||
path: Vec<String>,
|
||||
|
||||
title: InputBuffer,
|
||||
description: InputBuffer,
|
||||
|
||||
item: GraphItem,
|
||||
|
||||
focused: EditItemFocused,
|
||||
}
|
||||
|
||||
impl EditItemState {
|
||||
pub fn new(
|
||||
root: impl Into<String>,
|
||||
path: impl IntoIterator<Item = impl Into<String>>,
|
||||
item: &GraphItem,
|
||||
) -> Self {
|
||||
let root = root.into();
|
||||
let path = path.into_iter().map(|p| p.into()).collect_vec();
|
||||
|
||||
match item {
|
||||
GraphItem::Item {
|
||||
title, description, ..
|
||||
} => {
|
||||
let title_len = title.len();
|
||||
let mut title = InputBuffer::new(title.clone());
|
||||
title.transform_focused();
|
||||
title.set_position(title_len);
|
||||
|
||||
Self {
|
||||
root,
|
||||
path,
|
||||
|
||||
item: item.clone(),
|
||||
|
||||
title,
|
||||
description: InputBuffer::new(description.clone()),
|
||||
focused: Default::default(),
|
||||
}
|
||||
}
|
||||
_ => todo!("cannot edit item from other than GraphItem::Item"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, msg: &Msg) -> anyhow::Result<()> {
|
||||
match &msg {
|
||||
Msg::MoveDown | Msg::MoveUp => match self.focused {
|
||||
EditItemFocused::Title => self.focused = EditItemFocused::Description,
|
||||
EditItemFocused::Description => self.focused = EditItemFocused::Title,
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match self.focused {
|
||||
EditItemFocused::Title => {
|
||||
self.title.update(msg)?;
|
||||
}
|
||||
EditItemFocused::Description => {
|
||||
self.description.update(msg)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_command(&self) -> Option<hyperlog_core::commander::Command> {
|
||||
let title = self.title.string();
|
||||
let description = self.description.string();
|
||||
|
||||
if !title.is_empty() {
|
||||
let path = self.path.clone();
|
||||
|
||||
Some(hyperlog_core::commander::Command::UpdateItem {
|
||||
root: self.root.clone(),
|
||||
path,
|
||||
title: title.trim().into(),
|
||||
description: description.trim().into(),
|
||||
state: match &self.item {
|
||||
GraphItem::User(_) => Default::default(),
|
||||
GraphItem::Section(_) => Default::default(),
|
||||
GraphItem::Item { state, .. } => state.clone(),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EditItem {}
|
||||
|
||||
impl StatefulWidget for &mut EditItem {
|
||||
type State = EditItemState;
|
||||
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let chunks = Layout::vertical(vec![
|
||||
Constraint::Length(2),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
])
|
||||
.split(area);
|
||||
|
||||
let path = format!("path: {}.{}", state.root, state.path.join("."));
|
||||
let path_header = Paragraph::new(path).dark_gray();
|
||||
path_header.render(chunks[0], buf);
|
||||
|
||||
let mut title_input = InputField::new("title");
|
||||
let mut description_input = InputField::new("description");
|
||||
|
||||
match state.focused {
|
||||
EditItemFocused::Title => title_input.focused = true,
|
||||
EditItemFocused::Description => description_input.focused = true,
|
||||
}
|
||||
|
||||
title_input.render(chunks[1], buf, &mut state.title);
|
||||
description_input.render(chunks[2], buf, &mut state.description);
|
||||
}
|
||||
}
|
@@ -6,6 +6,7 @@ pub enum Commands {
|
||||
WriteQuit,
|
||||
Archive,
|
||||
CreateSection { name: String },
|
||||
Edit,
|
||||
}
|
||||
|
||||
impl Commands {
|
||||
@@ -35,6 +36,7 @@ impl CommandParser {
|
||||
"cs" | "create-section" => rest.first().map(|name| Commands::CreateSection {
|
||||
name: name.to_string(),
|
||||
}),
|
||||
"e" | "edit" => Some(Commands::Edit),
|
||||
_ => None,
|
||||
},
|
||||
None => None,
|
||||
|
@@ -5,7 +5,7 @@ use hyperlog_core::log::{GraphItem, ItemState};
|
||||
use itertools::Itertools;
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
|
||||
use crate::{command_parser::Commands, state::SharedState};
|
||||
use crate::{command_parser::Commands, models::Msg, state::SharedState};
|
||||
|
||||
pub struct GraphExplorer<'a> {
|
||||
state: SharedState,
|
||||
@@ -145,7 +145,17 @@ impl<'a> GraphExplorer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_command(&mut self, command: &Commands) -> anyhow::Result<()> {
|
||||
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<Msg>> {
|
||||
match command {
|
||||
Commands::Archive => {
|
||||
if !self.get_current_path().is_empty() {
|
||||
@@ -165,12 +175,35 @@ impl<'a> GraphExplorer<'a> {
|
||||
)?;
|
||||
}
|
||||
}
|
||||
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 { .. } => {
|
||||
if let Some(item) = self.state.querier.get(&self.inner.root, path) {
|
||||
if let GraphItem::Item { .. } = item {
|
||||
return Ok(Some(Msg::OpenEditItemDialog { item }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
self.update_graph()?;
|
||||
|
||||
Ok(())
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub(crate) fn interact(&mut self) -> anyhow::Result<()> {
|
||||
@@ -240,7 +273,7 @@ impl RenderGraph for MovementGraph {
|
||||
|
||||
let embedded_sections = item.values.render_graph_spans(rest);
|
||||
for section in &embedded_sections {
|
||||
let mut line = vec![Span::raw(" ")];
|
||||
let mut line = vec![Span::raw(" ".repeat(4))];
|
||||
line.extend_from_slice(section);
|
||||
lines.push(Line::from(line));
|
||||
}
|
||||
@@ -255,7 +288,7 @@ impl RenderGraph for MovementGraph {
|
||||
|
||||
let embedded_sections = item.values.render_graph_spans(&[]);
|
||||
for section in &embedded_sections {
|
||||
let mut line = vec![Span::raw(" ")];
|
||||
let mut line = vec![Span::raw(" ".repeat(4))];
|
||||
line.extend_from_slice(section);
|
||||
lines.push(Line::from(line));
|
||||
}
|
||||
@@ -306,7 +339,7 @@ impl RenderGraph for MovementGraph {
|
||||
|
||||
let embedded_sections = item.values.render_graph_spans(rest);
|
||||
for section in &embedded_sections {
|
||||
let mut line = vec![Span::raw(" ")];
|
||||
let mut line = vec![Span::raw(" ".repeat(4))];
|
||||
line.extend_from_slice(section);
|
||||
lines.push(line);
|
||||
}
|
||||
@@ -319,7 +352,7 @@ impl RenderGraph for MovementGraph {
|
||||
|
||||
let embedded_sections = item.values.render_graph_spans(&[]);
|
||||
for section in &embedded_sections {
|
||||
let mut line = vec![Span::raw(" ")];
|
||||
let mut line = vec![Span::raw(" ".repeat(4))];
|
||||
line.extend_from_slice(section);
|
||||
lines.push(line);
|
||||
}
|
||||
@@ -396,7 +429,7 @@ impl MovementGraph {
|
||||
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_item(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);
|
||||
@@ -415,16 +448,29 @@ impl MovementGraph {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_graph_item(&self, items: &[usize]) -> Option<&MovementGraph> {
|
||||
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_item(rest),
|
||||
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) {
|
||||
@@ -462,7 +508,7 @@ impl From<GraphItem> for MovementGraph {
|
||||
index: i,
|
||||
name: key.clone(),
|
||||
values: value.clone().into(),
|
||||
item_type: match value.deref() {
|
||||
item_type: match value {
|
||||
GraphItem::User(_) => GraphItemType::Section,
|
||||
GraphItem::Section(_) => GraphItemType::Section,
|
||||
GraphItem::Item { state, .. } => GraphItemType::Item {
|
||||
@@ -522,33 +568,30 @@ mod test {
|
||||
fn test_can_transform_to_movement_graph() {
|
||||
let graph = GraphItem::User(BTreeMap::from([(
|
||||
"0".to_string(),
|
||||
Box::new(GraphItem::Section(BTreeMap::from([
|
||||
(
|
||||
"00".to_string(),
|
||||
Box::new(GraphItem::Section(BTreeMap::new())),
|
||||
),
|
||||
GraphItem::Section(BTreeMap::from([
|
||||
("00".to_string(), GraphItem::Section(BTreeMap::new())),
|
||||
(
|
||||
"01".to_string(),
|
||||
Box::new(GraphItem::Section(BTreeMap::from([
|
||||
GraphItem::Section(BTreeMap::from([
|
||||
(
|
||||
"010".to_string(),
|
||||
Box::new(GraphItem::Item {
|
||||
GraphItem::Item {
|
||||
title: "some-title".into(),
|
||||
description: "some-desc".into(),
|
||||
state: ItemState::NotDone,
|
||||
}),
|
||||
},
|
||||
),
|
||||
(
|
||||
"011".to_string(),
|
||||
Box::new(GraphItem::Item {
|
||||
GraphItem::Item {
|
||||
title: "some-title".into(),
|
||||
description: "some-desc".into(),
|
||||
state: ItemState::NotDone,
|
||||
}),
|
||||
},
|
||||
),
|
||||
]))),
|
||||
])),
|
||||
),
|
||||
]))),
|
||||
])),
|
||||
)]));
|
||||
|
||||
let actual: MovementGraph = graph.into();
|
||||
@@ -672,16 +715,16 @@ mod test {
|
||||
],
|
||||
};
|
||||
|
||||
let actual_default = graph.get_graph_item(&[]);
|
||||
let actual_default = graph.get_graph(&[]);
|
||||
assert_eq!(Some(&graph), actual_default);
|
||||
|
||||
let actual_first = graph.get_graph_item(&[0]);
|
||||
let actual_first = graph.get_graph(&[0]);
|
||||
assert_eq!(graph.items.first().map(|i| &i.values), actual_first);
|
||||
|
||||
let actual_second = graph.get_graph_item(&[1]);
|
||||
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_item(&[0, 0]);
|
||||
let actual_nested = graph.get_graph(&[0, 0]);
|
||||
assert_eq!(
|
||||
graph
|
||||
.items
|
||||
@@ -691,7 +734,7 @@ mod test {
|
||||
actual_nested
|
||||
);
|
||||
|
||||
let actual_nested = graph.get_graph_item(&[0, 1]);
|
||||
let actual_nested = graph.get_graph(&[0, 1]);
|
||||
assert_eq!(
|
||||
graph
|
||||
.items
|
||||
@@ -701,7 +744,7 @@ mod test {
|
||||
actual_nested
|
||||
);
|
||||
|
||||
let actual_nested = graph.get_graph_item(&[1, 2]);
|
||||
let actual_nested = graph.get_graph(&[1, 2]);
|
||||
assert_eq!(
|
||||
graph
|
||||
.items
|
||||
|
@@ -1,3 +1,5 @@
|
||||
use hyperlog_core::log::GraphItem;
|
||||
|
||||
use crate::commands::{Command, IntoCommand};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -7,6 +9,7 @@ pub enum Msg {
|
||||
MoveDown,
|
||||
MoveUp,
|
||||
OpenCreateItemDialog,
|
||||
OpenEditItemDialog { item: GraphItem },
|
||||
Interact,
|
||||
|
||||
EnterInsertMode,
|
||||
|
Reference in New Issue
Block a user