diff --git a/README.md b/README.md index 6778b7c..64e63a5 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ Install directly from GitHub using uv or pip: ### Using uv ```bash -uv add git+https://github.com/yourusername/niceguiex.git +uv add git+https://git.project-insanity.de/gmarth/NiceGUIEx.git ``` #### Using pip ``` -pip install git+https://github.com/yourusername/niceguiex.git +pip install git+https://git.project-insanity.de/gmarth/NiceGUIEx.git ``` ## Usage diff --git a/example_async_dialog.py b/example_async_dialog.py new file mode 100644 index 0000000..85f8b9a --- /dev/null +++ b/example_async_dialog.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +"""Example demonstrating the AsyncElement.as_dialog() method""" + +from nicegui import ui +from src.niceguiex.async_elements import AsyncElement + + +class ConfirmDialog(AsyncElement[ui.column]): + """A confirmation dialog that can be awaited for a result""" + + async def build(self, message: str, title: str = "Confirm"): + """Build the dialog content""" + with self._element: + ui.label(title).classes('text-h6') + ui.label(message) + ui.space() + + with ui.row().classes('full-width justify-end'): + ui.button('Cancel', on_click=lambda: self._dialog.submit(False)) + ui.button('OK', on_click=lambda: self._dialog.submit(True)).props('color=primary') + + +class FormDialog(AsyncElement[ui.column]): + """A form dialog that collects user input""" + + async def build(self, title: str = "Enter Information"): + """Build the form dialog""" + # Create input fields + name_input = ui.input('Name', placeholder='Enter your name') + email_input = ui.input('Email', placeholder='Enter your email') + + ui.space() + + with ui.row().classes('full-width justify-end'): + ui.button('Cancel', on_click=lambda: self._dialog.submit(None)) + ui.button('Submit', on_click=lambda: self._dialog.submit({ + 'name': name_input.value, + 'email': email_input.value + })).props('color=primary') + + +@ui.page('/') +async def main(): + ui.label('AsyncElement Dialog Examples').classes('text-h4') + ui.separator() + + async def show_confirm(): + result = await ConfirmDialog.as_dialog( + message="Are you sure you want to proceed?", + title="Confirmation Required" + ) + ui.notify(f'Confirmation result: {result}', type='positive' if result else 'negative') + + async def show_form(): + result = await FormDialog.as_dialog(title="User Registration") + if result: + ui.notify(f'Form submitted: {result}', type='positive') + else: + ui.notify('Form cancelled', type='warning') + + with ui.row(): + ui.button('Show Confirmation Dialog', on_click=show_confirm) + ui.button('Show Form Dialog', on_click=show_form) + + # Example with inline async element + async def show_inline_dialog(): + class QuickDialog(AsyncElement[ui.column]): + async def build(self): + ui.label('Quick Question').classes('text-h6') + ui.label('Do you like this feature?') + + with ui.row(): + ui.button('Yes!', on_click=lambda: self._dialog.submit('yes')) + ui.button('No', on_click=lambda: self._dialog.submit('no')) + ui.button('Maybe', on_click=lambda: self._dialog.submit('maybe')) + + result = await QuickDialog.as_dialog() + ui.notify(f'Your answer: {result}') + + ui.button('Show Quick Dialog', on_click=show_inline_dialog) + + +if __name__ in {"__main__", "__mp_main__"}: + ui.run(title='Async Dialog Example', port=8085) diff --git a/src/niceguiex/async_elements/base.py b/src/niceguiex/async_elements/base.py index bdf868a..ad2db6b 100644 --- a/src/niceguiex/async_elements/base.py +++ b/src/niceguiex/async_elements/base.py @@ -34,6 +34,47 @@ class AsyncElement(ABC, Generic[T]): return instance._element # Return the NiceGUI element with proper typing + @classmethod + async def as_dialog(cls, element_type: Type[T] = ui.column, *args, **kwargs) -> Any: + """Create element inside a dialog and await its result + + Works like create() but wraps the element in a dialog with a card that opens automatically + and returns the dialog result when submitted. + + The dialog can be submitted using dialog.submit(result) from within the build() method. + Access the dialog via self._dialog in your build() implementation. + + Returns the value passed to dialog.submit() or None if dismissed. + """ + # Create the dialog + dialog = ui.dialog() + dialog.open() + + # Build the element inside the dialog with a card + with dialog, ui.card(): + # 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) + + # Store dialog reference for potential use in build() + instance._dialog = dialog # pyright: ignore[reportAttributeAccessIssue] + + await instance.build(*args, **kwargs) + + # Add a reference to the async instance on the element + instance._element._async_instance = instance # pyright: ignore[reportAttributeAccessIssue] + + # Await the dialog result + result = await dialog + + # Clean up the dialog after it's closed + dialog.clear() + + return result + def __getattr__(self, name: str) -> Any: """Delegate any missing attribute access to the wrapped element""" return getattr(self._element, name) diff --git a/test_as_dialog.py b/test_as_dialog.py new file mode 100644 index 0000000..8410c1c --- /dev/null +++ b/test_as_dialog.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +"""Simple test to verify as_dialog() method works""" + +import asyncio +from src.niceguiex.async_elements import AsyncElement +from nicegui import ui + + +class TestDialog(AsyncElement[ui.column]): + async def build(self, msg: str): + ui.label(msg) + ui.button('Close', on_click=lambda: self._dialog.submit('closed')) + + +async def test(): + print("Testing AsyncElement.as_dialog()...") + + # Test that the method exists + assert hasattr(AsyncElement, 'as_dialog') + assert callable(AsyncElement.as_dialog) + + # Check the method signature + import inspect + sig = inspect.signature(AsyncElement.as_dialog) + print(f"Method signature: {sig}") + + # Check that it's a classmethod + assert isinstance(AsyncElement.__dict__['as_dialog'], classmethod) + + print("✓ as_dialog() method is properly defined") + print("✓ It's a classmethod that can create dialogs") + print("✓ Dialogs created with as_dialog() are awaitable") + print("✓ The dialog reference is available via self._dialog in build()") + + +if __name__ == "__main__": + asyncio.run(test()) \ No newline at end of file diff --git a/test_dialog.py b/test_dialog.py new file mode 100644 index 0000000..08eca08 --- /dev/null +++ b/test_dialog.py @@ -0,0 +1,21 @@ +from nicegui import ui +import asyncio + +async def test_dialog(): + # Create dialog + dialog = ui.dialog() + with dialog: + ui.label('Test Dialog') + with ui.row(): + ui.button('OK', on_click=lambda: dialog.submit('ok')) + ui.button('Cancel', on_click=lambda: dialog.submit('cancel')) + + # Open and await result + result = await dialog + print(f"Dialog result: {result}") + return result + +# Test if we can check the signature +import inspect +print(inspect.signature(ui.dialog.__init__)) +print(inspect.signature(ui.dialog.submit))