Selaa lähdekoodia

Merge branch 'v1' of github.com:zauberzeug/nicegui into v1

Falko Schindler 2 vuotta sitten
vanhempi
säilyke
96b4835c59
9 muutettua tiedostoa jossa 181 lisäystä ja 100 poistoa
  1. 29 18
      examples/authentication/main.py
  2. 95 59
      main.py
  3. 1 1
      nicegui/functions/notify.py
  4. 15 3
      poetry.lock
  5. 2 1
      pyproject.toml
  6. 1 0
      tests/test_keyboard.py
  7. 3 3
      website/demo_card.py
  8. 5 1
      website/example.py
  9. 30 14
      website/reference.py

+ 29 - 18
examples/authentication/main.py

@@ -1,46 +1,57 @@
 #!/usr/bin/env python3
+'''This is only a very simple authentication example which stores session ids in memory and does not do any password hashing.
+
+Please see the `OAuth2 example at FastAPI <https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/>`_  or
+use the great `Authlib package <https://docs.authlib.org/en/v0.13/client/starlette.html#using-fastapi>`_ to implement a real authentication system.
+
+Here we just demonstrate the NiceGUI integration.
+'''
+
 import uuid
 from typing import Dict
 
+from fastapi import Request
+from fastapi.responses import RedirectResponse
 from starlette.middleware.sessions import SessionMiddleware
-from starlette.requests import Request
 
 from nicegui import app, ui
 
-app.add_middleware(SessionMiddleware, secret_key='some_random_string')
+app.add_middleware(SessionMiddleware, secret_key='some_random_string')  # use your own secret key here
 
-session_info: Dict[str, Dict] = {}  # in reality in a database
+# in reality users and session_info would be persistent (e.g. database, file, ...) and passwords obviously hashed
+users = [('user1', 'pass1'), ('user2', 'pass2')]
+session_info: Dict[str, Dict] = {}
 
 
 @ui.page('/')
 def main_page(request: Request) -> None:
     if is_authenticated(request):
-        create_welcome_message(session_info[request.session['id']]['username'])
+        session = session_info[request.session['id']]
+        with ui.row().classes('absolute-center'):
+            ui.label(f'Hello {session["username"]}!').classes('text-2xl')
     else:
-        request.session['id'] = str(uuid.uuid4())
-        create_login_form(request.session['id'])
-
-
-def is_authenticated(request: Request) -> bool:
-    return session_info.get(request.session.get('id'), {}).get('authenticated', False)
+        return RedirectResponse('/login')
 
 
-def create_login_form(session_id: str) -> None:
+@ui.page('/login')
+def login(request: Request) -> None:
+    if is_authenticated(request):
+        return RedirectResponse('/')
+    request.session['id'] = str(uuid.uuid4())  # NOTE this stores a new session id in the cookie of the client
     with ui.card().classes('absolute-center'):
         username = ui.input('Username')
         password = ui.input('Password').classes('w-full').props('type=password')
-        ui.button('Log in', on_click=lambda: try_login(session_id, username.value, password.value))
+        ui.button('Log in', on_click=lambda: try_login(request.session['id'], username.value, password.value))
+
+
+def is_authenticated(request: Request) -> bool:
+    return session_info.get(request.session.get('id'), {}).get('authenticated', False)
 
 
 def try_login(session_id: str, username: str, password: str) -> None:
-    if (username, password) in [('user1', 'pass1'), ('user2', 'pass2')]:
+    if (username, password) in users:
         session_info[session_id] = {'username': username, 'authenticated': True}
     ui.open('/')
 
 
-def create_welcome_message(username: str) -> None:
-    with ui.row().classes('absolute-center'):
-        ui.label(f'Hello {username}!').classes('text-2xl')
-
-
 ui.run()

+ 95 - 59
main.py

@@ -7,7 +7,7 @@ from pygments.formatters import HtmlFormatter
 from nicegui import Client, ui
 from website import demo_card, reference
 from website.constants import ACCENT_COLOR, HEADER_HEIGHT, STATIC
-from website.example import bash_window, python_window
+from website.example import bash_window, browser_window, python_window
 
 ui.add_static_files('/favicon', Path(__file__).parent / 'website' / 'favicon')
 
@@ -72,14 +72,16 @@ def add_header() -> None:
     with ui.header() \
             .classes('items-center duration-200 px-4', remove='q-pa-md') \
             .style('box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1)'):
-        ui.html((STATIC / 'happy_face.svg').read_text()).classes('w-8 stroke-white')
-        with ui.link(target=index_page):
+        with ui.link(target=index_page).classes('row gap-4 items-center'):
+            ui.html((STATIC / 'happy_face.svg').read_text()).classes('w-8 stroke-white')
             ui.html((STATIC / 'nicegui_word.svg').read_text()).classes('w-24')
         with ui.row().classes('items-center ml-auto'):
             ui.link('Features', '/#features').classes(replace='text-lg text-white')
             ui.link('Installation', '/#installation').classes(replace='text-lg text-white')
             ui.link('Examples', '/#examples').classes(replace='text-lg text-white')
             ui.link('API Reference', reference_page).classes(replace='text-lg text-white')
+            ui.link('Demos', '/#demos').classes(replace='text-lg text-white')
+            ui.link('Why?', '/#why').classes(replace='text-lg text-white')
             with ui.link(target='https://github.com/zauberzeug/nicegui/'):
                 ui.html((STATIC / 'github.svg').read_text()).classes('fill-white scale-125 m-1')
 
@@ -97,23 +99,29 @@ async def index_page(client: Client):
         with ui.column().classes('gap-8'):
             ui.html('Meet the <em>NiceGUI</em>.') \
                 .style('font-size: 400%; line-height: 0.9; font-weight: 500')
-            ui.markdown('Your easy-to-use Python framework to create\n\nuser interfaces which show up in the browser.') \
+            ui.markdown('And let any browser be the frontend\n\nof your Python code.') \
                 .style('font-size: 200%; line-height: 0.9')
+            with ui.column().classes('q-mt-md'):
+                ui.icon('keyboard_arrow_down').classes('text-4xl text-grey-5').style('margin: 0 0 -1.4em 0.32em')
+                ui.icon('keyboard_arrow_down').classes('text-6xl text-black')
+                ui.icon('keyboard_arrow_down').classes('text-4xl text-grey-5').style('margin: -1.3em 0 0 0.32em')
 
     with ui.row() \
             .classes('w-full h-screen q-pa-md items-center gap-28 p-32 no-wrap') \
             .style(f'background: {ACCENT_COLOR}'):
         with ui.column().classes('gap-6'):
-            ui.markdown('Create buttons, dialogs, markdown,\n\n3D scenes, plots and much more at ease.') \
+            ui.markdown('Interact with Python through buttons, dialogs, 3D scenes, plots and much more.') \
                 .style('font-size: 300%; color: white; line-height: 0.9; font-weight: 500').classes('mb-4')
             ui.label('''
-                It is great for micro web apps, dashboards, robotics projects, smart home solutions
-                and similar use cases. You can also use it in development, for example when
-                tweaking/configuring a machine learning algorithm or tuning motor controllers.
+                NiceGUI handles all the web development details for you.
+                So you can focus on writing Python code.
+                Anything from short scripts and dashboards to full robotics projects, IoT solutions, 
+                smart home automations and machine learning projects can benefit from having all code in one place.
+                
             ''').style('font-size: 150%; color: white').classes('leading-tight')
             with ui.row().style('font-size: 150%; color: white').classes('leading-tight gap-2'):
                 ui.html('''
-                    NiceGUI is available as
+                    Available as
                     <a href="https://pypi.org/project/nicegui/"><strong>PyPI package</strong><span class="material-icons">north_east</span></a>,
                     <a href="https://hub.docker.com/r/zauberzeug/nicegui"><strong>Docker image</strong><span class="material-icons">north_east</span></a> and on
                     <a href="https://github.com/zauberzeug/nicegui"><strong>GitHub</strong><span class="material-icons">north_east</span></a>.
@@ -122,46 +130,63 @@ async def index_page(client: Client):
         demo_card.create()
 
     ui.link_target('features').style(f'position: relative; top: -{HEADER_HEIGHT}')
-    with ui.column().classes('w-full q-pa-xl'):
+    with ui.column().classes('w-full q-pa-xl q-mb-xl'):
         ui.label('Features').classes('text-bold text-lg')
-        ui.html('What has <em>NiceGUI</em> to offer?') \
+        ui.html('Code <em>nicely</em>') \
             .style('font-size: 300%; font-weight: 500; margin-top: -20px')
-        with ui.row().classes('w-full no-wrap text-lg leading-tight justify-between'):
-            with ui.column().classes('gap-1'):
-                ui.label('User Interface').classes('text-bold mb-4')
-                ui.markdown('- common elements like label, button, checkbox, switch, slider, input, ...')
-                ui.markdown('- page layout with navigation bars, tabs, panels, ...')
-                ui.markdown('- grouping with rows, columns, cards and dialogs')
-                ui.markdown('- HTML and markdown elements')
-                ui.markdown('- high-level elements like charts, tables, trees, 3D scenes, joystick, ...')
-                ui.markdown('- built-in timer to refresh data in intervals')
+        with ui.row().classes('w-full no-wrap text-lg leading-tight justify-between q-mb-xl'):
+            with ui.column().classes('gap-1 col-3'):
+                ui.icon('swap_horiz').classes('text-5xl q-mb-md text-primary opacity-80')
+                ui.label('Interaction').classes('text-bold mb-4')
+                ui.markdown('- buttons, switches, slider, input, ...')
                 ui.markdown('- notifications, dialogs and menus')
                 ui.markdown('- keyboard input')
-            with ui.column().classes('gap-1'):
-                ui.label('Under the hood').classes('text-bold mb-4')
-                ui.markdown('- browser-based graphical user interface')
-                ui.markdown('- based on FastAPI and Uvicorn')
-                ui.markdown('- live-cycle events and session data')
-                ui.markdown('- customizable page layout and colors')
-            with ui.column().classes('gap-1'):
-                ui.label('Development').classes('text-bold mb-4')
+                ui.markdown('- on-screen joystick')
+            with ui.column().classes('gap-1 col-3'):
+                ui.icon('space_dashboard').classes('text-5xl q-mb-md text-primary opacity-80')
+                ui.label('Layout').classes('text-bold mb-4')
+                ui.markdown('- navigation bars, tabs, panels, ...')
+                ui.markdown('- grouping with rows, columns and cards')
+                ui.markdown('- HTML and markdown elements')
+                ui.markdown('- flex layout by default')
+            with ui.column().classes('gap-1 col-3'):
+                ui.icon('insights').classes('text-5xl q-mb-md text-primary')
+                ui.label('Visualization').classes('text-bold mb-4')
+                ui.markdown('- charts, diagrams and tables')
+                ui.markdown('- 3D scenes')
+                ui.markdown('- progress bars')
+                ui.markdown('- built-in timer for data refresh')
+        with ui.row().classes('w-full no-wrap text-lg leading-tight justify-between'):
+            with ui.column().classes('gap-1 col-3'):
+                ui.icon('brush').classes('text-5xl q-mb-xs text-primary opacity-80')
+                ui.label('Styling').classes('text-bold mb-4')
+                ui.markdown('- customizable color themes')
+                ui.markdown('- add custom css and classes')
+                ui.markdown('- modern look with material design')
+            with ui.column().classes('gap-1 col-3'):
+                ui.icon('source').classes('text-5xl q-mb-md text-primary opacity-80')
+                ui.label('Coding').classes('text-bold mb-4')
+                ui.markdown('- live-cycle events')
                 ui.markdown('- implicit reload on code change')
                 ui.markdown('- straight-forward data binding')
+                ui.markdown('- execute javascript from Python')
+            with ui.column().classes('gap-1 col-3'):
+                ui.icon('anchor').classes('text-5xl q-mb-md text-primary opacity-80')
+                ui.label('Foundation').classes('text-bold mb-4')
+                ui.markdown('- generic Vue to Python bridge')
+                ui.markdown('- dynamic GUI through Quasar')
+                ui.markdown('- content is served with FastAPI')
+                ui.markdown('- Python 3.7 - 3.11')
 
     ui.link_target('installation').style(f'position: relative; top: -{HEADER_HEIGHT}')
-    with ui.column().classes('w-full q-pa-xl'):
+    with ui.column().classes('w-full q-pa-xl q-mb-xl'):
         ui.label('Installation').classes('text-bold text-lg')
-        ui.html('Getting <em>started</em>') \
+        ui.html('Get <em>started</em>') \
             .style('font-size: 300%; font-weight: 500; margin-top: -20px')
         with ui.row().classes('w-full no-wrap text-lg leading-tight'):
             with ui.column().classes('w-1/3 gap-2'):
                 ui.html('<em>1.</em>').classes('text-3xl text-bold')
-                ui.markdown('Install').classes('text-lg')
-                with bash_window().classes('w-full h-52'):
-                    ui.markdown('```bash\npython3 -m pip install nicegui\n```')
-            with ui.column().classes('w-1/3 gap-2'):
-                ui.html('<em>2.</em>').classes('text-3xl text-bold')
-                ui.markdown('Write file __main.py__').classes('text-lg')
+                ui.markdown('Create __main.py__').classes('text-lg')
                 with python_window().classes('w-full h-52'):
                     ui.markdown('''```python\n
 from nicegui import ui
@@ -171,15 +196,20 @@ ui.label('Hello NiceGUI!')
 ui.run()
 ```''')
             with ui.column().classes('w-1/3 gap-2'):
-                ui.html('<em>3.</em>').classes('text-3xl text-bold')
-                ui.markdown('Launch it with').classes('text-lg')
+                ui.html('<em>2.</em>').classes('text-3xl text-bold')
+                ui.markdown('Install and launch').classes('text-lg')
                 with bash_window().classes('w-full h-52'):
-                    ui.markdown('```bash\npython3 main.py\n```')
+                    ui.markdown('```bash\npip3 install nicegui\npython3 main.py\n```')
+            with ui.column().classes('w-1/3 gap-2'):
+                ui.html('<em>3.</em>').classes('text-3xl text-bold')
+                ui.markdown('Enjoy').classes('text-lg')
+                with browser_window().classes('w-full h-52'):
+                    ui.label('Hello NiceGUI!')
 
     ui.link_target('examples').style(f'position: relative; top: -{HEADER_HEIGHT}')
-    with ui.column().classes('w-full q-pa-xl'):
-        ui.label('Documentation').classes('text-bold text-lg')
-        ui.html('Interactive <em>Examples</em>') \
+    with ui.column().classes('w-full q-pa-xl q-mb-xl'):
+        ui.label('Examples').classes('text-bold text-lg')
+        ui.html('Try <em>this</em>') \
             .style('font-size: 300%; font-weight: 500; margin-top: -20px')
         reference.create_intro()
 
@@ -187,37 +217,42 @@ ui.run()
             .classes('w-full items-center gap-28 px-32 py-16 no-wrap') \
             .style(f'background: {ACCENT_COLOR}'):
         with ui.column().classes('gap-4'):
-            ui.markdown('Go to the API reference to see a ton of live examples.') \
+            ui.markdown('Browse through tons of live examples.') \
                 .style('font-size: 220%; color: white; line-height: 0.9; font-weight: 500')
-            ui.html('The whole content of <a href="https://nicegui.io/">nicegui.io</a> is implemented with NiceGUI itself.') \
+            ui.html('Fun-Fact: This whole website is also coded with NiceGUI.') \
                 .style('font-size: 150%; color: white')
         ui.link('API reference', '/reference') \
             .classes('rounded-full mx-auto px-12 py-2 text-xl text-bold bg-white')
 
-    with ui.column().classes('w-full q-pa-xl'):
-        ui.label('In-depth demonstration').classes('text-bold text-lg')
-        ui.html('What else can you do with <em>NiceGUI</em>?') \
+    ui.link_target('demos').style(f'position: relative; top: -{HEADER_HEIGHT}')
+    with ui.column().classes('w-full q-pa-xl q-mb-xl'):
+        ui.label('In-depth demonstrations').classes('text-bold text-lg')
+        ui.html('Pick your <em>solution</em>') \
             .style('font-size: 300%; font-weight: 500; margin-top: -20px')
         with ui.row().classes('w-full no-wrap text-lg leading-tight'):
             with ui.column().classes('w-1/3'):
-                ui.markdown(
-                    'You may also have a look at the following examples for in-depth demonstrations of what you can do with NiceGUI:')
                 example_link('Slideshow', 'implements a keyboard-controlled image slideshow')
                 example_link('Authentication', 'shows how to use sessions to build a login screen')
                 example_link(
                     'Modularization',
-                    'provides an example of how to modularize your application into multiple files and create a specialized page decorator')
+                    'provides an example of how to modularize your application into multiple files and reuse code')
+                example_link(
+                    'FastAPI',
+                    'illustrates the integration of NiceGUI with an existing FastAPI application')
             with ui.column().classes('w-1/3'):
-                example_link('Map', 'uses the JavaScript library leaflet to display a map at specific locations')
+                example_link(
+                    'Map',
+                    'demonstrates wrapping the JavaScript library leaflet to display a map at specific locations')
                 example_link(
                     'AI Interface',
-                    'utilizes the great but non-async API from <https://replicate.com> to perform voice-to-text transcription and generate images from prompts with Stable Diffusion')
-                example_link('3D Scene', 'creates a 3D scene and loads an STL mesh illuminated with a spotlight')
+                    'utilizes the great [replicate](https://replicate.com) library to perform voice-to-text transcription and generate images from prompts with Stable Diffusion')
+                example_link('3D Scene', 'creates a webGL view and loads an STL mesh illuminated with a spotlight')
             with ui.column().classes('w-1/3'):
                 example_link('Custom Vue Component', 'shows how to write and integrate a custom vue component')
                 example_link('Image Mask Overlay', 'shows how to overlay an image with a mask')
-                example_link('Infinite Scroll', 'shows an infinitely scrolling image gallery')
+                example_link('Infinite Scroll', 'presents an infinitely scrolling image gallery')
 
+    ui.link_target('why')
     with ui.row() \
             .classes('w-full h-screen q-pa-md items-center gap-28 p-32 no-wrap') \
             .style(f'background: {ACCENT_COLOR}'):
@@ -239,12 +274,13 @@ ui.run()
                 <strong><a href="https://quasar.dev/">Quasar</a></strong>
                 for the frontend.<br/>
 
-                The backend is build on top of
+                We have build on top of
                 <strong><a href="https://fastapi.tiangolo.com/">FastAPI</a></strong>,
                 which itself is based on the ASGI framework
                 <strong><a href="https://www.starlette.io/">Starlette</a></strong>,
                 and the ASGI webserver
-                <strong><a href="https://www.uvicorn.org/">Uvicorn</a></strong>.
+                <strong><a href="https://www.uvicorn.org/">Uvicorn</a></strong>
+                because of their great performance and ease of use.
             ''').style('font-size: 150%; color: white').classes('leading-tight')
 
         ui.html((STATIC / 'happy_face.svg').read_text()).classes('stroke-white').style('width: 1500px')
@@ -253,9 +289,9 @@ ui.run()
 def example_link(title: str, description: str) -> None:
     name = title.lower().replace(' ', '_')
     with ui.column().classes('gap-0'):
-        ui.link(title, f'https://github.com/zauberzeug/nicegui/tree/main/examples/{name}/main.py') \
-            .classes(replace='text-black text-bold')
-        ui.markdown(description)
+        with ui.link(target=f'https://github.com/zauberzeug/nicegui/tree/main/examples/{name}/main.py'):
+            ui.label(title).classes(replace='text-black text-bold')
+            ui.markdown(description).classes(replace='text-black')
 
 
 @ui.page('/reference')

+ 1 - 1
nicegui/functions/notify.py

@@ -21,7 +21,7 @@ def notify(message: str, *,
     :param type: optional type ("positive", "negative", "warning", "info" or "ongoing")
     :param color: optional color name
 
-    Note: You can pass additional keyword arguments according to [Quasar's Notify API](https://quasar.dev/quasar-plugins/notify#notify-api).
+    Note: You can pass additional keyword arguments according to `Quasar's Notify API <https://quasar.dev/quasar-plugins/notify#notify-api>`_.
     """
     options = {key: value for key, value in locals().items() if not key.startswith('_') and value is not None}
     create_task(globals.sio.emit('notify', options, room=str(globals.get_client().id)))

+ 15 - 3
poetry.lock

@@ -307,6 +307,14 @@ category = "dev"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "itsdangerous"
+version = "2.1.2"
+description = "Safely pass data to untrusted environments and back."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
 [[package]]
 name = "kiwisolver"
 version = "1.4.4"
@@ -693,7 +701,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
 
 [[package]]
 name = "selenium"
-version = "4.7.0"
+version = "4.7.2"
 description = ""
 category = "dev"
 optional = false
@@ -941,7 +949,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.7"
-content-hash = "1fd5c2160bd84817dbeadd7bf67102db075caa635897963771c4cca954682bd1"
+content-hash = "f4f7d8667e1d71c4bd364cabdc219d855492b8b24f69a7fda197c4f194e13e9b"
 
 [metadata.files]
 anyio = [
@@ -1192,6 +1200,10 @@ iniconfig = [
     {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
     {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
 ]
+itsdangerous = [
+    {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
+    {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
+]
 kiwisolver = [
     {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"},
     {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"},
@@ -1592,7 +1604,7 @@ requests = [
     {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
 ]
 selenium = [
-    {file = "selenium-4.7.0-py3-none-any.whl", hash = "sha256:771cb63b821a2aea01b0cdb623caa5b5c35b9a19f44d0d4f32a44b037a324b10"},
+    {file = "selenium-4.7.2-py3-none-any.whl", hash = "sha256:06a1c7d9f313130b21c3218ddd8852070d0e7419afdd31f96160cd576555a5ce"},
 ]
 setuptools = [
     {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"},

+ 2 - 1
pyproject.toml

@@ -24,7 +24,7 @@ fastapi-socketio = "^0.0.9"
 vbuild = "^0.8.1"
 watchfiles = "^0.18.1"
 
-[tool.poetry.dev-dependencies]
+[tool.poetry.group.dev.dependencies]
 icecream = "^2.1.0"
 autopep8 = "^1.5.7"
 debugpy = "^1.3.0"
@@ -32,6 +32,7 @@ pytest-selenium = "^4.0.0"
 pytest-asyncio = "^0.19.0"
 pytest = "6.2.5"
 replicate = "^0.4.0"
+itsdangerous = "^2.1.2" # required by SessionMiddleware (see https://fastapi.tiangolo.com/?h=itsdangerous#optional-dependencies)
 
 [build-system]
 requires = [

+ 1 - 0
tests/test_keyboard.py

@@ -15,4 +15,5 @@ def test_keyboard(screen: Screen):
     ActionChains(screen.selenium) \
         .send_keys('t') \
         .perform()
+    screen.wait(0.5)
     screen.should_contain('t, KeyboardAction(keydown=False, keyup=True, repeat=False)')

+ 3 - 3
website/demo_card.py

@@ -11,18 +11,18 @@ def create():
                 ui.html((STATIC / 'happy_face.svg').read_text()) \
                     .classes('w-16 mx-6 stroke-black').on('click', lambda _: output.set_text("That's my face!"))
                 ui.button('Click me!', on_click=lambda: output.set_text('Clicked')).classes('w-full')
+                ui.input('Text', value='abc', on_change=lambda e: output.set_text(e.value))
                 ui.checkbox('Check', on_change=lambda e: output.set_text('Checked' if e.value else 'Unchecked'))
                 ui.switch('Switch', on_change=lambda e: output.set_text('Switched on' if e.value else 'Switched off'))
-                ui.input('Text', value='abc', on_change=lambda e: output.set_text(e.value))
 
             with ui.column().classes('items-center'):
                 output = ui.label('Try it out!') \
                     .classes('w-44 my-6 h-8 text-xl text-grey-9 overflow-hidden text-ellipsis text-center')
                 ui.slider(min=0, max=100, value=50, step=0.1, on_change=lambda e: output.set_text(e.value)) \
                     .style('width: 150px')
-                ui.radio(['A', 'B', 'C'], value='A', on_change=lambda e: output.set_text(e.value)).props('inline')
-                ui.toggle(['1', '2', '3'], value='1', on_change=lambda e: output.set_text(e.value))
                 with ui.row().classes('mt-1'):
                     ui.number('Number', value=3.1415927, format='%.2f', on_change=lambda e: output.set_text(e.value)) \
                         .classes('w-20')
                     ui.select({1: 'One', 2: 'Two', 3: 'Three'}, value=1, on_change=lambda e: output.set_text(e.value))
+                ui.toggle(['1', '2', '3'], value='1', on_change=lambda e: output.set_text(e.value))
+                ui.radio(['A', 'B', 'C'], value='A', on_change=lambda e: output.set_text(e.value)).props('inline')

+ 5 - 1
website/example.py

@@ -94,7 +94,11 @@ def python_window() -> ui.card:
 
 
 def browser_window() -> ui.card:
-    return window('white')
+    with ui.card().style(f'box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); background: white') as card:
+        with ui.row():
+            ui.icon('language').classes('text-blue-400').style('font-size: 90%; margin: -4px 0px 0px -4px')
+            ui.label('localhost:8080').classes('text-blue-200').style('font-size: 60%; margin: -3px 0px 0px -12px')
+    return card
 
 
 def bash_window() -> ui.card:

+ 30 - 14
website/reference.py

@@ -6,14 +6,6 @@ from .example import example
 
 
 def create_intro() -> None:
-    @example('''#### Hello, World!
-
-Creating a user interface with NiceGUI is as simple as writing a single line of code.
-''', tight=True)
-    def hello_world_example():
-        ui.label('Hello, world!')
-        ui.markdown('Have a look at the full <br/> [API reference](reference)!')
-
     @example('''#### Common UI Elements
 
 NiceGUI comes with a collection of commonly used UI elements.
@@ -25,15 +17,37 @@ NiceGUI comes with a collection of commonly used UI elements.
         ui.input('Text input', on_change=lambda e: ui.notify(e.value))
         ui.radio(['A', 'B'], value='A', on_change=lambda e: ui.notify(e.value)).props('inline')
         ui.select(['One', 'Two'], value='One', on_change=lambda e: ui.notify(e.value))
-        ui.link('And many more...', '/reference').classes('text-lg')
+        ui.link('And many more...', '/reference')
+
+    @example('''#### Formatting
+
+
+''', tight=True)
+    def formatting_example():
+        ui.icon('thumb_up')
+        ui.markdown('''This is **Markdown**.''')
+        ui.html('This is <strong>HTML</strong>.')
+        with ui.row():
+            ui.label('css').style('color: #888; font-weight: bold')
+            ui.label('tailwind').classes('font-serif')
+            ui.label('quasar').classes('q-mt-md')
+        ui.link('NiceGUI on GitHub', 'https://github.com/zauberzeug/nicegui')
 
     @example('''#### Value Binding
 
 Binding values between UI elements or [to data models](http://127.0.0.1:8080/reference#bindings) is built into NiceGUI.
 ''', tight=True)
     def binding_example():
-        slider = ui.slider(min=0, max=100, value=50)
-        ui.number('Value').bind_value(slider, 'value').classes('fit')
+        class Demo:
+            def __init__(self):
+                self.number = 1
+
+        demo = Demo()
+        v = ui.checkbox('visible', value=True)
+        with ui.column().bind_visibility_from(v, 'value'):
+            ui.slider(min=1, max=3).bind_value(demo, 'number')
+            ui.toggle({1: 'A', 2: 'B', 3: 'C'}).bind_value(demo, 'number')
+            ui.number().bind_value(demo, 'number')
 
 
 def create_full() -> None:
@@ -540,8 +554,9 @@ The example code shows both methods for a `ui.chart`, where it is difficult to a
             chart.options['series'][0]['data'].clear()
             ui.update(chart)
 
-        ui.button('Add', on_click=add)
-        ui.button('Clear', on_click=clear)
+        with ui.row():
+            ui.button('Add', on_click=add)
+            ui.button('Clear', on_click=clear)
 
     @example('''#### Async event handlers
 
@@ -679,10 +694,11 @@ It also enables you to identify sessions using a [session middleware](https://ww
         from collections import Counter
         from datetime import datetime
 
-        from nicegui import app
         from starlette.middleware.sessions import SessionMiddleware
         from starlette.requests import Request
 
+        from nicegui import app
+
         app.add_middleware(SessionMiddleware, secret_key='some_random_string')
 
         counter = Counter()