Add Phoenix LiveView score display page and LV dashboard
This commit is contained in:
parent
452557581e
commit
c9237e2c58
21 changed files with 4901 additions and 36 deletions
|
@ -48,7 +48,7 @@ config :ex_speed_game,
|
|||
win_delay: 2_000,
|
||||
|
||||
# Interface to show IP for when using ShowIP
|
||||
iface: "usb0",
|
||||
iface: "wlan0",
|
||||
|
||||
# LED pattern to show when there is a crash
|
||||
crash_pattern: {false, true, true, false},
|
||||
|
@ -75,6 +75,20 @@ config :nerves, source_date_epoch: "1583172807"
|
|||
|
||||
config :logger, backends: [RingLogger]
|
||||
|
||||
config :ex_speed_game, ExSpeedGame.Web.Endpoint,
|
||||
url: [host: "localhost"],
|
||||
http: [port: 1337],
|
||||
debug_errors: true,
|
||||
check_origin: false,
|
||||
secret_key_base: "s65CvlHa8RM4jVIgqCAbpAwbuiUFSJr7FUy5mh7kB0UrnSpOEcAcDu1Eajgtm912",
|
||||
render_errors: [view: ExSpeedGame.Web.ErrorView, accepts: ~w(html json), layout: false],
|
||||
pubsub_server: ExSpeedGame.Web.PubSub,
|
||||
live_view: [signing_salt: "58shuVUu"],
|
||||
server: true
|
||||
|
||||
config :phoenix, :json_library, Jason
|
||||
config :phoenix, :stacktrace_depth, 20
|
||||
|
||||
if Mix.target() != :host do
|
||||
import_config "target.exs"
|
||||
end
|
||||
|
|
|
@ -42,7 +42,7 @@ config :nerves_firmware_ssh,
|
|||
|
||||
# Setting the node_name will enable Erlang Distribution.
|
||||
# Only enable this for prod if you understand the risks.
|
||||
node_name = if Mix.env() != :prod, do: "ex_speed_game"
|
||||
node_name = "ex_speed_game"
|
||||
|
||||
config :nerves_init_gadget,
|
||||
ifname: "usb0",
|
||||
|
@ -51,6 +51,22 @@ config :nerves_init_gadget,
|
|||
node_name: node_name,
|
||||
node_host: :mdns_domain
|
||||
|
||||
config :nerves_network,
|
||||
regulatory_domain: "FI"
|
||||
|
||||
key_mgmt = System.get_env("NERVES_NETWORK_KEY_MGMT") || "WPA-PSK"
|
||||
|
||||
config :nerves_network, :default,
|
||||
wlan0: [
|
||||
networks: [
|
||||
[
|
||||
ssid: System.get_env("NERVES_NETWORK_SSID"),
|
||||
psk: System.get_env("NERVES_NETWORK_PSK"),
|
||||
key_mgmt: String.to_atom(key_mgmt)
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
# Import target specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
# Uncomment to use target specific configurations
|
||||
|
|
|
@ -29,27 +29,25 @@ defmodule ExSpeedGame.Application do
|
|||
end
|
||||
|
||||
# List all child processes to be supervised
|
||||
def children(:host) do
|
||||
[
|
||||
# Children that only run on the host
|
||||
# Starts a worker by calling: ExSpeedGame.Worker.start_link(arg)
|
||||
# {ExSpeedGame.Worker, arg},
|
||||
]
|
||||
end
|
||||
|
||||
def children(_target) do
|
||||
pins = Application.get_env(:ex_speed_game, :button_pins)
|
||||
led_pins = Application.get_env(:ex_speed_game, :button_light_pins)
|
||||
debounce_delay = Application.get_env(:ex_speed_game, :debounce_delay)
|
||||
|
||||
[
|
||||
# Children for all targets except host
|
||||
# Starts a worker by calling: ExSpeedGame.Worker.start_link(arg)
|
||||
{Registry,
|
||||
[name: ExSpeedGame.PubSub, keys: :duplicate, partitions: System.schedulers_online()]},
|
||||
{ButtonInput,
|
||||
%ButtonInput.Options{pins: pins, debounce_delay: debounce_delay, name: ButtonInput}},
|
||||
{Lights, %Lights.Options{light_pins: led_pins, name: Lights}},
|
||||
{Menu, %Menu.Options{name: Menu}},
|
||||
{DynamicSupervisor, strategy: :one_for_one, name: GameSupervisor}
|
||||
{DynamicSupervisor, strategy: :one_for_one, name: GameSupervisor},
|
||||
|
||||
# Web UI
|
||||
ExSpeedGame.Web.Telemetry,
|
||||
{Phoenix.PubSub, name: ExSpeedGame.Web.PubSub},
|
||||
ExSpeedGame.Web.Endpoint
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
defmodule ExSpeedGame.Game.Menu do
|
||||
use GenServer
|
||||
require Logger
|
||||
|
||||
alias ExSpeedGame.Game.{Types, ButtonInput, ShowIP, Lights}
|
||||
|
||||
alias ExSpeedGame.Game.Modes.{
|
||||
Speed
|
||||
}
|
||||
|
||||
alias ExSpeedGame.PubSub
|
||||
|
||||
@menu {
|
||||
{"SpeedGame", Speed,
|
||||
%Speed.Options{
|
||||
|
@ -82,7 +85,9 @@ defmodule ExSpeedGame.Game.Menu do
|
|||
end
|
||||
|
||||
display_index(new_index)
|
||||
{:noreply, %State{state | index: new_index}}
|
||||
new_state = %State{state | index: new_index}
|
||||
PubSub.publish(new_state)
|
||||
{:noreply, new_state}
|
||||
end
|
||||
|
||||
@spec handle_info({:input, Types.pin()}, State.t()) :: {:noreply, State.t()}
|
||||
|
@ -96,7 +101,9 @@ defmodule ExSpeedGame.Game.Menu do
|
|||
|
||||
ref = Process.monitor(pid)
|
||||
|
||||
{:noreply, %State{state | mode: :ingame, game: pid, game_ref: ref}}
|
||||
new_state = %State{state | mode: :ingame, game: pid, game_ref: ref}
|
||||
PubSub.publish(new_state)
|
||||
{:noreply, new_state}
|
||||
end
|
||||
|
||||
@spec handle_info({:DOWN, reference(), :process, any, any}, State.t()) :: {:noreply, State.t()}
|
||||
|
@ -109,6 +116,8 @@ defmodule ExSpeedGame.Game.Menu do
|
|||
} = state
|
||||
)
|
||||
when ref == game_ref do
|
||||
PubSub.clear(state.game)
|
||||
|
||||
# Ensure input is released in case process crashed violently
|
||||
ButtonInput.release(ButtonInput)
|
||||
|
||||
|
@ -120,11 +129,26 @@ defmodule ExSpeedGame.Game.Menu do
|
|||
|
||||
ButtonInput.acquire(ButtonInput)
|
||||
display_index(index)
|
||||
{:noreply, %State{state | mode: :menu, game: nil, game_ref: nil}}
|
||||
|
||||
new_state = %State{state | mode: :menu, game: nil, game_ref: nil}
|
||||
PubSub.publish(new_state)
|
||||
{:noreply, new_state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec handle_call({:esg_pubsub, :get_state}, GenServer.from(), State.t()) ::
|
||||
{:reply, State.t(), State.t()}
|
||||
def handle_call({:esg_pubsub, :get_state}, _from, %State{} = state) do
|
||||
{:reply, state, state}
|
||||
end
|
||||
|
||||
@spec index_name(integer()) :: String.t()
|
||||
def index_name(index) do
|
||||
@menu |> elem(index) |> elem(0)
|
||||
end
|
||||
|
||||
defp display_index(index) do
|
||||
Logger.debug("Menu viewing: #{@menu |> elem(index) |> elem(0)}")
|
||||
Logger.debug("Menu viewing: #{index_name(index)}")
|
||||
Lights.show_binary(Lights, index + 1)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
defmodule ExSpeedGame.Game.Modes.Speed do
|
||||
use GenServer, restart: :temporary
|
||||
require Logger
|
||||
|
||||
alias ExSpeedGame.Game.{Types, Lights, ButtonInput, Randomiser}
|
||||
alias ExSpeedGame.PubSub
|
||||
|
||||
@max_queue Application.get_env(:ex_speed_game, :max_queue)
|
||||
|
||||
|
@ -64,14 +66,16 @@ defmodule ExSpeedGame.Game.Modes.Speed do
|
|||
|
||||
next_delay = schedule_tick(state.delay)
|
||||
|
||||
{:noreply,
|
||||
%State{
|
||||
state
|
||||
| delay: next_delay,
|
||||
previous: choice,
|
||||
queue: new_queue,
|
||||
queue_len: state.queue_len + 1
|
||||
}}
|
||||
new_state = %State{
|
||||
state
|
||||
| delay: next_delay,
|
||||
previous: choice,
|
||||
queue: new_queue,
|
||||
queue_len: state.queue_len + 1
|
||||
}
|
||||
|
||||
PubSub.publish(new_state)
|
||||
{:noreply, new_state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
@ -96,19 +100,28 @@ defmodule ExSpeedGame.Game.Modes.Speed do
|
|||
score = state.score + 1
|
||||
Logger.debug("Score: #{score}")
|
||||
|
||||
{:noreply,
|
||||
%State{
|
||||
state
|
||||
| queue: :queue.drop(state.queue),
|
||||
queue_len: state.queue_len - 1,
|
||||
score: score
|
||||
}}
|
||||
new_state = %State{
|
||||
state
|
||||
| queue: :queue.drop(state.queue),
|
||||
queue_len: state.queue_len - 1,
|
||||
score: score
|
||||
}
|
||||
|
||||
PubSub.publish(new_state)
|
||||
{:noreply, new_state}
|
||||
|
||||
_ ->
|
||||
end_game(state)
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec handle_call({:esg_pubsub, :get_state}, GenServer.from(), State.t()) ::
|
||||
{:reply, State.t(), State.t()}
|
||||
def handle_call({:esg_pubsub, :get_state}, _from, %State{} = state) do
|
||||
{:reply, state, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec terminate(any(), any()) :: term()
|
||||
def terminate(_reason, _state) do
|
||||
|
@ -131,13 +144,17 @@ defmodule ExSpeedGame.Game.Modes.Speed do
|
|||
|
||||
@spec end_game(State.t()) :: {:noreply, State.t()}
|
||||
defp end_game(state) do
|
||||
new_state = %State{state | active: false}
|
||||
|
||||
# Don't listen to button inputs while showing failure lights
|
||||
Logger.debug("Game ended with score #{state.score}.")
|
||||
PubSub.publish(new_state)
|
||||
|
||||
ButtonInput.release(ButtonInput)
|
||||
Lights.set(Lights, Application.get_env(:ex_speed_game, :fail_pattern))
|
||||
Process.sleep(Application.get_env(:ex_speed_game, :win_delay))
|
||||
ButtonInput.acquire(ButtonInput)
|
||||
|
||||
{:noreply, %State{state | active: false}}
|
||||
{:noreply, new_state}
|
||||
end
|
||||
end
|
||||
|
|
30
lib/utils/pubsub.ex
Normal file
30
lib/utils/pubsub.ex
Normal file
|
@ -0,0 +1,30 @@
|
|||
defmodule ExSpeedGame.PubSub do
|
||||
@moduledoc """
|
||||
Light pubsub system based on Registry so that processes can listen to state publishes from
|
||||
others.
|
||||
|
||||
Processes must be GenServers and respond to state request.
|
||||
"""
|
||||
|
||||
@spec subscribe(atom | pid) :: term
|
||||
def subscribe(target) when is_atom(target) or is_pid(target) do
|
||||
target = if is_atom(target), do: Process.whereis(target), else: target
|
||||
|
||||
{:ok, _} = Registry.register(__MODULE__, target, nil)
|
||||
GenServer.call(target, {:esg_pubsub, :get_state})
|
||||
end
|
||||
|
||||
@spec publish(any) :: :ok
|
||||
def publish(data) do
|
||||
Registry.dispatch(__MODULE__, self(), fn entries ->
|
||||
for {pid, _} <- entries, do: send(pid, {:esg_pubsub, :pub, data})
|
||||
end)
|
||||
end
|
||||
|
||||
@spec clear(atom | pid) :: :ok
|
||||
def clear(target) when is_atom(target) or is_pid(target) do
|
||||
target = if is_atom(target), do: Process.whereis(target), else: target
|
||||
|
||||
:ok = Registry.unregister(__MODULE__, target)
|
||||
end
|
||||
end
|
44
lib/web/endpoint.ex
Normal file
44
lib/web/endpoint.ex
Normal file
|
@ -0,0 +1,44 @@
|
|||
defmodule ExSpeedGame.Web.Endpoint do
|
||||
use Phoenix.Endpoint, otp_app: :ex_speed_game
|
||||
|
||||
# The session will be stored in the cookie and signed,
|
||||
# this means its contents can be read but not tampered with.
|
||||
# Set :encryption_salt if you would also like to encrypt it.
|
||||
@session_options [
|
||||
store: :cookie,
|
||||
key: "_ex_speed_game_web_key",
|
||||
signing_salt: "XKy30H5R"
|
||||
]
|
||||
|
||||
socket("/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]])
|
||||
|
||||
# Serve at "/" the static files from "priv/static" directory.
|
||||
#
|
||||
# You should set gzip to true if you are running phx.digest
|
||||
# when deploying your static files in production.
|
||||
plug(Plug.Static,
|
||||
at: "/",
|
||||
from: :ex_speed_game,
|
||||
gzip: false,
|
||||
only: ~w(css fonts images js favicon.ico robots.txt)
|
||||
)
|
||||
|
||||
plug(Phoenix.LiveDashboard.RequestLogger,
|
||||
param_key: "request_logger",
|
||||
cookie_key: "request_logger"
|
||||
)
|
||||
|
||||
plug(Plug.RequestId)
|
||||
plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])
|
||||
|
||||
plug(Plug.Parsers,
|
||||
parsers: [:urlencoded, :multipart, :json],
|
||||
pass: ["*/*"],
|
||||
json_decoder: Phoenix.json_library()
|
||||
)
|
||||
|
||||
plug(Plug.MethodOverride)
|
||||
plug(Plug.Head)
|
||||
plug(Plug.Session, @session_options)
|
||||
plug(ExSpeedGame.Web.Router)
|
||||
end
|
52
lib/web/live/main_live/main.ex
Normal file
52
lib/web/live/main_live/main.ex
Normal file
|
@ -0,0 +1,52 @@
|
|||
defmodule ExSpeedGame.Web.MainLive.Main do
|
||||
use ExSpeedGame.Web, :live_view
|
||||
require Logger
|
||||
|
||||
alias ExSpeedGame.PubSub
|
||||
alias ExSpeedGame.Game.{Menu}
|
||||
alias ExSpeedGame.Game.Modes.{Speed}
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
menu_state = PubSub.subscribe(ExSpeedGame.Game.Menu)
|
||||
|
||||
socket = assign(socket, score: 0, queue_len: 0)
|
||||
|
||||
{:ok, set_menu_assigns(socket, menu_state)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(msg, socket)
|
||||
|
||||
def handle_info({:esg_pubsub, :pub, %Menu.State{} = menu_state}, socket) do
|
||||
socket =
|
||||
if socket.assigns.mode == :menu and menu_state.mode == :ingame do
|
||||
set_game_assigns(socket, PubSub.subscribe(menu_state.game))
|
||||
else
|
||||
socket
|
||||
end
|
||||
|
||||
{:noreply, set_menu_assigns(socket, menu_state)}
|
||||
end
|
||||
|
||||
def handle_info({:esg_pubsub, :pub, %Speed.State{} = game_state}, socket) do
|
||||
{:noreply, set_game_assigns(socket, game_state)}
|
||||
end
|
||||
|
||||
def handle_info(msg, socket) do
|
||||
Logger.debug("Got unknown message into LV: #{inspect(msg)}")
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
defp set_menu_assigns(socket, %Menu.State{mode: mode, index: index}) do
|
||||
assign(socket, mode: mode, title: Menu.index_name(index))
|
||||
end
|
||||
|
||||
defp set_game_assigns(socket, %Speed.State{} = game_state) do
|
||||
assign(socket,
|
||||
mode: :ingame,
|
||||
score: game_state.score,
|
||||
queue_len: game_state.queue_len
|
||||
)
|
||||
end
|
||||
end
|
13
lib/web/live/main_live/main.html.leex
Normal file
13
lib/web/live/main_live/main.html.leex
Normal file
|
@ -0,0 +1,13 @@
|
|||
<h1><%= @title %></h1>
|
||||
|
||||
<%= if @mode == :ingame do %>
|
||||
<div id="game_score">
|
||||
<p>Score</p>
|
||||
<p><%= @score %></p>
|
||||
</div>
|
||||
|
||||
<div id="queue_len">
|
||||
<p>Queue length</p>
|
||||
<p><%= @queue_len %></p>
|
||||
</div>
|
||||
<% end %>
|
30
lib/web/router.ex
Normal file
30
lib/web/router.ex
Normal file
|
@ -0,0 +1,30 @@
|
|||
defmodule ExSpeedGame.Web.Router do
|
||||
use ExSpeedGame.Web, :router
|
||||
|
||||
import Phoenix.LiveDashboard.Router
|
||||
|
||||
pipeline :browser do
|
||||
plug(:accepts, ["html"])
|
||||
plug(:fetch_session)
|
||||
plug(:fetch_live_flash)
|
||||
plug(:put_root_layout, {ExSpeedGame.Web.LayoutView, :root})
|
||||
plug(:protect_from_forgery)
|
||||
plug(:put_secure_browser_headers)
|
||||
end
|
||||
|
||||
pipeline :api do
|
||||
plug(:accepts, ["json"])
|
||||
end
|
||||
|
||||
scope "/", ExSpeedGame.Web do
|
||||
pipe_through(:browser)
|
||||
|
||||
live("/", MainLive.Main, :main)
|
||||
live_dashboard("/dashboard", metrics: ExSpeedGame.Web.Telemetry)
|
||||
end
|
||||
|
||||
# Other scopes may use custom stacks.
|
||||
# scope "/api", ExSpeedGame.Web do
|
||||
# pipe_through :api
|
||||
# end
|
||||
end
|
46
lib/web/telemetry.ex
Normal file
46
lib/web/telemetry.ex
Normal file
|
@ -0,0 +1,46 @@
|
|||
defmodule ExSpeedGame.Web.Telemetry do
|
||||
use Supervisor
|
||||
import Telemetry.Metrics
|
||||
|
||||
def start_link(arg) do
|
||||
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(_arg) do
|
||||
children = [
|
||||
{:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
|
||||
# Add reporters as children of your supervision tree.
|
||||
# {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
|
||||
]
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
end
|
||||
|
||||
def metrics do
|
||||
[
|
||||
# Phoenix Metrics
|
||||
summary("phoenix.endpoint.stop.duration",
|
||||
unit: {:native, :millisecond}
|
||||
),
|
||||
summary("phoenix.router_dispatch.stop.duration",
|
||||
tags: [:route],
|
||||
unit: {:native, :millisecond}
|
||||
),
|
||||
|
||||
# VM Metrics
|
||||
summary("vm.memory.total", unit: {:byte, :kilobyte}),
|
||||
summary("vm.total_run_queue_lengths.total"),
|
||||
summary("vm.total_run_queue_lengths.cpu"),
|
||||
summary("vm.total_run_queue_lengths.io")
|
||||
]
|
||||
end
|
||||
|
||||
defp periodic_measurements do
|
||||
[
|
||||
# A module, function and arguments to be invoked periodically.
|
||||
# This function must call :telemetry.execute/3 and a metric must be added above.
|
||||
# {ExSpeedGame.Web, :count_users, []}
|
||||
]
|
||||
end
|
||||
end
|
11
lib/web/templates/layout/live.html.leex
Normal file
11
lib/web/templates/layout/live.html.leex
Normal file
|
@ -0,0 +1,11 @@
|
|||
<main role="main" class="container">
|
||||
<p class="alert alert-info" role="alert"
|
||||
phx-click="lv:clear-flash"
|
||||
phx-value-key="info"><%= live_flash(@flash, :info) %></p>
|
||||
|
||||
<p class="alert alert-danger" role="alert"
|
||||
phx-click="lv:clear-flash"
|
||||
phx-value-key="error"><%= live_flash(@flash, :error) %></p>
|
||||
|
||||
<%= @inner_content %>
|
||||
</main>
|
15
lib/web/templates/layout/root.html.leex
Normal file
15
lib/web/templates/layout/root.html.leex
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<%= csrf_meta_tag() %>
|
||||
<%= live_title_tag assigns[:page_title] || "ExSpeedGame", suffix: " · Phoenix Framework" %>
|
||||
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
|
||||
<script defer type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
|
||||
</head>
|
||||
<body>
|
||||
<%= @inner_content %>
|
||||
</body>
|
||||
</html>
|
16
lib/web/views/error_view.ex
Normal file
16
lib/web/views/error_view.ex
Normal file
|
@ -0,0 +1,16 @@
|
|||
defmodule ExSpeedGame.Web.ErrorView do
|
||||
use ExSpeedGame.Web, :view
|
||||
|
||||
# If you want to customize a particular status code
|
||||
# for a certain format, you may uncomment below.
|
||||
# def render("500.html", _assigns) do
|
||||
# "Internal Server Error"
|
||||
# end
|
||||
|
||||
# By default, Phoenix returns the status message from
|
||||
# the template name. For example, "404.html" becomes
|
||||
# "Not Found".
|
||||
def template_not_found(template, _assigns) do
|
||||
Phoenix.Controller.status_message_from_template(template)
|
||||
end
|
||||
end
|
3
lib/web/views/layout_view.ex
Normal file
3
lib/web/views/layout_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
|||
defmodule ExSpeedGame.Web.LayoutView do
|
||||
use ExSpeedGame.Web, :view
|
||||
end
|
98
lib/web/web.ex
Normal file
98
lib/web/web.ex
Normal file
|
@ -0,0 +1,98 @@
|
|||
defmodule ExSpeedGame.Web do
|
||||
@moduledoc """
|
||||
The entrypoint for defining your web interface, such
|
||||
as controllers, views, channels and so on.
|
||||
|
||||
This can be used in your application as:
|
||||
|
||||
use ExSpeedGame.Web, :controller
|
||||
use ExSpeedGame.Web, :view
|
||||
|
||||
The definitions below will be executed for every view,
|
||||
controller, etc, so keep them short and clean, focused
|
||||
on imports, uses and aliases.
|
||||
|
||||
Do NOT define functions inside the quoted expressions
|
||||
below. Instead, define any helper function in modules
|
||||
and import those modules here.
|
||||
"""
|
||||
|
||||
def controller do
|
||||
quote do
|
||||
use Phoenix.Controller, namespace: ExSpeedGame.Web
|
||||
|
||||
import Plug.Conn
|
||||
alias ExSpeedGame.Web.Router.Helpers, as: Routes
|
||||
end
|
||||
end
|
||||
|
||||
def view do
|
||||
quote do
|
||||
use Phoenix.View,
|
||||
root: "lib/web/templates",
|
||||
namespace: ExSpeedGame.Web
|
||||
|
||||
# Import convenience functions from controllers
|
||||
import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1]
|
||||
|
||||
# Include shared imports and aliases for views
|
||||
unquote(view_helpers())
|
||||
end
|
||||
end
|
||||
|
||||
def live_view do
|
||||
quote do
|
||||
use Phoenix.LiveView,
|
||||
layout: {ExSpeedGame.Web.LayoutView, "live.html"}
|
||||
|
||||
unquote(view_helpers())
|
||||
end
|
||||
end
|
||||
|
||||
def live_component do
|
||||
quote do
|
||||
use Phoenix.LiveComponent
|
||||
|
||||
unquote(view_helpers())
|
||||
end
|
||||
end
|
||||
|
||||
def router do
|
||||
quote do
|
||||
use Phoenix.Router
|
||||
|
||||
import Plug.Conn
|
||||
import Phoenix.Controller
|
||||
import Phoenix.LiveView.Router
|
||||
end
|
||||
end
|
||||
|
||||
def channel do
|
||||
quote do
|
||||
use Phoenix.Channel
|
||||
import ExSpeedGame.Web.Gettext
|
||||
end
|
||||
end
|
||||
|
||||
defp view_helpers do
|
||||
quote do
|
||||
# Use all HTML functionality (forms, tags, etc)
|
||||
use Phoenix.HTML
|
||||
|
||||
# Import LiveView helpers (live_render, live_component, live_patch, etc)
|
||||
import Phoenix.LiveView.Helpers
|
||||
|
||||
# Import basic rendering functionality (render, render_layout, etc)
|
||||
import Phoenix.View
|
||||
|
||||
alias ExSpeedGame.Web.Router.Helpers, as: Routes
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
When used, dispatch to the appropriate controller/view/etc.
|
||||
"""
|
||||
defmacro __using__(which) when is_atom(which) do
|
||||
apply(__MODULE__, which, [])
|
||||
end
|
||||
end
|
20
mix.exs
20
mix.exs
|
@ -2,7 +2,7 @@ defmodule ExSpeedGame.MixProject do
|
|||
use Mix.Project
|
||||
|
||||
@app :ex_speed_game
|
||||
@version "0.1.0"
|
||||
@version "0.2.0"
|
||||
@all_targets [:rpi0]
|
||||
|
||||
def project do
|
||||
|
@ -10,13 +10,14 @@ defmodule ExSpeedGame.MixProject do
|
|||
app: @app,
|
||||
version: @version,
|
||||
elixir: "~> 1.10",
|
||||
archives: [nerves_bootstrap: "~> 1.7"],
|
||||
archives: [nerves_bootstrap: "~> 1.8"],
|
||||
start_permanent: Mix.env() == :prod,
|
||||
build_embedded: true,
|
||||
aliases: [loadconfig: [&bootstrap/1]],
|
||||
deps: deps(),
|
||||
releases: [{@app, release()}],
|
||||
preferred_cli_target: [run: :host, test: :host]
|
||||
preferred_cli_target: [run: :host, test: :host],
|
||||
compilers: [:phoenix] ++ Mix.compilers()
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -31,7 +32,7 @@ defmodule ExSpeedGame.MixProject do
|
|||
def application do
|
||||
[
|
||||
mod: {ExSpeedGame.Application, []},
|
||||
extra_applications: [:logger, :runtime_tools]
|
||||
extra_applications: [:logger, :runtime_tools, :os_mon]
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -45,9 +46,20 @@ defmodule ExSpeedGame.MixProject do
|
|||
{:toolshed, "~> 0.2"},
|
||||
{:circuits_gpio, "~> 0.4"},
|
||||
|
||||
# Web UI stuff
|
||||
{:phoenix, "~> 1.5.1"},
|
||||
{:phoenix_live_view, "~> 0.12.0"},
|
||||
{:phoenix_html, "~> 2.11"},
|
||||
{:phoenix_live_dashboard, "~> 0.2.0"},
|
||||
{:telemetry_metrics, "~> 0.4.0"},
|
||||
{:telemetry_poller, "~> 0.4.0"},
|
||||
{:jason, "~> 1.0"},
|
||||
{:plug_cowboy, "~> 2.0"},
|
||||
|
||||
# Dependencies for all targets except :host
|
||||
{:nerves_runtime, "~> 0.6", targets: @all_targets},
|
||||
{:nerves_init_gadget, "~> 0.4", targets: @all_targets},
|
||||
{:nerves_network, "~> 0.5", targets: @all_targets},
|
||||
|
||||
# Dependencies for specific targets
|
||||
{:nerves_system_rpi0, "~> 1.10", runtime: false, targets: :rpi0}
|
||||
|
|
16
mix.lock
16
mix.lock
|
@ -1,8 +1,12 @@
|
|||
%{
|
||||
"circuits_gpio": {:hex, :circuits_gpio, "0.4.5", "4d5b0f707c425fc56f03086232259f65482a3d1f1cf15335253636d0bb846446", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "b42d28d60a6cfdfb6b21b66ab0b8c5de0ea5a32b390b61d2fe86a2ad8edb90ad"},
|
||||
"cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"},
|
||||
"cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"},
|
||||
"dns": {:hex, :dns, "2.1.2", "81c46d39f7934f0e73368355126e4266762cf227ba61d5889635d83b2d64a493", [:mix], [{:socket, "~> 0.3.13", [hex: :socket, repo: "hexpm", optional: false]}], "hexpm", "6818589d8e59c03a2c73001e5cd7a957f99c30a796021aa32445ea14d0f3356b"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
|
||||
"jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"},
|
||||
"mdns": {:hex, :mdns, "1.0.3", "f08414daf5636bf5cd364611e838818e9250c91a3282a817ad9174b03e757401", [:mix], [{:dns, "~> 2.0", [hex: :dns, repo: "hexpm", optional: false]}], "hexpm", "6cb44eac9d1d71d0c5b400a383ccdc2b474d2e89a49a1e049e496637a6bab4c1"},
|
||||
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
|
||||
"muontrap": {:hex, :muontrap, "0.5.1", "98fe96d0e616ee518860803a37a29eb23ffc2ca900047cb1bb7fd37521010093", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3c11b7f151b202148912c73cbdd633b76fa68fabc26cc441c9d6d140e22290dc"},
|
||||
"nerves": {:hex, :nerves, "1.5.4", "d5a2a29a642e92277d5680f40d0fadff17796e75faa82de87ba0bc920ffcf818", [:mix], [{:distillery, "~> 2.1", [hex: :distillery, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "283ce855f369ff209f3358d25e58f1ac941d58aef3ce7e8cc0be9919d25bf4f5"},
|
||||
"nerves_firmware_ssh": {:hex, :nerves_firmware_ssh, "0.4.4", "12b0d9c84ec9f79c1b0ac0de1c575372ef972d0c58ce21c36bf354062c6222d9", [:mix], [{:nerves_runtime, "~> 0.6", [hex: :nerves_runtime, repo: "hexpm", optional: false]}], "hexpm", "98c40104d0d2c6e6e8cce22f8c8fd8ad5b4b97f8694e42a9101ca44befac38f0"},
|
||||
|
@ -27,10 +31,22 @@
|
|||
"nerves_toolchain_x86_64_unknown_linux_musl": {:hex, :nerves_toolchain_x86_64_unknown_linux_musl, "1.2.0", "fbe688fa561b03190765e269d4336333c4961a48d2acd3f6cb283443a058e138", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 1.6.0", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm", "9cb676b7fb7a4564b18e1e16f72bc64f06f77346a477033fde6e4c2d1cc2ecff"},
|
||||
"nerves_wpa_supplicant": {:hex, :nerves_wpa_supplicant, "0.5.2", "4ec392fc08faf35f50d1070446c2e5019f6b85bd53f5716f904e3f75716d9596", [:make, :mix], [{:elixir_make, "~> 0.5", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "c286ed5397fa185ab986226596eeb72d8f5f9f1b1156103418193f85905da713"},
|
||||
"one_dhcpd": {:hex, :one_dhcpd, "0.2.4", "2664f2e1ac72cbae0474a88d1a3d55019ccc3ee582ded382589511627a8ed516", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "38f08c228d066153dbe352b150910345e395eacd2db3c19085d54c0baeeebacb"},
|
||||
"phoenix": {:hex, :phoenix, "1.5.1", "95156589879dc69201d5fc0ebdbfdfc7901a09a3616ea611ec297f81340275a2", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc272b38e79d2881790fccae6f67a9fbe9b790103d6878175ea03d23003152eb"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.2.3", "aba80790b5358877c39e89d4e9a28e65817c22f01601c2eea64749e18e5c9b6e", [:mix], [{:phoenix_html, "~> 2.14.1 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.12.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.4.0 or ~> 0.5.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3871e507e2811022d5af80b45aa61ef2c20d3e7b0465b3a64d1669fffb92fd67"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.12.1", "42f591c781edbf9fab921319076b7ac635d43aa23e6748d2644563326236d7e4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.4.16 or ~> 1.5.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "585321e98df1cd5943e370b9784e950a37ca073744eb534660c9048967c52ab6"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
|
||||
"plug": {:hex, :plug, "1.10.1", "c56a6d9da7042d581159bcbaef873ba9d87f15dce85420b0d287bca19f40f9bd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b5cd52259817eb8a31f2454912ba1cff4990bca7811918878091cb2ab9e52cb8"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.2.1", "fcf58aa33227a4322a050e4783ee99c63c031a2e7f9a2eb7340d55505e17f30f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3b43de24460d87c0971887286e7a20d40462e48eb7235954681a20cee25ddeb6"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
|
||||
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
|
||||
"ring_logger": {:hex, :ring_logger, "0.8.0", "b1baddc269099b2afe2ea3a87b8e2b71e57331c0000038ae55090068aac679db", [:mix], [], "hexpm", "9b2f482e4346c13c11ef555f898202d0ddbfda6e2354e5c6e0559d2b4e0cf781"},
|
||||
"shoehorn": {:hex, :shoehorn, "0.6.0", "f9a1b7d6212cf18ba91c4f71c26076059df33cea4db2eb3c098bfa6673349412", [:mix], [{:distillery, "~> 2.1", [hex: :distillery, repo: "hexpm", optional: true]}], "hexpm", "e54a1f58a121caf8f0f3a355686b2661258b1bc0d4fffef8923bd7b11c2f9d79"},
|
||||
"socket": {:hex, :socket, "0.3.13", "98a2ab20ce17f95fb512c5cadddba32b57273e0d2dba2d2e5f976c5969d0c632", [:mix], [], "hexpm", "f82ea9833ef49dde272e6568ab8aac657a636acb4cf44a7de8a935acb8957c2e"},
|
||||
"system_registry": {:hex, :system_registry, "0.8.2", "df791dc276652fcfb53be4dab823e05f8269b96ac57c26f86a67838dbc0eefe7", [:mix], [], "hexpm", "f7acdede22c73ab0b3735eead7f2095efb2a7a6198366564205274db2ca2a8f8"},
|
||||
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
|
||||
"telemetry_metrics": {:hex, :telemetry_metrics, "0.4.2", "1de986fad9aa6bf81f8a33ddfd16e5d8ab0dec6272e624eb517c1a92a44d41a9", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e56ffed2dbe293ab6cf7c94980faeb368cb360662c1927f54fc634a4ca55362e"},
|
||||
"telemetry_poller": {:hex, :telemetry_poller, "0.4.1", "50d03d976a3b8ab4898d9e873852e688840df47685a13af90af40e1ba43a758b", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c5bacbbcd62c1fe4e4517485bd64312622e9b83683273dcf2627ff224d7d485b"},
|
||||
"toolshed": {:hex, :toolshed, "0.2.11", "0cd5312bd6a48f5b654b6ffa9239a63af9f3d200da414790fe25f066e14842a9", [:mix], [{:nerves_runtime, "~> 0.8", [hex: :nerves_runtime, repo: "hexpm", optional: true]}], "hexpm", "f22ae95d77136f9f7db93cddd40d42bc8252d825f15a772a17d4c7947b6faad5"},
|
||||
"uboot_env": {:hex, :uboot_env, "0.1.1", "b01e3ec0973e99473234f27839e29e63b5b81eba6a136a18a78d049d4813d6c5", [:mix], [], "hexpm", "f7b82da0cb40c8db9c9fb1fc977780ab0c28d961ec1f3c7ab265c4352e4141ae"},
|
||||
}
|
||||
|
|
272
priv/static/css/app.css
Normal file
272
priv/static/css/app.css
Normal file
File diff suppressed because one or more lines are too long
4137
priv/static/js/app.js
Normal file
4137
priv/static/js/app.js
Normal file
File diff suppressed because it is too large
Load diff
1
priv/static/js/app.js.map
Normal file
1
priv/static/js/app.js.map
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue