浏览代码

Merge branch 'main' into fix_scene_object_duplication

Falko Schindler 2 年之前
父节点
当前提交
a186801911

+ 2 - 1
nicegui/client.py

@@ -36,7 +36,8 @@ class Client:
 
         with Element('q-layout', _client=self).props('view="HHH LpR FFF"') as self.layout:
             with Element('q-page-container'):
-                self.content = Element('div').classes('q-pa-md column items-start gap-4')
+                with Element('q-page'):
+                    self.content = Element('div').classes('q-pa-md column items-start gap-4')
 
         self.waiting_javascript_commands: Dict[str, str] = {}
 

+ 6 - 0
nicegui/elements/table.js

@@ -10,6 +10,11 @@ export default {
         ...this.options,
         onGridReady: (params) => params.api.sizeColumnsToFit(),
       };
+      for (const column of this.html_columns) {
+        if (this.gridOptions.columnDefs[column].cellRenderer === undefined) {
+          this.gridOptions.columnDefs[column].cellRenderer = (params) => (params.value ? params.value : "");
+        }
+      }
       this.grid = new agGrid.Grid(this.$el, this.gridOptions);
       this.gridOptions.api.addGlobalListener(this.handle_event);
     },
@@ -53,5 +58,6 @@ export default {
   },
   props: {
     options: Object,
+    html_columns: Array,
   },
 };

+ 4 - 2
nicegui/elements/table.py

@@ -1,4 +1,4 @@
-from typing import Dict
+from typing import Dict, List
 
 from ..dependencies import register_component
 from ..element import Element
@@ -8,16 +8,18 @@ register_component('table', __file__, 'table.js', ['lib/ag-grid-community.min.js
 
 class Table(Element):
 
-    def __init__(self, options: Dict, *, theme: str = 'balham') -> None:
+    def __init__(self, options: Dict, *, html_columns: List[int] = [], theme: str = 'balham') -> None:
         """Table
 
         An element to create a table using `AG Grid <https://www.ag-grid.com/>`_.
 
         :param options: dictionary of AG Grid options
+        :param html_columns: list of columns that should be rendered as HTML (default: `[]`)
         :param theme: AG Grid theme (default: 'balham')
         """
         super().__init__('table')
         self._props['options'] = options
+        self._props['html_columns'] = html_columns
         self._classes = [f'ag-theme-{theme}', 'w-full', 'h-64']
 
     @property

+ 5 - 6
nicegui/favicon.py

@@ -9,13 +9,12 @@ if TYPE_CHECKING:
     from .page import page
 
 
-def create_favicon_routes() -> None:
+def create_favicon_route(path: str, favicon: Optional[str]) -> None:
+    if favicon and is_remote_url(favicon):
+        return
     fallback = Path(__file__).parent / 'static' / 'favicon.ico'
-    for path, favicon in globals.favicons.items():
-        if favicon and is_remote_url(favicon):
-            continue
-        globals.app.add_route(f'{"" if path == "/" else path}/favicon.ico',
-                              lambda _, favicon=favicon or globals.favicon or fallback: FileResponse(favicon))
+    globals.app.add_route(f'{"" if path == "/" else path}/favicon.ico',
+                          lambda _: FileResponse(favicon or globals.favicon or fallback))
 
 
 def get_favicon_url(page: 'page', prefix: str) -> str:

+ 0 - 1
nicegui/globals.py

@@ -44,7 +44,6 @@ _current_socket_ids: List[str] = []
 _use_current_socket: List[bool] = [False]
 
 page_routes: Dict[Callable, str] = {}
-favicons: Dict[str, Optional[str]] = {}
 tasks: List[asyncio.tasks.Task] = []
 
 startup_handlers: List[Union[Callable, Awaitable]] = []

+ 14 - 5
nicegui/helpers.py

@@ -1,8 +1,10 @@
 import asyncio
 import functools
-from typing import Any, Awaitable, Callable, Union
+from contextlib import nullcontext
+from typing import Any, Awaitable, Callable, Optional, Union
 
 from . import globals
+from .client import Client
 from .task_logger import create_task
 
 
@@ -12,13 +14,20 @@ def is_coroutine(object: Any) -> bool:
     return asyncio.iscoroutinefunction(object)
 
 
-def safe_invoke(func: Union[Callable, Awaitable]) -> None:
+def safe_invoke(func: Union[Callable, Awaitable], client: Optional[Client] = None) -> None:
     try:
         if isinstance(func, Awaitable):
-            create_task(func)
+            async def func_with_client():
+                with client or nullcontext():
+                    await func
+            create_task(func_with_client())
         else:
-            result = func()
+            with client or nullcontext():
+                result = func()
             if isinstance(result, Awaitable):
-                create_task(result)
+                async def result_with_client():
+                    with client or nullcontext():
+                        await result
+                create_task(result_with_client())
     except:
         globals.log.exception(f'could not invoke {func}')

+ 4 - 7
nicegui/nicegui.py

@@ -15,7 +15,6 @@ from .client import Client
 from .dependencies import js_components, js_dependencies
 from .element import Element
 from .error import error_content
-from .favicon import create_favicon_routes
 from .helpers import safe_invoke
 from .page import page
 from .task_logger import create_task
@@ -50,7 +49,6 @@ def get_components(name: str):
 def handle_startup(with_welcome_message: bool = True) -> None:
     globals.state = globals.State.STARTING
     globals.loop = asyncio.get_running_loop()
-    create_favicon_routes()
     for t in globals.startup_handlers:
         safe_invoke(t)
     create_task(binding.loop())
@@ -94,9 +92,9 @@ async def handle_handshake(sid: str) -> bool:
         return False
     client.environ = sio.get_environ(sid)
     sio.enter_room(sid, client.id)
-    with client, globals.socketio_id(sid):
+    with globals.socketio_id(sid):
         for t in client.connect_handlers:
-            safe_invoke(t)
+            safe_invoke(t, client)
     return True
 
 
@@ -107,9 +105,8 @@ async def handle_disconnect(sid: str) -> None:
         return
     if not client.shared:
         delete_client(client.id)
-    with client:
-        for t in client.disconnect_handlers:
-            safe_invoke(t)
+    for t in client.disconnect_handlers:
+        safe_invoke(t, client)
 
 
 @sio.on('event')

+ 2 - 1
nicegui/page.py

@@ -8,6 +8,7 @@ from fastapi import Request, Response
 from . import globals
 from .async_updater import AsyncUpdater
 from .client import Client
+from .favicon import create_favicon_route
 from .task_logger import create_task
 
 
@@ -36,7 +37,7 @@ class page:
         self.dark = dark
         self.response_timeout = response_timeout
 
-        globals.favicons[self.path] = favicon
+        create_favicon_route(self.path, favicon)
 
     def resolve_title(self) -> str:
         return self.title if self.title is not None else globals.title

+ 7 - 3
nicegui/page_layout.py

@@ -1,10 +1,12 @@
+from . import globals
 from .element import Element
 
 
 class Header(Element):
 
     def __init__(self, fixed: bool = True) -> None:
-        super().__init__('q-header')
+        with globals.get_client().layout:
+            super().__init__('q-header')
         self.classes('q-pa-md row items-start gap-4')
         code = list(self.client.layout._props['view'])
         code[1] = 'H' if fixed else 'h'
@@ -15,7 +17,8 @@ class Drawer(Element):
 
     def __init__(self, side: str, *, fixed: bool = True, top_corner: bool = False, bottom_corner: bool = False) -> None:
         assert side in ['left', 'right']
-        super().__init__('q-drawer')
+        with globals.get_client().layout:
+            super().__init__('q-drawer')
         self._props['show-if-above'] = True
         self._props['side'] = side
         self._classes = ['q-pa-md']
@@ -41,7 +44,8 @@ class RightDrawer(Drawer):
 class Footer(Element):
 
     def __init__(self, fixed: bool = True) -> None:
-        super().__init__('q-footer')
+        with globals.get_client().layout:
+            super().__init__('q-footer')
         self.classes('q-pa-md row items-start gap-4')
         code = list(self.client.layout._props['view'])
         code[9] = 'F' if fixed else 'f'

+ 10 - 0
tests/test_lifecycle.py

@@ -9,3 +9,13 @@ def test_adding_elements_during_onconnect(screen: Screen):
 
     screen.open('/')
     screen.should_contain('Label 2')
+
+
+def test_async_connect_handler(screen: Screen):
+    async def run_js():
+        result.text = await ui.run_javascript('41 + 1')
+    result = ui.label()
+    ui.on_connect(run_js)
+
+    screen.open('/')
+    screen.should_contain('42')

+ 12 - 0
tests/test_table.py

@@ -50,3 +50,15 @@ def test_click_cell(screen: Screen):
     screen.open('/')
     screen.click('Alice')
     screen.should_contain('Alice has been clicked!')
+
+
+def test_html_columns(screen: Screen):
+    ui.table({
+        'columnDefs': [{'field': 'name'}, {'field': 'age'}],
+        'rowData': [{'name': '<span class="text-bold">Alice</span>', 'age': 18}],
+    }, html_columns=[0])
+
+    screen.open('/')
+    screen.should_contain('Alice')
+    screen.should_not_contain('<span')
+    assert 'text-bold' in screen.find('Alice').get_attribute('class')