solara-ui 1.40.0__py2.py3-none-any.whl → 1.42.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 +30 -11
- solara/_stores.py +185 -0
- solara/components/component_vue.py +26 -2
- solara/components/echarts.py +5 -2
- solara/components/echarts.vue +22 -5
- solara/components/file_drop.py +20 -0
- solara/components/input.py +16 -0
- solara/components/markdown.py +22 -13
- solara/components/spinner-solara.vue +2 -2
- solara/components/spinner.py +17 -2
- solara/hooks/use_reactive.py +8 -1
- solara/reactive.py +9 -3
- solara/server/assets/style.css +2 -0
- solara/server/kernel.py +2 -1
- solara/server/qt.py +113 -0
- solara/server/settings.py +1 -0
- solara/server/starlette.py +2 -2
- solara/server/static/main-vuetify.js +10 -0
- solara/server/static/solara_bootstrap.py +1 -1
- solara/server/templates/loader-solara.html +1 -1
- solara/server/templates/solara.html.j2 +6 -1
- solara/settings.py +14 -0
- solara/template/portal/pyproject.toml +1 -1
- solara/test/pytest_plugin.py +3 -0
- solara/toestand.py +139 -16
- solara/util.py +22 -0
- solara/website/components/markdown.py +45 -1
- solara/website/components/sidebar.py +3 -1
- solara/website/pages/__init__.py +13 -7
- solara/website/pages/changelog/changelog.md +9 -0
- solara/website/pages/documentation/advanced/content/20-understanding/40-routing.md +17 -1
- 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/utilities/on_kernel_start.py +17 -0
- solara/website/pages/documentation/components/input/input.py +22 -0
- solara/website/pages/documentation/components/viz/echarts.py +3 -1
- solara/website/pages/documentation/examples/__init__.py +13 -21
- solara/website/pages/documentation/examples/ai/chatbot.py +1 -1
- solara/website/pages/documentation/examples/general/pokemon_search.py +3 -3
- solara/website/pages/documentation/examples/general/vue_component.py +1 -1
- solara/website/pages/documentation/examples/libraries/altair.py +1 -0
- solara/website/pages/documentation/examples/libraries/bqplot.py +1 -1
- solara/website/pages/documentation/examples/libraries/ipyleaflet.py +1 -1
- solara/website/pages/documentation/examples/libraries/ipyleaflet_advanced.py +1 -1
- solara/website/pages/documentation/examples/utilities/countdown_timer.py +18 -20
- solara/website/pages/documentation/examples/visualization/annotator.py +1 -3
- solara/website/pages/documentation/examples/visualization/linked_views.py +3 -6
- solara/website/pages/documentation/getting_started/content/00-quickstart.md +19 -1
- solara/website/pages/documentation/getting_started/content/01-introduction.md +1 -1
- solara/website/pages/roadmap/roadmap.md +3 -0
- solara/widgets/vue/navigator.vue +46 -16
- solara/widgets/vue/vegalite.vue +18 -0
- {solara_ui-1.40.0.dist-info → solara_ui-1.42.0.dist-info}/METADATA +8 -5
- {solara_ui-1.40.0.dist-info → solara_ui-1.42.0.dist-info}/RECORD +66 -64
- {solara_ui-1.40.0.dist-info → solara_ui-1.42.0.dist-info}/WHEEL +1 -1
- {solara_ui-1.40.0.data → solara_ui-1.42.0.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
- {solara_ui-1.40.0.data → solara_ui-1.42.0.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
- {solara_ui-1.40.0.dist-info → solara_ui-1.42.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -9,7 +9,6 @@ The UI code demonstrates a lot of conditional rendering.
|
|
|
9
9
|
import time
|
|
10
10
|
|
|
11
11
|
import solara
|
|
12
|
-
from solara.alias import rv
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
@solara.component
|
|
@@ -41,24 +40,23 @@ def Page():
|
|
|
41
40
|
|
|
42
41
|
solara.use_thread(run_timer, dependencies=[duration, running])
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
if
|
|
46
|
-
|
|
47
|
-
solara.Error("Duration must be at least 1 second")
|
|
48
|
-
else:
|
|
49
|
-
solara.Markdown(f"# Timer set to {seconds} seconds")
|
|
43
|
+
if not running:
|
|
44
|
+
if duration < 1:
|
|
45
|
+
solara.Error("Duration must be at least 1 second")
|
|
50
46
|
else:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
solara.Markdown(f"# Timer set to {seconds} seconds")
|
|
48
|
+
else:
|
|
49
|
+
if seconds:
|
|
50
|
+
solara.Markdown(f"# {seconds} seconds left")
|
|
51
|
+
else:
|
|
52
|
+
solara.solara.Markdown("# Time's up!")
|
|
53
|
+
|
|
54
|
+
solara.v.TextField(type="number", v_model=duration, on_v_model=on_duration, disabled=running)
|
|
55
|
+
with solara.Row():
|
|
56
|
+
if running:
|
|
57
|
+
solara.Button("Stop", on_click=lambda: set_running(False), icon_name="mdi-stop")
|
|
58
|
+
else:
|
|
59
|
+
if duration != seconds:
|
|
60
|
+
solara.Button("Reset", on_click=lambda: set_seconds(duration), icon_name="mdi-restart")
|
|
59
61
|
else:
|
|
60
|
-
|
|
61
|
-
solara.Button("Reset", on_click=lambda: set_seconds(duration), icon_name="mdi-restart")
|
|
62
|
-
else:
|
|
63
|
-
solara.Button("Start", on_click=lambda: set_running(True), icon_name="mdi-play", disabled=seconds < 1)
|
|
64
|
-
return main
|
|
62
|
+
solara.Button("Start", on_click=lambda: set_running(True), icon_name="mdi-play", disabled=seconds < 1)
|
|
@@ -49,8 +49,8 @@ def ClickScatter(df, x, y, color, click_row, on_click: Callable[[ClickPoint], No
|
|
|
49
49
|
click_y = df[y].values[click_row]
|
|
50
50
|
fig.add_trace(px.scatter(x=[click_x], y=[click_y], text=["⭐️"]).data[0])
|
|
51
51
|
# make the figure a bit smaller
|
|
52
|
-
fig.update_layout(width=
|
|
53
|
-
with solara.
|
|
52
|
+
fig.update_layout(width=340)
|
|
53
|
+
with solara.Column(style={"width": "340px"}) as main:
|
|
54
54
|
solara.FigurePlotly(fig, on_click=on_click_trace)
|
|
55
55
|
solara.Select(label="X-axis", value=x, values=columns, on_value=set_x)
|
|
56
56
|
solara.Select(label="Y-axis", value=y, values=columns, on_value=set_y)
|
|
@@ -65,8 +65,7 @@ def Page():
|
|
|
65
65
|
else:
|
|
66
66
|
clicked_row = None
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
with solara.HBox():
|
|
68
|
+
with solara.Row(justify="center", style={"flex-wrap": "wrap"}):
|
|
70
69
|
ClickScatter(df, "sepal_length", "sepal_width", "species", clicked_row, on_click=set_click_point)
|
|
71
70
|
ClickScatter(df, "petal_length", "petal_width", "species", clicked_row, on_click=set_click_point)
|
|
72
71
|
if click_point is not None:
|
|
@@ -80,5 +79,3 @@ def Page():
|
|
|
80
79
|
)
|
|
81
80
|
else:
|
|
82
81
|
solara.Info("Click to select a point")
|
|
83
|
-
|
|
84
|
-
return main
|
|
@@ -24,7 +24,7 @@ Run `pip install solara`, or follow the [Installation instructions](/documentati
|
|
|
24
24
|
|
|
25
25
|
Put the following Python snippet in a file (we suggest `sol.py`), or put it in a Jupyter notebook cell:
|
|
26
26
|
|
|
27
|
-
```solara
|
|
27
|
+
```solara {pycafe-link}
|
|
28
28
|
import solara
|
|
29
29
|
|
|
30
30
|
# Declare reactive variables at the top level. Components using these variables
|
|
@@ -87,3 +87,21 @@ In case you forgot how to start a notebook server:
|
|
|
87
87
|
Or the more modern Jupyter lab:
|
|
88
88
|
|
|
89
89
|
$ jupyter lab
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
## Run as app (experimental)
|
|
93
|
+
|
|
94
|
+
You can also run the script as a standalone app. This requires the extra packages `qtpy` and `PySide6` (or `PyQt6`) to be installed.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
$ pip install pip install qtpy PySide6
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Run from the command line in the same directory where you put your file (`sol.py`):
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
$ solara run sol.py --qt
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
<img src="https://dxhl76zpt6fap.cloudfront.net/public/solara-quickstart-app.webp" alt="Markdown Monster icon"/>
|
|
@@ -11,6 +11,9 @@ Exciting news! We aim to release Solara 2.0 by the end of the year. For the 2.0
|
|
|
11
11
|
|
|
12
12
|
- Elimination of common mistakes, such as detecting state mutations and avoiding misuse of hooks (e.g., using hooks in loops).
|
|
13
13
|
|
|
14
|
+
State mutation detection will be the default for Solara 2.0, but can be enabled in Solara > 1.41.0 by setting the environment variable `SOLARA_STORAGE_MUTATION_DETECTION=1`.
|
|
15
|
+
|
|
16
|
+
|
|
14
17
|
- [See more details in the 2.0 milestone on GitHub.](https://github.com/widgetti/solara/milestone/1)
|
|
15
18
|
|
|
16
19
|
|
solara/widgets/vue/navigator.vue
CHANGED
|
@@ -14,43 +14,37 @@ modules.export = {
|
|
|
14
14
|
}
|
|
15
15
|
window.solara.router.push = (href) => {
|
|
16
16
|
console.log("external router push", href);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
this.location = href;
|
|
17
|
+
const url = new URL(href, window.location.origin + solara.rootPath);
|
|
18
|
+
this.location = url.pathname + url.search;
|
|
19
|
+
this.hash = url.hash;
|
|
22
20
|
};
|
|
23
21
|
let location = window.location.pathname.slice(solara.rootPath.length);
|
|
24
|
-
// take of the anchor
|
|
25
|
-
if (location.indexOf("#") !== -1) {
|
|
26
|
-
location = location.slice(0, location.indexOf("#"));
|
|
27
|
-
}
|
|
28
22
|
this.location = location + window.location.search;
|
|
23
|
+
this.hash = window.location.hash;
|
|
29
24
|
window.addEventListener("popstate", this.onPopState);
|
|
30
25
|
window.addEventListener("scroll", this.onScroll);
|
|
26
|
+
window.addEventListener("hashchange", this.onHashChange);
|
|
27
|
+
window.addEventListener("solara.pageReady", this.onPageLoad);
|
|
31
28
|
},
|
|
32
29
|
destroyed() {
|
|
33
30
|
window.removeEventListener("popstate", this.onPopState);
|
|
34
31
|
window.removeEventListener("scroll", this.onScroll);
|
|
32
|
+
window.removeEventListener("hashchange", this.onHashChange);
|
|
33
|
+
window.removeEventListener("solara.pageReady", this.onPageLoad);
|
|
35
34
|
},
|
|
36
35
|
methods: {
|
|
37
36
|
onScroll() {
|
|
38
37
|
window.history.replaceState(
|
|
39
38
|
{ top: document.documentElement.scrollTop },
|
|
40
39
|
null,
|
|
41
|
-
|
|
40
|
+
window.location.href
|
|
42
41
|
);
|
|
43
42
|
},
|
|
44
43
|
onPopState(event) {
|
|
45
|
-
console.log("pop state!", event.state, window.location.href);
|
|
46
44
|
if (!window.location.pathname.startsWith(solara.rootPath)) {
|
|
47
45
|
throw `window.location.pathname = ${window.location.pathname}, but it should start with the solara.rootPath = ${solara.rootPath}`;
|
|
48
46
|
}
|
|
49
47
|
let newLocation = window.location.pathname.slice(solara.rootPath.length);
|
|
50
|
-
// the router/server shouldn't care about the hash, that's for the frontend
|
|
51
|
-
if (newLocation.indexOf("#") !== -1) {
|
|
52
|
-
newLocation = newLocation.slice(0, newLocation.indexOf("#"));
|
|
53
|
-
}
|
|
54
48
|
this.location = newLocation + window.location.search;
|
|
55
49
|
if (event.state) {
|
|
56
50
|
const top = event.state.top;
|
|
@@ -63,6 +57,32 @@ modules.export = {
|
|
|
63
57
|
*/
|
|
64
58
|
}
|
|
65
59
|
},
|
|
60
|
+
onHashChange(event) {
|
|
61
|
+
if (!window.location.pathname.startsWith(solara.rootPath)) {
|
|
62
|
+
throw `window.location.pathname = ${window.location.pathname}, but it should start with the solara.rootPath = ${solara.rootPath}`;
|
|
63
|
+
}
|
|
64
|
+
this.hash = window.location.hash;
|
|
65
|
+
},
|
|
66
|
+
onPageLoad(event) {
|
|
67
|
+
if (!window.location.pathname.startsWith(solara.rootPath)) {
|
|
68
|
+
throw `window.location.pathname = ${window.location.pathname}, but it should start with the solara.rootPath = ${solara.rootPath}`;
|
|
69
|
+
}
|
|
70
|
+
// If we've navigated to a hash with the same name on a different page the watch on hash won't trigger
|
|
71
|
+
if (this.hash && this.hash === window.location.hash) {
|
|
72
|
+
this.navigateToHash(this.hash);
|
|
73
|
+
}
|
|
74
|
+
this.hash = window.location.hash;
|
|
75
|
+
},
|
|
76
|
+
makeFullRelativeUrl() {
|
|
77
|
+
const url = new URL(this.location, window.location.origin + solara.rootPath);
|
|
78
|
+
return url.pathname + this.hash + url.search;
|
|
79
|
+
},
|
|
80
|
+
navigateToHash(hash) {
|
|
81
|
+
const targetEl = document.getElementById(hash.slice(1));
|
|
82
|
+
if (targetEl) {
|
|
83
|
+
targetEl.scrollIntoView();
|
|
84
|
+
}
|
|
85
|
+
},
|
|
66
86
|
},
|
|
67
87
|
watch: {
|
|
68
88
|
location(value) {
|
|
@@ -89,7 +109,7 @@ modules.export = {
|
|
|
89
109
|
document.documentElement.scrollTop
|
|
90
110
|
);
|
|
91
111
|
if (oldLocation != this.location) {
|
|
92
|
-
window.history.pushState({ top: 0 }, null,
|
|
112
|
+
window.history.pushState({ top: 0 }, null, this.makeFullRelativeUrl());
|
|
93
113
|
if (pathnameNew != pathnameOld) {
|
|
94
114
|
// we scroll to the top only when we change page, not when we change
|
|
95
115
|
// the search string
|
|
@@ -99,6 +119,16 @@ modules.export = {
|
|
|
99
119
|
window.dispatchEvent(event);
|
|
100
120
|
}
|
|
101
121
|
},
|
|
122
|
+
hash(value) {
|
|
123
|
+
if (value) {
|
|
124
|
+
this.navigateToHash(value);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
data() {
|
|
129
|
+
return {
|
|
130
|
+
hash: "",
|
|
131
|
+
};
|
|
102
132
|
},
|
|
103
133
|
};
|
|
104
134
|
</script>
|
solara/widgets/vue/vegalite.vue
CHANGED
|
@@ -15,6 +15,11 @@ module.exports = {
|
|
|
15
15
|
mounted() {
|
|
16
16
|
this.do_plot_debounced();
|
|
17
17
|
},
|
|
18
|
+
destroyed() {
|
|
19
|
+
if (this.observer) {
|
|
20
|
+
this.observer.disconnect();
|
|
21
|
+
}
|
|
22
|
+
},
|
|
18
23
|
watch: {
|
|
19
24
|
spec() {
|
|
20
25
|
this.do_plot_debounced();
|
|
@@ -29,7 +34,15 @@ module.exports = {
|
|
|
29
34
|
(async () => {
|
|
30
35
|
const spec = {
|
|
31
36
|
...this.spec,
|
|
37
|
+
"renderer": "svg",
|
|
32
38
|
};
|
|
39
|
+
if (spec.width === "container") {
|
|
40
|
+
this.$refs.plotElement.classList.add("width-container")
|
|
41
|
+
this.observer = new ResizeObserver(() => {
|
|
42
|
+
view.resize();
|
|
43
|
+
});
|
|
44
|
+
this.observer.observe(this.$refs.plotElement);
|
|
45
|
+
}
|
|
33
46
|
const { view } = await vegaEmbed(this.$refs.plotElement, spec);
|
|
34
47
|
// events https://github.com/vega/vega-view#event-handling
|
|
35
48
|
if (this.listen_to_click) {
|
|
@@ -110,3 +123,8 @@ module.exports = {
|
|
|
110
123
|
},
|
|
111
124
|
}
|
|
112
125
|
</script>
|
|
126
|
+
<style id="vega-embed-container-width">
|
|
127
|
+
.width-container.vega-embed {
|
|
128
|
+
width: 100%;
|
|
129
|
+
}
|
|
130
|
+
</style>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: solara-ui
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.42.0
|
|
4
4
|
Dynamic: Summary
|
|
5
5
|
Project-URL: Home, https://www.github.com/widgetti/solara
|
|
6
6
|
Project-URL: Documentation, https://solara.dev
|
|
@@ -26,7 +26,6 @@ License: The MIT License (MIT)
|
|
|
26
26
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
27
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
28
28
|
THE SOFTWARE.
|
|
29
|
-
License-File: LICENSE
|
|
30
29
|
Classifier: License :: OSI Approved :: MIT License
|
|
31
30
|
Requires-Dist: humanize
|
|
32
31
|
Requires-Dist: ipyvue>=1.9.0
|
|
@@ -35,9 +34,13 @@ Requires-Dist: ipywidgets>=7.7
|
|
|
35
34
|
Requires-Dist: reacton>=1.7.1
|
|
36
35
|
Requires-Dist: requests
|
|
37
36
|
Provides-Extra: all
|
|
38
|
-
Requires-Dist:
|
|
39
|
-
Requires-Dist:
|
|
40
|
-
Requires-Dist:
|
|
37
|
+
Requires-Dist: cachetools; extra == 'all'
|
|
38
|
+
Requires-Dist: markdown; extra == 'all'
|
|
39
|
+
Requires-Dist: numpy; extra == 'all'
|
|
40
|
+
Requires-Dist: pillow; extra == 'all'
|
|
41
|
+
Requires-Dist: pygments; extra == 'all'
|
|
42
|
+
Requires-Dist: pygments==2.10; (python_version < '3.7') and extra == 'all'
|
|
43
|
+
Requires-Dist: pymdown-extensions; extra == 'all'
|
|
41
44
|
Provides-Extra: cache
|
|
42
45
|
Requires-Dist: cachetools; extra == 'cache'
|
|
43
46
|
Provides-Extra: extra
|