Current section
Files
Jump to
Current section
Files
src/sprocket@internal@patch.erl
-module(sprocket@internal@patch).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
-export([op_code/2, patch_to_json/2, 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;
{rendered_client_hook, _, Id@1} ->
Id@1
end.
-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 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 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 build_key_map(list(sprocket@render:rendered_element())) -> gleam@map:map_(binary(), {integer(),
sprocket@render:rendered_element()}).
build_key_map(Children) ->
_pipe = 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
).
-spec zip_all(list(LQA), list(LQC)) -> list({gleam@option:option(LQA),
gleam@option:option(LQC)}).
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 op_code(patch(), boolean()) -> binary().
op_code(Op, Debug) ->
case Debug of
true ->
case Op of
no_op ->
<<"NoOp"/utf8>>;
{update, _, _} ->
<<"Update"/utf8>>;
{replace, _} ->
<<"Replace"/utf8>>;
{insert, _} ->
<<"Insert"/utf8>>;
remove ->
<<"Remove"/utf8>>;
{change, _} ->
<<"Change"/utf8>>;
{move, _, _} ->
<<"Move"/utf8>>
end;
false ->
case Op of
no_op ->
<<"0"/utf8>>;
{update, _, _} ->
<<"1"/utf8>>;
{replace, _} ->
<<"2"/utf8>>;
{insert, _} ->
<<"3"/utf8>>;
remove ->
<<"4"/utf8>>;
{change, _} ->
<<"5"/utf8>>;
{move, _, _} ->
<<"6"/utf8>>
end
end.
-spec attrs_to_json(list(sprocket@render:rendered_attribute())) -> gleam@json:json().
attrs_to_json(Attrs) ->
_pipe = Attrs,
_pipe@1 = gleam@list:flat_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)}];
{rendered_client_hook, Name@1, Id@1} ->
[{sprocket@internal@constants:constant(event_attr_prefix),
gleam@json:string(Name@1)},
{gleam@string:concat(
[sprocket@internal@constants:constant(
event_attr_prefix
),
<<"-id"/utf8>>]
),
gleam@json:string(Id@1)}]
end end),
gleam@json:object(_pipe@1).
-spec map_index_fold(
gleam@map:map_(LQJ, LQK),
LQN,
fun((LQN, LQK, LQJ, integer()) -> LQN)
) -> {LQN, integer()}.
map_index_fold(Map, Acc, Fold_fn) ->
_pipe = Map,
gleam@map:fold(
_pipe,
{Acc, 0},
fun(Acc@1, Item, Key) ->
{Acc@2, Index} = Acc@1,
{Fold_fn(Acc@2, Key, Item, Index), Index + 1}
end
).
-spec map_key_to_str({integer(), patch()}, boolean()) -> {binary(),
gleam@json:json()}.
map_key_to_str(C, Debug) ->
{Index, Patch} = C,
{gleam@int:to_string(Index), patch_to_json(Patch, Debug)}.
-spec patch_to_json(patch(), boolean()) -> gleam@json:json().
patch_to_json(Patch, Debug) ->
case Patch of
no_op ->
gleam@json:preprocessed_array(
[gleam@json:string(op_code(Patch, Debug))]
);
{update, Attrs, Children} ->
gleam@json:preprocessed_array(
[gleam@json:string(op_code(Patch, Debug)),
gleam@json:nullable(Attrs, fun attrs_to_json/1),
gleam@json:nullable(
Children,
fun(_capture) -> children_to_json(_capture, Debug) end
)]
);
{replace, El} ->
gleam@json:preprocessed_array(
[gleam@json:string(op_code(Patch, Debug)),
(erlang:element(2, sprocket@internal@render@json:renderer()))(
El
)]
);
{insert, El@1} ->
gleam@json:preprocessed_array(
[gleam@json:string(op_code(Patch, Debug)),
(erlang:element(2, sprocket@internal@render@json:renderer()))(
El@1
)]
);
remove ->
gleam@json:preprocessed_array(
[gleam@json:string(op_code(Patch, Debug))]
);
{change, Text} ->
gleam@json:preprocessed_array(
[gleam@json:string(op_code(Patch, Debug)),
gleam@json:string(Text)]
);
{move, Index, Move_patch} ->
gleam@json:preprocessed_array(
[gleam@json:string(op_code(Patch, Debug)),
gleam@json:int(Index),
patch_to_json(Move_patch, Debug)]
)
end.
-spec children_to_json(list({integer(), patch()}), boolean()) -> gleam@json:json().
children_to_json(Children, Debug) ->
_pipe = Children,
_pipe@1 = gleam@list:map(
_pipe,
fun(_capture) -> map_key_to_str(_capture, Debug) end
),
gleam@json:object(_pipe@1).
-spec compare_child_at_index(
gleam@map:map_(integer(), patch()),
list(sprocket@render:rendered_element()),
sprocket@render:rendered_element(),
integer()
) -> gleam@map:map_(integer(), patch()).
compare_child_at_index(Acc, Old_children, New_child, Index) ->
case gleam@list:at(Old_children, Index) of
{ok, Old_child} ->
case create(Old_child, New_child) of
no_op ->
Acc;
Patch ->
gleam@map:insert(Acc, Index, Patch)
end;
{error, nil} ->
gleam@map:insert(Acc, Index, {insert, New_child})
end.
-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.
-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) ->
Old_key_map = build_key_map(Old_children),
New_key_map = build_key_map(New_children),
Removals = begin
_pipe = zip_all(Old_children, New_children),
gleam@list:index_fold(
_pipe,
gleam@map:new(),
fun(Acc, Child, Index) -> case Child of
{{some, {rendered_element, _, {some, Old_key}, _, _}},
{some, {rendered_element, _, {some, _}, _, _}}} ->
case gleam@map:get(New_key_map, Old_key) of
{ok, _} ->
Acc;
{error, nil} ->
gleam@map:insert(Acc, Index, remove)
end;
{_, none} ->
gleam@map:insert(Acc, Index, remove);
_ ->
Acc
end end
)
end,
_pipe@1 = New_children,
_pipe@2 = gleam@list:index_fold(
_pipe@1,
Removals,
fun(Acc@1, New_child, Index@1) -> case New_child of
{rendered_element, _, {some, Key}, _, _} ->
case gleam@map:get(Old_key_map, Key) of
{ok, Found} ->
{Old_index, Old_child} = Found,
Child_patch = create(Old_child, New_child),
case Index@1 =:= Old_index of
true ->
gleam@map:insert(
Acc@1,
Index@1,
Child_patch
);
false ->
gleam@map:insert(
Acc@1,
Index@1,
{move, Old_index, Child_patch}
)
end;
{error, nil} ->
compare_child_at_index(
Acc@1,
Old_children,
New_child,
Index@1
)
end;
_ ->
compare_child_at_index(
Acc@1,
Old_children,
New_child,
Index@1
)
end end
),
_pipe@3 = gleam@map:filter(_pipe@2, fun(_, Child_el) -> case Child_el of
no_op ->
false;
_ ->
true
end end),
(fun(Diff_map) -> case gleam@map:size(Diff_map) > 0 of
true ->
{some, gleam@map:to_list(Diff_map)};
false ->
none
end end)(_pipe@3).