Add Kino.LiveFrame
josevalim opened this issue · 1 comments
josevalim commented
It resembles LiveView. Here is a silly proof of concept:
import Kino.Control
import Kino.Shorts
defmodule Kino.LiveFrame do
use GenServer, restart: :temporary
def start_link({frame, arg}) do
GenServer.start_link(__MODULE__, {frame, arg})
end
def init({frame, arg}) do
{:ok, state} = c_init(arg)
{:ok, {frame, state}, {:continue, :render}}
end
def handle_continue(:render, {frame, state}) do
{:noreply, render({frame, state})}
end
def handle_info({{__MODULE__, fun}, data}, {frame, state}) when is_function(fun, 2) do
state = fun.(data, state)
{:noreply, render({frame, state})}
end
defp render({frame, state}) do
Kino.Frame.render(frame, c_render(state))
{frame, state}
end
defp control(from, fun) do
Kino.Control.subscribe(from, {__MODULE__, fun})
from
end
## Callbacks (those can be callbacks in a new behaviour)
def c_init(:ok) do
{:ok, %{page: 0, name: nil, address: nil}}
end
defp step_zero(_, state) do
%{state | page: 1}
end
defp step_one(%{data: %{name: name}}, state) do
if name == "" do
%{state | name: name}
else
%{state | name: name, page: 2}
end
end
defp step_two(%{data: %{address: address}}, state) do
case address do
"BUMP" <> _ -> %{state | address: address <> "!"}
"" -> %{state | address: ""}
_ -> %{state | address: address, page: 3}
end
end
defp go_back(_, state) do
%{state | page: state.page - 1}
end
def c_render(%{page: 0}) do
button("Start")
|> control(&step_zero/2)
end
def c_render(%{page: 1} = state) do
form(
[name: Kino.Input.text("Name", default: state.name)],
submit: "Step one"
)
|> control(&step_one/2)
end
def c_render(%{page: 2} = state) do
Kino.Control.form(
[address: Kino.Input.text("Address", default: state.address)],
submit: "Step two"
)
|> control(&step_two/2)
|> add_go_back()
end
def c_render(%{page: 3} = state) do
"Well done, #{state.name}. You live in #{state.address}."
|> add_go_back()
end
defp add_go_back(element) do
button =
button("Go back")
|> control(&go_back/2)
grid([element, button])
end
end
frame = Kino.Frame.new() |> Kino.render()
Kino.start_child!({Kino.LiveFrame, {frame, :ok}})
We need Kino.monitor_origins
before implementing it.