Explorar el Código

update ui.interactive_image

Falko Schindler hace 2 años
padre
commit
3fb63059e7

+ 2 - 2
api_docs_and_examples.py

@@ -258,13 +258,13 @@ To overlay an SVG, make the `viewBox` exactly the size of the image and provide
                 </svg>'''
             ui.html(content).style('background:transparent')
 
-    # @example(ui.interactive_image)
+    @example(ui.interactive_image, skip=False)
     def interactive_image_example():
         from nicegui.events import MouseEventArguments
 
         def mouse_handler(e: MouseEventArguments):
             color = 'SkyBlue' if e.type == 'mousedown' else 'SteelBlue'
-            ii.svg_content += f'<circle cx="{e.image_x}" cy="{e.image_y}" r="20" fill="{color}"/>'
+            ii.content += f'<circle cx="{e.image_x}" cy="{e.image_y}" r="20" fill="{color}"/>'
             ui.notify(f'{e.type} at ({e.image_x:.1f}, {e.image_y:.1f})')
 
         src = 'https://cdn.stocksnap.io/img-thumbs/960w/corn-cob_YSZZZEC59W.jpg'

+ 29 - 37
nicegui/elements/old/interactive_image.js → nicegui/elements/interactive_image.js

@@ -1,19 +1,23 @@
-Vue.component("interactive_image", {
+export default {
   template: `
-    <div :id="jp_props.id" style="position:relative" :style="jp_props.style" :class="jp_props.classes">
-      <img style="width:100%; height:100%">
+    <div style="position:relative">
+      <img style="width:100%; height:100%" />
       <svg style="position:absolute;top:0;left:0;pointer-events:none">
         <g style="display:none">
           <line x1="100" y1="0" x2="100" y2="100%" stroke="black" />
           <line x1="0" y1="100" x2="100%" y2="100" stroke="black" />
         </g>
-        <g v-html="jp_props.options.svg_content"></g>
+        <g v-html="content"></g>
       </svg>
     </div>
   `,
+  data() {
+    return {
+      content: "",
+    };
+  },
   mounted() {
-    comp_dict[this.$props.jp_props.id] = this;
-    this.image = document.getElementById(this.$props.jp_props.id).firstChild;
+    this.image = this.$el.firstChild;
     const handle_completion = () => {
       if (this.waiting_source) {
         this.image.src = this.waiting_source;
@@ -24,10 +28,10 @@ Vue.component("interactive_image", {
     };
     this.image.addEventListener("load", handle_completion);
     this.image.addEventListener("error", handle_completion);
-    const svg = document.getElementById(this.$props.jp_props.id).lastChild;
-    const cross = svg.firstChild;
+    this.svg = this.$el.lastChild;
+    const cross = this.svg.firstChild;
     this.image.ondragstart = () => false;
-    if (this.$props.jp_props.options.cross) {
+    if (this.cross) {
       this.image.style.cursor = "none";
       this.image.addEventListener("mouseenter", (e) => {
         cross.style.display = "block";
@@ -46,46 +50,29 @@ Vue.component("interactive_image", {
     }
     this.image.onload = (e) => {
       const viewBox = `0 0 ${this.image.naturalWidth} ${this.image.naturalHeight}`;
-      svg.setAttribute("viewBox", viewBox);
+      this.svg.setAttribute("viewBox", viewBox);
     };
-    this.image.src = this.$props.jp_props.options.source;
-    for (const type of this.$props.jp_props.options.events) {
+    this.image.src = this.src;
+    for (const type of this.events) {
       this.image.addEventListener(type, (e) => {
-        const event = {
-          event_type: "onMouse",
+        this.$emit("mouse", {
           mouse_event_type: type,
-          vue_type: this.$props.jp_props.vue_type,
-          id: this.$props.jp_props.id,
-          page_id: page_id,
-          websocket_id: websocket_id,
           image_x: (e.offsetX * e.target.naturalWidth) / e.target.clientWidth,
           image_y: (e.offsetY * e.target.naturalHeight) / e.target.clientHeight,
-        };
-        send_to_server(event, "event");
+        });
       });
     }
 
+    this.is_initialized = false;
     const sendConnectEvent = () => {
-      if (websocket_id === "") return;
-      const event = {
-        event_type: "onConnect",
-        vue_type: this.$props.jp_props.vue_type,
-        id: this.$props.jp_props.id,
-        page_id: page_id,
-        websocket_id: websocket_id,
-      };
-      send_to_server(event, "event");
-      clearInterval(connectInterval);
+      if (!this.is_initialized) this.$emit("connect");
+      else clearInterval(connectInterval);
     };
     const connectInterval = setInterval(sendConnectEvent, 100);
   },
-  updated() {
-    if (this.image.src != this.$props.jp_props.options.source) {
-      this.image.src = this.$props.jp_props.options.source;
-    }
-  },
   methods: {
     set_source(source) {
+      this.is_initialized = true;
       if (this.loading) {
         this.waiting_source = source;
         return;
@@ -93,8 +80,13 @@ Vue.component("interactive_image", {
       this.loading = true;
       this.image.src = source;
     },
+    set_content(content) {
+      this.content = content;
+    },
   },
   props: {
-    jp_props: Object,
+    src: String,
+    events: Array,
+    cross: Boolean,
   },
-});
+};

+ 52 - 0
nicegui/elements/interactive_image.py

@@ -0,0 +1,52 @@
+from __future__ import annotations
+
+from typing import Callable, Dict, List, Optional
+
+from ..events import MouseEventArguments, handle_event
+from ..vue import register_component
+from .mixins.content_element import ContentElement
+from .mixins.source_element import SourceElement
+
+register_component('interactive_image', __file__, 'interactive_image.js')
+
+
+class InteractiveImage(SourceElement, ContentElement):
+
+    def __init__(self, source: str = '', *,
+                 on_mouse: Optional[Callable] = None, events: List[str] = ['click'], cross: bool = False) -> None:
+        """Interactive Image
+
+        Create an image with an SVG overlay that handles mouse events and yields image coordinates.
+
+        :param source: the source of the image; can be an URL or a base64 string
+        :param on_mouse: callback for mouse events (yields `type`, `image_x` and `image_y`)
+        :param events: list of JavaScript events to subscribe to (default: `['click']`)
+        :param cross: whether to show crosshairs (default: `False`)
+        """
+        super().__init__(tag='interactive_image', source=source, content='')
+        self._props['events'] = events
+        self._props['cross'] = cross
+
+        def handle_connect(_) -> None:
+            self.run_method('set_source', self.source)
+            self.run_method('set_content', self.content)
+        self.on('connect', handle_connect)
+
+        def handle_mouse(msg: Dict) -> None:
+            if on_mouse is None:
+                return
+            arguments = MouseEventArguments(
+                sender=self,
+                client=self.client,
+                type=msg['args'].get('mouse_event_type'),
+                image_x=msg['args'].get('image_x'),
+                image_y=msg['args'].get('image_y'),
+            )
+            return handle_event(on_mouse, arguments)
+        self.on('mouse', handle_mouse, ['*'])
+
+    def on_source_change(self, source: str) -> None:
+        self.run_method('set_source', source)
+
+    def on_content_change(self, content: str) -> None:
+        self.run_method('set_content', content)

+ 0 - 90
nicegui/elements/old/interactive_image.py

@@ -1,90 +0,0 @@
-from __future__ import annotations
-
-import traceback
-from typing import Any, Callable, Dict, List, Optional
-
-from justpy import WebPage
-
-from ..binding import BindableProperty, BindSourceMixin
-from ..events import MouseEventArguments, handle_event
-from ..routes import add_dependencies
-from .custom_view import CustomView
-from .element import Element
-
-add_dependencies(__file__)
-
-
-class InteractiveImageView(CustomView):
-
-    def __init__(self, source: str, on_mouse: Callable, events: List[str], cross: bool):
-        super().__init__('interactive_image', source=source, events=events, cross=cross, svg_content='')
-        self.allowed_events = ['onMouse', 'onConnect']
-        self.initialize(onMouse=on_mouse, onConnect=self.on_connect)
-        self.sockets = []
-
-    def on_connect(self, msg):
-        self.prune_sockets()
-        self.sockets.append(msg.websocket)
-
-    def prune_sockets(self):
-        page_sockets = [s for page_id in self.pages for s in WebPage.sockets.get(page_id, {}).values()]
-        self.sockets = [s for s in self.sockets if s in page_sockets]
-
-
-def _handle_source_change(sender: Element, source: str) -> None:
-    sender.view.options.source = source
-    sender.update()
-
-
-class InteractiveImage(Element, BindSourceMixin):
-    source = BindableProperty(on_change=_handle_source_change)
-
-    def __init__(self, source: str = '', *,
-                 on_mouse: Optional[Callable] = None, events: List[str] = ['click'], cross: bool = False):
-        """Interactive Image
-
-        Create an image with an SVG overlay that handles mouse events and yields image coordinates.
-
-        :param source: the source of the image; can be an URL or a base64 string
-        :param on_mouse: callback for mouse events (yields `type`, `image_x` and `image_y`)
-        :param events: list of JavaScript events to subscribe to (default: `['click']`)
-        :param cross: whether to show crosshairs (default: `False`)
-        """
-        self.mouse_handler = on_mouse
-        super().__init__(InteractiveImageView(source, self.handle_mouse, events, cross))
-
-        self.source = source
-
-    def handle_mouse(self, msg: Dict[str, Any]) -> Optional[bool]:
-        if self.mouse_handler is None:
-            return False
-        try:
-            arguments = MouseEventArguments(
-                sender=self,
-                socket=msg.get('websocket'),
-                type=msg.get('mouse_event_type'),
-                image_x=msg.get('image_x'),
-                image_y=msg.get('image_y'),
-            )
-            return handle_event(self.mouse_handler, arguments)
-        except:
-            traceback.print_exc()
-
-    async def set_source(self, source: str) -> None:
-        self.view.options.source = source
-        self.view.prune_sockets()
-        for socket in self.view.sockets:
-            await self.view.run_method(f'set_source("{source}")', socket)
-
-    @property
-    def svg_content(self) -> str:
-        return self.view.options.svg_content
-
-    @svg_content.setter
-    def svg_content(self, content: str):
-        if self.view.options.svg_content != content:
-            self.view.options.svg_content = content
-            self.update()
-
-    def set_svg_content(self, svg_content: str) -> None:
-        self.svg_content = svg_content

+ 1 - 0
nicegui/ui.py

@@ -12,6 +12,7 @@ from .elements.html import Html as html
 from .elements.icon import Icon as icon
 from .elements.image import Image as image
 from .elements.input import Input as input
+from .elements.interactive_image import InteractiveImage as interactive_image
 from .elements.joystick import Joystick as joystick
 from .elements.keyboard import Keyboard as keyboard
 from .elements.label import Label as label