Skip to content

Commit

Permalink
outdent/indent functions for outline nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
electronicbites committed Sep 9, 2024
1 parent 761e380 commit 45ec445
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 54 deletions.
145 changes: 91 additions & 54 deletions lib/radiator/outline.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ defmodule Radiator.Outline do
@doc """
Returns a list of direct child nodes in correct order.
"""
def order_child_nodes(nil), do: []

def order_child_nodes(%Node{} = node) do
node
|> get_all_siblings()
Expand Down Expand Up @@ -56,13 +58,6 @@ defmodule Radiator.Outline do
|> List.flatten()
end

defp order_nodes_by_index(index, prev_id, collection) do
case index[prev_id] do
%{uuid: uuid} = node -> order_nodes_by_index(index, uuid, [node | collection])
_ -> Enum.reverse(collection)
end
end

@doc """
Inserts a node.
Expand Down Expand Up @@ -111,23 +106,27 @@ defmodule Radiator.Outline do
end)
end

defp episode_valid?(episode_id, %Node{episode_id: episode_id}, %Node{episode_id: episode_id}),
do: true

defp episode_valid?(episode_id, %Node{episode_id: episode_id}, nil), do: true
defp episode_valid?(episode_id, nil, %Node{episode_id: episode_id}), do: true
defp episode_valid?(_episode_id, nil, nil), do: true
defp episode_valid?(_episode_id, _parent_node, _prev_node), do: false
def indent_node(node_id) do
Repo.transaction(fn ->
case NodeRepository.get_node(node_id) do
nil ->
{:error, :not_found}

defp set_parent_id_if(attrs, nil), do: attrs
defp set_parent_id_if(attrs, %Node{uuid: uuid}), do: Map.put_new(attrs, "parent_id", uuid)
node ->
prev_node = get_prev_node(node)
do_indent_node(node, prev_node)
end
end)
|> case do
{:ok, {:error, error}} ->
{:error, error}

defp find_parent_node(%Node{parent_id: parent_id}, nil) do
NodeRepository.get_node_if(parent_id)
end
{:ok, {:ok, node_result}} ->
{:ok, node_result}

defp find_parent_node(_, parent_id) do
NodeRepository.get_node_if(parent_id)
{:error, error} ->
{:error, error}
end
end

@doc """
Expand Down Expand Up @@ -187,39 +186,6 @@ defmodule Radiator.Outline do
move_node(node_id, prev_id: new_prev_id, parent_id: parent_id)
end

# low level function to move a node
defp do_move_node(node, new_prev_id, new_parent_id, prev_node, parent_node) do
node_repo_result = %NodeRepoResult{node: node}

Repo.transaction(fn ->
old_next_node =
Node
|> where_prev_node_equals(node.uuid)
|> where_parent_node_equals(get_node_id(parent_node))
|> Repo.one()

new_next_node =
Node
|> where_prev_node_equals(new_prev_id)
|> where_parent_node_equals(new_parent_id)
|> Repo.one()

{:ok, node} = move_node_if(node, new_parent_id, new_prev_id)

{:ok, _old_next_node} =
move_node_if(old_next_node, get_node_id(parent_node), get_node_id(prev_node))

{:ok, _new_next_node} = move_node_if(new_next_node, new_parent_id, get_node_id(node))

Map.merge(node_repo_result, %{
node: node,
old_next_id: get_node_id(old_next_node),
old_prev_id: get_node_id(prev_node),
next_id: get_node_id(new_next_node)
})
end)
end

@doc """
Updates a nodes content.
Expand Down Expand Up @@ -453,6 +419,25 @@ defmodule Radiator.Outline do
{:ok, tree}
end

defp episode_valid?(episode_id, %Node{episode_id: episode_id}, %Node{episode_id: episode_id}),
do: true

defp episode_valid?(episode_id, %Node{episode_id: episode_id}, nil), do: true
defp episode_valid?(episode_id, nil, %Node{episode_id: episode_id}), do: true
defp episode_valid?(_episode_id, nil, nil), do: true
defp episode_valid?(_episode_id, _parent_node, _prev_node), do: false

defp set_parent_id_if(attrs, nil), do: attrs
defp set_parent_id_if(attrs, %Node{uuid: uuid}), do: Map.put_new(attrs, "parent_id", uuid)

defp find_parent_node(%Node{parent_id: parent_id}, nil) do
NodeRepository.get_node_if(parent_id)
end

defp find_parent_node(_, parent_id) do
NodeRepository.get_node_if(parent_id)
end

defp move_node_if(nil, _parent_node_id, _prev_node_id), do: {:ok, nil}

defp move_node_if(node, parent_id, prev_id) do
Expand All @@ -464,6 +449,51 @@ defmodule Radiator.Outline do
|> Repo.update()
end

# low level function to move a node
defp do_move_node(node, new_prev_id, new_parent_id, prev_node, parent_node) do
node_repo_result = %NodeRepoResult{node: node}

Repo.transaction(fn ->
old_next_node =
Node
|> where_prev_node_equals(node.uuid)
|> where_parent_node_equals(get_node_id(parent_node))
|> Repo.one()

new_next_node =
Node
|> where_prev_node_equals(new_prev_id)
|> where_parent_node_equals(new_parent_id)
|> Repo.one()

{:ok, node} = move_node_if(node, new_parent_id, new_prev_id)

{:ok, _old_next_node} =
move_node_if(old_next_node, get_node_id(parent_node), get_node_id(prev_node))

{:ok, _new_next_node} = move_node_if(new_next_node, new_parent_id, get_node_id(node))

Map.merge(node_repo_result, %{
node: node,
old_next_id: get_node_id(old_next_node),
old_prev_id: get_node_id(prev_node),
next_id: get_node_id(new_next_node)
})
end)
end

defp do_indent_node(_node, nil), do: {:error, :no_prev_node}

defp do_indent_node(node, prev_node) do
new_previous_id =
prev_node
|> order_child_nodes()
|> List.last()
|> get_node_id()

move_node(node.uuid, prev_id: new_previous_id, parent_id: prev_node.uuid)
end

defp parent_and_prev_consistent?(_, nil), do: true
defp parent_and_prev_consistent?(nil, _), do: true

Expand All @@ -483,6 +513,13 @@ defmodule Radiator.Outline do
node.uuid
end

defp order_nodes_by_index(index, prev_id, collection) do
case index[prev_id] do
%{uuid: uuid} = node -> order_nodes_by_index(index, uuid, [node | collection])
_ -> Enum.reverse(collection)
end
end

defp binaray_uuid_to_ecto_uuid(nil), do: nil

defp binaray_uuid_to_ecto_uuid(uuid) do
Expand Down
97 changes: 97 additions & 0 deletions test/radiator/outline_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,103 @@ defmodule Radiator.OutlineTest do
end
end

describe "indent_node/1 - simple context" do
setup :simple_node_fixture

# before 1 2
# intend node 2: 1 parentof 2
test "intended node is now child of old previous node", %{
node_1: node_1,
node_2: node_2
} do
assert node_2.parent_id == nil
assert node_2.prev_id == node_1.uuid

{:ok, _} = Outline.indent_node(node_2.uuid)

# reload nodes
node_1 = Repo.reload!(node_1)
node_2 = Repo.reload!(node_2)

assert node_2.parent_id == node_1.uuid
assert node_2.prev_id == nil
end

# before 1 2
# intend node 2: 1 parentof 2
test "result bla blub", %{
node_1: node_1,
node_2: node_2
} do
{:ok, result} = Outline.indent_node(node_2.uuid)
assert result.node.parent_id == node_1.uuid
assert result.old_prev_id == node_1.uuid
end

# before 1 2
# intend node 1: error because node 1 is the first in the list
test "intended node does not work when there is no previous node", %{
node_1: node_1,
node_2: node_2
} do
{:error, :no_prev_node} = Outline.indent_node(node_1.uuid)

node_1 = Repo.reload!(node_1)
node_2 = Repo.reload!(node_2)

assert node_1.parent_id == nil
assert node_1.prev_id == nil
assert node_2.parent_id == nil
assert node_2.prev_id == node_1.uuid
end
end

describe "indent_node/1" do
setup :complex_node_fixture

# 1 2 3 4 5
test "intend node 3 moves under node 2 and does not change nested nodes", %{
node_2: node_2,
node_3: node_3,
node_4: node_4,
nested_node_1: nested_node_1,
nested_node_2: nested_node_2
} do
{:ok, _} = Outline.indent_node(node_3.uuid)

# reload nodes
node_4 = Repo.reload!(node_4)
node_3 = Repo.reload!(node_3)
node_2 = Repo.reload!(node_2)
nested_node_1 = Repo.reload!(nested_node_1)
nested_node_2 = Repo.reload!(nested_node_2)

assert node_3.prev_id == nil
assert node_3.parent_id == node_2.uuid
assert node_4.prev_id == node_2.uuid
assert nested_node_1.prev_id == nil
assert nested_node_2.prev_id == nested_node_1.uuid
assert nested_node_1.parent_id == node_3.uuid
assert nested_node_2.parent_id == node_3.uuid
end

test "intend node 4 moves under node 3 and after existing nested nodes", %{
node_3: node_3,
node_4: node_4,
nested_node_2: nested_node_2
} do
{:ok, _} = Outline.indent_node(node_4.uuid)

# reload nodes
node_4 = Repo.reload!(node_4)
node_3 = Repo.reload!(node_3)
nested_node_2 = Repo.reload!(nested_node_2)

assert node_4.prev_id == nested_node_2.uuid
assert node_4.parent_id == node_3.uuid
end
end

describe "remove_node/1" do
setup :complex_node_fixture

Expand Down

0 comments on commit 45ec445

Please sign in to comment.