use std::collections::{HashMap, HashSet}; use gdn::ids::NoteId; use crate::types::Note; #[derive(Debug)] pub struct NoteInfo { pub text: String, pub children: Vec, } /// A note store for testing. #[derive(Default)] pub struct Store { last_id: u64, curr_id: u64, notes: HashMap, parents: HashMap>, } impl Store { pub fn new() -> Self { Self::default() } pub fn needs_update(&self) -> Option { 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 { let info = self.notes.get(&id)?; let parents = self .parents .get(&id) .map(|ps| ps.keys().copied().collect::>()) .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::>(); 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 { 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) -> 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 { 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(); } }