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.
Files changed (43) hide show
  1. solara/__init__.py +1 -1
  2. solara/components/applayout.py +6 -2
  3. solara/components/datatable.py +5 -12
  4. solara/components/markdown.py +38 -27
  5. solara/lab/hooks/dataframe.py +1 -12
  6. solara/lab/utils/dataframe.py +40 -0
  7. solara/minisettings.py +13 -5
  8. solara/server/flask.py +3 -5
  9. solara/server/kernel_context.py +110 -60
  10. solara/server/server.py +8 -4
  11. solara/server/settings.py +23 -1
  12. solara/server/starlette.py +4 -5
  13. solara/server/static/main-vuetify.js +3 -1
  14. solara/server/static/solara_bootstrap.py +1 -1
  15. solara/server/templates/solara.html.j2 +4 -4
  16. solara/tasks.py +19 -10
  17. solara/toestand.py +22 -13
  18. solara/website/assets/custom.css +13 -0
  19. solara/website/components/algolia.py +6 -0
  20. solara/website/components/algolia_api.vue +2 -1
  21. solara/website/components/header.py +9 -17
  22. solara/website/components/notebook.py +1 -1
  23. solara/website/components/sidebar.py +91 -0
  24. solara/website/pages/__init__.py +25 -67
  25. solara/website/pages/changelog/__init__.py +2 -0
  26. solara/website/pages/changelog/changelog.md +25 -0
  27. solara/website/pages/contact/__init__.py +2 -0
  28. solara/website/pages/documentation/__init__.py +2 -88
  29. solara/website/pages/documentation/advanced/content/10-howto/30-testing.md +267 -16
  30. solara/website/pages/documentation/advanced/content/15-reference/41-asset-files.md +36 -0
  31. solara/website/pages/documentation/advanced/content/20-understanding/40-routing.md +4 -4
  32. solara/website/pages/documentation/advanced/content/30-enterprise/10-oauth.md +11 -2
  33. solara/website/pages/documentation/advanced/content/40-development/10-setup.md +1 -1
  34. solara/website/pages/documentation/faq/content/99-faq.md +27 -0
  35. solara/website/pages/documentation/getting_started/content/02-installing.md +2 -2
  36. solara/website/pages/documentation/getting_started/content/04-tutorials/60-jupyter-dashboard-part1.py +50 -49
  37. solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +20 -4
  38. {solara_ui-1.31.0.dist-info → solara_ui-1.32.1.dist-info}/METADATA +2 -2
  39. {solara_ui-1.31.0.dist-info → solara_ui-1.32.1.dist-info}/RECORD +43 -41
  40. {solara_ui-1.31.0.data → solara_ui-1.32.1.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
  41. {solara_ui-1.31.0.data → solara_ui-1.32.1.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
  42. {solara_ui-1.31.0.dist-info → solara_ui-1.32.1.dist-info}/WHEEL +0 -0
  43. {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.reactive(
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
- task_instance = solara.use_memo(lambda: task(f, prefer_threaded=prefer_threaded), dependencies=[])
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
- return type(d1)(**{**d1.dict(), **kwargs}) # type: ignore
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
- try:
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:
@@ -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;
@@ -0,0 +1,6 @@
1
+ import solara
2
+
3
+
4
+ @solara.component_vue("algolia_api.vue")
5
+ def Algolia():
6
+ pass
@@ -12,9 +12,10 @@
12
12
  rounded
13
13
  clearable
14
14
  ref="search"
15
- style="max-width: 50%; flex-grow: 1;"
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 len(route_current.children) > 0:
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
- if (
42
- route_current_with_children is not None
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.v.Spacer()
48
- else:
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
- with rv.Html(tag="ul", class_="main-menu menu d-none d-md-flex"):
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": "100%"}) as main:
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
@@ -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
- route_current.module.Sidebar() # type: ignore
481
- else:
482
- if route_current is not None and route_current.path not in ["documentation", "showcase", "contact", "changelog"]:
483
- Sidebar()
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 auto;""",
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
- # Drawer navigation for sidebar
524
- with rv.NavigationDrawer(
525
- v_model=show_left_menu,
526
- on_v_model=set_show_left_menu,
527
- fixed=True,
528
- absolute=True,
529
- hide_overlay=False,
530
- overlay_color="#000000",
531
- overlay_opacity=0.5,
532
- style_="height: 100vh",
533
- ):
534
- with rv.List(nav=True):
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,8 +1,10 @@
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 = "Changelog"
6
7
  HERE = Path(__file__)
7
8
 
8
9
  Page = solara.Markdown(Path(HERE.parent / "changelog.md").read_text())
10
+ Sidebar = Sidebar
@@ -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,8 +1,10 @@
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 = "Contact"
6
7
  HERE = Path(__file__)
7
8
 
8
9
  Page = solara.Markdown(Path(HERE.parent / "contact.md").read_text())
10
+ Sidebar = Sidebar
@@ -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()