diff --git a/README.md b/README.md index 43d104e..87328dc 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ This is a simple and experimental password-based note-taking app with built-in [password-based encryption](https://docs.rs/pwbox/0.5.0/pwbox/). ## To-Do list -- [ ] Improve password checking -- [ ] Allow the addition of arbitrary metadata when creating a note +- [x] Improve password checking +- [x] Allow the addition of arbitrary metadata when creating a note - [ ] Add basic markdown support (bold, italics, underline) - [ ] Improve performance (duplicate decryption operations, tons of copying/cloning, etc) diff --git a/src/main.rs b/src/main.rs index 288c267..9615c3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ windows_subsystem = "windows" )] -use std::collections::{VecDeque}; +use std::collections::{HashMap, VecDeque}; use std::fs; use std::time::SystemTime; use directories::ProjectDirs; @@ -42,6 +42,8 @@ struct App { title_buffer: String, text_buffer: String, + metadata_key_buffer: String, + metadata_buffer: Option>, } impl App { @@ -58,6 +60,8 @@ impl App { title_buffer: String::new(), text_buffer: String::new(), + metadata_key_buffer: String::new(), + metadata_buffer: None, } } } @@ -198,6 +202,37 @@ impl eframe::App for App { ui.label("text:"); ui.text_edit_multiline(&mut self.text_buffer); + if let Some(metadata) = &mut self.metadata_buffer { + ui.label("metadata:"); + for (k, v) in &mut *metadata { + ui.horizontal(|ui| { + ui.monospace(format!("{k}:")); + ui.text_edit_singleline(v); + }); + } + if !metadata.is_empty() { + ui.add_space(10.0); + } + + ui.horizontal(|ui| { + ui.label("key:"); + ui.text_edit_singleline(&mut self.metadata_key_buffer); + + if ui.button("+").clicked() && !self.metadata_key_buffer.is_empty() { + metadata.insert(self.metadata_key_buffer.clone(), "".into()); + self.metadata_key_buffer = String::new(); + } + }); + } else { + ui.horizontal(|ui| { + ui.label("metadata:"); + + if ui.button("create").clicked() { + self.metadata_buffer = Some(HashMap::new()); + } + }); + } + ui.add_space(10.0); if ui.button("add note").clicked() { let note = Note::Decrypted { @@ -209,7 +244,7 @@ impl eframe::App for App { title: self.title_buffer.clone(), metadata: NoteMetadata { date: SystemTime::now(), - arbitrary: None, + arbitrary: self.metadata_buffer.clone(), }, text: self.text_buffer.clone(), }; @@ -221,6 +256,8 @@ impl eframe::App for App { self.notes = Vec::from(vec_deque); + self.metadata_buffer = None; + self.metadata_key_buffer = String::new(); self.title_buffer = String::new(); self.text_buffer = String::new(); self.mode = CurrentMode::View; @@ -250,8 +287,7 @@ impl eframe::App for App { ui.label(text); let entropy = entropy(&self.password_buffer) - .or(Some(0.0)) - .unwrap(); + .unwrap_or(0.0); let text = if entropy < 35_f64 { WidgetText::from(format!("entropy: {:.2}", entropy)) .color(Color32::from_rgb(240, 5, 5)) diff --git a/src/notes/mod.rs b/src/notes/mod.rs index adbc0d1..201bf3c 100644 --- a/src/notes/mod.rs +++ b/src/notes/mod.rs @@ -105,7 +105,7 @@ impl Note { let result = render_title_and_metadata(ui, title, metadata, false, None); if let Some(action) = result { match action { - RenderResult::DeleteNote => cb(id.to_string()), + NoteRenderAction::Delete => cb(id.to_string()), _ => unreachable!() } } @@ -117,8 +117,8 @@ impl Note { let result = render_title_and_metadata(ui, title, metadata, true, Some(ButtonType::Hide)); if let Some(action) = result { match action { - RenderResult::DeleteNote => cb(id.to_string()), - RenderResult::HideNote => value = Some(self.clone().hide()), + NoteRenderAction::Delete => cb(id.to_string()), + NoteRenderAction::Hide => value = Some(self.clone().hide()), _ => unreachable!() } } @@ -129,8 +129,8 @@ impl Note { let result = render_title_and_metadata(ui, title, metadata, true, Some(ButtonType::Unhide)); if let Some(action) = result { match action { - RenderResult::DeleteNote => cb(id.to_string()), - RenderResult::UnhideNote => value = Some(self.clone().unhide()), + NoteRenderAction::Delete => cb(id.to_string()), + NoteRenderAction::Unhide => value = Some(self.clone().unhide()), _ => unreachable!() } } @@ -152,10 +152,10 @@ impl Note { } } -enum RenderResult { - DeleteNote, - HideNote, - UnhideNote, +enum NoteRenderAction { + Delete, + Hide, + Unhide, } enum ButtonType { @@ -163,7 +163,7 @@ enum ButtonType { Unhide, } -fn render_title_and_metadata(ui: &mut Ui, title: impl Into, metadata: &NoteMetadata, show_delete_button: bool, show_hide_button: Option) -> Option { +fn render_title_and_metadata(ui: &mut Ui, title: impl Into, metadata: &NoteMetadata, show_delete_button: bool, show_hide_button: Option) -> Option { let mut result = None; ui.horizontal(|ui| { ui.label(RichText::new(title).size(14.0)); @@ -172,34 +172,41 @@ fn render_title_and_metadata(ui: &mut Ui, title: impl Into, metadata: &N ui.separator(); ui.label(date.format("%d-%m-%y %H:%M").to_string()); - if let Some(arbitrary) = &metadata.arbitrary { - for (k, v) in arbitrary.iter() { - ui.separator(); - ui.label(RichText::new(format!("{}: {}", k, v)).monospace()); + if show_delete_button { + ui.separator(); + if ui.button("delete note").clicked() { + result = Some(NoteRenderAction::Delete); } } - ui.separator(); - if show_delete_button && ui.button("delete note").clicked() { - result = Some(RenderResult::DeleteNote); - } - if let Some(button_type) = show_hide_button { match button_type { ButtonType::Hide => { if ui.button("hide note").clicked() { - result = Some(RenderResult::HideNote) + result = Some(NoteRenderAction::Hide) } } ButtonType::Unhide => { if ui.button("show note").clicked() { - result = Some(RenderResult::UnhideNote) + result = Some(NoteRenderAction::Unhide) } } } } }); + if let Some(arbitrary) = &metadata.arbitrary { + ui.horizontal(|ui| { + let mut arbitrary_iter = arbitrary.iter().peekable(); + while let Some((k, v)) = arbitrary_iter.next() { + ui.label(RichText::new(format!("{}: {}", k, v)).monospace()); + if arbitrary_iter.peek().is_some() { + ui.separator(); + } + } + }); + } + ui.separator(); result } \ No newline at end of file