diff --git a/nicegui/elements/group.py b/nicegui/elements/group.py index 8aa200c69..05c1caca7 100644 --- a/nicegui/elements/group.py +++ b/nicegui/elements/group.py @@ -12,13 +12,11 @@ class Group(Element): def __enter__(self): - globals.view_stack.append(self.view) + globals.get_view_stack().append(self.view) return self def __exit__(self, *_): - globals.view_stack.pop() - if len(globals.view_stack) <= 1: - self.update() # NOTE: update when we are back on top of the stack (only the first page is in view stack) + globals.get_view_stack().pop() def tight(self) -> Group: return self.classes(replace='').style(replace='') diff --git a/nicegui/elements/scene.py b/nicegui/elements/scene.py index aad1ef501..f040c5dc6 100644 --- a/nicegui/elements/scene.py +++ b/nicegui/elements/scene.py @@ -108,14 +108,14 @@ def __init__(self, width: int = 400, height: int = 300, on_click: Optional[Calla super().__init__(SceneView(width=width, height=height, on_click=on_click)) def __enter__(self): - globals.view_stack.append(self.view) + globals.get_view_stack().append(self.view) scene = self.view.objects.get('scene', SceneObject(self.view, self.page)) Object3D.stack.clear() Object3D.stack.append(scene) return self def __exit__(self, *_): - globals.view_stack.pop() + globals.get_view_stack().pop() def move_camera(self, x: Optional[float] = None, diff --git a/nicegui/globals.py b/nicegui/globals.py index c42cc97a2..16f16bdfe 100644 --- a/nicegui/globals.py +++ b/nicegui/globals.py @@ -18,7 +18,7 @@ server: Optional[Server] = None loop: Optional[asyncio.AbstractEventLoop] = None page_builders: Dict[str, 'PageBuilder'] = {} -view_stack: List[jp.HTMLBaseComponent] = [] +view_stacks: Dict[List[jp.HTMLBaseComponent]] = {} tasks: List[asyncio.tasks.Task] = [] log: logging.Logger = logging.getLogger('nicegui') connect_handlers: List[Union[Callable, Awaitable]] = [] @@ -34,11 +34,29 @@ def find_route(function: Callable) -> str: return routes[0] +def get_task_id() -> int: + return id(asyncio.current_task()) if loop and loop.is_running() else 0 + + +def get_view_stack() -> List[jp.HTMLBaseComponent]: + task_id = get_task_id() + if task_id not in view_stacks: + view_stacks[task_id] = [] + return view_stacks[task_id] + + +def prune_view_stack() -> None: + task_id = get_task_id() + if not view_stacks[task_id]: + del view_stacks[task_id] + + @contextmanager def within_view(view: jp.HTMLBaseComponent) -> Generator[None, None, None]: child_count = len(view) - view_stack.append(view) + get_view_stack().append(view) yield - view_stack.pop() + get_view_stack().pop() + prune_view_stack() if len(view) != child_count: create_task(view.update()) diff --git a/nicegui/page.py b/nicegui/page.py index 54bd1d8f1..094a76287 100644 --- a/nicegui/page.py +++ b/nicegui/page.py @@ -168,9 +168,8 @@ async def decorated(): on_disconnect=on_disconnect, shared=shared, ) - globals.view_stack.append(page.view) - await func() if is_coroutine(func) else func() - globals.view_stack.pop() + with globals.within_view(page.view): + await func() if is_coroutine(func) else func() return page builder = PageBuilder(decorated, shared) if globals.server: @@ -181,13 +180,14 @@ async def decorated(): def find_parent_view() -> jp.HTMLBaseComponent: - if not globals.view_stack: + view_stack = globals.get_view_stack() + if not view_stack: if globals.loop and globals.loop.is_running(): raise RuntimeError('cannot find parent view, view stack is empty') page = Page(shared=True) - globals.view_stack.append(page.view) + view_stack.append(page.view) jp.Route('/', page._route_function) - return globals.view_stack[-1] + return view_stack[-1] def error404() -> jp.QuasarPage: @@ -204,14 +204,15 @@ def error404() -> jp.QuasarPage: def init_auto_index_page() -> None: - if not globals.view_stack: + view_stack = globals.get_view_stack() + if not view_stack: return # there is no auto-index page on the view stack - page: Page = globals.view_stack.pop().pages[0] + page: Page = view_stack.pop().pages[0] page.title = globals.config.title page.favicon = globals.config.favicon page.dark = globals.config.dark page.view.classes = globals.config.main_page_classes - assert len(globals.view_stack) == 0 + assert len(view_stack) == 0 def create_page_routes() -> None: