Implement the addition of arbitrary metadata when creating a note

The UI/UX needs to improve, but this is good enough for a proof of concept to start iterating.

Also fix a few clippy warnings.
This commit is contained in:
Sofía Aritz 2023-03-08 21:12:55 +01:00
parent 2078c82f45
commit 0e2d79fc72
3 changed files with 70 additions and 27 deletions

View file

@ -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/). [password-based encryption](https://docs.rs/pwbox/0.5.0/pwbox/).
## To-Do list ## To-Do list
- [ ] Improve password checking - [x] Improve password checking
- [ ] Allow the addition of arbitrary metadata when creating a note - [x] Allow the addition of arbitrary metadata when creating a note
- [ ] Add basic markdown support (bold, italics, underline) - [ ] Add basic markdown support (bold, italics, underline)
- [ ] Improve performance (duplicate decryption operations, tons of copying/cloning, etc) - [ ] Improve performance (duplicate decryption operations, tons of copying/cloning, etc)

View file

@ -6,7 +6,7 @@
windows_subsystem = "windows" windows_subsystem = "windows"
)] )]
use std::collections::{VecDeque}; use std::collections::{HashMap, VecDeque};
use std::fs; use std::fs;
use std::time::SystemTime; use std::time::SystemTime;
use directories::ProjectDirs; use directories::ProjectDirs;
@ -42,6 +42,8 @@ struct App {
title_buffer: String, title_buffer: String,
text_buffer: String, text_buffer: String,
metadata_key_buffer: String,
metadata_buffer: Option<HashMap<String, String>>,
} }
impl App { impl App {
@ -58,6 +60,8 @@ impl App {
title_buffer: String::new(), title_buffer: String::new(),
text_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.label("text:");
ui.text_edit_multiline(&mut self.text_buffer); 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); ui.add_space(10.0);
if ui.button("add note").clicked() { if ui.button("add note").clicked() {
let note = Note::Decrypted { let note = Note::Decrypted {
@ -209,7 +244,7 @@ impl eframe::App for App {
title: self.title_buffer.clone(), title: self.title_buffer.clone(),
metadata: NoteMetadata { metadata: NoteMetadata {
date: SystemTime::now(), date: SystemTime::now(),
arbitrary: None, arbitrary: self.metadata_buffer.clone(),
}, },
text: self.text_buffer.clone(), text: self.text_buffer.clone(),
}; };
@ -221,6 +256,8 @@ impl eframe::App for App {
self.notes = Vec::from(vec_deque); self.notes = Vec::from(vec_deque);
self.metadata_buffer = None;
self.metadata_key_buffer = String::new();
self.title_buffer = String::new(); self.title_buffer = String::new();
self.text_buffer = String::new(); self.text_buffer = String::new();
self.mode = CurrentMode::View; self.mode = CurrentMode::View;
@ -250,8 +287,7 @@ impl eframe::App for App {
ui.label(text); ui.label(text);
let entropy = entropy(&self.password_buffer) let entropy = entropy(&self.password_buffer)
.or(Some(0.0)) .unwrap_or(0.0);
.unwrap();
let text = if entropy < 35_f64 { let text = if entropy < 35_f64 {
WidgetText::from(format!("entropy: {:.2}", entropy)) WidgetText::from(format!("entropy: {:.2}", entropy))
.color(Color32::from_rgb(240, 5, 5)) .color(Color32::from_rgb(240, 5, 5))

View file

@ -105,7 +105,7 @@ impl Note {
let result = render_title_and_metadata(ui, title, metadata, false, None); let result = render_title_and_metadata(ui, title, metadata, false, None);
if let Some(action) = result { if let Some(action) = result {
match action { match action {
RenderResult::DeleteNote => cb(id.to_string()), NoteRenderAction::Delete => cb(id.to_string()),
_ => unreachable!() _ => unreachable!()
} }
} }
@ -117,8 +117,8 @@ impl Note {
let result = render_title_and_metadata(ui, title, metadata, true, Some(ButtonType::Hide)); let result = render_title_and_metadata(ui, title, metadata, true, Some(ButtonType::Hide));
if let Some(action) = result { if let Some(action) = result {
match action { match action {
RenderResult::DeleteNote => cb(id.to_string()), NoteRenderAction::Delete => cb(id.to_string()),
RenderResult::HideNote => value = Some(self.clone().hide()), NoteRenderAction::Hide => value = Some(self.clone().hide()),
_ => unreachable!() _ => unreachable!()
} }
} }
@ -129,8 +129,8 @@ impl Note {
let result = render_title_and_metadata(ui, title, metadata, true, Some(ButtonType::Unhide)); let result = render_title_and_metadata(ui, title, metadata, true, Some(ButtonType::Unhide));
if let Some(action) = result { if let Some(action) = result {
match action { match action {
RenderResult::DeleteNote => cb(id.to_string()), NoteRenderAction::Delete => cb(id.to_string()),
RenderResult::UnhideNote => value = Some(self.clone().unhide()), NoteRenderAction::Unhide => value = Some(self.clone().unhide()),
_ => unreachable!() _ => unreachable!()
} }
} }
@ -152,10 +152,10 @@ impl Note {
} }
} }
enum RenderResult { enum NoteRenderAction {
DeleteNote, Delete,
HideNote, Hide,
UnhideNote, Unhide,
} }
enum ButtonType { enum ButtonType {
@ -163,7 +163,7 @@ enum ButtonType {
Unhide, Unhide,
} }
fn render_title_and_metadata(ui: &mut Ui, title: impl Into<String>, metadata: &NoteMetadata, show_delete_button: bool, show_hide_button: Option<ButtonType>) -> Option<RenderResult> { fn render_title_and_metadata(ui: &mut Ui, title: impl Into<String>, metadata: &NoteMetadata, show_delete_button: bool, show_hide_button: Option<ButtonType>) -> Option<NoteRenderAction> {
let mut result = None; let mut result = None;
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label(RichText::new(title).size(14.0)); ui.label(RichText::new(title).size(14.0));
@ -172,34 +172,41 @@ fn render_title_and_metadata(ui: &mut Ui, title: impl Into<String>, metadata: &N
ui.separator(); ui.separator();
ui.label(date.format("%d-%m-%y %H:%M").to_string()); ui.label(date.format("%d-%m-%y %H:%M").to_string());
if let Some(arbitrary) = &metadata.arbitrary { if show_delete_button {
for (k, v) in arbitrary.iter() { ui.separator();
ui.separator(); if ui.button("delete note").clicked() {
ui.label(RichText::new(format!("{}: {}", k, v)).monospace()); 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 { if let Some(button_type) = show_hide_button {
match button_type { match button_type {
ButtonType::Hide => { ButtonType::Hide => {
if ui.button("hide note").clicked() { if ui.button("hide note").clicked() {
result = Some(RenderResult::HideNote) result = Some(NoteRenderAction::Hide)
} }
} }
ButtonType::Unhide => { ButtonType::Unhide => {
if ui.button("show note").clicked() { 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(); ui.separator();
result result
} }