Packages
moon
2.90.1
2.90.5
retired
2.90.4
2.90.3
2.90.2
2.90.1
2.90.0
2.89.4
2.89.1
2.89.0
2.88.0
2.87.11
2.87.10
2.87.9
2.87.8
2.87.7
2.87.6
2.87.5
2.87.4
2.87.3
2.87.2
2.87.1
2.87.0
2.86.1
2.86.0
2.85.1
2.85.0
2.84.0
2.83.0
2.82.0
2.81.4
2.81.3
2.81.2
2.81.1
2.81.0
2.80.2
2.80.1
2.80.0
2.79.13
2.79.12
2.79.11
2.79.10
2.79.9
2.79.8
2.79.7
2.79.6
2.79.5
2.79.4
2.79.3
2.79.2
2.79.1
2.79.0
2.78.4
2.78.3
2.78.2
2.78.1
2.78.0
2.77.0
2.76.5
2.76.4
2.76.3
2.76.2
2.76.1
2.76.0
2.75.0
2.74.1
2.74.0
2.73.8
2.73.7
2.73.6
2.73.5
2.73.4
2.73.3
2.73.2
2.73.1
2.73.0
2.72.5
2.72.4
2.72.3
2.72.2
2.72.1
2.72.0
2.71.1
2.71.0
2.70.0
2.69.2
2.69.1
2.69.0
2.68.11
2.68.10
Components-based design system written in elixir
Current section
Files
Jump to
Current section
Files
lib/moon/convert/template.ex
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