Just run this like elixir grid.exs
and you have a liveview collaborative grid huzzah
Created
November 11, 2024 17:49
-
-
Save knewter/0d9936ba92def77d1920c9c5214b1855 to your computer and use it in GitHub Desktop.
single-file liveview collaborative drawing grid
This file contains hidden or 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
Mix.install([ | |
{:plug_cowboy, "~> 2.5"}, | |
{:jason, "~> 1.0"}, | |
{:phoenix, "~> 1.7.0"}, | |
{:phoenix_live_view, "~> 0.19.0"} | |
]) | |
defmodule Example.LayoutView do | |
use Phoenix.HTML | |
import Phoenix.Component | |
def render(assigns) do | |
~H""" | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>LiveView Grid</title> | |
<script defer src="https://unpkg.com/[email protected]/priv/static/phoenix.min.js"></script> | |
<script defer src="https://unpkg.com/[email protected]/priv/static/phoenix_live_view.js"></script> | |
<script> | |
document.addEventListener("DOMContentLoaded", () => { | |
const Socket = window.Phoenix.Socket; | |
const LiveSocket = window.LiveView.LiveSocket; | |
const csrfToken = null; | |
const liveSocket = new LiveSocket("/live", Socket, { params: { _csrf_token: csrfToken } }); | |
liveSocket.connect(); | |
}); | |
</script> | |
<style> | |
.grid-container { display: grid; grid-template-columns: repeat(<%= @grid_size %>, 10px); } | |
.grid-item { width: 10px; height: 10px; border: 1px solid #ccc; } | |
</style> | |
</head> | |
<body> | |
<main role="main"> | |
<%= @inner_content %> | |
</main> | |
</body> | |
</html> | |
""" | |
end | |
end | |
defmodule Example.ErrorView do | |
def render(template, _), do: Phoenix.Controller.status_message_from_template(template) | |
end | |
defmodule Example.Application do | |
use Application | |
def start(_type, _args) do | |
IO.puts("starting app") | |
children = [ | |
Example.GridState, | |
Example.Endpoint | |
] | |
opts = [strategy: :one_for_one, name: Example.Supervisor] | |
Supervisor.start_link(children, opts) | |
end | |
end | |
defmodule Example.GridState do | |
use GenServer | |
def start_link(_) do | |
GenServer.start_link(__MODULE__, %{}, name: __MODULE__) | |
end | |
def init(_) do | |
{:ok, %{}} | |
end | |
def get_grid() do | |
GenServer.call(__MODULE__, :get_grid) | |
end | |
def toggle_cell(x, y, color) do | |
GenServer.call(__MODULE__, {:toggle_cell, x, y, color}) | |
end | |
def handle_call(:get_grid, _from, grid) do | |
{:reply, {:ok, grid}, grid} | |
end | |
def handle_call({:toggle_cell, x, y, color}, _from, grid) do | |
new_grid = Map.put(grid, {x, y}, color) | |
broadcast_grid_update(new_grid) | |
{:reply, color, new_grid} | |
end | |
defp broadcast_grid_update(new_grid) do | |
Phoenix.PubSub.broadcast(Example.PubSub, "grid:update", {:grid_updated, new_grid}) | |
end | |
end | |
defmodule Example.GridLive do | |
use Phoenix.LiveView | |
alias Example.GridState | |
@grid_size 100 | |
@impl true | |
def mount(_params, _session, socket) do | |
if connected?(socket) do | |
Phoenix.PubSub.subscribe(Example.PubSub, "grid:update") | |
end | |
{:ok, grid} = GridState.get_grid() | |
{:ok, | |
socket | |
|> assign(:grid, grid) | |
|> assign(:grid_size, @grid_size) | |
# Default color is black | |
|> assign(:color, "#000000")} | |
end | |
@impl true | |
def render(assigns) do | |
~H""" | |
<div> | |
<form phx-change="color_changed"> | |
<label for="color-picker">Choose Color:</label> | |
<input type="color" id="color-picker" name="color-picker" value={@color} /> | |
</form> | |
</div> | |
<div class="grid-container"> | |
<%= for y <- 0..(@grid_size - 1) do %> | |
<%= for x <- 0..(@grid_size - 1) do %> | |
<div class="grid-item" | |
phx-click="toggle_cell" | |
phx-value-x={x} | |
phx-value-y={y} | |
style={"background-color: #{Map.get(assigns.grid, {x, y}, "#FFFFFF")}"}></div> | |
<% end %> | |
<% end %> | |
</div> | |
""" | |
end | |
@impl true | |
def handle_event("color_changed", %{"color-picker" => color}, socket) do | |
{:noreply, assign(socket, :color, color)} | |
end | |
@impl true | |
def handle_event("toggle_cell", %{"x" => x, "y" => y}, socket) do | |
x = String.to_integer(x) | |
y = String.to_integer(y) | |
color = socket.assigns.color | |
GridState.toggle_cell(x, y, color) | |
{:noreply, socket} | |
end | |
@impl true | |
def handle_info({:grid_updated, new_grid}, socket) do | |
{:noreply, assign(socket, grid: new_grid)} | |
end | |
end | |
defmodule Example.Router do | |
use Phoenix.Router | |
import Phoenix.LiveView.Router | |
pipeline :browser do | |
plug(:accepts, ["html"]) | |
plug(:put_root_layout, {Example.LayoutView, :render}) | |
end | |
scope "/", Example do | |
pipe_through(:browser) | |
live("/", GridLive, :index) | |
end | |
end | |
defmodule Example.Endpoint do | |
use Phoenix.Endpoint, otp_app: :sample | |
socket("/live", Phoenix.LiveView.Socket) | |
plug(Example.Router) | |
end | |
Application.put_env(:sample, Example.Endpoint, | |
http: [ip: {127, 0, 0, 1}, port: 5001], | |
server: true, | |
live_view: [signing_salt: "aaaaaaaa"], | |
secret_key_base: String.duplicate("a", 64), | |
pubsub_server: Example.PubSub | |
) | |
{:ok, _} = | |
Supervisor.start_link( | |
[ | |
{Phoenix.PubSub, name: Example.PubSub}, | |
Example.Endpoint, | |
Example.GridState | |
], | |
strategy: :one_for_one | |
) | |
Process.sleep(:infinity) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment