Răsfoiți Sursa

Improve support for drawing items in `ui.leaflet` (#3586)

* show drawn layers

* avoid circular references when emitting draw events

* wait a tick for more event arguments

* update documentation

* allow hiding drawn items

* add demos for editing drawn items and drawing with custom options
Falko Schindler 8 luni în urmă
părinte
comite
0ad7478da1

+ 21 - 5
nicegui/elements/leaflet.js

@@ -1,4 +1,5 @@
 import { loadResource } from "../../static/utils/resources.js";
+import { cleanObject } from "../../static/utils/json.js";
 
 export default {
   template: "<div></div>",
@@ -8,6 +9,7 @@ export default {
     options: Object,
     draw_control: Object,
     resource_path: String,
+    hide_drawn_items: Boolean,
   },
   async mounted() {
     await this.$nextTick(); // NOTE: wait for window.path_prefix to be set
@@ -85,10 +87,18 @@ export default {
     if (this.draw_control) {
       for (const key in L.Draw.Event) {
         const type = L.Draw.Event[key];
-        this.map.on(type, (e) => {
+        this.map.on(type, async (e) => {
+          await this.$nextTick(); // NOTE: allow drawn layers to be added
+          const cleanedObject = cleanObject(e, [
+            "_map",
+            "_events",
+            "_eventParents",
+            "_handlers",
+            "_mapToAdd",
+            "_initHooksCalled",
+          ]);
           this.$emit(type, {
-            ...e,
-            layer: e.layer ? { ...e.layer, editing: undefined, _events: undefined } : undefined,
+            ...cleanedObject,
             target: undefined,
             sourceTarget: undefined,
           });
@@ -97,10 +107,16 @@ export default {
       const drawnItems = new L.FeatureGroup();
       this.map.addLayer(drawnItems);
       const drawControl = new L.Control.Draw({
-        edit: { featureGroup: drawnItems },
-        ...this.draw_control,
+        draw: this.draw_control.draw,
+        edit: {
+          ...this.draw_control.edit,
+          featureGroup: drawnItems,
+        },
       });
       this.map.addControl(drawControl);
+      if (!this.hide_drawn_items) {
+        this.map.on("draw:created", (e) => drawnItems.addLayer(e.layer));
+      }
     }
     const connectInterval = setInterval(async () => {
       if (window.socket.id === undefined) return;

+ 3 - 0
nicegui/elements/leaflet.py

@@ -26,6 +26,7 @@ class Leaflet(Element, component='leaflet.js'):
                  *,
                  options: Dict = {},  # noqa: B006
                  draw_control: Union[bool, Dict] = False,
+                 hide_drawn_items: bool = False,
                  ) -> None:
         """Leaflet map
 
@@ -35,6 +36,7 @@ class Leaflet(Element, component='leaflet.js'):
         :param zoom: initial zoom level of the map (default: 13)
         :param draw_control: whether to show the draw toolbar (default: False)
         :param options: additional options passed to the Leaflet map (default: {})
+        :param hide_drawn_items: whether to hide drawn items on the map (default: False)
         """
         super().__init__()
         self.add_resource(Path(__file__).parent / 'lib' / 'leaflet')
@@ -49,6 +51,7 @@ class Leaflet(Element, component='leaflet.js'):
         self._props['zoom'] = zoom
         self._props['options'] = {**options}
         self._props['draw_control'] = draw_control
+        self._props['hide_drawn_items'] = hide_drawn_items
 
         self.on('init', self._handle_init)
         self.on('map-moveend', self._handle_moveend)

+ 26 - 0
nicegui/static/utils/json.js

@@ -0,0 +1,26 @@
+// Remove keysToRemove, functions, and circular references from obj
+export function cleanObject(obj, keysToRemove = [], seen = new WeakSet()) {
+  if (obj === null || typeof obj !== "object") {
+    return obj;
+  }
+
+  if (typeof value === "function") {
+    return undefined;
+  }
+
+  if (seen.has(obj)) {
+    return undefined;
+  }
+
+  seen.add(obj);
+
+  if (Array.isArray(obj)) {
+    return obj.map((item) => cleanObject(item, keysToRemove, seen));
+  }
+
+  return Object.fromEntries(
+    Object.entries(obj)
+      .filter(([key, value]) => !keysToRemove.includes(key) && typeof value !== "function" && !seen.has(value))
+      .map(([key, value]) => [key, cleanObject(value, keysToRemove, seen)])
+  );
+}

+ 41 - 7
website/documentation/content/leaflet_documentation.py

@@ -94,29 +94,63 @@ def disable_pan_zoom() -> None:
     You can enable a toolbar to draw on the map.
     The `draw_control` can be used to configure the toolbar.
     This demo adds markers and polygons by clicking on the map.
+    By setting "edit" and "remove" to `True` (the default), you can enable editing and deleting drawn shapes.
 ''')
 def draw_on_map() -> None:
     from nicegui import events
 
     def handle_draw(e: events.GenericEventArguments):
-        if e.args['layerType'] == 'marker':
-            m.marker(latlng=(e.args['layer']['_latlng']['lat'],
-                             e.args['layer']['_latlng']['lng']))
-        if e.args['layerType'] == 'polygon':
-            m.generic_layer(name='polygon', args=[e.args['layer']['_latlngs']])
+        layer_type = e.args['layerType']
+        coords = e.args['layer'].get('_latlng') or e.args['layer'].get('_latlngs')
+        ui.notify(f'Drawn a {layer_type} at {coords}')
 
     draw_control = {
         'draw': {
             'polygon': True,
             'marker': True,
+            'circle': True,
+            'rectangle': True,
+            'polyline': True,
+            'circlemarker': True,
+        },
+        'edit': {
+            'edit': True,
+            'remove': True,
+        },
+    }
+    m = ui.leaflet(center=(51.505, -0.09), draw_control=draw_control)
+    m.classes('h-96')
+    m.on('draw:created', handle_draw)
+    m.on('draw:edited', lambda: ui.notify('Edit completed'))
+    m.on('draw:deleted', lambda: ui.notify('Delete completed'))
+
+
+@doc.demo('Draw with Custom Options', '''
+    You can draw shapes with custom options like stroke color and weight.
+    To hide the default rendering of drawn items, set `hide_drawn_items` to `True`.
+''')
+def draw_custom_options():
+    from nicegui import events
+
+    def handle_draw(e: events.GenericEventArguments):
+        options = {'color': 'red', 'weight': 1}
+        m.generic_layer(name='polygon', args=[e.args['layer']['_latlngs'], options])
+
+    draw_control = {
+        'draw': {
+            'polygon': True,
+            'marker': False,
             'circle': False,
             'rectangle': False,
             'polyline': False,
             'circlemarker': False,
         },
-        'edit': False,
+        'edit': {
+            'edit': False,
+            'remove': False,
+        },
     }
-    m = ui.leaflet(center=(51.505, -0.09), zoom=13, draw_control=draw_control)
+    m = ui.leaflet(center=(51.5, 0), draw_control=draw_control, hide_drawn_items=True)
     m.on('draw:created', handle_draw)