Переглянути джерело

#826 use and improve ui.refreshable

Falko Schindler 2 роки тому
батько
коміт
d0ba5c3a5a
2 змінених файлів з 48 додано та 25 видалено
  1. 14 17
      examples/chat_app/main.py
  2. 34 8
      nicegui/functions/refreshable.py

+ 14 - 17
examples/chat_app/main.py

@@ -1,31 +1,29 @@
 #!/usr/bin/env python3
-import asyncio
 from datetime import datetime
 from typing import List, Tuple
 
 from nicegui import Client, ui
 
 messages: List[Tuple[str, str]] = []
-contents: List[Tuple[ui.column, ui.input]] = []
 
 
-async def update(content: ui.column, name_input: ui.input) -> None:
-    content.clear()
-    with content:  # use the context of each client to update their ui
-        for name, text in messages:
-            sent = name == name_input.value
-            avatar = f'https://robohash.org/{name or "anonymous"}'
-            stamp = datetime.utcnow().strftime('%X')
-            ui.chat_message(text=text, name=name, stamp=stamp, avatar=avatar, sent=sent).classes('w-full')
-        await ui.run_javascript('window.scrollTo(0, document.body.scrollHeight)', respond=False)
+@ui.refreshable
+async def chat_messages(name_input: ui.input) -> None:
+    for name, text in messages:
+        ui.chat_message(text=text,
+                        name=name,
+                        stamp=datetime.utcnow().strftime('%X'),
+                        avatar=f'https://robohash.org/{name or "anonymous"}?bgset=bg2',
+                        sent=name == name_input.value)
+    await ui.run_javascript('window.scrollTo(0, document.body.scrollHeight)', respond=False)
 
 
 @ui.page('/')
 async def main(client: Client):
-    async def send() -> None:
+    def send() -> None:
         messages.append((name.value, text.value))
         text.value = ''
-        await asyncio.gather(*[update(content, name) for content, name in contents])  # run updates concurrently
+        chat_messages.refresh()
 
     anchor_style = r'a:link, a:visited {color: inherit !important; text-decoration: none; font-weight: 500}'
     ui.add_head_html(f'<style>{anchor_style}</style>')
@@ -37,9 +35,8 @@ async def main(client: Client):
         ui.markdown('simple chat app built with [NiceGUI](https://nicegui.io)') \
             .classes('text-xs self-end mr-8 m-[-1em] text-primary')
 
-    await client.connected()  # update(...) uses run_javascript which is only possible after connecting
-    contents.append((ui.column().classes('w-full max-w-2xl mx-auto'), name))  # save ui context for updates
-    await update(*contents[-1])  # ensure all messages are shown after connecting
-
+    await client.connected()  # chat_messages(...) uses run_javascript which is only possible after connecting
+    with ui.column().classes('w-full max-w-2xl mx-auto items-stretch'):
+        await chat_messages(name_input=name)
 
 ui.run()

+ 34 - 8
nicegui/functions/refreshable.py

@@ -1,9 +1,11 @@
-from typing import Callable, List
+from typing import Any, Callable, Dict, List, Tuple
 
 from typing_extensions import Self
 
+from .. import background_tasks, globals
 from ..dependencies import register_component
 from ..element import Element
+from ..helpers import is_coroutine
 
 register_component('refreshable', __file__, 'refreshable.js')
 
@@ -18,19 +20,43 @@ class refreshable:
         """
         self.func = func
         self.instance = None
-        self.containers: List[Element] = []
+        self.containers: List[Tuple[Element, List[Any], Dict[str, Any]]] = []
 
     def __get__(self, instance, _) -> Self:
         self.instance = instance
         return self
 
-    def __call__(self) -> None:
+    def __call__(self, *args, **kwargs) -> None:
+        self.prune()
         with Element('refreshable') as container:
-            self.func() if self.instance is None else self.func(self.instance)
-        self.containers.append(container)
+            self.containers.append((container, args, kwargs))
+            return self.func(*args, **kwargs) if self.instance is None else self.func(self.instance, *args, **kwargs)
 
     def refresh(self) -> None:
-        for container in self.containers:
+        self.prune()
+        for container, args, kwargs in self.containers:
             container.clear()
-            with container:
-                self.func() if self.instance is None else self.func(self.instance)
+            if is_coroutine(self.func):
+                async def wait_for_result(container: Element, args, kwargs):
+                    with container:
+                        if self.instance is None:
+                            await self.func(*args, **kwargs)
+                        else:
+                            await self.func(self.instance, *args, **kwargs)
+                if globals.loop and globals.loop.is_running():
+                    background_tasks.create(wait_for_result(container=container, args=args, kwargs=kwargs))
+                else:
+                    globals.app.on_startup(wait_for_result(container=container, args=args, kwargs=kwargs))
+            else:
+                with container:
+                    if self.instance is None:
+                        self.func(*args, **kwargs)
+                    else:
+                        self.func(self.instance, *args, **kwargs)
+
+    def prune(self) -> None:
+        self.containers = [
+            (container, args, kwargs)
+            for container, args, kwargs in self.containers
+            if container.client.id in globals.clients
+        ]