Skip to content

Commit 2570d84

Browse files
committed
Merge branch 'spa-content' into pr/Alyxion/2811
2 parents bbb4580 + e8dfef9 commit 2570d84

15 files changed

Lines changed: 231 additions & 201 deletions

File tree

examples/authentication_spa/main.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
#!/usr/bin/env python3
22
import html
33
import uuid
4-
from typing import Optional, Union
54

65
from nicegui import app, ui
7-
from nicegui.single_page_target import SinglePageTarget
86

97
INDEX_URL = '/'
108
SECRET_AREA_URL = '/secret_area'
119

12-
DUMMY_LOGINS = {"admin": "NicePass"}
10+
DUMMY_LOGINS = {'admin': 'NicePass'}
1311

1412

1513
def verify_authentication(url) -> str:
@@ -19,7 +17,7 @@ def verify_authentication(url) -> str:
1917
return '/login'
2018

2119

22-
@ui.outlet('/', on_navigate=verify_authentication)
20+
@ui.content('/', on_navigate=verify_authentication)
2321
def main_layout():
2422
with ui.header():
2523
ui.link('SPA Login Example', '/').style('text-decoration: none; color: inherit;').classes('text-h3')

examples/single_page_app_complex/main.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Advanced demo showing how to use the ui.outlet and outlet.view decorators to create a nested multi-page app with a
1+
# Advanced demo showing how to use the ui.content decorators to create a nested multi-page app with a
22
# static header, footer and menu which is shared across all pages and hidden when the user navigates to the root page.
33

44
from examples.single_page_app_complex.cms_config import ServiceDefinition, SubServiceDefinition, services
@@ -8,7 +8,7 @@
88
# --- Other app ---
99

1010

11-
@ui.outlet('/other_app') # Needs to be defined before the main outlet / to avoid conflicts
11+
@ui.content('/other_app') # Needs to be defined before the / to avoid conflicts
1212
async def other_app_router():
1313
ui.label('Other app header').classes('text-h2')
1414
ui.html('<hr>')
@@ -17,14 +17,14 @@ async def other_app_router():
1717
ui.label('Other app footer')
1818

1919

20-
@other_app_router.view('/')
20+
@other_app_router.content('/')
2121
async def other_app_index():
2222
ui.label('Welcome to the index page of the other application')
2323

2424

2525
# --- Main app ---
2626

27-
@ui.outlet('/') # main app outlet
27+
@ui.content('/') # main app
2828
async def main_router(url_path: str):
2929
with ui.header():
3030
with ui.link('', '/').style('text-decoration: none; color: inherit;'):
@@ -36,10 +36,10 @@ async def main_router(url_path: str):
3636
with ui.element('a').props('href="/about"'):
3737
ui.label('Copyright 2024 by NiceCLOUD Inc.').classes('text-h7')
3838
with ui.element().classes('p-8'):
39-
yield {'menu_drawer': menu_drawer} # pass menu drawer to all sub elements (views and outlets)
39+
yield {'menu_drawer': menu_drawer} # pass menu drawer to all sub elements
4040

4141

42-
@main_router.view('/')
42+
@main_router.content('/')
4343
async def main_index(menu_drawer: LeftDrawer): # main app index page
4444
menu_drawer.clear() # clear drawer
4545
menu_drawer.hide() # hide drawer
@@ -61,7 +61,7 @@ async def main_index(menu_drawer: LeftDrawer): # main app index page
6161
ui.markdown('Click [here](/other_app) to visit the other app.')
6262

6363

64-
@main_router.view('/about')
64+
@main_router.content('/about')
6565
async def about_page(menu_drawer: LeftDrawer):
6666
menu_drawer.clear()
6767
menu_drawer.hide()
@@ -73,7 +73,7 @@ async def about_page(menu_drawer: LeftDrawer):
7373
ui.label('Nice Country')
7474

7575

76-
@main_router.outlet('/services/{service_name}') # service outlet
76+
@main_router.content('/services/{service_name}')
7777
async def services_router(service_name: str, menu_drawer: LeftDrawer):
7878
service: ServiceDefinition = services[service_name]
7979
menu_drawer.clear()
@@ -92,7 +92,7 @@ async def services_router(service_name: str, menu_drawer: LeftDrawer):
9292
service_element.classes('text-white text-h6 bg-gray cursor-pointer')
9393
service_element.style('text-shadow: 2px 2px #00000070;')
9494
service_element.on('click', lambda url=f'/services/{service_name}/{key}': ui.navigate.to(url))
95-
yield {'service': service} # pass service object to all sub elements (views and outlets)
95+
yield {'service': service} # pass service object to all sub elements
9696

9797

9898
def update_title(service: ServiceDefinition = None,
@@ -102,7 +102,7 @@ def update_title(service: ServiceDefinition = None,
102102
ui.page_title(new_title)
103103

104104

105-
@services_router.view('/') # service index page
105+
@services_router.content('/') # service index page
106106
async def show_index(service: ServiceDefinition):
107107
update_title(service, None)
108108
with ui.row():
@@ -113,15 +113,15 @@ async def show_index(service: ServiceDefinition):
113113
ui.html('<br>')
114114

115115

116-
@services_router.outlet('/{sub_service_name}') # sub service outlet
116+
@services_router.content('/{sub_service_name}')
117117
async def sub_service_router(service: ServiceDefinition, sub_service_name: str):
118118
sub_service: SubServiceDefinition = service.sub_services[sub_service_name]
119119
ui.label(f'{service.title} > {sub_service.title}').classes('text-h4')
120120
ui.html('<br>')
121-
yield {'sub_service': sub_service} # pass sub_service object to all sub elements (views and outlets)
121+
yield {'sub_service': sub_service} # pass sub_service object to all sub elements
122122

123123

124-
@sub_service_router.view('/') # sub service index page
124+
@sub_service_router.content('/')
125125
async def sub_service_index(service: ServiceDefinition, sub_service: SubServiceDefinition):
126126
update_title(service, sub_service)
127127
ui.label(sub_service.emoji).classes('text-h1')

nicegui/builder_utils.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,13 @@ def run_safe(builder, type_check: bool = True, **kwargs) -> Any:
4545
raise TypeError(
4646
f'Unsupported type annotation {expected_type} for parameter {func_param_name}')
4747
else: # noqa: PLR5501
48+
# query params are always lists, so we unpack single values if the type matches
49+
if isinstance(value, list) and len(value) == 1 and isinstance(value[0], expected_type):
50+
kwargs[func_param_name] = value[0]
51+
4852
# Non-generic types
49-
if not isinstance(value, expected_type):
50-
raise ValueError(f'Invalid type for parameter {func_param_name}, expected {expected_type}')
53+
elif not isinstance(value, expected_type):
54+
raise ValueError(f'Invalid type for parameter {func_param_name}, '
55+
f'expected {expected_type} but got {type(value)}')
5156
filtered = {k: v for k, v in kwargs.items() if k in args} if not has_kwargs else kwargs
5257
return builder(**filtered)

nicegui/client.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@
2828
from .version import __version__
2929

3030
if TYPE_CHECKING:
31-
from nicegui.outlet import Outlet
32-
31+
from .content import Content
3332
from .page import page
3433
from .single_page_router import SinglePageRouter
3534

@@ -43,7 +42,7 @@ class Client:
4342
page_configs: ClassVar[Dict[Callable[..., Any], page]] = {}
4443
'''Maps page builders to their page configuration.'''
4544

46-
top_level_outlets: ClassVar[Dict[str, Outlet]] = {}
45+
top_level_content: ClassVar[Dict[str, Content]] = {}
4746
'''Maps paths to the associated single page routers.'''
4847

4948
instances: ClassVar[Dict[str, Client]] = {}
@@ -242,9 +241,9 @@ def open(self, target: Union[Callable[..., Any], str], new_tab: bool = False) ->
242241
"""Open a new page in the client."""
243242
path = target if isinstance(target, str) else self.page_routes[target]
244243
if not new_tab and context.client.single_page_router is not None:
245-
for outlet in self.top_level_outlets.values():
246-
outlet_target = outlet.resolve_target(path)
247-
if outlet_target.valid and context.client.single_page_router.is_path_included(path):
244+
for content in self.top_level_content.values():
245+
content_target = content.resolve_target(path)
246+
if content_target.valid and context.client.single_page_router.is_path_included(path):
248247
context.client.single_page_router.navigate_to(path)
249248
return
250249
self.outbox.enqueue_message('open', {'path': path, 'new_tab': new_tab}, self.id)

0 commit comments

Comments
 (0)