Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(app): add rename book functionality (#15) #34

Merged
merged 4 commits into from
Jun 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
153 changes: 123 additions & 30 deletions src/dnote_lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::{process::Command, str::FromStr};

type NoteId = u32;

#[derive(Debug, Clone)]
pub struct DnoteBook {
pub name: String,
Expand All @@ -15,16 +17,20 @@ impl FromStr for DnoteBook {

#[derive(Debug, Clone)]
pub struct DnotePage {
pub id: u32,
/// Truncated content from page
pub id: NoteId,
/// Truncated content from the page
pub summary: String,
}

impl FromStr for DnotePage {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split(')').collect();
let id = parts[0].trim().trim_start_matches('(').parse().unwrap();
let id = parts[0]
.trim()
.trim_start_matches('(')
.parse()
.map_err(|_| ())?;
let summary = parts[1]
.trim()
.trim_end_matches("[---More---]")
Expand All @@ -47,6 +53,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,46 +95,103 @@ pub enum DnoteClientError {
}

impl DnoteClient {
pub fn get_books(&self) -> Result<Vec<DnoteBook>, DnoteClientError> {
// println!("Viewing all books...");
fn execute_command(&self, command: DnoteCommand) -> Result<String, DnoteClientError> {
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::ViewByNoteId { note_id } => {
let args = vec![note_id.to_string(), "--content-only".to_string()];
("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)
}
DnoteCommand::RemoveBook { book_name } => {
let args = vec![book_name];
("rm", args)
}
DnoteCommand::RemoveNoteById { note_id } => {
let args = vec![note_id.to_string()];
("rm", args)
}
};
let output = Command::new("dnote")
.arg("view")
.arg("--name-only")
.arg(cmd)
.args(args)
.output()
.map_err(|_| DnoteClientError::DnoteCommand)?;
let stdout: String =
String::from_utf8(output.stdout).map_err(|_| DnoteClientError::UTF8ParseError)?;
let result: Result<Vec<DnoteBook>, _> = stdout.lines().map(|l| l.parse()).collect();
Ok(stdout)
}

pub fn get_books(&self) -> Result<Vec<DnoteBook>, DnoteClientError> {
let output = self.execute_command(DnoteCommand::ViewBooks)?;
let result: Result<Vec<DnoteBook>, _> = output.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> {
self.execute_command(DnoteCommand::EditBook {
book_name: book_name.to_string(),
new_name: Some(new_book_name.to_string()),
})?;
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")
.arg("view")
.arg(book_name)
.output()
.map_err(|_| DnoteClientError::DnoteCommand)?;
let stdout =
String::from_utf8(output.stdout).map_err(|_| DnoteClientError::UTF8ParseError)?;
let result: Result<Vec<DnotePage>, _> = stdout
let output = self.execute_command(DnoteCommand::ViewByBook {
book_name: book_name.to_string(),
})?;
let result: Result<Vec<DnotePage>, _> = output
.lines()
// skip first line e.g ' • on book ccu'
.skip(1)
.skip(1) // skip first line e.g ' • on book ccu'
.map(|l| l.parse())
.collect();
result.map_err(|_| DnoteClientError::ParseError)
}
pub fn get_page_content(&self, page_id: u32) -> Result<DnotePageInfo, DnoteClientError> {
// println!("Viewing content for page with id {}", page_id);
let output = Command::new("dnote")
.arg("view")
.arg(page_id.to_string())
.arg("--content-only")
.output()
.map_err(|_| DnoteClientError::DnoteCommand)?;
let stdout =
String::from_utf8(output.stdout).map_err(|_| DnoteClientError::UTF8ParseError)?;
stdout.parse().map_err(|_| DnoteClientError::ParseError)

pub fn get_page_content(&self, page_id: NoteId) -> Result<DnotePageInfo, DnoteClientError> {
let output = self.execute_command(DnoteCommand::ViewByNoteId { note_id: page_id })?;
output.parse().map_err(|_| DnoteClientError::ParseError)
}
}

Expand Down
108 changes: 74 additions & 34 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,87 @@ 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.clone_from(new_name);
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.clone_from(
&app.books.items[app.books.state.selected().unwrap_or(0)].name,
);
}
}
}
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(())
}
Loading
Loading