Skip to content

Commit

Permalink
clean up examples a bit (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
ogrodnek authored Aug 13, 2024
1 parent 20f81fc commit 9f9cf61
Show file tree
Hide file tree
Showing 23 changed files with 167 additions and 128 deletions.
140 changes: 23 additions & 117 deletions examples/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from starlette.routing import Route
from pyview import PyView, defaultRootTemplate
from markupsafe import Markup
from .format_examples import ExampleEntry, format_examples

from .views import (
CountLiveView,
Expand Down Expand Up @@ -62,76 +63,16 @@ def content_wrapper(_context, content: Markup) -> Markup:
app.rootTemplate = defaultRootTemplate(css=Markup(css), content_wrapper=content_wrapper)

routes = [
(
"/count",
CountLiveView,
"Basic Counter",
"count.py",
"""
Gotta start somewhere, right? This example shows how to send click events
to the backend to update state. We also snuck in handling URL params.
""",
),
(
"/count_pubsub",
CountLiveViewPubSub,
"Basic Counter with PubSub",
"count_pubsub.py",
"""
The counter example, but with PubSub. Open this example in multiple windows
to see the state update in real time across all windows.
""",
),
("/volume", VolumeLiveView, "Volume Control", "volume.py", "Keyboard events!"),
(
"/registration",
RegistrationLiveView,
"Registration Form Validation",
"registration",
"Form validation using Pydantic",
),
("/plants", PlantsLiveView, "Form Validation 2", "form_validation", ""),
(
"/fifa",
FifaAudienceLiveView,
"Table Pagination",
"fifa",
"Table Pagination, and updating the URL from the backend.",
),
(
"/podcasts",
PodcastLiveView,
"Podcasts",
"podcasts",
"""
URL Parameters, client navigation updates, and dynamic page titles.
""",
),
(
"/status",
StatusLiveView,
"Realtime Status Dashboard",
"status",
"Pushing updates from the backend to the client.",
),
(
"/js_commands",
JsCommandsLiveView,
"JS Commands",
"js_commands",
"""
JS Commands let you update the DOM without making a trip to the server.
""",
),
(
"/webping",
PingLiveView,
"Web Ping",
"webping",
"""
Another example of pushing updates from the backend to the client.
""",
),
("/count", CountLiveView),
("/count_pubsub", CountLiveViewPubSub),
("/volume", VolumeLiveView),
("/registration", RegistrationLiveView),
("/plants", PlantsLiveView),
("/fifa", FifaAudienceLiveView),
("/podcasts", PodcastLiveView),
("/status", StatusLiveView),
("/js_commands", JsCommandsLiveView),
("/webping", PingLiveView),
# (
# "/checkboxes",
# CheckboxLiveView,
Expand All @@ -141,58 +82,20 @@ def content_wrapper(_context, content: Markup) -> Markup:
# A silly multi-user game where you can click checkboxes.
# """,
# ),
(
"/presence",
PresenceLiveView,
"Presence",
"presence",
"""
A simple example of presence tracking. Open this example in multiple windows
""",
),
(
"/maps",
MapLiveView,
"Maps",
"maps",
"""
A simple example of using Leaflet.js with PyView, and sending information back and
forth between the liveview and the JS library.
""",
),
(
"/file_upload",
FileUploadDemoLiveView,
"File Upload",
"file_upload",
"""
File upload example, with previews and progress bars.
""",
),
(
"/kanban",
KanbanLiveView,
"Kanban Board",
"kanban",
"""
A simple Kanban board example with drag and drop (another hooks example showing integration w/ SortableJS).
""",
),
("/presence", PresenceLiveView),
("/maps", MapLiveView),
("/file_upload", FileUploadDemoLiveView),
("/kanban", KanbanLiveView),
]

for path, view, _, _, _ in routes:
app.add_live_view(path, view)


async def get(request):
def render_example(path, title, src_file, text):
src_link = (
f"https://github.com/ogrodnek/pyview/tree/main/examples/views/{src_file}"
)
def render_example(e: ExampleEntry):
src_link = f"https://github.com/ogrodnek/pyview/tree/main/{e.src_path}"
return f"""
<div class="card">
<p><a href='{path}'>{title}</a></p>
<p>{text}</p>
<p><a href='{e.url_path}'>{e.title}</a></p>
<p>{e.text}</p>
<p><a target="_main" style="font-size: 12px" href="{src_link}"><i>Source Code</i></a></p>
</div>
"""
Expand All @@ -211,7 +114,7 @@ def render_example(path, title, src_file, text):
</div>
<div>
<h1 style='padding-bottom: 8px'>PyView Examples</h1>
{"".join([render_example(k,t,src, text) for k, _, t, src, text in routes])}
{"".join([render_example(e) for e in format_examples(routes)])}
</div>
</body>
</html>
Expand All @@ -220,3 +123,6 @@ def render_example(path, title, src_file, text):


app.routes.append(Route("/", get, methods=["GET"]))

for path, view in routes:
app.add_live_view(path, view)
35 changes: 35 additions & 0 deletions examples/format_examples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from dataclasses import dataclass
from pyview import LiveView
from typing import Optional, Iterator


@dataclass
class ExampleEntry:
url_path: str
title: str
src_path: str
text: str


def format_example(url_path: str, lv: type[LiveView]) -> Optional[ExampleEntry]:
if not lv.__doc__:
return None

# parse name and title from docstring, separated by blank line
docs = lv.__doc__.strip().split("\n\n")
title = docs[0]
text = "".join(docs[1:])

# get dirpectory path from module name
src_path = "/".join(lv.__module__.split(".")[:-1])

return ExampleEntry(url_path, title, src_path, text.strip())


def format_examples(
routes: list[tuple[str, type[LiveView]]],
) -> Iterator[ExampleEntry]:
for url, lv in routes:
f = format_example(url, lv)
if f:
yield f
5 changes: 2 additions & 3 deletions examples/views/__init__.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
from .count import CountLiveView
from .volume import VolumeLiveView
from .fifa import FifaAudienceLiveView
from .status import StatusLiveView
from .podcasts import PodcastLiveView
from .form_validation import PlantsLiveView
from .registration import RegistrationLiveView
from .count_pubsub import CountLiveViewPubSub
from .js_commands import JsCommandsLiveView
from .webping import PingLiveView
from .checkboxes import CheckboxLiveView
from .presence import PresenceLiveView
from .maps import MapLiveView
from .file_upload import FileUploadDemoLiveView
from .kanban import KanbanLiveView
from .count_pubsub import CountLiveViewPubSub
from .count import CountLiveView

__all__ = [
"CountLiveView",
"CountLiveView",
"VolumeLiveView",
"FifaAudienceLiveView",
Expand Down
1 change: 1 addition & 0 deletions examples/views/count/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .count import CountLiveView
File renamed without changes.
7 changes: 7 additions & 0 deletions examples/views/count.py → examples/views/count/count.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ class CountContext(TypedDict):


class CountLiveView(LiveView[CountContext]):
"""
Basic Counter
Gotta start somewhere, right? This example shows how to send click events
to the backend to update state. We also snuck in handling URL params.
"""

async def mount(self, socket: LiveViewSocket[CountContext], _session):
socket.context = {"count": 0}

Expand Down
1 change: 1 addition & 0 deletions examples/views/count_pubsub/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .count_pubsub import CountLiveViewPubSub
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ def increment(self):


class CountLiveViewPubSub(LiveView[Count]):
"""
Basic Counter with PubSub
The counter example, but with PubSub. Open this example in multiple windows
to see the state update in real time across all windows.
"""

async def mount(self, socket: LiveViewSocket[Count], _session):
socket.context = Count()
if socket.connected:
Expand Down
10 changes: 9 additions & 1 deletion examples/views/fifa/fifa.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ class FifaContext(TypedDict):


class FifaAudienceLiveView(LiveView[FifaContext]):
"""
Table Pagination
Table Pagination, and updating the URL from the backend.
"""

async def mount(self, socket: LiveViewSocket[FifaContext], _session):
paging = Paging(1, 10)
audiences = list_items(paging)
Expand All @@ -22,7 +28,9 @@ async def handle_event(self, event, payload, socket: LiveViewSocket[FifaContext]

socket.context["audiences"] = audiences

await socket.push_patch("/fifa", {"page": paging.page, "perPage": paging.perPage})
await socket.push_patch(
"/fifa", {"page": paging.page, "perPage": paging.perPage}
)

async def handle_params(self, url, params, socket: LiveViewSocket[FifaContext]):
paging = socket.context["paging"]
Expand Down
7 changes: 6 additions & 1 deletion examples/views/file_upload/file_upload.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from pyview import LiveView, LiveViewSocket
from pyview.uploads import UploadConfig, UploadConstraints
from typing import Optional
from dataclasses import dataclass, field
from pyview.vendor.ibis import filters
from .file_repository import FileRepository, FileEntry
Expand Down Expand Up @@ -29,6 +28,12 @@ class FileUploadDemoContext:


class FileUploadDemoLiveView(LiveView[FileUploadDemoContext]):
"""
File Upload
File upload example, with previews and progress bars.
"""

async def mount(self, socket: LiveViewSocket[FileUploadDemoContext], _session):
config = socket.allow_upload(
"photos",
Expand Down
8 changes: 7 additions & 1 deletion examples/views/form_validation/plants.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,18 @@ class Config:


class PlantsLiveView(LiveView[PlantsContext]):
"""
More Form Validation
"""

async def mount(self, socket: LiveViewSocket[PlantsContext], _session):
socket.context = PlantsContext(plants())

async def handle_event(self, event, payload, socket: LiveViewSocket[PlantsContext]):
if event == "water":
plant = next((p for p in socket.context.plants if p.id == payload["id"]), None)
plant = next(
(p for p in socket.context.plants if p.id == payload["id"]), None
)
if plant:
print(f"Watering {plant.name}...")
plant.last_watered = datetime.now()
Expand Down
6 changes: 6 additions & 0 deletions examples/views/js_commands/js_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,11 @@


class JsCommandsLiveView(LiveView[dict]):
"""
JS Commands
JS Commands let you update the DOM without making a trip to the server.
"""

async def mount(self, socket: LiveViewSocket[dict], _session):
socket.context = {}
7 changes: 7 additions & 0 deletions examples/views/kanban/kanban.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ def __post_init__(self):


class KanbanLiveView(LiveView[KanbanContext]):
"""
Kanban Board
A simple Kanban board example with drag and drop
(another hooks example showing integration w/ SortableJS).
"""

async def mount(self, socket: LiveViewSocket[KanbanContext], _session):
socket.context = KanbanContext()

Expand Down
7 changes: 7 additions & 0 deletions examples/views/maps/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ class MapContext:


class MapLiveView(LiveView[MapContext]):
"""
Maps
A simple example of using Leaflet.js with PyView, and sending information back and
forth between the liveview and the JS library.
"""

async def mount(self, socket: LiveViewSocket[MapContext], _session):
socket.context = MapContext(
parks=national_parks, selected_park_name=national_parks[0]["name"]
Expand Down
6 changes: 6 additions & 0 deletions examples/views/podcasts/podcasts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ class PodcastContext:


class PodcastLiveView(LiveView):
"""
Podcasts
URL Parameters, client navigation updates, and dynamic page titles.
"""

async def mount(self, socket: LiveViewSocket[PodcastContext], _session):
casts = podcasts()
socket.context = PodcastContext(casts[0], casts)
Expand Down
Loading

0 comments on commit 9f9cf61

Please sign in to comment.