|
| 1 | +defmodule LiveViewNative.Layouts do |
| 2 | + def extract_layouts(%{file: file} = opts) do |
| 3 | + file |
| 4 | + |> File.read!() |
| 5 | + |> Code.string_to_quoted!() |
| 6 | + |> extract_layouts_recursive(opts) |
| 7 | + |> List.flatten() |
| 8 | + |> Enum.map(fn layout_params -> {layout_params.render_function, layout_params} end) |
| 9 | + |> Enum.reject(&(format_excluded?(&1, opts))) |
| 10 | + |> Enum.into(%{}) |
| 11 | + |> apply_default_layouts(opts) |
| 12 | + |> generate_class_trees(opts) |
| 13 | + |> persist_class_trees(opts) |
| 14 | + end |
| 15 | + |
| 16 | + def extract_layouts_recursive({:embed_templates, _meta, [template | _args]}, %{} = opts) do |
| 17 | + base_path = Path.join([opts.dirname, Path.dirname(template)]) |
| 18 | + opts = Map.put(opts, :base_path, base_path) |
| 19 | + |
| 20 | + base_path |
| 21 | + |> File.ls!() |
| 22 | + |> Enum.map(&extract_layout(&1, opts)) |
| 23 | + |> Enum.filter(& &1) |
| 24 | + end |
| 25 | + |
| 26 | + def extract_layouts_recursive({_func, _meta, [_ | _] = nodes}, %{} = opts), |
| 27 | + do: Enum.map(nodes, &extract_layouts_recursive(&1, opts)) |
| 28 | + |
| 29 | + def extract_layouts_recursive([do: {:__block__, [], args}], %{} = opts), do: extract_layouts_recursive(args, opts) |
| 30 | + |
| 31 | + def extract_layouts_recursive([_ | _] = nodes, %{} = opts), |
| 32 | + do: Enum.map(nodes, &extract_layouts_recursive(&1, opts)) |
| 33 | + |
| 34 | + def extract_layouts_recursive(_node, _opts), do: [] |
| 35 | + |
| 36 | + def extract_layout(filename, %{platforms: platforms} = opts) do |
| 37 | + template_path = Path.join(opts.base_path, filename) |
| 38 | + |
| 39 | + platforms |
| 40 | + |> Enum.find(&matches_template?(&1, filename)) |
| 41 | + |> compile_layout(template_path, opts) |
| 42 | + end |
| 43 | + |
| 44 | + def compile_layout({_format, platform}, template_path, _opts) do |
| 45 | + func_name = |
| 46 | + template_path |
| 47 | + |> Path.basename() |
| 48 | + |> Path.rootname() |
| 49 | + |> String.replace(".", "_") |
| 50 | + |> String.to_atom() |
| 51 | + |
| 52 | + %{ |
| 53 | + class_tree: %{}, |
| 54 | + template: File.read!(template_path), |
| 55 | + eex_engine: platform.eex_engine, |
| 56 | + platform_id: platform.platform_id, |
| 57 | + render_function: func_name, |
| 58 | + tag_handler: platform.tag_handler, |
| 59 | + template_path: template_path |
| 60 | + } |
| 61 | + end |
| 62 | + |
| 63 | + def compile_layout(_platform, _template_path, _opts), do: nil |
| 64 | + |
| 65 | + def matches_template?({_key, %{} = platform}, filename) do |
| 66 | + case platform.template_extension do |
| 67 | + nil -> |
| 68 | + false |
| 69 | + |
| 70 | + extension -> |
| 71 | + String.ends_with?(filename, extension) |
| 72 | + end |
| 73 | + end |
| 74 | + |
| 75 | + def generate_class_trees(%{} = layouts, %{} = opts) do |
| 76 | + Enum.reduce(layouts, layouts, fn {func_name, %{template: template, platform_id: platform_id} = layout}, acc -> |
| 77 | + opts = Map.put(opts, :render_function, {layout.render_function, 1}) |
| 78 | + |
| 79 | + case LiveViewNative.Templates.compile_class_tree(template, platform_id, opts) do |
| 80 | + {:ok, %{} = class_tree} -> |
| 81 | + Map.put(acc, func_name, %{layout | class_tree: class_tree}) |
| 82 | + |
| 83 | + _ -> |
| 84 | + acc |
| 85 | + end |
| 86 | + end) |
| 87 | + end |
| 88 | + |
| 89 | + def persist_class_trees(%{} = layouts, opts) do |
| 90 | + layouts |
| 91 | + |> Enum.map(fn {func_name, %{class_tree: class_tree}} -> {func_name, class_tree} end) |
| 92 | + |> LiveViewNative.Templates.persist_class_tree_map(opts.caller.module) |
| 93 | + |
| 94 | + layouts |
| 95 | + end |
| 96 | + |
| 97 | + ### |
| 98 | + |
| 99 | + defp apply_default_layouts(%{} = layouts, %{default_layouts: true, platforms: platforms} = opts) do |
| 100 | + platforms |
| 101 | + |> Enum.reject(&(format_excluded?(&1, opts))) |
| 102 | + |> Enum.flat_map(fn {format, %{default_layouts: %{} = default_layouts} = platform} -> |
| 103 | + Enum.map(default_layouts, fn {layout_name, layout_source} -> |
| 104 | + {String.to_atom("#{layout_name}_#{format}"), {layout_source, platform}} |
| 105 | + end) |
| 106 | + end) |
| 107 | + |> Enum.into(%{}) |
| 108 | + |> Enum.reduce(layouts, fn {func_name, {layout_source, platform}}, %{} = acc -> |
| 109 | + if Map.has_key?(acc, func_name) do |
| 110 | + acc |
| 111 | + else |
| 112 | + Map.put(acc, func_name, %{ |
| 113 | + template: layout_source, |
| 114 | + render_function: func_name, |
| 115 | + template_path: nil, |
| 116 | + eex_engine: platform.eex_engine, |
| 117 | + platform_id: platform.platform_id, |
| 118 | + tag_handler: platform.tag_handler |
| 119 | + }) |
| 120 | + end |
| 121 | + end) |
| 122 | + end |
| 123 | + |
| 124 | + defp apply_default_layouts(%{} = layouts, _opts), do: layouts |
| 125 | + |
| 126 | + defp format_excluded?({_, %{platform_id: platform_id}}, %{} = opts) do |
| 127 | + case opts do |
| 128 | + %{exclude: [_ | _] = excluded_formats} -> |
| 129 | + platform_id in excluded_formats |
| 130 | + |
| 131 | + _ -> |
| 132 | + false |
| 133 | + end |
| 134 | + end |
| 135 | + |
| 136 | + defmacro __using__(_opts \\ []) do |
| 137 | + quote bind_quoted: [caller: Macro.escape(__CALLER__)], location: :keep do |
| 138 | + use LiveViewNative.Extensions, role: :layouts |
| 139 | + |
| 140 | + layout_templates = |
| 141 | + %{ |
| 142 | + caller: caller, |
| 143 | + default_layouts: true, |
| 144 | + dirname: Path.dirname(__ENV__.file), |
| 145 | + exclude: [:html], |
| 146 | + file: __ENV__.file, |
| 147 | + platforms: LiveViewNative.platforms() |
| 148 | + } |
| 149 | + |> LiveViewNative.Layouts.extract_layouts() |
| 150 | + |> Enum.map(fn {render_func, %{} = layout_params} -> |
| 151 | + if layout_params.template_path do |
| 152 | + @external_resource layout_params.template_path |
| 153 | + end |
| 154 | + |
| 155 | + eex_opts = [ |
| 156 | + caller: caller, |
| 157 | + engine: layout_params.eex_engine, |
| 158 | + file: __ENV__.file, |
| 159 | + render_function: {render_func, 1}, |
| 160 | + source: layout_params.template, |
| 161 | + persist_class_tree: false, |
| 162 | + tag_handler: layout_params.tag_handler |
| 163 | + ] |
| 164 | + LiveViewNative.Templates.compile_class_tree(layout_params.template, layout_params.platform_id, eex_opts) |
| 165 | + expr = LiveViewNative.Templates.with_stylesheet_wrapper(layout_params.template, render_func) |
| 166 | + |
| 167 | + EEx.function_from_string(:def, render_func, expr, [:assigns], eex_opts) |
| 168 | + end) |
| 169 | + end |
| 170 | + end |
| 171 | +end |
0 commit comments