1use std::collections::{BTreeMap, BTreeSet};
3
4use crate::{LayoutDirection, MindMapData, MindMapError, Node, NodeDirection, NodeImage};
5
6#[derive(Clone, Debug, PartialEq, Eq)]
8pub enum Placement {
9 In(String),
11 Before(String),
13 After(String),
15}
16impl Placement {
17 fn target(&self) -> &str {
18 match self {
19 Self::In(id) | Self::Before(id) | Self::After(id) => id,
20 }
21 }
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
26pub enum Operation {
27 Insert {
29 node: Node,
31 placement: Placement,
33 },
34 InsertParent {
36 target: String,
38 parent: Node,
40 },
41 Remove(Vec<String>),
43 Move {
45 ids: Vec<String>,
47 placement: Placement,
49 },
50 Copy {
52 ids: Vec<String>,
54 placement: Placement,
56 },
57 SetTopic {
59 id: String,
61 topic: String,
63 },
64 SetExpanded {
66 id: String,
68 expanded: bool,
70 },
71 SetNote {
73 id: String,
75 note: Option<String>,
77 },
78 SetImage {
80 id: String,
82 image: Option<NodeImage>,
84 },
85 SetLayout(LayoutDirection),
87 SetCompact(bool),
89}
90
91#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
93pub struct OperationOptions {
94 pub silent: bool,
98}
99
100#[derive(Clone, Debug, PartialEq, Eq)]
102pub enum CoreEvent {
103 Operation(Box<Operation>),
105 Selection(Vec<String>),
107 History {
109 undo: bool,
111 },
112 Refresh,
114}
115
116#[derive(Clone, Debug, Default, PartialEq, Eq)]
118pub struct OperationResult {
119 pub changed: bool,
121 pub ids: Vec<String>,
123 pub events: Vec<CoreEvent>,
125}
126
127#[derive(Clone, Debug, PartialEq, Eq)]
128struct Snapshot {
129 data: MindMapData,
130 selection: Vec<String>,
131}
132#[derive(Clone, Debug)]
133struct HistoryEntry {
134 before: Snapshot,
135 after: Snapshot,
136}
137
138#[derive(Clone, Debug)]
143pub struct MindMapState {
144 snapshot: Snapshot,
145 parents: BTreeMap<String, Option<String>>,
146 undo: Vec<HistoryEntry>,
147 redo: Vec<HistoryEntry>,
148 history_limit: usize,
149 read_only: bool,
150 revision: u64,
151 next_id: u64,
152}
153impl MindMapState {
154 pub fn new(data: MindMapData) -> Result<Self, MindMapError> {
156 data.validate()?;
157 let mut state = Self {
158 snapshot: Snapshot {
159 data,
160 selection: Vec::new(),
161 },
162 parents: BTreeMap::new(),
163 undo: Vec::new(),
164 redo: Vec::new(),
165 history_limit: 100,
166 read_only: false,
167 revision: 0,
168 next_id: 0,
169 };
170 state.normalize_sides();
171 state.reindex();
172 Ok(state)
173 }
174 pub fn data(&self) -> &MindMapData {
176 &self.snapshot.data
177 }
178 pub fn selection(&self) -> &[String] {
180 &self.snapshot.selection
181 }
182 pub fn node(&self, id: &str) -> Option<&Node> {
184 self.data().node_data.find(id)
185 }
186 pub fn parent_id(&self, id: &str) -> Option<&str> {
188 self.parents.get(id).and_then(Option::as_deref)
189 }
190 pub fn revision(&self) -> u64 {
192 self.revision
193 }
194 pub fn is_read_only(&self) -> bool {
196 self.read_only
197 }
198 pub fn set_read_only(&mut self, value: bool) {
200 if self.read_only != value {
201 self.read_only = value;
202 self.bump();
203 }
204 }
205 pub fn set_history_limit(&mut self, limit: usize) {
207 self.history_limit = limit;
208 if self.undo.len() > limit {
209 self.undo.drain(..self.undo.len() - limit);
210 }
211 if self.redo.len() > limit {
212 self.redo.drain(..self.redo.len() - limit);
213 }
214 }
215 pub fn history_len(&self) -> (usize, usize) {
217 (self.undo.len(), self.redo.len())
218 }
219 pub fn select(&mut self, ids: &[String]) -> Result<Vec<CoreEvent>, MindMapError> {
221 for id in ids {
222 self.require(id)?;
223 }
224 let wanted: BTreeSet<_> = ids.iter().collect();
225 let ordered = ordered_nodes(&self.data().node_data)
226 .into_iter()
227 .filter(|node| wanted.contains(&node.id))
228 .map(|node| node.id.clone())
229 .collect::<Vec<_>>();
230 if ordered == self.snapshot.selection {
231 return Ok(Vec::new());
232 }
233 self.snapshot.selection = ordered.clone();
234 self.bump();
235 Ok(vec![CoreEvent::Selection(ordered)])
236 }
237 pub fn apply(&mut self, operation: Operation) -> Result<OperationResult, MindMapError> {
239 self.apply_with(operation, OperationOptions::default())
240 }
241 pub fn apply_with(
243 &mut self,
244 operation: Operation,
245 options: OperationOptions,
246 ) -> Result<OperationResult, MindMapError> {
247 self.writable()?;
248 match &operation {
249 Operation::Insert { node, .. } | Operation::InsertParent { parent: node, .. } => {
250 node.validate()?
251 }
252 _ => {}
253 }
254 let before = self.snapshot.clone();
255 let mut candidate = before.clone();
256 let mut next_id = self.next_id;
257 let ids = self.mutate(&mut candidate, &operation, &mut next_id)?;
258 candidate.data.validate()?;
259 normalize_sides(&mut candidate.data);
260 if candidate.data == before.data {
261 return Ok(OperationResult::default());
262 }
263 candidate
264 .selection
265 .retain(|id| candidate.data.node_data.find(id).is_some());
266 let selection_changed = before.selection != candidate.selection;
267 self.snapshot = candidate;
268 self.next_id = next_id;
269 self.reindex();
270 self.bump();
271 self.redo.clear();
272 let mut events = Vec::new();
273 if !options.silent {
274 if self.history_limit > 0 {
275 self.undo.push(HistoryEntry {
276 before,
277 after: self.snapshot.clone(),
278 });
279 if self.undo.len() > self.history_limit {
280 self.undo.remove(0);
281 }
282 }
283 events.push(CoreEvent::Operation(Box::new(operation)));
284 }
285 if selection_changed {
286 events.push(CoreEvent::Selection(self.selection().to_vec()));
287 }
288 Ok(OperationResult {
289 changed: true,
290 ids,
291 events,
292 })
293 }
294 pub fn undo(&mut self) -> Result<Vec<CoreEvent>, MindMapError> {
296 self.writable()?;
297 let entry = self.undo.pop().ok_or(MindMapError::NoHistory)?;
298 self.snapshot = entry.before.clone();
299 self.redo.push(entry);
300 self.reindex();
301 self.bump();
302 Ok(vec![
303 CoreEvent::History { undo: true },
304 CoreEvent::Selection(self.selection().to_vec()),
305 ])
306 }
307 pub fn redo(&mut self) -> Result<Vec<CoreEvent>, MindMapError> {
309 self.writable()?;
310 let entry = self.redo.pop().ok_or(MindMapError::NoHistory)?;
311 self.snapshot = entry.after.clone();
312 self.undo.push(entry);
313 self.reindex();
314 self.bump();
315 Ok(vec![
316 CoreEvent::History { undo: false },
317 CoreEvent::Selection(self.selection().to_vec()),
318 ])
319 }
320 pub fn refresh(&mut self, mut data: MindMapData) -> Result<Vec<CoreEvent>, MindMapError> {
322 self.writable()?;
323 data.validate()?;
324 normalize_sides(&mut data);
325 self.snapshot = Snapshot {
326 data,
327 selection: Vec::new(),
328 };
329 self.undo.clear();
330 self.redo.clear();
331 self.reindex();
332 self.bump();
333 Ok(vec![CoreEvent::Refresh, CoreEvent::Selection(Vec::new())])
334 }
335 fn require(&self, id: &str) -> Result<&Node, MindMapError> {
336 self.node(id)
337 .ok_or_else(|| MindMapError::NodeNotFound(id.into()))
338 }
339 fn writable(&self) -> Result<(), MindMapError> {
340 if self.read_only {
341 Err(MindMapError::ReadOnly)
342 } else {
343 Ok(())
344 }
345 }
346 fn bump(&mut self) {
347 self.revision = self.revision.saturating_add(1);
348 }
349 fn normalize_sides(&mut self) {
350 normalize_sides(&mut self.snapshot.data);
351 }
352 fn reindex(&mut self) {
353 self.parents.clear();
354 let mut pending = vec![(&self.snapshot.data.node_data, None)];
355 while let Some((node, parent)) = pending.pop() {
356 self.parents.insert(node.id.clone(), parent);
357 pending.extend(
358 node.children
359 .iter()
360 .map(|child| (child, Some(node.id.clone()))),
361 );
362 }
363 }
364 fn sources(&self, ids: &[String]) -> Result<Vec<String>, MindMapError> {
365 let wanted: BTreeSet<_> = ids.iter().collect();
366 for id in ids {
367 self.require(id)?;
368 if id == &self.data().node_data.id {
369 return Err(MindMapError::RootOperation);
370 }
371 }
372 let mut result = Vec::new();
373 for node in ordered_nodes(&self.data().node_data) {
374 if !wanted.contains(&node.id) {
375 continue;
376 }
377 let mut parent = self.parent_id(&node.id);
378 let mut covered = false;
379 while let Some(id) = parent {
380 if wanted.iter().any(|wanted| wanted.as_str() == id) {
381 covered = true;
382 break;
383 }
384 parent = self.parent_id(id);
385 }
386 if !covered {
387 result.push(node.id.clone());
388 }
389 }
390 Ok(result)
391 }
392 fn mutate(
393 &self,
394 candidate: &mut Snapshot,
395 operation: &Operation,
396 next_id: &mut u64,
397 ) -> Result<Vec<String>, MindMapError> {
398 let root = &mut candidate.data.node_data;
399 match operation {
400 Operation::SetTopic { id, topic } => {
401 if topic.trim().is_empty() {
402 return Err(MindMapError::InvalidOperation(
403 "Topic cannot be empty".into(),
404 ));
405 }
406 root.find_mut(id)
407 .ok_or_else(|| MindMapError::NodeNotFound(id.clone()))?
408 .topic = topic.clone();
409 Ok(Vec::new())
410 }
411 Operation::SetExpanded { id, expanded } => {
412 root.find_mut(id)
413 .ok_or_else(|| MindMapError::NodeNotFound(id.clone()))?
414 .expanded = *expanded;
415 Ok(Vec::new())
416 }
417 Operation::SetNote { id, note } => {
418 root.find_mut(id)
419 .ok_or_else(|| MindMapError::NodeNotFound(id.clone()))?
420 .note = note.clone().filter(|note| !note.trim().is_empty());
421 Ok(Vec::new())
422 }
423 Operation::SetImage { id, image } => {
424 if let Some(image) = image {
425 image.validate()?;
426 }
427 root.find_mut(id)
428 .ok_or_else(|| MindMapError::NodeNotFound(id.clone()))?
429 .image = image.clone();
430 Ok(Vec::new())
431 }
432 Operation::SetLayout(direction) => {
433 candidate.data.direction = *direction;
434 Ok(Vec::new())
435 }
436 Operation::SetCompact(compact) => {
437 candidate.data.compact = *compact;
438 Ok(Vec::new())
439 }
440 Operation::Insert { node, placement } => {
441 let ids = vec![node.id.clone()];
442 insert(root, vec![node.clone()], placement)?;
443 candidate.selection = ids.clone();
444 Ok(ids)
445 }
446 Operation::InsertParent { target, parent } => {
447 self.require(target)?;
448 if !parent.children.is_empty() {
449 return Err(MindMapError::InvalidOperation(
450 "Inserted parent must be a leaf".into(),
451 ));
452 }
453 let parent_id = self.parent_id(target).ok_or(MindMapError::RootOperation)?;
454 let siblings = &mut root
455 .find_mut(parent_id)
456 .ok_or_else(|| MindMapError::NodeNotFound(parent_id.into()))?
457 .children;
458 let index = siblings
459 .iter()
460 .position(|node| &node.id == target)
461 .ok_or_else(|| MindMapError::NodeNotFound(target.clone()))?;
462 let mut wrapper = parent.clone();
463 let original = siblings.remove(index);
464 wrapper.direction = original.direction;
465 wrapper.children.push(original);
466 siblings.insert(index, wrapper);
467 candidate.selection = vec![parent.id.clone()];
468 Ok(candidate.selection.clone())
469 }
470 Operation::Remove(ids) => {
471 let sources = self.sources(ids)?;
472 let fallback = sources
473 .first()
474 .and_then(|id| self.parent_id(id))
475 .map(str::to_owned);
476 for id in &sources {
477 take(root, id).ok_or_else(|| MindMapError::NodeNotFound(id.clone()))?;
478 }
479 if !sources.is_empty() {
480 candidate.selection = fallback.into_iter().collect();
481 }
482 Ok(sources)
483 }
484 Operation::Move { ids, placement } | Operation::Copy { ids, placement } => {
485 let sources = self.sources(ids)?;
486 self.require(placement.target())?;
487 let copying = matches!(operation, Operation::Copy { .. });
488 if !copying {
489 for id in &sources {
490 if self.require(id)?.find(placement.target()).is_some() {
491 return Err(MindMapError::AncestorMove);
492 }
493 }
494 }
495 let mut nodes = Vec::new();
496 let mut used: BTreeSet<String> = ordered_nodes(root)
497 .iter()
498 .map(|node| node.id.clone())
499 .collect();
500 for id in &sources {
501 let mut node = if copying {
502 self.require(id)?.clone()
503 } else {
504 take(root, id).ok_or_else(|| MindMapError::NodeNotFound(id.clone()))?
505 };
506 if copying {
507 regenerate_ids(&mut node, &mut used, next_id)?;
508 }
509 nodes.push(node);
510 }
511 let inserted = nodes.iter().map(|node| node.id.clone()).collect::<Vec<_>>();
512 if !nodes.is_empty() {
513 insert(root, nodes, placement)?;
514 candidate.selection = inserted.clone();
515 }
516 Ok(inserted)
517 }
518 }
519 }
520}
521
522fn ordered_nodes(root: &Node) -> Vec<&Node> {
523 let mut result = Vec::new();
524 let mut pending = vec![root];
525 while let Some(node) = pending.pop() {
526 result.push(node);
527 pending.extend(node.children.iter().rev());
528 }
529 result
530}
531fn take(root: &mut Node, id: &str) -> Option<Node> {
532 if let Some(index) = root.children.iter().position(|node| node.id == id) {
533 return Some(root.children.remove(index));
534 }
535 root.children.iter_mut().find_map(|node| take(node, id))
536}
537fn find_parent_mut<'a>(root: &'a mut Node, id: &str) -> Option<&'a mut Node> {
538 if root.children.iter().any(|node| node.id == id) {
539 return Some(root);
540 }
541 root.children
542 .iter_mut()
543 .find_map(|node| find_parent_mut(node, id))
544}
545fn insert(
546 root: &mut Node,
547 mut nodes: Vec<Node>,
548 placement: &Placement,
549) -> Result<(), MindMapError> {
550 let target = placement.target();
551 match placement {
552 Placement::In(_) => {
553 let parent = root
554 .find_mut(target)
555 .ok_or_else(|| MindMapError::NodeNotFound(target.into()))?;
556 parent.expanded = true;
557 parent.children.append(&mut nodes);
558 }
559 Placement::Before(_) | Placement::After(_) => {
560 if root.id == target {
561 return Err(MindMapError::RootOperation);
562 }
563 let parent = find_parent_mut(root, target)
564 .ok_or_else(|| MindMapError::NodeNotFound(target.into()))?;
565 let index = parent
566 .children
567 .iter()
568 .position(|node| node.id == target)
569 .ok_or_else(|| MindMapError::NodeNotFound(target.into()))?;
570 let side = parent.children.get(index).and_then(|node| node.direction);
571 for node in &mut nodes {
572 node.direction = side;
573 }
574 let offset = usize::from(matches!(placement, Placement::After(_)));
575 parent
576 .children
577 .splice(index + offset..index + offset, nodes);
578 }
579 }
580 Ok(())
581}
582fn regenerate_ids(
583 node: &mut Node,
584 used: &mut BTreeSet<String>,
585 next: &mut u64,
586) -> Result<(), MindMapError> {
587 loop {
588 *next = next
589 .checked_add(1)
590 .ok_or_else(|| MindMapError::InvalidOperation("Generated ID space exhausted".into()))?;
591 let id = format!("dme-{next}");
592 if used.insert(id.clone()) {
593 node.id = id;
594 break;
595 }
596 }
597 for child in &mut node.children {
598 regenerate_ids(child, used, next)?;
599 }
600 Ok(())
601}
602fn normalize_sides(data: &mut MindMapData) {
603 if data.direction != LayoutDirection::Side {
604 return;
605 }
606 let (mut left, mut right) = (0usize, 0usize);
607 for child in &mut data.node_data.children {
608 let side = match child.direction {
609 Some(side) => side,
610 None if left <= right => NodeDirection::Left,
611 None => NodeDirection::Right,
612 };
613 child.direction = Some(side);
614 match side {
615 NodeDirection::Left => left += 1,
616 NodeDirection::Right => right += 1,
617 }
618 }
619}