Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feature/per_session_data' into f…
Browse files Browse the repository at this point in the history
…eature/per_session_data
  • Loading branch information
Alyxion committed Apr 6, 2024
2 parents 9dd4227 + df3335a commit ca99fcf
Show file tree
Hide file tree
Showing 15 changed files with 60 additions and 50 deletions.
21 changes: 10 additions & 11 deletions nicegui/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from .dependencies import generate_resources
from .element import Element
from .favicon import get_favicon_url
from .javascript_request import JavaScriptRequest
from .logging import log
from .observables import ObservableDict
from .outbox import Outbox
Expand Down Expand Up @@ -67,8 +68,6 @@ def __init__(self, page: page, *, shared: bool = False) -> None:
with Element('q-page'):
self.content = Element('div').classes('nicegui-content')

self.waiting_javascript_commands: Dict[str, Any] = {}

self.title: Optional[str] = None

self._head_html = ''
Expand Down Expand Up @@ -181,7 +180,9 @@ async def disconnected(self, check_interval: float = 0.1) -> None:

def run_javascript(self, code: str, *,
respond: Optional[bool] = None, # DEPRECATED
timeout: float = 1.0, check_interval: float = 0.01) -> AwaitableResponse:
timeout: float = 1.0,
check_interval: float = 0.01, # DEPRECATED
) -> AwaitableResponse:
"""Execute JavaScript on the client.
The client connection must be established before this method is called.
Expand All @@ -192,7 +193,6 @@ def run_javascript(self, code: str, *,
:param code: JavaScript code to run
:param timeout: timeout in seconds (default: `1.0`)
:param check_interval: interval in seconds to check for a response (default: `0.01`)
:return: AwaitableResponse that can be awaited to get the result of the JavaScript code
"""
Expand All @@ -204,6 +204,10 @@ def run_javascript(self, code: str, *,
raise ValueError('The "respond" argument of run_javascript() has been removed. '
'Now the method always returns an AwaitableResponse that can be awaited. '
'Please remove the "respond=False" argument and call the method without awaiting.')
if check_interval != 0.01:
log.warning('The "check_interval" argument of run_javascript() and similar methods has been removed. '
'Now the method automatically returns when receiving a response without checking regularly in an interval. '
'Please remove the "check_interval" argument.')

request_id = str(uuid.uuid4())
target_id = self._temporary_socket_id or self.id
Expand All @@ -213,12 +217,7 @@ def send_and_forget():

async def send_and_wait():
self.outbox.enqueue_message('run_javascript', {'code': code, 'request_id': request_id}, target_id)
deadline = time.time() + timeout
while request_id not in self.waiting_javascript_commands:
if time.time() > deadline:
raise TimeoutError(f'JavaScript did not respond within {timeout:.1f} s')
await asyncio.sleep(check_interval)
return self.waiting_javascript_commands.pop(request_id)
return await JavaScriptRequest(request_id, timeout=timeout)

return AwaitableResponse(send_and_forget, send_and_wait)

Expand Down Expand Up @@ -277,7 +276,7 @@ def handle_event(self, msg: Dict) -> None:

def handle_javascript_response(self, msg: Dict) -> None:
"""Store the result of a JavaScript command."""
self.waiting_javascript_commands[msg['request_id']] = msg['result']
JavaScriptRequest.resolve(msg['request_id'], msg['result'])

def safe_invoke(self, func: Union[Callable[..., Any], Awaitable]) -> None:
"""Invoke the potentially async function in the client context and catch any exceptions."""
Expand Down
1 change: 0 additions & 1 deletion nicegui/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,6 @@ def run_method(self, name: str, *args: Any, timeout: float = 1, check_interval:
:param name: name of the method
:param args: arguments to pass to the method
:param timeout: maximum time to wait for a response (default: 1 second)
:param check_interval: time between checks for a response (default: 0.01 seconds)
"""
if not core.loop:
return NullResponse()
Expand Down
5 changes: 1 addition & 4 deletions nicegui/elements/aggrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ def run_grid_method(self, name: str, *args, timeout: float = 1, check_interval:
:param name: name of the method
:param args: arguments to pass to the method
:param timeout: timeout in seconds (default: 1 second)
:param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
:return: AwaitableResponse that can be awaited to get the result of the method call
"""
Expand All @@ -127,7 +126,6 @@ def run_column_method(self, name: str, *args,
:param name: name of the method
:param args: arguments to pass to the method
:param timeout: timeout in seconds (default: 1 second)
:param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
:return: AwaitableResponse that can be awaited to get the result of the method call
"""
Expand All @@ -146,7 +144,6 @@ def run_row_method(self, row_id: str, name: str, *args,
:param name: name of the method
:param args: arguments to pass to the method
:param timeout: timeout in seconds (default: 1 second)
:param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
:return: AwaitableResponse that can be awaited to get the result of the method call
"""
Expand Down Expand Up @@ -185,7 +182,7 @@ async def get_client_data(self, *, timeout: float = 1, check_interval: float = 0
This does not happen when the cell loses focus, unless ``stopEditingWhenCellsLoseFocus: True`` is set.
:param timeout: timeout in seconds (default: 1 second)
:param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
:return: list of row data
"""
result = await self.client.run_javascript(f'''
Expand Down
1 change: 0 additions & 1 deletion nicegui/elements/echart.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ def run_chart_method(self, name: str, *args, timeout: float = 1,
:param name: name of the method (a prefix ":" indicates that the arguments are JavaScript expressions)
:param args: arguments to pass to the method (Python objects or JavaScript expressions)
:param timeout: timeout in seconds (default: 1 second)
:param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
:return: AwaitableResponse that can be awaited to get the result of the method call
"""
Expand Down
1 change: 0 additions & 1 deletion nicegui/elements/json_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ def run_editor_method(self, name: str, *args, timeout: float = 1,
:param name: name of the method (a prefix ":" indicates that the arguments are JavaScript expressions)
:param args: arguments to pass to the method (Python objects or JavaScript expressions)
:param timeout: timeout in seconds (default: 1 second)
:param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
:return: AwaitableResponse that can be awaited to get the result of the method call
"""
Expand Down
9 changes: 3 additions & 6 deletions nicegui/elements/leaflet.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,10 @@ export default {
clearInterval(connectInterval);
}, 100);
},
updated() {
this.map?.setView(this.center, this.zoom);
},
methods: {
setCenter(center) {
this.map.panTo(center);
},
setZoom(zoom) {
this.map.setZoom(zoom);
},
add_layer(layer, id) {
const l = L[layer.type](...layer.args);
l.id = id;
Expand Down
6 changes: 2 additions & 4 deletions nicegui/elements/leaflet.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,14 @@ def set_center(self, center: Tuple[float, float]) -> None:
if self._props['center'] == center:
return
self._props['center'] = center
self.run_method('setCenter', center)
self.update()

def set_zoom(self, zoom: int) -> None:
"""Set the zoom level of the map."""
if self._props['zoom'] == zoom:
return
self._props['zoom'] = zoom
self.run_method('setZoom', zoom)
self.update()

def remove_layer(self, layer: Layer) -> None:
"""Remove a layer from the map."""
Expand All @@ -128,7 +128,6 @@ def run_map_method(self, name: str, *args, timeout: float = 1, check_interval: f
:param name: name of the method (a prefix ":" indicates that the arguments are JavaScript expressions)
:param args: arguments to pass to the method
:param timeout: timeout in seconds (default: 1 second)
:param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
:return: AwaitableResponse that can be awaited to get the result of the method call
"""
Expand All @@ -144,7 +143,6 @@ def run_layer_method(self, layer_id: str, name: str, *args, timeout: float = 1,
:param name: name of the method (a prefix ":" indicates that the arguments are JavaScript expressions)
:param args: arguments to pass to the method
:param timeout: timeout in seconds (default: 1 second)
:param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
:return: AwaitableResponse that can be awaited to get the result of the method call
"""
Expand Down
1 change: 0 additions & 1 deletion nicegui/elements/leaflet_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ def run_method(self, name: str, *args: Any, timeout: float = 1, check_interval:
:param name: name of the method (a prefix ":" indicates that the arguments are JavaScript expressions)
:param args: arguments to pass to the method
:param timeout: timeout in seconds (default: 1 second)
:param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
:return: AwaitableResponse that can be awaited to get the result of the method call
"""
Expand Down
8 changes: 5 additions & 3 deletions nicegui/elements/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ export default {
this.ensure_codehilite_css();
if (this.use_mermaid) {
this.mermaid = (await import("mermaid")).default;
this.update(this.$el.innerHTML);
this.renderMermaid();
}
},
data() {
return {
mermaid: null,
};
},
updated() {
this.renderMermaid();
},
methods: {
update(content) {
this.$el.innerHTML = content;
renderMermaid() {
this.$el.querySelectorAll(".mermaid-pre").forEach(async (pre, i) => {
await this.mermaid.run({ nodes: [pre.children[0]] });
});
Expand Down
2 changes: 1 addition & 1 deletion nicegui/elements/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def _handle_content_change(self, content: str) -> None:
html = prepare_content(content, extras=' '.join(self.extras))
if self._props.get('innerHTML') != html:
self._props['innerHTML'] = html
self.run_method('update', html)
self.update()


@lru_cache(maxsize=int(os.environ.get('MARKDOWN_CONTENT_CACHE_SIZE', '1000')))
Expand Down
2 changes: 1 addition & 1 deletion nicegui/elements/restructured_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def _handle_content_change(self, content: str) -> None:
html = prepare_content(content)
if self._props.get('innerHTML') != html:
self._props['innerHTML'] = html
self.run_method('update', html)
self.update()


@lru_cache(maxsize=int(os.environ.get('RST_CONTENT_CACHE_SIZE', '1000')))
Expand Down
15 changes: 2 additions & 13 deletions nicegui/functions/javascript.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

from .. import context
from ..awaitable_response import AwaitableResponse
from ..logging import log


def run_javascript(code: str, *,
respond: Optional[bool] = None, # DEPRECATED
respond: Optional[bool] = None,
timeout: float = 1.0, check_interval: float = 0.01) -> AwaitableResponse:
"""Run JavaScript
Expand All @@ -19,17 +18,7 @@ def run_javascript(code: str, *,
:param code: JavaScript code to run
:param timeout: timeout in seconds (default: `1.0`)
:param check_interval: interval in seconds to check for a response (default: `0.01`)
:return: AwaitableResponse that can be awaited to get the result of the JavaScript code
"""
if respond is True:
log.warning('The "respond" argument of run_javascript() has been removed. '
'Now the function always returns an AwaitableResponse that can be awaited. '
'Please remove the "respond=True" argument.')
if respond is False:
raise ValueError('The "respond" argument of run_javascript() has been removed. '
'Now the function always returns an AwaitableResponse that can be awaited. '
'Please remove the "respond=False" argument and call the function without awaiting.')

return context.get_client().run_javascript(code, timeout=timeout, check_interval=check_interval)
return context.get_client().run_javascript(code, respond=respond, timeout=timeout, check_interval=check_interval)
32 changes: 32 additions & 0 deletions nicegui/javascript_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from __future__ import annotations

import asyncio
from typing import Any, Dict


class JavaScriptRequest:
_instances: Dict[str, JavaScriptRequest] = {}

def __init__(self, request_id: str, *, timeout: float) -> None:
self.request_id = request_id
self._instances[request_id] = self
self.timeout = timeout
self._event = asyncio.Event()
self._result: Any = None

@classmethod
def resolve(cls, request_id: str, result: Any) -> None:
"""Store the result of a JavaScript request and unblock the awaiter."""
request = cls._instances[request_id]
request._result = result # pylint: disable=protected-access
request._event.set() # pylint: disable=protected-access

def __await__(self) -> Any:
try:
yield from asyncio.wait_for(self._event.wait(), self.timeout).__await__()
except asyncio.TimeoutError as e:
raise TimeoutError(f'JavaScript did not respond within {self.timeout:.1f} s') from e
else:
return self._result
finally:
self._instances.pop(self.request_id)
2 changes: 1 addition & 1 deletion nicegui/native/native_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def window_method_executor() -> None:
else:
log.error(f'window.{method_name} is not callable')
except queue.Empty:
time.sleep(0.01)
time.sleep(0.016) # NOTE: avoid issue https://github.com/zauberzeug/nicegui/issues/2482 on Windows
except Exception:
log.exception(f'error in window.{method_name}')

Expand Down
4 changes: 2 additions & 2 deletions website/documentation/content/section_audiovisual_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ def captions_and_overlays_demo():
with ui.image('https://cdn.stocksnap.io/img-thumbs/960w/airplane-sky_DYPWDEEILG.jpg'):
ui.html('''
<svg viewBox="0 0 960 638" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<circle cx="445" cy="300" r="100" fill="none" stroke="red" stroke-width="20" />
<circle cx="445" cy="300" r="100" fill="none" stroke="red" stroke-width="10" />
</svg>
''').classes('bg-transparent')
''').classes('w-full bg-transparent')


doc.intro(interactive_image_documentation)
Expand Down

0 comments on commit ca99fcf

Please sign in to comment.