Skip to content

Commit 36556e5

Browse files
committed
Merge remote-tracking branch 'origin/main' into 827-update-layout-of-livedebugger
2 parents a576be6 + 2bcf4d6 commit 36556e5

File tree

20 files changed

+5755
-16
lines changed

20 files changed

+5755
-16
lines changed

config/config.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ if config_env() == :dev do
6969
]
7070

7171
config :live_debugger, LiveDebugger.App.Web.Endpoint, debug_errors: true
72+
73+
config :phoenix_live_view, enable_expensive_runtime_checks: true
7274
end
7375

7476
if config_env() == :test do

dev/components.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ defmodule LiveDebuggerDev.Components do
99
{"/embedded", "Embedded"},
1010
{"/embedded_in_controller", "EmbeddedInController"},
1111
{"/endless_crash_reload", "EndlessCrashReload"},
12+
{"/stream", "Stream"},
1213
{"/async_demo", "AsyncDemo"}
1314
]
1415

dev/live_components/stream.ex

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
defmodule LiveDebuggerDev.LiveComponents.StreamComponent do
2+
use DevWeb, :live_component
3+
4+
def update(assigns, socket) do
5+
items = [
6+
%{id: "item-1", name: "first"},
7+
%{id: "item-2", name: "second"},
8+
%{id: "item-3", name: "third"}
9+
]
10+
11+
socket
12+
|> assign(assigns)
13+
|> stream(:component_items, items)
14+
|> then(&{:ok, &1})
15+
end
16+
17+
def render(assigns) do
18+
~H"""
19+
<div>
20+
<.box title="Component with stream" color="yellow"></.box>
21+
</div>
22+
"""
23+
end
24+
end

dev/live_views/stream.ex

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
defmodule LiveDebuggerDev.LiveViews.Stream do
2+
use DevWeb, :live_view
3+
4+
alias LiveDebuggerDev.LiveComponents
5+
6+
@impl true
7+
def mount(_params, _session, socket) do
8+
socket =
9+
socket
10+
|> stream_configure(:items, dom_id: &"item-#{&1.id}")
11+
|> stream_configure(:another_items, dom_id: &"another-#{&1.id}")
12+
|> assign(:current_id, 0)
13+
|> assign(:another_items_id, 0)
14+
|> stream(:items, [])
15+
|> stream(:another_items, [])
16+
17+
{:ok, socket}
18+
end
19+
20+
@impl true
21+
def render(assigns) do
22+
~H"""
23+
<.box title="Items Stream options">
24+
<.button id="create-item" phx-click="create_item">Create Item</.button>
25+
<.button id="update-item" phx-click="update_item">Update Last Item</.button>
26+
<.button id="insert-many" phx-click="insert_many">Insert Many (Reverse Order)</.button>
27+
<.button id="insert-at-index" phx-click="insert_at_index">Insert At Index 4</.button>
28+
<.button id="delete-item" phx-click="delete_item">Delete Last</.button>
29+
<.button id="reset-items" phx-click="reset_items">Reset Stream</.button>
30+
<.button id="add-new-stream" phx-click="add_new_stream">Add new stream</.button>
31+
<.button id="delete-both-last" phx-click="delete_both_last">
32+
Delete Last From Both Streams
33+
</.button>
34+
</.box>
35+
36+
<hr />
37+
38+
<.box title="Items Stream">
39+
<ul id="item-list" phx-update="stream">
40+
<li :for={{id, item} <- @streams.items} id={id}>
41+
<strong>ID:</strong> <%= item.id %>, <strong>Number:</strong> <%= item.number %>
42+
<button phx-click="delete_by_dom_id" phx-value-id={id}>X</button>
43+
</li>
44+
</ul>
45+
</.box>
46+
47+
<hr />
48+
49+
<.box title="Another Items stream options">
50+
<.button id="create-another-item" phx-click="create_another_item">Create Another Item</.button>
51+
</.box>
52+
53+
<.box title="Another Items Stream">
54+
<ul id="another-items-list" phx-update="stream">
55+
<li :for={{id, item} <- @streams.another_items} id={id}>
56+
<strong><%= item.id %></strong> -><%= item.number %>
57+
</li>
58+
</ul>
59+
</.box>
60+
61+
<hr />
62+
63+
<.live_component id="stream_component" module={LiveComponents.StreamComponent} />
64+
"""
65+
end
66+
67+
@impl true
68+
def handle_event("create_item", _params, socket) do
69+
next_id = socket.assigns.current_id
70+
item = %{id: next_id, number: Enum.random(1..1000)}
71+
72+
socket =
73+
socket
74+
|> stream_insert(:items, item, at: 0)
75+
|> assign(:current_id, next_id + 1)
76+
77+
{:noreply, socket}
78+
end
79+
80+
@impl true
81+
def handle_event("update_item", _params, socket) do
82+
id_to_update = socket.assigns.current_id - 1
83+
84+
if id_to_update >= 0 do
85+
updated_item = %{id: id_to_update, number: Enum.random(1001..2000)}
86+
87+
socket =
88+
socket
89+
|> stream_insert(:items, updated_item, update_only: true)
90+
91+
{:noreply, socket}
92+
else
93+
{:noreply, socket}
94+
end
95+
end
96+
97+
@impl true
98+
def handle_event("insert_many", _params, socket) do
99+
start_id = socket.assigns.current_id
100+
101+
items =
102+
Enum.map(0..2, fn i ->
103+
%{id: start_id + i, number: Enum.random(1..99)}
104+
end)
105+
106+
socket =
107+
socket
108+
|> stream(:items, Enum.reverse(items), at: -1)
109+
|> assign(:current_id, start_id + length(items))
110+
111+
{:noreply, socket}
112+
end
113+
114+
@impl true
115+
def handle_event("insert_at_index", _params, socket) do
116+
next_id = socket.assigns.current_id
117+
item = %{id: next_id, number: 9999}
118+
119+
socket =
120+
socket
121+
|> stream_insert(:items, item, at: 4)
122+
|> assign(:current_id, next_id + 1)
123+
124+
{:noreply, socket}
125+
end
126+
127+
@impl true
128+
def handle_event("delete_item", _params, socket) do
129+
last_id = socket.assigns.current_id - 1
130+
131+
if last_id >= 0 do
132+
socket =
133+
socket
134+
|> stream_delete(:items, %{id: last_id})
135+
|> assign(:current_id, last_id)
136+
137+
{:noreply, socket}
138+
else
139+
{:noreply, socket}
140+
end
141+
end
142+
143+
@impl true
144+
def handle_event("delete_by_dom_id", %{"id" => dom_id}, socket) do
145+
socket = stream_delete_by_dom_id(socket, :items, dom_id)
146+
{:noreply, socket}
147+
end
148+
149+
@impl true
150+
def handle_event("reset_items", _params, socket) do
151+
socket =
152+
socket
153+
|> stream(:items, [], reset: true)
154+
|> assign(:current_id, 0)
155+
156+
{:noreply, socket}
157+
end
158+
159+
@impl true
160+
def handle_event("create_another_item", _params, socket) do
161+
next_id = socket.assigns.another_items_id
162+
item = %{id: next_id, number: Enum.random(1..100)}
163+
164+
socket =
165+
socket
166+
|> stream_insert(:another_items, item, at: -1)
167+
|> assign(:another_items_id, next_id + 1)
168+
169+
{:noreply, socket}
170+
end
171+
172+
@impl true
173+
def handle_event("delete_both_last", _params, socket) do
174+
last_item_id = socket.assigns.current_id - 1
175+
last_another_id = socket.assigns.another_items_id - 1
176+
177+
socket =
178+
socket
179+
|> then(fn s ->
180+
if last_item_id >= 0 do
181+
stream_delete(s, :items, %{id: last_item_id})
182+
else
183+
s
184+
end
185+
end)
186+
|> then(fn s ->
187+
if last_another_id >= 0 do
188+
stream_delete(s, :another_items, %{id: last_another_id})
189+
else
190+
s
191+
end
192+
end)
193+
|> assign(:current_id, max(last_item_id, 0))
194+
|> assign(:another_items_id, max(last_another_id, 0))
195+
196+
{:noreply, socket}
197+
end
198+
199+
@impl true
200+
def handle_event("add_new_stream", _params, socket) do
201+
socket =
202+
socket
203+
|> stream(:new_items, [])
204+
205+
{:noreply, socket}
206+
end
207+
end

dev/router.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ defmodule LiveDebuggerDev.Router do
1818
live("/messages", LiveDebuggerDev.LiveViews.Messages)
1919
live("/embedded", LiveDebuggerDev.LiveViews.Embedded)
2020
live("/endless_crash_reload", LiveDebuggerDev.LiveViews.EndlessCrashReload)
21+
live("/stream", LiveDebuggerDev.LiveViews.Stream)
2122
live("/async_demo", LiveDebuggerDev.LiveViews.AsyncDemo)
2223
get("/embedded_in_controller", LiveDebuggerDev.EmbeddedLiveViewController, :embedded)
2324
end

lib/live_debugger/app/debugger/node_state/web/components.ex

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.Components do
4242

4343
~H"""
4444
<div id="assigns-section-container" phx-hook="AssignsBodySearchHighlight">
45-
<.section id="assigns" class="h-max overflow-y-hidden" title="Assigns">
45+
<.section id="assigns" class="h-max overflow-y-hidden" title="Assigns" title_class="!min-w-14">
4646
<:right_panel>
4747
<div class="flex gap-2">
4848
<AssignsSearch.render
@@ -59,16 +59,18 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.Components do
5959
class="w-full h-max max-h-full overflow-y-auto"
6060
data-search_phrase={@assigns_search_phrase}
6161
>
62-
<div id="pinned-assigns" class="p-4 border-b border-default-border">
62+
<div id="pinned-assigns" class="p-4 border-b border-default-border overflow-x-auto">
6363
<.pinned_assigns_section
6464
id="pinned-"
6565
term_node={@term_node}
6666
pinned_assigns={@pinned_assigns}
6767
/>
6868
</div>
69-
<div id="all-assigns" class="p-4 relative">
69+
<div id="all-assigns" class="relative">
7070
<.assigns_sizes_section assigns_sizes={@assigns_sizes} id="display-container-size-label" />
71-
<ElixirDisplay.static_term id="assigns-" node={@term_node} selectable_level={1} />
71+
<div class="p-4 overflow-x-auto">
72+
<ElixirDisplay.static_term id="assigns-" node={@term_node} selectable_level={1} />
73+
</div>
7274
</div>
7375
</div>
7476
</.section>
@@ -80,16 +82,18 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.Components do
8082
/>
8183
</:search_bar_slot>
8284
<div id="assigns-display-fullscreen-container" data-search_phrase={@assigns_search_phrase}>
83-
<div class="p-4 border-b border-default-border">
85+
<div class="p-4 border-b border-default-border overflow-x-auto">
8486
<.pinned_assigns_section
8587
id="pinned-fullscreen-"
8688
term_node={@term_node}
8789
pinned_assigns={@pinned_assigns}
8890
/>
8991
</div>
90-
<div class="p-4 relative">
92+
<div class="relative">
9193
<.assigns_sizes_section assigns_sizes={@assigns_sizes} id="display-fullscreen-size-label" />
92-
<ElixirDisplay.static_term id="fullscreen-" node={@term_node} selectable_level={1} />
94+
<div class="p-4 overflow-x-auto">
95+
<ElixirDisplay.static_term id="fullscreen-" node={@term_node} selectable_level={1} />
96+
</div>
9397
</div>
9498
</div>
9599
</.fullscreen>
@@ -111,7 +115,7 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.Components do
111115
:if={pinned}
112116
class="flex min-h-4.5 [&>div>button]:hidden hover:[&>div>button]:block"
113117
>
114-
<div class="w-4">
118+
<div class="w-4 shrink-0">
115119
<button
116120
class="text-button-red-content hover:text-button-red-content-hover"
117121
phx-click="unpin-assign"

lib/live_debugger/app/debugger/node_state/web/hooks/node_assigns.ex

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,24 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.Hooks.NodeAssigns do
180180
# If both async tasks are running, we start the second async task
181181
# It stops already running second async tasks and start a new one
182182
defp assign_size_async(%{private: %{live_async: %{assigns_size_1: _}}} = socket, assigns) do
183-
start_async(socket, :assigns_size_2, fn -> calculate_assigns_size(assigns) end)
183+
# We have to calculate the size of assigns of debugged process asynchronously.
184+
# When user has enabled `enable_expensive_runtime_checks` in `config.exs`, LiveView will assume,
185+
# that we are accessing assigns inside a function given to `async` and will raise a warning.
186+
# We wrap the assigns in a map to avoid this warning.
187+
wrapper = %{assigns: assigns}
188+
189+
start_async(socket, :assigns_size_2, fn ->
190+
calculate_assigns_size(wrapper.assigns)
191+
end)
184192
end
185193

186194
# If assigns are not calculated, we start the first async task
187195
defp assign_size_async(socket, assigns) do
188-
start_async(socket, :assigns_size_1, fn -> calculate_assigns_size(assigns) end)
196+
wrapper = %{assigns: assigns}
197+
198+
start_async(socket, :assigns_size_1, fn ->
199+
calculate_assigns_size(wrapper.assigns)
200+
end)
189201
end
190202

191203
defp calculate_assigns_size(assigns) do
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
defmodule LiveDebugger.App.Debugger.Streams.Actions do
2+
@moduledoc """
3+
Actions for `LiveDebugger.App.Debugger.Streams` context.
4+
"""
5+
6+
alias LiveDebugger.App.Debugger.Streams.StreamUtils
7+
8+
@type stream_update_result :: %{
9+
functions: [function()],
10+
config: [function()],
11+
name: atom()
12+
}
13+
14+
@spec update_stream(
15+
stream_updates :: StreamUtils.live_stream_item(),
16+
dom_id_fun :: (any() -> String.t())
17+
) ::
18+
{:ok, stream_update_result()} | {:error, String.t()}
19+
def update_stream(stream_updates, dom_id_fun) do
20+
with name <- stream_updates.name,
21+
funs <- StreamUtils.stream_update_functions(stream_updates),
22+
config <-
23+
StreamUtils.stream_config(stream_updates, dom_id: dom_id_fun) do
24+
{:ok, %{functions: funs, config: config, name: name}}
25+
end
26+
end
27+
end

0 commit comments

Comments
 (0)