This commit is contained in:
2025-09-01 06:43:11 +02:00
parent bde3fc0df9
commit 45eb2b8bc5
38 changed files with 3424 additions and 915 deletions

View File

@@ -1,45 +1,119 @@
from abc import ABC, abstractmethod
from typing import Self, Any, Optional
from abc import ABC
from typing import Self, Type, Any, Optional
from nicegui import ui
import asyncio
class AsyncElement(ui.element, ABC):
class AsyncElement(ABC):
"""Base class for UI elements with async initialization"""
dialog: ui.dialog | None
def __init__(self, tag: str = 'div', dialog: Optional[ui.dialog] = None) -> None:
super().__init__(tag)
self.dialog = dialog
def __init__(self, element_type: Type = ui.column, *element_args, **element_kwargs) -> None:
self.element = element_type(*element_args, **element_kwargs)
@abstractmethod
async def build(self, *args, **kwargs) -> None:
"""Build/setup the element - must be implemented by subclasses"""
pass
@classmethod
async def create(cls, *args, **kwargs) -> Self:
async def create(cls, element_type: Type = ui.column, *args, **kwargs) -> Self:
"""Factory method to create and build an element instance"""
instance = cls()
# Separate element constructor args from build args
element_args = kwargs.pop('element_args', ())
element_kwargs = kwargs.pop('element_kwargs', {})
# Create and build the instance
instance = cls(element_type, *element_args, **element_kwargs)
await instance.build(*args, **kwargs)
return instance
return instance.element # Return the NiceGUI element directly
@classmethod
async def as_dialog(cls, dialog_classes: str = '', card_classes: str = '', *args, **kwargs) -> Any:
"""Create as dialog and return the awaited result"""
with ui.dialog().classes(dialog_classes) as dialog:
with ui.card().classes(card_classes):
instance = cls(dialog=dialog)
await instance.build(*args, **kwargs)
def __getattr__(self, name):
"""Delegate all attribute access to the wrapped element"""
return getattr(self.element, name)
result = await dialog
dialog.clear()
return result
def submit(self, result: Any) -> None:
if self.dialog:
self.dialog.submit(result)
if __name__ in {"__main__", "__mp_main__"}:
def close_dialog(self) -> None:
"""Close the dialog with a result"""
if self.dialog:
self.dialog.close()
# Example implementations
class UserCard(AsyncElement):
async def build(self, user_id: int, *args, **kwargs) -> None:
# Simulate loaded user data
user_data = {
'name': f'User {user_id}',
'email': f'user{user_id}@example.com',
'status': 'online' if user_id % 2 == 0 else 'offline'
}
# Build content directly in element
with self.element:
with ui.card().classes('w-full max-w-md'):
ui.label(user_data['name']).classes('text-h6')
ui.label(user_data['email']).classes('text-caption')
with ui.row().classes('w-full justify-between items-center'):
status_color = 'green' if user_data['status'] == 'online' else 'grey'
ui.badge(user_data['status']).props(f'color={status_color}')
ui.button('Edit', icon='edit').props('flat dense')
class DataTable(AsyncElement):
async def build(self, data_source: str, *args, **kwargs) -> None:
# Simulate loaded data
columns = [
{'name': 'name', 'label': 'Name', 'field': 'name', 'required': True, 'align': 'left'},
{'name': 'age', 'label': 'Age', 'field': 'age', 'sortable': True},
{'name': 'city', 'label': 'City', 'field': 'city'},
]
rows = [
{'id': 1, 'name': 'Alice', 'age': 25, 'city': 'New York'},
{'id': 2, 'name': 'Bob', 'age': 30, 'city': 'San Francisco'},
{'id': 3, 'name': 'Charlie', 'age': 35, 'city': 'London'},
]
with self.element:
ui.label(f'Data from {data_source}').classes('text-caption mb-2')
ui.table(columns=columns, rows=rows, row_key='id').classes('w-full')
class LoadingSection(AsyncElement):
async def build(self, title: str, delay: float = 1.0, *args, **kwargs) -> None:
with self.element:
ui.label(title).classes('text-h6 mb-2')
ui.label('Content loaded successfully!').classes('text-positive')
with ui.row().classes('gap-2'):
ui.button('Action 1', icon='star')
ui.button('Action 2', icon='favorite')
@ui.page('/')
async def main_page():
ui.label('Simple Async Elements Demo').classes('text-h4 mb-4')
# Example 1: User card as a column with method chaining
(await UserCard.create(element_type=ui.column, user_id=123)).classes('w-full mb-8')
# Example 2: Data table as a row
(await DataTable.create(element_type=ui.row, data_source="users_api")).classes('w-full mb-8')
# Example 3: Loading section as a card
(await LoadingSection.create(element_type=ui.card, title="Dashboard Section", delay=0.8)).classes('w-full p-4')
# Example 4: Multiple elements in a row
with ui.row().classes('w-full gap-4'):
for i in range(1, 4):
(await UserCard.create(element_type=ui.column, user_id=i)).classes('flex-1')
# Example 5: Nested usage
with ui.column().classes('w-full mt-8'):
ui.label('Nested Example').classes('text-h6')
(await LoadingSection.create(element_type=ui.row, title=f"Nested Section", delay=0.3)).classes(
'w-full items-center gap-4')
ui.run(
title='Simple AsyncElements',
favicon='🔒',
show=False,
dark=False,
port=8080
)