-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ef6d672
commit 0e0fcbe
Showing
165 changed files
with
5,245 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
reflex>=0.3.8 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
*.db | ||
*.py[cod] | ||
.web | ||
__pycache__/ | ||
reflex.db |
Binary file not shown.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Oops, something went wrong.