Skip to content

Commit

Permalink
feat(app): add rename book functionality (#15)
Browse files Browse the repository at this point in the history
- Closes #15

Signed-off-by: Deep Panchal <deep302001@gmail.com>
  • Loading branch information
deepanchal committed Jun 4, 2024
1 parent f3cae09 commit af7afc9
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 38 deletions.
6 changes: 6 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ pub struct App {
pub pages: StatefulList<DnotePage>,
/// Page Info
pub page_info: DnotePageInfo,
pub show_popup: bool,
pub popup_content: String,
}

impl Default for App {
Expand All @@ -85,6 +87,8 @@ impl Default for App {
match books_result {
Ok(books) => Self {
running: true,
show_popup: false,
popup_content: String::from(""),
dnote_client: DnoteClient {},
selected_section: TuiSection::BOOKS,
books: StatefulList::with_items(books),
Expand All @@ -97,6 +101,8 @@ impl Default for App {
println!("Something went wrong {:?}", e);
Self {
running: true,
show_popup: false,
popup_content: String::from(""),
dnote_client: DnoteClient {},
selected_section: TuiSection::BOOKS,
books: StatefulList::with_items(vec![]),
Expand Down
108 changes: 105 additions & 3 deletions src/dnote_lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::{process::Command, str::FromStr};
use std::{io::Error, process::Command, str::FromStr};

Check failure on line 1 in src/dnote_lib.rs

View workflow job for this annotation

GitHub Actions / Build 🏗️, Lint 💅, & Test 🧪

unused import: `io::Error`

type NoteId = u32;

#[derive(Debug, Clone)]
pub struct DnoteBook {
Expand All @@ -15,7 +17,7 @@ impl FromStr for DnoteBook {

#[derive(Debug, Clone)]
pub struct DnotePage {
pub id: u32,
pub id: NoteId,
/// Truncated content from page
pub summary: String,
}
Expand Down Expand Up @@ -47,6 +49,36 @@ impl FromStr for DnotePageInfo {
}
}

#[derive(Debug)]
pub enum DnoteCommand {
Add {
book_name: String,
note: String,
},
ViewBooks,
ViewByBook {
book_name: String,
},
ViewByNoteId {
note_id: NoteId,
},
EditNoteById {
note_id: String,
new_content: Option<String>,
new_book: Option<String>,
},
EditBook {
book_name: String,
new_name: Option<String>,
},
RemoveBook {
book_name: String,
},
RemoveNoteById {
note_id: NoteId,
},
}

#[derive(Debug)]
pub struct DnoteClient {}

Expand All @@ -59,6 +91,59 @@ pub enum DnoteClientError {
}

impl DnoteClient {
fn execute_command(&self, command: DnoteCommand) -> Result<String, DnoteClientError> {

Check failure on line 94 in src/dnote_lib.rs

View workflow job for this annotation

GitHub Actions / Build 🏗️, Lint 💅, & Test 🧪

method `execute_command` is never used
let (cmd, args) = match command {
DnoteCommand::Add { book_name, note } => {
let args = vec![book_name, "-c".to_string(), note];
("add", args)
}
DnoteCommand::ViewBooks {} => {
let args = vec!["--name-only".to_string()];
("view", args)
}
DnoteCommand::ViewByBook { book_name } => {
let args = vec![book_name];
("view", args)
}
DnoteCommand::EditNoteById {
note_id,
new_content,
new_book,
} => {
let mut args = vec![note_id];
if let Some(content) = new_content {
args.push("-c".to_string());
args.push(content);
}
if let Some(book) = new_book {
args.push("-b".to_string());
args.push(book);
}
("edit", args)
}
DnoteCommand::EditBook {
book_name,
new_name,
} => {
let mut args = vec![book_name];
if let Some(name) = new_name {
args.push("-n".to_string());
args.push(name);
}
("edit", args)
}
_ => todo!(),
};
let output = Command::new("dnote")
.arg(cmd)
.args(args)
.output()
.map_err(|_| DnoteClientError::DnoteCommand)?;
let stdout: String =
String::from_utf8(output.stdout).map_err(|_| DnoteClientError::UTF8ParseError)?;
Ok(stdout)
}

pub fn get_books(&self) -> Result<Vec<DnoteBook>, DnoteClientError> {
// println!("Viewing all books...");
let output = Command::new("dnote")
Expand All @@ -71,6 +156,23 @@ impl DnoteClient {
let result: Result<Vec<DnoteBook>, _> = stdout.lines().map(|l| l.parse()).collect();
result.map_err(|_| DnoteClientError::ParseError)
}
pub fn rename_book(
&self,
book_name: &str,
new_book_name: &str,
) -> Result<(), DnoteClientError> {
// dnote edit stm -n t3
let output = Command::new("dnote")
.arg("edit")
.arg(book_name)
.arg("-n")
.arg(new_book_name)
.output()
.map_err(|_| DnoteClientError::DnoteCommand)?;
let _stdout: String =
String::from_utf8(output.stdout).map_err(|_| DnoteClientError::UTF8ParseError)?;
Ok(())
}
pub fn get_pages(&self, book_name: &str) -> Result<Vec<DnotePage>, DnoteClientError> {
// println!("Viewing pages for book: {}", book_name);
let output = Command::new("dnote")
Expand All @@ -88,7 +190,7 @@ impl DnoteClient {
.collect();
result.map_err(|_| DnoteClientError::ParseError)
}
pub fn get_page_content(&self, page_id: u32) -> Result<DnotePageInfo, DnoteClientError> {
pub fn get_page_content(&self, page_id: NoteId) -> Result<DnotePageInfo, DnoteClientError> {
// println!("Viewing content for page with id {}", page_id);
let output = Command::new("dnote")
.arg("view")
Expand Down
109 changes: 75 additions & 34 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,88 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};

/// Handles the key events and updates the state of [`App`].
pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
match key_event.code {
// Exit application on `ESC` or `q`
KeyCode::Esc | KeyCode::Char('q') => {
app.quit();
if app.show_popup {
match key_event.code {
KeyCode::Char(c) => {
app.popup_content.push(c);
}
KeyCode::Backspace => {
app.popup_content.pop();
}
KeyCode::Enter => {
if app.show_popup {
let selected_index = app.books.state.selected().unwrap_or(0);
let old_name = &app.books.items[selected_index].name;
let new_name = &app.popup_content;
if let Err(e) = app.dnote_client.rename_book(old_name, new_name) {
println!("Error renaming book: {:?}", e);
// Handle error (e.g., show an error message to the user)
} else {
// Update the book's name in the UI
app.books.items[selected_index].name = new_name.clone();

Check failure on line 24 in src/handler.rs

View workflow job for this annotation

GitHub Actions / Build 🏗️, Lint 💅, & Test 🧪

assigning the result of `Clone::clone()` may be inefficient
app.show_popup = false;
}
}
}
KeyCode::Esc => {
app.show_popup = false;
}
_ => {}
}
// Exit application on `Ctrl-C`
KeyCode::Char('c') | KeyCode::Char('C') => {
if key_event.modifiers == KeyModifiers::CONTROL {
} else {
match key_event.code {
// Exit application on `ESC` or `q`
KeyCode::Esc | KeyCode::Char('q') => {
app.quit();
}
}
// Counter handlers
KeyCode::Left | KeyCode::Char('h') => {
app.pages.unselect();
app.select_prev_section();
match app.selected_section {
TuiSection::BOOKS => {}
TuiSection::PAGES => app.books.previous(),
TuiSection::CONTENT => app.pages.previous(),
// Exit application on `Ctrl-C`
KeyCode::Char('c') | KeyCode::Char('C') => {
if key_event.modifiers == KeyModifiers::CONTROL {
app.quit();
}
}
}
KeyCode::Right | KeyCode::Char('l') => {
match app.selected_section {
TuiSection::BOOKS => app.pages.next(),
TuiSection::PAGES => {}
// Counter handlers
KeyCode::Left | KeyCode::Char('h') => {
app.pages.unselect();
app.select_prev_section();
match app.selected_section {
TuiSection::BOOKS => {}
TuiSection::PAGES => app.books.previous(),
TuiSection::CONTENT => app.pages.previous(),
}
}
KeyCode::Right | KeyCode::Char('l') => {
match app.selected_section {
TuiSection::BOOKS => app.pages.next(),
TuiSection::PAGES => {}
_ => {}
}
app.select_next_section();
}
KeyCode::Up | KeyCode::Char('k') => match app.selected_section {
TuiSection::BOOKS => app.books.previous(),
TuiSection::PAGES => app.pages.previous(),
_ => {}
},
KeyCode::Down | KeyCode::Char('j') => match app.selected_section {
TuiSection::BOOKS => app.books.next(),
TuiSection::PAGES => app.pages.next(),
_ => {}
},
KeyCode::Char('r') => {
if app.selected_section == TuiSection::BOOKS {
app.show_popup = true;
if app.show_popup {
app.popup_content = app.books.items

Check failure on line 78 in src/handler.rs

View workflow job for this annotation

GitHub Actions / Build 🏗️, Lint 💅, & Test 🧪

assigning the result of `Clone::clone()` may be inefficient
[app.books.state.selected().unwrap_or(0)]
.name
.clone();
}
}
}
app.select_next_section();
}
KeyCode::Up | KeyCode::Char('k') => match app.selected_section {
TuiSection::BOOKS => app.books.previous(),
TuiSection::PAGES => app.pages.previous(),
_ => {}
},
KeyCode::Down | KeyCode::Char('j') => match app.selected_section {
TuiSection::BOOKS => app.books.next(),
TuiSection::PAGES => app.pages.next(),
// Other handlers you could add here.
_ => {}
},
// Other handlers you could add here.
_ => {}
}
}
Ok(())
}
40 changes: 39 additions & 1 deletion src/ui.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use tui::{
backend::Backend,
layout::{Alignment, Constraint, Direction, Layout},
prelude::Rect,
style::{Color, Modifier, Style},
widgets::{Block, Borders, List, ListItem, Paragraph},
text::Text,
widgets::{Block, Borders, Clear, List, ListItem, Paragraph},
Frame,
};

Expand Down Expand Up @@ -94,4 +96,40 @@ pub fn render<B: Backend>(app: &mut App, frame: &mut Frame<'_, B>) {
.style(Style::default().fg(Color::Gray))
.block(content_block);
frame.render_widget(paragraph, page_content_chunk);

if app.show_popup {
let input = Paragraph::new(Text::from(app.popup_content.as_str()))
.style(Style::default().fg(Color::White))
.block(Block::default().borders(Borders::ALL).title("Rename Book"));
let area = centered_rect(60, 20, frame.size());
frame.render_widget(Clear, area); // Clear the background
frame.render_widget(input, area);
}
}

/// helper function to create a centered rect using up certain percentage of the available rect `r`
fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
]
.as_ref(),
)
.split(r);

Layout::default()
.direction(Direction::Horizontal)
.constraints(
[
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
]
.as_ref(),
)
.split(popup_layout[1])[1]
}

0 comments on commit af7afc9

Please sign in to comment.