Sort notes my newest, save note ID in the Note struct, add ID-based note removal
This adds a weird callback-based system to properly update the state once a note is removed. This system isn't very nice, and it can be quite messy, but I'll improve it in the future
This commit is contained in:
parent
88dd8f034f
commit
b599f628de
4 changed files with 72 additions and 17 deletions
|
@ -4,13 +4,13 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
egui = "0.21"
|
||||
eframe = "0.21"
|
||||
anyhow = "1"
|
||||
log = "0.4"
|
||||
simple_logger = "4.0"
|
||||
chrono = "0.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
rand = "0.8"
|
||||
pwbox = "0.5"
|
||||
|
|
30
src/main.rs
30
src/main.rs
|
@ -1,4 +1,10 @@
|
|||
#![windows_subsystem = "windows"]
|
||||
#![cfg_attr(
|
||||
all(
|
||||
target_os = "windows",
|
||||
not(debug_assertions),
|
||||
),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use std::collections::{VecDeque};
|
||||
use std::fs;
|
||||
|
@ -23,6 +29,7 @@ enum CurrentMode {
|
|||
|
||||
struct App {
|
||||
notes: Vec<Note>,
|
||||
update_notes_next: bool,
|
||||
password: Option<String>,
|
||||
tested_password: bool,
|
||||
mode: CurrentMode,
|
||||
|
@ -60,6 +67,7 @@ fn main() {
|
|||
|
||||
let app = App {
|
||||
password: None,
|
||||
update_notes_next: false,
|
||||
tested_password: false,
|
||||
notes,
|
||||
mode: CurrentMode::PasswordInput,
|
||||
|
@ -93,6 +101,12 @@ impl eframe::App for App {
|
|||
}
|
||||
}
|
||||
|
||||
if self.update_notes_next {
|
||||
self.notes = self.saving.read_notes().unwrap();
|
||||
self.update_notes_next = false;
|
||||
info!("reloaded notes");
|
||||
}
|
||||
|
||||
match self.mode {
|
||||
CurrentMode::View => {
|
||||
let password = self.password.as_ref().unwrap();
|
||||
|
@ -109,7 +123,14 @@ impl eframe::App for App {
|
|||
ui.separator();
|
||||
|
||||
for note in &mut self.notes {
|
||||
if let Some(new_note) = note.render(ui, password) {
|
||||
let render_result = note.render(ui, password, |id| {
|
||||
let _ = &self.saving.delete_note(&id).unwrap();
|
||||
info!("note with id {} was successfully deleted", id);
|
||||
|
||||
self.update_notes_next = true;
|
||||
});
|
||||
|
||||
if let Some(new_note) = render_result {
|
||||
*note = new_note;
|
||||
}
|
||||
ui.add_space(10.0);
|
||||
|
@ -141,6 +162,11 @@ impl eframe::App for App {
|
|||
ui.add_space(10.0);
|
||||
if ui.button("add note").clicked() {
|
||||
let note = Note::Decrypted {
|
||||
id: SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis()
|
||||
.to_string(),
|
||||
title: self.title_buffer.clone(),
|
||||
metadata: NoteMetadata {
|
||||
date: SystemTime::now(),
|
||||
|
|
|
@ -15,11 +15,13 @@ pub struct NoteMetadata {
|
|||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Note {
|
||||
Encrypted {
|
||||
id: String,
|
||||
title: String,
|
||||
metadata: NoteMetadata,
|
||||
encrypted_text: String,
|
||||
},
|
||||
Decrypted {
|
||||
id: String,
|
||||
title: String,
|
||||
metadata: NoteMetadata,
|
||||
text: String,
|
||||
|
@ -31,7 +33,7 @@ impl Note {
|
|||
let mut rng = rand::thread_rng();
|
||||
|
||||
match self {
|
||||
Note::Decrypted { text, metadata, title } => {
|
||||
Note::Decrypted { id, text, metadata, title } => {
|
||||
let pwbox = Sodium::build_box(&mut rng)
|
||||
.seal(password.to_string().into_bytes(), text.clone().into_bytes())?;
|
||||
|
||||
|
@ -40,6 +42,7 @@ impl Note {
|
|||
let erased: ErasedPwBox = eraser.erase(&pwbox)?;
|
||||
|
||||
Ok(Self::Encrypted {
|
||||
id: id.clone(),
|
||||
title: title.clone(),
|
||||
metadata: metadata.clone(),
|
||||
encrypted_text: serde_json::to_string(&erased)?,
|
||||
|
@ -51,7 +54,7 @@ impl Note {
|
|||
|
||||
pub fn decrypt(&self, password: &str) -> anyhow::Result<Self> {
|
||||
match self {
|
||||
Note::Encrypted { title, metadata, encrypted_text } => {
|
||||
Note::Encrypted { id, title, metadata, encrypted_text } => {
|
||||
let mut eraser = Eraser::new();
|
||||
eraser.add_suite::<Sodium>();
|
||||
let erased: ErasedPwBox = serde_json::from_str(encrypted_text)?;
|
||||
|
@ -60,6 +63,7 @@ impl Note {
|
|||
let decrypted_text = String::from_utf8(decrypted_bytes)?;
|
||||
|
||||
Ok(Self::Decrypted {
|
||||
id: id.clone(),
|
||||
title: title.clone(),
|
||||
metadata: metadata.clone(),
|
||||
text: decrypted_text,
|
||||
|
@ -69,18 +73,18 @@ impl Note {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, ui: &mut Ui, password: &str) -> Option<Self> {
|
||||
pub fn render(&self, ui: &mut Ui, password: &str, cb: impl FnOnce(String)) -> Option<Self> {
|
||||
let mut value = None;
|
||||
ui.group(|ui| {
|
||||
match self {
|
||||
Note::Encrypted { title, metadata, .. } => {
|
||||
render_title_and_metadata(ui, title, metadata);
|
||||
Note::Encrypted { id, title, metadata, .. } => {
|
||||
render_title_and_metadata(ui, title, metadata, false, (id.clone(), Box::new(cb)));
|
||||
if ui.button("decrypt note").clicked() {
|
||||
value = Some(self.decrypt(password).unwrap());
|
||||
}
|
||||
}
|
||||
Note::Decrypted { title, metadata, text } => {
|
||||
render_title_and_metadata(ui, title, metadata);
|
||||
Note::Decrypted { id, title, metadata, text, .. } => {
|
||||
render_title_and_metadata(ui, title, metadata, true, (id.clone(), Box::new(cb)));
|
||||
ui.label(text);
|
||||
}
|
||||
};
|
||||
|
@ -88,15 +92,22 @@ impl Note {
|
|||
|
||||
value
|
||||
}
|
||||
|
||||
pub fn id(&self) -> String {
|
||||
match self {
|
||||
Note::Encrypted { id, .. } => id.clone(),
|
||||
Note::Decrypted { id, .. } => id.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_title_and_metadata(ui: &mut Ui, title: impl Into<String>, metadata: &NoteMetadata) {
|
||||
fn render_title_and_metadata(ui: &mut Ui, title: impl Into<String>, metadata: &NoteMetadata, show_delete_button: bool, delete_info: (String, Box<impl FnOnce(String)>)) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(RichText::new(title).size(14.0));
|
||||
|
||||
let date: DateTime<Utc> = metadata.date.into();
|
||||
ui.separator();
|
||||
ui.label(date.format("%d-%m-%y").to_string());
|
||||
ui.label(date.format("%d-%m-%y %H:%M").to_string());
|
||||
|
||||
if let Some(arbitrary) = &metadata.arbitrary {
|
||||
for (k, v) in arbitrary.iter() {
|
||||
|
@ -104,6 +115,15 @@ fn render_title_and_metadata(ui: &mut Ui, title: impl Into<String>, metadata: &N
|
|||
ui.label(RichText::new(format!("{}: {}", k, v)).monospace());
|
||||
}
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
if show_delete_button {
|
||||
let (id, cb) = delete_info;
|
||||
if ui.button("delete note").clicked() {
|
||||
cb(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
use crate::notes::Note;
|
||||
|
||||
pub struct Saving {
|
||||
|
@ -15,27 +15,36 @@ impl Saving {
|
|||
}
|
||||
|
||||
pub fn read_notes(&self) -> anyhow::Result<Vec<Note>> {
|
||||
let mut notes = vec![];
|
||||
let mut notes = VecDeque::new();
|
||||
let paths = fs::read_dir(&self.path)?;
|
||||
|
||||
for path in paths {
|
||||
let path = path?.path();
|
||||
|
||||
if path.is_file() {
|
||||
notes.push(serde_json::from_str(&fs::read_to_string(path)?)?);
|
||||
notes.push_front(serde_json::from_str(&fs::read_to_string(path)?)?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(notes)
|
||||
Ok(notes.into())
|
||||
}
|
||||
|
||||
pub fn save_note(&self, note: Note, password: String) -> anyhow::Result<()> {
|
||||
let note = note.encrypt(&password)?;
|
||||
let mut path = self.path.clone().join("_");
|
||||
path.set_file_name(format!("{}.json", SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_millis()));
|
||||
path.set_file_name(format!("{}.json", note.id()));
|
||||
|
||||
fs::write(path, serde_json::to_string(¬e)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_note(&self, id: &str) -> anyhow::Result<()> {
|
||||
let mut path = self.path.clone().join("_");
|
||||
path.set_file_name(format!("{}.json", id));
|
||||
|
||||
fs::remove_file(path)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue