Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,25 @@ def _main_page() -> None:
with ui.left_drawer() \
.classes('column no-wrap gap-1 bg-[#eee] dark:bg-[#1b1b1b] mt-[-20px] px-8 py-20') \
.style('height: calc(100% + 20px) !important') as menu:
def _on_tree_select(e) -> None:
if e.value in documentation.tree.group_ids:
ui.navigate.to(documentation.tree.group_ids[e.value])
else:
ui.navigate.to(f'/documentation/{e.value}')

def _on_tree_expand(e) -> None:
missing = documentation.tree.group_ids.keys() - set(e.value)
if missing:
e.sender.expand(list(missing))

tree = ui.tree(documentation.tree.nodes, label_key='title',
on_select=lambda e: ui.navigate.to(f'/documentation/{e.value}')) \
on_select=_on_tree_select, on_expand=_on_tree_expand) \
.classes('w-full').props('accordion no-connectors no-selection-unset')
# Hide expand arrows on sub-section group nodes (depth 1 in the tree)
ui.add_css('''
.q-tree > .q-tree__node > .q-tree__node-collapsible > .q-tree__children
> .q-tree__node--parent > .q-tree__node-header > .q-tree__arrow { display: none !important; }
''')
menu_button = header.add_header(menu)

window_state = {'is_desktop': None}
Expand Down Expand Up @@ -100,7 +116,11 @@ def _update_menu(path: str):


def _documentation_detail_page(name: str, tree: ui.tree) -> None:
tree.props.update(expanded=documentation.tree.ancestors(name))
if name in documentation.tree.group_ids:
ui.navigate.to(documentation.tree.group_ids[name])
return
expanded = [*documentation.tree.ancestors(name), *documentation.tree.group_ids]
tree.props.update(expanded=expanded)
tree.update()
if name in documentation.registry:
documentation.render_page(documentation.registry[name])
Expand Down
6 changes: 4 additions & 2 deletions website/documentation/content/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from .doc import redirects, registry
from .doc.page import DocumentationPage
from . import _doc as doc
from ._doc import redirects, registry
from ._doc.page import DocumentationPage

__all__ = [
'DocumentationPage',
'doc',
'registry',
'redirects',
]
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
extra_column,
get_page,
intro,
intro_group,
part,
redirects,
reference,
Expand All @@ -19,6 +20,7 @@
'extra_column',
'get_page',
'intro',
'intro_group',
'part',
'redirects',
'reference',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import inspect
import sys
import types
from collections.abc import Callable, Generator
from contextlib import contextmanager
from copy import deepcopy
from pathlib import Path
from types import ModuleType
from typing import Any, overload
from collections.abc import Callable, Generator
from contextlib import contextmanager

import nicegui
from nicegui import app as nicegui_app
from nicegui import Client
from nicegui import app as nicegui_app
from nicegui import ui as nicegui_ui
from nicegui.functions.navigate import Navigate
from nicegui.elements.markdown import remove_indentation
from nicegui.functions.navigate import Navigate

from .page import DocumentationPage
from .part import Demo, DocumentationPart
Expand Down Expand Up @@ -166,6 +166,21 @@ def intro(documentation: types.ModuleType) -> None:
current_page.parts.append(part)


def intro_group(documentation: types.ModuleType) -> None:
"""Add a grouped intro section that shows all sub-elements on the current page.

Creates a group header linked to the sub-section page, with all element intros
rendered inline as children. The tree builder uses the children for 3-level nesting.
"""
current_page = _get_current_page()
target_page = get_page(documentation)
target_page.back_link = current_page.name
group_part = DocumentationPart(title=target_page.title, link=target_page.name)
for child_part in target_page.parts:
group_part.children.append(deepcopy(child_part))
current_page.parts.append(group_part)


def reference(element: type, *,
title: str = 'Reference', # pylint: disable=redefined-outer-name
) -> None:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dataclasses import dataclass
from typing import Literal
from collections.abc import Callable
from dataclasses import dataclass, field
from typing import Literal

from nicegui.dataclasses import KWONLY_SLOTS

Expand All @@ -24,6 +24,7 @@ class DocumentationPart:
demo: Demo | None = None
reference: type | None = None
search_text: str | None = None
children: list['DocumentationPart'] = field(default_factory=list)

@property
def link_target(self) -> str | None:
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from nicegui import ui

from . import doc
from .. import doc


@doc.demo('Read and write to the clipboard', '''
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from nicegui import ui, Event

from . import doc
from .. import doc


@doc.demo(Event)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from nicegui import ui

from . import doc
from .. import doc

doc.title('Generic Events')

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from nicegui import ui

from . import doc
from .. import doc


@doc.demo(ui.keyboard)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from nicegui import ui

from . import doc
from .. import doc


@doc.demo(ui.refreshable)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from nicegui import ui

from . import doc
from .. import doc


@doc.demo(ui.run_javascript)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from .. import doc
from . import (
subsection_client_storage,
subsection_error_lifecycle,
subsection_timers_input,
subsection_updates_events,
)

doc.title('Action & *Events*')

doc.intro_group(subsection_timers_input)
doc.intro_group(subsection_updates_events)
doc.intro_group(subsection_client_storage)
doc.intro_group(subsection_error_lifecycle)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from nicegui import ui

from . import doc
from .. import doc

counter = Counter() # type: ignore
start = datetime.now().strftime(r'%H:%M, %d %B %Y')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from .. import doc
from . import (
clipboard_documentation,
event_documentation,
run_javascript_documentation,
storage_documentation,
)

doc.title('Client & Storage')

doc.intro(run_javascript_documentation)
doc.intro(clipboard_documentation)
doc.intro(event_documentation)
doc.intro(storage_documentation)
Original file line number Diff line number Diff line change
@@ -1,113 +1,12 @@
from nicegui import app, ui

from . import (
clipboard_documentation,
doc,
event_documentation,
generic_events_documentation,
keyboard_documentation,
refreshable_documentation,
run_javascript_documentation,
storage_documentation,
timer_documentation,
)

doc.title('Action & *Events*')

doc.intro(timer_documentation)
doc.intro(keyboard_documentation)


@doc.demo('UI Updates', '''
NiceGUI tries to automatically synchronize the state of UI elements with the client,
e.g. when a label text, an input value or style/classes/props of an element have changed.
In other cases, you can explicitly call `element.update()` or `ui.update(*elements)` to update.
The demo code shows how to update a `ui.radio` after a new option is added.
''')
def ui_updates_demo():
radio = ui.radio(['A', 'B', 'C'])

ui.button('Add option', on_click=lambda: radio.options.append('D'))
ui.button('Update', on_click=radio.update)


doc.intro(refreshable_documentation)


@doc.demo('Async event handlers', '''
Most elements also support asynchronous event handlers.

Note: You can also pass a `functools.partial` into the `on_click` property to wrap async functions with parameters.
''')
def async_handlers_demo():
import asyncio

async def async_task():
ui.notify('Asynchronous task started')
await asyncio.sleep(5)
ui.notify('Asynchronous task finished')

ui.button('start async task', on_click=async_task)


doc.intro(generic_events_documentation)

import traceback

@doc.demo('Running CPU-bound tasks', '''
NiceGUI provides a `cpu_bound` function for running CPU-bound tasks in a separate process.
This is useful for long-running computations that would otherwise block the event loop and make the UI unresponsive.
The function returns a future that can be awaited.

Note:
The function needs to transfer the whole state of the passed function to the process, which is done with pickle.
It is encouraged to create free functions or static methods which get all the data as simple parameters (i.e. no class or UI logic)
and return the result, instead of writing it in class properties or global variables.
''')
def cpu_bound_demo():
import time

from nicegui import run

def compute_sum(a: float, b: float) -> float:
time.sleep(1) # simulate a long-running computation
return a + b

async def handle_click():
result = await run.cpu_bound(compute_sum, 1, 2)
ui.notify(f'Sum is {result}')

# ui.button('Compute', on_click=handle_click)
# END OF DEMO
async def mock_click():
import asyncio
await asyncio.sleep(1)
ui.notify('Sum is 3')
ui.button('Compute', on_click=mock_click)


@doc.demo('Running I/O-bound tasks', '''
NiceGUI provides an `io_bound` function for running I/O-bound tasks in a separate thread.
This is useful for long-running I/O operations that would otherwise block the event loop and make the UI unresponsive.
The function returns a future that can be awaited.
''')
def io_bound_demo():
import httpx

from nicegui import run

async def handle_click():
URL = 'https://httpbin.org/delay/1'
response = await run.io_bound(httpx.get, URL, timeout=3)
ui.notify(f'Downloaded {len(response.content)} bytes')

ui.button('Download', on_click=handle_click)
from nicegui import app, ui

from .. import doc

doc.intro(run_javascript_documentation)
doc.intro(clipboard_documentation)
doc.intro(event_documentation)
doc.title('Error Handling')

doc.text('Error handling', '''
doc.text('', '''
There are 3 error handling means in NiceGUI:

1. [`app.on_exception`](#lifecycle_events):
Expand Down Expand Up @@ -183,7 +82,6 @@ def handle_connection():
''')
def error_page_demo():
from nicegui import app
import traceback

@app.on_page_exception
def timeout_error_page(exception: Exception) -> None:
Expand Down Expand Up @@ -220,7 +118,6 @@ def page():
''')
def error_event_demo():
import asyncio
import traceback

@ui.page('/error_dialog_page')
async def error_dialog_page():
Expand Down Expand Up @@ -261,7 +158,7 @@ async def clear_content_page():
raise ValueError('Test exception handling') # HIDE
except Exception as e: # HIDE
render_error_details(e, 'w-full') # HIDE
ui.link('Back to menu', '/documentation/section_action_events#error_event') # HIDE
ui.link('Back to menu', '/documentation/section_action_events#ui_on_exception') # HIDE

def render_error_details(error, code_classes=''):
ui.label('Page error').classes('text-lg font-bold')
Expand All @@ -286,6 +183,3 @@ def shutdown_demo():
# END OF DEMO
ui.button('shutdown', on_click=lambda: ui.notify(
'Nah. We do not actually shutdown the documentation server. Try it in your own app!'))


doc.intro(storage_documentation)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from .. import doc
from . import (
keyboard_documentation,
timer_documentation,
)

doc.title('Timers & Input')

doc.intro(timer_documentation)
doc.intro(keyboard_documentation)
Loading