Move notes store to rust
This commit is contained in:
parent
849400cf35
commit
75f3a84e5f
12 changed files with 617 additions and 138 deletions
112
gdn-app/src-tauri/src/api.rs
Normal file
112
gdn-app/src-tauri/src/api.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use tauri::{AppHandle, Emitter, State};
|
||||
|
||||
use crate::{
|
||||
ids::NoteId,
|
||||
store::Store,
|
||||
types::{EventNotesStoreUpdate, Note},
|
||||
};
|
||||
|
||||
// API methods are sorted alphabetically.
|
||||
|
||||
fn update_if_required(store: &mut Store, app: &AppHandle) {
|
||||
if let Some(store_id) = store.needs_update() {
|
||||
let payload = EventNotesStoreUpdate { store_id };
|
||||
app.emit("notes_store_update", payload).unwrap();
|
||||
store.update();
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn note_child_add(
|
||||
id: NoteId,
|
||||
child_id: NoteId,
|
||||
child_position: isize,
|
||||
app: AppHandle,
|
||||
store: State<'_, Arc<Mutex<Store>>>,
|
||||
) {
|
||||
let mut guard = store.lock().unwrap();
|
||||
guard.add_child_at_position(id, child_id, child_position);
|
||||
update_if_required(&mut guard, &app);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn note_child_move(
|
||||
child_id: NoteId,
|
||||
from_id: NoteId,
|
||||
from_iteration: usize,
|
||||
to_id: NoteId,
|
||||
to_position: isize,
|
||||
app: AppHandle,
|
||||
store: State<'_, Arc<Mutex<Store>>>,
|
||||
) {
|
||||
let mut guard = store.lock().unwrap();
|
||||
guard.move_child_by_id_to_position(child_id, from_id, from_iteration, to_id, to_position);
|
||||
update_if_required(&mut guard, &app);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn note_child_remove(
|
||||
id: NoteId,
|
||||
child_id: NoteId,
|
||||
child_iteration: usize,
|
||||
app: AppHandle,
|
||||
store: State<'_, Arc<Mutex<Store>>>,
|
||||
) {
|
||||
let mut guard = store.lock().unwrap();
|
||||
guard.remove_child_by_id(id, child_id, child_iteration);
|
||||
update_if_required(&mut guard, &app);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn note_children_set(
|
||||
id: NoteId,
|
||||
children: Vec<NoteId>,
|
||||
app: AppHandle,
|
||||
store: State<'_, Arc<Mutex<Store>>>,
|
||||
) {
|
||||
let mut guard = store.lock().unwrap();
|
||||
guard.set_children(id, children);
|
||||
update_if_required(&mut guard, &app);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn note_create(text: String, app: AppHandle, store: State<'_, Arc<Mutex<Store>>>) -> Note {
|
||||
let mut guard = store.lock().unwrap();
|
||||
let id = guard.create(text);
|
||||
update_if_required(&mut guard, &app);
|
||||
guard.get(id).unwrap()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn note_delete(id: NoteId, app: AppHandle, store: State<'_, Arc<Mutex<Store>>>) {
|
||||
let mut guard = store.lock().unwrap();
|
||||
guard.delete(id);
|
||||
update_if_required(&mut guard, &app);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn note_get(id: NoteId, store: State<'_, Arc<Mutex<Store>>>) -> Option<Note> {
|
||||
let guard = store.lock().unwrap();
|
||||
guard.get(id)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn note_text_set(
|
||||
id: NoteId,
|
||||
text: String,
|
||||
app: AppHandle,
|
||||
store: State<'_, Arc<Mutex<Store>>>,
|
||||
) {
|
||||
let mut guard = store.lock().unwrap();
|
||||
guard.set_text(id, text);
|
||||
update_if_required(&mut guard, &app);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_clear(app: AppHandle, store: State<'_, Arc<Mutex<Store>>>) {
|
||||
let mut guard = store.lock().unwrap();
|
||||
guard.clear();
|
||||
update_if_required(&mut guard, &app);
|
||||
}
|
||||
|
|
@ -1,16 +1,28 @@
|
|||
mod ids;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
#[tauri::command]
|
||||
fn greet(name: &str) -> String {
|
||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||
}
|
||||
use store::Store;
|
||||
|
||||
mod api;
|
||||
mod ids;
|
||||
pub mod store;
|
||||
mod types;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![greet])
|
||||
.manage(Arc::new(Mutex::new(Store::new())))
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
api::note_child_add,
|
||||
api::note_child_move,
|
||||
api::note_child_remove,
|
||||
api::note_children_set,
|
||||
api::note_create,
|
||||
api::note_delete,
|
||||
api::note_get,
|
||||
api::note_text_set,
|
||||
api::notes_clear,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
|
|
|||
238
gdn-app/src-tauri/src/store.rs
Normal file
238
gdn-app/src-tauri/src/store.rs
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::{ids::NoteId, types::Note};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NoteInfo {
|
||||
pub text: String,
|
||||
pub children: Vec<NoteId>,
|
||||
}
|
||||
|
||||
/// A note store for testing.
|
||||
#[derive(Default)]
|
||||
pub struct Store {
|
||||
last_id: u64,
|
||||
curr_id: u64,
|
||||
notes: HashMap<NoteId, NoteInfo>,
|
||||
parents: HashMap<NoteId, HashMap<NoteId, usize>>,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn needs_update(&self) -> Option<u64> {
|
||||
if self.last_id != self.curr_id {
|
||||
Some(self.curr_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
self.last_id = self.curr_id;
|
||||
}
|
||||
|
||||
pub fn get(&self, id: NoteId) -> Option<Note> {
|
||||
let info = self.notes.get(&id)?;
|
||||
|
||||
let parents = self
|
||||
.parents
|
||||
.get(&id)
|
||||
.map(|ps| ps.keys().copied().collect::<HashSet<_>>())
|
||||
.unwrap_or_default();
|
||||
|
||||
Some(Note {
|
||||
id,
|
||||
text: info.text.clone(),
|
||||
children: info.children.clone(),
|
||||
parents,
|
||||
})
|
||||
}
|
||||
|
||||
fn tick(&mut self) {
|
||||
self.curr_id += 1;
|
||||
}
|
||||
|
||||
fn update_parents(&mut self) {
|
||||
self.parents.clear();
|
||||
for (id, info) in &self.notes {
|
||||
for child in &info.children {
|
||||
*self
|
||||
.parents
|
||||
.entry(*child)
|
||||
.or_default()
|
||||
.entry(*id)
|
||||
.or_default() += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(&mut self, text: String) -> NoteId {
|
||||
let id = NoteId::new();
|
||||
let info = NoteInfo {
|
||||
text,
|
||||
children: vec![],
|
||||
};
|
||||
|
||||
self.notes.insert(id, info);
|
||||
self.update_parents();
|
||||
self.tick();
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, id: NoteId) -> Option<NoteInfo> {
|
||||
let info = self.notes.remove(&id)?;
|
||||
self.update_parents();
|
||||
self.tick();
|
||||
Some(info)
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, id: NoteId, text: String) -> Option<()> {
|
||||
let note = self.notes.get_mut(&id)?;
|
||||
if note.text == text {
|
||||
return None;
|
||||
}
|
||||
note.text = text;
|
||||
self.tick();
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn set_children(&mut self, id: NoteId, children: Vec<NoteId>) -> Option<()> {
|
||||
let note = self.notes.get_mut(&id)?;
|
||||
if note.children == children {
|
||||
return None;
|
||||
}
|
||||
note.children = children;
|
||||
self.tick();
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Find the index of a child based on its id and iteration.
|
||||
///
|
||||
/// The index returned is in the range `[0, note.children.len())`.
|
||||
///
|
||||
/// Iteration 0 refers to the first occurrence of the id, iteration 1 to the
|
||||
/// second, and so on. Returns [`None`] if no such iteration was found.
|
||||
fn resolve_child_iteration(
|
||||
children: &[NoteId],
|
||||
child_id: NoteId,
|
||||
child_iteration: usize,
|
||||
) -> Option<usize> {
|
||||
children
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, it)| **it == child_id)
|
||||
.map(|(i, _)| i)
|
||||
.nth(child_iteration)
|
||||
}
|
||||
|
||||
/// Find the index of a child based on its position.
|
||||
///
|
||||
/// The index returned is in the range `[0, note.children.len()]`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// A small example for a note with children `[a, b, c]`:
|
||||
///
|
||||
/// ```text
|
||||
/// Child [ a, b, c] _
|
||||
/// Position 0 1 2 3
|
||||
/// Position -4 -3 -2 -1
|
||||
/// ```
|
||||
fn resolve_child_position(children: &[NoteId], child_position: isize) -> usize {
|
||||
if child_position > 0 {
|
||||
let child_position = child_position as usize;
|
||||
child_position.min(children.len())
|
||||
} else {
|
||||
let child_position = (-child_position - 1) as usize;
|
||||
children.len().saturating_sub(child_position)
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a child at the specified position.
|
||||
///
|
||||
/// Returns `Some(())` if the operation was successful.
|
||||
pub fn add_child_at_position(
|
||||
&mut self,
|
||||
id: NoteId,
|
||||
child_id: NoteId,
|
||||
child_position: isize,
|
||||
) -> Option<()> {
|
||||
let note = self.notes.get_mut(&id)?;
|
||||
let index = Self::resolve_child_position(¬e.children, child_position);
|
||||
note.children.insert(index, child_id);
|
||||
|
||||
self.update_parents();
|
||||
self.tick();
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Remove the specified iteration of a child.
|
||||
///
|
||||
/// Returns `Some(())` if the operation was successful.
|
||||
pub fn remove_child_by_id(
|
||||
&mut self,
|
||||
id: NoteId,
|
||||
child_id: NoteId,
|
||||
child_iteration: usize,
|
||||
) -> Option<()> {
|
||||
let note = self.notes.get_mut(&id)?;
|
||||
let index = Self::resolve_child_iteration(¬e.children, child_id, child_iteration)?;
|
||||
note.children.remove(index);
|
||||
|
||||
self.update_parents();
|
||||
self.tick();
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// A combination of [`Self::add_child_at_position`] and
|
||||
/// [`Self::remove_child_by_id`].
|
||||
///
|
||||
/// Returns `Some(())` if the operation was successful.
|
||||
pub fn move_child_by_id_to_position(
|
||||
&mut self,
|
||||
child_id: NoteId,
|
||||
from_id: NoteId,
|
||||
from_iteration: usize,
|
||||
to_id: NoteId,
|
||||
to_position: isize,
|
||||
) -> Option<()> {
|
||||
let from = self.get(from_id)?;
|
||||
let to = self.get(to_id)?;
|
||||
|
||||
let from_idx = Self::resolve_child_iteration(&from.children, child_id, from_iteration)?;
|
||||
let mut to_idx = Self::resolve_child_position(&to.children, to_position);
|
||||
|
||||
if from_id == to_id && from_idx < to_idx {
|
||||
to_idx += 1;
|
||||
}
|
||||
|
||||
let removed_id = self
|
||||
.notes
|
||||
.get_mut(&from_id)
|
||||
.unwrap()
|
||||
.children
|
||||
.remove(from_idx);
|
||||
assert!(removed_id == child_id);
|
||||
|
||||
self.notes
|
||||
.get_mut(&to_id)
|
||||
.unwrap()
|
||||
.children
|
||||
.insert(to_idx, child_id);
|
||||
|
||||
self.update_parents();
|
||||
self.tick();
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.notes.clear();
|
||||
|
||||
self.update_parents();
|
||||
self.tick();
|
||||
}
|
||||
}
|
||||
24
gdn-app/src-tauri/src/types.rs
Normal file
24
gdn-app/src-tauri/src/types.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::ids::NoteId;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Note {
|
||||
pub id: NoteId,
|
||||
pub text: String,
|
||||
pub children: Vec<NoteId>,
|
||||
pub parents: HashSet<NoteId>,
|
||||
}
|
||||
|
||||
////////////
|
||||
// Events //
|
||||
////////////
|
||||
|
||||
#[derive(Clone, Copy, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EventNotesStoreUpdate {
|
||||
pub store_id: u64,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue