dioxus_leaflet/
components.rs

1use dioxus::prelude::*;
2use crate::types::*;
3use crate::interop;
4
5/// Generates a unique map ID
6pub fn generate_map_id() -> String {
7    format!("dioxus_leaflet_map_{}", fastrand::u32(..))
8}
9
10#[derive(Props, Clone, PartialEq)]
11pub struct MapProps {
12    /// Initial position of the map
13    #[props(default = MapPosition::default())]
14    pub initial_position: MapPosition,
15    
16    /// Markers to display on the map
17    #[props(default = vec![])]
18    pub markers: ReadOnlySignal<Vec<MapMarker>>,
19
20    /// Polygons to display on the map
21    #[props(default = vec![])]
22    pub polygons: ReadOnlySignal<Vec<Polygon>>,
23    
24    /// Height of the map container
25    #[props(default = "500px".to_string())]
26    pub height: String,
27    
28    /// Width of the map container
29    #[props(default = "100%".to_string())]
30    pub width: String,
31    
32    /// Map configuration options
33    #[props(default = MapOptions::default())]
34    pub options: MapOptions,
35    
36    /// Custom CSS class for the map container
37    #[props(default = "".to_string())]
38    pub class: String,
39    
40    /// Custom CSS styles for the map container
41    #[props(default = "".to_string())]
42    pub style: String,
43    
44    /// Callback when marker is clicked
45    pub on_marker_click: Option<EventHandler<MapMarker>>,
46    
47    /// Callback when map is clicked
48    pub on_map_click: Option<EventHandler<MapPosition>>,
49    
50    /// Callback when map is moved
51    pub on_map_move: Option<EventHandler<MapPosition>>,
52}
53
54/// Main map component using Leaflet
55#[component]
56pub fn Map(props: MapProps) -> Element {
57    let map_id: Signal<String> = use_signal(generate_map_id);
58    let mut load_error: Signal<Option<String>> = use_signal(|| None);
59    
60    let container_style = format!(
61        "position: relative; width: {}; height: {}; {}",
62        props.width, props.height, props.style
63    );
64    
65    let container_class = if props.class.is_empty() {
66        "dioxus-leaflet-container".to_string()
67    } else {
68        format!("dioxus-leaflet-container {}", props.class)
69    };
70
71    let css_path = if let Some(_) = props.options.leaflet_resources.css_integrity() {
72        props.options.leaflet_resources.css_url()
73    } else {
74        props.options.leaflet_resources.css_url()
75    };
76
77    let js_path = if let Some(_) = props.options.leaflet_resources.js_integrity() {
78        props.options.leaflet_resources.js_url()
79    } else {
80        props.options.leaflet_resources.js_url()
81    };
82
83    use_effect(move || {
84        let id = map_id();
85        let pos = props.initial_position.clone();
86        let markers = (props.markers)();
87        let gons = (props.polygons)();
88        let opts = props.options.clone();
89        spawn(async move {
90            if let Err(e) = interop::update(&id, &pos, &markers, &gons, &opts).await {
91                load_error.set(Some(e));
92            }
93        });
94    });
95
96    rsx! {
97        // Leaflet CSS
98        document::Style { href: css_path }
99        
100        // Leaflet JavaScript
101        document::Script { src: js_path }
102        
103        // boot logic
104        document::Script { src: interop::DL_JS }
105
106        if let Some(err) = &*load_error.read() {
107            p {
108                "{err}"
109            }
110        }
111        else {
112            div {
113                class: "{container_class}",
114                style: "{container_style}",
115
116                // Map container
117                div {
118                    id: "{map_id}",
119                    class: "dioxus-leaflet-map",
120                    style: "width: 100%; height: 100%; z-index: 1;",
121                }
122            }
123        }
124    }
125}