Current section

Files

Jump to
live_debugger lib live_debugger app debugger components_tree utils.ex
Raw

lib/live_debugger/app/debugger/components_tree/utils.ex

defmodule LiveDebugger.App.Debugger.ComponentsTree.Utils do
@moduledoc """
Utility functions for the Components Tree.
"""
alias LiveDebugger.Structs.LvState
alias LiveDebugger.App.Debugger.Structs.TreeNode
@max_node_number 20
@doc """
Calculates the maximum level to be opened in the tree.
"""
@spec max_opened_node_level(root_node :: TreeNode.t(), max_nodes :: integer()) :: integer()
def max_opened_node_level(root_node, max_nodes \\ @max_node_number) do
node_count = count_by_level(root_node)
node_count
|> Enum.reduce_while({0, 0}, fn {level, count}, acc ->
{_, parent_count} = acc
new_count = count + parent_count
if new_count > max_nodes do
{:halt, {level - 1, new_count}}
else
{:cont, {level, new_count}}
end
end)
|> elem(0)
end
defp count_by_level(node, level \\ 0, acc \\ %{}) do
acc = Map.update(acc, level, 1, &(&1 + 1))
Enum.reduce(node.children, acc, fn child, acc ->
count_by_level(child, level + 1, acc)
end)
end
@doc """
Creates a tree with `LiveDebugger.App.Debugger.Structs.TreeNode` elements from the live view state.
"""
@spec build_tree(lv_state :: LvState.t()) :: {:ok, TreeNode.t()} | {:error, term()}
def build_tree(lv_state) do
with {:ok, live_view} <- TreeNode.live_view_node(lv_state),
{:ok, live_components} <- TreeNode.live_component_nodes(lv_state) do
cid_tree =
lv_state
|> children_cids_mapping()
|> tree_merge(nil)
{:ok, add_children(live_view, cid_tree, live_components)}
end
end
defp children_cids_mapping(%{components: components}) do
components
|> get_base_parent_cids_mapping()
|> fill_parent_cids_mapping(components)
|> reverse_mapping()
end
defp get_base_parent_cids_mapping(components) do
components
|> Enum.map(fn %{cid: cid} -> {cid, nil} end)
|> Enum.into(%{})
end
defp fill_parent_cids_mapping(base_parent_cids_mapping, components) do
Enum.reduce(components, base_parent_cids_mapping, fn element, parent_cids_mapping ->
Enum.reduce(element.children_cids, parent_cids_mapping, fn child_cid, parent_cids ->
%{parent_cids | child_cid => element.cid}
end)
end)
end
defp reverse_mapping(components_with_children) do
Enum.reduce(components_with_children, %{}, fn {cid, parent_cid}, components_cids_map ->
Map.update(components_cids_map, parent_cid, [cid], &[cid | &1])
end)
end
defp tree_merge(components_cids_mapping, parent_cid) do
case Map.pop(components_cids_mapping, parent_cid) do
{nil, _} ->
nil
{children_cids, components_cids_map} ->
children_cids
|> Enum.map(fn cid -> {cid, tree_merge(components_cids_map, cid)} end)
|> Enum.into(%{})
end
end
defp add_children(parent_element, nil, _live_components), do: parent_element
defp add_children(parent_element, children_cids_map, live_components) do
tree_node =
Enum.reduce(children_cids_map, parent_element, fn {cid, children_cids_map},
parent_element ->
child =
live_components
|> Enum.find(fn element -> element.id.cid == cid end)
|> add_children(children_cids_map, live_components)
TreeNode.add_child(parent_element, child)
end)
%{tree_node | children: Enum.sort_by(tree_node.children, & &1.id.cid)}
end
end