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#[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 pending: Signal<Option<(usize, PendingEdit)>>,
21 subscribers: Signal<BTreeMap<u64, Listener>>,
22 next_subscription: Signal<u64>,
23 plugins: Signal<PluginRegistry>,
24}
25
26#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct PendingEdit {
32 pub id: String,
34 pub original: String,
36 pub draft: String,
38}
39impl PendingEdit {
40 pub fn is_changed(&self) -> bool {
42 !self.draft.trim().is_empty() && self.draft != self.original
43 }
44}
45
46pub fn use_mind_map(data: MindMapData) -> Result<MindMapHandle, MindMapError> {
49 use_hook(|| {
50 MindMapState::new(data).map(|state| MindMapHandle {
51 state: Signal::new(state),
52 before: Signal::new(None),
53 listener: Signal::new(None),
54 editor_owner: Signal::new(None),
55 pending: Signal::new(None),
56 subscribers: Signal::new(BTreeMap::new()),
57 next_subscription: Signal::new(0),
58 plugins: Signal::new(PluginRegistry::default()),
59 })
60 })
61}
62impl MindMapHandle {
63 pub fn document(self) -> Result<MindMapData, MindMapError> {
65 self.state
66 .try_read()
67 .map(|state| state.data().clone())
68 .map_err(|_| MindMapError::StaleOperation)
69 }
70 pub fn selection(self) -> Result<Vec<String>, MindMapError> {
72 self.state
73 .try_read()
74 .map(|state| state.selection().to_vec())
75 .map_err(|_| MindMapError::StaleOperation)
76 }
77 pub fn revision(self) -> Result<u64, MindMapError> {
79 self.state
80 .try_read()
81 .map(|state| state.revision())
82 .map_err(|_| MindMapError::StaleOperation)
83 }
84 pub fn is_read_only(self) -> Result<bool, MindMapError> {
86 self.state
87 .try_read()
88 .map(|state| state.is_read_only())
89 .map_err(|_| MindMapError::StaleOperation)
90 }
91 pub fn set_before(
94 mut self,
95 hook: impl Fn(&Operation) -> bool + 'static,
96 ) -> Result<(), MindMapError> {
97 *self
98 .before
99 .try_write()
100 .map_err(|_| MindMapError::StaleOperation)? = Some(Rc::new(hook));
101 Ok(())
102 }
103 pub fn clear_before(mut self) -> Result<(), MindMapError> {
105 *self
106 .before
107 .try_write()
108 .map_err(|_| MindMapError::StaleOperation)? = None;
109 Ok(())
110 }
111 pub fn set_listener(
113 mut self,
114 listener: impl Fn(CoreEvent) + 'static,
115 ) -> Result<(), MindMapError> {
116 *self
117 .listener
118 .try_write()
119 .map_err(|_| MindMapError::StaleOperation)? = Some(Rc::new(listener));
120 Ok(())
121 }
122 pub fn apply(self, operation: Operation) -> Result<OperationResult, MindMapError> {
124 self.apply_with(operation, OperationOptions::default())
125 }
126 pub fn apply_with(
128 mut self,
129 operation: Operation,
130 options: OperationOptions,
131 ) -> Result<OperationResult, MindMapError> {
132 let revision = self.revision()?;
133 if self.is_read_only()? {
134 return Err(MindMapError::ReadOnly);
135 }
136 let before = self
137 .before
138 .try_read()
139 .map_err(|_| MindMapError::StaleOperation)?
140 .clone();
141 if let Some(before) = before {
142 if !before(&operation) {
143 return Err(MindMapError::Rejected);
144 }
145 }
146 let result = {
147 let mut state = self
148 .state
149 .try_write()
150 .map_err(|_| MindMapError::StaleOperation)?;
151 if state.revision() != revision {
152 return Err(MindMapError::StaleOperation);
153 }
154 state.apply_with(operation, options)?
155 };
156 self.emit(&result.events)?;
157 Ok(result)
158 }
159 pub fn select(mut self, ids: &[String]) -> Result<(), MindMapError> {
161 let events = self
162 .state
163 .try_write()
164 .map_err(|_| MindMapError::StaleOperation)?
165 .select(ids)?;
166 self.emit(&events)
167 }
168 pub fn set_read_only(mut self, value: bool) -> Result<(), MindMapError> {
170 self.state
171 .try_write()
172 .map_err(|_| MindMapError::StaleOperation)?
173 .set_read_only(value);
174 Ok(())
175 }
176 pub fn undo(mut self) -> Result<(), MindMapError> {
178 let events = self
179 .state
180 .try_write()
181 .map_err(|_| MindMapError::StaleOperation)?
182 .undo()?;
183 self.emit(&events)
184 }
185 pub fn redo(mut self) -> Result<(), MindMapError> {
187 let events = self
188 .state
189 .try_write()
190 .map_err(|_| MindMapError::StaleOperation)?
191 .redo()?;
192 self.emit(&events)
193 }
194 pub fn refresh(mut self, data: MindMapData) -> Result<(), MindMapError> {
196 let events = self
197 .state
198 .try_write()
199 .map_err(|_| MindMapError::StaleOperation)?
200 .refresh(data)?;
201 self.emit(&events)
202 }
203 pub fn pending_edit(self) -> Result<Option<PendingEdit>, MindMapError> {
205 self.pending
206 .try_read()
207 .map(|pending| pending.as_ref().map(|(_, edit)| edit.clone()))
208 .map_err(|_| MindMapError::StaleOperation)
209 }
210 pub fn commit_pending(mut self) -> Result<bool, MindMapError> {
214 let pending = self
215 .pending
216 .try_read()
217 .map_err(|_| MindMapError::StaleOperation)?
218 .clone();
219 let Some((_, edit)) = pending else {
220 return Ok(false);
221 };
222 let changed = if edit.is_changed() {
223 self.apply(Operation::SetTopic {
224 id: edit.id.clone(),
225 topic: edit.draft.clone(),
226 })?
227 .changed
228 } else {
229 false
230 };
231 *self
232 .pending
233 .try_write()
234 .map_err(|_| MindMapError::StaleOperation)? = None;
235 Ok(changed)
236 }
237 pub fn cancel_pending(mut self) -> Result<(), MindMapError> {
239 *self
240 .pending
241 .try_write()
242 .map_err(|_| MindMapError::StaleOperation)? = None;
243 Ok(())
244 }
245 pub(crate) fn begin_edit(
246 mut self,
247 owner: usize,
248 id: &str,
249 original: &str,
250 ) -> Result<(), MindMapError> {
251 self.claim_editor(owner)?;
252 *self
253 .pending
254 .try_write()
255 .map_err(|_| MindMapError::StaleOperation)? = Some((
256 owner,
257 PendingEdit {
258 id: id.into(),
259 original: original.into(),
260 draft: original.into(),
261 },
262 ));
263 Ok(())
264 }
265 pub(crate) fn update_draft(mut self, owner: usize, draft: String) {
266 if let Ok(mut pending) = self.pending.try_write() {
267 if let Some((current, edit)) = pending.as_mut() {
268 if *current == owner {
269 edit.draft = draft;
270 }
271 }
272 }
273 }
274 #[doc(hidden)]
276 pub fn begin_edit_for_test(self, id: &str, original: &str) -> Result<(), MindMapError> {
277 self.begin_edit(usize::MAX, id, original)
278 }
279 #[doc(hidden)]
281 pub fn update_draft_for_test(self, draft: &str) {
282 self.update_draft(usize::MAX, draft.into());
283 }
284 pub(crate) fn edits(self, owner: usize, id: &str) -> bool {
286 self.pending
287 .try_read()
288 .is_ok_and(|p| p.as_ref().is_some_and(|(o, e)| *o == owner && e.id == id))
289 }
290 pub(crate) fn claim_editor(mut self, owner: usize) -> Result<(), MindMapError> {
291 *self
292 .editor_owner
293 .try_write()
294 .map_err(|_| MindMapError::StaleOperation)? = Some(owner);
295 Ok(())
296 }
297 pub(crate) fn owns_editor(self, owner: usize) -> Result<bool, MindMapError> {
298 Ok(*self
299 .editor_owner
300 .try_read()
301 .map_err(|_| MindMapError::StaleOperation)?
302 == Some(owner))
303 }
304 pub fn subscribe(
306 mut self,
307 listener: impl Fn(CoreEvent) + 'static,
308 ) -> Result<Subscription, MindMapError> {
309 let id = *self
310 .next_subscription
311 .try_read()
312 .map_err(|_| MindMapError::StaleOperation)?;
313 let next = id
314 .checked_add(1)
315 .ok_or_else(|| MindMapError::InvalidOperation("Subscription ID exhausted".into()))?;
316 *self
317 .next_subscription
318 .try_write()
319 .map_err(|_| MindMapError::StaleOperation)? = next;
320 self.subscribers
321 .try_write()
322 .map_err(|_| MindMapError::StaleOperation)?
323 .insert(id, Rc::new(listener));
324 Ok(Subscription { handle: self, id })
325 }
326 pub fn register_plugin(mut self, plugin: Plugin) -> Result<PluginGuard, MindMapError> {
328 let id = plugin.id.clone();
329 self.plugins
330 .try_write()
331 .map_err(|_| MindMapError::StaleOperation)?
332 .register(plugin)?;
333 Ok(PluginGuard {
334 handle: self,
335 id,
336 subscriptions: Vec::new(),
337 })
338 }
339 pub fn set_plugin_enabled(mut self, id: &str, enabled: bool) -> Result<(), MindMapError> {
341 self.plugins
342 .try_write()
343 .map_err(|_| MindMapError::StaleOperation)?
344 .set_enabled(id, enabled)
345 }
346 pub fn has_plugin(self, capability: Capability) -> bool {
348 self.plugins
349 .try_read()
350 .is_ok_and(|plugins| plugins.provides(capability))
351 }
352 pub fn plugin_commands(self) -> Result<Vec<Command>, MindMapError> {
354 Ok(self
355 .plugins
356 .try_read()
357 .map_err(|_| MindMapError::StaleOperation)?
358 .commands())
359 }
360 fn emit(self, events: &[CoreEvent]) -> Result<(), MindMapError> {
361 let listener = self
362 .listener
363 .try_read()
364 .map_err(|_| MindMapError::StaleOperation)?
365 .clone();
366 let subscribers = self
367 .subscribers
368 .try_read()
369 .map_err(|_| MindMapError::StaleOperation)?
370 .iter()
371 .map(|(id, listener)| (*id, listener.clone()))
372 .collect::<Vec<_>>();
373 for event in events {
374 if let Some(listener) = &listener {
375 listener(event.clone());
376 }
377 for (id, callback) in &subscribers {
378 if self
379 .subscribers
380 .try_read()
381 .is_ok_and(|current| current.contains_key(id))
382 {
383 callback(event.clone());
384 }
385 }
386 }
387 Ok(())
388 }
389}
390
391pub struct Subscription {
393 handle: MindMapHandle,
394 id: u64,
395}
396impl Drop for Subscription {
397 fn drop(&mut self) {
398 if let Ok(mut entries) = self.handle.subscribers.try_write() {
399 entries.remove(&self.id);
400 }
401 }
402}
403pub struct PluginGuard {
405 handle: MindMapHandle,
406 id: String,
407 subscriptions: Vec<Subscription>,
408}
409impl PluginGuard {
410 pub fn subscribe(
412 &mut self,
413 listener: impl Fn(CoreEvent) + 'static,
414 ) -> Result<(), MindMapError> {
415 let handle = self.handle;
416 let id = self.id.clone();
417 self.subscriptions.push(handle.subscribe(move |event| {
418 let enabled = handle
419 .plugins
420 .try_read()
421 .is_ok_and(|registry| registry.is_enabled(&id));
422 if enabled {
423 listener(event);
424 }
425 })?);
426 Ok(())
427 }
428 pub fn set_enabled(&self, enabled: bool) -> Result<(), MindMapError> {
430 self.handle.set_plugin_enabled(&self.id, enabled)
431 }
432}
433impl Drop for PluginGuard {
434 fn drop(&mut self) {
435 if let Ok(mut registry) = self.handle.plugins.try_write() {
436 let _ = registry.unregister(&self.id);
437 }
438 }
439}
440pub fn use_plugins(
442 handle: MindMapHandle,
443 plugins: Vec<Plugin>,
444) -> Result<Rc<Vec<PluginGuard>>, MindMapError> {
445 use_hook(|| {
446 plugins
447 .into_iter()
448 .map(|plugin| handle.register_plugin(plugin))
449 .collect::<Result<Vec<_>, _>>()
450 .map(Rc::new)
451 })
452}