242 lines
6.4 KiB
Rust
242 lines
6.4 KiB
Rust
use std::collections::{HashMap, HashSet};
|
|
|
|
use gdn::ids::NoteId;
|
|
|
|
use crate::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 make_consistent_and_tick(&mut self) {
|
|
// Remove child notes that don't exist
|
|
let children = self.notes.keys().copied().collect::<HashSet<_>>();
|
|
for info in &mut self.notes.values_mut() {
|
|
info.children.retain(|child| children.contains(child));
|
|
}
|
|
|
|
// Update parents to match new child notes
|
|
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;
|
|
}
|
|
}
|
|
|
|
self.tick();
|
|
}
|
|
|
|
pub fn create(&mut self, text: String) -> NoteId {
|
|
let id = NoteId::new();
|
|
let info = NoteInfo {
|
|
text,
|
|
children: vec![],
|
|
};
|
|
|
|
self.notes.insert(id, info);
|
|
self.make_consistent_and_tick();
|
|
|
|
id
|
|
}
|
|
|
|
pub fn delete(&mut self, id: NoteId) -> Option<NoteInfo> {
|
|
let info = self.notes.remove(&id)?;
|
|
self.make_consistent_and_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.make_consistent_and_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.make_consistent_and_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.make_consistent_and_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.make_consistent_and_tick();
|
|
Some(())
|
|
}
|
|
|
|
pub fn clear(&mut self) {
|
|
self.notes.clear();
|
|
self.make_consistent_and_tick();
|
|
}
|
|
}
|