├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature-requests.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ └── check-install.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .prettierrc ├── .python-version ├── .sassrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── SECURITY.md ├── assets └── readme_logo.svg ├── changelog.md ├── docs └── cn │ └── README.md ├── extensions ├── oauth_extension.py └── rio.toml ├── frontend ├── code │ ├── animations.ts │ ├── app.ts │ ├── colorConversion.ts │ ├── componentManagement.ts │ ├── components │ │ ├── buttons.ts │ │ ├── calendar.ts │ │ ├── card.ts │ │ ├── checkbox.ts │ │ ├── classContainer.ts │ │ ├── codeBlock.ts │ │ ├── codeExplorer.ts │ │ ├── colorPicker.ts │ │ ├── componentBase.ts │ │ ├── componentPicker.ts │ │ ├── componentTree.ts │ │ ├── customTreeItem.ts │ │ ├── devToolsConnector.ts │ │ ├── dialogContainer.ts │ │ ├── drawer.ts │ │ ├── dropdown.ts │ │ ├── errorPlaceholder.ts │ │ ├── filePickerArea.ts │ │ ├── flowContainer.ts │ │ ├── fundamentalRootComponent.ts │ │ ├── graphEditor │ │ │ ├── cuttingConnectionStrategy.ts │ │ │ ├── draggingConnectionStrategy.ts │ │ │ ├── draggingNodesStrategy.ts │ │ │ ├── draggingSelectionStrategy.ts │ │ │ ├── graphEditor.ts │ │ │ ├── graphStore.ts │ │ │ └── utils.ts │ │ ├── grid.ts │ │ ├── highLevelComponent.ts │ │ ├── icon.ts │ │ ├── image.ts │ │ ├── keyEventListener.ts │ │ ├── keyboardFocusableComponent.ts │ │ ├── layoutDisplay.ts │ │ ├── linearContainers.ts │ │ ├── link.ts │ │ ├── listItems.ts │ │ ├── listView.ts │ │ ├── markdown.ts │ │ ├── mediaPlayer.ts │ │ ├── mouseEventListener.ts │ │ ├── multiLineTextInput.ts │ │ ├── nodeInput.ts │ │ ├── nodeOutput.ts │ │ ├── numberInput.ts │ │ ├── overlay.ts │ │ ├── pdf_viewer.ts │ │ ├── plot.ts │ │ ├── pointerEventListener.ts │ │ ├── popup.ts │ │ ├── progressBar.ts │ │ ├── progressCircle.ts │ │ ├── rectangle.ts │ │ ├── revealer.ts │ │ ├── scrollContainer.ts │ │ ├── scrollTarget.ts │ │ ├── separator.ts │ │ ├── slider.ts │ │ ├── slideshow.ts │ │ ├── stack.ts │ │ ├── switch.ts │ │ ├── switcher.ts │ │ ├── switcherBar.ts │ │ ├── table.ts │ │ ├── text.ts │ │ ├── textInput.ts │ │ ├── themeContextSwitcher.ts │ │ ├── tooltip.ts │ │ └── webview.ts │ ├── cssUtils.ts │ ├── dataModels.ts │ ├── debouncer.ts │ ├── designApplication.ts │ ├── devToolsTreeWalk.ts │ ├── easeFunctions.ts │ ├── elements │ │ └── pressableElement.ts │ ├── eventHandling.ts │ ├── highlighter.ts │ ├── inputBox.ts │ ├── naturalSizeObservers.ts │ ├── popupManager.ts │ ├── popupPositioners.ts │ ├── rippleEffect.ts │ ├── rpc.ts │ ├── rpcFunctions.ts │ ├── tweens │ │ ├── baseTween.ts │ │ ├── kineticTween.ts │ │ └── mappingTweens.ts │ └── utils.ts ├── css │ ├── components │ │ ├── buttons.scss │ │ ├── calendar.scss │ │ ├── card.scss │ │ ├── checkbox.scss │ │ ├── class_container.scss │ │ ├── code_block.scss │ │ ├── code_explorer.scss │ │ ├── color_picker.scss │ │ ├── dialog_container.scss │ │ ├── drawer.scss │ │ ├── dropdown.scss │ │ ├── error_placeholder.scss │ │ ├── file_picker_area.scss │ │ ├── flow_container.scss │ │ ├── fundamental_root_component.scss │ │ ├── graph_editor.scss │ │ ├── grid.scss │ │ ├── icon.scss │ │ ├── image.scss │ │ ├── key_event_listener.scss │ │ ├── layout_display.scss │ │ ├── linear_containers.scss │ │ ├── link.scss │ │ ├── list_view_and_items.scss │ │ ├── markdown.scss │ │ ├── media_player.scss │ │ ├── overlay.scss │ │ ├── pdf_viewer.scss │ │ ├── plot.scss │ │ ├── pointer_event_listener.scss │ │ ├── popup.scss │ │ ├── progress_bar.scss │ │ ├── progress_circle.scss │ │ ├── rectangle.scss │ │ ├── revealer.scss │ │ ├── scroll_container.scss │ │ ├── scroll_target.scss │ │ ├── separator.scss │ │ ├── slider.scss │ │ ├── slideshow.scss │ │ ├── spacer.scss │ │ ├── stack.scss │ │ ├── switch.scss │ │ ├── switcher.scss │ │ ├── switcher_bar.scss │ │ ├── table.scss │ │ ├── text.scss │ │ ├── tooltip.scss │ │ └── tree_view_and_items.scss │ ├── dev_tools.scss │ ├── html.scss │ ├── input_box.scss │ ├── pick_file.scss │ ├── popup_manager.scss │ ├── style.scss │ ├── switcheroos.scss │ ├── syntax_highlighting │ │ ├── highlightjs-default-dark.css │ │ └── highlightjs-default-light.css │ ├── traceback_popup.scss │ └── utils.scss └── index.html ├── package.json ├── pyproject.toml ├── raw_icons ├── README.md ├── brand │ ├── alipay.svg │ ├── amazon.svg │ ├── amd.svg │ ├── android.svg │ ├── apple.svg │ ├── bing.svg │ ├── bluesky.svg │ ├── bluetooth.svg │ ├── chrome.svg │ ├── discord.svg │ ├── dropbox.svg │ ├── edge.svg │ ├── facebook.svg │ ├── firefox.svg │ ├── git.svg │ ├── github.svg │ ├── gitlab.svg │ ├── google.svg │ ├── google_play.svg │ ├── head │ │ └── android.svg │ ├── instagram.svg │ ├── linkedin.svg │ ├── mastodon.svg │ ├── meta.svg │ ├── microsoft.svg │ ├── nvidia.svg │ ├── opencollective.svg │ ├── paypal.svg │ ├── pinterest.svg │ ├── reddit.svg │ ├── safari.svg │ ├── signal.svg │ ├── skype.svg │ ├── slack.svg │ ├── snapchat.svg │ ├── spotify.svg │ ├── stack_overflow.svg │ ├── steam.svg │ ├── stripe.svg │ ├── substack.svg │ ├── telegram.svg │ ├── threads.svg │ ├── tiktok.svg │ ├── trello.svg │ ├── twitch.svg │ ├── twitter.svg │ ├── ubuntu.svg │ ├── vimeo.svg │ ├── wechat.svg │ ├── whatsapp.svg │ ├── wikipedia.svg │ ├── windows.svg │ ├── x.svg │ └── youtube.svg ├── custom-material-icons │ ├── fill │ │ └── twinkle.svg │ └── twinkle.svg ├── rio │ ├── color │ │ ├── logo.svg │ │ ├── logo_and_text_horizontal.svg │ │ └── logo_and_text_vertical.svg │ ├── fill │ │ ├── logo.svg │ │ ├── logo_and_text_horizontal.svg │ │ └── logo_and_text_vertical.svg │ ├── logo.svg │ ├── logo_and_text_horizontal.svg │ └── logo_and_text_vertical.svg └── styling │ ├── corner_round_bottom_left.svg │ ├── corner_round_bottom_right.svg │ ├── corner_round_top_left.svg │ └── corner_round_top_right.svg ├── rio ├── __init__.py ├── __main__.py ├── app.py ├── app_server │ ├── __init__.py │ ├── abstract_app_server.py │ ├── fastapi_server.py │ └── testing_server.py ├── arequests.py ├── assets.py ├── assets │ ├── fonts │ │ ├── Roboto Mono │ │ │ ├── LICENSE.txt │ │ │ ├── RobotoMono-Bold.ttf │ │ │ ├── RobotoMono-BoldItalic.ttf │ │ │ ├── RobotoMono-Italic.ttf │ │ │ └── RobotoMono-Regular.ttf │ │ └── Roboto │ │ │ ├── LICENSE.txt │ │ │ ├── Roboto-Bold.ttf │ │ │ ├── Roboto-BoldItalic.ttf │ │ │ ├── Roboto-Italic.ttf │ │ │ └── Roboto-Regular.ttf │ ├── hosted │ │ ├── README.md │ │ └── rio_logos │ │ │ ├── logo_and_text_horizontal.png │ │ │ └── rio_logo_square.png │ └── icon_sets │ │ ├── brand.tar.xz │ │ ├── material.tar.xz │ │ ├── rio.tar.xz │ │ └── styling.tar.xz ├── byte_serving.py ├── cli │ ├── __init__.py │ ├── cloud_commands.py │ ├── project_setup.py │ └── run_project │ │ ├── __init__.py │ │ ├── app_loading.py │ │ ├── arbiter.py │ │ ├── file_watcher_worker.py │ │ ├── run_models.py │ │ ├── uvicorn_worker.py │ │ └── webview_worker.py ├── color.py ├── component_meta.py ├── components │ ├── __init__.py │ ├── app_root.py │ ├── auto_form.py │ ├── banner.py │ ├── button.py │ ├── calendar.py │ ├── card.py │ ├── checkbox.py │ ├── class_container.py │ ├── code_block.py │ ├── code_explorer.py │ ├── color_picker.py │ ├── component.py │ ├── container.py │ ├── date_input.py │ ├── default_root_component.py │ ├── devel_component.py │ ├── dialog_container.py │ ├── drawer.py │ ├── dropdown.py │ ├── error_placeholder.py │ ├── file_picker_area.py │ ├── flow_container.py │ ├── form_builder.py │ ├── fundamental_component.py │ ├── graph_editor.py │ ├── grid.py │ ├── html.py │ ├── icon.py │ ├── icon_button.py │ ├── image.py │ ├── key_event_listener.py │ ├── keyboard_focusable_components.py │ ├── labeled_column.py │ ├── linear_containers.py │ ├── link.py │ ├── list_items.py │ ├── list_view.py │ ├── markdown.py │ ├── media_player.py │ ├── mouse_event_listener.py │ ├── multi_line_text_input.py │ ├── node_input.py │ ├── node_output.py │ ├── number_input.py │ ├── overlay.py │ ├── page_view.py │ ├── pdf_viewer.py │ ├── plot.py │ ├── pointer_event_listener.py │ ├── popup.py │ ├── progress_bar.py │ ├── progress_circle.py │ ├── rectangle.py │ ├── revealer.py │ ├── root_components.py │ ├── scroll_container.py │ ├── scroll_target.py │ ├── separator.py │ ├── slider.py │ ├── slideshow.py │ ├── spacer.py │ ├── stack.py │ ├── switch.py │ ├── switcher.py │ ├── switcher_bar.py │ ├── table.py │ ├── tabs.py │ ├── text.py │ ├── text_input.py │ ├── theme_context_switcher.py │ ├── tooltip.py │ ├── tree_items.py │ ├── tree_view.py │ ├── website.py │ └── webview.py ├── cursor_style.py ├── data_models.py ├── debug │ ├── __init__.py │ ├── dev_tools │ │ ├── __init__.py │ │ ├── component_attributes.py │ │ ├── component_picker.py │ │ ├── component_tree.py │ │ ├── deploy_page.py │ │ ├── dev_tools_connector.py │ │ ├── dev_tools_sidebar.py │ │ ├── docs_page.py │ │ ├── icons_page.py │ │ ├── layout_display.py │ │ ├── layout_explainer.py │ │ ├── layout_subpage.py │ │ ├── project_page.py │ │ ├── rio_developer_page.py │ │ ├── sample_icons_grid.py │ │ ├── theme_picker_page.py │ │ └── tree_page.py │ ├── layouter.py │ └── monkeypatches.py ├── deprecations.py ├── dialog.py ├── docs.py ├── errors.py ├── event.py ├── extension.py ├── extension_event.py ├── fills.py ├── global_state.py ├── icon_registry.py ├── icons.py ├── inspection.py ├── language_info.py ├── maybes.py ├── nice_traceback.py ├── observables │ ├── __init__.py │ ├── component_property.py │ ├── containers.py │ ├── dataclass.py │ ├── observable_property.py │ ├── session_attachments.py │ └── session_property.py ├── patches_for_3rd_party_stuff │ ├── IocpProactor_accept_locals_accept_coro.py │ ├── ProactorBasePipeTransport_call_connection_lost.py │ ├── _OverlappedFuture_cancel_overlapped.py │ └── __init__.py ├── path_match.py ├── project_config.py ├── py.typed ├── routing.py ├── self_serializing.py ├── serialization.py ├── session.py ├── snippets │ ├── README.md │ ├── __init__.py │ ├── project_template.py │ ├── snippet-files │ │ ├── other-examples │ │ │ └── simple_counter_app.py │ │ ├── project-template-AI Chatbot │ │ │ ├── README.md │ │ │ ├── components │ │ │ │ ├── __init__.py │ │ │ │ ├── chat_message.py │ │ │ │ ├── chat_suggestion_card.py │ │ │ │ ├── empty_chat_placeholder.py │ │ │ │ └── generating_response_placeholder.py │ │ │ ├── conversation.py │ │ │ ├── meta.json │ │ │ ├── pages │ │ │ │ └── chat_page.py │ │ │ ├── root_init.py │ │ │ └── thumbnail.png │ │ ├── project-template-Authentication │ │ │ ├── README.md │ │ │ ├── components │ │ │ │ ├── __init__.py │ │ │ │ ├── navbar.py │ │ │ │ ├── news_article.py │ │ │ │ ├── root_component.py │ │ │ │ ├── testimonial.py │ │ │ │ └── user_sign_up_form.py │ │ │ ├── data_models.py │ │ │ ├── meta.json │ │ │ ├── pages │ │ │ │ ├── app_page.py │ │ │ │ ├── app_page │ │ │ │ │ ├── about_page.py │ │ │ │ │ ├── home_page.py │ │ │ │ │ └── news_page.py │ │ │ │ └── login_page.py │ │ │ ├── persistence.py │ │ │ ├── root_init.py │ │ │ └── thumbnail.png │ │ ├── project-template-Crypto Dashboard │ │ │ ├── README.md │ │ │ ├── assets │ │ │ │ ├── cryptos.csv │ │ │ │ └── testimonial.png │ │ │ ├── components │ │ │ │ ├── __init__.py │ │ │ │ ├── balance_card.py │ │ │ │ ├── colored_rectangle.py │ │ │ │ ├── content_card.py │ │ │ │ ├── contribution_wanted.py │ │ │ │ ├── crypto_card.py │ │ │ │ ├── crypto_chart.py │ │ │ │ ├── hamburger_button.py │ │ │ │ ├── major_section.py │ │ │ │ ├── nav_bar.py │ │ │ │ ├── notification_button.py │ │ │ │ ├── overlay_bar.py │ │ │ │ ├── portfolio_distribution.py │ │ │ │ ├── portfolio_overview.py │ │ │ │ ├── rio_logo.py │ │ │ │ ├── root_component.py │ │ │ │ ├── styled_portfolio.py │ │ │ │ ├── styled_transaction.py │ │ │ │ └── transactions_overview.py │ │ │ ├── constants.py │ │ │ ├── data_models.py │ │ │ ├── meta.json │ │ │ ├── pages │ │ │ │ ├── dashboard_page.py │ │ │ │ ├── statistics_page.py │ │ │ │ └── transaction_page.py │ │ │ ├── root_init.py │ │ │ ├── theme.py │ │ │ └── thumbnail.png │ │ ├── project-template-Dashboard Better Name │ │ │ ├── README.md │ │ │ ├── assets │ │ │ │ ├── user_1.png │ │ │ │ ├── user_10.png │ │ │ │ ├── user_11.png │ │ │ │ ├── user_12.png │ │ │ │ ├── user_13.png │ │ │ │ ├── user_2.png │ │ │ │ ├── user_3.png │ │ │ │ ├── user_4.png │ │ │ │ ├── user_5.png │ │ │ │ ├── user_6.png │ │ │ │ ├── user_7.png │ │ │ │ ├── user_8.png │ │ │ │ └── user_9.png │ │ │ ├── components │ │ │ │ ├── __init__.py │ │ │ │ ├── colored_rectangle.py │ │ │ │ ├── content_container.py │ │ │ │ ├── date_section.py │ │ │ │ ├── date_selector.py │ │ │ │ ├── filter_row.py │ │ │ │ ├── home_section.py │ │ │ │ ├── hover_card.py │ │ │ │ ├── inbox_email.py │ │ │ │ ├── inbox_header.py │ │ │ │ ├── mail_content.py │ │ │ │ ├── message_preview.py │ │ │ │ ├── multi_select_dropdown.py │ │ │ │ ├── multi_select_dropdown_change_event.py │ │ │ │ ├── popup_rectangle.py │ │ │ │ ├── recent_sales.py │ │ │ │ ├── revenue_graph.py │ │ │ │ ├── root_component.py │ │ │ │ ├── section_switcher.py │ │ │ │ ├── selector_button.py │ │ │ │ ├── side_bar.py │ │ │ │ ├── single_select_dropdown.py │ │ │ │ ├── single_select_dropdown_styled_header.py │ │ │ │ ├── stroke_rectangle.py │ │ │ │ ├── styled_rectangle.py │ │ │ │ ├── toggle_group.py │ │ │ │ ├── top_countries_card.py │ │ │ │ └── user_table.py │ │ │ ├── constants.py │ │ │ ├── data_models.py │ │ │ ├── meta.json │ │ │ ├── pages │ │ │ │ ├── dashboard_page.py │ │ │ │ ├── inbox_page.py │ │ │ │ ├── settings_page.py │ │ │ │ ├── settings_page │ │ │ │ │ ├── general_page.py │ │ │ │ │ ├── members_page.py │ │ │ │ │ └── notifications_page.py │ │ │ │ └── users_page.py │ │ │ ├── root_init.py │ │ │ ├── theme.py │ │ │ ├── thumbnail.png │ │ │ └── utils.py │ │ ├── project-template-Empty │ │ │ ├── README.md │ │ │ ├── meta.json │ │ │ ├── pages │ │ │ │ └── sample_page.py │ │ │ ├── root_init.py │ │ │ └── thumbnail.svg │ │ ├── project-template-Multipage Website │ │ │ ├── README.md │ │ │ ├── assets │ │ │ │ ├── basic_patterns │ │ │ │ │ └── background_gray_2_1.svg │ │ │ │ ├── blog_authors │ │ │ │ │ ├── blog_author_1.png │ │ │ │ │ ├── blog_author_2.png │ │ │ │ │ ├── blog_author_3.png │ │ │ │ │ ├── blog_author_4.png │ │ │ │ │ ├── blog_author_5.png │ │ │ │ │ ├── blog_author_6.png │ │ │ │ │ └── blog_author_7.png │ │ │ │ ├── brands │ │ │ │ │ ├── discord_logo.svg │ │ │ │ │ └── github_logo.svg │ │ │ │ └── testimonials │ │ │ │ │ ├── testimonial_1.png │ │ │ │ │ ├── testimonial_2.png │ │ │ │ │ ├── testimonial_3.png │ │ │ │ │ ├── testimonial_4.png │ │ │ │ │ ├── testimonial_5.png │ │ │ │ │ └── testimonial_6.png │ │ │ ├── components │ │ │ │ ├── __init__.py │ │ │ │ ├── author_card.py │ │ │ │ ├── blog_header.py │ │ │ │ ├── blog_major.py │ │ │ │ ├── blog_minor.py │ │ │ │ ├── blog_section.py │ │ │ │ ├── bullet_point.py │ │ │ │ ├── card_section.py │ │ │ │ ├── custom_switcher_bar.py │ │ │ │ ├── fake_link.py │ │ │ │ ├── faq.py │ │ │ │ ├── faq_section.py │ │ │ │ ├── footer.py │ │ │ │ ├── get_started.py │ │ │ │ ├── goody_column.py │ │ │ │ ├── hero_card.py │ │ │ │ ├── hero_section.py │ │ │ │ ├── image_placeholder.py │ │ │ │ ├── major_column.py │ │ │ │ ├── navbar.py │ │ │ │ ├── outlined_colored_button.py │ │ │ │ ├── outlined_neutral_button.py │ │ │ │ ├── pricing_cards.py │ │ │ │ ├── pricing_section.py │ │ │ │ ├── root_component.py │ │ │ │ ├── service_power.py │ │ │ │ ├── service_speed.py │ │ │ │ ├── subscribe_input_button.py │ │ │ │ ├── testimonial.py │ │ │ │ └── testimonials.py │ │ │ ├── data_models.py │ │ │ ├── meta.json │ │ │ ├── pages │ │ │ │ ├── blog_page.py │ │ │ │ ├── blog_page │ │ │ │ │ ├── adventure_in_alps.py │ │ │ │ │ ├── beauty_of_sunsets.py │ │ │ │ │ ├── blog_home_page.py │ │ │ │ │ ├── climate_change_effects.py │ │ │ │ │ ├── most_beautiful_beaches.py │ │ │ │ │ ├── secrets_of_photography.py │ │ │ │ │ ├── water_and_healing_power_of_stones.py │ │ │ │ │ └── work_life_balance.py │ │ │ │ ├── landing_page.py │ │ │ │ └── pricing_page.py │ │ │ ├── root_init.py │ │ │ ├── theme.py │ │ │ ├── thumbnail.png │ │ │ └── utils.py │ │ ├── project-template-Sales Dashboard │ │ │ ├── README.md │ │ │ ├── assets │ │ │ │ ├── sales_data.csv │ │ │ │ ├── top_performer_1.png │ │ │ │ ├── top_performer_2.png │ │ │ │ └── top_performer_3.png │ │ │ ├── components │ │ │ │ ├── __init__.py │ │ │ │ ├── donut_chart_card.py │ │ │ │ ├── line_chart_card.py │ │ │ │ ├── rate_card.py │ │ │ │ └── top_performers.py │ │ │ ├── data_models.py │ │ │ ├── meta.json │ │ │ ├── pages │ │ │ │ └── sales_page.py │ │ │ ├── persistence.py │ │ │ ├── root_init.py │ │ │ └── thumbnail.png │ │ ├── project-template-Simple CRUD │ │ │ ├── README.md │ │ │ ├── data_models.py │ │ │ ├── meta.json │ │ │ ├── pages │ │ │ │ └── crud_page.py │ │ │ ├── persistence.py │ │ │ ├── root_init.py │ │ │ └── thumbnail.png │ │ ├── project-template-Tic-Tac-Toe │ │ │ ├── README.md │ │ │ ├── components │ │ │ │ ├── __init__.py │ │ │ │ └── field.py │ │ │ ├── meta.json │ │ │ ├── pages │ │ │ │ └── tic_tac_toe_page.py │ │ │ ├── root_init.py │ │ │ └── thumbnail.png │ │ ├── project-template-Todo App │ │ │ ├── README.md │ │ │ ├── components │ │ │ │ ├── __init__.py │ │ │ │ ├── new_todo_item_input.py │ │ │ │ └── todo_item_component.py │ │ │ ├── data_models.py │ │ │ ├── meta.json │ │ │ ├── pages │ │ │ │ └── todo_list_page.py │ │ │ ├── root_init.py │ │ │ └── thumbnail.png │ │ ├── tutorial-tic-tac-toe-part-2 │ │ │ ├── components │ │ │ │ └── __init__.py │ │ │ └── pages │ │ │ │ ├── __init__.py │ │ │ │ └── tic_tac_toe_page.py │ │ ├── tutorial-tic-tac-toe-part-3 │ │ │ ├── components │ │ │ │ ├── __init__.py │ │ │ │ └── field.py │ │ │ └── pages │ │ │ │ ├── __init__.py │ │ │ │ └── tic_tac_toe_page.py │ │ ├── tutorial-tic-tac-toe-part-4 │ │ │ ├── components │ │ │ │ ├── __init__.py │ │ │ │ └── field.py │ │ │ └── pages │ │ │ │ ├── __init__.py │ │ │ │ └── tic_tac_toe_page.py │ │ └── tutorial-tic-tac-toe-part-5 │ │ │ ├── components │ │ │ ├── __init__.py │ │ │ └── field.py │ │ │ └── pages │ │ │ ├── __init__.py │ │ │ └── tic_tac_toe_page.py │ └── snippet_manager.py ├── testing │ ├── __init__.py │ ├── base_client.py │ ├── browser_client.py │ └── dummy_client.py ├── text_style.py ├── theme.py ├── tools │ └── create_code_for_icon_set.py ├── transports │ ├── __init__.py │ ├── abstract_transport.py │ ├── fastapi_websocket_transport.py │ ├── message_recorder_transport.py │ └── multi_transport.py ├── url_pattern.py ├── user_settings_module.py ├── utils.py ├── version.py ├── warnings.py └── webview_shim.py ├── scripts ├── benchmark.py ├── build.py ├── build_icon_set.py ├── build_rio_api_client.py ├── cloc.sh ├── code_coverage.py ├── generate_stubs.py └── publish.py ├── tests ├── __init__.py ├── test_app_build.py ├── test_arequests.py ├── test_attribute_bindings.py ├── test_cli │ └── test_app_loading.py ├── test_components.py ├── test_custom_components.py ├── test_documentation.py ├── test_events.py ├── test_extensions.py ├── test_file_info.py ├── test_frontend │ ├── conftest.py │ ├── test_dialogs.py │ ├── test_layouting │ │ ├── __init__.py │ │ ├── test_dev_tools.py │ │ ├── test_flow_container.py │ │ ├── test_linear_containers.py │ │ ├── test_popup.py │ │ ├── test_scroll_container.py │ │ ├── test_stack.py │ │ └── test_text.py │ └── test_pointer_event_listener.py ├── test_observables.py ├── test_page_views.py ├── test_project_templates.py ├── test_reconciliation.py ├── test_refresh.py ├── test_routing.py ├── test_session.py ├── test_table_data_ingestion.py ├── test_table_indexing.py ├── test_testing_tools.py ├── test_url_patterns.py ├── test_user_settings.py ├── test_utils.py ├── test_zzz_guardrails.py └── utils │ ├── __init__.py │ ├── layouting.py │ └── ruff.py ├── tsconfig.json └── vite.config.mjs /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | # - name: Feature Request 4 | # url: https://github.com/rio-labs/rio/discussions/categories/feature-requests 5 | # about: Suggestions for new features or improvements to enhance the functionality and user experience! 6 | - name: Rio Questions and Discussions 7 | url: https://github.com/rio-labs/rio/discussions/categories/q-a 8 | about: Please ask and answer questions on the community forums. 9 | - name: Join the Community Discord 10 | url: https://discord.com/invite/7ejXaPwhyH 11 | about: Come and chat with other community members! 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | ### What does it do? 14 | 15 | Describe the technical changes you did. 16 | 17 | ### Why is it needed? 18 | 19 | Describe the issue you are solving. 20 | 21 | ### How to test it? 22 | 23 | Provide information about the environment and the path to verify the behaviour. 24 | 25 | ### Related issue(s)/PR(s) 26 | 27 | Let us know if this is related to any issue/pull request 28 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/check-install.yml: -------------------------------------------------------------------------------- 1 | name: Check Install 2 | 3 | on: 4 | schedule: 5 | - cron: "0 8,20 * * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | check-install: 10 | name: python 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Install uv 17 | uses: astral-sh/setup-uv@v5 18 | 19 | - name: "Set up Python" 20 | uses: actions/setup-python@v5 21 | with: 22 | python-version: "3.10" 23 | 24 | - name: Install rio-ui 25 | run: | 26 | uv venv 27 | uv run uv pip install rio-ui 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python Cache Files 2 | __pycache__ 3 | *.py[cod] 4 | 5 | # Uv/Hatch 6 | build/ 7 | /dist/ 8 | wheels/ 9 | *.egg-info 10 | /.venv 11 | /uv.lock 12 | 13 | # Code coverage data 14 | /.coverage 15 | /htmlcov 16 | 17 | # Generated files 18 | /rio/frontend files/* 19 | /rio/cli/rio_api_client 20 | 21 | # Pytest 22 | /.pytest_cache 23 | 24 | # Editor Files 25 | /.vscode 26 | 27 | # JavaScript 28 | /node_modules 29 | /package-lock.json 30 | 31 | # Unknown 32 | /.cache 33 | 34 | # Thirdparty files, such as icons 35 | /thirdparty 36 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | rev: v0.9.7 4 | hooks: 5 | - id: ruff 6 | args: [check, --select, F401, --select, I, --fix] 7 | - id: ruff-format 8 | args: [--config, pyproject.toml] 9 | 10 | - repo: https://github.com/pre-commit/mirrors-prettier 11 | rev: v4.0.0-alpha.8 12 | hooks: 13 | - id: prettier 14 | exclude: '\.md$' 15 | args: [--config, .prettierrc] 16 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 4, 4 | "useTabs": false, 5 | "semi": true, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.10 2 | -------------------------------------------------------------------------------- /.sassrc: -------------------------------------------------------------------------------- 1 | { 2 | "includePaths": [ 3 | "node_modules" 4 | ] 5 | } -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | Please report any security vulnerabilities to 4 | -------------------------------------------------------------------------------- /extensions/rio.toml: -------------------------------------------------------------------------------- 1 | [app] 2 | main-module = "oauth_extension" 3 | -------------------------------------------------------------------------------- /frontend/code/components/classContainer.ts: -------------------------------------------------------------------------------- 1 | import { ComponentBase, ComponentState, DeltaState } from "./componentBase"; 2 | import { ComponentId } from "../dataModels"; 3 | import { ComponentStatesUpdateContext } from "../componentManagement"; 4 | 5 | export type ClassContainerState = ComponentState & { 6 | _type_: "ClassContainer-builtin"; 7 | content: ComponentId | null; 8 | classes: string[]; 9 | }; 10 | 11 | export class ClassContainerComponent extends ComponentBase { 12 | createElement(context: ComponentStatesUpdateContext): HTMLElement { 13 | return document.createElement("div"); 14 | } 15 | 16 | updateElement( 17 | deltaState: DeltaState, 18 | context: ComponentStatesUpdateContext 19 | ): void { 20 | super.updateElement(deltaState, context); 21 | 22 | this.replaceOnlyChild(context, deltaState.content); 23 | 24 | if (deltaState.classes !== undefined) { 25 | // Remove all old values 26 | this.element.className = "rio-component rio-class-container"; 27 | 28 | // Add all new values 29 | this.element.classList.add(...deltaState.classes); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/code/components/componentPicker.ts: -------------------------------------------------------------------------------- 1 | import { devToolsConnector } from "../app"; 2 | import { applyIcon } from "../designApplication"; 3 | import { ComponentBase, ComponentState, DeltaState } from "./componentBase"; 4 | 5 | export type ComponentPickerState = ComponentState & { 6 | _type_: "ComponentPicker-builtin"; 7 | }; 8 | 9 | export class ComponentPickerComponent extends ComponentBase { 10 | protected createElement( 11 | context: ComponentStatesUpdateContext 12 | ): HTMLElement { 13 | let element = document.createElement("div"); 14 | element.classList.add("rio-component-picker"); 15 | 16 | applyIcon(element, "material/arrow_selector_tool:fill"); 17 | 18 | element.addEventListener("click", this.pickComponent.bind(this)); 19 | 20 | return element; 21 | } 22 | 23 | async pickComponent() { 24 | this.element.classList.add("active"); 25 | 26 | await devToolsConnector!.pickComponent(); 27 | 28 | this.element.classList.remove("active"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /frontend/code/components/highLevelComponent.ts: -------------------------------------------------------------------------------- 1 | import { ComponentBase, ComponentState, DeltaState } from "./componentBase"; 2 | import { ComponentId } from "../dataModels"; 3 | import { ComponentStatesUpdateContext } from "../componentManagement"; 4 | 5 | export type HighLevelComponentState = ComponentState & { 6 | _type_: "HighLevelComponent-builtin"; 7 | _child_: ComponentId; 8 | }; 9 | 10 | export class HighLevelComponent extends ComponentBase { 11 | createElement(context: ComponentStatesUpdateContext): HTMLElement { 12 | let element = document.createElement("div"); 13 | element.classList.add("rio-high-level-component"); 14 | return element; 15 | } 16 | 17 | updateElement( 18 | deltaState: DeltaState, 19 | context: ComponentStatesUpdateContext 20 | ): void { 21 | super.updateElement(deltaState, context); 22 | 23 | this.replaceOnlyChild(context, deltaState._child_); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /frontend/code/components/stack.ts: -------------------------------------------------------------------------------- 1 | import { ComponentStatesUpdateContext } from "../componentManagement"; 2 | import { ComponentId } from "../dataModels"; 3 | import { ComponentBase, ComponentState, DeltaState } from "./componentBase"; 4 | 5 | export type StackState = ComponentState & { 6 | _type_: "Stack-builtin"; 7 | children: ComponentId[]; 8 | }; 9 | 10 | export class StackComponent extends ComponentBase { 11 | createElement(context: ComponentStatesUpdateContext): HTMLElement { 12 | let element = document.createElement("div"); 13 | element.classList.add("rio-stack"); 14 | return element; 15 | } 16 | 17 | updateElement( 18 | deltaState: DeltaState, 19 | context: ComponentStatesUpdateContext 20 | ): void { 21 | super.updateElement(deltaState, context); 22 | 23 | // For some reason, a CSS `grid` seems to squish children to their minimum size. 24 | // Wrapping each child in a container element fixes this, somehow. 25 | this.replaceChildren(context, deltaState.children, this.element, true); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/code/components/themeContextSwitcher.ts: -------------------------------------------------------------------------------- 1 | import { applySwitcheroo } from "../designApplication"; 2 | import { ColorSet, ComponentId } from "../dataModels"; 3 | import { ComponentBase, ComponentState, DeltaState } from "./componentBase"; 4 | import { ComponentStatesUpdateContext } from "../componentManagement"; 5 | 6 | export type ThemeContextSwitcherState = ComponentState & { 7 | _type_: "ThemeContextSwitcher-builtin"; 8 | content: ComponentId; 9 | color: ColorSet; 10 | }; 11 | 12 | export class ThemeContextSwitcherComponent extends ComponentBase { 13 | createElement(context: ComponentStatesUpdateContext): HTMLElement { 14 | let element = document.createElement("div"); 15 | element.classList.add("rio-single-container"); 16 | return element; 17 | } 18 | 19 | updateElement( 20 | deltaState: DeltaState, 21 | context: ComponentStatesUpdateContext 22 | ): void { 23 | super.updateElement(deltaState, context); 24 | 25 | // Update the child 26 | this.replaceOnlyChild(context, deltaState.content); 27 | 28 | // Colorize 29 | if (deltaState.color !== undefined) { 30 | applySwitcheroo(this.element, deltaState.color); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /frontend/code/tweens/mappingTweens.ts: -------------------------------------------------------------------------------- 1 | import { BaseTween } from "./baseTween"; 2 | 3 | /// A tween whose value is determined by mapping a fraction value to the output 4 | /// range. 5 | export class MappingTween extends BaseTween { 6 | private mapping: (value: number) => number; 7 | private duration: number; 8 | 9 | constructor({ 10 | mapping, 11 | duration, 12 | initialValue = 0, 13 | }: { 14 | mapping: (value: number) => number; 15 | duration: number; 16 | initialValue?: number; 17 | }) { 18 | // Chain to the parent constructor 19 | super({ 20 | initialValue: initialValue, 21 | }); 22 | 23 | // Local configuration 24 | this.mapping = mapping; 25 | this.duration = duration; 26 | } 27 | 28 | override update(): void { 29 | let linearProgress = this.progress; 30 | let mappedProgress = this.mapping(linearProgress); 31 | this._current = 32 | this._start + mappedProgress * (this._end - this._start); 33 | } 34 | 35 | override get progress(): number { 36 | let now = Date.now(); 37 | let timeSinceStart = now / 1000 - this.animationStartedAt; 38 | 39 | let progress = timeSinceStart / this.duration; 40 | return Math.min(progress, 1); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /frontend/css/components/card.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-card { 4 | pointer-events: auto; 5 | 6 | @include single-container(); 7 | 8 | background-color: var(--rio-local-bg); 9 | box-shadow: 0 0 0 var(--rio-global-shadow-color); 10 | 11 | transition: 12 | box-shadow 0.15s ease-out, 13 | background-color 0.1s ease-out; 14 | } 15 | 16 | .rio-card-elevate-on-hover:hover { 17 | box-shadow: 0 0.15rem 0.4rem var(--rio-global-shadow-color); 18 | } 19 | 20 | .rio-card-colorize-on-hover:hover { 21 | background-color: var(--rio-local-bg-active); 22 | } 23 | -------------------------------------------------------------------------------- /frontend/css/components/class_container.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | .rio-class-container { 3 | pointer-events: none; 4 | 5 | @include single-container(); 6 | } 7 | -------------------------------------------------------------------------------- /frontend/css/components/dialog_container.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-dialog-container-content { 4 | pointer-events: none; 5 | 6 | @include single-container(); 7 | } 8 | -------------------------------------------------------------------------------- /frontend/css/components/flow_container.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | .rio-flow-container { 3 | pointer-events: none; 4 | 5 | @include single-container(); 6 | 7 | & > div { 8 | display: flex; 9 | flex-wrap: wrap; 10 | 11 | width: min-content; 12 | min-width: 100%; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/css/components/grid.scss: -------------------------------------------------------------------------------- 1 | .rio-grid { 2 | pointer-events: none; 3 | display: inline-grid; 4 | } 5 | -------------------------------------------------------------------------------- /frontend/css/components/icon.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-icon { 4 | pointer-events: auto; 5 | 6 | svg { 7 | @include kill-size-request-with-absolute(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/css/components/image.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-image { 4 | pointer-events: none; 5 | 6 | @include center-content; // Required for the error icon 7 | 8 | img { 9 | pointer-events: auto; 10 | @include kill-size-request-with-absolute(); 11 | } 12 | 13 | // Error icon 14 | svg { 15 | pointer-events: auto; 16 | max-width: 3rem; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /frontend/css/components/key_event_listener.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-key-event-listener { 4 | // Enable mouse events so that users can click into the KeyEventListener to 5 | // give it keyboard focus 6 | pointer-events: auto; 7 | 8 | @include single-container(); 9 | } 10 | -------------------------------------------------------------------------------- /frontend/css/components/linear_containers.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-linear-container { 4 | pointer-events: none; 5 | 6 | @include single-container(); 7 | 8 | & > * > * { 9 | display: flex; 10 | align-items: stretch; 11 | 12 | // Stretch to fill the parent 13 | min-width: 100%; 14 | min-height: 100%; 15 | } 16 | } 17 | 18 | .rio-column > * > * { 19 | flex-direction: column; 20 | } 21 | 22 | // Container with proportions 23 | .has-proportions > * > * > div.rio-child-wrapper { 24 | // Since we determine the childrens' "natural size" by setting the container 25 | // to `min-content`, setting the `flex-basis` to `min-content` *should* be 26 | // the correct choice. In practice, it doesn't seem to matter though. 27 | flex-basis: min-content; 28 | } 29 | 30 | .rio-row.has-proportions > * { 31 | // Cut off the spacer element 32 | overflow-x: hidden; 33 | width: 100%; 34 | } 35 | 36 | .rio-column.has-proportions > * { 37 | // Cut off the spacer element 38 | overflow-y: hidden; 39 | height: 100%; 40 | } 41 | -------------------------------------------------------------------------------- /frontend/css/components/link.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-link { 4 | pointer-events: auto; 5 | cursor: pointer; 6 | 7 | display: block; 8 | 9 | &.rio-text-link { 10 | color: var(--rio-local-level-2-bg); 11 | 12 | // Apply global text styles 13 | font-size: var(--rio-global-text-font-size); 14 | font-weight: var(--rio-global-text-font-weight); 15 | font-family: var(--rio-global-text-font-family); 16 | 17 | white-space: nowrap; 18 | 19 | display: flex; 20 | align-items: center; 21 | justify-content: center; 22 | } 23 | 24 | &:not(.rio-text-link) { 25 | @include single-container(); 26 | } 27 | } 28 | 29 | .rio-text-link-icon { 30 | // NOT rem! Scale off the current text size. 31 | width: 1em; 32 | height: 1em; 33 | 34 | max-width: 1em; 35 | max-height: 1em; 36 | 37 | margin-right: 0.3em; 38 | } 39 | 40 | .rio-link:not(.rio-text-link) { 41 | // Remove the underline. For some reason it's necessary to set this on the 42 | // link even though our text has its own `text-decoration` setting anyway 43 | text-decoration: none; 44 | } 45 | -------------------------------------------------------------------------------- /frontend/css/components/overlay.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-overlay { 4 | pointer-events: none; 5 | } 6 | 7 | .rio-overlay-content { 8 | pointer-events: none; 9 | 10 | width: 100%; 11 | height: 100%; 12 | 13 | @include single-container(); 14 | 15 | // The rest of the CSS is in fundamental-root-component 16 | } 17 | -------------------------------------------------------------------------------- /frontend/css/components/pdf_viewer.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-pdf-viewer { 4 | pointer-events: auto; 5 | 6 | @include single-container(); 7 | 8 | object { 9 | pointer-events: auto; 10 | 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | } 15 | 16 | // Fallback content 17 | .rio-pdf-viewer-fallback { 18 | display: flex; 19 | flex-direction: column; 20 | 21 | justify-content: center; 22 | align-items: center; 23 | 24 | gap: 0.5rem; 25 | padding: 1rem; 26 | 27 | background-color: var(--rio-local-bg-variant); 28 | border-radius: var(--rio-global-corner-radius-large); 29 | color: var(--rio-local-fg); 30 | 31 | svg { 32 | min-width: 3rem; 33 | } 34 | 35 | div { 36 | text-align: center; 37 | line-height: 1.5; 38 | } 39 | 40 | a { 41 | font-weight: bold; 42 | text-decoration: none; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /frontend/css/components/plot.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-plot { 4 | pointer-events: auto; 5 | 6 | display: inline-block; 7 | 8 | // Force the corner radius to be applied to the plot as well 9 | overflow: hidden; 10 | 11 | @include single-container(); 12 | } 13 | 14 | .rio-plotly-plot { 15 | position: relative; 16 | 17 | & > div { 18 | @include kill-size-request-with-zero-zero(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/css/components/pointer_event_listener.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-pointer-event-listener { 4 | pointer-events: auto; 5 | 6 | @include single-container(); 7 | } 8 | -------------------------------------------------------------------------------- /frontend/css/components/popup.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-popup-anchor { 4 | pointer-events: none; 5 | 6 | @include single-container(); 7 | } 8 | 9 | .rio-popup-content { 10 | pointer-events: auto; 11 | 12 | .rio-popup-scroller { 13 | overflow: auto; 14 | } 15 | 16 | // In "dropdown" mode, only scroll if the content is too big for the screen. 17 | // Otherwise the scroll bar would appear during the open/close animation. 18 | &[data-position="dropdown"] > .rio-popup-scroller { 19 | overflow-y: hidden; 20 | } 21 | &[data-position="dropdown"].rio-dropdown-popup-scroll-y 22 | > .rio-popup-scroller { 23 | overflow-y: scroll; 24 | } 25 | 26 | // In "fullscreen" mode, make sure to stretch the content 27 | &[data-position="fullscreen"] > .rio-popup-scroller { 28 | @include single-container(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /frontend/css/components/progress_bar.scss: -------------------------------------------------------------------------------- 1 | .rio-progress-bar { 2 | pointer-events: auto; 3 | 4 | position: relative; 5 | 6 | overflow: hidden; // For the animation 7 | } 8 | 9 | .rio-progress-bar-track { 10 | // We can't set a min-height on the `rio-progress-bar` because JS will 11 | // overwrite it, so we'll set it here instead 12 | min-height: 0.2rem; 13 | 14 | width: 100%; 15 | height: 100%; 16 | 17 | background: var(--rio-local-text-color); 18 | opacity: 0.3; 19 | } 20 | 21 | .rio-progress-bar-fill { 22 | position: absolute; 23 | 24 | height: 100%; 25 | 26 | background: var(--rio-local-bg); 27 | } 28 | 29 | @keyframes rio-progress-bar-animation-indeterminate { 30 | 0% { 31 | left: -20%; 32 | width: 6%; 33 | } 34 | 35 | 50% { 36 | width: 30%; 37 | } 38 | 39 | 100% { 40 | left: 120%; 41 | width: 6%; 42 | } 43 | } 44 | 45 | .rio-progress-bar-indeterminate .rio-progress-bar-fill { 46 | transform: translateX(-50%); 47 | animation: rio-progress-bar-animation-indeterminate 1.5s ease-in-out 48 | infinite; 49 | } 50 | 51 | .rio-progress-bar:not(.rio-progress-bar-indeterminate) .rio-progress-bar-fill { 52 | left: 0; 53 | width: var(--rio-progress-bar-fraction); 54 | 55 | transition: width 0.3s ease-in-out; 56 | } 57 | -------------------------------------------------------------------------------- /frontend/css/components/revealer.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-revealer { 4 | pointer-events: auto; 5 | 6 | display: flex; 7 | flex-direction: column; 8 | align-items: stretch; 9 | justify-content: stretch; 10 | 11 | border-radius: var(--rio-global-corner-radius-small); 12 | 13 | transition: background-color 0.15s ease-out; 14 | } 15 | 16 | .rio-revealer-header { 17 | cursor: pointer; 18 | 19 | display: flex; 20 | align-items: center; 21 | justify-content: space-between; 22 | 23 | color: var(--rio-local-text-color); 24 | } 25 | 26 | .rio-revealer-label { 27 | flex-grow: 1; 28 | } 29 | 30 | .rio-revealer-arrow { 31 | transform: rotate(90deg); 32 | 33 | transition: transform 0.25s ease-in-out; 34 | } 35 | 36 | .rio-revealer-open > * > .rio-revealer-arrow { 37 | transform: rotate(0deg); 38 | } 39 | 40 | .rio-revealer-content-outer { 41 | flex-grow: 1; 42 | 43 | overflow: hidden; // Required for the open/close animation 44 | } 45 | 46 | .rio-revealer-content-inner { 47 | position: relative; 48 | 49 | @include single-container(); 50 | 51 | opacity: 0; 52 | transform: translateY(-50%); 53 | 54 | transition: 55 | opacity 0.45s ease-in-out, 56 | transform 0.35s ease; 57 | } 58 | 59 | .rio-revealer-open > * > .rio-revealer-content-inner { 60 | opacity: 1; 61 | transform: translateY(0%); 62 | } 63 | -------------------------------------------------------------------------------- /frontend/css/components/scroll_target.scss: -------------------------------------------------------------------------------- 1 | .rio-scroll-target { 2 | pointer-events: auto; 3 | 4 | display: flex; 5 | align-items: stretch; 6 | gap: 0.5rem; 7 | 8 | & > .rio-scroll-target-child-container { 9 | flex-grow: 1; 10 | } 11 | 12 | // Hide the link-copy-button unless the cursor is hovering above 13 | & > .rio-scroll-target-url-copy-button { 14 | visibility: hidden; 15 | } 16 | 17 | &:hover > .rio-scroll-target-url-copy-button { 18 | visibility: visible; 19 | cursor: pointer; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/css/components/separator.scss: -------------------------------------------------------------------------------- 1 | .rio-separator { 2 | pointer-events: none; 3 | 4 | background-color: var(--rio-local-bg); 5 | 6 | // @single-container doesn't work with our ::after element, so we'll inline 7 | // it 8 | display: flex; 9 | align-items: stretch; 10 | } 11 | 12 | .rio-separator::after { 13 | content: ""; 14 | 15 | flex-grow: 1; 16 | 17 | // We can't set the min-size on the separator element itself because JS will 18 | // overwrite it with the value from the backend, so we'll do it here 19 | $min-size: 1px; 20 | min-width: $min-size; 21 | min-height: $min-size; 22 | 23 | // This creates a lighter shade of the text color without being see-through 24 | background-color: var(--separator-color); 25 | opacity: var(--separator-opacity); 26 | } 27 | -------------------------------------------------------------------------------- /frontend/css/components/slideshow.scss: -------------------------------------------------------------------------------- 1 | .rio-slideshow { 2 | pointer-events: auto; 3 | overflow: hidden; 4 | } 5 | 6 | .slideshow-child-container { 7 | position: relative; 8 | display: grid; 9 | width: 100%; 10 | height: 100%; 11 | } 12 | 13 | .slideshow-child-container > div { 14 | grid-column-start: 1; 15 | grid-row-start: 1; 16 | width: 100%; 17 | height: 100%; 18 | } 19 | 20 | .slideshow-child-container > div > * { 21 | grid-column-start: 1; 22 | grid-row-start: 1; 23 | width: 100%; 24 | height: 100%; 25 | } 26 | 27 | .slideshow-progress { 28 | position: absolute; 29 | bottom: 0; 30 | 31 | width: 100%; 32 | height: 0.3rem; 33 | background-color: var(--rio-local-level-2-bg); 34 | } 35 | -------------------------------------------------------------------------------- /frontend/css/components/spacer.scss: -------------------------------------------------------------------------------- 1 | .rio-spacer { 2 | pointer-events: none; 3 | } 4 | -------------------------------------------------------------------------------- /frontend/css/components/stack.scss: -------------------------------------------------------------------------------- 1 | .rio-stack { 2 | pointer-events: none; 3 | display: grid; 4 | } 5 | 6 | .rio-stack > * { 7 | grid-row: 1; 8 | grid-column: 1; 9 | } 10 | -------------------------------------------------------------------------------- /frontend/css/components/switcher.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-switcher { 4 | pointer-events: none; 5 | 6 | // This is not a single-container because it can briefly have two children 7 | // during an animation. It's more like a Stack. 8 | display: grid; 9 | 10 | overflow: hidden; 11 | } 12 | 13 | .rio-switcher > * { 14 | grid-row: 1; 15 | grid-column: 1; 16 | 17 | @include single-container(); 18 | 19 | opacity: 0; 20 | transition: opacity var(--rio-switcher-transition-time) ease-in-out; 21 | } 22 | 23 | .rio-switcher > *.rio-switcher-active-child { 24 | opacity: 1; 25 | } 26 | 27 | .rio-switcher.resizing > *:not(.rio-switcher-resizer) { 28 | // During the resize animation, make the children absolute so they can't 29 | // influence the switcher's size 30 | position: absolute; 31 | width: 100%; 32 | height: 100%; 33 | } 34 | 35 | .rio-switcher-resizer { 36 | transition: 37 | min-width var(--rio-switcher-transition-time) ease-in-out, 38 | min-height var(--rio-switcher-transition-time) ease-in-out; 39 | } 40 | -------------------------------------------------------------------------------- /frontend/css/components/table.scss: -------------------------------------------------------------------------------- 1 | // Note: For some reason, cells with multi-line text don't respect 2 | // `justify-content`. `text-align` similarly is not respected by everyone. So we 3 | // have to use both. 4 | 5 | .rio-table { 6 | pointer-events: auto; 7 | 8 | display: grid; 9 | 10 | border-radius: var(--rio-global-corner-radius-medium); 11 | 12 | // Headers 13 | & > .rio-table-header { 14 | position: relative; 15 | 16 | font-weight: bold; 17 | justify-content: center; 18 | text-align: center; 19 | 20 | opacity: 1 !important; 21 | 22 | background-color: var(--rio-local-bg-variant); 23 | } 24 | 25 | // Row Number 26 | & > .rio-table-row-number { 27 | justify-content: end; 28 | text-align: end; 29 | } 30 | 31 | // Regular cell 32 | & > .rio-table-cell { 33 | justify-content: end; 34 | text-align: end; 35 | background-color: var(--rio-table-background-color); 36 | } 37 | 38 | // Non-header cells 39 | & > .rio-table-row-number, 40 | & > .rio-table-cell { 41 | border-bottom: 1px solid var(--rio-local-bg-variant); 42 | } 43 | 44 | // All cells 45 | & > div { 46 | display: flex; 47 | align-items: center; 48 | 49 | padding: 0.5rem; 50 | transition: background-color 0.1s ease-out; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /frontend/css/components/text.scss: -------------------------------------------------------------------------------- 1 | .rio-text { 2 | pointer-events: auto; 3 | 4 | display: flex; 5 | align-items: center; 6 | 7 | color: var(--rio-local-text-color); 8 | } 9 | 10 | .rio-text > * { 11 | flex-grow: 1; 12 | 13 | // Remove default styling of headings 14 | margin: 0; 15 | padding: 0; 16 | } 17 | -------------------------------------------------------------------------------- /frontend/css/components/tooltip.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-tooltip { 4 | pointer-events: auto; 5 | 6 | @include single-container(); 7 | } 8 | 9 | .rio-tooltip-popup { 10 | pointer-events: none; 11 | 12 | width: min(max-content, 100vw); 13 | padding: 0.5rem; 14 | border-radius: var(--rio-global-corner-radius-small); 15 | 16 | background: var(--rio-global-hud-bg); 17 | box-shadow: 0 0.1rem 0.2rem var(--rio-global-shadow-color); 18 | } 19 | 20 | .rio-tooltip-popup * { 21 | pointer-events: none !important; 22 | } 23 | -------------------------------------------------------------------------------- /frontend/css/components/tree_view_and_items.scss: -------------------------------------------------------------------------------- 1 | @import "../utils"; 2 | 3 | .rio-custom-tree-item { 4 | pointer-events: auto; 5 | 6 | padding: 0.2rem; 7 | 8 | display: flex; 9 | flex-direction: column; 10 | } 11 | 12 | .rio-tree-header-row { 13 | display: flex; 14 | gap: 0.5rem; 15 | align-items: center; 16 | } 17 | 18 | .rio-custom-tree-item.rio-selectable-item > .rio-tree-header-row { 19 | cursor: pointer; 20 | } 21 | .rio-custom-tree-item.rio-selectable-item > .rio-tree-header-row:hover { 22 | background: var(--rio-local-bg-active); 23 | } 24 | .rio-custom-tree-item.rio-selectable-item.selected > .rio-tree-header-row { 25 | background-color: var(--rio-global-secondary-bg); 26 | } 27 | .rio-custom-tree-item.rio-selectable-item.selected 28 | > .rio-tree-header-row:hover { 29 | background: var(--rio-global-secondary-bg-active); 30 | } 31 | 32 | .rio-tree-expand-button-container { 33 | @include single-container(); 34 | 35 | // Assign a minimum size so that the width hopefully stays consistent even 36 | // if the user uses weird custom arrow elements 37 | min-width: 1rem; 38 | 39 | &[role="button"]:not([aria-disabled="true"]):hover { 40 | background-color: rgba(255, 255, 255, 0.15); 41 | border-radius: $infinite-corner-radius; 42 | } 43 | } 44 | 45 | .rio-tree-content-container { 46 | transition: background-color 0.1s ease-out; 47 | 48 | &:hover { 49 | background: var(--hover-color); 50 | } 51 | } 52 | 53 | .rio-tree-children-container { 54 | margin-left: 1.5rem; 55 | } 56 | -------------------------------------------------------------------------------- /frontend/css/syntax_highlighting/highlightjs-default-dark.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Dark style from softwaremaniacs.org (c) Ivan Sagalaev 4 | 5 | */ 6 | 7 | .hljs { 8 | color: #ddd; 9 | background: #303030; 10 | } 11 | 12 | .hljs-keyword, 13 | .hljs-selector-tag, 14 | .hljs-literal, 15 | .hljs-section, 16 | .hljs-link { 17 | color: white; 18 | } 19 | 20 | .hljs-subst { 21 | /* default */ 22 | } 23 | 24 | .hljs-string, 25 | .hljs-title, 26 | .hljs-name, 27 | .hljs-type, 28 | .hljs-attribute, 29 | .hljs-symbol, 30 | .hljs-bullet, 31 | .hljs-built_in, 32 | .hljs-addition, 33 | .hljs-variable, 34 | .hljs-template-tag, 35 | .hljs-template-variable { 36 | color: #d88; 37 | } 38 | 39 | .hljs-comment, 40 | .hljs-quote, 41 | .hljs-deletion, 42 | .hljs-meta { 43 | color: #979797; 44 | } 45 | 46 | .hljs-keyword, 47 | .hljs-selector-tag, 48 | .hljs-literal, 49 | .hljs-title, 50 | .hljs-section, 51 | .hljs-doctag, 52 | .hljs-type, 53 | .hljs-name, 54 | .hljs-strong { 55 | font-weight: bold; 56 | } 57 | 58 | .hljs-emphasis { 59 | font-style: italic; 60 | } 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "browserslist": "> 0.5%, last 2 versions, not dead", 4 | "dependencies": { 5 | "ally.js": "^1.4.1", 6 | "highlight.js": "^11.9.0", 7 | "math-expression-evaluator": "^2.0.6", 8 | "micromark": "^4.0.2" 9 | }, 10 | "devDependencies": { 11 | "sass": "~1.76.0", 12 | "typescript": "^5.8.3", 13 | "vite": "^5.4.14", 14 | "vite-plugin-compression2": "^1.1.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /raw_icons/README.md: -------------------------------------------------------------------------------- 1 | # Rio Icons 2 | 3 | This directory contains icons that ship with Rio. These come from various 4 | sources, and this file serves to give credits and track licenses for each of 5 | them. 6 | 7 | ## brand 8 | 9 | These originate from [Bootstrap Icons](https://icons.getbootstrap.com/). They 10 | include a good selection, and given their widespread use it's safe to say these 11 | companies don't mind their logos being used in this context. 12 | 13 | ## custom-material-icons 14 | 15 | This are additional icons following the style of Material Icons. They were 16 | created by Rio developers and as such are thus available under the same license 17 | as the rest of the project. 18 | 19 | ## file 20 | 21 | The icons are created by the Rio project, and are available under the same 22 | license as the rest of the project. **BUT** some of the icons contain logos 23 | owned by other companies. 24 | 25 | ## rio 26 | 27 | These contain Rio-specific logos and icons. They are created by the Rio project 28 | and are available under the same license as the rest of the project. 29 | 30 | ## styling 31 | 32 | Simple helper icons that can be used to style components. They are created by 33 | the Rio project and are available under the same license as the rest of the 34 | project. 35 | -------------------------------------------------------------------------------- /raw_icons/brand/alipay.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /raw_icons/brand/amazon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /raw_icons/brand/amd.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/android.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/apple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /raw_icons/brand/bing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /raw_icons/brand/bluesky.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /raw_icons/brand/bluetooth.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/chrome.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/discord.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/dropbox.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/edge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /raw_icons/brand/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/firefox.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/git.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/gitlab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/google.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/google_play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/head/android.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/instagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/linkedin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/mastodon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/meta.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/microsoft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/nvidia.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/opencollective.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /raw_icons/brand/paypal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/pinterest.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/reddit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /raw_icons/brand/signal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/skype.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/slack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/spotify.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/stack_overflow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /raw_icons/brand/steam.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /raw_icons/brand/stripe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/substack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/telegram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/threads.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/tiktok.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/trello.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/twitch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /raw_icons/brand/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/ubuntu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/vimeo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/wechat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /raw_icons/brand/whatsapp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/wikipedia.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/windows.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/x.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/brand/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /raw_icons/custom-material-icons/fill/twinkle.svg: -------------------------------------------------------------------------------- 1 | 6 | 8 | -------------------------------------------------------------------------------- /raw_icons/custom-material-icons/twinkle.svg: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /raw_icons/rio/fill/logo.svg: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /rio/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.11.2rc5" 2 | 3 | 4 | import logging 5 | 6 | _logger = logging.getLogger(__name__) 7 | 8 | # Re-export dataclass stuff for easy use. 9 | from dataclasses import KW_ONLY as KW_ONLY 10 | from dataclasses import field as field 11 | 12 | # URLs are used as an important datatype within rio. Re-export them for easy 13 | # use. 14 | from yarl import URL as URL 15 | 16 | from . import event as event 17 | from . import extension_event as extension_event 18 | from . import icons as icons # For backwards compat. Delete eventually 19 | from . import patches_for_3rd_party_stuff 20 | from .app import * 21 | from .color import * 22 | from .components import * 23 | from .cursor_style import * 24 | from .dialog import * 25 | from .errors import * 26 | from .extension import * 27 | from .extension_event import ( 28 | ExtensionAppCloseEvent as ExtensionAppCloseEvent, 29 | ) 30 | from .extension_event import ( 31 | ExtensionAppStartEvent as ExtensionAppStartEvent, 32 | ) 33 | from .extension_event import ( 34 | ExtensionAsFastapiEvent as ExtensionAsFastapiEvent, 35 | ) 36 | from .extension_event import ( 37 | ExtensionPageChangeEvent as ExtensionPageChangeEvent, 38 | ) 39 | from .extension_event import ( 40 | ExtensionSessionCloseEvent as ExtensionSessionCloseEvent, 41 | ) 42 | from .extension_event import ( 43 | ExtensionSessionStartEvent as ExtensionSessionStartEvent, 44 | ) 45 | from .fills import * 46 | from .observables import * 47 | from .routing import * 48 | from .session import * 49 | from .text_style import * 50 | from .theme import * 51 | from .user_settings_module import * 52 | from .utils import * 53 | 54 | del patches_for_3rd_party_stuff 55 | -------------------------------------------------------------------------------- /rio/__main__.py: -------------------------------------------------------------------------------- 1 | import rio.cli 2 | 3 | 4 | def main(): 5 | rio.cli.app.run() 6 | 7 | 8 | if __name__ == "__main__": 9 | main() 10 | -------------------------------------------------------------------------------- /rio/app_server/__init__.py: -------------------------------------------------------------------------------- 1 | from .abstract_app_server import * 2 | from .fastapi_server import * 3 | from .testing_server import * 4 | -------------------------------------------------------------------------------- /rio/assets/fonts/Roboto Mono/RobotoMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/assets/fonts/Roboto Mono/RobotoMono-Bold.ttf -------------------------------------------------------------------------------- /rio/assets/fonts/Roboto Mono/RobotoMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/assets/fonts/Roboto Mono/RobotoMono-BoldItalic.ttf -------------------------------------------------------------------------------- /rio/assets/fonts/Roboto Mono/RobotoMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/assets/fonts/Roboto Mono/RobotoMono-Italic.ttf -------------------------------------------------------------------------------- /rio/assets/fonts/Roboto Mono/RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/assets/fonts/Roboto Mono/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /rio/assets/fonts/Roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/assets/fonts/Roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /rio/assets/fonts/Roboto/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/assets/fonts/Roboto/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /rio/assets/fonts/Roboto/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/assets/fonts/Roboto/Roboto-Italic.ttf -------------------------------------------------------------------------------- /rio/assets/fonts/Roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/assets/fonts/Roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /rio/assets/hosted/README.md: -------------------------------------------------------------------------------- 1 | # Hosted Assets 2 | 3 | **Any files in this directory are publicly hosted, WITHOUT ACCESS CONTROL.** 4 | 5 | Use this to host assets such as JavaScript dependencies, fonts, etc. Just be 6 | careful what you place here. 7 | 8 | Yes, this file is also hosted. 9 | -------------------------------------------------------------------------------- /rio/assets/hosted/rio_logos/logo_and_text_horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/assets/hosted/rio_logos/logo_and_text_horizontal.png -------------------------------------------------------------------------------- /rio/assets/hosted/rio_logos/rio_logo_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/assets/hosted/rio_logos/rio_logo_square.png -------------------------------------------------------------------------------- /rio/assets/icon_sets/brand.tar.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/assets/icon_sets/brand.tar.xz -------------------------------------------------------------------------------- /rio/assets/icon_sets/material.tar.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/assets/icon_sets/material.tar.xz -------------------------------------------------------------------------------- /rio/assets/icon_sets/rio.tar.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/assets/icon_sets/rio.tar.xz -------------------------------------------------------------------------------- /rio/assets/icon_sets/styling.tar.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/assets/icon_sets/styling.tar.xz -------------------------------------------------------------------------------- /rio/cli/run_project/__init__.py: -------------------------------------------------------------------------------- 1 | from .arbiter import Arbiter as Arbiter 2 | -------------------------------------------------------------------------------- /rio/cli/run_project/file_watcher_worker.py: -------------------------------------------------------------------------------- 1 | import time 2 | import typing as t 3 | from pathlib import Path 4 | 5 | import watchfiles 6 | 7 | from ... import project_config 8 | from . import run_models 9 | 10 | 11 | class FileWatcherWorker: 12 | def __init__( 13 | self, 14 | *, 15 | push_event: t.Callable[[run_models.Event], None], 16 | proj: project_config.RioProjectConfig, 17 | ) -> None: 18 | self.push_event = push_event 19 | self.proj = proj 20 | 21 | async def run(self) -> None: 22 | """ 23 | Watch the project directory for changes and report them as events. 24 | """ 25 | # Watch the project directory 26 | async for changes in watchfiles.awatch(self.proj.project_directory): 27 | timestamp = time.monotonic_ns() 28 | 29 | for change, path in changes: 30 | path = Path(path) 31 | 32 | # Not all files trigger a reload 33 | if not self.proj.file_is_path_of_project(path): 34 | continue 35 | 36 | # Report the change 37 | self.push_event(run_models.FileChanged(timestamp, path)) 38 | -------------------------------------------------------------------------------- /rio/cli/run_project/run_models.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | from pathlib import Path 3 | 4 | 5 | class Event: 6 | pass 7 | 8 | 9 | @dataclasses.dataclass 10 | class FileChanged(Event): 11 | """ 12 | A file in the project has changed, necessitating a reload. 13 | """ 14 | 15 | timestamp_nanoseconds: ( 16 | int # Monotonic timestamp of the change, in nanoseconds 17 | ) 18 | path_to_file: Path 19 | 20 | 21 | @dataclasses.dataclass 22 | class StopRequested(Event): 23 | """ 24 | Request a shutdown of the running project. 25 | """ 26 | 27 | pass 28 | -------------------------------------------------------------------------------- /rio/components/class_container.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing as t 4 | 5 | import rio 6 | 7 | from .fundamental_component import FundamentalComponent 8 | 9 | __all__ = [ 10 | "ClassContainer", 11 | ] 12 | 13 | 14 | class ClassContainer(FundamentalComponent): 15 | """ 16 | A Component which holds a single child. 17 | 18 | Component which holds a single child, and applies a list of CSS classes to 19 | it. This is enough to implement several components, preventing the need to 20 | create a whole bunch of almost identical JavaScript components. 21 | 22 | This component is only intended for internal use and is not part of the 23 | public API. 24 | 25 | 26 | ## Attributes 27 | 28 | `content`: The child component to apply the classes to. 29 | 30 | `classes`: The list of classes to apply to the child component. 31 | 32 | 33 | ## Metadata 34 | 35 | `public`: False 36 | """ 37 | 38 | content: rio.Component | None 39 | classes: t.Sequence[str] 40 | 41 | def _get_debug_details_(self) -> dict[str, t.Any]: 42 | result = super()._get_debug_details_() 43 | result.pop("classes") 44 | return result 45 | 46 | 47 | ClassContainer._unique_id_ = "ClassContainer-builtin" 48 | -------------------------------------------------------------------------------- /rio/components/code_explorer.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing as t 4 | 5 | from uniserde import JsonDoc 6 | 7 | import rio 8 | 9 | from .fundamental_component import FundamentalComponent 10 | 11 | __all__ = [ 12 | "CodeExplorer", 13 | ] 14 | 15 | 16 | class CodeExplorer(FundamentalComponent): 17 | """ 18 | ## Metadata 19 | 20 | `public`: False 21 | """ 22 | 23 | source_code: str 24 | build_result: rio.Component 25 | line_indices_to_component_keys: t.Sequence[str | int | None] 26 | 27 | style: t.Literal["horizontal", "vertical"] = "horizontal" 28 | 29 | def _custom_serialize_(self) -> JsonDoc: 30 | return { 31 | "line_indices_to_component_keys": self.line_indices_to_component_keys, # type: ignore 32 | } 33 | 34 | 35 | CodeExplorer._unique_id_ = "CodeExplorer-builtin" 36 | -------------------------------------------------------------------------------- /rio/components/error_placeholder.py: -------------------------------------------------------------------------------- 1 | from uniserde import JsonDoc 2 | 3 | from .fundamental_component import FundamentalComponent 4 | 5 | __all__ = ["ErrorPlaceholder"] 6 | 7 | 8 | class ErrorPlaceholder(FundamentalComponent): 9 | """ 10 | Used as a placeholder in case the real component isn't available for 11 | whatever reason. For example: 12 | 13 | - When a `build` function throws an error 14 | - When a page can't be imported 15 | 16 | 17 | ## Metadata 18 | 19 | `public`: False 20 | """ 21 | 22 | error_summary: str 23 | error_details: str 24 | 25 | # Debug details can contain sensitive information. Sending these to the 26 | # client is fine during development, but mustn't happen in production. 27 | def _custom_serialize_(self) -> JsonDoc: 28 | if self.session._app_server.debug_mode: 29 | return { 30 | "_rio_internal_": True, 31 | } 32 | else: 33 | return { 34 | "error_summary": "This component has crashed", 35 | "error_details": "", 36 | "_rio_internal_": True, 37 | } 38 | 39 | 40 | ErrorPlaceholder._unique_id_ = "ErrorPlaceholder-builtin" 41 | -------------------------------------------------------------------------------- /rio/components/html.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | import typing as t 3 | 4 | from ..deprecations import deprecated 5 | from .component import Component 6 | from .webview import Webview 7 | 8 | __all__ = ["Html"] 9 | 10 | 11 | @t.final 12 | @deprecated(since="0.10.9", replacement=Webview) 13 | class Html(Component): 14 | """ 15 | Displays raw HTML. 16 | 17 | The `Html` component allows you to embed arbitrary HTML in your app. It 18 | takes HTML code as input and renders it. If the HTML code starts with 19 | "Hello World 40 |

Welcome to rio!

41 | ''') 42 | """ 43 | 44 | html: str 45 | _: dataclasses.KW_ONLY 46 | enable_pointer_events: bool = True 47 | 48 | def build(self): 49 | return Webview( 50 | self.html, 51 | enable_pointer_events=self.enable_pointer_events, 52 | ) 53 | -------------------------------------------------------------------------------- /rio/components/keyboard_focusable_components.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import dataclasses 3 | 4 | from .component import Component 5 | from .fundamental_component import FundamentalComponent 6 | 7 | __all__ = [ 8 | "KeyboardFocusableComponent", 9 | "KeyboardFocusableFundamentalComponent", 10 | ] 11 | 12 | 13 | class KeyboardFocusableComponent(Component, abc.ABC): 14 | """ 15 | ## Attributes 16 | 17 | `auto_focus`: Whether this component should receive the keyboard focus when 18 | it is created. 19 | 20 | ## Metadata 21 | 22 | `public`: False 23 | """ 24 | 25 | _: dataclasses.KW_ONLY 26 | auto_focus: bool = False 27 | 28 | @abc.abstractmethod 29 | async def grab_keyboard_focus(self) -> None: 30 | """ 31 | ## Metadata 32 | 33 | `public`: False 34 | """ 35 | raise NotImplementedError 36 | 37 | 38 | class KeyboardFocusableFundamentalComponent( 39 | KeyboardFocusableComponent, FundamentalComponent 40 | ): 41 | async def grab_keyboard_focus(self) -> None: 42 | """ 43 | ## Metadata 44 | 45 | `public`: False 46 | """ 47 | await self.session._remote_set_keyboard_focus(self._id_) 48 | -------------------------------------------------------------------------------- /rio/components/separator.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import dataclasses 4 | import typing as t 5 | 6 | import rio 7 | 8 | from .fundamental_component import FundamentalComponent 9 | 10 | __all__ = [ 11 | "Separator", 12 | ] 13 | 14 | 15 | @t.final 16 | class Separator(FundamentalComponent): 17 | """ 18 | A line to separate content. 19 | 20 | `Separator` creates a horizontal or vertical line that can be used to 21 | separate content. You can optionally assign a color. 22 | 23 | It is generally preferable to separate components using whitespace over 24 | explicit separators. Whitespace tends to look cleaner, while separators can 25 | add clutter. Separators can still absolutely make sense though, so here they 26 | are! 27 | 28 | 29 | ## Attributes 30 | 31 | `color`: The color of the `Separator`. If `None`, the color will be 32 | determined by the theme. 33 | 34 | 35 | ## Examples 36 | 37 | This example will display a two text components separated by a line: 38 | 39 | ```python 40 | rio.Row( 41 | rio.Text("First"), 42 | rio.Separator(), 43 | rio.Text("Second"), 44 | spacing=0.5, 45 | ) 46 | ``` 47 | """ 48 | 49 | _: dataclasses.KW_ONLY 50 | color: rio.Color | None = None 51 | 52 | 53 | Separator._unique_id_ = "Separator-builtin" 54 | -------------------------------------------------------------------------------- /rio/components/website.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | from ..deprecations import deprecated 4 | from ..utils import URL 5 | from .component import Component 6 | from .webview import Webview 7 | 8 | __all__ = [ 9 | "Website", 10 | ] 11 | 12 | 13 | @t.final 14 | @deprecated(since="0.10.9", replacement=Webview) 15 | class Website(Component): 16 | """ 17 | Displays a website. 18 | 19 | `Website` takes a URL as input and displays that website in your app. Since 20 | the website component cannot know how large its content is going to be, 21 | you'll probably want to assign a width and height to it. 22 | 23 | 24 | ## Attributes 25 | `url`: The URL of the website you want to display. 26 | 27 | 28 | ## Examples 29 | 30 | Here's a simple example that will display an example website: 31 | 32 | ```python 33 | rio.Website( 34 | url=rio.URL("https://www.example.com"), 35 | ) 36 | ``` 37 | """ 38 | 39 | url: URL 40 | 41 | def build(self): 42 | return Webview(self.url) 43 | -------------------------------------------------------------------------------- /rio/cursor_style.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | from . import deprecations 4 | 5 | __all__ = ["CursorStyle"] 6 | 7 | 8 | @deprecations.deprecated( 9 | since="0.11.3", 10 | description="`CursorStyle` is deprecated in favor of string literals.", 11 | ) 12 | class CursorStyle(enum.Enum): 13 | """ 14 | Enumeration of all available cursor styles. Use these to indicate which 15 | kinds of action a user can perform while hovering over a component. 16 | 17 | ## Example 18 | 19 | ```python 20 | rio.Rectangle( 21 | fill=rio.Color.WHITE, 22 | cursor=rio.CursorStyle.POINTER, 23 | ) 24 | ``` 25 | """ 26 | 27 | DEFAULT = enum.auto() 28 | NONE = enum.auto() 29 | HELP = enum.auto() 30 | POINTER = enum.auto() 31 | LOADING = enum.auto() # "wait" in CSS 32 | BACKGROUND_LOADING = enum.auto() # "progress" in CSS 33 | CROSSHAIR = enum.auto() 34 | TEXT = enum.auto() 35 | MOVE = enum.auto() 36 | NOT_ALLOWED = enum.auto() 37 | CAN_GRAB = enum.auto() # "grab" in CSS 38 | IS_GRABBED = enum.auto() # "grabbing" in CSS 39 | ZOOM_IN = enum.auto() 40 | ZOOM_OUT = enum.auto() 41 | -------------------------------------------------------------------------------- /rio/debug/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/debug/__init__.py -------------------------------------------------------------------------------- /rio/debug/dev_tools/__init__.py: -------------------------------------------------------------------------------- 1 | from .dev_tools_sidebar import DevToolsSidebar as DevToolsSidebar 2 | -------------------------------------------------------------------------------- /rio/debug/dev_tools/component_picker.py: -------------------------------------------------------------------------------- 1 | from ...components.fundamental_component import FundamentalComponent 2 | 3 | __all__ = ["ComponentPicker"] 4 | 5 | 6 | class ComponentPicker(FundamentalComponent): 7 | """ 8 | Lets the user select a component in the ComponentTree by clicking on it in 9 | the DOM. 10 | 11 | ## Metadata 12 | 13 | `public`: False 14 | """ 15 | 16 | 17 | ComponentPicker._unique_id_ = "ComponentPicker-builtin" 18 | -------------------------------------------------------------------------------- /rio/debug/dev_tools/component_tree.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import dataclasses 4 | 5 | from uniserde import JsonDoc 6 | 7 | import rio 8 | 9 | from ...components.fundamental_component import FundamentalComponent 10 | 11 | __all__ = ["ComponentTree"] 12 | 13 | 14 | class ComponentTree(FundamentalComponent): 15 | """ 16 | Note: This component makes no attempt to request the correct amount of 17 | space. Specify a width/height manually, or make sure it's in a properly 18 | sized parent. 19 | 20 | ## Metadata 21 | 22 | `public`: False 23 | """ 24 | 25 | component_id: int # This can be invalid. The component must deal with it. 26 | 27 | _: dataclasses.KW_ONLY 28 | 29 | # Triggered whenever the user selects a component in the tree. The passed 30 | # value is the component's ID. 31 | on_select_component: rio.EventHandler[int] = None 32 | 33 | def _validate_delta_state_from_frontend(self, delta_state: JsonDoc) -> None: 34 | if not set(delta_state) <= {"component_id"}: 35 | raise AssertionError( 36 | f"Frontend tried to change `{type(self).__name__}` state: {delta_state}" 37 | ) 38 | 39 | async def _call_event_handlers_for_delta_state( 40 | self, delta_state: JsonDoc 41 | ) -> None: 42 | # Trigger the change event 43 | try: 44 | new_id = delta_state["component_id"] 45 | except KeyError: 46 | pass 47 | else: 48 | assert isinstance(new_id, int), new_id 49 | await self.call_event_handler(self.on_select_component, new_id) 50 | 51 | 52 | ComponentTree._unique_id_ = "ComponentTree-builtin" 53 | -------------------------------------------------------------------------------- /rio/debug/dev_tools/deploy_page.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | 4 | class DeployPage(rio.Component): 5 | def build(self) -> rio.Component: 6 | return rio.Column( 7 | rio.Text( 8 | "Deploy", 9 | style="heading2", 10 | margin=1, 11 | justify="left", 12 | ), 13 | rio.Column( 14 | rio.Icon( 15 | "material/rocket_launch", 16 | min_width=6, 17 | min_height=6, 18 | margin_bottom=3, 19 | fill=self.session.theme.secondary_color, 20 | ), 21 | rio.Text( 22 | "One-click deployment is coming soon!", 23 | justify="center", 24 | ), 25 | rio.Text( 26 | "Stay tuned for updates.", 27 | justify="center", 28 | ), 29 | spacing=1, 30 | grow_y=True, 31 | align_y=0.3, 32 | margin=1, 33 | ), 34 | ) 35 | -------------------------------------------------------------------------------- /rio/debug/dev_tools/dev_tools_connector.py: -------------------------------------------------------------------------------- 1 | from ...components.fundamental_component import FundamentalComponent 2 | 3 | __all__ = [ 4 | "DevToolsConnector", 5 | ] 6 | 7 | 8 | class DevToolsConnector(FundamentalComponent): 9 | pass 10 | 11 | 12 | DevToolsConnector._unique_id_ = "DevToolsConnector-builtin" 13 | -------------------------------------------------------------------------------- /rio/debug/dev_tools/docs_page.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | 4 | class DocsPage(rio.Component): 5 | def build(self) -> rio.Component: 6 | return rio.Column( 7 | rio.Text( 8 | "Documentation", 9 | style="heading2", 10 | margin=1, 11 | justify="left", 12 | ), 13 | rio.Column( 14 | rio.Text( 15 | "New here? The Rio tutorial can help you get started.", 16 | justify="center", 17 | overflow="wrap", 18 | ), 19 | rio.Button( 20 | "Read the Tutorial", 21 | icon="material/school", 22 | style="minor", 23 | margin=1, 24 | on_press=self._open_tutorial, 25 | ), 26 | spacing=1, 27 | grow_y=True, 28 | align_y=0.5, 29 | margin=1, 30 | ), 31 | ) 32 | 33 | async def _open_tutorial(self) -> None: 34 | await self.session._evaluate_javascript(""" 35 | window.open('https://rio.dev/get-started', '_blank'); 36 | """) 37 | -------------------------------------------------------------------------------- /rio/observables/__init__.py: -------------------------------------------------------------------------------- 1 | from .dataclass import * 2 | -------------------------------------------------------------------------------- /rio/observables/component_property.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing as t 4 | 5 | import rio 6 | 7 | from .observable_property import ObservableProperty 8 | 9 | __all__ = ["ComponentProperty"] 10 | 11 | 12 | class ComponentProperty(ObservableProperty["rio.Component"]): 13 | """ 14 | ObservableProperty implementation for Rio components. Tracks some additional 15 | information required for reconciliation, which is unique to components. 16 | """ 17 | 18 | def _get_affected_sessions( 19 | self, instance: rio.Component 20 | ) -> t.Iterable[rio.Session]: 21 | return [instance._session_] 22 | 23 | def _on_value_change(self, component: rio.Component) -> None: 24 | super()._on_value_change(component) 25 | component._properties_assigned_after_creation_.add(self.name) 26 | -------------------------------------------------------------------------------- /rio/observables/session_property.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing as t 4 | 5 | import rio 6 | 7 | from .observable_property import ObservableProperty 8 | 9 | __all__ = ["SessionProperty"] 10 | 11 | 12 | class SessionProperty(ObservableProperty["rio.Session"]): 13 | def __set_name__(self, owner: type, name: str): 14 | self.name = name 15 | 16 | def _get_affected_sessions( 17 | self, session: rio.Session 18 | ) -> t.Iterable[rio.Session]: 19 | return [session] 20 | -------------------------------------------------------------------------------- /rio/patches_for_3rd_party_stuff/_OverlappedFuture_cancel_overlapped.py: -------------------------------------------------------------------------------- 1 | # This silences the following error: 2 | # 3 | # Cancelling an overlapped future failed 4 | # future: <_OverlappedFuture pending cb=[BaseProactorEventLoop._start_serving..loop() at C:\Python312\Lib\asyncio\proactor_events.py:841, Task.task_wakeup()]> 5 | # Traceback (most recent call last): 6 | # File "C:\Python312\Lib\asyncio\windows_events.py", line 71, in _cancel_overlapped 7 | # self._ov.cancel() 8 | # OSError: [WinError 6] The handle is invalid 9 | 10 | import asyncio.windows_events 11 | 12 | 13 | def _cancel_overlapped(self): 14 | if self._ov is None: 15 | return 16 | 17 | try: 18 | self._ov.cancel() 19 | except OSError: 20 | pass 21 | 22 | self._ov = None 23 | 24 | 25 | asyncio.windows_events._OverlappedFuture._cancel_overlapped = _cancel_overlapped # type: ignore 26 | -------------------------------------------------------------------------------- /rio/patches_for_3rd_party_stuff/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.platform == "win32": 4 | from . import ( 5 | IocpProactor_accept_locals_accept_coro, 6 | ProactorBasePipeTransport_call_connection_lost, 7 | _OverlappedFuture_cancel_overlapped, 8 | ) 9 | 10 | del _OverlappedFuture_cancel_overlapped 11 | del IocpProactor_accept_locals_accept_coro 12 | del ProactorBasePipeTransport_call_connection_lost 13 | -------------------------------------------------------------------------------- /rio/path_match.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | from pathlib import Path 3 | 4 | import gitignore_parser 5 | 6 | __all__ = [ 7 | "PathMatch", 8 | ] 9 | 10 | 11 | class PathMatch: 12 | """ 13 | Takes a list of paths to include / exclude and provides a method to check 14 | whether a given path should be included or excluded. 15 | 16 | This class uses the same syntax as `.gitignore` files, with the exceptions 17 | that a match is considered positive, while `.gitignore` files consider a 18 | match negative. 19 | """ 20 | 21 | def __init__( 22 | self, 23 | base_dir: Path, 24 | *, 25 | rules: t.Iterable[str] = tuple(), 26 | ) -> None: 27 | self._base_dir = base_dir.resolve() 28 | self._rules: list[gitignore_parser.IgnoreRule] = [] 29 | 30 | for rule in rules: 31 | self.add_rule(rule) 32 | 33 | def add_rule(self, line: str) -> None: 34 | """ 35 | Add a rule to the include list. 36 | """ 37 | # Parse the rule 38 | line = line.rstrip("\n") 39 | rule = gitignore_parser.rule_from_pattern( 40 | line, 41 | base_path=self._base_dir, 42 | source=(None, len(self._rules)), 43 | ) 44 | 45 | # Empty line / comment 46 | if not rule: 47 | return 48 | 49 | self._rules.append(rule) 50 | 51 | def match(self, path: Path) -> bool: 52 | """ 53 | Given a path, return whether the given path matches any of the rules. 54 | """ 55 | for rule in reversed(self._rules): 56 | if rule.match(path): 57 | return not rule.negation 58 | 59 | return False 60 | -------------------------------------------------------------------------------- /rio/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/py.typed -------------------------------------------------------------------------------- /rio/self_serializing.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import abc 4 | 5 | from uniserde import Jsonable 6 | 7 | import rio 8 | 9 | __all__ = ["SelfSerializing"] 10 | 11 | 12 | class SelfSerializing(abc.ABC): 13 | """ 14 | Properties with types that inherit from `SelfSerializing` will be serialized 15 | when sent to the client. 16 | """ 17 | 18 | @abc.abstractmethod 19 | def _serialize(self, sess: rio.Session) -> Jsonable: 20 | raise NotImplementedError 21 | -------------------------------------------------------------------------------- /rio/snippets/README.md: -------------------------------------------------------------------------------- 1 | # Snippets 2 | 3 | This directory, and in particular the subdirectory `snippet-files` contains rio 4 | related code snippets which can be used for tutorials, setting up sample 5 | projects, or similar. You can find facilities for reading these files in this 6 | very directory - just import it as a Python module. 7 | 8 | ## Snippets Structure 9 | 10 | There should be no files directly in the `snippet-files` directory. Instead, all 11 | files must be located in subdirectories. The name of the subdirectory is used as 12 | the group name for all snippets within it (directly or indirectly). Any more 13 | subdirectories will be ignored. 14 | 15 | ``` 16 | snippet-files 17 | └── group 18 | ├── foo.py 19 | ├── bar.py 20 | └── baz.py 21 | ``` 22 | 23 | Note that the file extensions are part of the snippet name. 24 | 25 | ## Semantic Naming conventions 26 | 27 | There is some semantic relevance to snippet names. The following prefixes used 28 | in snippet names have special meaning: 29 | 30 | - **project-template-**: These will be offered to the user as templates for 31 | newly created projects. The Rio website also presents them as tutorials. 32 | 33 | Each template must have files located in either `assets`, `components` and 34 | `pages` subdirectories, as well as contain a `README.md` file which explains 35 | the template. 36 | 37 | Each Python file must have a `` section, which will be copied into 38 | newly created projects. All other code (such as imports) will be 39 | auto-generated. 40 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-AI Chatbot/README.md: -------------------------------------------------------------------------------- 1 | This example shows off a simple AI-Chat, which you can use to talk to OpenAI's 2 | large language models. 3 | 4 | **This example requires an OpenAI API key to function.** 5 | 6 | You can get your API key from [OpenAI's 7 | website](https://platform.openai.com/api-keys). Make sure to enter your key into 8 | the `__init__.py` file before trying to run the project. 9 | 10 | The app has a single chat page, where you can type in a message and get a 11 | response. If there aren't any messages yet, the bot displays a placeholder with 12 | helfpul suggestions to the user. Once the first message is entered, it switches 13 | to a conversation view. 14 | 15 | A progress circle indicates to the user when the bot is busy generating a 16 | response. 17 | 18 | ## Lessons 🎓 19 | 20 | In this example you can see how to: 21 | 22 | - Interact with an external API 23 | - Make components communicate via custom events 24 | 25 | ## Components 🧩 26 | 27 | This project contains 5 components: 28 | 29 | 1. `ChatMessage`: Displays a single chat message with a colored background. 30 | 2. `ChatSuggestionCard`: A clickable component containing a suggested 31 | conversation starter. 32 | 3. `EmptyChatPlaceholder`: A placeholder displayed when there is no chat 33 | history. 34 | 4. `GeneratingResponsePlaceholder`: A placeholder displayed while the AI is 35 | generating its response. 36 | 5. `ChatPage`: The core of the app that combines the other components as 37 | necessary. 38 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-AI Chatbot/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .chat_message import ChatMessage as ChatMessage 2 | from .chat_suggestion_card import ChatSuggestionCard as ChatSuggestionCard 3 | from .empty_chat_placeholder import EmptyChatPlaceholder as EmptyChatPlaceholder 4 | from .generating_response_placeholder import ( 5 | GeneratingResponsePlaceholder as GeneratingResponsePlaceholder, 6 | ) 7 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-AI Chatbot/components/chat_suggestion_card.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import rio 4 | 5 | 6 | # 7 | class ChatSuggestionCard(rio.Component): 8 | """ 9 | While the chat is empty, a placeholder is displayed. That placeholder 10 | contains several of these components, which are suggestions for the user to 11 | start a conversation. 12 | """ 13 | 14 | icon: str 15 | text: str 16 | 17 | on_press: rio.EventHandler[str] = None 18 | 19 | async def _on_press(self) -> None: 20 | await self.call_event_handler(self.on_press, self.text) 21 | 22 | def build(self) -> rio.Component: 23 | # A suggestion is just an icon, text and button wrapped inside a card. 24 | return rio.Card( 25 | rio.Column( 26 | rio.Icon(self.icon, min_width=1.8, min_height=1.8), 27 | rio.Text( 28 | self.text, 29 | justify="center", 30 | overflow="wrap", 31 | grow_y=True, 32 | align_y=0.5, 33 | ), 34 | rio.Button( 35 | "Ask", 36 | icon="material/navigate_next", 37 | style="minor", 38 | on_press=self._on_press, 39 | ), 40 | spacing=0.6, 41 | margin=1, 42 | ), 43 | ) 44 | 45 | 46 | # 47 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-AI Chatbot/components/generating_response_placeholder.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import rio 4 | 5 | 6 | # 7 | class GeneratingResponsePlaceholder(rio.Component): 8 | """ 9 | This component is displayed while the chatbot is generating a response. It 10 | provides feedback to the user that the app is working on their question. 11 | """ 12 | 13 | def build(self) -> rio.Component: 14 | return rio.Row( 15 | rio.ProgressCircle(min_size=1.5), 16 | rio.Text( 17 | "Thinking...", 18 | justify="center", 19 | ), 20 | spacing=1, 21 | ) 22 | 23 | 24 | # 25 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-AI Chatbot/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "level": "intermediate", 3 | "summary": "Your own custom chatbot powered by ChatGPT", 4 | "onAppStart": "on_app_start", 5 | "dependencies": { 6 | "openai": ">=1.16.2" 7 | }, 8 | "readyToRun": false, 9 | "supportsMobile": false 10 | } 11 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-AI Chatbot/root_init.py: -------------------------------------------------------------------------------- 1 | # 2 | import openai # type: ignore (hidden from user) 3 | 4 | import rio 5 | 6 | # 7 | 8 | 9 | # 10 | OPENAI_API_KEY = "" # Replace this with your OpenAI API key 11 | 12 | 13 | # Instruct the developer to replace the placeholder key if they haven't done so 14 | # yet. Feel free to delete this code if you've already replaced the key. 15 | 16 | if OPENAI_API_KEY == "": 17 | message = """ 18 | This template requires an OpenAI API key to work 19 | 20 | You can get your API key from [OpenAI's website](https://platform.openai.com/api-keys 21 | Make sure to enter your key into the `__init__.py` file before trying to run the project 22 | """.strip() 23 | 24 | print(message) 25 | raise RuntimeError(message) 26 | 27 | 28 | def on_app_start(app: rio.App) -> None: 29 | # Create the OpenAI client and attach it to the app 30 | app.default_attachments.append(openai.AsyncOpenAI(api_key=OPENAI_API_KEY)) 31 | 32 | 33 | # 34 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-AI Chatbot/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-AI Chatbot/thumbnail.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Authentication/README.md: -------------------------------------------------------------------------------- 1 | This template demonstrates how to implement user authentication in your app. It 2 | uses SQLite to store user data, but you are of course free to replace it with 3 | whichever persistence mechanism you prefer. 4 | 5 | ## Outline 📝 6 | 7 | All database access is done using a `Persistence` class. This abstracts away the 8 | database operations and makes it easy to switch to a different database if 9 | needed. This class is attached to the session to make it easily retrievable 10 | throughout the app. 11 | 12 | When a user logs in, two things happen: 13 | 14 | - A session token is stored on the user's device so the user can be recognized 15 | on subsequent visits. 16 | - Information about the user, such as their ID and username are attached to the 17 | session. This indicates to the app that somebody is logged in, and who that 18 | person is. 19 | 20 | Any pages that shouldn't be accessible without logging in are protected using 21 | Rio's guard mechanism. 22 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Authentication/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .navbar import Navbar as Navbar 2 | from .news_article import NewsArticle as NewsArticle 3 | from .root_component import RootComponent as RootComponent 4 | from .testimonial import Testimonial as Testimonial 5 | from .user_sign_up_form import UserSignUpForm as UserSignUpForm 6 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Authentication/components/news_article.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import rio 4 | 5 | 6 | # 7 | class NewsArticle(rio.Component): 8 | """ 9 | Displays a news article with some visual separation from the background. 10 | """ 11 | 12 | markdown: str 13 | 14 | def build(self) -> rio.Component: 15 | return rio.Card( 16 | rio.Markdown( 17 | self.markdown, 18 | margin=2, 19 | ) 20 | ) 21 | 22 | 23 | # 24 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Authentication/components/root_component.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import rio 4 | 5 | from .. import components as comps 6 | 7 | 8 | # 9 | class RootComponent(rio.Component): 10 | """ 11 | This component will be used as the root for the app. This means that it will 12 | always be visible, regardless of which page is currently active. 13 | 14 | This makes it the perfect place to put components that should be visible on 15 | all pages, such as a navbar or a footer. 16 | 17 | Additionally, the root will contain a `rio.PageView`. Page views don't have 18 | any appearance of their own, but they are used to display the content of the 19 | currently active page. Thus, we'll always see the navbar and footer, with 20 | the content of the current page sandwiched in between. 21 | """ 22 | 23 | def build(self) -> rio.Component: 24 | return rio.Column( 25 | # The navbar contains a `rio.Overlay`, so it will always be on top 26 | # of all other components. 27 | comps.Navbar(), 28 | # The page view will display the content of the current page. 29 | rio.PageView( 30 | # Make sure the page view takes up all available space. Without 31 | # this the navbar would be assigned the same space as the page 32 | # content. 33 | grow_y=True, 34 | ), 35 | ) 36 | 37 | 38 | # 39 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Authentication/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "level": "beginner", 3 | "summary": "In this project, you will learn how to create a simple authentication system using SQLite.", 4 | "onAppStart": "on_app_start", 5 | "onSessionStart": "on_session_start", 6 | "defaultAttachments": ["data_models.UserSettings(auth_token=\"\")"], 7 | "rootComponent": "RootComponent", 8 | "readyToRun": true, 9 | "supportsMobile": true 10 | } 11 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Authentication/pages/app_page.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import rio 4 | 5 | # 6 | from .. import data_models 7 | 8 | # 9 | 10 | 11 | # 12 | def guard(event: rio.GuardEvent) -> str | None: 13 | # This website allows access to sensitive information. Enforce stringent 14 | # access control to all in-app pages. 15 | 16 | # Check if the user is authenticated by looking for a user session 17 | try: 18 | event.session[data_models.AppUser] 19 | 20 | except KeyError: 21 | # User is not logged in, redirect to the login page 22 | return "/" 23 | 24 | # User is logged in, no redirection needed 25 | return None 26 | 27 | 28 | @rio.page( 29 | name="App", 30 | url_segment="app", 31 | guard=guard, 32 | ) 33 | class InnerAppPage(rio.Component): 34 | def build(self) -> rio.Component: 35 | return rio.Column( 36 | # Add some empty space so the navbar doesn't cover the content. 37 | rio.Spacer(min_height=10, grow_y=True), 38 | rio.PageView( 39 | grow_y=True, 40 | ), 41 | ) 42 | 43 | 44 | # 45 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Authentication/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Authentication/thumbnail.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Crypto Dashboard/README.md: -------------------------------------------------------------------------------- 1 | This example shows off a simple interactive Dashboard, which allows you to view 2 | the balance of a cryptocurrency wallet, the details of a cryptocurrency, and a 3 | chart of the price of a cryptocurrency. 4 | 5 | ## Lessons 🎓 6 | 7 | In this example you can see how to: 8 | 9 | - Create a simple interactive dashboard 10 | - Pass data between components 11 | - Populate your application with your own data 12 | - Fetch data from an API or Local Storage 13 | 14 | ## Components 🧩 15 | 16 | This template is composed of four main components: 17 | 18 | 1. `BalanceCard`: Displays the balance of a cryptocurrency wallet. 19 | 2. `CryptoCard`: Displays the details of a cryptocurrency. 20 | 3. `CryptoChart`: Displays a chart of the price of a cryptocurrency. 21 | 4. `DashboardPage`: Combines the `BalanceCard`, `CryptoCard`, and `CryptoChart` 22 | components and add some logic. 23 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Crypto Dashboard/assets/testimonial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Crypto Dashboard/assets/testimonial.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Crypto Dashboard/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .balance_card import BalanceCard as BalanceCard 2 | from .colored_rectangle import ColoredRectangle as ColoredRectangle 3 | from .content_card import ContentCard as ContentCard 4 | from .contribution_wanted import ContributionWanted as ContributionWanted 5 | from .crypto_card import CryptoCard as CryptoCard 6 | from .crypto_chart import CryptoChart as CryptoChart 7 | from .hamburger_button import HamburgerButton as HamburgerButton 8 | from .major_section import MajorSection as MajorSection 9 | from .nav_bar import NavBar as NavBar 10 | from .notification_button import NotificationButton as NotificationButton 11 | from .overlay_bar import OverlayBar as OverlayBar 12 | from .portfolio_distribution import ( 13 | PortfolioDistribution as PortfolioDistribution, 14 | ) 15 | from .portfolio_overview import PortfolioOverview as PortfolioOverview 16 | from .rio_logo import RioLogo as RioLogo 17 | from .root_component import RootComponent as RootComponent 18 | from .styled_portfolio import StyledPortfolio as StyledPortfolio 19 | from .styled_transaction import StyledTransaction as StyledTransaction 20 | from .transactions_overview import TransactionsOverview as TransactionsOverview 21 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Crypto Dashboard/components/colored_rectangle.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | 4 | # 5 | class ColoredRectangle(rio.Component): 6 | """ 7 | A component that represents a rectangle with a color and text indicating a 8 | percentage difference. 9 | 10 | 11 | ## Attributes: 12 | 13 | `percentage_difference`: The percentage difference to display in the 14 | rectangle. Positive values are displayed in green, while negative values 15 | are displayed in red. 16 | """ 17 | 18 | percentage_difference: float 19 | 20 | def build(self) -> rio.Component: 21 | # Determine the color according of the percentage difference. 22 | if self.percentage_difference > 0: 23 | color = rio.Color.GREEN 24 | else: 25 | color = rio.Color.RED 26 | 27 | # Return a rectangle component with the appropriate styling and content 28 | return rio.Rectangle( 29 | content=rio.Text( 30 | text=f"{self.percentage_difference:,.2f} %", 31 | fill=color, 32 | font_size=self.session.theme.text_style.font_size * 0.9, 33 | margin=0.2, 34 | ), 35 | # Set a semi-transparent fill color for the rectangle 36 | fill=color.replace(opacity=0.1), 37 | stroke_color=color, 38 | stroke_width=0.1, 39 | align_y=0.5, 40 | corner_radius=self.session.theme.corner_radius_small, 41 | ) 42 | 43 | 44 | # 45 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Crypto Dashboard/components/content_card.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | 4 | # 5 | class ContentCard(rio.Component): 6 | """ 7 | A component that represents a card with a header and content. 8 | 9 | 10 | ## Attributes: 11 | 12 | `header`: The text to display as the card's header. 13 | 14 | `content`: The content component to display within the card. 15 | """ 16 | 17 | header: str 18 | content: rio.Component 19 | 20 | def build(self) -> rio.Component: 21 | return rio.Card( 22 | content=rio.Column( 23 | # Add header with bold font 24 | rio.Text( 25 | self.header, 26 | font_size=self.session.theme.text_style.font_size * 1.5, 27 | font_weight="bold", 28 | ), 29 | # Add content 30 | self.content, 31 | # Fill up remaining space with a spacer, therefore all cards 32 | # have the same height 33 | rio.Spacer(), 34 | spacing=1, 35 | margin=1, 36 | ), 37 | ) 38 | 39 | 40 | # 41 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Crypto Dashboard/components/contribution_wanted.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | # 4 | from .. import data_models 5 | 6 | # 7 | 8 | 9 | # 10 | class ContributionWanted(rio.Component): 11 | """ 12 | The ContributionWanted class is a component of a dashboard application, designed to 13 | display a message for contributors. 14 | 15 | ## Attributes 16 | 17 | `message`: The message for contributors. 18 | """ 19 | 20 | def build(self) -> rio.Component: 21 | """ 22 | Creates a message for contributors. 23 | """ 24 | # Set the margin and minimum width based on the device 25 | device = self.session[data_models.PageLayout].device 26 | 27 | if device == "desktop": 28 | margin = 5 29 | min_width = 40 30 | else: 31 | margin = 2 32 | min_width = self.session.window_width - 2 33 | 34 | return rio.Card( 35 | rio.Markdown( 36 | """ 37 | # Contribution Wanted! :) 38 | 39 | Would you like to help us improve this template or share your ideas? Feel free 40 | to get in touch with us [GitHub 41 | issue](https://github.com/rio-labs/rio/issues/217)! 42 | 43 | We would love to hear from you! 🚀 44 | """, 45 | margin=margin, 46 | ), 47 | min_width=min_width, 48 | ) 49 | 50 | 51 | # 52 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Crypto Dashboard/components/rio_logo.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | # 4 | LOGO_ASPECT_RATIO = 1.75 / 2.5 5 | LOGO_FONT_ASPECT_RATIO = 24.667 / 16 6 | LOGO_UNIT_RATIO = 0.625 7 | 8 | 9 | class RioLogo(rio.Component): 10 | """ 11 | A component that renders the Rio logo alongside the text "Rio". 12 | 13 | 14 | ## Attributes: 15 | 16 | `min_height`: The minimum height of the logo. 17 | 18 | `text_size`: The size of the "Rio" text. 19 | 20 | `spacing`: Spacing between the logo and the text. 21 | 22 | `logo_fill`: The color of the "Rio" text. 23 | """ 24 | 25 | min_height: float = 4 26 | text_size: float = 1 27 | spacing: float = 1 28 | logo_fill: rio.Color | None = None 29 | 30 | def build(self) -> rio.Component: 31 | return rio.Row( 32 | rio.Icon( 33 | "rio/logo:color", 34 | min_width=self.min_height * LOGO_ASPECT_RATIO * LOGO_UNIT_RATIO, 35 | min_height=self.min_height * LOGO_UNIT_RATIO, 36 | ), 37 | rio.Text( 38 | "Rio", 39 | font_size=self.text_size * LOGO_FONT_ASPECT_RATIO, 40 | fill=( 41 | self.session.theme.heading1_style.fill 42 | if self.logo_fill is None 43 | else self.logo_fill 44 | ), 45 | ), 46 | spacing=self.spacing * LOGO_UNIT_RATIO, 47 | align_x=0.5, 48 | align_y=0.5, 49 | ) 50 | 51 | 52 | # 53 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Crypto Dashboard/components/transactions_overview.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | # 4 | from .. import components as comps 5 | from .. import constants 6 | 7 | # 8 | 9 | 10 | # 11 | class TransactionsOverview(rio.Component): 12 | """ 13 | A component that provides an overview of all user transactions. 14 | 15 | The component displays a list of transactions in a styled format, including: 16 | - Transaction details such as type, amount, and date. 17 | - Separators between transactions for visual distinction. 18 | - A header titled "Transaction" for context. 19 | """ 20 | 21 | def build(self) -> rio.Component: 22 | # Create a column to hold the transaction list 23 | content = rio.Column(spacing=1) 24 | 25 | # Get the total number of transactions to determine where separators are 26 | # needed 27 | total_transactions = len(constants.TRANSACTIONS) 28 | 29 | for i, transaction in enumerate(constants.TRANSACTIONS): 30 | # Add a styled transaction component for the current transaction 31 | content.add(comps.StyledTransaction(transaction)) 32 | 33 | # Add a separator if the current item is not the last in the list 34 | if i < total_transactions - 1: 35 | content.add(rio.Separator()) 36 | 37 | return comps.ContentCard( 38 | header="Transaction", 39 | content=content, 40 | ) 41 | 42 | 43 | # 44 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Crypto Dashboard/data_models.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | from dataclasses import dataclass 3 | from datetime import datetime 4 | 5 | 6 | @dataclass 7 | class MainSection: 8 | main_section_name: str 9 | icon: str 10 | target_url: str 11 | 12 | 13 | @dataclass 14 | class Coin: 15 | name: str 16 | quantity_owned: float 17 | ticker: str 18 | color: str 19 | logo: str 20 | 21 | 22 | @dataclass 23 | class Transaction: 24 | name: str 25 | ticker: str 26 | amount: float 27 | price: float 28 | date: datetime 29 | 30 | 31 | @dataclass(frozen=True) 32 | class PageLayout: 33 | device: t.Literal["desktop", "mobile"] 34 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Crypto Dashboard/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "level": "intermediate", 3 | "summary": "A crypto currency portfolio dashboard", 4 | "dependencies": { 5 | "numpy": ">=1.26.4", 6 | "plotly": ">=5.20.0", 7 | "pycoingecko": ">=3.1.0", 8 | "pandas": ">=2.2.1" 9 | }, 10 | "onSessionStart": "on_session_start", 11 | "rootComponent": "RootComponent", 12 | "theme": "theme.THEME", 13 | "readyToRun": true, 14 | "supportsMobile": true 15 | } 16 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Crypto Dashboard/pages/statistics_page.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | # 4 | from .. import components as comps 5 | 6 | # 7 | 8 | 9 | # 10 | @rio.page( 11 | name="Statistics", 12 | url_segment="statistics", 13 | ) 14 | class TransactionPage(rio.Component): 15 | """ 16 | HELP US IMPROVE THIS TEMPLATE 17 | """ 18 | 19 | def build(self) -> rio.Component: 20 | return comps.ContributionWanted( 21 | align_x=0.5, 22 | align_y=0.5, 23 | grow_x=True, 24 | ) 25 | 26 | 27 | # 28 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Crypto Dashboard/pages/transaction_page.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | # 4 | from .. import components as comps 5 | 6 | # 7 | 8 | 9 | # 10 | @rio.page( 11 | name="Transactions", 12 | url_segment="transactions", 13 | ) 14 | class TransactionPage(rio.Component): 15 | """ 16 | HELP US IMPROVE THIS TEMPLATE 17 | """ 18 | 19 | def build(self) -> rio.Component: 20 | return comps.ContributionWanted( 21 | align_x=0.5, 22 | align_y=0.5, 23 | grow_x=True, 24 | ) 25 | 26 | 27 | # 28 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Crypto Dashboard/root_init.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | # 4 | from . import data_models, theme 5 | 6 | # 7 | 8 | 9 | # 10 | def on_session_start(sess: rio.Session) -> None: 11 | # Determine which layout to use 12 | if sess.window_width < 60: 13 | layout = data_models.PageLayout( 14 | device="mobile", 15 | ) 16 | else: 17 | layout = data_models.PageLayout( 18 | device="desktop", 19 | ) 20 | 21 | # Attach the layout to the session 22 | sess.attach(layout) 23 | 24 | 25 | # 26 | 27 | 28 | # Make sure ruff doesn't remove unused imports 29 | # Create the Rio app 30 | app = rio.App( 31 | name="crypto_dashboard", 32 | theme=theme.THEME, 33 | ) 34 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Crypto Dashboard/theme.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | THEME = rio.Theme.from_colors( 4 | mode="dark", 5 | heading_fill=rio.Color.from_hex("#ffffff"), # Fill color for headings 6 | ) 7 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Crypto Dashboard/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Crypto Dashboard/thumbnail.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/README.md: -------------------------------------------------------------------------------- 1 | This example showcases a responsive and interactive sales dashboard designed to 2 | provide a comprehensive view of sales performance. The dashboard enables users 3 | to track key metrics, visualize data, and gain insights into sales trends. It 4 | supports both mobile and desktop layouts, ensuring a seamless experience across 5 | devices. 6 | 7 | The sales dashboard includes features such as revenue tracking, top performer 8 | highlights, and detailed breakdowns of key performance indicators like renewal 9 | and churn rates. With its ability to persistently load data on app start and 10 | define layouts across sessions, this dashboard is a powerful tool for monitoring 11 | and improving sales performance. 12 | 13 | ## Lessons 🎓 14 | 15 | In this example you can see how to: 16 | 17 | - Create a simple interactive dashboard 18 | - Pass data between components 19 | - Persistently load data on app start 20 | - Define and load page layout across sessions 21 | - How to build a dashboard supporting mobile and desktop layouts 22 | 23 | ## Components 🧩 24 | 25 | This template is composed of four main components: 26 | 27 | 1. `LineChartCard`: Displays the revenue data for a specific period. 28 | 2. `DonutChartCard`: Displays the breakdown of key performance indicators. 29 | 3. `TopPerformers`: Highlights the top performer based on sales data. 30 | 4. `RateCard`: Displays the renewal and churn rates. 31 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_1.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_10.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_11.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_12.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_13.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_2.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_3.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_4.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_5.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_6.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_7.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_8.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Dashboard Better Name/assets/user_9.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/components/multi_select_dropdown_change_event.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # 4 | import dataclasses 5 | import typing as t 6 | 7 | # 8 | 9 | # 10 | T = t.TypeVar("T") 11 | 12 | 13 | @dataclasses.dataclass 14 | class MultiSelectDropdownChangeEvent(t.Generic[T]): 15 | """ 16 | Holds information regarding a dropdown change event. 17 | 18 | This is a simple dataclass that stores useful information for when the user 19 | selects an option in a `Dropdown`. You'll typically receive this as argument 20 | in `on_change` events. 21 | 22 | ## Attributes 23 | 24 | `value`: The new `selected_value` of the `Dropdown`. 25 | """ 26 | 27 | values: list[T] 28 | 29 | 30 | # 31 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/components/popup_rectangle.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | 4 | # 5 | class PopupRectangle(rio.Component): 6 | """ 7 | A component that represents a popup rectangle with customizable content. 8 | 9 | This component displays a rectangle with a specified content component 10 | inside it. The rectangle is styled using the application's theme, including 11 | its fill color, stroke color, and corner radius. 12 | 13 | 14 | ## Attributes: 15 | 16 | `content`: The content to be displayed inside the rectangle. 17 | """ 18 | 19 | content: rio.Component 20 | 21 | def build(self) -> rio.Component: 22 | return rio.Rectangle( 23 | content=self.content, 24 | fill=self.session.theme.neutral_color, 25 | stroke_width=0.1, 26 | stroke_color=self.session.theme.neutral_color.brighter(0.2), 27 | corner_radius=self.session.theme.corner_radius_small, 28 | ) 29 | 30 | 31 | # 32 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/components/root_component.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import rio 4 | 5 | # 6 | from .. import components as comps 7 | 8 | # 9 | 10 | 11 | # 12 | class RootComponent(rio.Component): 13 | """ 14 | This page will be used as the root component for the app. This means, that 15 | it will always be visible, regardless of which page is currently active. 16 | 17 | This makes it the perfect place to put components that should be visible on 18 | all pages, such as a navbar or a footer. 19 | 20 | Additionally, the root page will contain a `rio.PageView`. Page views don't 21 | have any appearance on their own, but they are used to display the content 22 | of the currently active page. Thus, we'll always see the navbar and footer, 23 | with the content of the current page in between. 24 | """ 25 | 26 | def build(self) -> rio.Component: 27 | return rio.Row( 28 | # The navbar contains a `rio.Overlay`, so it will always be on top 29 | # of all other components. 30 | comps.SideBar(), 31 | # Add some empty space so the navbar doesn't cover the content. 32 | # The page view will display the content of the current page. 33 | rio.PageView( 34 | # Make sure the page view takes up all available space. 35 | grow_y=True, 36 | grow_x=True, 37 | ), 38 | ) 39 | 40 | 41 | # 42 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "level": "intermediate", 3 | "summary": "A responsive sales dashboard displaying key performance metrics", 4 | "dependencies": { 5 | "pandas": ">=2.2.3", 6 | "plotly": ">=6.0.1" 7 | }, 8 | "rootComponent": "RootComponent", 9 | "theme": "theme.THEME", 10 | "readyToRun": true, 11 | "supportsMobile": false 12 | } 13 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/pages/settings_page/members_page.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | # 4 | from ... import components as comps 5 | 6 | # 7 | 8 | 9 | # 10 | @rio.page( 11 | name="Members", 12 | url_segment="members", 13 | ) 14 | class MembersPage(rio.Component): 15 | """ 16 | This page allows users to manage access by inviting new members via email. 17 | """ 18 | 19 | def build(self) -> rio.Component: 20 | return rio.Column( 21 | comps.ContentContainer( 22 | "Manage access", 23 | "Invite new members by email address.", 24 | add_separator=False, 25 | ), 26 | rio.Button( 27 | "Invite people", 28 | shape="rounded", 29 | color=rio.Color.WHITE, 30 | # TODO: Add an event handler to open the invite dialog 31 | align_y=0.5, 32 | align_x=0, 33 | ), 34 | spacing=1.5, 35 | align_y=0, 36 | ) 37 | 38 | 39 | # 40 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/root_init.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | # 4 | from . import theme 5 | 6 | # 7 | 8 | 9 | # Make sure ruff doesn't remove unused imports 10 | # Create the Rio app 11 | app = rio.App( 12 | name="Multipage Website", 13 | theme=theme.THEME, 14 | ) 15 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/theme.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | import rio 4 | 5 | # Define a theme for Rio to use. 6 | # 7 | # You can modify the colors here to adapt the appearance of your app or website. 8 | # The most important parameters are listed, but more are available! You can find 9 | # them all in the docs 10 | # 11 | # https://rio.dev/docs/api/theme 12 | 13 | 14 | THEME = rio.Theme.from_colors( 15 | mode="dark", 16 | ) 17 | 18 | POPUP_INNER_MARGIN = 0.3 19 | TOOLTIP_TEXTSTYLE = rio.TextStyle(font_size=0.8) 20 | 21 | # Define brighter and darker text colors for the theme 22 | TEXT_FILL_BRIGHTER = THEME.text_style.fill 23 | TEXT_FILL_DARKER = THEME.text_style.fill 24 | 25 | # Ensure that the text fill colors are of type rio.Color 26 | # and apply the brighter and darker adjustments 27 | assert isinstance(TEXT_FILL_BRIGHTER, rio.Color) 28 | assert isinstance(TEXT_FILL_DARKER, rio.Color) 29 | TEXT_FILL_BRIGHTER = TEXT_FILL_BRIGHTER.brighter(0.1) 30 | TEXT_FILL_DARKER = TEXT_FILL_DARKER.darker(0.15) 31 | 32 | # Define text styles for different use cases 33 | TEXT_STYLE_DARKER_SMALL = rio.TextStyle( 34 | fill=TEXT_FILL_DARKER, 35 | font_size=0.9, 36 | ) 37 | TEXT_STYLE_SMALL_BOLD = rio.TextStyle(font_weight="bold", font_size=0.9) 38 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Dashboard Better Name/thumbnail.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Dashboard Better Name/utils.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | PROJECT_ROOT_DIR = Path(__file__).resolve().parent 4 | ASSETS_DIR = PROJECT_ROOT_DIR / "assets" 5 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Empty/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Empty/README.md -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Empty/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "level": "beginner", 3 | "summary": "A project containing the bare minimum to get started", 4 | "readyToRun": true, 5 | "supportsMobile": true 6 | } 7 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Empty/pages/sample_page.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | 4 | # 5 | @rio.page( 6 | name="Sample Page", 7 | url_segment="", 8 | ) 9 | class SamplePage(rio.Component): 10 | """ 11 | This is an example Page. Pages are identical to other Components and only 12 | differ in how they're used. 13 | """ 14 | 15 | def build(self) -> rio.Component: 16 | return rio.Column( 17 | rio.Text("My App", style="heading2"), 18 | rio.Text( 19 | "This is a sample page. Replace it with your own content." 20 | ), 21 | spacing=2, 22 | margin=2, 23 | align_x=0.5, 24 | align_y=0, 25 | ) 26 | 27 | 28 | # 29 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Empty/root_init.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Empty/root_init.py -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/README.md: -------------------------------------------------------------------------------- 1 | This is a simple website which shows off how to add multiple pages to your Rio 2 | app. The website comes with a custom navbar that allows you to switch between 3 | the different pages. 4 | 5 | ## Outline 📝 6 | 7 | The navbar is placed inside a `rio.Overlay` component, which makes it hover 8 | above all other components. It contains buttons to switch between pages, and 9 | also displays the currently active page by highlighting the corresponding 10 | button. 11 | 12 | To avoid placing the app on each page individually, this app makes use of the 13 | app's build method. That's right, build functions aren't just for components! 14 | The app's build creates an instance of `RootPage`, which in turn displays the 15 | navbar and a `rio.PageView`. The currently active page is then always displayed 16 | inside of that page view. 17 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/assets/basic_patterns/background_gray_2_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/assets/blog_authors/blog_author_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Multipage Website/assets/blog_authors/blog_author_1.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/assets/blog_authors/blog_author_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Multipage Website/assets/blog_authors/blog_author_2.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/assets/blog_authors/blog_author_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Multipage Website/assets/blog_authors/blog_author_3.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/assets/blog_authors/blog_author_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Multipage Website/assets/blog_authors/blog_author_4.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/assets/blog_authors/blog_author_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Multipage Website/assets/blog_authors/blog_author_5.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/assets/blog_authors/blog_author_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Multipage Website/assets/blog_authors/blog_author_6.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/assets/blog_authors/blog_author_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Multipage Website/assets/blog_authors/blog_author_7.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/assets/testimonials/testimonial_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Multipage Website/assets/testimonials/testimonial_1.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/assets/testimonials/testimonial_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Multipage Website/assets/testimonials/testimonial_2.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/assets/testimonials/testimonial_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Multipage Website/assets/testimonials/testimonial_3.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/assets/testimonials/testimonial_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Multipage Website/assets/testimonials/testimonial_4.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/assets/testimonials/testimonial_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Multipage Website/assets/testimonials/testimonial_5.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/assets/testimonials/testimonial_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Multipage Website/assets/testimonials/testimonial_6.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/components/fake_link.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # 4 | import rio 5 | 6 | # 7 | 8 | 9 | # 10 | class FakeLink(rio.Component): 11 | """ 12 | A styled component resembling a hyperlink. 13 | 14 | This component is designed to visually appear like a link but does not have 15 | any associated functionality or navigation. 16 | 17 | 18 | ## Attributes: 19 | 20 | `text`: The text to display as the link. 21 | """ 22 | 23 | text: str 24 | 25 | def build(self) -> rio.Component: 26 | """ 27 | Constructs the fake link component. 28 | 29 | The component includes: 30 | - Text styled to resemble a hyperlink. 31 | - A pointer cursor on hover for visual feedback. 32 | """ 33 | return rio.Rectangle( 34 | content=rio.Text( 35 | text=self.text, 36 | font_size=0.9, 37 | ), 38 | cursor="pointer", 39 | fill=self.session.theme.background_color, 40 | align_x=0, 41 | ) 42 | 43 | 44 | # 45 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/components/goody_column.py: -------------------------------------------------------------------------------- 1 | # 2 | import rio 3 | 4 | from .. import theme 5 | 6 | # 7 | 8 | 9 | # 10 | class GoodyColumn(rio.Component): 11 | """ 12 | A column that displays the amount of goodies that come with a plan. 13 | 14 | 15 | ## Attributes: 16 | 17 | `amount_goodies`: The amount of goodies that come with the plan. 18 | 19 | `goodie_text`: The text that describes the goodies. For example, "GB Storage". 20 | """ 21 | 22 | amount_goodies: int 23 | goodie_text: str 24 | 25 | def build(self) -> rio.Component: 26 | # Determine if the text should be pluralized based on the quantity of 27 | # goodies 28 | if self.amount_goodies > 1 and not self.goodie_text == "GB Storage": 29 | plural = "s" 30 | else: 31 | plural = "" 32 | 33 | # Build and return the row component with the icon and text 34 | return rio.Row( 35 | rio.Icon( 36 | "material/check_circle:fill", 37 | fill=self.session.theme.primary_color, 38 | ), 39 | rio.Text( 40 | f"{self.amount_goodies} {self.goodie_text}{plural}", 41 | style=theme.DARK_TEXT_SMALLER, 42 | ), 43 | align_x=0, # Align the row's contents horizontally to the left 44 | spacing=0.8, # Add spacing between the icon and text 45 | ) 46 | 47 | 48 | # 49 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/components/root_component.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # 4 | import rio 5 | 6 | from .. import components as comps 7 | 8 | # 9 | 10 | 11 | # 12 | class RootComponent(rio.Component): 13 | """ 14 | This page will be used as the root component for the app. This means, that 15 | it will always be visible, regardless of which page is currently active. 16 | 17 | This makes it the perfect place to put components that should be visible on 18 | all pages, such as a navbar or a footer. 19 | 20 | Additionally, the root page will contain a `rio.PageView`. Page views don't 21 | have any appearance on their own, but they are used to display the content 22 | of the currently active page. Thus, we'll always see the navbar and footer, 23 | with the content of the current page in between. 24 | """ 25 | 26 | def build(self) -> rio.Component: 27 | return rio.Column( 28 | # The navbar contains a `rio.Overlay`, so it will always be on top 29 | # of all other components. 30 | comps.Navbar(), 31 | # Add some empty space so the navbar doesn't cover the content. 32 | rio.Spacer(min_height=10), 33 | # The page view will display the content of the current page. 34 | rio.PageView( 35 | # Make sure the page view takes up all available space. 36 | grow_y=True, 37 | ), 38 | # The footer is also common to all pages, so place it here. 39 | comps.Footer(), 40 | ) 41 | 42 | 43 | # 44 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "level": "intermediate", 3 | "summary": "A website with multiple pages and a custom navbar", 4 | "onSessionStart": "on_session_start", 5 | "rootComponent": "RootComponent", 6 | "theme": "theme.THEME", 7 | "readyToRun": true, 8 | "supportsMobile": true 9 | } 10 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/pages/blog_page.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # 4 | import rio 5 | 6 | # 7 | 8 | 9 | # 10 | @rio.page( 11 | name="Blog", 12 | url_segment="blog", 13 | ) 14 | class BlogPage(rio.Component): 15 | """ 16 | This page will be used as the root component for blog pages. This means, 17 | that it will always be visible, regardless of which sub page is currently 18 | active. 19 | 20 | Similar to the root component the blog page contains a `rio.PageView`. Page 21 | views don't have any appearance on their own, but they are used to display 22 | the content of the currently active page. 23 | """ 24 | 25 | def build(self) -> rio.Component: 26 | return rio.PageView() 27 | 28 | 29 | # 30 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/root_init.py: -------------------------------------------------------------------------------- 1 | # 2 | from __future__ import annotations 3 | 4 | import rio 5 | 6 | from . import data_models, theme 7 | from .utils import ASSETS_DIR 8 | 9 | # 10 | 11 | # 12 | rio.Icon.register_single_icon( 13 | ASSETS_DIR / "discord_logo.svg", 14 | "thirdparty", 15 | "discord_logo", 16 | ) 17 | 18 | rio.Icon.register_single_icon( 19 | ASSETS_DIR / "github_logo.svg", 20 | "thirdparty", 21 | "github_logo", 22 | ) 23 | 24 | 25 | def on_session_start(sess: rio.Session) -> None: 26 | # Determine which layout to use 27 | if sess.window_width < 60: 28 | layout = data_models.PageLayout( 29 | device="mobile", 30 | ) 31 | else: 32 | layout = data_models.PageLayout( 33 | device="desktop", 34 | ) 35 | 36 | # Attach the layout to the session 37 | sess.attach(layout) 38 | 39 | 40 | # 41 | 42 | 43 | # Make sure ruff doesn't remove unused imports 44 | # Create the Rio app 45 | app = rio.App( 46 | name="Multipage Website", 47 | theme=theme.THEME, 48 | ) 49 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Multipage Website/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Multipage Website/thumbnail.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Sales Dashboard/README.md: -------------------------------------------------------------------------------- 1 | This example showcases a responsive and interactive sales dashboard designed to 2 | provide a comprehensive view of sales performance. The dashboard enables users 3 | to track key metrics, visualize data, and gain insights into sales trends. It 4 | supports both mobile and desktop layouts, ensuring a seamless experience across 5 | devices. 6 | 7 | The sales dashboard includes features such as revenue tracking, top performer 8 | highlights, and detailed breakdowns of key performance indicators like renewal 9 | and churn rates. With its ability to persistently load data on app start and 10 | define layouts across sessions, this dashboard is a powerful tool for monitoring 11 | and improving sales performance. 12 | 13 | ## Lessons 🎓 14 | 15 | In this example you can see how to: 16 | 17 | - Create a simple interactive dashboard 18 | - Pass data between components 19 | - Persistently load data on app start 20 | - Define and load page layout across sessions 21 | - How to build a dashboard supporting mobile and desktop layouts 22 | 23 | ## Components 🧩 24 | 25 | This template is composed of four main components: 26 | 27 | 1. `LineChartCard`: Displays the revenue data for a specific period. 28 | 2. `DonutChartCard`: Displays the breakdown of key performance indicators. 29 | 3. `TopPerformers`: Highlights the top performer based on sales data. 30 | 4. `RateCard`: Displays the renewal and churn rates. 31 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Sales Dashboard/assets/sales_data.csv: -------------------------------------------------------------------------------- 1 | employee;calls booked;sales won;conversion rate;outbound calls 2 | Luca Moretti;193;22503;0.46;83 3 | Sofia Petrova;189;18491;0.41;59 4 | Miloš Jovanović;184;10906;0.5;78 5 | Freya Andersson;180;19384;0.47;71 6 | Dimitri Papadopoulos;177;11982;0.52;68 7 | Anouk Dubois;172;11081;0.54;56 8 | Mateusz Kowalski;169;23752;0.4;53 9 | Elena Popescu;164;20401;0.43;76 10 | Jasper van Dijk;159;19998;0.42;86 11 | Isolde Klein;155;19063;0.5;63 12 | Sven Larsen;151;17475;0.62;60 13 | Chiara Bianchi;148;21563;0.5;75 14 | Niko Virtanen;147;13004;0.49;83 15 | Agnes Horváth;144;12241;0.45;66 16 | Rafaël Almeida;142;18224;0.44;50 17 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Sales Dashboard/assets/top_performer_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Sales Dashboard/assets/top_performer_1.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Sales Dashboard/assets/top_performer_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Sales Dashboard/assets/top_performer_2.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Sales Dashboard/assets/top_performer_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Sales Dashboard/assets/top_performer_3.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Sales Dashboard/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .donut_chart_card import DonutChartCard as DonutChartCard 2 | from .line_chart_card import LineChartCard as LineChartCard 3 | from .rate_card import RateCard as RateCard 4 | from .top_performers import TopPerformers as TopPerformers 5 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Sales Dashboard/data_models.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import dataclasses 4 | import typing as t 5 | 6 | 7 | @dataclasses.dataclass 8 | class PageLayout: 9 | """ 10 | Represents the layout configuration for a page. 11 | 12 | 13 | ## Attributes: 14 | 15 | `device`: Specifies the type of device for the page layout. 16 | 17 | `app_margin`: The margin of the app. 18 | 19 | `grow_x_content`: Whether the content should grow in the x direction. 20 | 21 | `grow_y_content`: Whether the content should grow in the y direction. 22 | 23 | `margin_in_card`: The margin of the card. 24 | 25 | `min_width_content`: The minimum width of the content. 26 | """ 27 | 28 | device: t.Literal["desktop", "mobile"] 29 | margin_app: float 30 | grow_x_content: bool 31 | grow_y_content: bool 32 | margin_in_card: float 33 | min_width_content: float = 0 34 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Sales Dashboard/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "level": "intermediate", 3 | "summary": "A responsive sales dashboard displaying key performance metrics", 4 | "dependencies": { 5 | "pandas": ">=2.2.3", 6 | "plotly": ">=6.0.1" 7 | }, 8 | "onSessionStart": "on_session_start", 9 | "onAppStart": "on_app_start", 10 | "readyToRun": true, 11 | "supportsMobile": true 12 | } 13 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Sales Dashboard/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Sales Dashboard/thumbnail.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Simple CRUD/README.md: -------------------------------------------------------------------------------- 1 | This template shows off a simple CRUD App, which allows you to create, 2 | read, update, and delete menu items. 3 | 4 | ## Lessons 🎓 5 | 6 | In this example you can see: 7 | 8 | - How to populate your application with your own data, add new items, edit 9 | existing items, and delete items. The data is stored in the `menu_items` state 10 | of the `CrudPage` component. 11 | - How to to create and use `custom dialogs`. 12 | 13 | ## Components 🧩 14 | 15 | The example consists of one main component: 16 | 17 | `CrudPage`: Displays the list of menu items and allows the user to add new 18 | items, delete existing items, select an item for editing or create an new one. 19 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Simple CRUD/data_models.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import copy 4 | import dataclasses 5 | 6 | 7 | @dataclasses.dataclass 8 | class MenuItem: 9 | """ 10 | MenuItem data model. 11 | 12 | ## Attributes 13 | 14 | `name`: The name of the menu item. 15 | 16 | `description`: The description of the menu item. 17 | 18 | `price`: The price of the menu item. 19 | 20 | `category`: The category of the menu item. 21 | """ 22 | 23 | name: str 24 | description: str 25 | price: float 26 | category: str 27 | 28 | @staticmethod 29 | def new_empty() -> MenuItem: 30 | """ 31 | Creates a new empty MenuItem object. 32 | """ 33 | return MenuItem( 34 | name="", 35 | description="", 36 | price=0.0, 37 | category="", 38 | ) 39 | 40 | def copy(self) -> MenuItem: 41 | """ 42 | Creates a copy of the MenuItem object. 43 | """ 44 | return copy.copy(self) 45 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Simple CRUD/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "level": "beginner", 3 | "summary": "Your own custom CRUD interface", 4 | "readyToRun": true, 5 | "onAppStart": "on_app_start", 6 | "supportsMobile": true 7 | } 8 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Simple CRUD/root_init.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | # 4 | from . import persistence 5 | 6 | # 7 | 8 | 9 | # 10 | def on_app_start(app: rio.App) -> None: 11 | """ 12 | A function that runs when the app is started. 13 | """ 14 | # Create a persistence instance. This class hides the gritty details of 15 | # database interaction from the app. 16 | pers = persistence.Persistence() 17 | 18 | # Now attach it to the session. This way, the persistence instance is 19 | # available to all components using `self.session[persistence.Persistence]` 20 | app.default_attachments.append(pers) 21 | 22 | 23 | # 24 | 25 | 26 | # 27 | def on_demo_session_start(sess: rio.Session) -> None: 28 | """ 29 | A function that runs when a new session is started. 30 | """ 31 | # Create a persistence instance. 32 | pers = persistence.Persistence() 33 | 34 | # Attach the persistence instance to the session. 35 | sess.attach(pers) 36 | 37 | 38 | # 39 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Simple CRUD/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Simple CRUD/thumbnail.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Tic-Tac-Toe/README.md: -------------------------------------------------------------------------------- 1 | This example contains a simple Tic-Tac-Toe game. The game is played on a 3x3 2 | grid, where two players take turns to place their marks. The first player to get 3 | three of their marks in a straight line wins the game. You know the rules! 4 | 5 | The game has a simple UI with a grid of buttons that represent the game board. 6 | When a player clicks on a button, the corresponding cell is marked with their 7 | symbol. The game checks for a winner after each move and displays a message when 8 | the game is over. 9 | 10 | ## Lessons 🎓 11 | 12 | In this example you can see how to: 13 | 14 | - Create and use custom components 15 | - Make components communicate via custom events 16 | 17 | ## Components 🧩 18 | 19 | This example only consists of two components: 20 | 21 | 1. `Field`: Represents one of the 9 fields on the board. Can contain an `X` or 22 | an `O`, or be empty. 23 | 2. `TicTacToePage`: Represents the entire 3x3 board. This is where the state of 24 | the board is stored and where the logic for detecting the winner of the game 25 | is located. 26 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Tic-Tac-Toe/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .field import Field as Field 2 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Tic-Tac-Toe/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "level": "beginner", 3 | "summary": "The classic game played by millions worldwide", 4 | "readyToRun": true, 5 | "supportsMobile": true 6 | } 7 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Tic-Tac-Toe/root_init.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Tic-Tac-Toe/root_init.py -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Tic-Tac-Toe/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Tic-Tac-Toe/thumbnail.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Todo App/README.md: -------------------------------------------------------------------------------- 1 | This example shows off a simple Todo app which allows you to create tasks that 2 | can later be marked as completed. 3 | 4 | ## Lessons 🎓 5 | 6 | In this example you can see how to: 7 | 8 | - Persistently save data across sessions 9 | - Make components communicate via custom events 10 | 11 | ## Components 🧩 12 | 13 | The example is composed of 3 main components: 14 | 15 | 1. `TodoItemComponent`: Displays the title and creation date of a todo item, and 16 | also comes with buttons that lets the user mark it as completed or delete it. 17 | 2. `NewTodoItemInput`: Allows the user to create a new todo item. 18 | 3. `TodoListPage`: Combines the 2 components above and adds all the logic 19 | necessary for saving/loading the todo list. 20 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Todo App/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .new_todo_item_input import NewTodoItemInput as NewTodoItemInput 2 | from .todo_item_component import TodoItemComponent as TodoItemComponent 3 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Todo App/data_models.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | import datetime 3 | 4 | import rio 5 | 6 | 7 | @dataclasses.dataclass 8 | class TodoItem: 9 | title: str 10 | creation_time: datetime.datetime 11 | completed: bool = False 12 | 13 | 14 | class TodoAppSettings(rio.UserSettings): 15 | todo_items: list[TodoItem] = [ 16 | TodoItem( 17 | title="Wake up (optional, but highly recommended)", 18 | creation_time=datetime.datetime.now(), 19 | ), 20 | TodoItem( 21 | title="Convince myself I'm a morning person (fake it till I make it)", 22 | creation_time=datetime.datetime.now(), 23 | ), 24 | TodoItem( 25 | title="Drink coffee (or legally change my name to 'Zombie #1')", 26 | creation_time=datetime.datetime.now(), 27 | ), 28 | TodoItem( 29 | title="Attempt to be productive (a.k.a. stare at my to-do list and panic)", 30 | creation_time=datetime.datetime.now(), 31 | ), 32 | TodoItem( 33 | title="Eat something healthy (or at least something that once looked at a vegetable)", 34 | creation_time=datetime.datetime.now(), 35 | ), 36 | TodoItem( 37 | title="Pretend to work while actually just Googling random facts (Did you know octopuses have three hearts?)", 38 | creation_time=datetime.datetime.now(), 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Todo App/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "level": "beginner", 3 | "summary": "A simple application that saves every user's Todo list", 4 | "defaultAttachments": ["data_models.TodoAppSettings()"], 5 | "readyToRun": true, 6 | "supportsMobile": true 7 | } 8 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Todo App/root_init.py: -------------------------------------------------------------------------------- 1 | import rio 2 | 3 | # 4 | from . import data_models 5 | 6 | # 7 | 8 | # Make sure ruff doesn't remove unused imports 9 | # Create the Rio app 10 | app = rio.App( 11 | name="todo", 12 | default_attachments=[data_models.TodoAppSettings()], 13 | ) 14 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/project-template-Todo App/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/project-template-Todo App/thumbnail.png -------------------------------------------------------------------------------- /rio/snippets/snippet-files/tutorial-tic-tac-toe-part-2/components/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-2/components/__init__.py -------------------------------------------------------------------------------- /rio/snippets/snippet-files/tutorial-tic-tac-toe-part-2/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-2/pages/__init__.py -------------------------------------------------------------------------------- /rio/snippets/snippet-files/tutorial-tic-tac-toe-part-2/pages/tic_tac_toe_page.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing as t 4 | 5 | import rio 6 | 7 | 8 | # 9 | #
10 | @rio.page( 11 | name="Tic Tac Toe", 12 | url_segment="", 13 | ) 14 | class TicTacToePage(rio.Component): 15 | #
16 | # 17 | fields: list[t.Literal["X", "O", ""]] = [""] * 9 18 | # 19 | 20 | # 21 | def build(self) -> rio.Component: 22 | # Spawn components for the fields 23 | field_components: list[rio.Component] = [] 24 | 25 | for index, field in enumerate(self.fields): 26 | field_components.append(rio.Text(f"Field {index}")) 27 | 28 | # Arrange all components in a grid 29 | return rio.Grid( 30 | field_components[0:3], 31 | field_components[3:6], 32 | field_components[6:9], 33 | row_spacing=1, 34 | column_spacing=1, 35 | align_x=0.5, 36 | ) 37 | 38 | # 39 | 40 | 41 | #
42 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/tutorial-tic-tac-toe-part-3/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .field import Field as Field 2 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/tutorial-tic-tac-toe-part-3/components/field.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing as t 4 | 5 | import rio 6 | 7 | 8 | # 9 | class Field(rio.Component): 10 | value: t.Literal["X", "O", ""] 11 | 12 | on_press: rio.EventHandler[[]] = None 13 | 14 | def build(self) -> rio.Component: 15 | # If the field is empty, allow the player to press on it. Since buttons 16 | # would look out of place here, cards are a nice alternative. 17 | if self.value == "": 18 | return rio.Card( 19 | content=rio.Spacer( 20 | min_width=3, 21 | min_height=3, 22 | ), 23 | on_press=self.on_press, 24 | ) 25 | 26 | # For fields that already contain an X or O, show the respective icon. 27 | # Also vary the color based on the player. 28 | if self.value == "X": 29 | color = rio.Color.RED 30 | icon = "material/close" 31 | else: 32 | color = rio.Color.BLUE 33 | icon = "material/circle" 34 | 35 | return rio.Icon( 36 | icon=icon, 37 | fill=color, 38 | min_width=3, 39 | min_height=3, 40 | ) 41 | 42 | 43 | # 44 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/tutorial-tic-tac-toe-part-3/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-3/pages/__init__.py -------------------------------------------------------------------------------- /rio/snippets/snippet-files/tutorial-tic-tac-toe-part-3/pages/tic_tac_toe_page.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing as t 4 | 5 | import rio 6 | 7 | from .. import components as comps 8 | 9 | 10 | # 11 | @rio.page( 12 | name="Tic Tac Toe", 13 | url_segment="", 14 | ) 15 | class TicTacToePage(rio.Component): 16 | # The contents of all fields. Each field can contain an X, an O, or be 17 | # empty. The initial state is an empty board. 18 | fields: list[t.Literal["X", "O", ""]] = [""] * 9 19 | 20 | def build(self) -> rio.Component: 21 | # Spawn components for the fields 22 | field_components: list[rio.Component] = [] 23 | 24 | for index, field in enumerate(self.fields): 25 | field_components.append( 26 | comps.Field( 27 | value=field, 28 | ) 29 | ) 30 | 31 | # Arrange all components in a grid 32 | return rio.Grid( 33 | field_components[0:3], 34 | field_components[3:6], 35 | field_components[6:9], 36 | row_spacing=1, 37 | column_spacing=1, 38 | align_x=0.5, 39 | ) 40 | 41 | 42 | # 43 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/tutorial-tic-tac-toe-part-4/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .field import Field as Field 2 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/tutorial-tic-tac-toe-part-4/components/field.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing as t 4 | 5 | import rio 6 | 7 | 8 | # 9 | class Field(rio.Component): 10 | value: t.Literal["X", "O", ""] 11 | 12 | on_press: rio.EventHandler[[]] = None 13 | 14 | def build(self) -> rio.Component: 15 | # If the field is empty, allow the player to press on it. Since buttons 16 | # would look out of place here, cards are a nice alternative. 17 | if self.value == "": 18 | return rio.Card( 19 | content=rio.Spacer( 20 | min_width=3, 21 | min_height=3, 22 | ), 23 | on_press=self.on_press, 24 | ) 25 | 26 | # For fields that already contain an X or O, show the respective icon. 27 | # Also vary the color based on the player. 28 | if self.value == "X": 29 | color = rio.Color.RED 30 | icon = "material/close" 31 | else: 32 | color = rio.Color.BLUE 33 | icon = "material/circle" 34 | 35 | return rio.Icon( 36 | icon=icon, 37 | fill=color, 38 | min_width=3, 39 | min_height=3, 40 | ) 41 | 42 | 43 | # 44 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/tutorial-tic-tac-toe-part-4/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-4/pages/__init__.py -------------------------------------------------------------------------------- /rio/snippets/snippet-files/tutorial-tic-tac-toe-part-5/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .field import Field as Field 2 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/tutorial-tic-tac-toe-part-5/components/field.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing as t 4 | 5 | import rio 6 | 7 | 8 | # 9 | class Field(rio.Component): 10 | value: t.Literal["X", "O", ""] 11 | dim: bool 12 | 13 | on_press: rio.EventHandler[[]] = None 14 | 15 | def build(self) -> rio.Component: 16 | # If the field is empty, allow the player to press on it. Since buttons 17 | # would look out of place here, cards are a nice alternative. 18 | if self.value == "": 19 | return rio.Card( 20 | content=rio.Spacer( 21 | min_width=3, 22 | min_height=3, 23 | ), 24 | on_press=self.on_press, 25 | ) 26 | 27 | # For fields that already contain an X or O, show the respective icon. 28 | # Also vary the color based on the player. 29 | if self.value == "X": 30 | color = rio.Color.RED 31 | icon = "material/close" 32 | else: 33 | color = rio.Color.BLUE 34 | icon = "material/circle" 35 | 36 | # If a player has won, and this field isn't part of the winning 37 | # combination, dim it. 38 | if self.dim: 39 | color = color.replace(opacity=0.2) 40 | 41 | return rio.Icon( 42 | icon=icon, 43 | fill=color, 44 | min_width=3, 45 | min_height=3, 46 | ) 47 | 48 | 49 | # 50 | -------------------------------------------------------------------------------- /rio/snippets/snippet-files/tutorial-tic-tac-toe-part-5/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/rio/snippets/snippet-files/tutorial-tic-tac-toe-part-5/pages/__init__.py -------------------------------------------------------------------------------- /rio/testing/__init__.py: -------------------------------------------------------------------------------- 1 | from .base_client import * 2 | from .browser_client import * 3 | from .dummy_client import * 4 | -------------------------------------------------------------------------------- /rio/transports/__init__.py: -------------------------------------------------------------------------------- 1 | from .abstract_transport import * 2 | from .fastapi_websocket_transport import * 3 | from .message_recorder_transport import * 4 | from .multi_transport import * 5 | -------------------------------------------------------------------------------- /rio/transports/multi_transport.py: -------------------------------------------------------------------------------- 1 | from .abstract_transport import AbstractTransport, TransportClosed 2 | 3 | __all__ = ["MultiTransport"] 4 | 5 | 6 | class MultiTransport(AbstractTransport): 7 | """ 8 | Sends outgoing messages to multiple transports. 9 | """ 10 | 11 | def __init__( 12 | self, 13 | main_transport: AbstractTransport, 14 | *extra_transports: AbstractTransport, 15 | ) -> None: 16 | super().__init__() 17 | 18 | assert not main_transport.is_closed 19 | 20 | self._main_transport = main_transport 21 | self._extra_transports = extra_transports 22 | 23 | async def send_if_possible(self, message: str, /) -> None: 24 | await self._main_transport.send_if_possible(message) 25 | 26 | for transport in self._extra_transports: 27 | await transport.send_if_possible(message) 28 | 29 | if self._main_transport.is_closed: 30 | await self.close() 31 | 32 | async def receive(self) -> str: 33 | try: 34 | return await self._main_transport.receive() 35 | except TransportClosed: 36 | await self.close() 37 | raise 38 | 39 | async def close(self) -> None: 40 | await self._main_transport.close() 41 | 42 | for transport in self._extra_transports: 43 | await transport.close() 44 | 45 | self.closed_event.set() 46 | -------------------------------------------------------------------------------- /rio/warnings.py: -------------------------------------------------------------------------------- 1 | class RioDeprecationWarning(DeprecationWarning): 2 | """ 3 | The user used functionality that has been deprecated. 4 | """ 5 | 6 | 7 | class RioPotentialMistakeWarning(Warning): 8 | """ 9 | The user did something sketchy that's probably a bad idea. 10 | """ 11 | -------------------------------------------------------------------------------- /scripts/build.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | import typing as t 4 | from pathlib import Path 5 | 6 | import rio.utils 7 | 8 | PROJECT_ROOT_DIR = rio.utils.PROJECT_ROOT_DIR 9 | INPUT_DIR = PROJECT_ROOT_DIR / "frontend" 10 | OUTPUT_DIR = PROJECT_ROOT_DIR / "rio" / "frontend files" 11 | ASSETS_DIR = OUTPUT_DIR / "assets" 12 | 13 | 14 | def main() -> None: 15 | if "--release" in sys.argv: 16 | mode = "release" 17 | else: 18 | mode = "dev" 19 | 20 | build_frontend(mode=mode) 21 | 22 | 23 | def build_frontend(mode: t.Literal["dev", "release"]) -> None: 24 | # npx(*"tsc --noEmit".split()) # type check 25 | 26 | if mode == "release": 27 | extra_args = [] 28 | else: 29 | extra_args = ["--mode", "development", "--minify", "false"] 30 | 31 | # Build with vite 32 | npx( 33 | "vite", 34 | "build", 35 | INPUT_DIR, 36 | "--outDir", 37 | OUTPUT_DIR, 38 | "--config", 39 | PROJECT_ROOT_DIR / "vite.config.mjs", 40 | # The real base URL is only known at runtime, and may differ for each 41 | # session. This string here is an intentionally easy-to-replace 42 | # placeholder. 43 | "--base", 44 | "/rio-base-url-placeholder/rio/frontend/", 45 | "--emptyOutDir", 46 | *extra_args, 47 | ) 48 | 49 | 50 | def npx(*args: str | Path) -> None: 51 | subprocess.run( 52 | ["npx", *map(str, args)], 53 | check=True, 54 | shell=sys.platform == "win32", 55 | ) 56 | 57 | 58 | if __name__ == "__main__": 59 | main() 60 | -------------------------------------------------------------------------------- /scripts/build_rio_api_client.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import subprocess 3 | from pathlib import Path 4 | 5 | PROJECT_ROOT_DIR = Path(__file__).parent.parent.resolve() 6 | assert (PROJECT_ROOT_DIR / "pyproject.toml").exists() 7 | 8 | 9 | def main() -> None: 10 | output_parent_dir = PROJECT_ROOT_DIR / "rio" / "cli" 11 | output_tmp_dir = output_parent_dir / "rio_api_tmp" 12 | output_final_dir = output_parent_dir / "rio_api_client" 13 | 14 | # Wipe any files from previous runs 15 | try: 16 | shutil.rmtree(output_tmp_dir) 17 | except FileNotFoundError: 18 | pass 19 | 20 | try: 21 | shutil.rmtree(output_final_dir) 22 | except FileNotFoundError: 23 | pass 24 | 25 | # Generate a HTTP wrapper for Rio's API 26 | subprocess.run( 27 | [ 28 | "openapi-python-client", 29 | "generate", 30 | "--url", 31 | "http://localhost:8001/openapi.json", 32 | "--output-path", 33 | str(output_tmp_dir), 34 | ], 35 | check=True, 36 | ) 37 | 38 | # The command above creates an entire huge project. Rip out just the Python 39 | # module 40 | shutil.move( 41 | output_tmp_dir / "fast_api_client", 42 | output_final_dir, 43 | ) 44 | shutil.rmtree(output_tmp_dir) 45 | 46 | 47 | if __name__ == "__main__": 48 | main() 49 | -------------------------------------------------------------------------------- /scripts/cloc.sh: -------------------------------------------------------------------------------- 1 | cloc --exclude-dir=node_modules,.venv,.git,generated,package-lock.json . 2 | 3 | 4 | -------------------------------------------------------------------------------- /scripts/code_coverage.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import sys 3 | import webbrowser 4 | from pathlib import Path 5 | 6 | import coverage 7 | import pytest 8 | 9 | project_dir = Path(__file__).parent.parent 10 | html_dir = project_dir / "htmlcov" 11 | 12 | # Remove old files 13 | cov_file_path = project_dir / ".coverage" 14 | try: 15 | cov_file_path.unlink() 16 | except FileNotFoundError: 17 | pass 18 | try: 19 | shutil.rmtree(html_dir) 20 | except FileNotFoundError: 21 | pass 22 | 23 | # Run unit tests with coverage 24 | cov = coverage.Coverage(branch=True, source=["rio"]) 25 | cov.start() 26 | 27 | pytest.main(["tests"]) 28 | 29 | cov.stop() 30 | cov.save() 31 | 32 | # Generate the HTML report 33 | cov.html_report() 34 | 35 | if "--no-open" not in sys.argv: 36 | html_path = html_dir / "index.html" 37 | webbrowser.open(html_path.as_uri()) 38 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_app_build.py: -------------------------------------------------------------------------------- 1 | import rio.testing 2 | 3 | 4 | async def test_fundamental_container_as_root() -> None: 5 | def build() -> rio.Component: 6 | return rio.Row(rio.Text("Hello")) 7 | 8 | async with rio.testing.DummyClient(build) as test_client: 9 | row_component = test_client.get_component(rio.Row) 10 | text_component = test_client.get_component(rio.Text) 11 | 12 | assert { 13 | row_component, 14 | text_component, 15 | } <= test_client._last_updated_components 16 | -------------------------------------------------------------------------------- /tests/test_arequests.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import pytest 4 | 5 | import rio.arequests as arequests 6 | 7 | 8 | def test_http_response_invalid_json() -> None: 9 | response = arequests.HttpResponse( 10 | status_code=200, 11 | headers={}, 12 | content=b"invalid json", 13 | ) 14 | 15 | with pytest.raises(json.JSONDecodeError, match="Expecting value"): 16 | response.json() 17 | 18 | 19 | def test_http_response_invalid_utf8() -> None: 20 | response = arequests.HttpResponse( 21 | status_code=200, 22 | headers={}, 23 | content=b"\xff", 24 | ) 25 | 26 | with pytest.raises(json.JSONDecodeError, match="UTF-8"): 27 | response.json() 28 | 29 | 30 | def test_request() -> None: 31 | response = arequests.request_sync( 32 | "get", 33 | "https://postman-echo.com/get", 34 | json={"foo": "bar"}, 35 | ) 36 | 37 | assert response.status_code == 200 38 | assert response.headers["content-type"] == "application/json; charset=utf-8" 39 | 40 | response_json = response.json() 41 | 42 | assert response_json["headers"]["user-agent"] == "rio.arequests/0.1" 43 | assert response_json["headers"]["host"] == "postman-echo.com" 44 | assert response_json["headers"]["content-type"] == "application/json" 45 | assert response_json["headers"]["content-length"] == "14" 46 | assert response_json["args"] == {"foo": "bar"} 47 | -------------------------------------------------------------------------------- /tests/test_custom_components.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | 3 | import rio.testing 4 | 5 | 6 | async def test_fields_with_defaults(): 7 | class TestComponent(rio.Component): 8 | foo: list[str] = dataclasses.field(init=False, default_factory=list) 9 | bar: int = dataclasses.field(init=False, default=5) 10 | 11 | def build(self) -> rio.Component: 12 | raise NotImplementedError() 13 | 14 | async with rio.testing.DummyClient(TestComponent) as test_client: 15 | component = test_client.get_component(TestComponent) 16 | assert component.foo == [] 17 | assert component.bar == 5 18 | 19 | 20 | async def test_post_init(): 21 | class TestComponent(rio.Component): 22 | post_init_called: bool = False 23 | 24 | def __post_init__(self): 25 | self.post_init_called = True 26 | 27 | def build(self) -> rio.Component: 28 | return rio.Text("hi") 29 | 30 | async with rio.testing.DummyClient(TestComponent) as test_client: 31 | root_component = test_client.get_component(TestComponent) 32 | assert root_component.post_init_called 33 | -------------------------------------------------------------------------------- /tests/test_frontend/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from rio.testing import prepare_browser_client 4 | 5 | 6 | # Somewhat counter-intuitively, `scope='module'` would be incorrect here. That 7 | # would execute the fixture once for each submodule. Scoping it to the session 8 | # instead does exactly what we want: It runs only once, and only if it's needed. 9 | @pytest.fixture(scope="session", autouse=True) 10 | @pytest.mark.async_timeout(60) # Yes, fixtures can time out 11 | async def manage_server(): 12 | async with prepare_browser_client(): 13 | yield 14 | -------------------------------------------------------------------------------- /tests/test_frontend/test_layouting/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/tests/test_frontend/test_layouting/__init__.py -------------------------------------------------------------------------------- /tests/test_frontend/test_layouting/test_flow_container.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import rio 4 | from tests.utils.layouting import verify_layout 5 | 6 | 7 | @pytest.mark.parametrize( 8 | "justify", 9 | [ 10 | "left", 11 | "right", 12 | "center", 13 | "justified", 14 | "grow", 15 | ], 16 | ) 17 | async def test_flow_container_layout(justify: str) -> None: 18 | await verify_layout( 19 | lambda: rio.FlowContainer( 20 | rio.Text("foo", min_width=5), 21 | rio.Text("bar", min_width=10), 22 | rio.Text("qux", min_width=4), 23 | column_spacing=3, 24 | row_spacing=2, 25 | justify=justify, # type: ignore 26 | min_width=20, 27 | align_x=0, 28 | ) 29 | ) 30 | -------------------------------------------------------------------------------- /tests/test_frontend/test_layouting/test_popup.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import rio 4 | from tests.utils.layouting import BrowserClient 5 | 6 | 7 | async def test_popup_moves_with_anchor(): 8 | def build(): 9 | return rio.Column( 10 | rio.Webview('
'), 11 | rio.Popup( 12 | anchor=rio.Text("anchor", min_width=10, min_height=10), 13 | content=rio.Text("content"), 14 | position="center", 15 | is_open=True, 16 | ), 17 | ) 18 | 19 | async def get_content_y_coordinate(): 20 | return await client.execute_js(""" 21 | document.querySelector('.rio-popup-content').getBoundingClientRect().top; 22 | """) 23 | 24 | async with BrowserClient(build) as client: 25 | y1 = await get_content_y_coordinate() 26 | 27 | # Move the anchor down by making the element above it taller 28 | await client.execute_js( 29 | "document.querySelector('#expander').style.height = '100px';" 30 | ) 31 | await asyncio.sleep(0.5) 32 | 33 | # Check if the popup moved down 34 | y2 = await get_content_y_coordinate() 35 | 36 | assert y2 > y1 37 | -------------------------------------------------------------------------------- /tests/test_frontend/test_layouting/test_scroll_container.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | import pytest 4 | 5 | import rio 6 | from tests.utils.layouting import verify_layout 7 | 8 | 9 | @pytest.mark.parametrize( 10 | "scroll_x,scroll_y", 11 | [ 12 | ("never", "auto"), 13 | ("auto", "never"), 14 | ("auto", "auto"), 15 | ], 16 | ) 17 | async def test_scrolling( 18 | scroll_x: t.Literal["never", "always", "auto"], 19 | scroll_y: t.Literal["never", "always", "auto"], 20 | ) -> None: 21 | await verify_layout( 22 | lambda: rio.ScrollContainer( 23 | rio.Text("hi", min_width=30, min_height=30), 24 | scroll_x=scroll_x, 25 | scroll_y=scroll_y, 26 | min_width=20, 27 | min_height=20, 28 | align_x=0.5, 29 | align_y=0.5, 30 | ) 31 | ) 32 | -------------------------------------------------------------------------------- /tests/test_frontend/test_layouting/test_stack.py: -------------------------------------------------------------------------------- 1 | import rio 2 | from tests.utils.layouting import verify_layout 3 | 4 | 5 | async def test_stack() -> None: 6 | """ 7 | All children in stacks should be the same size. 8 | """ 9 | layouter = await verify_layout( 10 | lambda: rio.Stack( 11 | rio.Text("Small", key="small_text", min_width=10, min_height=20), 12 | rio.Text("Large", key="large_text", min_width=30, min_height=40), 13 | align_x=0, 14 | align_y=0, 15 | ) 16 | ) 17 | 18 | small_layout = layouter.get_layout_by_key("small_text") 19 | 20 | assert small_layout.left_in_viewport_inner == 0 21 | assert small_layout.top_in_viewport_inner == 0 22 | 23 | assert small_layout.allocated_inner_width == 30 24 | assert small_layout.allocated_inner_height == 40 25 | 26 | large_layout = layouter.get_layout_by_key("large_text") 27 | 28 | assert large_layout.left_in_viewport_inner == 0 29 | assert large_layout.top_in_viewport_inner == 0 30 | 31 | assert large_layout.allocated_inner_width == 30 32 | assert large_layout.allocated_inner_height == 40 33 | -------------------------------------------------------------------------------- /tests/test_frontend/test_layouting/test_text.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import rio 4 | from tests.utils.layouting import verify_layout 5 | 6 | 7 | @pytest.mark.parametrize( 8 | "text", 9 | [ 10 | "", 11 | "short-text", 12 | "-".join(["long-text"] * 100), 13 | ], 14 | ) 15 | async def test_single_component(text: str) -> None: 16 | """ 17 | Just one component - this should fill the whole screen. 18 | """ 19 | await verify_layout(lambda: rio.Text(text)) 20 | 21 | 22 | async def test_ellipsized_text() -> None: 23 | layouter = await verify_layout( 24 | lambda: rio.Text( 25 | "My natural size should become 0", 26 | overflow="ellipsize", 27 | align_x=0, 28 | key="text", 29 | ) 30 | ) 31 | 32 | layout = layouter.get_layout_by_key("text") 33 | 34 | assert layout.natural_width == 0 35 | -------------------------------------------------------------------------------- /tests/test_page_views.py: -------------------------------------------------------------------------------- 1 | import rio.testing 2 | 3 | 4 | async def test_one_page_view() -> None: 5 | def build(): 6 | return rio.Column( 7 | rio.Text("Welcome", style="heading1"), 8 | rio.PageView(), 9 | ) 10 | 11 | app = rio.App( 12 | build=build, 13 | pages=[ 14 | rio.ComponentPage( 15 | name="Home", 16 | url_segment="", 17 | build=rio.Spacer, 18 | ), 19 | ], 20 | ) 21 | 22 | async with rio.testing.DummyClient(app) as test_client: 23 | # Make sure the Spacer (which is located on the home page) exists 24 | test_client.get_component(rio.Spacer) 25 | 26 | 27 | async def test_nested_page_views() -> None: 28 | def build(): 29 | return rio.Column( 30 | rio.Text("Welcome", style="heading1"), 31 | rio.PageView(), 32 | ) 33 | 34 | app = rio.App( 35 | build=build, 36 | pages=[ 37 | rio.ComponentPage( 38 | name="Home", 39 | url_segment="", 40 | build=rio.PageView, 41 | children=[ 42 | rio.ComponentPage( 43 | name="Inner Home", 44 | url_segment="", 45 | build=rio.Spacer, 46 | ), 47 | ], 48 | ), 49 | ], 50 | ) 51 | 52 | async with rio.testing.DummyClient(app) as test_client: 53 | # Make sure the Spacer (which is located on the innermost page) exists 54 | test_client.get_component(rio.Spacer) 55 | -------------------------------------------------------------------------------- /tests/test_session.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import rio.testing 4 | 5 | 6 | async def test_client_attachments(): 7 | async with rio.testing.DummyClient() as test_client: 8 | session = test_client.session 9 | 10 | list1 = ["foo", "bar"] 11 | list2 = [] 12 | 13 | session.attach(list1) 14 | assert session[list] is list1 15 | 16 | session.attach(list2) 17 | assert session[list] is list2 18 | 19 | 20 | async def test_access_nonexistent_session_attachment(): 21 | async with rio.testing.DummyClient() as test_client: 22 | with pytest.raises(KeyError): 23 | test_client.session[list] 24 | 25 | 26 | async def test_default_attachments(): 27 | class Settings(rio.UserSettings): 28 | foo: int 29 | 30 | dict_attachment = {"foo": "bar"} 31 | settings_attachment = Settings(3) 32 | 33 | async with rio.testing.DummyClient( 34 | default_attachments=[dict_attachment, settings_attachment] 35 | ) as test_client: 36 | session = test_client.session 37 | 38 | # Default attachments shouldn't be copied, unless they're UserSettings 39 | assert session[dict] is dict_attachment 40 | 41 | assert session[Settings] is not settings_attachment 42 | assert session[Settings]._equals(settings_attachment) 43 | -------------------------------------------------------------------------------- /tests/test_testing_tools.py: -------------------------------------------------------------------------------- 1 | import rio.testing 2 | 3 | 4 | async def test_active_page_url(): 5 | url = "foo/bar" 6 | 7 | async with rio.testing.DummyClient(active_url=url) as test_client: 8 | assert test_client.session.active_page_url.path == "/" + url 9 | 10 | 11 | async def test_crashed_build_functions_are_tracked(): 12 | def build() -> rio.Component: 13 | return 3 # type: ignore 14 | 15 | async with rio.testing.DummyClient(build) as test_client: 16 | assert len(test_client.crashed_build_functions) == 1 17 | 18 | 19 | async def test_rebuild_resets_crashed_build_functions(): 20 | class CrashingComponent(rio.Component): 21 | fail: bool = True 22 | 23 | def build(self) -> rio.Component: 24 | if self.fail: 25 | raise RuntimeError 26 | else: 27 | return rio.Text("hi") 28 | 29 | async with rio.testing.DummyClient(CrashingComponent) as test_client: 30 | assert len(test_client.crashed_build_functions) == 1 31 | 32 | crashing_component = test_client.get_component(CrashingComponent) 33 | crashing_component.fail = False 34 | 35 | await test_client.wait_for_refresh() 36 | 37 | assert not test_client.crashed_build_functions 38 | -------------------------------------------------------------------------------- /tests/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rio-labs/rio/1190ca427998d5ec6932c0810175c48da5957cbb/tests/utils/__init__.py -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "es2022", 5 | "lib": ["es2021", "dom", "dom.iterable"], 6 | "strict": false, // TODO: fix and enable strict checking 7 | "moduleResolution": "node", 8 | "skipLibCheck": true, 9 | "useDefineForClassFields": false 10 | }, 11 | "include": ["frontend/code/**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /vite.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { compression } from "vite-plugin-compression2"; 3 | 4 | export default defineConfig({ 5 | build: { 6 | rollupOptions: { 7 | external: [/^\/rio\/asset\/.*/], 8 | }, 9 | }, 10 | plugins: [ 11 | compression({ 12 | exclude: /.*index\.html$/, 13 | deleteOriginalAssets: true, 14 | }), 15 | ], 16 | }); 17 | --------------------------------------------------------------------------------