Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use alfort v0.1.9 #27

Merged
merged 1 commit into from
Oct 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion alfort_dom/dom.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def dom_effect(
) -> Callable[[Callable[[HTMLElement, Dispatch[M]], None]], Effect[M]]:
def _wrapper(fun: Callable[[HTMLElement, Dispatch[M]], None]) -> Effect[M]:
@functools.wraps(fun)
def _wrapped(dispatch: Dispatch[M]) -> None:
async def _wrapped(dispatch: Dispatch[M]) -> None:
def _f(_: Any) -> None:
dom = document.getElementById(dom_id)
fun(dom, dispatch)
Expand Down
1 change: 1 addition & 0 deletions docs/examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<div>
<ul>
<li> <a href="./simple_counter/"> simple_counter </a></li>
<li> <a href="./simple_api/"> simple_api </a></li>
<li><a href="./todomvc/"> todomvc </a></li>
<li><a href="./key_and_mouse_events/"> key_and_mouse_events </a></li>
</ul>
Expand Down
29 changes: 29 additions & 0 deletions docs/examples/simple_api/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Alfort-Dom • Simple Counter</title>
</head>
<body>
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"
></script>
<script type="text/javascript">
async function main() {
let pyodide = await loadPyodide();
await pyodide.loadPackage("micropip");

const initScript = await fetch("./pkg_install.py");
const initScriptText = await initScript.text();
await pyodide.runPythonAsync(initScriptText);

const script = await fetch("./main.py");
const scriptText = await script.text();
await pyodide.runPythonAsync(scriptText);
}
main();
</script>
<div id="root"></div>
</body>
</html>
157 changes: 157 additions & 0 deletions docs/examples/simple_api/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
from dataclasses import dataclass
from typing import Any, Callable, Coroutine, TypeAlias
from urllib.parse import ParseResult as URL
from urllib.parse import urlparse

import pyodide.http
from alfort import Dispatch, Effect
from alfort.vdom import VDom, el

from alfort_dom import AlfortDom


@dataclass(frozen=True)
class Photo:
album_id: int
id: int
title: str
url: URL
thumbnail_url: URL


@dataclass(frozen=True)
class State:
album_id: int
is_fetching: bool
photos: list[Photo]


@dataclass(frozen=True)
class SelectAlbum:
alumb_id: int


@dataclass(frozen=True)
class ReceivePhotos:
photos: list[Photo]


Msg: TypeAlias = SelectAlbum | ReceivePhotos


async def fetch_photos(album_id: int) -> list[Photo]:
url = f"https://jsonplaceholder.typicode.com/albums/{album_id}/photos"
res = await pyodide.http.pyfetch(url)
return [
Photo(
album_id=obj["albumId"],
id=obj["id"],
title=obj["title"],
url=urlparse(obj["url"]),
thumbnail_url=urlparse(obj["thumbnailUrl"]),
)
for obj in await res.json()
]


def title(text: str) -> VDom:
return el("h1", {}, [text])


def album_selector(album_id: int) -> VDom:
def _on_change(e: Any) -> Msg:
return SelectAlbum(e.target.value)

return el(
"div",
{"style": {"margin": "15px"}},
[
el("label", {"style": {"margin-right": "8px"}}, ["Album ID"]),
el(
"select",
{
"onchange": _on_change,
},
[
el(
"option",
{
"value": i,
"selected": album_id == i,
},
[str(i)],
)
for i in range(15)
],
),
],
)


def album_photos(photos: list[Photo]) -> VDom:
photo_elms: list[VDom] = []
for photo in photos:
photo_elms.append(
el(
"img",
{"src": photo.thumbnail_url.geturl(), "style": {"margin": "3px"}},
[],
)
)

return el("div", {"style": {"width": "75%", "line-height": "0px"}}, photo_elms)


def fetching_dialog() -> VDom:
return el("div", {}, ["Loading..."])


def view(state: State) -> VDom:
return el(
"div",
{
"style": {
"display": "flex",
"justify-content": "center",
"align-items": "center",
"flex-flow": "column",
}
},
[
title("Simple Photo Album"),
album_selector(state.album_id),
fetching_dialog() if state.is_fetching else album_photos(state.photos),
],
)


def create_fetch_effect(
album_id: int,
) -> Callable[[Dispatch[Msg]], Coroutine[None, None, Any]]:
async def _fetch(dispatch: Dispatch[Msg]) -> None:
recv_photos = await fetch_photos(album_id=album_id)
dispatch(ReceivePhotos(recv_photos))

return _fetch


def init() -> tuple[State, list[Effect[Msg]]]:
return (State(album_id=1, is_fetching=True, photos=[]), [create_fetch_effect(1)])


def update(msg: Msg, state: State) -> tuple[State, list[Effect[Msg]]]:
match msg:
case SelectAlbum(album_id):
state = State(album_id=album_id, is_fetching=True, photos=[])
return (state, [create_fetch_effect(album_id=album_id)])
case ReceivePhotos(photos):
state = State(album_id=state.album_id, is_fetching=False, photos=photos)
return (state, [])


app = AlfortDom[State, Msg](
init=init,
view=view,
update=update,
)
app.main(root="root")
7 changes: 7 additions & 0 deletions docs/examples/simple_api/pkg_install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import micropip # type: ignore # noqa: F401

try:
# try to use development version
await micropip.install("../dist/alfort_dom-0.0.0.dev0-py3-none-any.whl") # type: ignore # noqa: F704
except ValueError:
await micropip.install("alfort-dom") # type: ignore # noqa: F704
6 changes: 5 additions & 1 deletion docs/examples/todomvc/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ def with_local_storage(update: Update["Msg", "Model"]) -> Update["Msg", "Model"]
@functools.wraps(update)
def _update(msg: Msg, model: Model) -> tuple[Model, list[Effect[Msg]]]:
model, effects = update(msg, model)
return model, [lambda _: save_model(model), *effects]

async def _save_model(_: Dispatch[Msg]) -> None:
save_model(model)

return model, [_save_model, *effects]

return _update

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ include = ["alfort_dom/py.typed"]

[tool.poetry.dependencies]
python = "^3.10"
alfort = "^0.1.8"
alfort = "^0.1.9"

[tool.poetry.group.dev.dependencies]
poethepoet = "^0.13.1"
Expand Down
37 changes: 37 additions & 0 deletions stubs/pyodide/http/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# derived from https://github.com/pyodide/pyodide/blob/main/src/py/pyodide/http.py

from io import StringIO
from typing import Any, BinaryIO, TextIO

from .. import JsProxy

def open_url(url: str) -> StringIO: ...

class FetchResponse:
def __init__(self, url: str, js_response: JsProxy) -> None: ...
@property
def body_used(self) -> bool: ...
@property
def ok(self) -> bool: ...
@property
def redirected(self) -> bool: ...
@property
def status(self) -> str: ...
@property
def status_text(self) -> str: ...
@property
def type(self) -> str: ...
@property
def url(self) -> str: ...
def clone(self) -> "FetchResponse": ...
async def buffer(self) -> JsProxy: ...
async def string(self) -> str: ...
async def json(self, **kwargs: Any) -> Any: ...
async def memoryview(self) -> memoryview: ...
async def bytes(self) -> bytes: ...
async def _into_file(self, f: TextIO | BinaryIO) -> None: ...
async def unpack_archive(
self, *, extract_dir: str | None, format: str | None
) -> None: ...

async def pyfetch(url: str, **kwargs: Any) -> FetchResponse: ...