defmodule Moon.Convert.Template do
@moduledoc "Functions for converting html tokens surface -> live_view"
require Logger
import Moon.Convert.Ast
defp translate_slot(name, attrs, children) do
str =
[name | attrs |> Enum.map(&translate_slot_attr/1) |> Enum.filter(&(!!&1))]
|> Enum.join(", ")
case children do
[] ->
{:expr, "render_slot(#{str})", %{}}
[{:expr, expr, _}] ->
{:expr, "render_slot(#{str}) || #{expr}", %{}}
_ ->
# {#if slot_assigned?(@default)}\n {render_slot(@default)}\n {#else}\n children {/if}
{:block, "if",
[
{:root, {:attribute_expr, "has_slot?(#{name})", %{}}, %{}}
],
[
{:block, :default, [],
[
"\n ",
{:expr, "render_slot(#{str})", %{}},
"\n "
], %{}},
{:block, "else", [], children, %{}}
], %{has_sub_blocks?: true}}
end
end
def translate_node(text, _) when is_binary(text), do: text
def translate_node({:expr, expr, meta}, _), do: {:expr, expr, meta}
# TODO: context_put & children
def translate_node(
{"#slot", [{:root, {:attribute_expr, expr, _m2}, _m1} | attrs], children, _m3},
_
) do
translate_slot(expr, attrs, children)
end
def translate_node({"#slot", attrs, children, _}, _) do
translate_slot("@inner_block", attrs, children)
end
def translate_node({type, attributes, children, node_meta}, c) do
{res_type, source_type} = translate_type(type, c)
{res_type, attributes, children} =
pre_translate_node(source_type, {res_type, attributes, children}, c)
{res_type,
attributes
|> List.wrap()
|> Enum.map(&translate_attr(&1, source_type.__props__()))
|> List.flatten(), children, node_meta}
end
def translate_node(other, _), do: other
defp pre_translate_node(Moon.Design.Table, {res_type, attributes, children}, c) do
{attributes, m_name} =
attributes
|> Enum.map(fn
{"items", {:attribute_expr, expr, m1}, m2} ->
[model, models] = expr |> String.split("<-") |> Enum.map(&String.trim/1)
{{"items", {:attribute_expr, models, m1}, m2}, model}
other ->
{other, nil}
end)
|> Enum.reduce({[], nil}, fn {node, model}, {nodes, cur_model} ->
{nodes ++ [node], cur_model || model}
end)
{res_type, attributes,
children
|> Enum.map(fn
{type2, attributes2, children2, meta} ->
{_, source_type} = translate_type(type2, c)
case source_type do
Moon.Design.Table.Column -> {":cols :let={#{m_name}}", attributes2, children2, meta}
_ -> {type2, attributes2, children2, meta}
end
other ->
other
end)}
end
defp pre_translate_node(Moon.Design.Form, {_, attributes, children}, c) do
{".form",
[
{":let", {:attribute_expr, "form", %{}}, %{}}
| attributes
|> Enum.map(fn
{event, {:attribute_expr, expr, m1}, m2} when event in ~w(change submit) ->
[
{"phx-#{event}", {:attribute_expr, "Event.from(#{expr}).name", m1}, m2},
{"phx-target", {:attribute_expr, "Event.from(#{expr}).target", m1}, m2}
]
{event, expr, m2} when event in ~w(change submit) and is_binary(expr) ->
[
{"phx-#{event}", expr, m2},
{"phx-target", {:attribute_expr, "@myself", m2}, m2}
]
other ->
other
end)
|> List.flatten()
],
children
|> Enum.map(fn
{type2, attributes2, children2, meta} ->
{_, source_type2} = translate_type(type2, c)
case source_type2 do
Moon.Design.Form.Field ->
{_, {_, f_name, _}, _} =
attributes2
|> Enum.find(fn
{"field", {:attribute_expr, _, _}, _} -> true
_ -> false
end)
{type2,
attributes2
|> Enum.map(fn
{"field", {:attribute_expr, expr, m1}, m2} ->
{"field", {:attribute_expr, "form[#{expr}]", m1}, m2}
other ->
other
end),
children2
|> Enum.map(fn
{type3, attributes3, children3, meta3} ->
{type3,
[{"field", {:attribute_expr, "form[#{f_name}]", %{}}, %{}} | attributes3],
children3, meta3}
other ->
other
end), meta}
_ ->
{type2, attributes2, children2, meta}
end
other ->
other
end)}
end
defp pre_translate_node(_, {res_type, attributes, children}, _),
do: {res_type, attributes, children}
defp translate_slot_attr({"generator_value", {:attribute_expr, expr, _m2}, _m1}), do: expr
defp translate_slot_attr(other) do
Logger.warning("Unknown slot attribute: #{inspect(other)}")
nil
end
defp get_alias(alias_, aliases) do
[key | other] = alias_ |> String.split(".")
(((aliases[key] && [aliases[key]]) || ["Elixir", key]) ++ other)
|> Enum.join(".")
|> String.to_atom()
end
defp translate_type(type, c = %{aliases: aliases}) do
cond do
!String.match?(type, ~r/^[A-Z].*$/) ->
{type, %{__props__: []}}
type == "Icon" ->
{".icon", Moon.Icon}
# get_alias(type, aliases) == Moon.Design.Table.Column ->
# {":cols :let={model}", Moon.Design.Table.Column}
get_alias(type, aliases) === nil ->
Logger.warning("Unknown type: #{type}")
{type, %{__props__: []}}
function_exported?(get_alias(type, aliases), :__slot_name__, 0) ->
# TODO: get subcompoent's render function
Logger.warning(
"Replacing #{get_alias(type, aliases)} with slot :#{get_alias(type, aliases).__slot_name__()}"
)
{":#{get_alias(type, aliases).__slot_name__()}", get_alias(type, aliases)}
get_alias(type, aliases).component_type() == Surface.Component ->
mod = get_alias(type, aliases) |> parent_module() |> c.config.module_translates.()
{case Code.ensure_compiled(mod) do
{:module, _} ->
func_name =
type |> String.split(".") |> Enum.at(-1) |> Macro.underscore() |> String.to_atom()
if Keyword.has_key?(mod.__info__(:functions), func_name) do
# TODO: add imports here
"#{(mod in [c.config.module_translates.(c.module), Moon.Light] && "") || mod}.#{func_name}"
else
".moon module={#{type}}"
end
{:error, _} ->
".moon module={#{type}}"
end, get_alias(type, aliases)}
get_alias(type, aliases).component_type() == Surface.LiveComponent ->
mod = get_alias(type, aliases) |> c.config.module_translates.()
{case Code.ensure_compiled(mod) do
{:module, _} -> ".live_component module={#{mod}}"
{:error, _} -> ".moon module={#{type}}"
end, get_alias(type, aliases)}
end
end
defp translate_attr({name, expr, meta}, node_props) when is_atom(name),
do: translate_attr({"#{name}", expr, meta}, node_props)
defp translate_attr({name, expr, meta}, _) when name in ~w(root :let :if :for),
do: {name, expr, meta}
defp translate_attr({":attrs", expr, meta}, _), do: {:root, expr, meta}
# TODO: translate each to data-value attribute
defp translate_attr({":values", {:attribute_expr, expr, m2}, m1}, _),
do: {:root, {:attribute_expr, "data_values(#{expr})", m2}, m1}
# defp translate_attr({":values", expr, m1}),
# do: {:root, {:attribute_expr, "data_values(#{expr})", m1}, m1}
defp translate_attr({":on-" <> name, value, meta}, _) when is_binary(value) do
[
{:"phx-#{name}", value, meta},
{:"phx-target", {:attribute_expr, "@myself", meta}, meta}
]
end
defp translate_attr({":on-" <> name, {:attribute_expr, expr, m2}, m1}, _) do
[
{:"phx-#{name}", {:attribute_expr, "Event.from(#{expr}).name", m2}, m2},
{:"phx-target", {:attribute_expr, "Event.from(#{expr}).target", m2}, m1}
]
end
# defp translate_attr({":" <> name, value, meta}),
# do: {:"phx-#{name}", translate_attr_value(value), meta}
defp translate_attr({name, value, meta}, node_props) do
prop = node_props |> Enum.find(&("#{&1.name}" == "#{name}"))
if prop && prop.type == :event && is_binary(value) do
{:"#{name}", {:attribute_expr, "%Event{name: \"#{value}\", target: @myself}", meta}, meta}
else
{:"#{name}", translate_attr_value(value), meta}
end
end
defp translate_attr_value({:attribute_expr, expr, meta}) do
{:attribute_expr, expr |> translate_attr_values(), meta}
end
defp translate_attr_value(expr), do: expr
defp translate_attr_values(expr) do
case Code.string_to_quoted(expr) do
{:ok, _} -> expr
{:error, _} -> "[#{expr}]"
end
end
end