Current section
Files
Jump to
Current section
Files
src/sprocket@internal@patch.erl
-module(sprocket@internal@patch).
-compile([no_auto_import, nowarn_unused_vars]).
-export([patch_to_json/1, create/2]).
-export_type([patch/0]).
-type patch() :: no_op |
{update,
gleam@option:option(list(sprocket@render:rendered_attribute())),
gleam@option:option(list({integer(), patch()}))} |
{replace, sprocket@render:rendered_element()} |
{insert, sprocket@render:rendered_element()} |
remove |
{change, binary()} |
{move, integer(), patch()}.
-spec attr_key(sprocket@render:rendered_attribute()) -> binary().
attr_key(Attribute) ->
case Attribute of
{rendered_attribute, Name, _} ->
Name;
{rendered_event_handler, _, Id} ->
Id
end.
-spec build_attrs_map(list(sprocket@render:rendered_attribute())) -> gleam@map:map_(binary(), sprocket@render:rendered_attribute()).
build_attrs_map(Attributes) ->
_pipe = Attributes,
gleam@list:fold(
_pipe,
gleam@map:new(),
fun(Acc, Attr) -> gleam@map:insert(Acc, attr_key(Attr), Attr) end
).
-spec op_code(patch()) -> integer().
op_code(Op) ->
case Op of
no_op ->
0;
{update, _, _} ->
1;
{replace, _} ->
2;
{insert, _} ->
3;
remove ->
4;
{change, _} ->
5;
{move, _, _} ->
6
end.
-spec attrs_to_json(list(sprocket@render:rendered_attribute())) -> gleam@json:json().
attrs_to_json(Attrs) ->
_pipe = Attrs,
_pipe@1 = gleam@list:map(_pipe, fun(Attr) -> case Attr of
{rendered_attribute, Name, Value} ->
{Name, gleam@json:string(Value)};
{rendered_event_handler, Kind, Id} ->
{gleam@string:concat(
[sprocket@internal@constants:constant(
event_attr_prefix
),
<<"-"/utf8>>,
Kind]
),
gleam@json:string(Id)}
end end),
gleam@json:object(_pipe@1).
-spec compare_attributes_helper(
gleam@map:map_(binary(), sprocket@render:rendered_attribute()),
list(sprocket@render:rendered_attribute())
) -> gleam@option:option(list(sprocket@render:rendered_attribute())).
compare_attributes_helper(Old_attributes, New_attributes) ->
case New_attributes of
[New_attr | Rest_new] ->
case gleam@map:get(Old_attributes, attr_key(New_attr)) of
{ok, Old_attr} ->
case Old_attr =:= New_attr of
true ->
case compare_attributes_helper(
gleam@map:delete(
Old_attributes,
attr_key(Old_attr)
),
Rest_new
) of
{some, _} ->
{some, New_attributes};
none ->
none
end;
false ->
{some, New_attributes}
end;
_ ->
{some, New_attributes}
end;
_ ->
case {gleam@map:size(Old_attributes),
gleam@list:length(New_attributes)} of
{0, 0} ->
none;
{Old_size, New_size} when Old_size > New_size ->
{some, New_attributes}
end
end.
-spec compare_attributes(
list(sprocket@render:rendered_attribute()),
list(sprocket@render:rendered_attribute())
) -> gleam@option:option(list(sprocket@render:rendered_attribute())).
compare_attributes(Old_attributes, New_attributes) ->
compare_attributes_helper(build_attrs_map(Old_attributes), New_attributes).
-spec zip_all(list(LAL), list(LAN)) -> list({gleam@option:option(LAL),
gleam@option:option(LAN)}).
zip_all(A, B) ->
case {A, B} of
{[], []} ->
[];
{[A@1 | Rest_a], [B@1 | Rest_b]} ->
[{{some, A@1}, {some, B@1}} | zip_all(Rest_a, Rest_b)];
{[A@2 | Rest_a@1], []} ->
[{{some, A@2}, none} | zip_all(Rest_a@1, [])];
{[], [B@2 | Rest_b@1]} ->
[{none, {some, B@2}} | zip_all([], Rest_b@1)]
end.
-spec patch_to_json(patch()) -> gleam@json:json().
patch_to_json(Patch) ->
case Patch of
no_op ->
gleam@json:preprocessed_array([gleam@json:int(op_code(Patch))]);
{update, Attrs, Children} ->
gleam@json:preprocessed_array(
[gleam@json:int(op_code(Patch)),
gleam@json:nullable(Attrs, fun attrs_to_json/1),
gleam@json:nullable(Children, fun children_to_json/1)]
);
{replace, El} ->
gleam@json:preprocessed_array(
[gleam@json:int(op_code(Patch)),
(erlang:element(2, sprocket@internal@render@json:renderer()))(
El
)]
);
{insert, El@1} ->
gleam@json:preprocessed_array(
[gleam@json:int(op_code(Patch)),
(erlang:element(2, sprocket@internal@render@json:renderer()))(
El@1
)]
);
remove ->
gleam@json:preprocessed_array([gleam@json:int(op_code(Patch))]);
{change, Text} ->
gleam@json:preprocessed_array(
[gleam@json:int(op_code(Patch)), gleam@json:string(Text)]
);
{move, Index, Move_patch} ->
gleam@json:preprocessed_array(
[gleam@json:int(op_code(Patch)),
gleam@json:int(Index),
patch_to_json(Move_patch)]
)
end.
-spec map_key_to_str({integer(), patch()}) -> {binary(), gleam@json:json()}.
map_key_to_str(C) ->
{Index, Patch} = C,
{gleam@int:to_string(Index), patch_to_json(Patch)}.
-spec children_to_json(list({integer(), patch()})) -> gleam@json:json().
children_to_json(Children) ->
_pipe = Children,
_pipe@1 = gleam@list:map(_pipe, fun map_key_to_str/1),
gleam@json:object(_pipe@1).
-spec compare_children(
list(sprocket@render:rendered_element()),
list(sprocket@render:rendered_element())
) -> gleam@option:option(list({integer(), patch()})).
compare_children(Old_children, New_children) ->
Key_map = begin
_pipe = Old_children,
gleam@list:index_fold(
_pipe,
gleam@map:new(),
fun(Acc, Child, Index) -> case Child of
{rendered_element, _, {some, Key}, _, _} ->
gleam@map:insert(Acc, Key, {Index, Child});
_ ->
Acc
end end
)
end,
_pipe@1 = zip_all(Old_children, New_children),
_pipe@2 = gleam@list:index_map(
_pipe@1,
fun(Index@1, Zipped) ->
{Old_child, New_child} = Zipped,
case {Old_child, New_child} of
{{some, Old_child@1}, {some, New_child@1}} ->
case {Old_child@1, New_child@1} of
{{rendered_element, _, _, _, _},
{rendered_element, _, {some, Key@1}, _, _}} ->
case gleam@map:get(Key_map, Key@1) of
{ok, Found} ->
{Old_index, Old_child@2} = Found,
Child_patch = create(
Old_child@2,
New_child@1
),
case Index@1 =:= Old_index of
true ->
{Index@1, Child_patch};
false ->
{Index@1,
{move, Old_index, Child_patch}}
end;
{error, nil} ->
{Index@1, create(Old_child@1, New_child@1)}
end;
{_, _} ->
{Index@1, create(Old_child@1, New_child@1)}
end;
{none, {some, New_child@2}} ->
case New_child@2 of
{rendered_element, _, {some, Key@2}, _, _} ->
case gleam@map:get(Key_map, Key@2) of
{ok, Found@1} ->
{Old_index@1, Old_child@3} = Found@1,
{Index@1,
{move,
Old_index@1,
create(Old_child@3, New_child@2)}};
{error, nil} ->
{Index@1, {insert, New_child@2}}
end;
_ ->
{Index@1, {insert, New_child@2}}
end;
{{some, _}, _} ->
{Index@1, remove}
end
end
),
_pipe@3 = gleam@list:filter_map(_pipe@2, fun(Child_el) -> case Child_el of
{_, no_op} ->
{error, nil};
{Index@2, Patch} ->
{ok, {Index@2, Patch}}
end end),
(fun(Diff_list) -> case Diff_list of
[] ->
none;
_ ->
{some, Diff_list}
end end)(_pipe@3).
-spec create(
sprocket@render:rendered_element(),
sprocket@render:rendered_element()
) -> patch().
create(Old, New) ->
case {Old, New} of
{{rendered_element, Old_tag, Old_key, Old_attrs, Old_children},
{rendered_element, New_tag, New_key, New_attrs, New_children}} when Old_tag =:= New_tag ->
case Old_key =:= New_key of
true ->
case compare_attributes(Old_attrs, New_attrs) of
{some, Attrs} ->
{update,
{some, Attrs},
compare_children(Old_children, New_children)};
none ->
case compare_children(Old_children, New_children) of
{some, Children} ->
{update, none, {some, Children}};
none ->
no_op
end
end;
false ->
{replace, New}
end;
{{rendered_component, Old_fc, _, Old_props, _, Old_children@1},
{rendered_component, New_fc, _, New_props, _, New_children@1}} when (Old_fc =:= New_fc) andalso (Old_props =:= New_props) ->
case compare_children(Old_children@1, New_children@1) of
{some, Children@1} ->
{update, none, {some, Children@1}};
none ->
no_op
end;
{{rendered_text, Old_text}, {rendered_text, New_text}} ->
case Old_text =:= New_text of
true ->
no_op;
false ->
{change, New_text}
end;
{_, _} ->
{replace, New}
end.