wslでelixir その159

# 概要
wsl(wsl2じゃない)で、elixirやってみた。
Livebookの、kino.jsを調査してみた。

# setup
```
Mix.install([
  {:kino, "~> 0.15.0"}
])
```

# サンプルコード

```
defmodule KinoGuide.HTML do
  use Kino.JS
  def new(html) when is_binary(html) do
    Kino.JS.new(__MODULE__, html)
  end
  asset "main.js" do
    """
    export function init(ctx, html) {
      ctx.root.innerHTML = html;
    }
    """
  end
end

KinoGuide.HTML.new("""
<h3>Look!</h3>

<p>I wrote this HTML from <strong>Kino</strong>!</p>
""")

defmodule KinoGuide.Counter do
  use Kino.JS
  use Kino.JS.Live
  def new(count) do
    Kino.JS.Live.new(__MODULE__, count)
  end
  def bump(kino) do
    Kino.JS.Live.cast(kino, :bump)
  end
  @impl true
  def init(count, ctx) do
    {:ok, assign(ctx, count: count)}
  end
  @impl true
  def handle_connect(ctx) do
    {:ok, ctx.assigns.count, ctx}
  end
  @impl true
  def handle_cast(:bump, ctx) do
    {:noreply, bump_count(ctx)}
  end
  @impl true
  def handle_event("bump", _, ctx) do
    IO.puts("ok")
    {:noreply, bump_count(ctx)}
  end
  defp bump_count(ctx) do
    ctx = update(ctx, :count, &(&1 + 1))
    broadcast_event(ctx, "update", ctx.assigns.count)
    ctx
  end
  asset "main.js" do
    """
    export function init(ctx, count) {
      ctx.root.innerHTML = `
        <div id="count"></div>
        <button id="bump">Bump</button>
      `;
      const countEl = document.getElementById("count");
      const bumpEl = document.getElementById("bump");
      countEl.innerHTML = count;
      ctx.handleEvent("update", (count) => {
        countEl.innerHTML = count;
      });
      bumpEl.addEventListener("click", (event) => {
        ctx.pushEvent("bump");
      });
    }
    """
  end
end

counter = KinoGuide.Counter.new(0)

KinoGuide.Counter.bump(counter)

defmodule KinoDocs.LiveHTML do
	use Kino.JS
	use Kino.JS.Live
	def new(html) do
		Kino.JS.Live.new(__MODULE__, html)
	end
	def replace(kino, html) do
		Kino.JS.Live.cast(kino, {:replace, html})
	end
	@impl true
	def init(html, ctx) do
		{:ok, assign(ctx, html: html)}
	end
	@impl true
	def handle_connect(ctx) do
		{:ok, ctx.assigns.html, ctx}
	end
	@impl true
	def handle_cast({:replace, html}, ctx) do
		broadcast_event(ctx, "replace", html)
		{:noreply, assign(ctx, html: html)}
	end
	asset "main.js" do
		"""
		export function init(ctx, html) {
			ctx.root.innerHTML = html;
			ctx.handleEvent("replace", (html) => {
				ctx.root.innerHTML = html;
			});
		}
		"""
	end
end

list = KinoDocs.LiveHTML.new("""
<h1>Hello</h1>
""")

KinoDocs.LiveHTML.replace(list, """
<h2 style="color: red">World</h2>
""")

defmodule KinoCustom.Bar do
  use Kino.JS
  use Kino.JS.Live

  def new(width) do
    Kino.JS.Live.new(__MODULE__, width)
  end

  def update(kino, width) do
    Kino.JS.Live.cast(kino, {:update, width})
  end

  @impl true
  def init(html, ctx) do
    {:ok, assign(ctx, html: html)}
  end

  @impl true
  def handle_connect(ctx) do
    {:ok, ctx.assigns.html, ctx}
  end

  @impl true
  def handle_cast({:update, width}, ctx) do
    broadcast_event(ctx, "update", width)
    {:noreply, assign(ctx, width: width)}
  end

  asset "main.js" do
    """
    export function init(ctx, width) {
      const bar = document.createElement("div");
      bar.className = "bar";
      bar.style.width = width;
      bar.style.height = "40px";
      bar.style.backgroundColor = "aqua";
      ctx.root.appendChild(bar);
      ctx.handleEvent("update", (width) => {
        bar.style.width = width
      });
    }
    """
  end
end

bar = KinoCustom.Bar.new("50%")

Stream.interval(250)
|> Stream.take(100)
|> Kino.animate(fn width ->
  KinoCustom.Bar.update(bar, "#{width}%")
end)


```

# elixirからjs呼び出し

KinoCustom.Bar.update(bar, "#{width}%")

# jsからelixir呼び出し

ctx.pushEvent("bump");

以上。