solara-ui 1.31.0__py2.py3-none-any.whl → 1.32.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- solara/__init__.py +1 -1
- solara/components/applayout.py +6 -2
- solara/components/datatable.py +5 -12
- solara/components/markdown.py +38 -27
- solara/lab/hooks/dataframe.py +1 -12
- solara/lab/utils/dataframe.py +40 -0
- solara/minisettings.py +13 -5
- solara/server/flask.py +3 -5
- solara/server/kernel_context.py +110 -60
- solara/server/server.py +8 -4
- solara/server/settings.py +23 -1
- solara/server/starlette.py +4 -5
- solara/server/static/main-vuetify.js +3 -1
- solara/server/static/solara_bootstrap.py +1 -1
- solara/server/templates/solara.html.j2 +4 -4
- solara/tasks.py +19 -10
- solara/toestand.py +22 -13
- solara/website/assets/custom.css +13 -0
- solara/website/components/algolia.py +6 -0
- solara/website/components/algolia_api.vue +2 -1
- solara/website/components/header.py +9 -17
- solara/website/components/notebook.py +1 -1
- solara/website/components/sidebar.py +91 -0
- solara/website/pages/__init__.py +25 -67
- solara/website/pages/changelog/__init__.py +2 -0
- solara/website/pages/changelog/changelog.md +25 -0
- solara/website/pages/contact/__init__.py +2 -0
- solara/website/pages/documentation/__init__.py +2 -88
- solara/website/pages/documentation/advanced/content/10-howto/30-testing.md +267 -16
- solara/website/pages/documentation/advanced/content/15-reference/41-asset-files.md +36 -0
- solara/website/pages/documentation/advanced/content/20-understanding/40-routing.md +4 -4
- solara/website/pages/documentation/advanced/content/30-enterprise/10-oauth.md +11 -2
- solara/website/pages/documentation/advanced/content/40-development/10-setup.md +1 -1
- solara/website/pages/documentation/faq/content/99-faq.md +27 -0
- solara/website/pages/documentation/getting_started/content/02-installing.md +2 -2
- solara/website/pages/documentation/getting_started/content/04-tutorials/60-jupyter-dashboard-part1.py +50 -49
- solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +20 -4
- {solara_ui-1.31.0.dist-info → solara_ui-1.32.1.dist-info}/METADATA +2 -2
- {solara_ui-1.31.0.dist-info → solara_ui-1.32.1.dist-info}/RECORD +43 -41
- {solara_ui-1.31.0.data → solara_ui-1.32.1.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
- {solara_ui-1.31.0.data → solara_ui-1.32.1.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
- {solara_ui-1.31.0.dist-info → solara_ui-1.32.1.dist-info}/WHEEL +0 -0
- {solara_ui-1.31.0.dist-info → solara_ui-1.32.1.dist-info}/licenses/LICENSE +0 -0
solara/tasks.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import asyncio
|
|
3
3
|
import dataclasses
|
|
4
|
+
import functools
|
|
4
5
|
import inspect
|
|
5
6
|
import logging
|
|
6
7
|
import threading
|
|
@@ -76,12 +77,13 @@ class TaskResult(Generic[T]):
|
|
|
76
77
|
|
|
77
78
|
|
|
78
79
|
class Task(Generic[P, R], abc.ABC):
|
|
79
|
-
def __init__(self):
|
|
80
|
-
self._result = solara.
|
|
80
|
+
def __init__(self, key: str):
|
|
81
|
+
self._result = solara.Reactive(
|
|
81
82
|
TaskResult[R](
|
|
82
83
|
value=None,
|
|
83
84
|
_state=TaskState.NOTCALLED,
|
|
84
|
-
)
|
|
85
|
+
),
|
|
86
|
+
key="solara.tasks:TaskResult:" + key,
|
|
85
87
|
)
|
|
86
88
|
self._last_value: Optional[R] = None
|
|
87
89
|
self._last_progress: Optional[float] = None
|
|
@@ -171,10 +173,10 @@ class TaskAsyncio(Task[P, R]):
|
|
|
171
173
|
_cancel: Optional[Callable[[], None]] = None
|
|
172
174
|
_retry: Optional[Callable[[], None]] = None
|
|
173
175
|
|
|
174
|
-
def __init__(self, run_in_thread: bool, function: Callable[P, Coroutine[Any, Any, R]]):
|
|
176
|
+
def __init__(self, run_in_thread: bool, function: Callable[P, Coroutine[Any, Any, R]], key: str):
|
|
175
177
|
self.run_in_thread = run_in_thread
|
|
176
178
|
self.function = function
|
|
177
|
-
super().__init__()
|
|
179
|
+
super().__init__(key)
|
|
178
180
|
|
|
179
181
|
def cancel(self) -> None:
|
|
180
182
|
if self._cancel:
|
|
@@ -290,8 +292,8 @@ class TaskThreaded(Task[P, R]):
|
|
|
290
292
|
_cancel: Optional[Callable[[], None]] = None
|
|
291
293
|
_retry: Optional[Callable[[], None]] = None
|
|
292
294
|
|
|
293
|
-
def __init__(self, function: Callable[P, R]):
|
|
294
|
-
super().__init__()
|
|
295
|
+
def __init__(self, function: Callable[P, R], key: str):
|
|
296
|
+
super().__init__(key)
|
|
295
297
|
self.__qualname__ = function.__qualname__
|
|
296
298
|
self.function = function
|
|
297
299
|
self.lock = threading.Lock()
|
|
@@ -667,11 +669,14 @@ def task(
|
|
|
667
669
|
"""
|
|
668
670
|
|
|
669
671
|
def wrapper(f: Union[None, Callable[P, Union[Coroutine[Any, Any, R], R]]]) -> Task[P, R]:
|
|
672
|
+
# we use wraps to make the key of the reactive variable more unique
|
|
673
|
+
# and less likely to mixup during hot reloads
|
|
674
|
+
@functools.wraps(f) # type: ignore
|
|
670
675
|
def create_task():
|
|
671
676
|
if inspect.iscoroutinefunction(f):
|
|
672
|
-
return TaskAsyncio[P, R](prefer_threaded and has_threads, f)
|
|
677
|
+
return TaskAsyncio[P, R](prefer_threaded and has_threads, f, key=solara.toestand._create_key_callable(create_task))
|
|
673
678
|
else:
|
|
674
|
-
return TaskThreaded[P, R](cast(Callable[P, R], f))
|
|
679
|
+
return TaskThreaded[P, R](cast(Callable[P, R], f), key=solara.toestand._create_key_callable(create_task))
|
|
675
680
|
|
|
676
681
|
return cast(Task[P, R], Proxy(create_task))
|
|
677
682
|
|
|
@@ -806,7 +811,10 @@ def use_task(
|
|
|
806
811
|
"""
|
|
807
812
|
|
|
808
813
|
def wrapper(f):
|
|
809
|
-
|
|
814
|
+
def create_task() -> Task[P, R]:
|
|
815
|
+
return task(f, prefer_threaded=prefer_threaded)
|
|
816
|
+
|
|
817
|
+
task_instance = solara.use_memo(create_task, dependencies=[])
|
|
810
818
|
# we always update the function so we do not have stale data in the function
|
|
811
819
|
task_instance.function = f # type: ignore
|
|
812
820
|
|
|
@@ -828,6 +836,7 @@ def use_task(
|
|
|
828
836
|
solara.use_effect(run, dependencies=dependencies)
|
|
829
837
|
if raise_error:
|
|
830
838
|
if task_instance.error:
|
|
839
|
+
assert task_instance.exception is not None
|
|
831
840
|
raise task_instance.exception
|
|
832
841
|
return task_instance
|
|
833
842
|
|
solara/toestand.py
CHANGED
|
@@ -77,7 +77,12 @@ def merge_state(d1: S, **kwargs) -> S:
|
|
|
77
77
|
if dataclasses.is_dataclass(d1):
|
|
78
78
|
return dataclasses.replace(d1, **kwargs) # type: ignore
|
|
79
79
|
if "pydantic" in sys.modules and isinstance(d1, sys.modules["pydantic"].BaseModel):
|
|
80
|
-
|
|
80
|
+
module = sys.modules["pydantic"]
|
|
81
|
+
version_major = int(module.__version__.split(".")[0])
|
|
82
|
+
if version_major >= 2:
|
|
83
|
+
return d1.model_copy(update=kwargs)
|
|
84
|
+
else:
|
|
85
|
+
return d1.copy(update=kwargs)
|
|
81
86
|
return cast(S, {**cast(dict, d1), **kwargs})
|
|
82
87
|
|
|
83
88
|
|
|
@@ -282,21 +287,25 @@ class KernelStoreValue(KernelStore[S]):
|
|
|
282
287
|
return self.default_value
|
|
283
288
|
|
|
284
289
|
|
|
290
|
+
def _create_key_callable(f: Callable[[], S]):
|
|
291
|
+
try:
|
|
292
|
+
prefix = f.__qualname__
|
|
293
|
+
except Exception:
|
|
294
|
+
prefix = repr(f)
|
|
295
|
+
with KernelStore.scope_lock:
|
|
296
|
+
index = KernelStore._type_counter[prefix]
|
|
297
|
+
KernelStore._type_counter[prefix] += 1
|
|
298
|
+
try:
|
|
299
|
+
key = f.__module__ + ":" + prefix + ":" + str(index)
|
|
300
|
+
except Exception:
|
|
301
|
+
key = prefix + ":" + str(index)
|
|
302
|
+
return key
|
|
303
|
+
|
|
304
|
+
|
|
285
305
|
class KernelStoreFactory(KernelStore[S]):
|
|
286
306
|
def __init__(self, factory: Callable[[], S], key=None):
|
|
287
307
|
self.factory = factory
|
|
288
|
-
|
|
289
|
-
prefix = factory.__qualname__
|
|
290
|
-
except Exception:
|
|
291
|
-
prefix = repr(factory)
|
|
292
|
-
if key is None:
|
|
293
|
-
with KernelStore.scope_lock:
|
|
294
|
-
index = self._type_counter[prefix]
|
|
295
|
-
self._type_counter[prefix] += 1
|
|
296
|
-
try:
|
|
297
|
-
key = factory.__module__ + ":" + prefix + ":" + str(index)
|
|
298
|
-
except Exception:
|
|
299
|
-
key = prefix + ":" + str(index)
|
|
308
|
+
key = key or _create_key_callable(factory)
|
|
300
309
|
super().__init__(key=key)
|
|
301
310
|
|
|
302
311
|
def initial_value(self) -> S:
|
solara/website/assets/custom.css
CHANGED
|
@@ -239,6 +239,16 @@ blockquote p:last-child {
|
|
|
239
239
|
border-radius: 8px;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
.v-navigation-drawer__content .algolia {
|
|
243
|
+
padding: 0.35rem;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@media screen and (max-width: 960px) {
|
|
247
|
+
.header-logo-container{
|
|
248
|
+
justify-content: center !important;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
242
252
|
/* vuetify component overrides */
|
|
243
253
|
|
|
244
254
|
.v-btn.solara-docs-button {
|
|
@@ -262,6 +272,9 @@ blockquote p:last-child {
|
|
|
262
272
|
}
|
|
263
273
|
|
|
264
274
|
/* header */
|
|
275
|
+
.header-logo {
|
|
276
|
+
display: block;
|
|
277
|
+
}
|
|
265
278
|
.news {
|
|
266
279
|
background-color: var(--color-primary);
|
|
267
280
|
padding: 10px;
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
rounded
|
|
13
13
|
clearable
|
|
14
14
|
ref="search"
|
|
15
|
-
style="
|
|
15
|
+
style="flex-grow: 1; max-width: 650px;"
|
|
16
16
|
@click="show($event, on);"
|
|
17
17
|
@keyup.enter="item = 0"
|
|
18
|
+
class="algolia"
|
|
18
19
|
></v-text-field>
|
|
19
20
|
</template>
|
|
20
21
|
<v-list v-if="results != null && results.length == 0">
|
|
@@ -4,11 +4,7 @@ import solara
|
|
|
4
4
|
import solara.lab
|
|
5
5
|
from solara.alias import rv
|
|
6
6
|
from solara.server import settings
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@solara.component_vue("algolia_api.vue")
|
|
10
|
-
def Algolia():
|
|
11
|
-
pass
|
|
7
|
+
from .algolia import Algolia
|
|
12
8
|
|
|
13
9
|
|
|
14
10
|
@solara.component
|
|
@@ -31,21 +27,17 @@ def Header(
|
|
|
31
27
|
)
|
|
32
28
|
with solara.v.AppBar(tag="header", flat=True, class_="bg-primary-fade padding-40", height="auto", clipped_left=True):
|
|
33
29
|
with rv.ToolbarTitle(class_="d-flex", style_="align-items:center"):
|
|
34
|
-
if route_current and
|
|
30
|
+
if route_current is not None and route_current.module is not None and hasattr(route_current.module, "Sidebar"):
|
|
35
31
|
with solara.Button(icon=True, class_="hidden-md-and-up", on_click=lambda: on_toggle_left_menu and on_toggle_left_menu()):
|
|
36
32
|
rv.Icon(children=["mdi-menu"])
|
|
37
|
-
with solara.Link(path_or_route="/"):
|
|
38
|
-
solara.Image(router.root_path + f"/static/assets/images/logo{'_white' if dark_effective else ''}.svg")
|
|
39
|
-
rv.Spacer()
|
|
40
33
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
and route_current is not None
|
|
44
|
-
and route_current.path == "documentation"
|
|
45
|
-
and route_current_with_children.path == "/"
|
|
34
|
+
with solara.Row(
|
|
35
|
+
justify="start", classes=["header-logo-container"], style={"flex-grow": "1", "background-color": "transparent", "align-items": "center"}
|
|
46
36
|
):
|
|
47
|
-
solara.
|
|
48
|
-
|
|
37
|
+
with solara.Link(path_or_route="/"):
|
|
38
|
+
solara.Image(router.root_path + f"/static/assets/images/logo{'_white' if dark_effective else ''}.svg", classes=["header-logo"])
|
|
39
|
+
|
|
40
|
+
with rv.Html(tag="ul", class_="main-menu menu d-none d-md-flex", style_="flex-grow: 1;"):
|
|
49
41
|
if settings.search.enabled:
|
|
50
42
|
from solara_enterprise.search.search import Search
|
|
51
43
|
|
|
@@ -53,7 +45,7 @@ def Header(
|
|
|
53
45
|
else:
|
|
54
46
|
with solara.Row(justify="end", style={"align-items": "center", "flex-grow": "1", "background-color": "transparent"}):
|
|
55
47
|
Algolia()
|
|
56
|
-
|
|
48
|
+
|
|
57
49
|
for route in all_routes:
|
|
58
50
|
if route.path in ["apps", "contact", "changelog"]:
|
|
59
51
|
continue
|
|
@@ -133,7 +133,7 @@ def Notebook(notebook_path: Path, show_last_expressions=False, auto_show_page=Fa
|
|
|
133
133
|
if execute:
|
|
134
134
|
return NotebookExecute(notebook_path, show_last_expressions, auto_show_page)
|
|
135
135
|
else:
|
|
136
|
-
with solara.Column(style={"max-width": "
|
|
136
|
+
with solara.Column(style={"max-width": "1024px"}) as main:
|
|
137
137
|
nb: nbformat.NotebookNode = nbformat.read(notebook_path, 4)
|
|
138
138
|
for cell_index, cell in enumerate(nb.cells):
|
|
139
139
|
cell_index += 1
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import solara
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@solara.component
|
|
5
|
+
def Sidebar():
|
|
6
|
+
route_current, all_routes = solara.use_route(-1)
|
|
7
|
+
router = solara.use_router()
|
|
8
|
+
if route_current is None:
|
|
9
|
+
return solara.Error("Page not found")
|
|
10
|
+
|
|
11
|
+
# Pick out documentation route, so we can use this on any page
|
|
12
|
+
for route in all_routes:
|
|
13
|
+
if route.path == "documentation":
|
|
14
|
+
all_routes = route.children
|
|
15
|
+
break
|
|
16
|
+
|
|
17
|
+
with solara.v.List(expand=True, nav=True, style_="height: 100%; display: flex; flex-direction: column;") as main:
|
|
18
|
+
with solara.v.ListItemGroup(v_model=router.path):
|
|
19
|
+
for route in all_routes:
|
|
20
|
+
if len(route.children) == 1 or route.path == "/":
|
|
21
|
+
with solara.Link("/documentation/" + route.path if route.path != "/" else "/documentation"):
|
|
22
|
+
with solara.v.ListItem(value="/documentation/" + route.path if route.path != "/" else "/documentation"):
|
|
23
|
+
if route.path == "/":
|
|
24
|
+
solara.v.ListItemIcon(children=[solara.v.Icon(children=["mdi-home"])])
|
|
25
|
+
solara.v.ListItemTitle(style_="padding: 0 20px;", children=[route.label])
|
|
26
|
+
else:
|
|
27
|
+
with solara.v.ListGroup(
|
|
28
|
+
v_slots=[
|
|
29
|
+
{
|
|
30
|
+
"name": "activator",
|
|
31
|
+
"children": solara.v.ListItemTitle(
|
|
32
|
+
children=[route.label],
|
|
33
|
+
style_="padding: 0 20px;",
|
|
34
|
+
),
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
value=router.path.startswith("/documentation/" + route.path),
|
|
38
|
+
):
|
|
39
|
+
for item in route.children:
|
|
40
|
+
if item.path == "/":
|
|
41
|
+
continue
|
|
42
|
+
if item.children != [] and any([c.label is not None and c.path != "/" for c in item.children]):
|
|
43
|
+
with solara.v.ListGroup(
|
|
44
|
+
v_slots=[
|
|
45
|
+
{
|
|
46
|
+
"name": "activator",
|
|
47
|
+
"children": solara.v.ListItemTitle(
|
|
48
|
+
children=[item.label],
|
|
49
|
+
),
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
sub_group=True,
|
|
53
|
+
no_action=True,
|
|
54
|
+
value=router.path.startswith("/documentation/" + route.path + "/" + item.path),
|
|
55
|
+
):
|
|
56
|
+
for subitem in item.children:
|
|
57
|
+
# skip pages that are only used to demonstrate Link or Router usage
|
|
58
|
+
if subitem.path == "/" or subitem.label is None:
|
|
59
|
+
continue
|
|
60
|
+
path = (
|
|
61
|
+
"/documentation/" + route.path + "/" + item.path + "/" + subitem.path
|
|
62
|
+
if item.path != "fullscreen"
|
|
63
|
+
else "/apps/" + subitem.path
|
|
64
|
+
)
|
|
65
|
+
with solara.Link(
|
|
66
|
+
path,
|
|
67
|
+
):
|
|
68
|
+
with solara.v.ListItem(dense=True, style_="padding: 0 20px;", value=path):
|
|
69
|
+
solara.v.ListItemContent(
|
|
70
|
+
children=[subitem.label],
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
with solara.v.ListItemGroup(value="/documentation/" + route.path + "/" + item.path):
|
|
74
|
+
with solara.Link(
|
|
75
|
+
"/documentation/" + route.path + "/" + item.path,
|
|
76
|
+
):
|
|
77
|
+
with solara.v.ListItem(dense=True, style_="padding: 0 20px;"):
|
|
78
|
+
solara.v.ListItemContent(
|
|
79
|
+
children=[item.label],
|
|
80
|
+
)
|
|
81
|
+
with solara.v.ListItemGroup():
|
|
82
|
+
with solara.Link("/contact"):
|
|
83
|
+
with solara.v.ListItem():
|
|
84
|
+
solara.v.ListItemIcon(children=[solara.v.Icon(children=["mdi-email"])])
|
|
85
|
+
solara.v.ListItemTitle(style_="padding: 0 20px;", children=["Contact"])
|
|
86
|
+
with solara.Link("/changelog"):
|
|
87
|
+
with solara.v.ListItem():
|
|
88
|
+
solara.v.ListItemIcon(children=[solara.v.Icon(children=["mdi-history"])])
|
|
89
|
+
solara.v.ListItemTitle(style_="padding: 0 20px;", children=["Changelog"])
|
|
90
|
+
|
|
91
|
+
return main
|
solara/website/pages/__init__.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import contextlib
|
|
2
|
-
|
|
3
1
|
import solara
|
|
4
2
|
from solara import autorouting
|
|
5
3
|
from solara.alias import rv
|
|
6
4
|
from solara.components.title import Title
|
|
7
5
|
from solara.server import server
|
|
6
|
+
from solara.website.components.algolia import Algolia
|
|
8
7
|
|
|
9
8
|
from ..components import Header, Hero
|
|
10
9
|
from ..components.mailchimp import MailChimp
|
|
@@ -237,39 +236,6 @@ def List(children=[], class_: str = None):
|
|
|
237
236
|
return rv.Html(tag="ul", children=children, attributes={"class": class_})
|
|
238
237
|
|
|
239
238
|
|
|
240
|
-
@solara.component
|
|
241
|
-
def Sidebar():
|
|
242
|
-
route_current, all_routes = solara.use_route()
|
|
243
|
-
router = solara.use_router()
|
|
244
|
-
selected = router.path
|
|
245
|
-
with rv.Col(tag="aside", md=4, lg=3, class_="sidebar bg-grey d-none d-md-block") as main:
|
|
246
|
-
with List():
|
|
247
|
-
for route in all_routes:
|
|
248
|
-
if route.children and route.data is None:
|
|
249
|
-
path = solara.resolve_path(route.children[0])
|
|
250
|
-
path = getattr(route.module, "redirect", path)
|
|
251
|
-
path = getattr(route.children[0].module, "redirect", path)
|
|
252
|
-
with solara.Link(path) if path is not None else contextlib.nullcontext():
|
|
253
|
-
with SimpleListItem(route.label, class_="active" if path == selected else None):
|
|
254
|
-
with List():
|
|
255
|
-
for child in route.children[1:]:
|
|
256
|
-
path = solara.resolve_path(child)
|
|
257
|
-
path = getattr(child.module, "redirect", path)
|
|
258
|
-
with solara.Link(path) if path is not None else contextlib.nullcontext():
|
|
259
|
-
title = child.label or "no label"
|
|
260
|
-
if callable(title):
|
|
261
|
-
title = "Error: dynamic title"
|
|
262
|
-
SimpleListItem(title, class_="active" if path == selected else None)
|
|
263
|
-
else:
|
|
264
|
-
path = solara.resolve_path(route)
|
|
265
|
-
path = getattr(route.module, "redirect", path)
|
|
266
|
-
if route.children:
|
|
267
|
-
path = getattr(route.children[0].module, "redirect", path)
|
|
268
|
-
with solara.Link(path) if path is not None else contextlib.nullcontext():
|
|
269
|
-
SimpleListItem(route.label, class_="active" if path == selected else None)
|
|
270
|
-
return main
|
|
271
|
-
|
|
272
|
-
|
|
273
239
|
@solara.component
|
|
274
240
|
def Layout(children=[]):
|
|
275
241
|
router = solara.use_router()
|
|
@@ -476,17 +442,22 @@ def Layout(children=[]):
|
|
|
476
442
|
style_="flex-wrap: nowrap; margin: 0; min-height: calc(100vh - 215.5px);",
|
|
477
443
|
justify="center" if route_current is not None and route_current.path in ["documentation", "showcase"] else "start",
|
|
478
444
|
):
|
|
479
|
-
if route_current is not None and hasattr(route_current.module, "Sidebar"):
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
445
|
+
if route_current is not None and route_current.module is not None and hasattr(route_current.module, "Sidebar"):
|
|
446
|
+
with solara.v.NavigationDrawer(
|
|
447
|
+
clipped=True,
|
|
448
|
+
class_="d-none d-md-block",
|
|
449
|
+
height="unset",
|
|
450
|
+
style_="min-height: calc(100vh - 215.5px);",
|
|
451
|
+
width="20rem",
|
|
452
|
+
v_model=True, # Forces menu to display even if it had somehow been closed
|
|
453
|
+
):
|
|
454
|
+
route_current.module.Sidebar()
|
|
484
455
|
with rv.Col(
|
|
485
456
|
tag="main",
|
|
486
457
|
md=True,
|
|
487
458
|
class_="pa-0",
|
|
488
459
|
style_=f"""max-width: {'1024px' if route_current.path not in ['documentation', 'contact', 'changelog']
|
|
489
|
-
else 'unset'}; overflow: hidden
|
|
460
|
+
else 'unset'}; overflow-x: hidden;""",
|
|
490
461
|
):
|
|
491
462
|
if route_current is not None and route_current.path == "/":
|
|
492
463
|
with rv.Row(align="center"):
|
|
@@ -512,6 +483,7 @@ def Layout(children=[]):
|
|
|
512
483
|
overlay_opacity=0.5,
|
|
513
484
|
style_="height: 100vh",
|
|
514
485
|
):
|
|
486
|
+
Algolia()
|
|
515
487
|
with rv.List(nav=True):
|
|
516
488
|
with rv.ListItemGroup(active_class="text--primary"):
|
|
517
489
|
for route in all_routes:
|
|
@@ -520,32 +492,18 @@ def Layout(children=[]):
|
|
|
520
492
|
with solara.Link(route):
|
|
521
493
|
solara.ListItem(route.label)
|
|
522
494
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
current_path = router.path
|
|
536
|
-
with rv.ListItemGroup(active_class="text--primary", v_model=current_path):
|
|
537
|
-
for route in all_routes_sidebar:
|
|
538
|
-
# this gets rid of the api/link child routes
|
|
539
|
-
if len([k for k in route.children if k.label]) == 0:
|
|
540
|
-
with solara.Link(route):
|
|
541
|
-
solara.ListItem(route.label, value=solara.resolve_path(route))
|
|
542
|
-
else:
|
|
543
|
-
with solara.ListItem(route.label or "no-title", value=solara.resolve_path(route) + "_no_select"):
|
|
544
|
-
for subroute in route.children:
|
|
545
|
-
if subroute.label:
|
|
546
|
-
with solara.Link(subroute):
|
|
547
|
-
with solara.ListItem(subroute.label, value=solara.resolve_path(subroute)):
|
|
548
|
-
pass
|
|
495
|
+
if route_current is not None and route_current.module is not None and hasattr(route_current.module, "Sidebar"):
|
|
496
|
+
with solara.v.NavigationDrawer(
|
|
497
|
+
absolute=True,
|
|
498
|
+
clipped=True,
|
|
499
|
+
class_="d-md-none d-block",
|
|
500
|
+
height="unset",
|
|
501
|
+
style_="min-height: 100vh;",
|
|
502
|
+
v_model=show_left_menu,
|
|
503
|
+
on_v_model=set_show_left_menu,
|
|
504
|
+
width="20rem",
|
|
505
|
+
):
|
|
506
|
+
route_current.module.Sidebar()
|
|
549
507
|
|
|
550
508
|
return main
|
|
551
509
|
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Solara Changelog
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
## Version 1.32.0
|
|
5
|
+
|
|
6
|
+
### Details
|
|
7
|
+
|
|
8
|
+
* Feature: Support for multiple custom asset locations. [#602](https://github.com/widgetti/solara/pull/602)
|
|
9
|
+
* Refactor: Improve API for working with third-party dataframes. [#600](https://github.com/widgetti/solara/pull/600)
|
|
10
|
+
* Bug Fix: Give task results more unique keys. [#609](https://github.com/widgetti/solara/pull/609)
|
|
11
|
+
* Bug Fix: Avoid closing kernel multiple times. [e5ca67b](https://github.com/widgetti/solara/commit/e5ca67b6be46a8d236cc127cc06068702aa40754)
|
|
12
|
+
* Bug Fix: Make `page_close` beacon thread safe. [2501c83](https://github.com/widgetti/solara/commit/2501c83f135bc342ed9845198f7aff3bdcf09bc0)
|
|
13
|
+
* Bug Fix: Viewing embedded Solara application in fullscreen could cause a Reacton error. [#603](https://github.com/widgetti/solara/pull/603)
|
|
14
|
+
* Bug Fix: Remove fade when navigation is done from a thread/task. [#594](https://github.com/widgetti/solara/pull/594)
|
|
15
|
+
* Bug Fix: Support Pydantic 1 and 2 in reactive variables. [#601](https://github.com/widgetti/solara/pull/601)
|
|
16
|
+
|
|
17
|
+
## Version 1.31
|
|
18
|
+
|
|
19
|
+
We changed solara from a single package into multiple packages.
|
|
20
|
+
|
|
21
|
+
The `solara` package is a meta package that installs all the necessary dependencies to get started with Solara. By default, we install:
|
|
22
|
+
|
|
23
|
+
* [`pip install "solara-ui[all]"`](https://pypi.org/project/solara-ui)
|
|
24
|
+
* [`pip install "solara-server[starlette,dev]"`](https://pypi.org/project/solara-ui)
|
|
25
|
+
|
|
26
|
+
Notebook users can simply install `solara-ui` if they do not need the solara-server. Read our [installation guide](https://solara.dev/documentation/getting_started/installing) for more information.
|
|
27
|
+
|
|
3
28
|
## Version 1.30.1
|
|
4
29
|
|
|
5
30
|
### Details
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
3
|
import solara
|
|
4
|
+
from solara.website.components.sidebar import Sidebar
|
|
4
5
|
|
|
5
6
|
_title = "Documentation"
|
|
6
7
|
|
|
7
8
|
HERE = Path(__file__).parent
|
|
9
|
+
Sidebar = Sidebar
|
|
8
10
|
|
|
9
11
|
route_order = ["/", "getting_started", "examples", "components", "api", "advanced", "faq"]
|
|
10
12
|
|
|
@@ -79,94 +81,6 @@ def Page(children=[]):
|
|
|
79
81
|
pass
|
|
80
82
|
|
|
81
83
|
|
|
82
|
-
@solara.component
|
|
83
|
-
def Sidebar():
|
|
84
|
-
route_current, all_routes = solara.use_route()
|
|
85
|
-
router = solara.use_router()
|
|
86
|
-
if route_current is None:
|
|
87
|
-
return solara.Error("Page not found")
|
|
88
|
-
|
|
89
|
-
with solara.v.NavigationDrawer(
|
|
90
|
-
clipped=True, width="20rem", height="unset", style_="min-height: calc(100vh - 215.5px);", class_="d-none d-md-block"
|
|
91
|
-
) as main:
|
|
92
|
-
with solara.v.List(expand=True, nav=True, style_="height: calc(100vh - 215.5px); display: flex; flex-direction: column;"):
|
|
93
|
-
with solara.v.ListItemGroup(v_model=router.path):
|
|
94
|
-
for route in all_routes:
|
|
95
|
-
if len(route.children) == 1 or route.path == "/":
|
|
96
|
-
with solara.Link("/documentation/" + route.path if route.path != "/" else "/documentation"):
|
|
97
|
-
with solara.v.ListItem(value="/documentation/" + route.path if route.path != "/" else "/documentation"):
|
|
98
|
-
if route.path == "/":
|
|
99
|
-
solara.v.ListItemIcon(children=[solara.v.Icon(children=["mdi-home"])])
|
|
100
|
-
solara.v.ListItemTitle(style_="padding: 0 20px;", children=[route.label])
|
|
101
|
-
else:
|
|
102
|
-
with solara.v.ListGroup(
|
|
103
|
-
v_slots=[
|
|
104
|
-
{
|
|
105
|
-
"name": "activator",
|
|
106
|
-
"children": solara.v.ListItemTitle(
|
|
107
|
-
children=[route.label],
|
|
108
|
-
style_="padding: 0 20px;",
|
|
109
|
-
),
|
|
110
|
-
}
|
|
111
|
-
],
|
|
112
|
-
value=router.path.startswith("/documentation/" + route.path),
|
|
113
|
-
):
|
|
114
|
-
for item in route.children:
|
|
115
|
-
if item.path == "/":
|
|
116
|
-
continue
|
|
117
|
-
if item.children != [] and any([c.label is not None and c.path != "/" for c in item.children]):
|
|
118
|
-
with solara.v.ListGroup(
|
|
119
|
-
v_slots=[
|
|
120
|
-
{
|
|
121
|
-
"name": "activator",
|
|
122
|
-
"children": solara.v.ListItemTitle(
|
|
123
|
-
children=[item.label],
|
|
124
|
-
),
|
|
125
|
-
}
|
|
126
|
-
],
|
|
127
|
-
sub_group=True,
|
|
128
|
-
no_action=True,
|
|
129
|
-
value=router.path.startswith("/documentation/" + route.path + "/" + item.path),
|
|
130
|
-
):
|
|
131
|
-
for subitem in item.children:
|
|
132
|
-
# skip pages that are only used to demonstrate Link or Router usage
|
|
133
|
-
if subitem.path == "/" or subitem.label is None:
|
|
134
|
-
continue
|
|
135
|
-
path = (
|
|
136
|
-
"/documentation/" + route.path + "/" + item.path + "/" + subitem.path
|
|
137
|
-
if item.path != "fullscreen"
|
|
138
|
-
else "/apps/" + subitem.path
|
|
139
|
-
)
|
|
140
|
-
with solara.Link(
|
|
141
|
-
path,
|
|
142
|
-
):
|
|
143
|
-
with solara.v.ListItem(dense=True, style_="padding: 0 20px;", value=path):
|
|
144
|
-
solara.v.ListItemContent(
|
|
145
|
-
children=[subitem.label],
|
|
146
|
-
)
|
|
147
|
-
else:
|
|
148
|
-
with solara.v.ListItemGroup(value="/documentation/" + route.path + "/" + item.path):
|
|
149
|
-
with solara.Link(
|
|
150
|
-
"/documentation/" + route.path + "/" + item.path,
|
|
151
|
-
):
|
|
152
|
-
with solara.v.ListItem(dense=True, style_="padding: 0 20px;"):
|
|
153
|
-
solara.v.ListItemContent(
|
|
154
|
-
children=[item.label],
|
|
155
|
-
)
|
|
156
|
-
solara.v.Spacer(style_="flex-grow: 1;")
|
|
157
|
-
with solara.v.ListItemGroup():
|
|
158
|
-
with solara.Link("/contact"):
|
|
159
|
-
with solara.v.ListItem():
|
|
160
|
-
solara.v.ListItemIcon(children=[solara.v.Icon(children=["mdi-email"])])
|
|
161
|
-
solara.v.ListItemTitle(style_="padding: 0 20px;", children=["Contact"])
|
|
162
|
-
with solara.Link("/changelog"):
|
|
163
|
-
with solara.v.ListItem():
|
|
164
|
-
solara.v.ListItemIcon(children=[solara.v.Icon(children=["mdi-history"])])
|
|
165
|
-
solara.v.ListItemTitle(style_="padding: 0 20px;", children=["Changelog"])
|
|
166
|
-
|
|
167
|
-
return main
|
|
168
|
-
|
|
169
|
-
|
|
170
84
|
@solara.component
|
|
171
85
|
def Layout(children=[]):
|
|
172
86
|
route_current, all_routes = solara.use_route()
|