leptos_leaflet/components/
context.rs

1use leaflet::Map;
2use leptos::prelude::*;
3use wasm_bindgen::JsCast;
4
5use crate::core::{JsReadSignal, JsRwSignal, JsWriteSignal};
6
7
8/// A context struct for the Leaflet map.
9/// 
10/// This struct provides a way to access the current state of the Leaflet map.
11#[derive(Debug, Clone, Copy)]
12pub struct LeafletMapContext {
13    map: JsRwSignal<Option<leaflet::Map>>,
14    thread_id: std::thread::ThreadId,
15}
16
17impl LeafletMapContext {
18    /// Creates a new `LeafletMapContext` instance.
19    pub fn new() -> Self {
20        Self {
21            map: JsRwSignal::new_local(None),
22            thread_id: std::thread::current().id(),
23        }
24    }
25
26    /// Sets the map for the context.
27    ///
28    /// # Arguments
29    ///
30    /// * `map` - A reference to a `leaflet::Map` object.
31    pub fn set_map(&self, map: &leaflet::Map) {
32        if !self.is_valid() {
33            leptos::logging::error!("Accessing map from a different thread. Probably running on the server.");
34            return;
35        }
36        self.map.set(Some(map.clone()));
37    }
38
39    /// Returns an optional `leaflet::Map` instance.
40    pub fn map(&self) -> Option<leaflet::Map> {
41        if self.is_valid() {
42            self.map.get()
43        } else {
44            leptos::logging::error!("Accessing map from a different thread. Probably running on the server.");
45            None
46        }
47    }
48
49    pub fn map_untracked(&self) -> Option<leaflet::Map> {
50        if self.is_valid() {
51            self.map.get_untracked()
52        } else {
53            leptos::logging::error!("Accessing map from a different thread. Probably running on the server.");
54            None
55        }
56    }
57
58    /// Returns a signal that emits the current map instance.
59    pub fn map_signal(&self) -> JsReadSignal<Option<leaflet::Map>> {
60        if self.is_valid() {
61            self.map.read_only()
62        } else {
63            panic!("Accessing map from a different thread. Probably running on the server.");
64        }
65    }
66
67    /// Adds a layer to the context.
68    ///
69    /// # Arguments
70    ///
71    /// * `layer` - A reference to the layer to be added.
72    ///
73    pub fn add_layer<L: Into<leaflet::Layer> + Clone>(&self, layer: &L) {
74        if !self.is_valid() {
75            leptos::logging::error!("Accessing map from a different thread. Probably running on the server.");
76            return;
77        }
78        let map = self.map.get_untracked().expect("Map to be available");
79        let layer: leaflet::Layer = layer.to_owned().into();
80        layer.add_to(&map);
81    }
82
83    /// Removes a layer from the context.
84    ///
85    /// # Arguments
86    ///
87    /// * `layer` - A reference to the layer to be removed.
88    ///
89    pub fn remove_layer<L: Into<leaflet::Layer> + Clone>(&self, layer: &L) {
90        if !self.is_valid() {
91            leptos::logging::error!("Accessing map from a different thread. Probably running on the server.");
92            return;
93        }
94        let map = self.map.get_untracked().expect("Map to be available");
95        let layer: leaflet::Layer = layer.to_owned().into();
96        layer.remove_from(&map);
97    }
98
99    fn is_valid(&self) -> bool {
100        std::thread::current().id() == self.thread_id && !self.map.is_disposed()
101    }
102}
103
104impl Default for LeafletMapContext {
105    fn default() -> Self {
106        Self::new()
107    }
108}
109
110/// Provides the Leaflet map context.
111pub fn provide_leaflet_context() -> LeafletMapContext {
112    let context = LeafletMapContext::new();
113    provide_context(context);
114    context
115}
116
117/// Returns an optional `LeafletMapContext` object that can be used to access the current state of the Leaflet map.
118pub fn use_leaflet_context() -> Option<LeafletMapContext> {
119    use_context::<LeafletMapContext>()
120}
121
122/// Extends the context with an overlay container.
123pub fn extend_context_with_overlay() -> LeafletOverlayContainerContext {
124    let overlay_context = LeafletOverlayContainerContext::new();
125    provide_context(overlay_context);
126    overlay_context
127}
128
129/// Returns an optional `LeafletOverlayContainerContext` for the current overlay.
130pub fn use_overlay_context() -> Option<LeafletOverlayContainerContext> {
131    use_context::<LeafletOverlayContainerContext>()
132}
133
134/// Returns an optional `leafet::Layer` for the overlay context layer.
135pub fn use_overlay_context_layer<T>() -> Option<T>
136where
137    T: Into<leaflet::Layer> + Clone + JsCast,
138{
139    expect_context::<LeafletOverlayContainerContext>().container::<T>()
140}
141
142/// Updates the overlay context with the given layer.
143///
144/// # Arguments
145///
146/// * `layer` - A cloneable object that can be converted into a `leaflet::Layer`.
147pub fn update_overlay_context<C: Into<leaflet::Layer> + Clone>(layer: &C) {
148    let overlay_context = use_context::<LeafletOverlayContainerContext>().expect("overlay context");
149    overlay_context.set_container(layer);
150}
151
152/// A context struct for Leaflet overlay container.
153#[derive(Debug, Clone, Copy)]
154pub struct LeafletOverlayContainerContext {
155    container: JsRwSignal<Option<leaflet::Layer>>,
156    thread_id: std::thread::ThreadId,
157}
158
159impl LeafletOverlayContainerContext {
160    pub fn new() -> Self {
161        Self {
162            container: JsRwSignal::new_local(None),
163            thread_id: std::thread::current().id(),
164        }
165    }
166
167    /// Calls set on the inner signal for the Layer
168    pub fn set_container<C: Into<leaflet::Layer> + Clone>(&self, layer: &C) {
169        if !self.is_valid() {
170            leptos::logging::error!("Accessing map from a different thread. Probably running on the server.");
171            return;
172        }
173        self.container.set(Some(layer.clone().into()));
174    }
175
176    /// Calls get on the inner signal for the Layer
177    pub fn container<T: JsCast>(&self) -> Option<T> {
178        if !self.is_valid() {
179            leptos::logging::error!("Accessing map from a different thread. Probably running on the server.");
180            return None;
181        }
182        self.container.get().map(|layer| layer.unchecked_into())
183    }
184
185    /// Calls get_untracked on the inner signal for the Layer
186    pub fn untrack_container<C: JsCast>(&self) -> Option<C> {
187        if !self.is_valid() {
188            leptos::logging::error!("Accessing map from a different thread. Probably running on the server.");
189            return None;
190        }
191        self.container
192            .get_untracked()
193            .map(|layer| layer.unchecked_into())
194    }
195
196    fn is_valid(&self) -> bool {
197        std::thread::current().id() == self.thread_id && !self.container.is_disposed()
198    }
199}
200
201impl Default for LeafletOverlayContainerContext {
202    fn default() -> Self {
203        Self::new()
204    }
205}
206
207#[derive(Debug, Clone, Copy)]
208pub struct TileLayerWmsContext {
209    wms: JsRwSignal<Option<leaflet::TileLayerWms>>,
210    thread_id: std::thread::ThreadId,
211}
212
213impl Default for TileLayerWmsContext {
214    fn default() -> Self {
215        Self::new()
216    }
217}
218
219impl TileLayerWmsContext {
220    pub fn new() -> Self {
221        Self {
222            wms: JsRwSignal::new_local(None),
223            thread_id: std::thread::current().id(),
224        }
225    }
226
227    pub fn set_wms(&self, wms: &leaflet::TileLayerWms) {
228        if !self.is_valid() {
229            leptos::logging::error!("Accessing map from a different thread. Probably running on the server.");
230            return;
231        }
232        self.wms.set(Some(wms.clone()));
233    }
234
235    pub fn wms(&self) -> Option<leaflet::TileLayerWms> {
236        if self.is_valid() {
237            self.wms.get()
238        } else {
239            leptos::logging::error!("Accessing map from a different thread. Probably running on the server.");
240            None
241        }
242    }
243
244    fn is_valid(&self) -> bool {
245        std::thread::current().id() == self.thread_id && !self.wms.is_disposed()
246    }
247}
248
249pub type MapReadSignal = JsReadSignal<Option<Map>>;
250pub type MapWriteSignal = JsWriteSignal<Option<Map>>;
251
252/// Creates a pair of signals for reading and writing a `leaflet::Map` instance.
253pub fn create_map_signal() -> (MapReadSignal, MapWriteSignal) {
254    JsRwSignal::new_local(None).split()
255}