Skip to main content

dioxus_mind_elixir/
handle.rs

1use crate::plugins::{Capability, Command, Plugin, PluginRegistry};
2use crate::{
3    CoreEvent, MindMapData, MindMapError, MindMapState, Operation, OperationOptions,
4    OperationResult,
5};
6use dioxus::prelude::*;
7use std::{collections::BTreeMap, rc::Rc};
8
9type Before = Rc<dyn Fn(&Operation) -> bool>;
10type Listener = Rc<dyn Fn(CoreEvent)>;
11
12/// Copyable shared document handle. Mutation always passes through checked core
13/// transactions. Individual views own their viewport, editor and mounted nodes.
14#[derive(Clone, Copy, PartialEq)]
15pub struct MindMapHandle {
16    state: Signal<MindMapState>,
17    before: Signal<Option<Before>>,
18    listener: Signal<Option<Listener>>,
19    editor_owner: Signal<Option<usize>>,
20    subscribers: Signal<BTreeMap<u64, Listener>>,
21    next_subscription: Signal<u64>,
22    plugins: Signal<PluginRegistry>,
23}
24
25/// Create one shared document for any number of views in the current scope.
26/// The initial value is consumed only when the component first mounts.
27pub fn use_mind_map(data: MindMapData) -> Result<MindMapHandle, MindMapError> {
28    use_hook(|| {
29        MindMapState::new(data).map(|state| MindMapHandle {
30            state: Signal::new(state),
31            before: Signal::new(None),
32            listener: Signal::new(None),
33            editor_owner: Signal::new(None),
34            subscribers: Signal::new(BTreeMap::new()),
35            next_subscription: Signal::new(0),
36            plugins: Signal::new(PluginRegistry::default()),
37        })
38    })
39}
40impl MindMapHandle {
41    /// Read a reactive persistent snapshot. No internal mutable signal is exposed.
42    pub fn document(self) -> Result<MindMapData, MindMapError> {
43        self.state
44            .try_read()
45            .map(|state| state.data().clone())
46            .map_err(|_| MindMapError::StaleOperation)
47    }
48    /// Current selected IDs, reactively tracked.
49    pub fn selection(self) -> Result<Vec<String>, MindMapError> {
50        self.state
51            .try_read()
52            .map(|state| state.selection().to_vec())
53            .map_err(|_| MindMapError::StaleOperation)
54    }
55    /// Current generation, reactively tracked.
56    pub fn revision(self) -> Result<u64, MindMapError> {
57        self.state
58            .try_read()
59            .map(|state| state.revision())
60            .map_err(|_| MindMapError::StaleOperation)
61    }
62    /// Whether mutation is disabled.
63    pub fn is_read_only(self) -> Result<bool, MindMapError> {
64        self.state
65            .try_read()
66            .map(|state| state.is_read_only())
67            .map_err(|_| MindMapError::StaleOperation)
68    }
69    /// Set a synchronous veto hook. Invoked without a state borrow; reentrant
70    /// mutations cause the outer operation to fail with StaleOperation.
71    pub fn set_before(
72        mut self,
73        hook: impl Fn(&Operation) -> bool + 'static,
74    ) -> Result<(), MindMapError> {
75        *self
76            .before
77            .try_write()
78            .map_err(|_| MindMapError::StaleOperation)? = Some(Rc::new(hook));
79        Ok(())
80    }
81    /// Clear the veto hook.
82    pub fn clear_before(mut self) -> Result<(), MindMapError> {
83        *self
84            .before
85            .try_write()
86            .map_err(|_| MindMapError::StaleOperation)? = None;
87        Ok(())
88    }
89    /// Install a host event listener. Events are delivered after releasing state.
90    pub fn set_listener(
91        mut self,
92        listener: impl Fn(CoreEvent) + 'static,
93    ) -> Result<(), MindMapError> {
94        *self
95            .listener
96            .try_write()
97            .map_err(|_| MindMapError::StaleOperation)? = Some(Rc::new(listener));
98        Ok(())
99    }
100    /// Execute a checked mutation using the current before hook.
101    pub fn apply(self, operation: Operation) -> Result<OperationResult, MindMapError> {
102        self.apply_with(operation, OperationOptions::default())
103    }
104    /// Execute a checked mutation with explicit recording options.
105    pub fn apply_with(
106        mut self,
107        operation: Operation,
108        options: OperationOptions,
109    ) -> Result<OperationResult, MindMapError> {
110        let revision = self.revision()?;
111        if self.is_read_only()? {
112            return Err(MindMapError::ReadOnly);
113        }
114        let before = self
115            .before
116            .try_read()
117            .map_err(|_| MindMapError::StaleOperation)?
118            .clone();
119        if let Some(before) = before {
120            if !before(&operation) {
121                return Err(MindMapError::Rejected);
122            }
123        }
124        let result = {
125            let mut state = self
126                .state
127                .try_write()
128                .map_err(|_| MindMapError::StaleOperation)?;
129            if state.revision() != revision {
130                return Err(MindMapError::StaleOperation);
131            }
132            state.apply_with(operation, options)?
133        };
134        self.emit(&result.events)?;
135        Ok(result)
136    }
137    /// Select existing IDs, including collapsed descendants.
138    pub fn select(mut self, ids: &[String]) -> Result<(), MindMapError> {
139        let events = self
140            .state
141            .try_write()
142            .map_err(|_| MindMapError::StaleOperation)?
143            .select(ids)?;
144        self.emit(&events)
145    }
146    /// Enable/disable all document mutation entry points.
147    pub fn set_read_only(mut self, value: bool) -> Result<(), MindMapError> {
148        self.state
149            .try_write()
150            .map_err(|_| MindMapError::StaleOperation)?
151            .set_read_only(value);
152        Ok(())
153    }
154    /// Undo and restore selection.
155    pub fn undo(mut self) -> Result<(), MindMapError> {
156        let events = self
157            .state
158            .try_write()
159            .map_err(|_| MindMapError::StaleOperation)?
160            .undo()?;
161        self.emit(&events)
162    }
163    /// Redo and restore selection.
164    pub fn redo(mut self) -> Result<(), MindMapError> {
165        let events = self
166            .state
167            .try_write()
168            .map_err(|_| MindMapError::StaleOperation)?
169            .redo()?;
170        self.emit(&events)
171    }
172    /// Validate and replace the document, clearing selection and history.
173    pub fn refresh(mut self, data: MindMapData) -> Result<(), MindMapError> {
174        let events = self
175            .state
176            .try_write()
177            .map_err(|_| MindMapError::StaleOperation)?
178            .refresh(data)?;
179        self.emit(&events)
180    }
181    pub(crate) fn claim_editor(mut self, owner: usize) -> Result<(), MindMapError> {
182        *self
183            .editor_owner
184            .try_write()
185            .map_err(|_| MindMapError::StaleOperation)? = Some(owner);
186        Ok(())
187    }
188    pub(crate) fn owns_editor(self, owner: usize) -> Result<bool, MindMapError> {
189        Ok(*self
190            .editor_owner
191            .try_read()
192            .map_err(|_| MindMapError::StaleOperation)?
193            == Some(owner))
194    }
195    /// Add an independent listener. Dropping the returned guard unsubscribes it.
196    pub fn subscribe(
197        mut self,
198        listener: impl Fn(CoreEvent) + 'static,
199    ) -> Result<Subscription, MindMapError> {
200        let id = *self
201            .next_subscription
202            .try_read()
203            .map_err(|_| MindMapError::StaleOperation)?;
204        let next = id
205            .checked_add(1)
206            .ok_or_else(|| MindMapError::InvalidOperation("Subscription ID exhausted".into()))?;
207        *self
208            .next_subscription
209            .try_write()
210            .map_err(|_| MindMapError::StaleOperation)? = next;
211        self.subscribers
212            .try_write()
213            .map_err(|_| MindMapError::StaleOperation)?
214            .insert(id, Rc::new(listener));
215        Ok(Subscription { handle: self, id })
216    }
217    /// Register a plugin for the lifetime of a guard. No document/history mutation.
218    pub fn register_plugin(mut self, plugin: Plugin) -> Result<PluginGuard, MindMapError> {
219        let id = plugin.id.clone();
220        self.plugins
221            .try_write()
222            .map_err(|_| MindMapError::StaleOperation)?
223            .register(plugin)?;
224        Ok(PluginGuard {
225            handle: self,
226            id,
227            subscriptions: Vec::new(),
228        })
229    }
230    /// Toggle an existing registration. Disabled capabilities disappear reactively.
231    pub fn set_plugin_enabled(mut self, id: &str, enabled: bool) -> Result<(), MindMapError> {
232        self.plugins
233            .try_write()
234            .map_err(|_| MindMapError::StaleOperation)?
235            .set_enabled(id, enabled)
236    }
237    /// Test an enabled capability for this document only.
238    pub fn has_plugin(self, capability: Capability) -> bool {
239        self.plugins
240            .try_read()
241            .is_ok_and(|plugins| plugins.provides(capability))
242    }
243    /// Commands supplied by enabled plugins.
244    pub fn plugin_commands(self) -> Result<Vec<Command>, MindMapError> {
245        Ok(self
246            .plugins
247            .try_read()
248            .map_err(|_| MindMapError::StaleOperation)?
249            .commands())
250    }
251    fn emit(self, events: &[CoreEvent]) -> Result<(), MindMapError> {
252        let listener = self
253            .listener
254            .try_read()
255            .map_err(|_| MindMapError::StaleOperation)?
256            .clone();
257        let subscribers = self
258            .subscribers
259            .try_read()
260            .map_err(|_| MindMapError::StaleOperation)?
261            .iter()
262            .map(|(id, listener)| (*id, listener.clone()))
263            .collect::<Vec<_>>();
264        for event in events {
265            if let Some(listener) = &listener {
266                listener(event.clone());
267            }
268            for (id, callback) in &subscribers {
269                if self
270                    .subscribers
271                    .try_read()
272                    .is_ok_and(|current| current.contains_key(id))
273                {
274                    callback(event.clone());
275                }
276            }
277        }
278        Ok(())
279    }
280}
281
282/// An event subscription; dropping it releases the callback, including on unmount.
283pub struct Subscription {
284    handle: MindMapHandle,
285    id: u64,
286}
287impl Drop for Subscription {
288    fn drop(&mut self) {
289        if let Ok(mut entries) = self.handle.subscribers.try_write() {
290            entries.remove(&self.id);
291        }
292    }
293}
294/// Registration lifetime and owned event subscriptions for one plugin instance.
295pub struct PluginGuard {
296    handle: MindMapHandle,
297    id: String,
298    subscriptions: Vec<Subscription>,
299}
300impl PluginGuard {
301    /// Attach a listener that is removed together with this plugin.
302    pub fn subscribe(
303        &mut self,
304        listener: impl Fn(CoreEvent) + 'static,
305    ) -> Result<(), MindMapError> {
306        let handle = self.handle;
307        let id = self.id.clone();
308        self.subscriptions.push(handle.subscribe(move |event| {
309            let enabled = handle
310                .plugins
311                .try_read()
312                .is_ok_and(|registry| registry.is_enabled(&id));
313            if enabled {
314                listener(event);
315            }
316        })?);
317        Ok(())
318    }
319    /// Toggle this registration without releasing it.
320    pub fn set_enabled(&self, enabled: bool) -> Result<(), MindMapError> {
321        self.handle.set_plugin_enabled(&self.id, enabled)
322    }
323}
324impl Drop for PluginGuard {
325    fn drop(&mut self) {
326        if let Ok(mut registry) = self.handle.plugins.try_write() {
327            let _ = registry.unregister(&self.id);
328        }
329    }
330}
331/// Install plugins for the current component's lifetime. Returns errors explicitly.
332pub fn use_plugins(
333    handle: MindMapHandle,
334    plugins: Vec<Plugin>,
335) -> Result<Rc<Vec<PluginGuard>>, MindMapError> {
336    use_hook(|| {
337        plugins
338            .into_iter()
339            .map(|plugin| handle.register_plugin(plugin))
340            .collect::<Result<Vec<_>, _>>()
341            .map(Rc::new)
342    })
343}