Skip to content
Merged
16 changes: 15 additions & 1 deletion dev/live_views/messages.ex
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
defmodule LiveDebuggerDev.LiveViews.Messages do
use DevWeb, :live_view

def mount(_params, _session, socket) do
socket
|> assign(:messages, [])
|> ok()
end

attr(:messages, :list, required: true)

def render(assigns) do
~H"""
<.box title="Messages [LiveView]" color="purple">
<div>
<.button phx-click="big-message" color="purple">Send big message</.button>
<div>Message count: <%= length(@messages) %></div>
</div>
</.box>
"""
end

def handle_event("big-message", _, socket) do
send(self(), very_big_message())
{:noreply, socket}

socket
|> assign(:messages, [
{length(socket.assigns.messages), very_big_message()} | socket.assigns.messages
])
|> noreply()
end

def handle_info(_, socket) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,7 @@ defmodule LiveDebugger.App.Debugger.CallbackTracing.Web.Components.Trace do
</p>
<.copy_button id={"#{@id}-arg-#{index}"} value={TermParser.term_to_copy_string(args)} />
</div>
<ElixirDisplay.term
id={@id <> "-#{index}"}
node={TermParser.term_to_display_tree(args)}
level={1}
/>
<ElixirDisplay.term id={@id <> "-#{index}"} node={TermParser.term_to_display_tree(args)} />
</div>
<% end %>
</div>
Expand Down
8 changes: 4 additions & 4 deletions lib/live_debugger/app/debugger/node_state/queries.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ defmodule LiveDebugger.App.Debugger.NodeState.Queries do
alias LiveDebugger.App.Debugger.Structs.TreeNode

@spec fetch_node_assigns(pid :: pid(), node_id :: TreeNode.id()) ::
{:ok, %{node_assigns: map()}} | {:error, term()}
{:ok, map()} | {:error, term()}
def fetch_node_assigns(pid, node_id) when is_pid(node_id) do
case fetch_node_state(pid) do
{:ok, %LvState{socket: %{assigns: assigns}}} ->
{:ok, %{node_assigns: assigns}}
{:ok, assigns}

{:error, reason} ->
{:error, reason}
Expand Down Expand Up @@ -48,8 +48,8 @@ defmodule LiveDebugger.App.Debugger.NodeState.Queries do
nil ->
{:error, "Component with CID #{cid} not found"}

component ->
{:ok, %{node_assigns: component.assigns}}
%{assigns: assigns} ->
{:ok, assigns}
end
end
end
19 changes: 7 additions & 12 deletions lib/live_debugger/app/debugger/node_state/web/components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.Components do
use LiveDebugger.App.Web, :component

alias LiveDebugger.App.Debugger.Web.Components.ElixirDisplay
alias LiveDebugger.App.Utils.TermParser
alias LiveDebugger.App.Debugger.NodeState.Web.AssignsSearch
alias LiveDebugger.App.Debugger.NodeState.Web.HookComponents.AssignsSearch
alias LiveDebugger.App.Utils.TermNode
alias LiveDebugger.Utils.Memory

def loading(assigns) do
Expand All @@ -27,6 +27,8 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.Components do
end

attr(:assigns, :list, required: true)
attr(:term_node, TermNode, required: true)
attr(:copy_string, :string, required: true)
attr(:fullscreen_id, :string, required: true)
attr(:assigns_search_phrase, :string, default: "")

Expand All @@ -40,11 +42,7 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.Components do
assigns_search_phrase={@assigns_search_phrase}
input_id="assigns-search-input"
/>
<.copy_button
id="assigns-copy-button"
variant="icon-button"
value={TermParser.term_to_copy_string(@assigns)}
/>
<.copy_button id="assigns-copy-button" variant="icon-button" value={@copy_string} />
<.fullscreen_button id={@fullscreen_id} />
</div>
</:right_panel>
Expand All @@ -56,7 +54,7 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.Components do
<div class="absolute top-2 right-2 z-10">
<.assigns_size_label assigns={@assigns} id="display-container-size-label" />
</div>
<ElixirDisplay.term id="assigns-display" node={TermParser.term_to_display_tree(@assigns)} />
<ElixirDisplay.static_term node={@term_node} />
</div>
</.section>
<.fullscreen id={@fullscreen_id} title="Assigns">
Expand All @@ -74,10 +72,7 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.Components do
<div class="absolute top-0 right-2 z-10">
<.assigns_size_label assigns={@assigns} id="display-fullscreen-size-label" />
</div>
<ElixirDisplay.term
id="assigns-display-fullscreen-term"
node={TermParser.term_to_display_tree(@assigns)}
/>
<ElixirDisplay.static_term node={@term_node} />
</div>
</.fullscreen>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule LiveDebugger.App.Debugger.NodeState.Web.AssignsSearch do
defmodule LiveDebugger.App.Debugger.NodeState.Web.HookComponents.AssignsSearch do
@moduledoc """
This component is used to add search functionality for assigns.
It produces `search` and `search-submit` events handled by hook added via `init/1`.
Expand Down
121 changes: 121 additions & 0 deletions lib/live_debugger/app/debugger/node_state/web/hooks/node_assigns.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
defmodule LiveDebugger.App.Debugger.NodeState.Web.Hooks.NodeAssigns do
@moduledoc """
This hook is responsible for fetching assigns of specific node.
"""

use LiveDebugger.App.Web, :hook

alias Phoenix.LiveView.AsyncResult
alias LiveDebugger.App.Debugger.NodeState.Queries, as: NodeStateQueries
alias LiveDebugger.App.Utils.TermDiffer
alias LiveDebugger.App.Utils.TermDiffer.Diff
alias LiveDebugger.App.Utils.TermParser

@required_assigns [
:node_id,
:lv_process
]

@doc """
Initializes the hook by attaching the hook to the socket and checking the required assigns.
"""
@spec init(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t()
def init(socket) do
socket
|> check_assigns!(@required_assigns)
|> attach_hook(:node_assigns, :handle_async, &handle_async/3)
|> register_hook(:node_assigns)
|> assign(:node_assigns_info, AsyncResult.loading())
|> assign_async_node_assigns()
end

@doc """
Assigns the async node assigns to the socket.

## Options

- `:reset` - If `true`, assigns won't diff current assigns with new ones.
"""
@spec assign_async_node_assigns(Phoenix.LiveView.Socket.t(), Keyword.t()) ::
Phoenix.LiveView.Socket.t()
def assign_async_node_assigns(socket, opts \\ [])

def assign_async_node_assigns(
%{assigns: %{node_id: node_id, lv_process: %{pid: pid}}} = socket,
opts
)
when not is_nil(node_id) do
node_assigns_info =
if Keyword.get(opts, :reset, false) do
AsyncResult.loading()
else
socket.assigns.node_assigns_info
end

socket
|> assign(:node_assigns_info, node_assigns_info)
|> start_async(:fetch_node_assigns, fn ->
NodeStateQueries.fetch_node_assigns(pid, node_id)
end)
end

def assign_async_node_assigns(socket, _) do
socket
|> assign(:node_assigns_info, AsyncResult.failed(%AsyncResult{}, :no_node_id))
end

defp handle_async(
:fetch_node_assigns,
{:ok, {:ok, node_assigns}},
%{
assigns: %{
node_assigns_info: %AsyncResult{ok?: true, result: {old_assigns, old_term_node, _}}
}
} =
socket
) do
node_assigns_info =
case TermDiffer.diff(old_assigns, node_assigns) do
%Diff{type: :equal} ->
AsyncResult.ok(socket.assigns.node_assigns_info.result)

diff ->
copy_string = TermParser.term_to_copy_string(node_assigns)

case TermParser.update_by_diff(old_term_node, diff) do
{:ok, term_node} ->
AsyncResult.ok({node_assigns, term_node, copy_string})

{:error, reason} ->
AsyncResult.failed(socket.assigns.node_assigns_info, reason)
end
end

socket
|> assign(:node_assigns_info, node_assigns_info)
|> halt()
end

defp handle_async(:fetch_node_assigns, {:ok, {:ok, node_assigns}}, socket) do
term_node = TermParser.term_to_display_tree(node_assigns)
copy_string = TermParser.term_to_copy_string(node_assigns)

socket
|> assign(:node_assigns_info, AsyncResult.ok({node_assigns, term_node, copy_string}))
|> halt()
end

defp handle_async(:fetch_node_assigns, {:ok, {:error, reason}}, socket) do
socket
|> assign(:node_assigns_info, AsyncResult.failed(socket.assigns.node_assigns_info, reason))
|> halt()
end

defp handle_async(:fetch_node_assigns, {:exit, reason}, socket) do
socket
|> assign(:node_assigns_info, AsyncResult.failed(socket.assigns.node_assigns_info, reason))
|> halt()
end

defp handle_async(_, _, socket), do: {:cont, socket}
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
defmodule LiveDebugger.App.Debugger.NodeState.Web.Hooks.TermNodeToggle do
@moduledoc """
Hook that can be used to toggle the open state of a `assigns` term node.
"""

use LiveDebugger.App.Web, :hook

alias LiveDebugger.App.Utils.TermNode
alias LiveDebugger.App.Utils.TermParser
alias Phoenix.LiveView.AsyncResult

@required_assigns [:node_assigns_info]

@spec init(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t()
def init(socket) do
socket
|> check_assigns!(@required_assigns)
|> attach_hook(:term_node_toggle, :handle_event, &handle_event/3)
|> register_hook(:term_node_toggle)
end

defp handle_event("toggle_node", %{"id" => id}, socket) do
node_assigns_info =
with %AsyncResult{ok?: true, result: {node_assigns, term_node, copy_string}} <-
socket.assigns.node_assigns_info,
{:ok, updated_term_node} <-
TermParser.update_by_id(term_node, id, &%TermNode{&1 | open?: !&1.open?}) do
AsyncResult.ok({node_assigns, updated_term_node, copy_string})
else
{:error, reason} ->
AsyncResult.failed(socket.assigns.node_assigns_info, reason)

_ ->
socket.assigns.node_assigns_info
end

socket
|> assign(:node_assigns_info, node_assigns_info)
|> halt()
end

defp handle_event("toggle_node", _, socket), do: {:halt, socket}
defp handle_event(_, _, socket), do: {:cont, socket}
end
32 changes: 10 additions & 22 deletions lib/live_debugger/app/debugger/node_state/web/node_state_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.NodeStateLive do

use LiveDebugger.App.Web, :live_view

alias Phoenix.LiveView.AsyncResult
alias LiveDebugger.Structs.LvProcess
alias LiveDebugger.App.Debugger.NodeState.Web.Hooks
alias LiveDebugger.App.Debugger.NodeState.Web.HookComponents
alias LiveDebugger.App.Debugger.NodeState.Web.Components, as: NodeStateComponents
alias LiveDebugger.App.Debugger.NodeState.Queries, as: NodeStateQueries

alias LiveDebugger.App.Debugger.NodeState.Web.AssignsSearch

alias LiveDebugger.Bus
alias LiveDebugger.App.Debugger.Events.NodeIdParamChanged
Expand Down Expand Up @@ -65,16 +63,17 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.NodeStateLive do
|> assign(:lv_process, lv_process)
|> assign(:node_id, node_id)
|> assign(:assigns_search_phrase, "")
|> assign_async_node_assigns()
|> AssignsSearch.init()
|> Hooks.NodeAssigns.init()
|> Hooks.TermNodeToggle.init()
|> HookComponents.AssignsSearch.init()
|> ok()
end

@impl true
def render(assigns) do
~H"""
<div class="flex-1 max-w-full flex flex-col gap-4">
<.async_result :let={node_assigns} assign={@node_assigns}>
<.async_result :let={{node_assigns, term_node, copy_string}} assign={@node_assigns_info}>
<:loading>
<NodeStateComponents.loading />
</:loading>
Expand All @@ -83,6 +82,8 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.NodeStateLive do
</:failed>

<NodeStateComponents.assigns_section
term_node={term_node}
copy_string={copy_string}
assigns={node_assigns}
fullscreen_id="assigns-display-fullscreen"
assigns_search_phrase={@assigns_search_phrase}
Expand All @@ -96,28 +97,15 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.NodeStateLive do
def handle_info(%NodeIdParamChanged{node_id: node_id}, socket) do
socket
|> assign(:node_id, node_id)
|> assign_async_node_assigns()
|> Hooks.NodeAssigns.assign_async_node_assigns(reset: true)
|> noreply()
end

def handle_info(%StateChanged{}, socket) do
socket
|> assign_async_node_assigns()
|> Hooks.NodeAssigns.assign_async_node_assigns()
|> noreply()
end

def handle_info(_, socket), do: {:noreply, socket}

defp assign_async_node_assigns(
%{assigns: %{node_id: node_id, lv_process: %{pid: pid}}} = socket
)
when not is_nil(node_id) do
assign_async(socket, :node_assigns, fn ->
NodeStateQueries.fetch_node_assigns(pid, node_id)
end)
end

defp assign_async_node_assigns(socket) do
assign(socket, :node, AsyncResult.failed(%AsyncResult{}, :no_node_id))
end
end
Loading
Loading