defmodule ExshomeWeb.Live.AppPage do
@moduledoc """
Generic module for app pages.
"""
alias Exshome.Dependency
alias Phoenix.LiveView
alias Phoenix.LiveView.Socket
import Phoenix.LiveView.Helpers
@callback action() :: atom()
@callback app_module() :: atom()
@callback view_module() :: atom()
@callback icon() :: String.t()
@callback path() :: String.t()
@callback dependencies() :: Keyword.t()
def on_mount(_, _params, _session, %Socket{} = socket) do
socket =
LiveView.attach_hook(
socket,
:dependency_hook,
:handle_info,
&__MODULE__.handle_info/2
)
deps =
for {dependency, key} <- get_dependencies(socket), into: %{} do
{key, Dependency.subscribe(dependency)}
end
{:cont, LiveView.assign(socket, deps: deps)}
end
def handle_info({Dependency, {module, value}}, %Socket{} = socket) do
key = get_dependencies(socket)[module]
if key do
socket = LiveView.update(socket, :deps, &Map.put(&1, key, value))
{:halt, socket}
else
{:cont, socket}
end
end
def handle_info(_event, %Socket{} = socket), do: {:cont, socket}
def render(%{socket: %Socket{} = socket, deps: deps} = assigns) do
missing_deps =
deps
|> Enum.filter(fn {_key, value} -> value == Dependency.NotReady end)
|> Enum.map(fn {key, _value} -> key end)
if Enum.any?(missing_deps) do
~H"Missing dependencies: <%= inspect(missing_deps) %>"
else
template = template_name(socket)
view_module(socket).render(template, assigns)
end
end
def validate_module!(%Macro.Env{module: module}, _bytecode) do
NimbleOptions.validate!(
module.__config__(),
dependencies: [type: :keyword_list],
icon: [type: :string]
)
end
defp get_dependencies(%Socket{view: view}), do: view.dependencies()
defp view_module(%Socket{view: view}) do
view.view_module()
end
defp template_name(%Socket{view: view}), do: view.path()
defmacro __using__(config) do
quote do
@action __MODULE__
|> Module.split()
|> List.last()
|> Macro.underscore()
|> String.to_atom()
@view_module __MODULE__
|> Module.split()
|> Enum.slice(0..0)
|> List.insert_at(-1, ["Web", "View"])
|> List.flatten()
|> Module.safe_concat()
@app_module __MODULE__
|> Module.split()
|> Enum.slice(0..0)
|> Module.safe_concat()
alias ExshomeWeb.Live.AppPage
alias Phoenix.LiveView
alias Phoenix.LiveView.Socket
@afer_compile {AppPage, :validate_module!}
@behaviour AppPage
@impl AppPage
def app_module, do: @app_module
@impl AppPage
def action, do: @action
@impl AppPage
def view_module, do: @view_module
@impl AppPage
def path, do: "#{@action}.html"
@impl AppPage
def icon, do: unquote(config[:icon] || "")
@impl AppPage
def dependencies, do: unquote(config[:dependencies])
use ExshomeWeb, :live_view
on_mount(AppPage)
def __config__, do: unquote(config)
@impl LiveView
defdelegate render(assigns), to: AppPage
end
end
@hook_module Application.compile_env(:exshome, :app_page_hook_module)
if @hook_module do
defoverridable(handle_info: 2)
def handle_info(event, %Socket{} = socket) do
original_result = super(event, socket)
@hook_module.handle_info(event, socket, original_result)
end
end
end