Skip to content

Commit

Permalink
Add examples
Browse files Browse the repository at this point in the history
  • Loading branch information
ElijahAhianyo committed Sep 17, 2024
1 parent ef6d672 commit 0e0fcbe
Show file tree
Hide file tree
Showing 165 changed files with 5,245 additions and 0 deletions.
7 changes: 7 additions & 0 deletions examples/chatroom/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
*.db
*.py[cod]
.web
__pycache__/
assets/external/
pynecone.db
reflex.db
35 changes: 35 additions & 0 deletions examples/chatroom/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# chatroom

A multi-client chat room.

NOTE: this example does NOT work in prod mode with redis!

<img src="assets/screenshot.png">

## `broadcast_event`

This function iterates through all states in the app, applying the Event payload
against each state instance. The resulting change list is passed off to
`pynecone.app.EventNamespace.emit()` directly.

Either event handlers or other out-of-band callbacks can use this API to emit
Events from the server as if they originated from the client. This preserves the
simple State and Event Handler conceptual model, while allowing server-to-client
communication and state updates at will.

## `send_message`

`broadcast_event` is used in the `send_message` event handler, which broadcasts
the `state.incoming_message` event to all connected clients, with the details of
the message. This in itself doesn't trigger any network traffic, unless the
state update creates a delta.

## `nick_change`

When the user sets or changes their nick, the event handler first updates the
local state, as usual. Then it awaits, `broadcast_nicks` which collects the full
nick list from the `State` instance of each connected client by iterating over
`app.state_manager.states` values.

The same `broadcast_event` mechanism described above is then used to pass the
nick list via the `state.set_nicks` event to all connected clients.
Binary file added examples/chatroom/assets/favicon.ico
Binary file not shown.
Binary file added examples/chatroom/assets/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
120 changes: 120 additions & 0 deletions examples/chatroom/chatroom/chatroom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""Reflex chatroom -- send server events to other sessions."""
import time
import typing as t

from rxconfig import config

import reflex as rx
import reflex_chakra as rc


class Message(rx.Base):
nick: str
sent: float
message: str


class State(rx.State):
nick: t.Optional[str] = ""
nicks: t.List[str] = []
messages: t.List[Message] = []
in_message: str = ""

def set_nicks(self, nicks: t.List[str]) -> None:
"""Set the list of nicks (from broadcast_nicks)."""
self.nicks = nicks

def incoming_message(self, message: Message) -> None:
"""Append incoming message to current message list."""
self.messages.append(message)

async def nick_change(self, nick: str) -> None:
"""Handle on_blur from nick text input."""
self.nick = nick
await broadcast_nicks()

async def send_message(self) -> None:
"""Broadcast chat message to other connected clients."""
m = Message(nick=self.nick, sent=time.time(), message=self.in_message)
await broadcast_event(f"{self.get_full_name()}.incoming_message", payload=dict(message=m))
self.in_message = ""

@rx.var
def other_nicks(self) -> t.List[str]:
"""Filter nicks list to exclude nick from this instance."""
return [n for n in self.nicks if n != self.nick]


def index() -> rx.Component:
return rc.vstack(
rc.center(rc.heading("Reflex Chat!", font_size="2em")),
rc.hstack(
rc.vstack(
rc.input(
placeholder="Nick",
default_value=State.nick,
on_blur=State.nick_change,
),
rc.text("Other Users", font_weight="bold"),
rx.foreach(State.other_nicks, rc.text),
width="20vw",
align_items="left",
),
rc.vstack(
rx.foreach(
State.messages,
lambda m: rc.text("<", m.nick, "> ", m.message),
),
rc.form(
rc.hstack(
rc.input(
placeholder="Message",
value=State.in_message,
on_change=State.set_in_message,
flex_grow=1,
),
rc.button("Send", on_click=State.send_message),
),
on_submit=lambda d: State.send_message(),
),
width="60vw",
align_items="left",
),
),
)


app = rx.App()
app.add_page(index)


async def broadcast_event(name: str, payload: t.Dict[str, t.Any] = {}) -> None:
"""Simulate frontend event with given name and payload from all clients."""
responses = []
for state in app.state_manager.states.values():
async for update in state._process(
event=rx.event.Event(
token=state.router.session.client_token,
name=name,
router_data=state.router_data,
payload=payload,
),
):
# Emit the event.
responses.append(
app.event_namespace.emit(
str(rx.constants.SocketEvent.EVENT),
update.json(),
to=state.router.session.session_id,
),
)
for response in responses:
await response


async def broadcast_nicks() -> None:
"""Simulate State.set_nicks event with updated nick list from all clients."""
nicks = []
for state in app.state_manager.states.values():
nicks.append(state.get_substate(State.get_full_name().split(".")).nick)
await broadcast_event(f"{State.get_full_name()}.set_nicks", payload=dict(nicks=nicks))
1 change: 1 addition & 0 deletions examples/chatroom/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
reflex>=0.3.8
7 changes: 7 additions & 0 deletions examples/chatroom/rxconfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import reflex as rx

config = rx.Config(
app_name="chatroom",
db_url="sqlite:///reflex.db",
env=rx.Env.DEV,
)
5 changes: 5 additions & 0 deletions examples/clock/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.db
*.py[cod]
.web
__pycache__/
reflex.db
Binary file added examples/clock/assets/favicon.ico
Binary file not shown.
Empty file.
214 changes: 214 additions & 0 deletions examples/clock/clock/clock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
"""A Reflex example of a analog clock."""

import asyncio
from datetime import datetime, timezone
from typing import Any

import reflex as rx
import reflex_chakra as rc
import pytz


# The supported time zones.
TIMEZONES = [
"Asia/Tokyo",
"Australia/Sydney",
"Europe/London",
"Europe/Paris",
"Europe/Moscow",
"US/Pacific",
"US/Eastern",
]
DEFAULT_ZONE = TIMEZONES[-2]


def rotate(degrees: int) -> str:
"""CSS to rotate a clock hand.
Args:
degrees: The degrees to rotate the clock hand.
Returns:
The CSS to rotate the clock hand.
"""
return f"rotate({degrees}deg)"


class State(rx.State):
"""The app state."""

# The time zone to display the clock in.
zone: str = rx.Cookie(DEFAULT_ZONE)

# Whether the clock is running.
running: bool = False

# The last updated timestamp
_now: datetime = datetime.fromtimestamp(0)

@rx.cached_var
def valid_zone(self) -> str:
"""Get the current time zone.
Returns:
The current time zone.
"""
try:
pytz.timezone(self.zone)
except Exception:
return DEFAULT_ZONE
return self.zone

@rx.cached_var
def time_info(self) -> dict[str, Any]:
"""Get the current time info.
This can also be done as several computed vars, but this is more concise.
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),
}

def on_load(self):
"""Switch the clock off when the page refreshes."""
self.running = False
self.refresh()

def refresh(self):
"""Refresh the clock."""
self._now = datetime.now(timezone.utc)

@rx.background
async def tick(self):
"""Update the clock every second."""
while self.running:
async with self:
self.refresh()

# Sleep for a second.
await asyncio.sleep(1)

def flip_switch(self, running: bool):
"""Start or stop the clock.
Args:
running: Whether the clock should be running.
"""
# Set the switch state.
self.running = running

# Start the clock if the switch is on.
if self.running:
return State.tick


def clock_hand(rotation: str, color: str, length: str) -> rx.Component:
"""Create a clock hand.
Args:
rotation: The rotation of the clock hand.
color: The color of the clock hand.
length: The length of the clock hand.
Returns:
A clock hand component.
"""
return rc.divider(
transform=rotation,
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",
z_index=0,
)


def analog_clock() -> rx.Component:
"""Create the analog clock."""
return rc.circle(
# The inner circle.
rc.circle(
width="1em",
height="1em",
border_width="thick",
border_color="#43464B",
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"),
border_width="thick",
border_color="#43464B",
width="25em",
height="25em",
bg="rgb(250,250,250)",
box_shadow="dark-lg",
)


def digital_clock() -> rx.Component:
"""Create the digital clock."""
return rc.hstack(
rc.heading(State.time_info["hour"]),
rc.heading(":"),
rc.heading(State.time_info["minute_display"]),
rc.heading(":"),
rc.heading(State.time_info["second_display"]),
rc.heading(State.time_info["meridiem"]),
border_width="medium",
border_color="#43464B",
border_radius="2em",
padding_x="2em",
bg="white",
color="#333",
)


def timezone_select() -> rx.Component:
"""Create the timezone select."""
return rc.select(
TIMEZONES,
placeholder="Select a time zone.",
on_change=State.set_zone,
value=State.valid_zone,
bg="#white",
)


def index():
"""The main view."""
return rc.center(
rc.vstack(
analog_clock(),
rc.hstack(
digital_clock(),
rc.switch(is_checked=State.running, on_change=State.flip_switch),
),
timezone_select(),
padding="5em",
border_width="medium",
border_color="#43464B",
border_radius="25px",
bg="#ededed",
text_align="center",
),
padding="5em",
)


app = rx.App()
app.add_page(index, title="Clock", on_load=State.on_load)
Loading

0 comments on commit 0e0fcbe

Please sign in to comment.