Packages
A pluggable storybook for your Phoenix components.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/phoenix_storybook/rendering/component_renderer.ex
defmodule PhoenixStorybook.Rendering.ComponentRenderer do
@moduledoc """
Responsible for rendering your function & live components.
"""
alias Phoenix.LiveView.Engine, as: LiveViewEngine
alias Phoenix.LiveView.TagEngine
alias PhoenixStorybook.Rendering.{RenderingContext, RenderingVariation}
alias PhoenixStorybook.TemplateHelpers
@doc """
Renders a component from a `RenderingContext`.
Returns a `Phoenix.LiveView.Rendered`.
"""
def render(context)
def render(context = %RenderingContext{type: :component, function: function}) do
render(function, context)
end
def render(context = %RenderingContext{type: :live_component, component: component}) do
render(component, context)
end
def render(fun_or_mod, context = %RenderingContext{}) do
heex =
cond do
TemplateHelpers.variation_template?(context.template) ->
for variation = %RenderingVariation{} <- context.variations, into: "" do
template_heex(
fun_or_mod,
{context.group_id, variation.id},
context.template,
variation,
context.options[:playground_topic]
)
end
TemplateHelpers.variation_group_template?(context.template) ->
heex =
for variation = %RenderingVariation{} <- context.variations, into: "" do
extra_attributes =
extract_placeholder_attributes(
context.template,
variation.id,
context.options[:playground_topic]
)
component_heex(
fun_or_mod,
variation.attributes,
variation.let,
variation.slots,
extra_attributes
)
end
context.template
|> TemplateHelpers.set_variation_dom_id(context.dom_id)
|> TemplateHelpers.set_js_push_variation_id(context.group_id)
|> TemplateHelpers.replace_template_variation_group(heex)
true ->
context.template
|> TemplateHelpers.set_variation_dom_id(context.dom_id)
|> TemplateHelpers.set_js_push_variation_id(context.group_id)
end
render_component_heex(fun_or_mod, heex, context.options)
end
defp component_heex(fun, assigns, _let, [], extra_attrs) when is_function(fun) do
"""
<.#{function_name(fun)} #{attributes_markup(assigns)} #{extra_attrs}/>
"""
end
defp component_heex(fun, assigns, let, slots, extra_attrs) when is_function(fun) do
"""
<.#{function_name(fun)} #{let_markup(let)} #{attributes_markup(assigns)} #{extra_attrs}>
#{slots}
</.#{function_name(fun)}>
"""
end
defp component_heex(module, assigns, _let, [], extra_attrs) when is_atom(module) do
"""
<.live_component module={#{inspect(module)}} #{attributes_markup(assigns)} #{extra_attrs}/>
"""
end
defp component_heex(module, assigns, let, slots, extra_attrs) when is_atom(module) do
"""
<.live_component module={#{inspect(module)}} #{let_markup(let)} #{attributes_markup(assigns)} #{extra_attrs}>
#{slots}
</.live_component>
"""
end
defp template_heex(
fun_or_mod,
variation_id,
template,
%RenderingVariation{dom_id: dom_id, let: let, slots: slots, attributes: attributes},
playground_topic
) do
extra_attributes = extract_placeholder_attributes(template, variation_id, playground_topic)
template
|> TemplateHelpers.set_variation_dom_id(dom_id)
|> TemplateHelpers.set_js_push_variation_id(variation_id)
|> TemplateHelpers.replace_template_variation(
component_heex(fun_or_mod, attributes, let, slots, extra_attributes)
)
end
defp extract_placeholder_attributes(template, _variation_id, _topic = nil) do
TemplateHelpers.extract_placeholder_attributes(template)
end
defp extract_placeholder_attributes(template, variation_id, topic) do
TemplateHelpers.extract_placeholder_attributes(template, {topic, variation_id})
end
defp let_markup(nil), do: ""
defp let_markup(let), do: ":let={#{to_string(let)}}"
defp attributes_markup(attributes) do
Enum.map_join(attributes, " ", fn
{name, {:eval, val}} ->
~s|#{name}={#{val}}|
{name, val} when is_binary(val) ->
~s|#{name}="#{val}"|
{name, val} ->
~s|#{name}={#{inspect(val, structs: false, limit: :infinity, printable_limit: :infinity)}}|
end)
end
defp render_component_heex(fun_or_mod, heex, opts) do
quoted_code =
EEx.compile_string(heex,
engine: TagEngine,
caller: __ENV__,
source: heex,
tag_handler: Phoenix.LiveView.HTMLEngine
)
env =
Map.merge(
__ENV__,
%{
requires: [Kernel],
aliases: eval_quoted_aliases(opts, fun_or_mod),
functions: eval_quoted_functions(opts, fun_or_mod),
macros: [{Kernel, Kernel.__info__(:macros)}]
}
)
{evaluated, _, _} =
Code.eval_quoted_with_env(
quoted_code,
[assigns: %{}],
env
)
LiveViewEngine.live_to_iodata(evaluated)
end
defp eval_quoted_aliases(opts, fun_or_mod) do
default_aliases = [module(fun_or_mod), Phoenix.LiveView.JS]
aliases = Keyword.get(opts, :aliases, [])
eval_quoted_aliases(default_aliases ++ aliases)
end
defp eval_quoted_aliases(modules) do
for mod <- modules, reduce: [] do
aliases ->
alias_name = :"Elixir.#{mod |> Module.split() |> Enum.at(-1) |> String.to_atom()}"
if alias_name == mod do
aliases
else
[{alias_name, mod} | aliases]
end
end
end
defp eval_quoted_functions(opts, fun) when is_function(fun) do
[
{Phoenix.Component, Phoenix.Component.__info__(:functions)},
{Kernel, Kernel.__info__(:functions)},
{function_module(fun), [{function_name(fun), 1}]}
] ++ extra_imports(opts)
end
defp eval_quoted_functions(opts, mod) when is_atom(mod) do
[
{Phoenix.Component, Phoenix.Component.__info__(:functions)},
{Kernel, Kernel.__info__(:functions)}
] ++ extra_imports(opts)
end
defp extra_imports(opts) do
for {mod, imports} <- Keyword.get(opts, :imports, []), imp <- imports do
{mod, [imp]}
end
end
defp module(fun) when is_function(fun), do: function_module(fun)
defp module(mod) when is_atom(mod), do: mod
defp function_module(fun), do: Function.info(fun)[:module]
defp function_name(fun), do: Function.info(fun)[:name]
end