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:
parent
2078c82f45
commit
0e2d79fc72
3 changed files with 70 additions and 27 deletions
|
@ -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)
|
||||||
|
|
||||||
|
|
44
src/main.rs
44
src/main.rs
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
Loading…
Reference in a new issue