1
0
Falko Schindler 2 жил өмнө
parent
commit
7fdf1f2196

+ 3 - 5
api_docs_and_examples.py

@@ -791,17 +791,15 @@ It also enables you to identify sessions over [longer time spans by configuring
 
 With `ui.run_javascript()` you can run arbitrary JavaScript code on a page that is executed in the browser.
 The asynchronous function will return after the command(s) are executed.
-The result of the execution is returned as a dictionary containing the response string per websocket.
 You can also set `respond=False` to send a command without waiting for a response.
-''')
+''', skip=False)
     def javascript_example():
         async def alert():
             await ui.run_javascript('alert("Hello!")', respond=False)
 
         async def get_date():
-            response = await ui.run_javascript('Date()')
-            for socket, time in response.items():
-                ui.notify(f'Browser time on host {socket.client.host}: {time}')
+            time = await ui.run_javascript('Date()')
+            ui.notify(f'Browser time: {time}')
 
         ui.button('fire and forget', on_click=alert)
         ui.button('receive result', on_click=get_date)

+ 21 - 0
nicegui/client.py

@@ -1,6 +1,7 @@
 import asyncio
 import json
 import time
+import uuid
 from pathlib import Path
 from typing import Any, Dict, List, Optional
 
@@ -9,6 +10,7 @@ from fastapi.responses import HTMLResponse
 from . import globals, ui, vue
 from .element import Element
 from .slot import Slot
+from .task_logger import create_task
 
 TEMPLATE = (Path(__file__).parent / 'templates' / 'index.html').read_text()
 
@@ -30,6 +32,8 @@ class Client:
         self.content = ui.column().classes('q-ma-md')
         globals.client_stack.pop()
 
+        self.waiting_javascript_commands: Dict[str, str] = {}
+
     @property
     def ip(self) -> Optional[str]:
         return self.environ.get('REMOTE_ADDR') if self.environ else None
@@ -63,3 +67,20 @@ class Client:
                 raise TimeoutError(f'No handshake after {timeout} seconds')
             await asyncio.sleep(check_interval)
         self.is_waiting_for_handshake = False
+
+    async def run_javascript(self, code: str, *,
+                             respond: bool = True, timeout: float = 1.0, check_interval: float = 0.01) -> Optional[str]:
+        request_id = str(uuid.uuid4())
+        command = {
+            'code': code,
+            'request_id': request_id if respond else None,
+        }
+        create_task(globals.sio.emit('run_javascript', command, room=str(self.id)))
+        if not respond:
+            return
+        deadline = time.time() + timeout
+        while request_id not in self.waiting_javascript_commands:
+            if time.time() > deadline:
+                raise TimeoutError('JavaScript did not respond in time')
+            await asyncio.sleep(check_interval)
+        return self.waiting_javascript_commands.pop(request_id)

+ 9 - 0
nicegui/javascript.py

@@ -0,0 +1,9 @@
+from typing import Optional
+
+from . import globals
+
+
+async def run_javascript(code: str, *,
+                         respond: bool = True, timeout: float = 1.0, check_interval: float = 0.01) -> Optional[str]:
+    client = globals.client_stack[-1]
+    return await client.run_javascript(code, respond=respond, timeout=timeout, check_interval=check_interval)

+ 5 - 0
nicegui/nicegui.py

@@ -71,6 +71,11 @@ def handle_event(sid: str, msg: Dict) -> None:
             sender.handle_event(msg)
 
 
+@sio.on('javascript_response')
+def handle_event(sid: str, msg: Dict) -> None:
+    get_client(sid).waiting_javascript_commands[msg['request_id']] = msg['result']
+
+
 def get_client(sid: str) -> Client:
     query_bytes: bytearray = sio.get_environ(sid)['asgi.scope']['query_string']
     query = urllib.parse.parse_qs(query_bytes.decode())

+ 8 - 0
nicegui/templates/index.html

@@ -50,6 +50,13 @@
         return Vue.h(Vue.resolveComponent(element.tag), props, slots);
       }
 
+      function run_javascript(code, request_id) {
+        const result = eval(code);
+        if (request_id) {
+          window.socket.emit("javascript_response", {request_id, result});
+        }
+      }
+
       const app = Vue.createApp({
         data() {
           return {
@@ -66,6 +73,7 @@
             Object.entries(msg.elements).forEach(([id, element]) => this.elements[element.id] = element);
           });
           window.socket.on("run_method", (msg) => this.$refs['r' + msg.id][msg.name](...msg.args));
+          window.socket.on("run_javascript", (msg) => run_javascript(msg['code'], msg['request_id']));
           window.socket.on("notify", (msg) => Quasar.Notify.create(msg));
           window.socket.on("disconnect", () => window.location.reload());
         },

+ 1 - 0
nicegui/ui.py

@@ -40,6 +40,7 @@ from .elements.toggle import Toggle as toggle
 from .elements.tooltip import Tooltip as tooltip
 from .elements.tree import Tree as tree
 from .elements.upload import Upload as upload
+from .javascript import run_javascript
 from .notify import notify
 from .page import page
 from .run import run