diff --git a/clock/assets/favicon.ico b/clock/assets/favicon.ico deleted file mode 100644 index 166ae995..00000000 Binary files a/clock/assets/favicon.ico and /dev/null differ diff --git a/clock/clock/__init__.py b/clock/clock/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/clock/requirements.txt b/clock/requirements.txt deleted file mode 100644 index 43d8eeda..00000000 --- a/clock/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -reflex>=0.5.6 -pytz==2022.7.1 -reflex-chakra>=0.6.0a7 diff --git a/clock/rxconfig.py b/clock/rxconfig.py deleted file mode 100644 index c545ccdf..00000000 --- a/clock/rxconfig.py +++ /dev/null @@ -1,5 +0,0 @@ -import reflex as rx - -config = rx.Config( - app_name="clock", -) diff --git a/json-tree/assets/favicon.ico b/json-tree/assets/favicon.ico deleted file mode 100644 index 166ae995..00000000 Binary files a/json-tree/assets/favicon.ico and /dev/null differ diff --git a/json-tree/json_tree/__init__.py b/json-tree/json_tree/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/json-tree/requirements.txt b/json-tree/requirements.txt deleted file mode 100644 index 1de84caa..00000000 --- a/json-tree/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -reflex>=0.6.5 diff --git a/json-tree/rxconfig.py b/json-tree/rxconfig.py deleted file mode 100644 index 00369abe..00000000 --- a/json-tree/rxconfig.py +++ /dev/null @@ -1,5 +0,0 @@ -import reflex as rx - -config = rx.Config( - app_name="json_tree", -) \ No newline at end of file diff --git a/lorem-stream/.gitignore b/lorem-stream/.gitignore deleted file mode 100644 index e97bb370..00000000 --- a/lorem-stream/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -*.db -*.py[cod] -.web -__pycache__/ diff --git a/lorem-stream/README.md b/lorem-stream/README.md deleted file mode 100644 index 846420b2..00000000 --- a/lorem-stream/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# `lorem_stream` - -This example uses background tasks to concurrently generate and stream lorem -text, simulating how an app might make multiple API calls and display the -results as they become available. - -The state keeps track of several dicts that are keyed on the task id: - - * `running: bool` if the task should keep processing data - * `progress: int` how many iterations the task has completed - * `end_at: int` the task stops after this many iterations - * `text: str` the actual generated text - -## `LoremState.stream_text` - -This is the background task that does most of the work. When starting, if a -`task_id` is not provided, it assigns the next available task id to itself; -otherwise it will assume the values of the given `task_id`. - -The task then proceeds to iterate a random number of times, generating 3 lorem -words on each iteration. - -## UI - -The page initially only shows the "New Task" button. Each time it is clicked, a -new `stream_text` task is started. - -The tasks are presented as a grid of cards, each of which shows the progress of -the task, a play/pause/restart button, and a kill/delete button. Below the -controls, the text streams as it is available. - - -https://github.com/reflex-dev/reflex-examples/assets/1524005/09c832ff-ecbd-4a9d-a8a5-67779c673045 - - diff --git a/lorem-stream/assets/favicon.ico b/lorem-stream/assets/favicon.ico deleted file mode 100644 index 166ae995..00000000 Binary files a/lorem-stream/assets/favicon.ico and /dev/null differ diff --git a/lorem-stream/lorem_stream/__init__.py b/lorem-stream/lorem_stream/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/lorem-stream/requirements.txt b/lorem-stream/requirements.txt deleted file mode 100644 index 1714a004..00000000 --- a/lorem-stream/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -reflex>=0.4.8 -lorem_text>=2.1 -reflex-chakra>=0.6.0a7 diff --git a/lorem-stream/rxconfig.py b/lorem-stream/rxconfig.py deleted file mode 100644 index 2243281f..00000000 --- a/lorem-stream/rxconfig.py +++ /dev/null @@ -1,5 +0,0 @@ -import reflex as rx - -config = rx.Config( - app_name="lorem_stream", -) \ No newline at end of file diff --git a/clock/.gitignore b/playground/.gitignore similarity index 66% rename from clock/.gitignore rename to playground/.gitignore index e97bb370..aae8b042 100644 --- a/clock/.gitignore +++ b/playground/.gitignore @@ -1,4 +1,5 @@ -*.db -*.py[cod] .web __pycache__/ +assets/external/ +*.db +*.py[cod] diff --git a/playground/README.md b/playground/README.md new file mode 100644 index 00000000..5b160b1e --- /dev/null +++ b/playground/README.md @@ -0,0 +1,3 @@ +# Reflex Example Playground + +A collection of small toy examples diff --git a/playground/lint.sh b/playground/lint.sh new file mode 100755 index 00000000..cf497973 --- /dev/null +++ b/playground/lint.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -xeuo pipefail + +ruff check --fix playground +ruff format playground +pyright playground diff --git a/playground/playground/__init__.py b/playground/playground/__init__.py new file mode 100644 index 00000000..acf7d103 --- /dev/null +++ b/playground/playground/__init__.py @@ -0,0 +1,7 @@ +from . import clock, json_tree, lorem_stream + +__all__ = [ + "clock", + "json_tree", + "lorem_stream", +] diff --git a/playground/playground/clock/__init__.py b/playground/playground/clock/__init__.py new file mode 100644 index 00000000..3e70d27e --- /dev/null +++ b/playground/playground/clock/__init__.py @@ -0,0 +1,8 @@ +from ..common import demo +from .clock import theme_ui + +demo( + route="/clock", + title="Clock", + description="A classic Reflex toy example, showing an analog and digital clock", +)(theme_ui) diff --git a/clock/clock/clock.py b/playground/playground/clock/clock.py similarity index 62% rename from clock/clock/clock.py rename to playground/playground/clock/clock.py index 35ed786f..7bcfe1cb 100644 --- a/clock/clock/clock.py +++ b/playground/playground/clock/clock.py @@ -1,15 +1,11 @@ """A Reflex example of a analog clock in Radix.""" import asyncio +import dataclasses from datetime import datetime, timezone -from typing import Any - -import reflex as rx -import reflex_chakra as rc -from reflex.components.radix.themes import theme import pytz - +import reflex as rx # The supported time zones. TIMEZONES = [ @@ -24,7 +20,7 @@ DEFAULT_ZONE = TIMEZONES[-2] -def rotate(degrees: int) -> str: +def rotate(degrees: float) -> str: """CSS to rotate a clock hand. Args: @@ -36,6 +32,31 @@ def rotate(degrees: int) -> str: return f"rotate({degrees}deg)" +@dataclasses.dataclass(frozen=True) +class TimeInfo: + """The current time info.""" + + hour: int + meridiem: str + minute_display: str + second_display: str + hour_rotation: str + minute_rotation: str + second_rotation: str + + @classmethod + def from_datetime(cls, dt) -> "TimeInfo": + return cls( + hour=dt.hour if dt.hour <= 12 else dt.hour % 12, + meridiem="AM" if dt.hour < 12 else "PM", + minute_display=f"{dt.minute:02}", + second_display=f"{dt.second:02}", + hour_rotation=rotate(dt.hour * 30 - 90), + minute_rotation=rotate(dt.minute * 0.0167 * 360 - 90), + second_rotation=rotate(dt.second * 0.0167 * 360 - 90), + ) + + class State(rx.State): """The app state.""" @@ -62,7 +83,7 @@ def valid_zone(self) -> str: return self.zone @rx.var(cache=True) - def time_info(self) -> dict[str, Any]: + def time_info(self) -> TimeInfo: """Get the current time info. This can also be done as several computed vars, but this is more concise. @@ -70,18 +91,9 @@ def time_info(self) -> dict[str, Any]: Returns: A dictionary of the current time info. """ - now = self._now.astimezone(pytz.timezone(self.valid_zone)) - return { - "hour": now.hour if now.hour <= 12 else now.hour % 12, - "minute": now.minute, - "second": now.second, - "meridiem": "AM" if now.hour < 12 else "PM", - "minute_display": f"{now.minute:02}", - "second_display": f"{now.second:02}", - "hour_rotation": rotate(now.hour * 30 - 90), - "minute_rotation": rotate(now.minute * 0.0167 * 360 - 90), - "second_rotation": rotate(now.second * 0.0167 * 360 - 90), - } + return TimeInfo.from_datetime( + self._now.astimezone(pytz.timezone(self.valid_zone)) + ) def on_load(self): """Switch the clock off when the page refreshes.""" @@ -92,7 +104,7 @@ def refresh(self): """Refresh the clock.""" self._now = datetime.now(timezone.utc) - @rx.background + @rx.event(background=True) async def tick(self): """Update the clock every second.""" while self.running: @@ -116,7 +128,7 @@ def flip_switch(self, running: bool): return State.tick -def clock_hand(rotation: str, color: str, length: str) -> rx.Component: +def clock_hand(rotation: str, color: str | rx.Color, length: str) -> rx.Component: """Create a clock hand. Args: @@ -132,52 +144,54 @@ def clock_hand(rotation: str, color: str, length: str) -> rx.Component: width=f"{length}em", position="absolute", border_style="solid", - border_width="4px", - border_image=f"linear-gradient(to right, rgb(250,250,250) 50%, {color} 100%) 0 0 100% 0", + border_width="2px", + border_image=f"linear-gradient(to right, {rx.color('accent', 1)}, 50%, {color} 100%) 2", z_index=0, ) def analog_clock() -> rx.Component: """Create the analog clock.""" - return rc.circle( + return rx.center( # The inner circle. - rc.circle( - width="1em", - height="1em", + rx.center( + width="0.75em", + height="0.75em", + border_radius="1em", border_width="thick", - border_color="#43464B", + border_color=rx.color("gray", 10), z_index=1, ), # The clock hands. - clock_hand(State.time_info["hour_rotation"], "black", "16"), - clock_hand(State.time_info["minute_rotation"], "red", "18"), - clock_hand(State.time_info["second_rotation"], "blue", "19"), + clock_hand(State.time_info.hour_rotation, rx.color("accent", 8), "12"), + clock_hand(State.time_info.minute_rotation, rx.color("red", 8), "16"), + clock_hand(State.time_info.second_rotation, rx.color("blue", 8), "19"), border_width="thick", - border_color="#43464B", + border_color=rx.color("gray", 10), + border_radius="25em", width="25em", height="25em", - bg="rgb(250,250,250)", - box_shadow="dark-lg", + background_color=rx.color("accent", 1), + box_shadow="0 8px 16px rgba(0, 0, 0, 0.5)", ) def digital_clock() -> rx.Component: """Create the digital clock.""" return rx.hstack( - rx.heading(State.time_info["hour"], size="8"), + rx.heading(State.time_info.hour, size="8"), rx.heading(":", size="8"), - rx.heading(State.time_info["minute_display"], size="8"), + rx.heading(State.time_info.minute_display, size="8"), rx.heading(":", size="8"), - rx.heading(State.time_info["second_display"], size="8"), - rx.heading(State.time_info["meridiem"], size="8"), + rx.heading(State.time_info.second_display, size="8"), + rx.heading(State.time_info.meridiem, size="8"), border_width="medium", - border_color="#43464B", + border_color=rx.color("gray", 10), border_radius="2em", padding_inline_start="2em", padding_inline_end="2em", - background="white", - color="#333", + background=rx.color("accent", 1), + color=rx.color("gray", 12), ) @@ -186,14 +200,15 @@ def timezone_select() -> rx.Component: return rx.select( TIMEZONES, placeholder="Select a time zone.", - on_change=State.set_zone, + on_change=State.set_zone, # pyright: ignore [reportAttributeAccessIssue] value=State.valid_zone, width="100%", size="3", ) -def index(): +@rx.page(route="/clock/ui", on_load=State.on_load) +def ui(): """The main view.""" return rx.center( rx.vstack( @@ -208,22 +223,22 @@ def index(): timezone_select(), padding="5em", border_width="medium", - border_color="#43464B", + border_color=rx.color("gray", 10), border_radius="25px", - background="#ededed", + background=rx.color("accent", 2), text_align="center", ), padding="5em", ) -app = rx.App( - theme=theme( +@rx.page(route="/clock/theme_ui", on_load=State.on_load) +def theme_ui(): + return rx.theme( + ui(), appearance="light", has_background=True, radius="large", accent_color="amber", gray_color="sand", ) -) -app.add_page(index, title="Clock", on_load=State.on_load) diff --git a/playground/playground/common.py b/playground/playground/common.py new file mode 100644 index 00000000..e869ac9d --- /dev/null +++ b/playground/playground/common.py @@ -0,0 +1,202 @@ +"""Common components used by all demo pages.""" + +import dataclasses +import inspect +from functools import wraps +from pathlib import Path +from typing import Callable + +import reflex as rx +from reflex.page import DECORATED_PAGES + + +@dataclasses.dataclass(frozen=True) +class AppPage: + """A demo page.""" + + route: str + title: str + description: str + + +_APP_PAGES: dict[str, AppPage] = {} + + +class DemoState(rx.State): + """State for the demo pages.""" + + @rx.var(cache=True) + def pages(self) -> list[AppPage]: + return [p for p in _APP_PAGES.values() if p.route != "/"] + + +def demo_dropdown() -> rx.Component: + """Dropdown to navigate between demo pages.""" + + return rx.select.root( + rx.select.trigger(placeholder="Select Demo"), + rx.select.content( + rx.foreach( + DemoState.pages, + lambda page: rx.select.item( + rx.heading(page.title, size="3"), value=page.route + ), + ) + ), + value=rx.cond( + rx.State.router.page.path == "/", + "", + rx.State.router.page.path, + ), + on_change=rx.redirect, + ) + + +def page_template(page: Callable[[], rx.Component]) -> rx.Component: + """Template for all pages.""" + page_data = _APP_PAGES[page.__name__] + + return rx.container( + rx.color_mode.button(position="top-right"), + rx.hstack( + demo_dropdown(), + rx.spacer(), + rx.link( + rx.heading("playground", size="4"), + href="/", + ), + width="100%", + align="center", + ), + rx.text(page_data.description, margin_left="10px", margin_top="5px"), + page(), + rx.logo(), + size="4", + ) + + +@dataclasses.dataclass(frozen=True) +class ExtraTab: + """Extra tab to add to the demo page.""" + + trigger: rx.Component + content: rx.Component + + +def badged_card( + badge_content: rx.Component, card_content: rx.Component +) -> rx.Component: + return rx.card( + rx.inset( + rx.badge( + badge_content, + width="100%", + size="2", + height="3em", + radius="none", + ), + side="top", + pb="current", + ), + card_content, + ) + + +def demo_tabs(page: Callable[[], rx.Component]) -> rx.Component: + page_file = Path(inspect.getfile(page)) + page_source = page_file.read_text() + relative_page_file = page_file.relative_to( + Path(__file__).parent.parent.parent.parent.resolve() + ) + readme_file = page_file.with_name("README.md") + + extra_tabs = [] + + if readme_file.exists(): + readme = readme_file.read_text() + relative_readme_file = relative_page_file.with_name("README.md") + extra_tabs.append( + ExtraTab( + trigger=rx.tabs.trigger( + rx.hstack(rx.icon("book"), rx.text("Readme")), value="readme" + ), + content=rx.tabs.content( + badged_card( + badge_content=rx.code(str(relative_readme_file)), + card_content=rx.markdown(readme), + ), + value="readme", + ), + ), + ) + + return rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger( + rx.hstack(rx.icon("eye"), rx.text("Example")), value="example" + ), + rx.tabs.trigger( + rx.hstack(rx.icon("code"), rx.text("Source")), value="source" + ), + *[tab.trigger for tab in extra_tabs], + margin_bottom="1em", + ), + rx.tabs.content(page(), value="example", height="fit-content"), + rx.tabs.content( + badged_card( + badge_content=rx.code(str(relative_page_file)), + card_content=rx.code_block( + page_source, language="python", show_line_numbers=True + ), + ), + value="source", + ), + *[tab.content for tab in extra_tabs], + default_value="example", + margin_top="1em", + ) + + +def demo_template(page: Callable[[], rx.Component]) -> rx.Component: + """Template for all demo pages.""" + + @wraps(page) + def _page_wrapper(): + return demo_tabs(page) + + return page_template(_page_wrapper) + + +def page( + route: str, title: str, description: str, **kwargs +) -> Callable[[Callable], Callable]: + """Decorator to add the demo page to the demo registry.""" + + template = kwargs.pop("template", page_template) + + def decorator(page): + _APP_PAGES[page.__name__] = AppPage( + route=route, title=title, description=description + ) + page_kwargs = kwargs + # Snag any on_load or other metadata from the existing decorator + for render_fn, render_fn_kwargs in DECORATED_PAGES["playground"]: + if render_fn == page: + for key in render_fn_kwargs: + if key in ("on_load", "meta", "script_tags"): + page_kwargs.setdefault(key, render_fn_kwargs[key]) + + @rx.page(route=route, title=title, description=description, **page_kwargs) + @wraps(page) + def inner(): + return template(page) + + return inner + + return decorator + + +def demo( + route: str, title: str, description: str, **kwargs +) -> Callable[[Callable], Callable]: + return page(route, title, description, template=demo_template, **kwargs) diff --git a/json-tree/README.md b/playground/playground/json_tree/README.md similarity index 100% rename from json-tree/README.md rename to playground/playground/json_tree/README.md diff --git a/playground/playground/json_tree/__init__.py b/playground/playground/json_tree/__init__.py new file mode 100644 index 00000000..72e1db7d --- /dev/null +++ b/playground/playground/json_tree/__init__.py @@ -0,0 +1,8 @@ +from ..common import demo +from .json_tree import page + +demo( + route="/json_tree", + title="JSON Tree (CSR, dynamic components)", + description="Render nested JSON data as a tree using dynamic components from a state.", +)(page) diff --git a/json-tree/json_tree/json_tree.py b/playground/playground/json_tree/json_tree.py similarity index 90% rename from json-tree/json_tree/json_tree.py rename to playground/playground/json_tree/json_tree.py index 9c5db82a..0821f940 100644 --- a/json-tree/json_tree/json_tree.py +++ b/playground/playground/json_tree/json_tree.py @@ -30,8 +30,10 @@ def make_json(node) -> rx.Component: ) return rx.data_list.root(*children, margin_bottom="1rem") if isinstance(node, list): - for item in node: - children.append(rx.list_item(make_json(item))) + children.extend( + rx.data_list.item(rx.data_list.value(make_json(item))) + for item in node + ) return rx.unordered_list(*children) return rx.text(str(node)) @@ -52,7 +54,7 @@ def handle_paste_json(self, data: list[tuple[str, str]]): break -def index() -> rx.Component: +def page() -> rx.Component: return rx.fragment( rx.vstack( State.info, @@ -61,7 +63,3 @@ def index() -> rx.Component: ), rx.clipboard(on_paste=State.handle_paste_json), ) - - -app = rx.App() -app.add_page(index) diff --git a/playground/playground/lorem_stream/__init__.py b/playground/playground/lorem_stream/__init__.py new file mode 100644 index 00000000..b17918cb --- /dev/null +++ b/playground/playground/lorem_stream/__init__.py @@ -0,0 +1,8 @@ +from ..common import demo +from .lorem_stream import example + +demo( + route="/lorem_stream", + title="Lorem Streaming Background Tasks", + description="Demonstrates how to use background tasks to stream text concurrently.", +)(example) diff --git a/lorem-stream/lorem_stream/lorem_stream.py b/playground/playground/lorem_stream/lorem_stream.py similarity index 78% rename from lorem-stream/lorem_stream/lorem_stream.py rename to playground/playground/lorem_stream/lorem_stream.py index d30a8f2f..54e0a6d4 100644 --- a/lorem-stream/lorem_stream/lorem_stream.py +++ b/playground/playground/lorem_stream/lorem_stream.py @@ -1,11 +1,8 @@ import asyncio import random -from lorem_text import lorem - import reflex as rx -import reflex_chakra as rc - +from lorem_text import lorem ITERATIONS_RANGE = (7, 12) @@ -18,11 +15,11 @@ class LoremState(rx.State): _next_task_id: int = 0 - @rx.var + @rx.var(cache=True) def task_ids(self) -> list[int]: return list(reversed(self.text)) - @rx.background + @rx.event(background=True) async def stream_text(self, task_id: int = -1): if task_id < 0: async with self: @@ -45,6 +42,7 @@ async def stream_text(self, task_id: int = -1): async with self: self.running.pop(task_id, None) + @rx.event def toggle_running(self, task_id: int): if self.progress.get(task_id, 0) >= self.end_at.get(task_id, 0): self.progress[task_id] = 0 @@ -55,6 +53,7 @@ def toggle_running(self, task_id: int): else: return LoremState.stream_text(task_id) + @rx.event def kill(self, task_id: int): self.running.pop(task_id, None) self.text.pop(task_id, None) @@ -63,31 +62,41 @@ def kill(self, task_id: int): def render_task(task_id: int) -> rx.Component: return rx.vstack( rx.hstack( - rc.circular_progress( - rc.circular_progress_label(task_id), - value=LoremState.progress[task_id], - max_=LoremState.end_at[task_id], - is_indeterminate=LoremState.progress[task_id] < 1, - ), + rx.badge("Task ", task_id), rx.button( rx.cond( LoremState.progress[task_id] < LoremState.end_at[task_id], "⏯️", "🔄" ), on_click=LoremState.toggle_running(task_id), + variant="outline", ), - rx.button("❌", on_click=LoremState.kill(task_id)), + rx.button( + "❌", + on_click=LoremState.kill(task_id), + variant="outline", + ), + ), + rx.progress( + value=LoremState.progress[task_id], + max=LoremState.end_at[task_id], + min_height="10px", + max_height="10px", ), rx.text(LoremState.text[task_id], overflow_y="scroll"), + rx.spacer(), width=["180px", "190px", "210px", "240px", "300px"], height="300px", padding="10px", ) -@rx.page(title="Lorem Streaming Background Tasks") -def index() -> rx.Component: +def example() -> rx.Component: return rx.vstack( - rx.button("➕ New Task", on_click=LoremState.stream_text(-1)), + rx.button( + "➕ New Task", # noqa: RUF001 + on_click=LoremState.stream_text(-1), + variant="surface", + ), rx.flex( rx.foreach(LoremState.task_ids, render_task), flex_wrap="wrap", @@ -96,6 +105,3 @@ def index() -> rx.Component: align="center", padding_top="20px", ) - - -app = rx.App() diff --git a/playground/playground/playground.py b/playground/playground/playground.py new file mode 100644 index 00000000..734e33d3 --- /dev/null +++ b/playground/playground/playground.py @@ -0,0 +1,15 @@ +import reflex as rx + +from .common import page + + +@page( + route="/", + title="Playground", + description="Select an example from the drop down to get started.", +) +def test(): + return rx.fragment() + + +app = rx.App() diff --git a/playground/requirements-dev.txt b/playground/requirements-dev.txt new file mode 100644 index 00000000..7af7b9ff --- /dev/null +++ b/playground/requirements-dev.txt @@ -0,0 +1,2 @@ +pyright +ruff diff --git a/playground/requirements.txt b/playground/requirements.txt new file mode 100644 index 00000000..8c9cc6a5 --- /dev/null +++ b/playground/requirements.txt @@ -0,0 +1,5 @@ +reflex>=0.6.8 + +pytz # for "clock" + +lorem_text>=2.1 # for "lorem_stream" \ No newline at end of file diff --git a/playground/ruff.toml b/playground/ruff.toml new file mode 100644 index 00000000..407c248e --- /dev/null +++ b/playground/ruff.toml @@ -0,0 +1,26 @@ +# Define which rules to enable +output-format = "concise" + +# Exclude specific files or directories (e.g., migrations or test data) +exclude = [ + ".git", + ".venv", + "migrations", + "tests/data", +] +# Define line length (matches Black by default) +line-length = 88 + +[lint] +select = ["B", "C4", "E", "ERA", "F", "FURB", "I", "PERF", "PTH", "RUF", "SIM", "T", "W"] +ignore = ["E501", "F403", "RUF012"] + + +# Add per-file specific configurations +[lint.per-file-ignores] +# Ignore docstring checks in tests +"tests/**/*.py" = ["D"] +# Ignore all linter checks for certain scripts +"scripts/*.py" = ["ALL"] +# Never lint the alembic files +"*/alembic/*" = ["ALL"] diff --git a/playground/rxconfig.py b/playground/rxconfig.py new file mode 100644 index 00000000..aebfcf87 --- /dev/null +++ b/playground/rxconfig.py @@ -0,0 +1,3 @@ +import reflex as rx + +config = rx.Config(app_name="playground")