Sfoglia il codice sorgente

Merge pull request #218 from zauberzeug/fix_scene_object_duplication

Fix object duplication in ui.scene when using without page decorator
Falko Schindler 2 anni fa
parent
commit
92fb41871d

+ 6 - 2
nicegui/client.py

@@ -53,6 +53,10 @@ class Client:
     def ip(self) -> Optional[str]:
         return self.environ.get('REMOTE_ADDR') if self.environ else None
 
+    @property
+    def room(self) -> str:
+        return globals._current_socket_ids[-1] if globals._use_current_socket[-1] else self.id
+
     @property
     def has_socket_connection(self) -> bool:
         return self.environ is not None
@@ -99,7 +103,7 @@ class Client:
             'code': code,
             'request_id': request_id if respond else None,
         }
-        create_task(globals.sio.emit('run_javascript', command, room=self.id))
+        create_task(globals.sio.emit('run_javascript', command, room=self.room))
         if not respond:
             return None
         deadline = time.time() + timeout
@@ -111,4 +115,4 @@ class Client:
 
     def open(self, target: Union[Callable, str]) -> None:
         path = target if isinstance(target, str) else globals.page_routes[target]
-        create_task(globals.sio.emit('open', path, room=self.id))
+        create_task(globals.sio.emit('open', path, room=self.room))

+ 2 - 2
nicegui/element.py

@@ -168,13 +168,13 @@ class Element(ABC, Visibility):
             return
         ids = self.collect_descendant_ids()
         elements = {id: self.client.elements[id].to_dict() for id in ids}
-        create_task(globals.sio.emit('update', {'elements': elements}, room=self.client.id))
+        create_task(globals.sio.emit('update', {'elements': elements}, room=self.client.room))
 
     def run_method(self, name: str, *args: Any) -> None:
         if not globals.loop:
             return
         data = {'id': self.id, 'name': name, 'args': args}
-        create_task(globals.sio.emit('run_method', data, room=self.client.id))
+        create_task(globals.sio.emit('run_method', data, room=self.client.room))
 
     def clear(self) -> None:
         descendants = [self.client.elements[id] for id in self.collect_descendant_ids()[1:]]

+ 0 - 10
nicegui/elements/scene.js

@@ -134,19 +134,9 @@ export default {
 
     this.texture_loader = new THREE.TextureLoader();
     this.stl_loader = new THREE.STLLoader();
-
-    this.is_initialized = false;
-    const sendConnectEvent = () => {
-      if (!this.is_initialized) this.$emit("connect");
-      else clearInterval(connectInterval);
-    };
-    const connectInterval = setInterval(sendConnectEvent, 100);
   },
 
   methods: {
-    init() {
-      this.is_initialized = true;
-    },
     create(type, id, parent_id, ...args) {
       let mesh;
       if (type == "group") {

+ 8 - 6
nicegui/elements/scene.py

@@ -1,9 +1,11 @@
 from dataclasses import dataclass
 from typing import Callable, Dict, List, Optional, Union
 
+from .. import globals
 from ..dependencies import register_component
 from ..element import Element
 from ..events import SceneClickEventArguments, SceneClickHit, handle_event
+from ..functions.lifecycle import on_connect
 from .scene_object3d import Object3D
 from .scene_objects import Scene as SceneObject
 
@@ -70,14 +72,14 @@ class Scene(Element):
         self.stack: List[Union[Object3D, SceneObject]] = [SceneObject()]
         self.camera: SceneCamera = SceneCamera()
         self.on_click = on_click
-        self.on('connect', self.handle_connect)
         self.on('click3d', self.handle_click)
+        on_connect(self.handle_connect)
 
-    def handle_connect(self, _) -> None:
-        self.run_method('init')
-        self.move_camera(duration=0)
-        for object in self.objects.values():
-            object.send()
+    def handle_connect(self) -> None:
+        with globals.current_socket():
+            self.move_camera(duration=0)
+            for object in self.objects.values():
+                object.send()
 
     def handle_click(self, msg: Dict) -> None:
         arguments = SceneClickEventArguments(

+ 1 - 1
nicegui/functions/notify.py

@@ -24,4 +24,4 @@ def notify(message: str, *,
     Note: You can pass additional keyword arguments according to `Quasar's Notify API <https://quasar.dev/quasar-plugins/notify#notify-api>`_.
     """
     options = {key: value for key, value in locals().items() if not key.startswith('_') and value is not None}
-    create_task(globals.sio.emit('notify', options, room=globals.get_client().id))
+    create_task(globals.sio.emit('notify', options, room=globals.get_client().room))

+ 17 - 0
nicegui/globals.py

@@ -1,5 +1,6 @@
 import asyncio
 import logging
+from contextlib import contextmanager
 from enum import Enum
 from typing import TYPE_CHECKING, Awaitable, Callable, Dict, List, Optional, Union
 
@@ -39,6 +40,8 @@ socket_io_js_extra_headers: Dict = {}
 slot_stacks: Dict[int, List['Slot']] = {}
 clients: Dict[str, 'Client'] = {}
 index_client: 'Client'
+_current_socket_ids: List[str] = []
+_use_current_socket: List[bool] = [False]
 
 page_routes: Dict[Callable, str] = {}
 tasks: List[asyncio.tasks.Task] = []
@@ -73,3 +76,17 @@ def get_slot() -> 'Slot':
 
 def get_client() -> 'Client':
     return get_slot().parent.client
+
+
+@contextmanager
+def socketio_id(id: str) -> None:
+    _current_socket_ids.append(id)
+    yield
+    _current_socket_ids.pop()
+
+
+@contextmanager
+def current_socket() -> None:
+    _use_current_socket.append(True)
+    yield
+    _use_current_socket.pop()

+ 3 - 2
nicegui/nicegui.py

@@ -92,8 +92,9 @@ async def handle_handshake(sid: str) -> bool:
         return False
     client.environ = sio.get_environ(sid)
     sio.enter_room(sid, client.id)
-    for t in client.connect_handlers:
-        safe_invoke(t, client)
+    with globals.socketio_id(sid):
+        for t in client.connect_handlers:
+            safe_invoke(t, client)
     return True
 
 

+ 9 - 0
tests/screen.py

@@ -70,6 +70,15 @@ class Screen:
         if self.is_open:
             self.selenium.close()
 
+    def switch_to(self, tab_id: int) -> None:
+        window_count = len(self.selenium.window_handles)
+        if tab_id > window_count:
+            raise IndexError(f'Could not go to or create tab {tab_id}, there are only {window_count} tabs')
+        elif tab_id == window_count:
+            self.selenium.switch_to.new_window('tab')
+        else:
+            self.selenium.switch_to.window(self.selenium.window_handles[tab_id])
+
     def should_contain(self, text: str) -> None:
         assert self.selenium.title == text or self.find(text), f'could not find "{text}"'
 

+ 14 - 0
tests/test_scene.py

@@ -25,3 +25,17 @@ def test_moving_sphere_with_timer(screen: Screen):
 
     screen.wait(0.2)
     assert position() > 0
+
+
+def test_no_object_duplication_on_index_client(screen: Screen):
+    with ui.scene() as scene:
+        sphere = scene.sphere().move(0, -4, 0)
+        ui.timer(0.1, lambda: sphere.move(0, sphere.y + 0.5, 0))
+
+    screen.open('/')
+    screen.wait(0.4)
+    screen.switch_to(1)
+    screen.open('/')
+    screen.switch_to(0)
+    screen.wait(0.2)
+    assert screen.selenium.execute_script(f'return scene_{scene.id}.children.length') == 5