solara-ui 1.31.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.
- prefix/etc/jupyter/jupyter_notebook_config.d/solara.json +7 -0
- prefix/etc/jupyter/jupyter_server_config.d/solara.json +7 -0
- solara/__init__.py +124 -0
- solara/__main__.py +734 -0
- solara/alias.py +6 -0
- solara/autorouting.py +546 -0
- solara/cache.py +303 -0
- solara/checks.html +71 -0
- solara/checks.py +224 -0
- solara/comm.py +28 -0
- solara/components/__init__.py +59 -0
- solara/components/alert.py +155 -0
- solara/components/applayout.py +393 -0
- solara/components/button.py +85 -0
- solara/components/card.py +87 -0
- solara/components/checkbox.py +50 -0
- solara/components/code_highlight_css.py +11 -0
- solara/components/code_highlight_css.vue +63 -0
- solara/components/columns.py +159 -0
- solara/components/component_vue.py +110 -0
- solara/components/cross_filter.py +335 -0
- solara/components/dataframe.py +546 -0
- solara/components/datatable.py +221 -0
- solara/components/datatable.vue +175 -0
- solara/components/details.py +21 -0
- solara/components/download.vue +35 -0
- solara/components/echarts.py +75 -0
- solara/components/echarts.vue +128 -0
- solara/components/figure_altair.py +39 -0
- solara/components/file_browser.py +182 -0
- solara/components/file_download.py +199 -0
- solara/components/file_drop.py +139 -0
- solara/components/file_drop.vue +83 -0
- solara/components/file_list_widget.vue +78 -0
- solara/components/head.py +27 -0
- solara/components/head_tag.py +49 -0
- solara/components/head_tag.vue +60 -0
- solara/components/image.py +173 -0
- solara/components/input.py +436 -0
- solara/components/link.py +55 -0
- solara/components/markdown.py +378 -0
- solara/components/markdown_editor.py +25 -0
- solara/components/markdown_editor.vue +362 -0
- solara/components/matplotlib.py +74 -0
- solara/components/meta.py +47 -0
- solara/components/misc.py +333 -0
- solara/components/pivot_table.py +258 -0
- solara/components/pivot_table.vue +158 -0
- solara/components/progress.py +47 -0
- solara/components/select.py +182 -0
- solara/components/select.vue +27 -0
- solara/components/slider.py +442 -0
- solara/components/slider_date.vue +56 -0
- solara/components/spinner-solara.vue +105 -0
- solara/components/spinner.py +30 -0
- solara/components/sql_code.py +33 -0
- solara/components/sql_code.vue +128 -0
- solara/components/style.py +105 -0
- solara/components/switch.py +68 -0
- solara/components/tab_navigation.py +37 -0
- solara/components/title.py +90 -0
- solara/components/title.vue +38 -0
- solara/components/togglebuttons.py +200 -0
- solara/components/tooltip.py +61 -0
- solara/datatypes.py +143 -0
- solara/express.py +241 -0
- solara/hooks/__init__.py +4 -0
- solara/hooks/dataframe.py +99 -0
- solara/hooks/misc.py +263 -0
- solara/hooks/use_reactive.py +129 -0
- solara/hooks/use_thread.py +129 -0
- solara/kitchensink.py +8 -0
- solara/lab/__init__.py +34 -0
- solara/lab/components/__init__.py +6 -0
- solara/lab/components/chat.py +203 -0
- solara/lab/components/confirmation_dialog.py +163 -0
- solara/lab/components/cross_filter.py +7 -0
- solara/lab/components/input_date.py +298 -0
- solara/lab/components/menu.py +181 -0
- solara/lab/components/menu.vue +38 -0
- solara/lab/components/tabs.py +274 -0
- solara/lab/components/theming.py +98 -0
- solara/lab/components/theming.vue +72 -0
- solara/lab/hooks/__init__.py +0 -0
- solara/lab/hooks/dataframe.py +12 -0
- solara/lab/toestand.py +3 -0
- solara/lab/utils/__init__.py +2 -0
- solara/lab/utils/cookies.py +5 -0
- solara/lab/utils/dataframe.py +115 -0
- solara/lab/utils/headers.py +5 -0
- solara/layout.py +44 -0
- solara/lifecycle.py +46 -0
- solara/minisettings.py +133 -0
- solara/py.typed +0 -0
- solara/reactive.py +93 -0
- solara/routing.py +268 -0
- solara/scope/__init__.py +88 -0
- solara/scope/types.py +55 -0
- solara/server/__init__.py +0 -0
- solara/server/app.py +491 -0
- solara/server/assets/custom.css +1 -0
- solara/server/assets/custom.js +1 -0
- solara/server/assets/favicon.png +0 -0
- solara/server/assets/favicon.svg +5 -0
- solara/server/assets/style.css +1665 -0
- solara/server/assets/theme-dark.css +437 -0
- solara/server/assets/theme-light.css +420 -0
- solara/server/assets/theme.js +3 -0
- solara/server/cdn_helper.py +77 -0
- solara/server/esm.py +69 -0
- solara/server/fastapi.py +5 -0
- solara/server/flask.py +286 -0
- solara/server/jupyter/__init__.py +2 -0
- solara/server/jupyter/cdn_handler.py +28 -0
- solara/server/jupyter/server_extension.py +29 -0
- solara/server/jupytertools.py +46 -0
- solara/server/kernel.py +338 -0
- solara/server/kernel_context.py +357 -0
- solara/server/patch.py +552 -0
- solara/server/reload.py +242 -0
- solara/server/server.py +456 -0
- solara/server/settings.py +215 -0
- solara/server/shell.py +251 -0
- solara/server/starlette.py +601 -0
- solara/server/static/ansi.js +270 -0
- solara/server/static/highlight-dark.css +82 -0
- solara/server/static/highlight.css +43 -0
- solara/server/static/main-vuetify.js +260 -0
- solara/server/static/main.js +163 -0
- solara/server/static/solara_bootstrap.py +129 -0
- solara/server/static/sun.svg +23 -0
- solara/server/static/webworker.js +42 -0
- solara/server/telemetry.py +212 -0
- solara/server/templates/index.html.j2 +1 -0
- solara/server/templates/loader-plain.css +11 -0
- solara/server/templates/loader-plain.html +20 -0
- solara/server/templates/loader-solara.css +111 -0
- solara/server/templates/loader-solara.html +40 -0
- solara/server/templates/plain.html +82 -0
- solara/server/templates/solara.html.j2 +446 -0
- solara/server/threaded.py +75 -0
- solara/server/utils.py +30 -0
- solara/server/websocket.py +45 -0
- solara/settings.py +56 -0
- solara/tasks.py +837 -0
- solara/template/button.py +16 -0
- solara/template/markdown.py +42 -0
- solara/template/portal/.flake8 +6 -0
- solara/template/portal/.pre-commit-config.yaml +28 -0
- solara/template/portal/LICENSE +21 -0
- solara/template/portal/Procfile +7 -0
- solara/template/portal/mypy.ini +3 -0
- solara/template/portal/pyproject.toml +26 -0
- solara/template/portal/solara_portal/__init__.py +4 -0
- solara/template/portal/solara_portal/components/__init__.py +2 -0
- solara/template/portal/solara_portal/components/article.py +28 -0
- solara/template/portal/solara_portal/components/data.py +28 -0
- solara/template/portal/solara_portal/components/header.py +6 -0
- solara/template/portal/solara_portal/components/layout.py +6 -0
- solara/template/portal/solara_portal/content/articles/equis-in-vidi.md +85 -0
- solara/template/portal/solara_portal/content/articles/substiterat-vati.md +70 -0
- solara/template/portal/solara_portal/data.py +60 -0
- solara/template/portal/solara_portal/pages/__init__.py +67 -0
- solara/template/portal/solara_portal/pages/article/__init__.py +26 -0
- solara/template/portal/solara_portal/pages/tabular.py +29 -0
- solara/template/portal/solara_portal/pages/viz/__init__.py +70 -0
- solara/template/portal/solara_portal/pages/viz/overview.py +14 -0
- solara/test/__init__.py +0 -0
- solara/test/pytest_plugin.py +697 -0
- solara/toestand.py +772 -0
- solara/util.py +308 -0
- solara/website/__init__.py +0 -0
- solara/website/assets/custom.css +468 -0
- solara/website/assets/images/logo-small.png +0 -0
- solara/website/assets/images/logo.svg +17 -0
- solara/website/assets/images/logo_white.svg +50 -0
- solara/website/assets/theme.js +8 -0
- solara/website/components/__init__.py +5 -0
- solara/website/components/algolia.vue +24 -0
- solara/website/components/algolia_api.vue +187 -0
- solara/website/components/docs.py +118 -0
- solara/website/components/header.py +72 -0
- solara/website/components/hero.py +15 -0
- solara/website/components/mailchimp.py +12 -0
- solara/website/components/mailchimp.vue +47 -0
- solara/website/components/markdown.py +30 -0
- solara/website/components/notebook.py +171 -0
- solara/website/pages/__init__.py +575 -0
- solara/website/pages/apps/__init__.py +16 -0
- solara/website/pages/apps/authorization/__init__.py +119 -0
- solara/website/pages/apps/authorization/admin.py +12 -0
- solara/website/pages/apps/authorization/users.py +12 -0
- solara/website/pages/apps/jupyter-dashboard-1.py +116 -0
- solara/website/pages/apps/layout-demo.py +40 -0
- solara/website/pages/apps/multipage/__init__.py +38 -0
- solara/website/pages/apps/multipage/page1.py +26 -0
- solara/website/pages/apps/multipage/page2.py +34 -0
- solara/website/pages/apps/scatter.py +136 -0
- solara/website/pages/apps/scrolling.py +63 -0
- solara/website/pages/apps/tutorial-streamlit.py +18 -0
- solara/website/pages/changelog/__init__.py +8 -0
- solara/website/pages/changelog/changelog.md +204 -0
- solara/website/pages/contact/__init__.py +8 -0
- solara/website/pages/contact/contact.md +17 -0
- solara/website/pages/doc_use_download.py +85 -0
- solara/website/pages/documentation/__init__.py +184 -0
- solara/website/pages/documentation/advanced/__init__.py +9 -0
- solara/website/pages/documentation/advanced/content/00-overview.md +1 -0
- solara/website/pages/documentation/advanced/content/10-howto/00-overview.md +6 -0
- solara/website/pages/documentation/advanced/content/10-howto/10-multipage.md +196 -0
- solara/website/pages/documentation/advanced/content/10-howto/20-layout.md +125 -0
- solara/website/pages/documentation/advanced/content/10-howto/30-testing.md +162 -0
- solara/website/pages/documentation/advanced/content/10-howto/31-debugging.md +69 -0
- solara/website/pages/documentation/advanced/content/10-howto/40-embed.md +49 -0
- solara/website/pages/documentation/advanced/content/10-howto/50-ipywidget_libraries.md +124 -0
- solara/website/pages/documentation/advanced/content/15-reference/00-overview.md +3 -0
- solara/website/pages/documentation/advanced/content/15-reference/40-static_files.md +31 -0
- solara/website/pages/documentation/advanced/content/15-reference/41-asset-files.md +36 -0
- solara/website/pages/documentation/advanced/content/15-reference/60-static-site-generation.md +59 -0
- solara/website/pages/documentation/advanced/content/15-reference/70-search.md +34 -0
- solara/website/pages/documentation/advanced/content/15-reference/80-reloading.md +34 -0
- solara/website/pages/documentation/advanced/content/15-reference/90-notebook-support.md +7 -0
- solara/website/pages/documentation/advanced/content/15-reference/95-caching.md +148 -0
- solara/website/pages/documentation/advanced/content/20-understanding/00-introduction.md +10 -0
- solara/website/pages/documentation/advanced/content/20-understanding/05-ipywidgets.md +35 -0
- solara/website/pages/documentation/advanced/content/20-understanding/06-ipyvuetify.md +42 -0
- solara/website/pages/documentation/advanced/content/20-understanding/10-reacton.md +28 -0
- solara/website/pages/documentation/advanced/content/20-understanding/12-reacton-basics.md +108 -0
- solara/website/pages/documentation/advanced/content/20-understanding/15-anatomy.md +23 -0
- solara/website/pages/documentation/advanced/content/20-understanding/17-rules-of-hooks.md +7 -0
- solara/website/pages/documentation/advanced/content/20-understanding/18-containers.md +166 -0
- solara/website/pages/documentation/advanced/content/20-understanding/20-solara.md +18 -0
- solara/website/pages/documentation/advanced/content/20-understanding/40-routing.md +240 -0
- solara/website/pages/documentation/advanced/content/20-understanding/50-solara-server.md +97 -0
- solara/website/pages/documentation/advanced/content/20-understanding/60-voila.md +12 -0
- solara/website/pages/documentation/advanced/content/30-enterprise/00-overview.md +1 -0
- solara/website/pages/documentation/advanced/content/30-enterprise/10-oauth.md +171 -0
- solara/website/pages/documentation/advanced/content/40-development/00-overview.md +0 -0
- solara/website/pages/documentation/advanced/content/40-development/01-contribute.md +45 -0
- solara/website/pages/documentation/advanced/content/40-development/10-setup.md +76 -0
- solara/website/pages/documentation/api/__init__.py +19 -0
- solara/website/pages/documentation/api/cross_filter/__init__.py +9 -0
- solara/website/pages/documentation/api/cross_filter/cross_filter_dataframe.py +23 -0
- solara/website/pages/documentation/api/cross_filter/cross_filter_report.py +22 -0
- solara/website/pages/documentation/api/cross_filter/cross_filter_select.py +22 -0
- solara/website/pages/documentation/api/cross_filter/cross_filter_slider.py +22 -0
- solara/website/pages/documentation/api/hooks/__init__.py +9 -0
- solara/website/pages/documentation/api/hooks/use_cross_filter.py +25 -0
- solara/website/pages/documentation/api/hooks/use_dark_effective.py +12 -0
- solara/website/pages/documentation/api/hooks/use_effect.md +43 -0
- solara/website/pages/documentation/api/hooks/use_effect.py +9 -0
- solara/website/pages/documentation/api/hooks/use_exception.py +33 -0
- solara/website/pages/documentation/api/hooks/use_memo.md +16 -0
- solara/website/pages/documentation/api/hooks/use_memo.py +9 -0
- solara/website/pages/documentation/api/hooks/use_previous.py +33 -0
- solara/website/pages/documentation/api/hooks/use_reactive.py +16 -0
- solara/website/pages/documentation/api/hooks/use_state.py +10 -0
- solara/website/pages/documentation/api/hooks/use_state_or_update.py +69 -0
- solara/website/pages/documentation/api/hooks/use_thread.md +58 -0
- solara/website/pages/documentation/api/hooks/use_thread.py +44 -0
- solara/website/pages/documentation/api/hooks/use_trait_observe.py +12 -0
- solara/website/pages/documentation/api/routing/__init__.py +9 -0
- solara/website/pages/documentation/api/routing/generate_routes.py +10 -0
- solara/website/pages/documentation/api/routing/generate_routes_directory.py +10 -0
- solara/website/pages/documentation/api/routing/resolve_path.py +35 -0
- solara/website/pages/documentation/api/routing/route.py +31 -0
- solara/website/pages/documentation/api/routing/use_route.py +80 -0
- solara/website/pages/documentation/api/routing/use_router.py +16 -0
- solara/website/pages/documentation/api/utilities/__init__.py +9 -0
- solara/website/pages/documentation/api/utilities/component_vue.py +10 -0
- solara/website/pages/documentation/api/utilities/computed.py +16 -0
- solara/website/pages/documentation/api/utilities/display.py +16 -0
- solara/website/pages/documentation/api/utilities/get_kernel_id.py +16 -0
- solara/website/pages/documentation/api/utilities/get_session_id.py +16 -0
- solara/website/pages/documentation/api/utilities/memoize.py +35 -0
- solara/website/pages/documentation/api/utilities/on_kernel_start.py +27 -0
- solara/website/pages/documentation/api/utilities/reactive.py +16 -0
- solara/website/pages/documentation/api/utilities/widget.py +104 -0
- solara/website/pages/documentation/components/__init__.py +12 -0
- solara/website/pages/documentation/components/advanced/__init__.py +9 -0
- solara/website/pages/documentation/components/advanced/link.py +27 -0
- solara/website/pages/documentation/components/advanced/meta.py +20 -0
- solara/website/pages/documentation/components/advanced/style.py +45 -0
- solara/website/pages/documentation/components/common.py +9 -0
- solara/website/pages/documentation/components/data/__init__.py +9 -0
- solara/website/pages/documentation/components/data/dataframe.py +44 -0
- solara/website/pages/documentation/components/data/pivot_table.py +81 -0
- solara/website/pages/documentation/components/enterprise/__init__.py +9 -0
- solara/website/pages/documentation/components/enterprise/avatar.py +24 -0
- solara/website/pages/documentation/components/enterprise/avatar_menu.py +25 -0
- solara/website/pages/documentation/components/input/__init__.py +9 -0
- solara/website/pages/documentation/components/input/button.py +23 -0
- solara/website/pages/documentation/components/input/checkbox.py +10 -0
- solara/website/pages/documentation/components/input/file_browser.py +32 -0
- solara/website/pages/documentation/components/input/file_drop.py +76 -0
- solara/website/pages/documentation/components/input/input.py +19 -0
- solara/website/pages/documentation/components/input/select.py +22 -0
- solara/website/pages/documentation/components/input/slider.py +29 -0
- solara/website/pages/documentation/components/input/switch.py +10 -0
- solara/website/pages/documentation/components/input/togglebuttons.py +21 -0
- solara/website/pages/documentation/components/lab/__init__.py +9 -0
- solara/website/pages/documentation/components/lab/chat.py +109 -0
- solara/website/pages/documentation/components/lab/confirmation_dialog.py +55 -0
- solara/website/pages/documentation/components/lab/cookies_headers.py +48 -0
- solara/website/pages/documentation/components/lab/input_date.py +20 -0
- solara/website/pages/documentation/components/lab/menu.py +22 -0
- solara/website/pages/documentation/components/lab/tab.py +25 -0
- solara/website/pages/documentation/components/lab/tabs.py +45 -0
- solara/website/pages/documentation/components/lab/task.py +11 -0
- solara/website/pages/documentation/components/lab/theming.py +72 -0
- solara/website/pages/documentation/components/lab/use_task.py +11 -0
- solara/website/pages/documentation/components/layout/__init__.py +9 -0
- solara/website/pages/documentation/components/layout/app_bar.py +16 -0
- solara/website/pages/documentation/components/layout/app_bar_title.py +16 -0
- solara/website/pages/documentation/components/layout/app_layout.py +24 -0
- solara/website/pages/documentation/components/layout/card.py +15 -0
- solara/website/pages/documentation/components/layout/card_actions.py +16 -0
- solara/website/pages/documentation/components/layout/column.py +30 -0
- solara/website/pages/documentation/components/layout/columns.py +27 -0
- solara/website/pages/documentation/components/layout/columns_responsive.py +68 -0
- solara/website/pages/documentation/components/layout/griddraggable.py +62 -0
- solara/website/pages/documentation/components/layout/gridfixed.py +21 -0
- solara/website/pages/documentation/components/layout/hbox.py +18 -0
- solara/website/pages/documentation/components/layout/row.py +30 -0
- solara/website/pages/documentation/components/layout/sidebar.py +24 -0
- solara/website/pages/documentation/components/layout/vbox.py +19 -0
- solara/website/pages/documentation/components/output/__init__.py +9 -0
- solara/website/pages/documentation/components/output/file_download.py +11 -0
- solara/website/pages/documentation/components/output/html.py +21 -0
- solara/website/pages/documentation/components/output/image.py +11 -0
- solara/website/pages/documentation/components/output/markdown.py +57 -0
- solara/website/pages/documentation/components/output/markdown_editor.py +51 -0
- solara/website/pages/documentation/components/output/sql_code.py +85 -0
- solara/website/pages/documentation/components/output/tooltip.py +11 -0
- solara/website/pages/documentation/components/page/__init__.py +9 -0
- solara/website/pages/documentation/components/page/head.py +18 -0
- solara/website/pages/documentation/components/page/title.py +27 -0
- solara/website/pages/documentation/components/status/__init__.py +9 -0
- solara/website/pages/documentation/components/status/error.py +40 -0
- solara/website/pages/documentation/components/status/info.py +40 -0
- solara/website/pages/documentation/components/status/progress.py +10 -0
- solara/website/pages/documentation/components/status/spinner.py +11 -0
- solara/website/pages/documentation/components/status/success.py +40 -0
- solara/website/pages/documentation/components/status/warning.py +47 -0
- solara/website/pages/documentation/components/viz/__init__.py +9 -0
- solara/website/pages/documentation/components/viz/altair.py +42 -0
- solara/website/pages/documentation/components/viz/echarts.py +75 -0
- solara/website/pages/documentation/components/viz/matplotlib.py +30 -0
- solara/website/pages/documentation/components/viz/plotly.py +63 -0
- solara/website/pages/documentation/components/viz/plotly_express.py +41 -0
- solara/website/pages/documentation/examples/__init__.py +52 -0
- solara/website/pages/documentation/examples/ai/__init__.py +11 -0
- solara/website/pages/documentation/examples/ai/chatbot.py +95 -0
- solara/website/pages/documentation/examples/ai/tokenizer.py +107 -0
- solara/website/pages/documentation/examples/basics/__init__.py +10 -0
- solara/website/pages/documentation/examples/basics/sine.py +28 -0
- solara/website/pages/documentation/examples/fullscreen/__init__.py +10 -0
- solara/website/pages/documentation/examples/fullscreen/authorization.py +3 -0
- solara/website/pages/documentation/examples/fullscreen/layout_demo.py +3 -0
- solara/website/pages/documentation/examples/fullscreen/multipage.py +3 -0
- solara/website/pages/documentation/examples/fullscreen/scatter.py +3 -0
- solara/website/pages/documentation/examples/fullscreen/scrolling.py +3 -0
- solara/website/pages/documentation/examples/fullscreen/tutorial_streamlit.py +3 -0
- solara/website/pages/documentation/examples/general/__init__.py +10 -0
- solara/website/pages/documentation/examples/general/custom_storage.py +70 -0
- solara/website/pages/documentation/examples/general/deploy_model.py +115 -0
- solara/website/pages/documentation/examples/general/live_update.py +38 -0
- solara/website/pages/documentation/examples/general/login_oauth.py +81 -0
- solara/website/pages/documentation/examples/general/mycard.vue +58 -0
- solara/website/pages/documentation/examples/general/pokemon_search.py +51 -0
- solara/website/pages/documentation/examples/general/vue_component.py +50 -0
- solara/website/pages/documentation/examples/ipycanvas.py +49 -0
- solara/website/pages/documentation/examples/libraries/__init__.py +10 -0
- solara/website/pages/documentation/examples/libraries/altair.py +64 -0
- solara/website/pages/documentation/examples/libraries/bqplot.py +39 -0
- solara/website/pages/documentation/examples/libraries/ipyleaflet.py +33 -0
- solara/website/pages/documentation/examples/libraries/ipyleaflet_advanced.py +66 -0
- solara/website/pages/documentation/examples/utilities/__init__.py +10 -0
- solara/website/pages/documentation/examples/utilities/calculator.py +157 -0
- solara/website/pages/documentation/examples/utilities/countdown_timer.py +64 -0
- solara/website/pages/documentation/examples/utilities/todo.py +196 -0
- solara/website/pages/documentation/examples/visualization/__init__.py +6 -0
- solara/website/pages/documentation/examples/visualization/annotator.py +69 -0
- solara/website/pages/documentation/examples/visualization/linked_views.py +84 -0
- solara/website/pages/documentation/examples/visualization/plotly.py +44 -0
- solara/website/pages/documentation/faq/__init__.py +12 -0
- solara/website/pages/documentation/faq/content/99-faq.md +76 -0
- solara/website/pages/documentation/getting_started/__init__.py +9 -0
- solara/website/pages/documentation/getting_started/content/00-quickstart.md +89 -0
- solara/website/pages/documentation/getting_started/content/01-introduction.md +125 -0
- solara/website/pages/documentation/getting_started/content/02-installing.md +134 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/00-overview.md +14 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/10_data_science.py +13 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/20-web-app.md +89 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/30-ipywidgets.md +124 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/40-streamlit.md +146 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/50-dash.md +144 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/60-jupyter-dashboard-part1.py +64 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/SF_crime_sample.csv.gz +0 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/_data_science.ipynb +445 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/_jupyter_dashboard_1.ipynb +1000 -0
- solara/website/pages/documentation/getting_started/content/05-fundamentals/00-overview.md +11 -0
- solara/website/pages/documentation/getting_started/content/05-fundamentals/10-components.md +223 -0
- solara/website/pages/documentation/getting_started/content/05-fundamentals/50-state-management.md +88 -0
- solara/website/pages/documentation/getting_started/content/07-deploying/00-overview.md +7 -0
- solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +273 -0
- solara/website/pages/documentation/getting_started/content/07-deploying/20-cloud-hosted.md +80 -0
- solara/website/pages/documentation/getting_started/content/80-what-is-lab.md +7 -0
- solara/website/pages/documentation/getting_started/content/90-troubleshoot.md +26 -0
- solara/website/pages/docutils.py +38 -0
- solara/website/pages/showcase/__init__.py +105 -0
- solara/website/pages/showcase/domino_code_assist.py +60 -0
- solara/website/pages/showcase/planeto_tessa.py +19 -0
- solara/website/pages/showcase/solara_dev.py +54 -0
- solara/website/pages/showcase/solarathon_2023_team_2.py +22 -0
- solara/website/pages/showcase/solarathon_2023_team_4.py +22 -0
- solara/website/pages/showcase/solarathon_2023_team_5.py +23 -0
- solara/website/pages/showcase/solarathon_2023_team_6.py +34 -0
- solara/website/pages/showcase/wanderlust.py +27 -0
- solara/website/public/beach.jpeg +0 -0
- solara/website/public/logo.svg +6 -0
- solara/website/public/social/discord.svg +1 -0
- solara/website/public/social/github.svg +1 -0
- solara/website/public/social/twitter.svg +3 -0
- solara/website/public/success.html +25 -0
- solara/website/templates/index.html.j2 +117 -0
- solara/website/utils.py +51 -0
- solara/widgets/__init__.py +1 -0
- solara/widgets/vue/gridlayout.vue +110 -0
- solara/widgets/vue/html.vue +4 -0
- solara/widgets/vue/navigator.vue +104 -0
- solara/widgets/vue/vegalite.vue +115 -0
- solara/widgets/widgets.py +66 -0
- solara_ui-1.31.0.data/data/etc/jupyter/jupyter_notebook_config.d/solara.json +7 -0
- solara_ui-1.31.0.data/data/etc/jupyter/jupyter_server_config.d/solara.json +7 -0
- solara_ui-1.31.0.dist-info/METADATA +158 -0
- solara_ui-1.31.0.dist-info/RECORD +439 -0
- solara_ui-1.31.0.dist-info/WHEEL +5 -0
- solara_ui-1.31.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from os.path import isfile, join
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Callable, Dict, List, Optional, Union, cast
|
|
5
|
+
|
|
6
|
+
import humanize
|
|
7
|
+
import ipyvuetify as vy
|
|
8
|
+
import traitlets
|
|
9
|
+
|
|
10
|
+
import solara
|
|
11
|
+
from solara.components import Div
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def list_dir(path, filter: Callable[[Path], bool] = lambda x: True, directory_first: bool = False) -> List[dict]:
|
|
15
|
+
def mk_item(n):
|
|
16
|
+
full_path = join(path, n)
|
|
17
|
+
is_file = isfile(full_path)
|
|
18
|
+
return {"name": n, "is_file": is_file, "size": humanize.naturalsize(os.stat(full_path).st_size) if is_file else None}
|
|
19
|
+
|
|
20
|
+
files = [mk_item(k) for k in os.listdir(path) if not k.startswith(".") if filter(Path(path) / k)]
|
|
21
|
+
sorted_files = sorted(files, key=lambda item: (item["is_file"] == directory_first, item["name"].lower()))
|
|
22
|
+
|
|
23
|
+
return sorted_files
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FileListWidget(vy.VuetifyTemplate):
|
|
27
|
+
template_file = (__file__, "file_list_widget.vue")
|
|
28
|
+
|
|
29
|
+
files = traitlets.List(cast(List[Dict], [])).tag(sync=True)
|
|
30
|
+
clicked = traitlets.Dict(allow_none=True, default_value=None).tag(sync=True)
|
|
31
|
+
double_clicked = traitlets.Dict(allow_none=True, default_value=None).tag(sync=True)
|
|
32
|
+
scroll_pos = traitlets.Int(allow_none=True).tag(sync=True)
|
|
33
|
+
|
|
34
|
+
def test_click(self, path: Union[Path, str], double_click=False):
|
|
35
|
+
"""Simulate a click or double click at the Python side"""
|
|
36
|
+
matches = [k for k in self.files if k["name"] == str(path)]
|
|
37
|
+
if len(matches) == 0:
|
|
38
|
+
names = [k["name"] for k in self.files]
|
|
39
|
+
raise NameError(f"Could not find {path}, possible filenames: {names}")
|
|
40
|
+
item = matches[0]
|
|
41
|
+
if double_click:
|
|
42
|
+
self.double_clicked = item
|
|
43
|
+
else:
|
|
44
|
+
self.clicked = item
|
|
45
|
+
|
|
46
|
+
def __contains__(self, name):
|
|
47
|
+
"""Test if filename/directory name is in the current directory."""
|
|
48
|
+
return name in [k["name"] for k in self.files]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@solara.component
|
|
52
|
+
def FileBrowser(
|
|
53
|
+
directory: Union[None, str, Path, solara.Reactive[Path]] = None,
|
|
54
|
+
on_directory_change: Callable[[Path], None] = None,
|
|
55
|
+
on_path_select: Callable[[Optional[Path]], None] = None,
|
|
56
|
+
on_file_open: Callable[[Path], None] = None,
|
|
57
|
+
filter: Callable[[Path], bool] = lambda x: True,
|
|
58
|
+
directory_first: bool = False,
|
|
59
|
+
on_file_name: Callable[[str], None] = None,
|
|
60
|
+
start_directory=None,
|
|
61
|
+
can_select=False,
|
|
62
|
+
):
|
|
63
|
+
"""File/directory browser at the server side.
|
|
64
|
+
|
|
65
|
+
There are two modes possible
|
|
66
|
+
|
|
67
|
+
* `can_select=False`
|
|
68
|
+
* `on_file_open`: Triggered when **single** clicking a file or directory.
|
|
69
|
+
* `on_path_select`: Never triggered
|
|
70
|
+
* `on_directory_change`: Triggered when clicking a directory
|
|
71
|
+
* `can_select=True`
|
|
72
|
+
* `on_file_open`: Triggered when **double** clicking a file or directory.
|
|
73
|
+
* `on_path_select`: Triggered when clicking a file or directory
|
|
74
|
+
* `on_directory_change`: Triggered when double clicking a directory
|
|
75
|
+
|
|
76
|
+
## Arguments
|
|
77
|
+
|
|
78
|
+
* `directory`: The directory to start in. If `None` the current working directory is used.
|
|
79
|
+
* `on_directory_change`: Depends on mode, see above.
|
|
80
|
+
* `on_path_select`: Depends on mode, see above.
|
|
81
|
+
* `on_file_open`: Depends on mode, see above.
|
|
82
|
+
* `filter`: A function that takes a `Path` and returns `True` if the file/directory should be shown.
|
|
83
|
+
* `directory_first`: If `True` directories are shown before files. Default: `False`.
|
|
84
|
+
* `on_file_name`: (deprecated) Use on_file_open instead.
|
|
85
|
+
* `start_directory`: (deprecated) Use directory instead.
|
|
86
|
+
"""
|
|
87
|
+
if start_directory is not None:
|
|
88
|
+
directory = start_directory # pragma: no cover
|
|
89
|
+
if directory is None:
|
|
90
|
+
directory = os.getcwd() # pragma: no cover
|
|
91
|
+
if isinstance(directory, str):
|
|
92
|
+
directory = Path(directory)
|
|
93
|
+
current_dir = solara.use_reactive(directory)
|
|
94
|
+
selected, set_selected = solara.use_state(None)
|
|
95
|
+
double_clicked, set_double_clicked = solara.use_state(None)
|
|
96
|
+
warning, set_warning = solara.use_state(cast(Optional[str], None))
|
|
97
|
+
scroll_pos_stack, set_scroll_pos_stack = solara.use_state(cast(List[int], []))
|
|
98
|
+
scroll_pos, set_scroll_pos = solara.use_state(0)
|
|
99
|
+
selected, set_selected = solara.use_state(None)
|
|
100
|
+
|
|
101
|
+
def change_dir(new_dir: str):
|
|
102
|
+
if os.access(new_dir, os.R_OK):
|
|
103
|
+
current_dir.value = Path(new_dir)
|
|
104
|
+
if on_directory_change:
|
|
105
|
+
on_directory_change(Path(new_dir))
|
|
106
|
+
set_warning(None)
|
|
107
|
+
return True
|
|
108
|
+
else:
|
|
109
|
+
set_warning(f"[no read access to {new_dir}]")
|
|
110
|
+
|
|
111
|
+
def on_item(item, double_click):
|
|
112
|
+
if item is None:
|
|
113
|
+
if can_select and on_path_select:
|
|
114
|
+
on_path_select(None)
|
|
115
|
+
return
|
|
116
|
+
if item["name"] == "..":
|
|
117
|
+
current_dir_str = str(current_dir.value)
|
|
118
|
+
new_dir = current_dir_str[: current_dir_str.rfind(os.path.sep)]
|
|
119
|
+
action_change_directory = (can_select and double_click) or (not can_select and not double_click)
|
|
120
|
+
if action_change_directory and change_dir(new_dir):
|
|
121
|
+
if scroll_pos_stack:
|
|
122
|
+
last_pos = scroll_pos_stack[-1]
|
|
123
|
+
set_scroll_pos_stack(scroll_pos_stack[:-1])
|
|
124
|
+
set_scroll_pos(last_pos)
|
|
125
|
+
set_selected(None)
|
|
126
|
+
set_double_clicked(None)
|
|
127
|
+
if on_path_select and can_select:
|
|
128
|
+
on_path_select(None)
|
|
129
|
+
if can_select and not double_click:
|
|
130
|
+
if on_path_select:
|
|
131
|
+
on_path_select(Path(new_dir))
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
path = os.path.join(current_dir.value, item["name"])
|
|
135
|
+
is_file = item["is_file"]
|
|
136
|
+
if (can_select and double_click) or (not can_select and not double_click):
|
|
137
|
+
if is_file:
|
|
138
|
+
if on_file_open:
|
|
139
|
+
on_file_open(Path(path))
|
|
140
|
+
if on_file_name is not None:
|
|
141
|
+
on_file_name(path)
|
|
142
|
+
else:
|
|
143
|
+
if change_dir(path):
|
|
144
|
+
set_scroll_pos_stack(scroll_pos_stack + [scroll_pos])
|
|
145
|
+
set_scroll_pos(0)
|
|
146
|
+
set_selected(None)
|
|
147
|
+
set_double_clicked(None)
|
|
148
|
+
if on_path_select and can_select:
|
|
149
|
+
on_path_select(None)
|
|
150
|
+
elif can_select and not double_click:
|
|
151
|
+
if on_path_select:
|
|
152
|
+
on_path_select(Path(path))
|
|
153
|
+
else: # not can_select and double_click is ignored
|
|
154
|
+
raise RuntimeError("Combination should not happen") # pragma: no cover
|
|
155
|
+
|
|
156
|
+
def on_click(item):
|
|
157
|
+
set_selected(item)
|
|
158
|
+
on_item(item, False)
|
|
159
|
+
|
|
160
|
+
def on_double_click(item):
|
|
161
|
+
set_double_clicked(item)
|
|
162
|
+
if can_select:
|
|
163
|
+
on_item(item, True)
|
|
164
|
+
# otherwise we can ignore it, single click will handle it
|
|
165
|
+
|
|
166
|
+
files = [{"name": "..", "is_file": False}] + list_dir(current_dir.value, filter=filter, directory_first=directory_first)
|
|
167
|
+
with Div(class_="solara-file-browser") as main:
|
|
168
|
+
Div(children=[str(current_dir.value)])
|
|
169
|
+
FileListWidget.element(
|
|
170
|
+
files=files,
|
|
171
|
+
selected=selected,
|
|
172
|
+
clicked=selected,
|
|
173
|
+
on_clicked=on_click,
|
|
174
|
+
double_clicked=double_clicked,
|
|
175
|
+
on_double_clicked=on_double_click,
|
|
176
|
+
scroll_pos=scroll_pos,
|
|
177
|
+
on_scroll_pos=set_scroll_pos,
|
|
178
|
+
).key("FileList")
|
|
179
|
+
if warning:
|
|
180
|
+
Div(style_="font-weight: bold; color: red", children=[warning])
|
|
181
|
+
|
|
182
|
+
return main
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import BinaryIO, Callable, List, Optional, Union, cast
|
|
3
|
+
|
|
4
|
+
import ipyvuetify as vy
|
|
5
|
+
import ipywidgets as widgets
|
|
6
|
+
import traitlets
|
|
7
|
+
|
|
8
|
+
import solara
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FileDownloadWidget(vy.VuetifyTemplate):
|
|
12
|
+
template_file = (__file__, "download.vue")
|
|
13
|
+
children = traitlets.List(cast(List[solara.Element], [])).tag(sync=True, **widgets.widget_serialization)
|
|
14
|
+
filename = traitlets.Unicode().tag(sync=True)
|
|
15
|
+
bytes = traitlets.Bytes(None, allow_none=True).tag(sync=True)
|
|
16
|
+
mime_type = traitlets.Unicode("application/octet-stream").tag(sync=True)
|
|
17
|
+
request_download = traitlets.Bool(False).tag(sync=True)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@solara.component
|
|
21
|
+
def FileDownload(
|
|
22
|
+
data: Union[str, bytes, BinaryIO, Callable[[], Union[str, bytes, BinaryIO]]],
|
|
23
|
+
filename: Optional[str] = None,
|
|
24
|
+
label: Optional[str] = None,
|
|
25
|
+
icon_name: Optional[str] = "mdi-cloud-download-outline",
|
|
26
|
+
close_file: bool = True,
|
|
27
|
+
mime_type: str = "application/octet-stream",
|
|
28
|
+
string_encoding: str = "utf-8",
|
|
29
|
+
children=[],
|
|
30
|
+
):
|
|
31
|
+
"""Download a file or data.
|
|
32
|
+
|
|
33
|
+
## Simple usage
|
|
34
|
+
|
|
35
|
+
By default, if no children are provided, a button is created with the label "Download: {filename}".
|
|
36
|
+
|
|
37
|
+
```solara
|
|
38
|
+
import solara
|
|
39
|
+
|
|
40
|
+
data = "This is the content of the file"
|
|
41
|
+
|
|
42
|
+
@solara.component
|
|
43
|
+
def Page():
|
|
44
|
+
solara.FileDownload(data, filename="solara-download.txt", label="Download file")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Advanced usage
|
|
48
|
+
|
|
49
|
+
If children are provided, they are displayed instead of the button. The children can be any solara component,
|
|
50
|
+
including a button, markdown text, or an image.
|
|
51
|
+
|
|
52
|
+
```solara
|
|
53
|
+
import solara
|
|
54
|
+
|
|
55
|
+
data = "This is the content of the file"
|
|
56
|
+
|
|
57
|
+
@solara.component
|
|
58
|
+
def Page():
|
|
59
|
+
with solara.FileDownload(data, "solara-download-2.txt"):
|
|
60
|
+
solara.Markdown("Any text, or even an image")
|
|
61
|
+
solara.Image("https://solara.dev/static/public/beach.jpeg", width="200px")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Custom button
|
|
65
|
+
|
|
66
|
+
If children are provided, they are displayed instead of the button. The children can be any solara component,
|
|
67
|
+
including a button, markdown text, or an image.
|
|
68
|
+
|
|
69
|
+
```solara
|
|
70
|
+
import solara
|
|
71
|
+
|
|
72
|
+
data = "This is the content of the file"
|
|
73
|
+
|
|
74
|
+
@solara.component
|
|
75
|
+
def Page():
|
|
76
|
+
with solara.FileDownload(data, "solara-download-2.txt"):
|
|
77
|
+
solara.Button("Custom download button", icon_name="mdi-cloud-download-outline", color="primary")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Usage with file
|
|
81
|
+
|
|
82
|
+
A file object can be used as data. The file will be closed after downloading by default.
|
|
83
|
+
|
|
84
|
+
```solara
|
|
85
|
+
import solara
|
|
86
|
+
import pandas as pd
|
|
87
|
+
|
|
88
|
+
df = pd.DataFrame({"id": [1, 2, 3], "name": ["John", "Mary", "Bob"]})
|
|
89
|
+
|
|
90
|
+
@solara.component
|
|
91
|
+
def Page():
|
|
92
|
+
file_object = df.to_csv(index=False)
|
|
93
|
+
solara.FileDownload(file_object, "users.csv", mime_type="application/vnd.ms-excel")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
If a file like object is used, we try to base the filename on the file object.
|
|
97
|
+
```solara
|
|
98
|
+
import solara
|
|
99
|
+
import solara.website.pages
|
|
100
|
+
import os
|
|
101
|
+
|
|
102
|
+
filename = os.path.dirname(solara.website.__file__) + "/public/beach.jpeg"
|
|
103
|
+
|
|
104
|
+
@solara.component
|
|
105
|
+
def Page():
|
|
106
|
+
# only open the file once by using use_memo
|
|
107
|
+
file_object = solara.use_memo(lambda: open(filename, "rb"), [])
|
|
108
|
+
# no filename is provided, but we can extract it from the file object
|
|
109
|
+
solara.FileDownload(file_object, mime_type="image/jpeg", close_file=False)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Lazy reading
|
|
113
|
+
|
|
114
|
+
Not only is the data lazily uploaded to the browser, but also the data is only read when the download is requested.
|
|
115
|
+
This happens for files by default, but can also be used by passing in a callback function.
|
|
116
|
+
|
|
117
|
+
```solara
|
|
118
|
+
import solara
|
|
119
|
+
import time
|
|
120
|
+
|
|
121
|
+
@solara.component
|
|
122
|
+
def Page():
|
|
123
|
+
def get_data():
|
|
124
|
+
# I run in a thread, so I can do some heavy processing
|
|
125
|
+
time.sleep(3)
|
|
126
|
+
# I only get called when the download is requested
|
|
127
|
+
return "This is the content of the file"
|
|
128
|
+
solara.FileDownload(get_data, "solara-lazy-download.txt")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Arguments
|
|
132
|
+
|
|
133
|
+
* `data`: The data to download. Can be a string, bytes, or a file like object, or a function that returns one of these.
|
|
134
|
+
* `filename`: The name of the file the user will see as default when downloading (default name is "solara-download.dat").
|
|
135
|
+
If a file object is provided, the filename will be extracted from the file object if possible.
|
|
136
|
+
* `label`: The label of the button. If not provided, the label will be "Download: {filename}".
|
|
137
|
+
- `icon_name`: The name of the icon to display on the button ([Overview of available icons](https://pictogrammers.github.io/@mdi/font/4.9.95/)).
|
|
138
|
+
* `close_file`: If a file object is provided, close the file after downloading (default True).
|
|
139
|
+
* `mime_type`: The mime type of the file. If not provided, the mime type will be "application/octet-stream",
|
|
140
|
+
For instance setting it to "application/vnd.ms-excel" will allow the user OS to directly open the
|
|
141
|
+
file into Excel.
|
|
142
|
+
* `string_encoding`: The encoding to use when converting a string to bytes (default "utf-8").
|
|
143
|
+
|
|
144
|
+
## Note on file size
|
|
145
|
+
|
|
146
|
+
Note that the data will be kept in memory when downloading.
|
|
147
|
+
If the file is large (>10 MB), and when using [Solara server](/documentation/advanced/understanding/solara-server), we recommend using the
|
|
148
|
+
[static files directory](/documentation/advanced/reference/static-files) instead.
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
request_download, set_request_download = solara.use_state(False)
|
|
152
|
+
|
|
153
|
+
# if the data changes, we 'reset'
|
|
154
|
+
def reset():
|
|
155
|
+
nonlocal request_download
|
|
156
|
+
request_download = False
|
|
157
|
+
set_request_download(False)
|
|
158
|
+
|
|
159
|
+
solara.use_memo(reset, [data])
|
|
160
|
+
|
|
161
|
+
# we only upload to the frontend if clicked
|
|
162
|
+
def get_data() -> Optional[bytes]:
|
|
163
|
+
if request_download:
|
|
164
|
+
if callable(data):
|
|
165
|
+
data_non_lazy = data()
|
|
166
|
+
else:
|
|
167
|
+
data_non_lazy = data
|
|
168
|
+
if hasattr(data_non_lazy, "read"):
|
|
169
|
+
if hasattr(data_non_lazy, "seek"):
|
|
170
|
+
if hasattr(data_non_lazy, "tell") and data_non_lazy.tell() != 0:
|
|
171
|
+
data_non_lazy.seek(0)
|
|
172
|
+
content = data_non_lazy.read() # type: ignore
|
|
173
|
+
if close_file:
|
|
174
|
+
data_non_lazy.close() # type: ignore
|
|
175
|
+
return content
|
|
176
|
+
elif isinstance(data_non_lazy, str):
|
|
177
|
+
return data_non_lazy.encode(string_encoding)
|
|
178
|
+
else:
|
|
179
|
+
return cast(bytes, data_non_lazy)
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
bytes_result: solara.Result[Optional[bytes]] = solara.use_thread(get_data, dependencies=[request_download, data])
|
|
183
|
+
if filename is None and hasattr(data, "name"):
|
|
184
|
+
try:
|
|
185
|
+
filename = Path(data.name).name # type: ignore
|
|
186
|
+
except Exception:
|
|
187
|
+
pass
|
|
188
|
+
filename = filename or "solara-download.dat"
|
|
189
|
+
label = label or ("Download: " + filename)
|
|
190
|
+
FileDownloadWidget.element(
|
|
191
|
+
filename=filename,
|
|
192
|
+
bytes=bytes_result.value if bytes_result.state == solara.ResultState.FINISHED else None,
|
|
193
|
+
request_download=request_download,
|
|
194
|
+
on_request_download=set_request_download,
|
|
195
|
+
children=children or [solara.Button(label, loading=bytes_result.state == solara.ResultState.RUNNING, icon_name=icon_name)],
|
|
196
|
+
mime_type=mime_type,
|
|
197
|
+
)
|
|
198
|
+
if bytes_result.state == solara.ResultState.ERROR and bytes_result.error:
|
|
199
|
+
raise bytes_result.error
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import typing
|
|
3
|
+
from typing import Any, Callable, List, Optional, Union, cast
|
|
4
|
+
|
|
5
|
+
import traitlets
|
|
6
|
+
from ipyvue import Template
|
|
7
|
+
from ipyvuetify.extra import FileInput
|
|
8
|
+
from ipywidgets import widget_serialization
|
|
9
|
+
from typing_extensions import TypedDict
|
|
10
|
+
|
|
11
|
+
import solara
|
|
12
|
+
import solara.hooks as hooks
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FileInfo(TypedDict):
|
|
16
|
+
name: str
|
|
17
|
+
size: int
|
|
18
|
+
file_obj: typing.BinaryIO
|
|
19
|
+
data: Optional[bytes]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FileDropZone(FileInput):
|
|
23
|
+
# override to narrow traitlet of FileInput
|
|
24
|
+
template = traitlets.Instance(Template).tag(sync=True, **widget_serialization)
|
|
25
|
+
template_file = (__file__, "file_drop.vue")
|
|
26
|
+
items = traitlets.List(default_value=cast(List[Any], [])).tag(sync=True)
|
|
27
|
+
label = traitlets.Unicode().tag(sync=True)
|
|
28
|
+
multiple = traitlets.Bool(True).tag(sync=True)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@solara.component
|
|
32
|
+
def _FileDrop(
|
|
33
|
+
label="Drop file(s) here",
|
|
34
|
+
on_total_progress: Optional[Callable[[float], None]] = None,
|
|
35
|
+
on_file: Optional[Callable[[Union[FileInfo, List[FileInfo]]], None]] = None,
|
|
36
|
+
lazy: bool = True,
|
|
37
|
+
multiple: bool = False,
|
|
38
|
+
):
|
|
39
|
+
"""Generic implementation used by FileDrop and FileDropMultiple.
|
|
40
|
+
|
|
41
|
+
If multiple=True, multiple files can be uploaded.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
file_info, set_file_info = solara.use_state(None)
|
|
45
|
+
wired_files, set_wired_files = solara.use_state(cast(Optional[typing.List[FileInfo]], None))
|
|
46
|
+
|
|
47
|
+
file_drop = FileDropZone.element(label=label, on_total_progress=on_total_progress, on_file_info=set_file_info, multiple=multiple) # type: ignore
|
|
48
|
+
|
|
49
|
+
def wire_files():
|
|
50
|
+
if not file_info:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
real = cast(FileDropZone, solara.get_widget(file_drop))
|
|
54
|
+
|
|
55
|
+
# workaround for @observe being cleared
|
|
56
|
+
real.version += 1
|
|
57
|
+
real.reset_stats()
|
|
58
|
+
|
|
59
|
+
set_wired_files(cast(typing.List[FileInfo], real.get_files()))
|
|
60
|
+
|
|
61
|
+
solara.use_side_effect(wire_files, [file_info])
|
|
62
|
+
|
|
63
|
+
def handle_file(cancel: threading.Event):
|
|
64
|
+
if not wired_files:
|
|
65
|
+
return
|
|
66
|
+
if on_file:
|
|
67
|
+
for i in range(len(wired_files)):
|
|
68
|
+
if not lazy:
|
|
69
|
+
wired_files[i]["data"] = wired_files[i]["file_obj"].read()
|
|
70
|
+
else:
|
|
71
|
+
wired_files[i]["data"] = None
|
|
72
|
+
if multiple:
|
|
73
|
+
on_file(wired_files)
|
|
74
|
+
else:
|
|
75
|
+
on_file(wired_files[0])
|
|
76
|
+
|
|
77
|
+
result: solara.Result = hooks.use_thread(handle_file, [wired_files])
|
|
78
|
+
if result.error:
|
|
79
|
+
raise result.error
|
|
80
|
+
|
|
81
|
+
return file_drop
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@solara.component
|
|
85
|
+
def FileDrop(
|
|
86
|
+
label="Drop file here",
|
|
87
|
+
on_total_progress: Optional[Callable[[float], None]] = None,
|
|
88
|
+
on_file: Optional[Callable[[FileInfo], None]] = None,
|
|
89
|
+
lazy: bool = True,
|
|
90
|
+
):
|
|
91
|
+
"""Region a user can drop a file into for file uploading.
|
|
92
|
+
|
|
93
|
+
If lazy=True, no file content will be loaded into memory,
|
|
94
|
+
nor will any data be transferred by default.
|
|
95
|
+
If lazy=False, file content will be loaded into memory and passed to the `on_file` callback via the `FileInfo.data` attribute.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
A file object is of the following argument type:
|
|
99
|
+
```python
|
|
100
|
+
class FileInfo(typing.TypedDict):
|
|
101
|
+
name: str # file name
|
|
102
|
+
size: int # file size in bytes
|
|
103
|
+
file_obj: typing.BinaryIO
|
|
104
|
+
data: Optional[bytes]: bytes # only present if lazy=False
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
## Arguments
|
|
109
|
+
* `on_total_progress`: Will be called with the progress in % of the file upload.
|
|
110
|
+
* `on_file`: Will be called with a `FileInfo` object, which contains the file `.name`, `.length` and a `.file_obj` object.
|
|
111
|
+
* `lazy`: Whether to load the file contents into memory or not. If `False`,
|
|
112
|
+
the file contents will be loaded into memory via the `.data` attribute of file object(s).
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
return _FileDrop(label=label, on_total_progress=on_total_progress, on_file=on_file, lazy=lazy, multiple=False)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@solara.component
|
|
120
|
+
def FileDropMultiple(
|
|
121
|
+
label="Drop files here",
|
|
122
|
+
on_total_progress: Optional[Callable[[float], None]] = None,
|
|
123
|
+
on_file: Optional[Callable[[List[FileInfo]], None]] = None,
|
|
124
|
+
lazy: bool = True,
|
|
125
|
+
):
|
|
126
|
+
"""Region a user can drop multiple files into for file uploading.
|
|
127
|
+
|
|
128
|
+
Almost identical to `FileDrop` except that multiple files can be dropped and `on_file` is called
|
|
129
|
+
with a list of `FileInfo` objects.
|
|
130
|
+
|
|
131
|
+
## Arguments
|
|
132
|
+
* `on_total_progress`: Will be called with the progress in % of the file(s) upload.
|
|
133
|
+
* `on_file`: Will be called with a `List[FileInfo]`.
|
|
134
|
+
Each `FileInfo` contains the file `.name`, `.length`, `.file_obj` object, and `.data` attributes.
|
|
135
|
+
* `lazy`: Whether to load the file contents into memory or not.
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
return _FileDrop(label=label, on_total_progress=on_total_progress, on_file=on_file, lazy=lazy, multiple=True)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="dropzone" class="solara-file-drop" effectAllowed="move">
|
|
3
|
+
<template v-if="file_info && file_info.length > 0">
|
|
4
|
+
<template v-if="multiple">
|
|
5
|
+
<div v-for="file in file_info">
|
|
6
|
+
{{ file.name }}
|
|
7
|
+
</div>
|
|
8
|
+
</template>
|
|
9
|
+
<template v-else>
|
|
10
|
+
{{ file_info[0].name }}
|
|
11
|
+
</template>
|
|
12
|
+
</template>
|
|
13
|
+
<template v-else>
|
|
14
|
+
{{ label }}
|
|
15
|
+
</template>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
module.exports = {
|
|
21
|
+
mounted() {
|
|
22
|
+
this.chunk_size = 2 * 1024 * 1024;
|
|
23
|
+
this.$refs.dropzone.addEventListener('dragover', event => {
|
|
24
|
+
event.preventDefault();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
this.$refs.dropzone.addEventListener('drop', async event => {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
const items = await Promise.all([...event.dataTransfer.items]);
|
|
30
|
+
const files = items.map(i => i.webkitGetAsEntry())
|
|
31
|
+
const fileHolders = files.filter(f => f.isFile)
|
|
32
|
+
const nativeFilesPromises = fileHolders.map(fileHolder => new Promise((rs, rj) => fileHolder.file(rs, rj)))
|
|
33
|
+
const nativeFiles = await Promise.all(nativeFilesPromises)
|
|
34
|
+
|
|
35
|
+
this.native_file_info = nativeFiles
|
|
36
|
+
this.file_info = this.native_file_info.map(
|
|
37
|
+
({name, size}) => ({
|
|
38
|
+
name,
|
|
39
|
+
isFile: true,
|
|
40
|
+
size,
|
|
41
|
+
}));
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
methods: {
|
|
45
|
+
jupyter_clear() {
|
|
46
|
+
this.native_file_info = [];
|
|
47
|
+
this.file_info = [];
|
|
48
|
+
},
|
|
49
|
+
jupyter_read(chunk) {
|
|
50
|
+
const {id, file_index, offset, length} = chunk;
|
|
51
|
+
let to_do = length;
|
|
52
|
+
let sub_offset = offset;
|
|
53
|
+
|
|
54
|
+
(async () => {
|
|
55
|
+
while (to_do > 0) {
|
|
56
|
+
console.log(this.chunk_size, to_do);
|
|
57
|
+
const sub_length = Math.min(to_do, this.chunk_size);
|
|
58
|
+
|
|
59
|
+
const file = this.native_file_info[file_index];
|
|
60
|
+
const blob = file.slice(sub_offset, sub_offset + sub_length);
|
|
61
|
+
const buff = await blob.arrayBuffer();
|
|
62
|
+
|
|
63
|
+
const msg = {id, file_index, offset: sub_offset, length: sub_length}
|
|
64
|
+
this.upload(msg, [buff]);
|
|
65
|
+
|
|
66
|
+
to_do -= sub_length
|
|
67
|
+
sub_offset += sub_length
|
|
68
|
+
}
|
|
69
|
+
})();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<style id="solara-file-drop">
|
|
76
|
+
.solara-file-drop {
|
|
77
|
+
height: 100px;
|
|
78
|
+
border: 1px dashed gray;
|
|
79
|
+
margin: 8px 0;
|
|
80
|
+
padding: 8px;
|
|
81
|
+
overflow: auto;
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-sheet class="solara-file-list" ref="scrollpane"
|
|
3
|
+
@click="clicked = null"
|
|
4
|
+
>
|
|
5
|
+
<v-list
|
|
6
|
+
@click="clicked = null"
|
|
7
|
+
>
|
|
8
|
+
<v-list-item
|
|
9
|
+
v-for="{name, is_file, size} in files"
|
|
10
|
+
:key="name + '|' + is_file"
|
|
11
|
+
@click.stop="clicked = { name, is_file }"
|
|
12
|
+
@dblclick="double_clicked = { name, is_file }"
|
|
13
|
+
:class="(clicked && clicked.name == name) ? 'solara-file-list-selected': ''"
|
|
14
|
+
>
|
|
15
|
+
<v-list-item-icon>
|
|
16
|
+
<v-icon>{{ name === '..' ? 'mdi-keyboard-backspace' : is_file ? 'mdi-file-document' : 'mdi-folder' }}</v-icon>
|
|
17
|
+
</v-list-item-icon>
|
|
18
|
+
|
|
19
|
+
<v-list-item-content>
|
|
20
|
+
<v-list-item-title :class="'solara-file-list-' + (is_file ? 'file' : 'dir')">
|
|
21
|
+
{{ name }}<span v-if="size"> - {{ size }}</span>
|
|
22
|
+
</v-list-item-title>
|
|
23
|
+
</v-list-item-content>
|
|
24
|
+
</v-list-item>
|
|
25
|
+
</v-list>
|
|
26
|
+
</v-sheet>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script>
|
|
30
|
+
module.exports = {
|
|
31
|
+
mounted() {
|
|
32
|
+
const element = this.$refs.scrollpane.$el
|
|
33
|
+
element.scrollTop = this.scroll_pos
|
|
34
|
+
|
|
35
|
+
this._scrollListener = _.debounce((e) => {
|
|
36
|
+
this.scroll_pos = Math.round(element.scrollTop)
|
|
37
|
+
}, 50)
|
|
38
|
+
element.addEventListener('scroll', this._scrollListener)
|
|
39
|
+
},
|
|
40
|
+
watch: {
|
|
41
|
+
scroll_pos(v) {
|
|
42
|
+
this.$nextTick(() => this.$refs.scrollpane.$el.scrollTop = v);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<style id="solara-file-list">
|
|
49
|
+
.solara-file-list {
|
|
50
|
+
height: 400px;
|
|
51
|
+
overflow: auto;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.solara-file-list-dir {
|
|
55
|
+
font-weight: bold;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.solara-file-list-selected {
|
|
59
|
+
background-color: #3333;
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.solara-file-list .v-list-item__icon,
|
|
64
|
+
.solara-file-list .v-list-item__list {
|
|
65
|
+
margin-top: 0;
|
|
66
|
+
margin-bottom: 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.v-application--is-ltr .solara-file-list .v-list-item__icon {
|
|
70
|
+
margin-right: 8px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.solara-file-list .v-list-item {
|
|
74
|
+
height: 28px;
|
|
75
|
+
min-height: 0;
|
|
76
|
+
padding-left: 0;
|
|
77
|
+
}
|
|
78
|
+
</style>
|