Browse Source

#826 use and improve ui.refreshable

Falko Schindler 2 years ago
parent
commit
d0ba5c3a5a
2 changed files with 48 additions and 25 deletions
  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
 #!/usr/bin/env python3
-import asyncio
 from datetime import datetime
 from datetime import datetime
 from typing import List, Tuple
 from typing import List, Tuple
 
 
 from nicegui import Client, ui
 from nicegui import Client, ui
 
 
 messages: List[Tuple[str, str]] = []
 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('/')
 @ui.page('/')
 async def main(client: Client):
 async def main(client: Client):
-    async def send() -> None:
+    def send() -> None:
         messages.append((name.value, text.value))
         messages.append((name.value, text.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}'
     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>')
     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)') \
         ui.markdown('simple chat app built with [NiceGUI](https://nicegui.io)') \
             .classes('text-xs self-end mr-8 m-[-1em] text-primary')
             .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()
 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 typing_extensions import Self
 
 
+from .. import background_tasks, globals
 from ..dependencies import register_component
 from ..dependencies import register_component
 from ..element import Element
 from ..element import Element
+from ..helpers import is_coroutine
 
 
 register_component('refreshable', __file__, 'refreshable.js')
 register_component('refreshable', __file__, 'refreshable.js')
 
 
@@ -18,19 +20,43 @@ class refreshable:
         """
         """
         self.func = func
         self.func = func
         self.instance = None
         self.instance = None
-        self.containers: List[Element] = []
+        self.containers: List[Tuple[Element, List[Any], Dict[str, Any]]] = []
 
 
     def __get__(self, instance, _) -> Self:
     def __get__(self, instance, _) -> Self:
         self.instance = instance
         self.instance = instance
         return self
         return self
 
 
-    def __call__(self) -> None:
+    def __call__(self, *args, **kwargs) -> None:
+        self.prune()
         with Element('refreshable') as container:
         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:
     def refresh(self) -> None:
-        for container in self.containers:
+        self.prune()
+        for container, args, kwargs in self.containers:
             container.clear()
             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
+        ]