Skip to main content

pptx_edit/
undo.rs

1use std::collections::HashSet;
2use std::sync::Arc;
3
4use yrs::sync::time::Clock;
5use yrs::{Doc, Origin, ReadTxn, Transact};
6
7use crate::{COMMENTS, EditError, EditResult, META, 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        let comments = txn
31            .get_map(COMMENTS)
32            .ok_or_else(|| EditError::InvalidState("missing comments map".to_owned()))?;
33        let meta = txn
34            .get_map(META)
35            .ok_or_else(|| EditError::InvalidState("missing meta map".to_owned()))?;
36        drop(txn);
37        let options = yrs::undo::Options {
38            capture_timeout_millis: CAPTURE_TIMEOUT_MS,
39            tracked_origins: HashSet::from([Origin::from(client_id)]),
40            capture_transaction: None,
41            timestamp: default_clock(),
42            init_undo_stack: Vec::new(),
43            init_redo_stack: Vec::new(),
44        };
45        let mut inner = yrs::undo::UndoManager::with_options(options);
46        inner.expand_scope(doc, &order);
47        inner.expand_scope(doc, &slides);
48        inner.expand_scope(doc, &shapes);
49        inner.expand_scope(doc, &stories);
50        inner.expand_scope(doc, &comments);
51        inner.expand_scope(doc, &meta);
52        Ok(Self { inner })
53    }
54
55    pub fn undo(&mut self) -> bool {
56        self.inner.undo_blocking()
57    }
58
59    pub fn redo(&mut self) -> bool {
60        self.inner.redo_blocking()
61    }
62
63    pub fn can_undo(&self) -> bool {
64        self.inner.can_undo()
65    }
66
67    pub fn can_redo(&self) -> bool {
68        self.inner.can_redo()
69    }
70
71    pub fn add_undo_barrier(&mut self) {
72        self.inner.reset();
73    }
74}
75
76fn default_clock() -> Arc<dyn Clock> {
77    #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
78    {
79        Arc::new(|| {
80            std::time::SystemTime::now()
81                .duration_since(std::time::UNIX_EPOCH)
82                .map(|elapsed| elapsed.as_millis() as u64)
83                .unwrap_or_default()
84        })
85    }
86    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
87    {
88        use std::sync::atomic::{AtomicU64, Ordering};
89        let ticks = AtomicU64::new(0);
90        Arc::new(move || ticks.fetch_add(CAPTURE_TIMEOUT_MS + 1, Ordering::Relaxed))
91    }
92}