Browse Source

add support for multiple layers

Falko Schindler 1 year ago
parent
commit
9b0e52723d

+ 8 - 2
nicegui/elements/leaflet.js

@@ -2,14 +2,17 @@ export default {
   template: "<div></div>",
   props: {
     map_options: Object,
-    layers: Array,
   },
   async mounted() {
     await this.load_dependencies();
     this.map = L.map(this.$el, this.map_options);
     this.map.on("moveend", (e) => this.$emit("moveend", e.target.getCenter()));
     this.map.on("zoomend", (e) => this.$emit("zoomend", e.target.getZoom()));
-    this.layers.forEach((layer) => L.tileLayer(layer.url_template, layer.options).addTo(this.map));
+    const connectInterval = setInterval(async () => {
+      if (window.socket.id === undefined) return;
+      this.$emit("init", { socket_id: window.socket.id });
+      clearInterval(connectInterval);
+    }, 100);
   },
   updated() {
     this.map.setView(L.latLng(this.map_options.center.lat, this.map_options.center.lng), this.map_options.zoom);
@@ -34,5 +37,8 @@ export default {
         });
       }
     },
+    add_layer(layer) {
+      L[layer.type](...layer.args).addTo(this.map);
+    },
   },
 };

+ 41 - 28
nicegui/elements/leaflet.py

@@ -1,36 +1,52 @@
-from __future__ import annotations
+from typing import Any, List, Tuple, cast
 
-from dataclasses import dataclass
-from typing import Dict, Tuple, cast
-
-from ..binding import BindableProperty
+from .. import binding, globals
 from ..element import Element
-from ..helpers import KWONLY_SLOTS
-
-
-@dataclass(**KWONLY_SLOTS)
-class LeafletLayer:
-    url_template: str
-    options: Dict
-
-    @staticmethod
-    def default() -> LeafletLayer:
-        return LeafletLayer(url_template='http://{s}.tile.osm.org/{z}/{x}/{y}.png', options={
-            'attribution': '&copy; <a href="https://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
-        })
+from ..events import GenericEventArguments
+from .leaflet_layer import Layer
 
 
 class Leaflet(Element, component='leaflet.js'):
-    location = BindableProperty(lambda sender, _: cast(Leaflet, sender).update())
-    zoom = BindableProperty(lambda sender, _: cast(Leaflet, sender).update())
+    from .leaflet_layers import Marker as marker
+    from .leaflet_layers import TileLayer as tile_layer
+
+    location = binding.BindableProperty(lambda sender, _: cast(Leaflet, sender).update())
+    zoom = binding.BindableProperty(lambda sender, _: cast(Leaflet, sender).update())
 
     def __init__(self, location: Tuple[float, float] = (0, 0), zoom: int = 13) -> None:
         super().__init__()
-        self.layers = [LeafletLayer.default()]
+        self.layers: List[Layer] = []
         self.set_location(location)
         self.set_zoom(zoom)
+        self.is_initialized = False
+        self.on('init', self.handle_init)
         self.on('moveend', lambda e: self.set_location((e.args['lat'], e.args['lng'])))
         self.on('zoomend', lambda e: self.set_zoom(e.args))
+        self.tile_layer(
+            url_template='http://{s}.tile.osm.org/{z}/{x}/{y}.png',
+            options={'attribution': '&copy; <a href="https://openstreetmap.org">OpenStreetMap</a> contributors'},
+        )
+
+    def __enter__(self) -> 'Leaflet':
+        Layer.current_leaflet = self
+        return super().__enter__()
+
+    def __getattribute__(self, name: str) -> Any:
+        attribute = super().__getattribute__(name)
+        if isinstance(attribute, type) and issubclass(attribute, Layer):
+            Layer.current_leaflet = self
+        return attribute
+
+    def handle_init(self, e: GenericEventArguments) -> None:
+        self.is_initialized = True
+        with globals.socket_id(e.args['socket_id']):
+            for layer in self.layers:
+                self.run_method('add_layer', layer.to_dict())
+
+    def run_method(self, name: str, *args: Any) -> None:
+        if not self.is_initialized:
+            return
+        super().run_method(name, *args)
 
     def set_location(self, location: Tuple[float, float]) -> None:
         self.location = location
@@ -46,11 +62,8 @@ class Leaflet(Element, component='leaflet.js'):
             },
             'zoom': self.zoom,
         }
-        self._props['layers'] = [
-            {
-                'url_template': layer.url_template,
-                'options': layer.options
-            }
-            for layer in self.layers
-        ]
         super().update()
+
+    def delete(self) -> None:
+        binding.remove(self.layers, Layer)
+        super().delete()

+ 23 - 0
nicegui/elements/leaflet_layer.py

@@ -0,0 +1,23 @@
+from abc import abstractmethod
+from dataclasses import dataclass, field
+from typing import TYPE_CHECKING, ClassVar, Optional
+
+from ..helpers import KWONLY_SLOTS
+
+if TYPE_CHECKING:
+    from .leaflet import Leaflet
+
+
+@dataclass(**KWONLY_SLOTS)
+class Layer:
+    current_leaflet: ClassVar[Optional['Leaflet']] = None
+    leaflet: 'Leaflet' = field(init=False)
+
+    def __post_init__(self) -> None:
+        self.leaflet = self.current_leaflet
+        self.leaflet.layers.append(self)
+        self.leaflet.run_method('add_layer', self.to_dict())
+
+    @abstractmethod
+    def to_dict(self) -> dict:
+        pass

+ 35 - 0
nicegui/elements/leaflet_layers.py

@@ -0,0 +1,35 @@
+from dataclasses import dataclass, field
+from typing import Dict, Tuple
+
+from typing_extensions import Self
+
+from ..helpers import KWONLY_SLOTS
+from .leaflet_layer import Layer
+
+
+@dataclass(**KWONLY_SLOTS)
+class TileLayer(Layer):
+    url_template: str
+    options: Dict = field(default_factory=dict)
+
+    def to_dict(self) -> Dict:
+        return {
+            'type': 'tileLayer',
+            'args': [self.url_template, self.options],
+        }
+
+
+@dataclass(**KWONLY_SLOTS)
+class Marker(Layer):
+    location: Tuple[float, float]
+    options: Dict = field(default_factory=dict)
+
+    def to_dict(self) -> Dict:
+        return {
+            'type': 'marker',
+            'args': [{'lat': self.location[0], 'lng': self.location[1]}, self.options],
+        }
+
+    def draggable(self, value: bool = True) -> Self:
+        self.options['draggable'] = value
+        return self