Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions assets/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import TraceLabelSearchHighlight from './hooks/trace_label_search_highlight';
import AssignsBodySearchHighlight from './hooks/assigns_body_search_highlight';
import DiffPulse from './hooks/diff_pulse';
import ChartHook from './hooks/chart_hook';
import CollapsedSectionPulse from './hooks/collapsed_section_pulse';

import topbar from './vendor/topbar';

Expand Down Expand Up @@ -42,6 +43,7 @@ function createHooks() {
AssignsBodySearchHighlight,
DiffPulse,
ChartHook,
CollapsedSectionPulse,
};
}

Expand Down
17 changes: 17 additions & 0 deletions assets/app/hooks/collapsed_section_pulse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const CollapsedSectionPulse = {
mounted() {
const collapsibleEl = this.el.closest('details');

this.handleEvent(`${this.el.id}-pulse`, () => {
if (collapsibleEl.open) return;

collapsibleEl.classList.add('animate-section-pulse');

setTimeout(() => {
collapsibleEl.classList.remove('animate-section-pulse');
}, 500);
});
},
};

export default CollapsedSectionPulse;
37 changes: 37 additions & 0 deletions assets/app/hooks/collapsible.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,46 @@
function setOpen(el) {
if (el.dataset.saveStateInBrowser === 'true') {
setOpenFromLocalStorage(el);
return;
}

if (el.dataset.open === 'true') {
el.open = true;
} else {
el.open = false;
}
}

function setOpenFromLocalStorage(el) {
const open_state = localStorage.getItem(`collapsible-open-${el.id}`);

if (open_state !== null) {
el.open = open_state === 'true';
return;
}

if (el.dataset.open === 'true') {
el.open = true;
localStorage.setItem(`collapsible-open-${el.id}`, 'true');
} else {
el.open = false;
localStorage.setItem(`collapsible-open-${el.id}`, 'false');
}
}

function maybeSaveStateOnChange(el) {
if (el.dataset.saveStateInBrowser === 'true') {
el.addEventListener('toggle', () => {
localStorage.setItem(`collapsible-open-${el.id}`, el.open.toString());
});

window.addEventListener('storage', ({ key }) => {
if (key !== `collapsible-open-${el.id}`) return;
setOpenFromLocalStorage(el);
});
}
}

function handleCollapsibleEvent(payload, el) {
if (payload.action === 'toggle') {
el.open = !el.open;
Expand All @@ -24,6 +59,8 @@ const Collapsible = {
mounted() {
setOpen(this.el);

maybeSaveStateOnChange(this.el);

this.handleEvent(`${this.el.id}-collapsible`, (payload) => {
handleCollapsibleEvent(payload, this.el);
});
Expand Down
9 changes: 9 additions & 0 deletions assets/app/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,22 @@ module.exports = {
},
'100%': { backgroundColor: '', color: '' },
},
sectionPulse: {
'0%': {
boxShadow: '0 0 10px var(--diff-pulse-bg)',
},
'100%': {
boxShadow: '0 0 0px var(--diff-pulse-bg)',
},
},
},
animation: {
'fade-out': 'fadeOut 200ms ease-out forwards',
'fade-out-mobile': 'fadeOutMobile 200ms ease-out forwards',
'fade-in': 'fadeIn 100ms ease-in forwards',
'fade-in-mobile': 'fadeInMobile 100ms ease-in forwards',
'diff-pulse': 'diffPulse 500ms ease-out',
'section-pulse': 'sectionPulse 500ms ease-out',
},
},
},
Expand Down
22 changes: 20 additions & 2 deletions lib/live_debugger/app/debugger/async_jobs/web/async_jobs_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ defmodule LiveDebugger.App.Debugger.AsyncJobs.Web.AsyncJobsLive do

alias LiveDebugger.App.Debugger.AsyncJobs.Queries, as: AsyncJobsQueries

@async_jobs_sectinon_id "async-jobs"

@doc """
Renders the `AsyncJobsLive` as a nested LiveView component.

Expand Down Expand Up @@ -72,9 +74,17 @@ defmodule LiveDebugger.App.Debugger.AsyncJobs.Web.AsyncJobsLive do

@impl true
def render(assigns) do
assigns = assign(assigns, id: @async_jobs_sectinon_id)

~H"""
<div class="max-w-full flex flex-1">
<.section title="Async jobs" id="async-jobs" inner_class="mx-0 p-4" class="flex-1">
<.collapsible_section
title="Async jobs"
id={@id}
inner_class="mx-0 p-4"
class="flex-1"
save_state_in_browser={true}
>
<div class="w-full h-full flex flex-col gap-2">
<.async_result :let={async_jobs} assign={@async_jobs}>
<:failed>
Expand All @@ -90,7 +100,7 @@ defmodule LiveDebugger.App.Debugger.AsyncJobs.Web.AsyncJobsLive do
/>
</.async_result>
</div>
</.section>
</.collapsible_section>
</div>
"""
end
Expand All @@ -113,6 +123,7 @@ defmodule LiveDebugger.App.Debugger.AsyncJobs.Web.AsyncJobsLive do

socket
|> assign(:async_jobs, AsyncResult.ok(updated_async_jobs))
|> push_event("#{@async_jobs_sectinon_id}-summary-pulse", %{})
|> noreply()
end

Expand All @@ -137,6 +148,12 @@ defmodule LiveDebugger.App.Debugger.AsyncJobs.Web.AsyncJobsLive do
def handle_info(_, socket), do: {:noreply, socket}

@impl true
def handle_async(:fetch_async_jobs, {:ok, {:ok, []}}, socket) do
socket
|> assign(:async_jobs, AsyncResult.ok([]))
|> noreply()
end

def handle_async(:fetch_async_jobs, {:ok, {:ok, async_jobs}}, socket)
when is_list(async_jobs) do
Enum.each(async_jobs, fn async_job ->
Expand All @@ -145,6 +162,7 @@ defmodule LiveDebugger.App.Debugger.AsyncJobs.Web.AsyncJobsLive do

socket
|> assign(:async_jobs, AsyncResult.ok(async_jobs))
|> push_event("#{@async_jobs_sectinon_id}-summary-pulse", %{})
|> noreply()
end

Expand Down
20 changes: 12 additions & 8 deletions lib/live_debugger/app/debugger/streams/web/components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,22 @@ defmodule LiveDebugger.App.Debugger.Streams.Web.Components do
"""
end

attr(:id, :string, required: true)
slot(:display, required: true)

def streams_section(assigns) do
~H"""
<div id="streams_section-container">
<.section id="streams" class="h-max overflow-y-hidden" title="Streams">
<:right_panel>
<.streams_info_tooltip id="stream-info" />
</:right_panel>
<%= render_slot(@display) %>
</.section>
</div>
<.collapsible_section
id={@id}
class="h-max overflow-y-hidden"
title="Streams"
save_state_in_browser={true}
>
<:right_panel>
<.streams_info_tooltip id="stream-info" />
</:right_panel>
<%= render_slot(@display) %>
</.collapsible_section>
"""
end

Expand Down
7 changes: 6 additions & 1 deletion lib/live_debugger/app/debugger/streams/web/streams_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ defmodule LiveDebugger.App.Debugger.Streams.Web.StreamsLive do
alias LiveDebugger.Services.CallbackTracer.Events.StreamUpdated
alias LiveDebugger.App.Debugger.Events.NodeIdParamChanged

@streams_section_id "streams-section-container"

@doc """
Renders the `StreamsLive` as a nested LiveView component.

Expand Down Expand Up @@ -67,9 +69,11 @@ defmodule LiveDebugger.App.Debugger.Streams.Web.StreamsLive do

@impl true
def render(assigns) do
assigns = assign(assigns, id: @streams_section_id)

~H"""
<div class="flex-1 max-w-full flex flex-col gap-4">
<StreamsComponents.streams_section>
<StreamsComponents.streams_section id={@id}>
<:display>
<.async_result :let={stream_names} assign={@stream_names}>
<:loading>
Expand Down Expand Up @@ -112,6 +116,7 @@ defmodule LiveDebugger.App.Debugger.Streams.Web.StreamsLive do
) do
socket
|> Hooks.Streams.assign_async_streams(stream, dom_id_fun)
|> push_event("#{@streams_section_id}-summary-pulse", %{})
|> noreply()
end

Expand Down
55 changes: 54 additions & 1 deletion lib/live_debugger/app/web/components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ defmodule LiveDebugger.App.Web.Components do
attr(:chevron_class, :any, default: nil, doc: "CSS class for the chevron icon")
attr(:open, :boolean, default: false, doc: "Whether the collapsible is open by default")

attr(:save_state_in_browser, :boolean,
default: false,
doc: "Whether to save the open/closed state in the browser's local storage"
)

attr(:icon, :string,
default: "icon-chevron-right",
doc: "Icon for chevron. It will be rotated 90 degrees when the collapsible is open"
Expand All @@ -140,13 +145,17 @@ defmodule LiveDebugger.App.Web.Components do
slot(:inner_block, required: true)

def collapsible(assigns) do
assigns = assign(assigns, :open, to_string(assigns.open))
assigns =
assigns
|> assign(:open, to_string(assigns.open))
|> assign(:save_state_in_browser, to_string(assigns.save_state_in_browser))

~H"""
<details
id={@id}
phx-hook="Collapsible"
data-open={@open}
data-save-state-in-browser={@save_state_in_browser}
class={[
"block"
| List.wrap(@class)
Expand Down Expand Up @@ -320,6 +329,50 @@ defmodule LiveDebugger.App.Web.Components do
"""
end

attr(:id, :string, required: true)
attr(:title, :string, required: true)

attr(:save_state_in_browser, :boolean,
default: false,
doc: "Whether to save the open/closed state in the browser's local storage"
)

attr(:class, :any, default: nil)
attr(:title_class, :any, default: nil)
attr(:inner_class, :any, default: nil)

slot(:right_panel)
slot(:inner_block)

def collapsible_section(assigns) do
~H"""
<.collapsible
id={@id}
phx-hook="CollapsedSectionPulse"
class={[
"w-full min-w-[20rem] flex flex-col shadow-custom rounded-sm bg-surface-0-bg border border-default-border"
| List.wrap(@class)
]}
label_class="pr-4 flex items-center h-12 p-2 border-b border-default-border"
save_state_in_browser={@save_state_in_browser}
>
<:label>
<div class="ml-1 flex justify-between items-center w-full gap-2">
<div class={["font-medium text-sm min-w-26" | List.wrap(@title_class)]}><%= @title %></div>
<div class="w-max">
<%= render_slot(@right_panel) %>
</div>
</div>
</:label>
<div class={[
"flex flex-1 overflow-auto rounded-sm bg-surface-0-bg" | List.wrap(@inner_class)
]}>
<%= render_slot(@inner_block) %>
</div>
</.collapsible>
"""
end

@doc """
Typography component to render headings.
"""
Expand Down
2 changes: 1 addition & 1 deletion priv/static/app.css

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions priv/static/app.js

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions priv/static/app.js.map

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions test/e2e/async_jobs_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ defmodule LiveDebugger.E2E.AsyncJobsTest do
:ok
end

setup %{sessions: [_dev_app, debugger]} do
debugger
|> visit("/")
|> set_collapsible_open_state("async-jobs", "true")

:ok
end

@sessions 2
feature "user can see and track async jobs in LiveView and LiveComponent", %{
sessions: [dev_app, debugger]
Expand Down Expand Up @@ -104,4 +112,14 @@ defmodule LiveDebugger.E2E.AsyncJobsTest do
do: css("#node-inspector-basic-info-current-node-module", text: text)

defp component_tree_node(cid), do: css("#button-tree-node-#{cid}-components-tree")

defp set_collapsible_open_state(debugger, section_id, state) do
debugger
|> execute_script(
"""
localStorage.setItem(`collapsible-open-${arguments[0]}`, arguments[1])
""",
[section_id, state]
)
end
end
Loading
Loading