|
4 | 4 | import logging |
5 | 5 | from contextlib import AsyncExitStack |
6 | 6 | from types import TracebackType |
7 | | -from typing import Any, Optional, Tuple, Type, Union |
| 7 | +from typing import Any, Callable, Optional, Tuple, Type, Union |
8 | 8 | from urllib.parse import urlencode, urlunparse |
9 | 9 |
|
| 10 | +from idom import component, use_effect, use_state |
10 | 11 | from idom.backend import default as default_server |
11 | 12 | from idom.backend.types import BackendImplementation |
12 | 13 | from idom.backend.utils import find_available_port |
13 | | -from idom.widgets import hotswap |
| 14 | +from idom.core.hooks import use_callback |
| 15 | +from idom.core.types import ComponentConstructor |
| 16 | +from idom.utils import Ref |
14 | 17 |
|
15 | 18 | from .logs import LogAssertionError, capture_idom_logs, list_logged_exceptions |
16 | 19 |
|
@@ -41,7 +44,7 @@ def __init__( |
41 | 44 | ) -> None: |
42 | 45 | self.host = host |
43 | 46 | self.port = port or find_available_port(host, allow_reuse_waiting_ports=False) |
44 | | - self.mount, self._root_component = hotswap() |
| 47 | + self.mount, self._root_component = _hotswap() |
45 | 48 |
|
46 | 49 | if app is not None: |
47 | 50 | if implementation is None: |
@@ -146,3 +149,69 @@ async def __aexit__( |
146 | 149 | raise LogAssertionError("Unexpected logged exception") from logged_errors[0] |
147 | 150 |
|
148 | 151 | return None |
| 152 | + |
| 153 | + |
| 154 | +_MountFunc = Callable[["Callable[[], Any] | None"], None] |
| 155 | + |
| 156 | + |
| 157 | +def _hotswap(update_on_change: bool = False) -> Tuple[_MountFunc, ComponentConstructor]: |
| 158 | + """Swap out components from a layout on the fly. |
| 159 | +
|
| 160 | + Since you can't change the component functions used to create a layout |
| 161 | + in an imperative manner, you can use ``hotswap`` to do this so |
| 162 | + long as you set things up ahead of time. |
| 163 | +
|
| 164 | + Parameters: |
| 165 | + update_on_change: Whether or not all views of the layout should be udpated on a swap. |
| 166 | +
|
| 167 | + Example: |
| 168 | + .. code-block:: python |
| 169 | +
|
| 170 | + import idom |
| 171 | +
|
| 172 | + show, root = idom.hotswap() |
| 173 | + PerClientStateServer(root).run_in_thread("localhost", 8765) |
| 174 | +
|
| 175 | + @idom.component |
| 176 | + def DivOne(self): |
| 177 | + return {"tagName": "div", "children": [1]} |
| 178 | +
|
| 179 | + show(DivOne) |
| 180 | +
|
| 181 | + # displaying the output now will show DivOne |
| 182 | +
|
| 183 | + @idom.component |
| 184 | + def DivTwo(self): |
| 185 | + return {"tagName": "div", "children": [2]} |
| 186 | +
|
| 187 | + show(DivTwo) |
| 188 | +
|
| 189 | + # displaying the output now will show DivTwo |
| 190 | + """ |
| 191 | + constructor_ref: Ref[Callable[[], Any]] = Ref(lambda: None) |
| 192 | + |
| 193 | + set_constructor_callbacks: set[Callable[[Callable[[], Any]], None]] = set() |
| 194 | + |
| 195 | + @component |
| 196 | + def HotSwap() -> Any: |
| 197 | + # new displays will adopt the latest constructor and arguments |
| 198 | + constructor, _set_constructor = use_state(lambda: constructor_ref.current) |
| 199 | + set_constructor = use_callback(lambda new: _set_constructor(lambda _: new)) |
| 200 | + |
| 201 | + def add_callback() -> Callable[[], None]: |
| 202 | + set_constructor_callbacks.add(set_constructor) |
| 203 | + return lambda: set_constructor_callbacks.remove(set_constructor) |
| 204 | + |
| 205 | + use_effect(add_callback) |
| 206 | + |
| 207 | + return constructor() |
| 208 | + |
| 209 | + def swap(constructor: Callable[[], Any] | None) -> None: |
| 210 | + constructor = constructor_ref.current = constructor or (lambda: None) |
| 211 | + |
| 212 | + for set_constructor in set_constructor_callbacks: |
| 213 | + set_constructor(constructor) |
| 214 | + |
| 215 | + return None |
| 216 | + |
| 217 | + return swap, HotSwap |
0 commit comments