Current section

Files

Jump to
sprocket src sprocket@render.erl
Raw

src/sprocket@render.erl

-module(sprocket@render).
-compile([no_auto_import, nowarn_unused_vars]).
-export([live_render/4, render/2, render_element/1, traverse/2, find/2, append_attribute/2]).
-export_type([rendered_attribute/0, rendered_element/0, renderer/1, render_result/1]).
-type rendered_attribute() :: {rendered_attribute, binary(), binary()} |
{rendered_event_handler, binary(), binary()}.
-type rendered_element() :: {rendered_element,
binary(),
gleam@option:option(binary()),
list(rendered_attribute()),
list(rendered_element())} |
{rendered_component,
fun((sprocket@socket:socket(), gleam@dynamic:dynamic()) -> {sprocket@socket:socket(),
list(sprocket@element:element())}),
gleam@option:option(binary()),
gleam@dynamic:dynamic(),
sprocket@internal@utils@ordered_map:ordered_map(integer(), sprocket@hooks:hook()),
list(rendered_element())} |
{rendered_text, binary()}.
-type renderer(KRF) :: {renderer, fun((rendered_element()) -> KRF)}.
-type render_result(KRG) :: {render_result, sprocket@socket:socket(), KRG}.
-spec get_key(sprocket@element:element()) -> gleam@option:option(binary()).
get_key(El) ->
case El of
{keyed, Key, _} ->
{some, Key};
_ ->
none
end.
-spec find_by_key(list(rendered_element()), binary()) -> gleam@option:option(rendered_element()).
find_by_key(Children, Key) ->
_pipe = gleam@list:find(Children, fun(Child) -> case Child of
{rendered_component, _, {some, Child_key}, _, _, _} ->
Child_key =:= Key;
{rendered_element, _, {some, Child_key@1}, _, _} ->
Child_key@1 =:= Key;
_ ->
false
end end),
gleam@option:from_result(_pipe).
-spec get_child_by_key(
gleam@option:option(rendered_element()),
gleam@option:option(binary())
) -> gleam@option:option(rendered_element()).
get_child_by_key(Prev, Key) ->
case {Prev, Key} of
{{some, {rendered_component, _, _, _, _, Children}}, {some, Key@1}} ->
find_by_key(Children, Key@1);
{{some, {rendered_element, _, _, _, Children@1}}, {some, Key@2}} ->
find_by_key(Children@1, Key@2);
{_, _} ->
none
end.
-spec safe_html(sprocket@socket:socket(), binary()) -> render_result(rendered_element()).
safe_html(Socket, Html) ->
{render_result, Socket, {rendered_text, Html}}.
-spec raw(sprocket@socket:socket(), binary()) -> render_result(rendered_element()).
raw(Socket, Text) ->
{render_result, Socket, {rendered_text, Text}}.
-spec live_render(
sprocket@socket:socket(),
sprocket@element:element(),
gleam@option:option(binary()),
gleam@option:option(rendered_element())
) -> render_result(rendered_element()).
live_render(Socket, El, Key, Prev) ->
case El of
{element, Tag, Attrs, Children} ->
element(Socket, Tag, Key, Attrs, Children, Prev);
{component, Fc, Props} ->
component(Socket, Fc, Key, Props, Prev);
{debug, Id, Meta, El@1} ->
gleam@io:debug({Id, Meta}),
gleam@io:debug(live_render(Socket, El@1, Key, Prev));
{keyed, Key@1, El@2} ->
live_render(Socket, El@2, {some, Key@1}, Prev);
{ignore_update, El@3} ->
case Prev of
{some, Prev@1} ->
_pipe = Prev@1,
_pipe@1 = append_attribute(
_pipe,
{rendered_attribute,
sprocket@internal@constants:constant(
ignore_update_attr
),
<<"true"/utf8>>}
),
{render_result, Socket, _pipe@1};
none ->
{render_result, Socket@1, Rendered} = live_render(
Socket,
El@3,
Key,
Prev
),
_pipe@2 = Rendered,
_pipe@3 = append_attribute(
_pipe@2,
{rendered_attribute,
sprocket@internal@constants:constant(
ignore_update_attr
),
<<"true"/utf8>>}
),
{render_result, Socket@1, _pipe@3}
end;
{safe_html, Html} ->
safe_html(Socket, Html);
{raw, Text} ->
raw(Socket, Text)
end.
-spec render(sprocket@element:element(), renderer(KRH)) -> KRH.
render(El, Renderer) ->
{render_result, _, Rendered} = live_render(
sprocket@socket:new(none),
El,
none,
none
),
(erlang:element(2, Renderer))(Rendered).
-spec render_element(sprocket@element:element()) -> rendered_element().
render_element(El) ->
{render_result, _, Rendered} = live_render(
sprocket@socket:new(none),
El,
none,
none
),
Rendered.
-spec maybe_matching_el(rendered_element(), sprocket@element:element()) -> gleam@option:option(rendered_element()).
maybe_matching_el(Prev_child, Child) ->
case {Prev_child, Child} of
{{rendered_component, Prev_fc, _, _, _, _}, {component, Fc, _}} ->
case Prev_fc =:= Fc of
true ->
{some, Prev_child};
false ->
none
end;
{{rendered_element, Prev_tag, _, _, _}, {element, Tag, _, _}} ->
case Prev_tag =:= Tag of
true ->
{some, Prev_child};
false ->
none
end;
{_, {debug, _, _, El}} ->
maybe_matching_el(Prev_child, El);
{_, _} ->
none
end.
-spec get_matching_prev_child_by_index(
gleam@option:option(rendered_element()),
sprocket@element:element(),
integer()
) -> gleam@option:option(rendered_element()).
get_matching_prev_child_by_index(Prev, Child, Index) ->
case Prev of
{some, {rendered_component, _, _, _, _, Children}} ->
case gleam@list:at(Children, Index) of
{ok, Prev_child} ->
maybe_matching_el(Prev_child, Child);
{error, nil} ->
none
end;
{some, {rendered_element, _, _, _, Children@1}} ->
case gleam@list:at(Children@1, Index) of
{ok, Prev_child@1} ->
maybe_matching_el(Prev_child@1, Child);
{error, nil} ->
none
end;
_ ->
none
end.
-spec find_prev_child(
gleam@option:option(rendered_element()),
sprocket@element:element(),
integer()
) -> gleam@option:option(rendered_element()).
find_prev_child(Prev, Child, Index) ->
Child_key = get_key(Child),
gleam@option:'or'(
get_child_by_key(Prev, Child_key),
get_matching_prev_child_by_index(Prev, Child, Index)
).
-spec element(
sprocket@socket:socket(),
binary(),
gleam@option:option(binary()),
list(sprocket@html@attributes:attribute()),
list(sprocket@element:element()),
gleam@option:option(rendered_element())
) -> render_result(rendered_element()).
element(Socket, Tag, Key, Attrs, Children, Prev) ->
{render_result, Socket@3, Rendered_attrs@1} = gleam@list:fold(
Attrs,
{render_result, Socket, []},
fun(Acc, Current) ->
{render_result, Socket@1, Rendered_attrs} = Acc,
case Current of
{attribute, Name, Value} ->
_assert_subject = gleam@dynamic:string(Value),
{ok, Value@1} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"sprocket/render"/utf8>>,
function => <<"element"/utf8>>,
line => 133})
end,
{render_result,
Socket@1,
[{rendered_attribute, Name, Value@1} | Rendered_attrs]};
{event, Kind, Identifiable_cb} ->
{Socket@2, Id} = sprocket@socket:push_event_handler(
Socket@1,
Identifiable_cb
),
{render_result,
Socket@2,
[{rendered_event_handler,
Kind,
sprocket@internal@utils@unique:to_string(Id)} |
Rendered_attrs]}
end
end
),
{render_result, Socket@6, Children@1} = begin
_pipe = Children,
gleam@list:index_fold(
_pipe,
{render_result, Socket@3, []},
fun(Acc@1, Child, I) ->
{render_result, Socket@4, Rendered} = Acc@1,
Prev_child = find_prev_child(Prev, Child, I),
{render_result, Socket@5, Rendered_child} = live_render(
Socket@4,
Child,
none,
Prev_child
),
{render_result, Socket@5, [Rendered_child | Rendered]}
end
)
end,
{render_result,
Socket@6,
{rendered_element,
Tag,
Key,
gleam@list:reverse(Rendered_attrs@1),
gleam@list:reverse(Children@1)}}.
-spec component(
sprocket@socket:socket(),
fun((sprocket@socket:socket(), gleam@dynamic:dynamic()) -> {sprocket@socket:socket(),
list(sprocket@element:element())}),
gleam@option:option(binary()),
gleam@dynamic:dynamic(),
gleam@option:option(rendered_element())
) -> render_result(rendered_element()).
component(Socket, Fc, Key, Props, Prev) ->
Socket@1 = case Prev of
none ->
erlang:setelement(
2,
Socket,
{component_wip,
sprocket@internal@utils@ordered_map:new(),
0,
true}
);
{some, {rendered_component, _, _, _, Hooks, _}} ->
erlang:setelement(2, Socket, {component_wip, Hooks, 0, false});
{some, _} ->
sprocket@internal@logger:error(<<"Invalid previous element"/utf8>>),
erlang:error(#{gleam_error => panic,
message => <<"panic expression evaluated"/utf8>>,
module => <<"sprocket/render"/utf8>>,
function => <<"component"/utf8>>,
line => 198})
end,
{Socket@2, Children} = Fc(Socket@1, Props),
Hooks@1 = erlang:element(2, erlang:element(2, Socket@2)),
{render_result, Socket@5, Children@1} = begin
_pipe = Children,
gleam@list:index_fold(
_pipe,
{render_result, Socket@2, []},
fun(Acc, Child, I) ->
{render_result, Socket@3, Rendered} = Acc,
Prev_child = find_prev_child(Prev, Child, I),
{render_result, Socket@4, Rendered_child} = live_render(
Socket@3,
Child,
none,
Prev_child
),
{render_result, Socket@4, [Rendered_child | Rendered]}
end
)
end,
{render_result,
Socket@5,
{rendered_component,
Fc,
Key,
Props,
Hooks@1,
gleam@list:reverse(Children@1)}}.
-spec traverse(
rendered_element(),
fun((rendered_element()) -> rendered_element())
) -> rendered_element().
traverse(El, Updater) ->
case Updater(El) of
{rendered_component, Fc, Key, Props, Hooks, Children} ->
{rendered_component,
Fc,
Key,
Props,
Hooks,
gleam@list:map(
Children,
fun(Child) -> traverse(Child, Updater) end
)};
{rendered_element, Tag, Key@1, Attrs, Children@1} ->
{rendered_element,
Tag,
Key@1,
Attrs,
gleam@list:map(
Children@1,
fun(Child@1) -> traverse(Child@1, Updater) end
)};
_ ->
El
end.
-spec find(rendered_element(), fun((rendered_element()) -> boolean())) -> {ok,
rendered_element()} |
{error, nil}.
find(El, Matches) ->
case Matches(El) of
true ->
{ok, El};
false ->
case El of
{rendered_component, _, _, _, _, Children} ->
gleam@list:find(
Children,
fun(Child) -> case find(Child, Matches) of
{ok, _} ->
true;
{error, _} ->
false
end end
);
{rendered_element, _, _, _, Children@1} ->
gleam@list:find(
Children@1,
fun(Child@1) -> case find(Child@1, Matches) of
{ok, _} ->
true;
{error, _} ->
false
end end
);
_ ->
{error, nil}
end
end.
-spec append_attribute(rendered_element(), rendered_attribute()) -> rendered_element().
append_attribute(El, Attr) ->
case El of
{rendered_element, Tag, Key, Attrs, Children} ->
{rendered_element,
Tag,
Key,
gleam@list:append(Attrs, [Attr]),
Children};
{rendered_component, Fc, Key@1, Props, Hooks, Children@1} ->
{rendered_component,
Fc,
Key@1,
Props,
Hooks,
gleam@list:map(
Children@1,
fun(Child) -> append_attribute(Child, Attr) end
)};
_ ->
El
end.