1use std::collections::HashSet;
2use std::sync::Arc;
3
4use yrs::sync::time::Clock;
5use yrs::{Doc, Origin, ReadTxn, Transact};
6
7use crate::{EditError, EditResult, SHAPES, SLIDE_ORDER, SLIDES, STORIES};
8
9const CAPTURE_TIMEOUT_MS: u64 = 500;
10
11pub struct DeckUndoManager {
12 inner: yrs::undo::UndoManager<()>,
13}
14
15impl DeckUndoManager {
16 pub(crate) fn new(doc: &Doc, client_id: u64) -> EditResult<Self> {
17 let txn = doc.transact();
18 let order = txn
19 .get_array(SLIDE_ORDER)
20 .ok_or_else(|| EditError::InvalidState("missing slide order".to_owned()))?;
21 let slides = txn
22 .get_map(SLIDES)
23 .ok_or_else(|| EditError::InvalidState("missing slides map".to_owned()))?;
24 let shapes = txn
25 .get_map(SHAPES)
26 .ok_or_else(|| EditError::InvalidState("missing shapes map".to_owned()))?;
27 let stories = txn
28 .get_map(STORIES)
29 .ok_or_else(|| EditError::InvalidState("missing stories map".to_owned()))?;
30 drop(txn);
31 let options = yrs::undo::Options {
32 capture_timeout_millis: CAPTURE_TIMEOUT_MS,
33 tracked_origins: HashSet::from([Origin::from(client_id)]),
34 capture_transaction: None,
35 timestamp: default_clock(),
36 init_undo_stack: Vec::new(),
37 init_redo_stack: Vec::new(),
38 };
39 let mut inner = yrs::undo::UndoManager::with_options(options);
40 inner.expand_scope(doc, &order);
41 inner.expand_scope(doc, &slides);
42 inner.expand_scope(doc, &shapes);
43 inner.expand_scope(doc, &stories);
44 Ok(Self { inner })
45 }
46
47 pub fn undo(&mut self) -> bool {
48 self.inner.undo_blocking()
49 }
50
51 pub fn redo(&mut self) -> bool {
52 self.inner.redo_blocking()
53 }
54
55 pub fn can_undo(&self) -> bool {
56 self.inner.can_undo()
57 }
58
59 pub fn can_redo(&self) -> bool {
60 self.inner.can_redo()
61 }
62
63 pub fn add_undo_barrier(&mut self) {
64 self.inner.reset();
65 }
66}
67
68fn default_clock() -> Arc<dyn Clock> {
69 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
70 {
71 Arc::new(|| {
72 std::time::SystemTime::now()
73 .duration_since(std::time::UNIX_EPOCH)
74 .map(|elapsed| elapsed.as_millis() as u64)
75 .unwrap_or_default()
76 })
77 }
78 #[cfg(all(target_family = "wasm", target_os = "unknown"))]
79 {
80 use std::sync::atomic::{AtomicU64, Ordering};
81 let ticks = AtomicU64::new(0);
82 Arc::new(move || ticks.fetch_add(CAPTURE_TIMEOUT_MS + 1, Ordering::Relaxed))
83 }
84}