Skip to content

Commit 02b628a

Browse files
Merge pull request #68 from liveview-native/layouts
Add support for native layouts
2 parents a1aab01 + a0976ed commit 02b628a

17 files changed

+309
-75
lines changed

lib/live_view_native/component.ex

+2-6
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,9 @@ defmodule LiveViewNative.Component do
1616
end
1717
```
1818
"""
19-
defmacro __using__(opts \\ []) do
20-
stylesheet = opts[:stylesheet]
21-
19+
defmacro __using__(_opts \\ []) do
2220
quote do
23-
use LiveViewNative.Extensions,
24-
role: :component,
25-
stylesheet: unquote(stylesheet)
21+
use LiveViewNative.Extensions, role: :component
2622
end
2723
end
2824
end

lib/live_view_native/extensions.ex

+19-11
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ defmodule LiveViewNative.Extensions do
88
should use `LiveViewNative.LiveView` or `LiveViewNative.LiveComponent`
99
respectively.
1010
"""
11-
defmacro __using__(opts \\ []) do
12-
quote bind_quoted: [caller: Macro.escape(__CALLER__)] do
11+
defmacro __using__(opts) do
12+
role = opts[:role]
13+
14+
quote bind_quoted: [caller: Macro.escape(__CALLER__), role: role], location: :keep do
15+
Code.put_compiler_option(:ignore_module_conflict, true)
16+
1317
for {platform_id, platform_context} <- LiveViewNative.platforms() do
18+
require EEx
19+
1420
platform_module = Module.concat(__ENV__.module, platform_context.template_namespace)
1521

1622
defmodule :"#{platform_module}" do
@@ -35,18 +41,20 @@ defmodule LiveViewNative.Extensions do
3541
platform_modifiers: platform_context.platform_modifiers || [],
3642
platform_module: platform_module
3743

38-
use LiveViewNative.Extensions.RenderMacro,
39-
platform_id: platform_id,
40-
render_macro: platform_context.render_macro
41-
42-
use LiveViewNative.Extensions.InlineRender,
43-
platform_id: platform_id
44+
if is_nil(platform_context.render_macro) do
45+
use LiveViewNative.Extensions.InlineRender,
46+
platform_id: platform_id,
47+
role: role
48+
else
49+
use LiveViewNative.Extensions.RenderMacro,
50+
platform_id: platform_id,
51+
render_macro: platform_context.render_macro,
52+
role: role
53+
end
4454
end
4555

4656
use LiveViewNative.Extensions.Render
47-
48-
use LiveViewNative.Extensions.Stylesheets,
49-
module: __ENV__.module
57+
use LiveViewNative.Extensions.Stylesheets, module: __ENV__.module
5058
end
5159
end
5260
end

lib/live_view_native/extensions/inline_render.ex

+7-4
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ defmodule LiveViewNative.Extensions.InlineRender do
3131
"""
3232
defmacro __using__(opts \\ []) do
3333
quote bind_quoted: [
34-
platform_id: opts[:platform_id],
35-
stylesheet: opts[:stylesheet]
36-
] do
34+
platform_id: opts[:platform_id],
35+
stylesheet: opts[:stylesheet],
36+
role: opts[:role]
37+
], location: :keep do
3738
require EEx
3839

3940
defmacro sigil_LVN({:<<>>, meta, [expr]}, modifiers) do
@@ -50,8 +51,10 @@ defmodule LiveViewNative.Extensions.InlineRender do
5051
file: __CALLER__.file,
5152
indentation: meta[:indentation] || 0,
5253
line: __CALLER__.line + 1,
54+
persist_class_tree: true,
5355
stylesheet: unquote(stylesheet),
54-
tag_handler: LiveViewNative.TagEngine
56+
tag_handler: LiveViewNative.TagEngine,
57+
with_stylesheet_wrapper: unquote(role) != :component
5558
]
5659

5760
expr = LiveViewNative.Templates.precompile(expr, unquote(platform_id), base_opts)

lib/live_view_native/extensions/modifiers.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ defmodule LiveViewNative.Extensions.Modifiers do
100100
modifiers_struct: opts[:modifiers_struct],
101101
platform_modifiers: opts[:platform_modifiers],
102102
platform_module: opts[:platform_module]
103-
] do
103+
], location: :keep do
104104
all_modifiers = Keyword.merge(platform_modifiers, custom_modifiers)
105105

106106
if is_nil(platform_module) do

lib/live_view_native/extensions/render_macro.ex

+6-5
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ defmodule LiveViewNative.Extensions.RenderMacro do
1010
defmacro __using__(opts \\ []) do
1111
quote bind_quoted: [
1212
render_macro: opts[:render_macro],
13-
platform_id: opts[:platform_id]
14-
] do
15-
require EEx
16-
13+
platform_id: opts[:platform_id],
14+
role: opts[:role]
15+
], location: :keep do
1716
defmacro unquote(:"#{render_macro}")({:<<>>, meta, [expr]}, _modifiers) do
1817
unless Macro.Env.has_var?(__CALLER__, {:assigns, nil}) do
1918
raise "#{unquote(render_macro)} requires a variable named \"assigns\" to exist and be set to a map"
@@ -28,7 +27,9 @@ defmodule LiveViewNative.Extensions.RenderMacro do
2827
file: __CALLER__.file,
2928
indentation: meta[:indentation] || 0,
3029
line: __CALLER__.line + 1,
31-
tag_handler: LiveViewNative.TagEngine
30+
persist_class_tree: true,
31+
tag_handler: LiveViewNative.TagEngine,
32+
with_stylesheet_wrapper: unquote(role) != :component
3233
]
3334

3435
expr = LiveViewNative.Templates.precompile(expr, unquote(platform_id), base_opts)

lib/live_view_native/extensions/stylesheets.ex

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ defmodule LiveViewNative.Extensions.Stylesheets do
99
defmacro __using__(opts \\ []) do
1010
module = opts[:module]
1111

12-
quote bind_quoted: [module: module] do
13-
def __compiled_stylesheet__ do
12+
quote bind_quoted: [module: module], location: :keep do
13+
def __compiled_stylesheet__(stylesheet_key) do
1414
class_tree_module =
1515
Module.safe_concat([LiveViewNative, Internal, ClassTree, unquote(module)])
1616

17-
class_tree = apply(class_tree_module, :class_tree, [])
17+
class_tree = apply(class_tree_module, :class_tree, [stylesheet_key])
1818

1919
class_names =
2020
class_tree

lib/live_view_native/extensions/templates.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ defmodule LiveViewNative.Extensions.Templates do
2424
template_basename: opts[:template_basename],
2525
template_directory: opts[:template_directory],
2626
template_extension: opts[:template_extension]
27-
] do
27+
], location: :keep do
2828
template_path = Path.join(template_directory, template_basename) <> template_extension
2929

3030
if is_binary(template_path) and File.exists?(template_path) do

lib/live_view_native/layouts.ex

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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

lib/live_view_native/live_component.ex

+3-7
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,9 @@ defmodule LiveViewNative.LiveComponent do
1414
end
1515
```
1616
"""
17-
defmacro __using__(opts \\ []) do
18-
stylesheet = opts[:stylesheet]
19-
20-
quote do
21-
use LiveViewNative.Extensions,
22-
role: :live_component,
23-
stylesheet: unquote(stylesheet)
17+
defmacro __using__(_opts \\ []) do
18+
quote location: :keep do
19+
use LiveViewNative.Extensions, role: :live_component
2420
end
2521
end
2622
end

lib/live_view_native/live_session.ex

+16-1
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ defmodule LiveViewNative.LiveSession do
1313
case get_native_assigns(socket, params) do
1414
%Assigns{} = native_assigns ->
1515
assigns = Map.from_struct(native_assigns)
16+
socket =
17+
socket
18+
|> assign(assigns)
19+
|> put_native_layout()
1620

17-
{:cont, assign(socket, assigns)}
21+
{:cont, socket}
1822

1923
_ ->
2024
{:cont, socket}
@@ -73,4 +77,15 @@ defmodule LiveViewNative.LiveSession do
7377
end
7478

7579
defp put_target(assigns, _lvn_params), do: assigns
80+
81+
defp put_native_layout(%Socket{} = socket) do
82+
with %Socket{assigns: %{format: format}, private: private, view: view} when not is_nil(view) <- socket,
83+
%{layout: {layout_mod, layout_name}} <- apply(view, :__live__, [])
84+
do
85+
%Socket{socket | private: Map.put(private, :live_layout, {layout_mod, "#{layout_name}_#{format}"})}
86+
else
87+
_ ->
88+
socket
89+
end
90+
end
7691
end

lib/live_view_native/live_view.ex

+3-7
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,11 @@ defmodule LiveViewNative.LiveView do
1616
end
1717
```
1818
"""
19-
defmacro __using__(opts \\ []) do
20-
stylesheet = opts[:stylesheet]
21-
22-
quote do
19+
defmacro __using__(_opts \\ []) do
20+
quote location: :keep do
2321
on_mount {LiveViewNative.LiveSession, :live_view_native}
2422

25-
use LiveViewNative.Extensions,
26-
role: :live_view,
27-
stylesheet: unquote(stylesheet)
23+
use LiveViewNative.Extensions, role: :live_view
2824
end
2925
end
3026
end

lib/live_view_native/modclasses.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ defmodule LiveViewNative.Modclasses do
44
defmacro __using__(opts) do
55
platform_id = "#{opts[:platform]}"
66

7-
quote do
7+
quote location: :keep do
88
with %{} = platforms <- LiveViewNative.platforms(),
99
%LiveViewNativePlatform.Env{} = context <- Map.get(platforms, unquote(platform_id)) do
1010
use LiveViewNative.Extensions.Modifiers,

0 commit comments

Comments
 (0)