solara-ui 1.41.0__py2.py3-none-any.whl → 1.43.0__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/__main__.py +17 -6
- solara/_stores.py +189 -0
- solara/components/__init__.py +18 -1
- solara/components/component_vue.py +23 -0
- solara/components/datatable.py +4 -4
- solara/components/echarts.py +5 -2
- solara/components/echarts.vue +22 -5
- solara/components/file_drop.py +20 -0
- solara/components/input.py +21 -1
- solara/components/markdown.py +62 -17
- solara/components/misc.py +2 -2
- solara/components/spinner-solara.vue +2 -2
- solara/components/spinner.py +17 -2
- solara/hooks/use_reactive.py +8 -1
- solara/lab/components/__init__.py +1 -0
- solara/lab/components/chat.py +3 -3
- solara/lab/components/input_time.py +133 -0
- solara/lab/hooks/dataframe.py +1 -0
- solara/lab/utils/dataframe.py +11 -1
- solara/reactive.py +9 -3
- solara/server/app.py +63 -30
- solara/server/flask.py +12 -2
- solara/server/jupyter/server_extension.py +1 -0
- solara/server/kernel.py +52 -4
- solara/server/kernel_context.py +66 -7
- solara/server/patch.py +25 -29
- solara/server/qt.py +1 -1
- solara/server/server.py +15 -5
- solara/server/settings.py +11 -0
- solara/server/shell.py +19 -1
- solara/server/starlette.py +39 -11
- solara/server/static/solara_bootstrap.py +1 -1
- solara/settings.py +17 -0
- solara/tasks.py +18 -8
- solara/template/portal/pyproject.toml +1 -1
- solara/test/pytest_plugin.py +4 -0
- solara/toestand.py +170 -16
- solara/util.py +40 -0
- solara/website/components/docs.py +4 -0
- solara/website/components/markdown.py +60 -2
- solara/website/pages/changelog/changelog.md +17 -0
- solara/website/pages/documentation/advanced/content/20-understanding/50-solara-server.md +10 -0
- solara/website/pages/documentation/api/cross_filter/cross_filter_dataframe.py +4 -5
- solara/website/pages/documentation/api/cross_filter/cross_filter_report.py +3 -5
- solara/website/pages/documentation/api/cross_filter/cross_filter_select.py +3 -5
- solara/website/pages/documentation/api/cross_filter/cross_filter_slider.py +3 -5
- solara/website/pages/documentation/api/hooks/use_cross_filter.py +3 -5
- solara/website/pages/documentation/api/hooks/use_exception.py +9 -11
- solara/website/pages/documentation/api/hooks/use_previous.py +6 -9
- solara/website/pages/documentation/api/hooks/use_state_or_update.py +23 -26
- solara/website/pages/documentation/api/hooks/use_thread.py +11 -13
- solara/website/pages/documentation/api/routing/route.py +10 -12
- solara/website/pages/documentation/api/routing/use_route.py +26 -30
- solara/website/pages/documentation/api/utilities/on_kernel_start.py +17 -0
- solara/website/pages/documentation/components/advanced/link.py +6 -8
- solara/website/pages/documentation/components/advanced/meta.py +6 -9
- solara/website/pages/documentation/components/advanced/style.py +7 -9
- solara/website/pages/documentation/components/input/file_browser.py +12 -14
- solara/website/pages/documentation/components/input/input.py +22 -0
- solara/website/pages/documentation/components/lab/input_time.py +15 -0
- solara/website/pages/documentation/components/layout/columns_responsive.py +37 -39
- solara/website/pages/documentation/components/layout/gridfixed.py +4 -6
- solara/website/pages/documentation/components/output/html.py +1 -3
- solara/website/pages/documentation/components/page/head.py +4 -7
- solara/website/pages/documentation/components/viz/echarts.py +3 -1
- solara/website/pages/documentation/examples/__init__.py +9 -0
- solara/website/pages/documentation/examples/ai/chatbot.py +60 -44
- solara/website/pages/documentation/examples/general/live_update.py +1 -0
- solara/website/pages/documentation/examples/general/pokemon_search.py +3 -3
- solara/website/pages/documentation/examples/visualization/linked_views.py +0 -3
- solara/website/pages/documentation/faq/content/99-faq.md +9 -0
- solara/website/pages/documentation/getting_started/content/00-quickstart.md +2 -2
- solara/website/pages/documentation/getting_started/content/01-introduction.md +1 -1
- solara/website/pages/documentation/getting_started/content/04-tutorials/_jupyter_dashboard_1.ipynb +23 -19
- solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +2 -2
- solara/website/pages/roadmap/roadmap.md +6 -0
- {solara_ui-1.41.0.dist-info → solara_ui-1.43.0.dist-info}/METADATA +9 -6
- {solara_ui-1.41.0.dist-info → solara_ui-1.43.0.dist-info}/RECORD +83 -80
- {solara_ui-1.41.0.dist-info → solara_ui-1.43.0.dist-info}/WHEEL +1 -1
- {solara_ui-1.41.0.data → solara_ui-1.43.0.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
- {solara_ui-1.41.0.data → solara_ui-1.43.0.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
- {solara_ui-1.41.0.dist-info → solara_ui-1.43.0.dist-info}/licenses/LICENSE +0 -0
solara/components/markdown.py
CHANGED
|
@@ -4,7 +4,8 @@ import logging
|
|
|
4
4
|
import textwrap
|
|
5
5
|
import traceback
|
|
6
6
|
import warnings
|
|
7
|
-
from typing import Any, Dict, List, Union, cast
|
|
7
|
+
from typing import Any, Callable, Dict, List, Optional, Union, cast
|
|
8
|
+
import typing
|
|
8
9
|
|
|
9
10
|
import ipyvuetify as v
|
|
10
11
|
|
|
@@ -16,6 +17,7 @@ try:
|
|
|
16
17
|
has_pymdownx = True
|
|
17
18
|
except ModuleNotFoundError:
|
|
18
19
|
has_pymdownx = False
|
|
20
|
+
import reacton.core
|
|
19
21
|
|
|
20
22
|
import solara
|
|
21
23
|
import solara.components.applayout
|
|
@@ -30,6 +32,9 @@ else:
|
|
|
30
32
|
from pygments.formatters import HtmlFormatter
|
|
31
33
|
from pygments.lexers import get_lexer_by_name
|
|
32
34
|
|
|
35
|
+
if typing.TYPE_CHECKING:
|
|
36
|
+
import markdown
|
|
37
|
+
|
|
33
38
|
logger = logging.getLogger(__name__)
|
|
34
39
|
|
|
35
40
|
html_no_execute_enabled = "<div><i>Solara execution is not enabled</i></div>"
|
|
@@ -50,7 +55,7 @@ def ExceptionGuard(children=[]):
|
|
|
50
55
|
solara.Column(children=children)
|
|
51
56
|
|
|
52
57
|
|
|
53
|
-
def _run_solara(code):
|
|
58
|
+
def _run_solara(code, cleanups):
|
|
54
59
|
ast = compile(code, "markdown", "exec")
|
|
55
60
|
local_scope: Dict[Any, Any] = {}
|
|
56
61
|
exec(ast, local_scope)
|
|
@@ -63,6 +68,13 @@ def _run_solara(code):
|
|
|
63
68
|
else:
|
|
64
69
|
raise NameError("No Page or app defined")
|
|
65
70
|
box = v.Html(tag="div")
|
|
71
|
+
|
|
72
|
+
rc: reacton.core.RenderContext
|
|
73
|
+
|
|
74
|
+
def cleanup():
|
|
75
|
+
rc.close()
|
|
76
|
+
|
|
77
|
+
cleanups.append(cleanup)
|
|
66
78
|
box, rc = solara.render(cast(solara.Element, app), container=box) # type: ignore
|
|
67
79
|
widget_id = box._model_id
|
|
68
80
|
return (
|
|
@@ -231,9 +243,8 @@ module.exports = {
|
|
|
231
243
|
return template
|
|
232
244
|
|
|
233
245
|
|
|
234
|
-
def _highlight(src, language,
|
|
246
|
+
def _highlight(src, language, class_name=None, options=None, md=None, unsafe_solara_execute=False, cleanups=None, **kwargs):
|
|
235
247
|
"""Highlight a block of code"""
|
|
236
|
-
|
|
237
248
|
if not has_pygments:
|
|
238
249
|
warnings.warn("Pygments is not installed, code highlighting will not work, use pip install pygments to install it.")
|
|
239
250
|
src_safe = html.escape(src)
|
|
@@ -250,7 +261,7 @@ def _highlight(src, language, unsafe_solara_execute, extra, *args, **kwargs):
|
|
|
250
261
|
|
|
251
262
|
if run_src_with_solara:
|
|
252
263
|
if unsafe_solara_execute:
|
|
253
|
-
html_widget = _run_solara(src)
|
|
264
|
+
html_widget = _run_solara(src, cleanups)
|
|
254
265
|
return src_html + html_widget
|
|
255
266
|
else:
|
|
256
267
|
return src_html + html_no_execute_enabled
|
|
@@ -258,6 +269,19 @@ def _highlight(src, language, unsafe_solara_execute, extra, *args, **kwargs):
|
|
|
258
269
|
return src_html
|
|
259
270
|
|
|
260
271
|
|
|
272
|
+
def formatter(unsafe_solara_execute: bool, cleanups: List[Callable[[], None]]):
|
|
273
|
+
def wrapper(*args, **kwargs):
|
|
274
|
+
try:
|
|
275
|
+
kwargs["unsafe_solara_execute"] = unsafe_solara_execute
|
|
276
|
+
kwargs["cleanups"] = cleanups
|
|
277
|
+
return _highlight(*args, **kwargs)
|
|
278
|
+
except Exception as e:
|
|
279
|
+
logger.exception("Error while highlighting code")
|
|
280
|
+
raise e
|
|
281
|
+
|
|
282
|
+
return wrapper
|
|
283
|
+
|
|
284
|
+
|
|
261
285
|
@solara.component
|
|
262
286
|
def MarkdownIt(md_text: str, highlight: List[int] = [], unsafe_solara_execute: bool = False):
|
|
263
287
|
md_text = textwrap.dedent(md_text)
|
|
@@ -267,8 +291,10 @@ def MarkdownIt(md_text: str, highlight: List[int] = [], unsafe_solara_execute: b
|
|
|
267
291
|
from mdit_py_plugins.footnote import footnote_plugin # noqa: F401
|
|
268
292
|
from mdit_py_plugins.front_matter import front_matter_plugin # noqa: F401
|
|
269
293
|
|
|
294
|
+
cleanups = solara.use_ref(cast(List[Callable[[], None]], []))
|
|
295
|
+
|
|
270
296
|
def highlight_code(code, name, attrs):
|
|
271
|
-
return _highlight(code, name, unsafe_solara_execute, attrs)
|
|
297
|
+
return _highlight(cleanups.current, code, name, unsafe_solara_execute, attrs)
|
|
272
298
|
|
|
273
299
|
md = MarkdownItMod(
|
|
274
300
|
"js-default",
|
|
@@ -281,6 +307,15 @@ def MarkdownIt(md_text: str, highlight: List[int] = [], unsafe_solara_execute: b
|
|
|
281
307
|
md = md.use(container.container_plugin, name="note")
|
|
282
308
|
html = md.render(md_text)
|
|
283
309
|
hash = hashlib.sha256((html + str(unsafe_solara_execute) + repr(highlight)).encode("utf-8")).hexdigest()
|
|
310
|
+
|
|
311
|
+
def cleanup_wrapper():
|
|
312
|
+
def cleanup():
|
|
313
|
+
for cleanup in cleanups.current:
|
|
314
|
+
cleanup()
|
|
315
|
+
|
|
316
|
+
return cleanup
|
|
317
|
+
|
|
318
|
+
solara.use_effect(cleanup_wrapper)
|
|
284
319
|
return v.VuetifyTemplate.element(template=_markdown_template(html)).key(hash)
|
|
285
320
|
|
|
286
321
|
|
|
@@ -293,7 +328,7 @@ def _no_deep_copy_emojione(options, md):
|
|
|
293
328
|
|
|
294
329
|
|
|
295
330
|
@solara.component
|
|
296
|
-
def Markdown(md_text: str, unsafe_solara_execute=False, style: Union[str, Dict, None] = None):
|
|
331
|
+
def Markdown(md_text: str, unsafe_solara_execute=False, style: Union[str, Dict, None] = None, md_parser: Optional["markdown.Markdown"] = None):
|
|
297
332
|
"""Renders markdown text
|
|
298
333
|
|
|
299
334
|
Renders markdown using https://python-markdown.github.io/
|
|
@@ -333,21 +368,19 @@ def Markdown(md_text: str, unsafe_solara_execute=False, style: Union[str, Dict,
|
|
|
333
368
|
* `unsafe_solara_execute`: If True, code marked with language "solara" will be executed. This is potentially unsafe
|
|
334
369
|
if the markdown text can come from user input and should only be used for trusted markdown.
|
|
335
370
|
* `style`: A string or dict of css styles to apply to the rendered markdown.
|
|
371
|
+
* `md_parser`: A markdown object to use for rendering. If not provided, a markdown object will be created.
|
|
336
372
|
|
|
337
373
|
"""
|
|
338
374
|
import markdown
|
|
339
375
|
|
|
340
376
|
md_text = textwrap.dedent(md_text)
|
|
341
377
|
style = solara.util._flatten_style(style)
|
|
378
|
+
cleanups = solara.use_ref(cast(List[Callable[[], None]], []))
|
|
342
379
|
|
|
343
380
|
def make_markdown_object():
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
except Exception as e:
|
|
348
|
-
logger.exception("Error highlighting code: %s", src)
|
|
349
|
-
return repr(e)
|
|
350
|
-
|
|
381
|
+
if md_parser is not None:
|
|
382
|
+
# we won't use the use_memo
|
|
383
|
+
return None
|
|
351
384
|
if has_pymdownx:
|
|
352
385
|
return markdown.Markdown( # type: ignore
|
|
353
386
|
extensions=[
|
|
@@ -371,7 +404,7 @@ def Markdown(md_text: str, unsafe_solara_execute=False, style: Union[str, Dict,
|
|
|
371
404
|
{
|
|
372
405
|
"name": "solara",
|
|
373
406
|
"class": "",
|
|
374
|
-
"format":
|
|
407
|
+
"format": formatter(unsafe_solara_execute, cleanups=cleanups.current),
|
|
375
408
|
},
|
|
376
409
|
],
|
|
377
410
|
},
|
|
@@ -388,8 +421,20 @@ def Markdown(md_text: str, unsafe_solara_execute=False, style: Union[str, Dict,
|
|
|
388
421
|
],
|
|
389
422
|
)
|
|
390
423
|
|
|
391
|
-
|
|
392
|
-
|
|
424
|
+
md_self = solara.use_memo(make_markdown_object, dependencies=[unsafe_solara_execute])
|
|
425
|
+
if md_parser is None:
|
|
426
|
+
assert md_self is not None
|
|
427
|
+
md_parser = md_self
|
|
428
|
+
html = md_parser.convert(md_text)
|
|
429
|
+
|
|
430
|
+
def cleanup_wrapper():
|
|
431
|
+
def cleanup():
|
|
432
|
+
for cleanup in cleanups.current:
|
|
433
|
+
cleanup()
|
|
434
|
+
|
|
435
|
+
return cleanup
|
|
436
|
+
|
|
437
|
+
solara.use_effect(cleanup_wrapper, [])
|
|
393
438
|
# if we update the template value, the whole vue tree will rerender (ipvue/ipyvuetify issue)
|
|
394
439
|
# however, using the hash we simply generate a new widget each time
|
|
395
440
|
hash = hashlib.sha256((html + str(unsafe_solara_execute)).encode("utf-8")).hexdigest()
|
solara/components/misc.py
CHANGED
|
@@ -136,7 +136,7 @@ def HTML(tag="div", unsafe_innerHTML=None, style: str = None, classes: List[str]
|
|
|
136
136
|
|
|
137
137
|
@solara.component
|
|
138
138
|
def VBox(children=[], grow=True, align_items="stretch", classes: List[str] = []):
|
|
139
|
-
"""Deprecated. Use `
|
|
139
|
+
"""Deprecated. Use `Column` instead."""
|
|
140
140
|
style = f"flex-direction: column; align-items: {align_items};"
|
|
141
141
|
if grow:
|
|
142
142
|
style += "flex-grow: 1;"
|
|
@@ -146,7 +146,7 @@ def VBox(children=[], grow=True, align_items="stretch", classes: List[str] = [])
|
|
|
146
146
|
|
|
147
147
|
@solara.component
|
|
148
148
|
def HBox(children=[], grow=True, align_items="stretch", classes: List[str] = []):
|
|
149
|
-
"""Deprecated. Use `
|
|
149
|
+
"""Deprecated. Use `Row` instead."""
|
|
150
150
|
style = f"flex-direction: row; align-items: {align_items}; "
|
|
151
151
|
if grow:
|
|
152
152
|
style += "flex-grow: 1;"
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
xmlns:svgjs="http://svgjs.com/svgjs" :width="size" :height="size" viewBox="0 0 65 65" fill="none">
|
|
6
6
|
<path id="sun-spinner1" style="transform-origin: center;"
|
|
7
7
|
d="M57.11 30.64L61.47 17.87L48.7 13.51L42.76 1.39999L30.65 7.34999L17.87 2.97999L13.51 15.75L1.40002 21.7L7.35002 33.81L2.99002 46.58L15.76 50.94L21.71 63.06L33.82 57.11L46.59 61.47L50.95 48.7L63.06 42.75L57.11 30.64ZM54.26 34.39L34.39 54.26C33.19 55.46 31.25 55.46 30.05 54.26L10.2 34.4C9.00002 33.2 9.00002 31.26 10.2 30.07L30.06 10.2C31.26 8.99999 33.2 8.99999 34.4 10.2L54.27 30.07C55.47 31.27 55.47 33.21 54.27 34.4L54.26 34.39Z"
|
|
8
|
-
fill="
|
|
8
|
+
:fill="color_back"></path>
|
|
9
9
|
<path id="sun-spinner2" style="transform-origin: center;"
|
|
10
10
|
d="M53.62 19.42L51.65 6.07L38.3 8.04L27.46 0L19.42 10.84L6.07 12.82L8.04 26.17L0 37L10.84 45.04L12.81 58.39L26.16 56.42L37 64.46L45.04 53.62L58.39 51.64L56.42 38.29L64.46 27.45L53.62 19.4V19.42ZM52.8 24.06L44.24 50.82C43.72 52.43 42 53.32 40.39 52.81L13.63 44.25C12.02 43.74 11.13 42.01 11.64 40.4L20.21 13.64C20.72 12.03 22.45 11.14 24.06 11.65L50.82 20.21C52.43 20.72 53.32 22.45 52.81 24.06H52.8Z"
|
|
11
|
-
fill="
|
|
11
|
+
:fill="color_front"></path>
|
|
12
12
|
</svg>
|
|
13
13
|
</div>
|
|
14
14
|
</template>
|
solara/components/spinner.py
CHANGED
|
@@ -8,12 +8,15 @@ class SpinnerSolaraWidget(ipyvue.VueTemplate):
|
|
|
8
8
|
template_file = (__file__, "spinner-solara.vue")
|
|
9
9
|
|
|
10
10
|
size = traitlets.Unicode("64px").tag(sync=True)
|
|
11
|
+
color_back = traitlets.Unicode("#FFCF64").tag(sync=True)
|
|
12
|
+
color_front = traitlets.Unicode("#FF8C3E").tag(sync=True)
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
@solara.component
|
|
14
|
-
def SpinnerSolara(size="64px"):
|
|
16
|
+
def SpinnerSolara(size="64px", color_back="#FFCF64", color_front="#FF8C3E"):
|
|
15
17
|
"""Spinner component with the Solara logo to indicate the app is busy.
|
|
16
18
|
|
|
19
|
+
## Examples
|
|
17
20
|
### Basic example
|
|
18
21
|
|
|
19
22
|
```solara
|
|
@@ -24,7 +27,19 @@ def SpinnerSolara(size="64px"):
|
|
|
24
27
|
solara.SpinnerSolara(size="100px")
|
|
25
28
|
```
|
|
26
29
|
|
|
30
|
+
## Changing the colors
|
|
31
|
+
```solara
|
|
32
|
+
import solara
|
|
33
|
+
|
|
34
|
+
@solara.component
|
|
35
|
+
def Page():
|
|
36
|
+
solara.SpinnerSolara(size="100px", color_back="Grey", color_front="Lime")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
|
|
27
40
|
## Arguments
|
|
28
41
|
* `size`: Size of the spinner.
|
|
42
|
+
* `color_back`: Color of the spinner in the background.
|
|
43
|
+
* `color_front`: Color of the spinner in the foreground.
|
|
29
44
|
"""
|
|
30
|
-
return SpinnerSolaraWidget.element(size=size)
|
|
45
|
+
return SpinnerSolaraWidget.element(size=size, color_back=color_back, color_front=color_front)
|
solara/hooks/use_reactive.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Callable, Optional, TypeVar, Union
|
|
1
|
+
from typing import Any, Callable, Optional, TypeVar, Union
|
|
2
2
|
|
|
3
3
|
import solara
|
|
4
4
|
|
|
@@ -8,6 +8,7 @@ T = TypeVar("T")
|
|
|
8
8
|
def use_reactive(
|
|
9
9
|
value: Union[T, solara.Reactive[T]],
|
|
10
10
|
on_change: Optional[Callable[[T], None]] = None,
|
|
11
|
+
equals: Callable[[Any, Any], bool] = solara.util.equals_extra,
|
|
11
12
|
) -> solara.Reactive[T]:
|
|
12
13
|
"""Creates a reactive variable with the a local component scope.
|
|
13
14
|
|
|
@@ -44,6 +45,12 @@ def use_reactive(
|
|
|
44
45
|
* on_change (Optional[Callable[[T], None]]): An optional callback function
|
|
45
46
|
that will be called when the reactive variable's value changes.
|
|
46
47
|
|
|
48
|
+
* equals: A function that returns True if two values are considered equal, and False otherwise.
|
|
49
|
+
The default function is `solara.util.equals`, which performs a deep comparison of the two values
|
|
50
|
+
and is more forgiving than the default `==` operator.
|
|
51
|
+
You can provide a custom function if you need to define a different notion of equality.
|
|
52
|
+
|
|
53
|
+
|
|
47
54
|
Returns:
|
|
48
55
|
solara.Reactive[T]: A reactive variable with the specified initial value
|
|
49
56
|
or the provided reactive variable.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from .chat import ChatBox, ChatInput, ChatMessage # noqa: F401
|
|
2
2
|
from .confirmation_dialog import ConfirmationDialog # noqa: F401
|
|
3
3
|
from .input_date import InputDate, InputDateRange # noqa: F401
|
|
4
|
+
from .input_time import InputTime as InputTime
|
|
4
5
|
from .menu import ClickMenu, ContextMenu, Menu # noqa: F401 F403
|
|
5
6
|
from .tabs import Tab, Tabs # noqa: F401
|
|
6
7
|
from .theming import ThemeToggle, theme, use_dark_effective # noqa: F401
|
solara/lab/components/chat.py
CHANGED
|
@@ -43,7 +43,7 @@ def ChatBox(
|
|
|
43
43
|
|
|
44
44
|
@solara.component
|
|
45
45
|
def ChatInput(
|
|
46
|
-
send_callback: Optional[Callable] = None,
|
|
46
|
+
send_callback: Optional[Callable[[str], None]] = None,
|
|
47
47
|
disabled: bool = False,
|
|
48
48
|
style: Optional[Union[str, Dict[str, str]]] = None,
|
|
49
49
|
input_text_style: Optional[Union[str, Dict[str, str]]] = None,
|
|
@@ -55,7 +55,7 @@ def ChatInput(
|
|
|
55
55
|
|
|
56
56
|
# Arguments
|
|
57
57
|
|
|
58
|
-
* `send_callback`: A callback function for when the user presses enter or clicks the send button.
|
|
58
|
+
* `send_callback`: A callback function for when the user presses enter or clicks the send button taking the message as an argument.
|
|
59
59
|
* `disabled`: Whether the input should be disabled. Useful for disabling sending further messages while a chatbot is replying,
|
|
60
60
|
among other things.
|
|
61
61
|
* `style`: CSS styles to apply to the `solara.Row` containing the input field and submit button. Either a string or a dictionary.
|
|
@@ -98,7 +98,7 @@ def ChatInput(
|
|
|
98
98
|
|
|
99
99
|
@solara.component
|
|
100
100
|
def ChatMessage(
|
|
101
|
-
children: Union[List[solara.Element], str],
|
|
101
|
+
children: Union[List[solara.Element], str] = [],
|
|
102
102
|
user: bool = False,
|
|
103
103
|
avatar: Union[solara.Element, str, Literal[False], None] = None,
|
|
104
104
|
name: Optional[str] = None,
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
from typing import Callable, Dict, List, Optional, Union
|
|
3
|
+
|
|
4
|
+
import solara
|
|
5
|
+
import solara.lab
|
|
6
|
+
from solara.lab.components.input_date import use_close_menu
|
|
7
|
+
from solara.components.input import _use_input_type
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@solara.component
|
|
11
|
+
def InputTime(
|
|
12
|
+
value: Union[solara.Reactive[Optional[dt.time]], Optional[dt.time]],
|
|
13
|
+
on_value: Optional[Callable[[Optional[dt.time]], None]] = None,
|
|
14
|
+
label: str = "Pick a time",
|
|
15
|
+
children: List[solara.Element] = [],
|
|
16
|
+
open_value: Union[solara.Reactive[bool], bool] = False,
|
|
17
|
+
on_open_value: Optional[Callable[[bool], None]] = None,
|
|
18
|
+
optional: bool = False,
|
|
19
|
+
twelve_hour_clock: bool = False,
|
|
20
|
+
use_seconds: bool = False,
|
|
21
|
+
allowed_minutes: Optional[List[int]] = None,
|
|
22
|
+
style: Optional[Union[str, Dict[str, str]]] = None,
|
|
23
|
+
classes: Optional[List[str]] = None,
|
|
24
|
+
):
|
|
25
|
+
"""
|
|
26
|
+
Show a textfield, which when clicked, opens a timepicker. The input time should be of type `datetime.time`.
|
|
27
|
+
|
|
28
|
+
## Basic Example
|
|
29
|
+
|
|
30
|
+
```solara {pycafe-link}
|
|
31
|
+
import solara
|
|
32
|
+
import solara.lab
|
|
33
|
+
import datetime as dt
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@solara.component
|
|
37
|
+
def Page():
|
|
38
|
+
time = solara.use_reactive(dt.time(12, 0))
|
|
39
|
+
|
|
40
|
+
solara.lab.InputTime(time, allowed_minutes=[0, 15, 30, 45])
|
|
41
|
+
solara.Text(str(time.value))
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Arguments
|
|
45
|
+
|
|
46
|
+
* value: Reactive variable of type `datetime.time`, or `None`. This time is selected the first time the component is rendered.
|
|
47
|
+
* on_value: a callback function for when value changes. The callback function receives the new value as an argument.
|
|
48
|
+
* label: Text used to label the text field that triggers the timepicker.
|
|
49
|
+
* children: List of Elements to be rendered under the timepicker. If empty, a close button is rendered.
|
|
50
|
+
* open_value: Controls and communicates the state of the timepicker. If True, the timepicker is open. If False, the timepicker is closed.
|
|
51
|
+
Intended to be used in conjunction with a custom set of controls to close the timepicker.
|
|
52
|
+
* on_open_value: a callback function for when open_value changes. Also receives the new value as an argument.
|
|
53
|
+
* optional: Determines whether to show an error when value is `None`. If `True`, no error is shown.
|
|
54
|
+
* twelve_hour_clock: If `True`, the timepicker will display in 12-hour format. If `False`, the timepicker will display in 24-hour format.
|
|
55
|
+
* use_seconds: If `True`, the timepicker will allow input of seconds.
|
|
56
|
+
* allowed_minutes: List of allowed minutes for the timepicker. Restricts the input to specific minute intervals.
|
|
57
|
+
* style: CSS style to apply to the text field. Either a string or a dictionary of CSS properties (i.e. `{"property": "value"}`).
|
|
58
|
+
* classes: List of CSS classes to apply to the text field.
|
|
59
|
+
"""
|
|
60
|
+
time_format_internal = f"%H:%M{':%S' if use_seconds else ''}"
|
|
61
|
+
time_format_display = f"%H:%M{':%S' if use_seconds else ''}"
|
|
62
|
+
if twelve_hour_clock:
|
|
63
|
+
time_format_display = f"%I:%M{':%S' if use_seconds else ''} %p"
|
|
64
|
+
value_reactive = solara.use_reactive(value, on_value) # type: ignore
|
|
65
|
+
del value, on_value
|
|
66
|
+
timepicker_is_open = solara.use_reactive(open_value, on_open_value) # type: ignore
|
|
67
|
+
del open_value, on_open_value
|
|
68
|
+
|
|
69
|
+
def set_time_typed_cast(value: Optional[str]):
|
|
70
|
+
if value:
|
|
71
|
+
try:
|
|
72
|
+
time_value = dt.datetime.strptime(value, time_format_display).time()
|
|
73
|
+
return time_value
|
|
74
|
+
except ValueError:
|
|
75
|
+
raise ValueError(f"Time {value} does not match format {time_format_display.replace('%', '')}")
|
|
76
|
+
elif optional:
|
|
77
|
+
return None
|
|
78
|
+
else:
|
|
79
|
+
raise ValueError("Time cannot be empty")
|
|
80
|
+
|
|
81
|
+
def time_to_str(time: Optional[dt.time]) -> str:
|
|
82
|
+
if time is not None:
|
|
83
|
+
return time.strftime(time_format_display)
|
|
84
|
+
return ""
|
|
85
|
+
|
|
86
|
+
def set_time_cast(new_value: Optional[str]):
|
|
87
|
+
if new_value:
|
|
88
|
+
time_value = dt.datetime.strptime(new_value, time_format_internal).time()
|
|
89
|
+
value_reactive.value = time_value
|
|
90
|
+
|
|
91
|
+
def standard_strfy(time: Optional[dt.time]):
|
|
92
|
+
if time is None:
|
|
93
|
+
return None
|
|
94
|
+
else:
|
|
95
|
+
return time.strftime(time_format_internal)
|
|
96
|
+
|
|
97
|
+
time_standard_str = standard_strfy(value_reactive.value)
|
|
98
|
+
|
|
99
|
+
style_flat = solara.util._flatten_style(style)
|
|
100
|
+
|
|
101
|
+
internal_value, error_message, set_value_cast = _use_input_type(value_reactive, set_time_typed_cast, time_to_str)
|
|
102
|
+
|
|
103
|
+
if error_message:
|
|
104
|
+
label += f" ({error_message})"
|
|
105
|
+
input = solara.v.TextField(
|
|
106
|
+
label=label,
|
|
107
|
+
v_model=internal_value,
|
|
108
|
+
on_v_model=set_value_cast,
|
|
109
|
+
append_icon="mdi-clock",
|
|
110
|
+
error=bool(error_message),
|
|
111
|
+
style_="min-width: 290px;" + style_flat,
|
|
112
|
+
class_=", ".join(classes) if classes else "",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
use_close_menu(input, timepicker_is_open)
|
|
116
|
+
|
|
117
|
+
with solara.lab.Menu(
|
|
118
|
+
activator=input,
|
|
119
|
+
close_on_content_click=False,
|
|
120
|
+
open_value=timepicker_is_open,
|
|
121
|
+
use_activator_width=False,
|
|
122
|
+
):
|
|
123
|
+
with solara.v.TimePicker(
|
|
124
|
+
ampm_in_title=twelve_hour_clock,
|
|
125
|
+
v_model=time_standard_str,
|
|
126
|
+
on_v_model=set_time_cast,
|
|
127
|
+
format="24hr" if not twelve_hour_clock else "ampm",
|
|
128
|
+
allowed_minutes=allowed_minutes,
|
|
129
|
+
use_seconds=use_seconds,
|
|
130
|
+
style_="width: 100%;",
|
|
131
|
+
):
|
|
132
|
+
if len(children) > 0:
|
|
133
|
+
solara.display(*children)
|
solara/lab/hooks/dataframe.py
CHANGED
solara/lab/utils/dataframe.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import List
|
|
1
|
+
from typing import List, Union
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
def get_pandas_major():
|
|
@@ -28,6 +28,16 @@ def df_columns(df) -> List[str]:
|
|
|
28
28
|
raise TypeError(f"{type(df)} not supported")
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
def df_row_names(df) -> List[Union[int, str]]:
|
|
32
|
+
"""Return a list of row names from a dataframe."""
|
|
33
|
+
if df_type(df) == "vaex" or df_type(df) == "polars":
|
|
34
|
+
return list(range(df_len(df)))
|
|
35
|
+
elif df_type(df) == "pandas":
|
|
36
|
+
return df.index.tolist()
|
|
37
|
+
else:
|
|
38
|
+
raise TypeError(f"{type(df)} not supported")
|
|
39
|
+
|
|
40
|
+
|
|
31
41
|
def df_slice(df, start: int, stop: int):
|
|
32
42
|
"""Return a subset of rows from a dataframe."""
|
|
33
43
|
if df_type(df) == "pandas":
|
solara/reactive.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
from typing import TypeVar
|
|
1
|
+
from typing import Any, Callable, TypeVar
|
|
2
2
|
|
|
3
3
|
from solara.toestand import Reactive
|
|
4
|
+
import solara.util
|
|
4
5
|
|
|
5
6
|
__all__ = ["reactive", "Reactive"]
|
|
6
7
|
|
|
7
8
|
T = TypeVar("T")
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
def reactive(value: T) -> Reactive[T]:
|
|
11
|
+
def reactive(value: T, equals: Callable[[Any, Any], bool] = solara.util.equals_extra) -> Reactive[T]:
|
|
11
12
|
"""Creates a new Reactive object with the given initial value.
|
|
12
13
|
|
|
13
14
|
Reactive objects are mostly used to manage global or application-wide state in
|
|
@@ -35,6 +36,11 @@ def reactive(value: T) -> Reactive[T]:
|
|
|
35
36
|
|
|
36
37
|
Args:
|
|
37
38
|
value (T): The initial value of the reactive variable.
|
|
39
|
+
equals: A function that returns True if two values are considered equal, and False otherwise.
|
|
40
|
+
The default function is `solara.util.equals`, which performs a deep comparison of the two values
|
|
41
|
+
and is more forgiving than the default `==` operator.
|
|
42
|
+
You can provide a custom function if you need to define a different notion of equality.
|
|
43
|
+
|
|
38
44
|
|
|
39
45
|
Returns:
|
|
40
46
|
Reactive[T]: A new Reactive object with the specified initial value.
|
|
@@ -90,4 +96,4 @@ def reactive(value: T) -> Reactive[T]:
|
|
|
90
96
|
Whenever the counter value changes, `CounterDisplay` automatically updates to display the new value.
|
|
91
97
|
|
|
92
98
|
"""
|
|
93
|
-
return Reactive(value)
|
|
99
|
+
return Reactive(value, equals=equals)
|