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, nowarn_nomatch]).
-export([op_code/2, patch_to_json/2, create/2]).
-export_type([patch/0]).
-type patch() :: no_op |
{update,
gleam@option:option(list(sprocket@internal@reconcile:reconciled_attribute())),
gleam@option:option(list({integer(), patch()}))} |
{replace, sprocket@internal@reconcile:reconciled_element()} |
{insert, sprocket@internal@reconcile:reconciled_element()} |
remove |
{change, binary()} |
{move, integer(), patch()}.
-spec attr_key(sprocket@internal@reconcile:reconciled_attribute()) -> binary().
attr_key(Attribute) ->
case Attribute of
{reconciled_attribute, Name, _} ->
Name;
{reconciled_event_handler, _, Id} ->
Id;
{reconciled_client_hook, _, Id@1} ->
Id@1
end.
-spec compare_attributes_helper(
gleam@dict:dict(binary(), sprocket@internal@reconcile:reconciled_attribute()),
list(sprocket@internal@reconcile:reconciled_attribute())
) -> gleam@option:option(list(sprocket@internal@reconcile:reconciled_attribute())).
compare_attributes_helper(Old_attributes, New_attributes) ->
case New_attributes of
[New_attr | Rest_new] ->
case gleam@dict:get(Old_attributes, attr_key(New_attr)) of
{ok, Old_attr} ->
case Old_attr =:= New_attr of
true ->
case compare_attributes_helper(
gleam@dict: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 {maps:size(Old_attributes), gleam@list:length(New_attributes)} of
{Old_size, New_size} when Old_size > New_size ->
{some, New_attributes};
{_, _} ->
none
end
end.
-spec build_attrs_map(list(sprocket@internal@reconcile:reconciled_attribute())) -> gleam@dict:dict(binary(), sprocket@internal@reconcile:reconciled_attribute()).
build_attrs_map(Attributes) ->
_pipe = Attributes,
gleam@list:fold(
_pipe,
gleam@dict:new(),
fun(Acc, Attr) -> gleam@dict:insert(Acc, attr_key(Attr), Attr) end
).
-spec compare_attributes(
list(sprocket@internal@reconcile:reconciled_attribute()),
list(sprocket@internal@reconcile:reconciled_attribute())
) -> gleam@option:option(list(sprocket@internal@reconcile:reconciled_attribute())).
compare_attributes(Old_attributes, New_attributes) ->
compare_attributes_helper(build_attrs_map(Old_attributes), New_attributes).
-spec build_key_map(list(sprocket@internal@reconcile:reconciled_element())) -> gleam@dict:dict(binary(), {integer(),
sprocket@internal@reconcile:reconciled_element()}).
build_key_map(Children) ->
_pipe = Children,
gleam@list:index_fold(
_pipe,
gleam@dict:new(),
fun(Acc, Child, Index) -> case Child of
{reconciled_element, _, {some, Key}, _, _} ->
gleam@dict:insert(Acc, Key, {Index, Child});
_ ->
Acc
end end
).
-spec zip_all(list(JWD), list(JWF)) -> list({gleam@option:option(JWD),
gleam@option:option(JWF)}).
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@internal@reconcile:reconciled_attribute())) -> gleam@json:json().
attrs_to_json(Attrs) ->
_pipe = Attrs,
_pipe@1 = gleam@list:flat_map(_pipe, fun(Attr) -> case Attr of
{reconciled_attribute, Name, Value} ->
[{Name, gleam@json:string(Value)}];
{reconciled_event_handler, Kind, Id} ->
[{gleam@string:concat(
[<<"spkt-event"/utf8>>, <<"-"/utf8>>, Kind]
),
gleam@json:string(Id)}];
{reconciled_client_hook, Name@1, Id@1} ->
[{<<"spkt-event"/utf8>>, gleam@json:string(Name@1)},
{gleam@string:concat(
[<<"spkt-event"/utf8>>, <<"-id"/utf8>>]
),
gleam@json:string(Id@1)}]
end end),
gleam@json:object(_pipe@1).
-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) ->
sprocket@internal@render:renderer(
sprocket@internal@renderers@json:json_renderer(),
fun(Render_json) -> 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)),
Render_json(El)]
);
{insert, El@1} ->
gleam@json:preprocessed_array(
[gleam@json:string(op_code(Patch, Debug)),
Render_json(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 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@dict:dict(integer(), patch()),
list(sprocket@internal@reconcile:reconciled_element()),
sprocket@internal@reconcile:reconciled_element(),
integer()
) -> gleam@dict:dict(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@dict:insert(Acc, Index, Patch)
end;
{error, nil} ->
gleam@dict:insert(Acc, Index, {insert, New_child})
end.
-spec create(
sprocket@internal@reconcile:reconciled_element(),
sprocket@internal@reconcile:reconciled_element()
) -> patch().
create(Old, New) ->
case {Old, New} of
{{reconciled_element, Old_tag, Old_key, Old_attrs, Old_children},
{reconciled_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;
{{reconciled_component, Old_fc, _, Old_props, _, Old_el},
{reconciled_component, New_fc, _, New_props, _, New_el}} when (Old_fc =:= New_fc) andalso (Old_props =:= New_props) ->
case create(Old_el, New_el) of
no_op ->
no_op;
Patch ->
{update, none, {some, [{0, Patch}]}}
end;
{{reconciled_fragment, _, Old_children@1},
{reconciled_fragment, _, New_children@1}} ->
case compare_children(Old_children@1, New_children@1) of
{some, Children@1} ->
{update, none, {some, Children@1}};
none ->
no_op
end;
{{reconciled_text, Old_text}, {reconciled_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@internal@reconcile:reconciled_element()),
list(sprocket@internal@reconcile:reconciled_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@dict:new(),
fun(Acc, Child, Index) -> case Child of
{{some, {reconciled_element, _, {some, Old_key}, _, _}},
{some, {reconciled_element, _, {some, _}, _, _}}} ->
case gleam@dict:get(New_key_map, Old_key) of
{ok, _} ->
Acc;
{error, nil} ->
gleam@dict:insert(Acc, Index, remove)
end;
{_, none} ->
gleam@dict: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
{reconciled_element, _, {some, Key}, _, _} ->
case gleam@dict: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@dict:insert(
Acc@1,
Index@1,
Child_patch
);
false ->
gleam@dict: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@dict:filter(_pipe@2, fun(_, Child_el) -> case Child_el of
no_op ->
false;
_ ->
true
end end),
(fun(Diff_map) -> case maps:size(Diff_map) > 0 of
true ->
{some, maps:to_list(Diff_map)};
false ->
none
end end)(_pipe@3).