use leaflet::{CanvasOptions, SvgOptions};
use leptos::context::Provider;
use leptos::prelude::*;
use web_sys::HtmlElement;
use super::use_leaflet_context;
use crate::core::JsStoredValue;
use tracing::debug;
#[derive(Debug, Clone, PartialEq)]
pub enum PaneStrategy {
Custom(Signal<String>),
Context,
Default,
}
impl Default for PaneStrategy {
fn default() -> Self {
Self::Context
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PaneRendererScope {
Global,
PaneSpecificSvg,
PaneSpecificCanvas,
}
impl Default for PaneRendererScope {
fn default() -> Self {
Self::Global
}
}
#[derive(Debug, Clone)]
pub struct PaneContext {
name: String,
renderer_scope: PaneRendererScope,
svg_renderer: JsStoredValue<Option<leaflet::Svg>>,
canvas_renderer: JsStoredValue<Option<leaflet::Canvas>>,
}
impl PaneContext {
pub fn new(name: String) -> Self {
Self {
name,
renderer_scope: PaneRendererScope::Global,
svg_renderer: JsStoredValue::new_local(None),
canvas_renderer: JsStoredValue::new_local(None),
}
}
pub fn new_with_renderer(
name: String,
renderer_scope: PaneRendererScope,
svg_renderer: JsStoredValue<Option<leaflet::Svg>>,
canvas_renderer: JsStoredValue<Option<leaflet::Canvas>>,
) -> Self {
Self {
name,
renderer_scope,
svg_renderer,
canvas_renderer,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn renderer_scope(&self) -> &PaneRendererScope {
&self.renderer_scope
}
pub fn svg_renderer(&self) -> Option<leaflet::Svg> {
self.svg_renderer.get_value()
}
pub fn canvas_renderer(&self) -> Option<leaflet::Canvas> {
self.canvas_renderer.get_value()
}
}
pub fn provide_pane_context(name: String) -> PaneContext {
let context = PaneContext::new(name);
provide_context(context.clone());
context
}
pub fn provide_pane_context_with_renderer(
name: String,
renderer_scope: PaneRendererScope,
svg_renderer: JsStoredValue<Option<leaflet::Svg>>,
canvas_renderer: JsStoredValue<Option<leaflet::Canvas>>,
) -> PaneContext {
let context =
PaneContext::new_with_renderer(name, renderer_scope, svg_renderer, canvas_renderer);
provide_context(context.clone());
context
}
pub fn use_pane_context() -> Option<PaneContext> {
use_context::<PaneContext>()
}
#[component(transparent)]
pub fn Pane(
#[prop(into)]
name: String,
#[prop(into, optional)]
z_index: Option<Signal<f64>>,
#[prop(into, optional)]
renderer: Option<PaneRendererScope>,
#[prop(optional)]
children: Option<Children>,
) -> impl IntoView {
let map_context = use_leaflet_context().expect("Pane must be used within a MapContainer");
let name_clone = name.clone();
let pane_element = JsStoredValue::new_local(None::<HtmlElement>);
let svg_renderer = JsStoredValue::new_local(None::<leaflet::Svg>);
let canvas_renderer = JsStoredValue::new_local(None::<leaflet::Canvas>);
let renderer_scope = renderer.unwrap_or(PaneRendererScope::Global);
debug!(
"Providing pane context for: {} with renderer scope: {:?}",
name_clone, renderer_scope
);
let _pane_context = provide_pane_context_with_renderer(
name_clone.clone(),
renderer_scope,
svg_renderer.clone(),
canvas_renderer.clone(),
);
Effect::new(move |_| {
if let Some(map) = map_context.map() {
debug!(
"Creating pane: {} with renderer scope: {:?}",
name, renderer_scope
);
let pane = map.create_pane_by_name(&name);
if let Some(z_idx) = z_index {
let z_value = z_idx.get_untracked();
let _ = pane.style().set_property("z-index", &z_value.to_string());
debug!("Set z-index {} for pane: {}", z_value, name);
}
match &renderer_scope {
PaneRendererScope::PaneSpecificSvg => {
let options = SvgOptions::default();
options.set_pane(name.clone());
let renderer = leaflet::Svg::with_options(&options);
renderer.add_to(&map);
svg_renderer.set_value(Some(renderer));
debug!(
"Created and stored pane-specific SVG renderer for pane: {}",
name
);
}
PaneRendererScope::PaneSpecificCanvas => {
let options = CanvasOptions::default();
options.set_pane(name.clone());
let renderer = leaflet::Canvas::with_options(&options);
renderer.add_to(&map);
canvas_renderer.set_value(Some(renderer));
debug!(
"Created and stored pane-specific Canvas renderer for pane: {}",
name
);
}
PaneRendererScope::Global => {
debug!("Using global renderer for pane: {}", name);
}
}
pane_element.set_value(Some(pane));
debug!("Finished creating pane: {}", name);
}
});
if let Some(z_idx) = z_index {
let z_index_effect = Effect::watch(
move || z_idx.get(),
move |&z_value, _, _| {
if let Some(pane) = pane_element.get_value().as_ref() {
let _ = pane.style().set_property("z-index", &z_value.to_string());
}
},
false,
);
on_cleanup(move || {
z_index_effect.stop();
});
}
on_cleanup(move || {
if let Some(map) = map_context.map() {
if let Some(svg) = svg_renderer.get_value() {
svg.remove_from(&map);
}
if let Some(canvas) = canvas_renderer.get_value() {
canvas.remove_from(&map);
}
}
if let Some(pane) = pane_element.get_value() {
if let Some(parent) = pane.parent_element() {
let _ = parent.remove_child(&pane);
}
}
});
view! {
<Provider value=_pane_context>
{children.map(|child| child())}
</Provider>
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pane_context_creation() {
let context = PaneContext::new("test-pane".to_string());
assert_eq!(context.name(), "test-pane");
assert_eq!(*context.renderer_scope(), PaneRendererScope::Global);
}
#[test]
fn test_pane_context_with_svg_renderer() {
let svg_renderer = JsStoredValue::new_local(None);
let canvas_renderer = JsStoredValue::new_local(None);
let context = PaneContext::new_with_renderer(
"svg-pane".to_string(),
PaneRendererScope::PaneSpecificSvg,
svg_renderer,
canvas_renderer,
);
assert_eq!(context.name(), "svg-pane");
assert_eq!(
*context.renderer_scope(),
PaneRendererScope::PaneSpecificSvg
);
}
#[test]
fn test_pane_context_with_canvas_renderer() {
let svg_renderer = JsStoredValue::new_local(None);
let canvas_renderer = JsStoredValue::new_local(None);
let context = PaneContext::new_with_renderer(
"canvas-pane".to_string(),
PaneRendererScope::PaneSpecificCanvas,
svg_renderer,
canvas_renderer,
);
assert_eq!(context.name(), "canvas-pane");
assert_eq!(
*context.renderer_scope(),
PaneRendererScope::PaneSpecificCanvas
);
}
#[test]
fn test_renderer_scope_default() {
assert_eq!(PaneRendererScope::default(), PaneRendererScope::Global);
}
#[test]
fn pane_strategy_default() {
assert_eq!(PaneStrategy::default(), PaneStrategy::Context);
}
}