Remember microprint state on reload
This commit is contained in:
parent
7afe5c1fe8
commit
790d2f0e1d
@ -7,8 +7,8 @@ defmodule FirehoseWeb.MicroprintsLive do
|
||||
@source_dirs ["app", "blogex"]
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
files = scan_source_files()
|
||||
def mount(params, _session, socket) do
|
||||
files = scan_source_files_with_dir()
|
||||
|
||||
microprints =
|
||||
files
|
||||
@ -18,9 +18,7 @@ defmodule FirehoseWeb.MicroprintsLive do
|
||||
socket
|
||||
|> assign(:page_title, "Microprints")
|
||||
|> assign(:microprints, microprints)
|
||||
|> assign(:expanded_path, nil)
|
||||
|> assign(:highlighted_path, nil)
|
||||
|> assign(:highlighted_line, nil)}
|
||||
|> restore_state_from_params(params)}
|
||||
rescue
|
||||
e ->
|
||||
{:ok,
|
||||
@ -28,11 +26,17 @@ defmodule FirehoseWeb.MicroprintsLive do
|
||||
|> assign(:page_title, "Microprints")
|
||||
|> assign(:microprints, [])
|
||||
|> assign(:expanded_path, nil)
|
||||
|> assign(:source_content, nil)
|
||||
|> assign(:highlighted_path, nil)
|
||||
|> assign(:highlighted_line, nil)
|
||||
|> put_flash(:error, "Error loading microprints: #{inspect(e)}")}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(params, _uri, socket) do
|
||||
{:noreply, restore_state_from_params(socket, params)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
@ -46,7 +50,7 @@ defmodule FirehoseWeb.MicroprintsLive do
|
||||
<.microprint_legend />
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mt-6">
|
||||
<%= for %{path: path, microprint: microprint, source: source} = item <- @microprints do %>
|
||||
<%= for %{path: path, microprint: microprint, source_dir: _source_dir} = item <- @microprints do %>
|
||||
{error = item[:error]}
|
||||
<div class="card bg-base-100 shadow-sm border border-zinc-200">
|
||||
<div class="card-body p-4">
|
||||
@ -76,8 +80,8 @@ defmodule FirehoseWeb.MicroprintsLive do
|
||||
highlighted_line={@highlighted_line}
|
||||
/>
|
||||
|
||||
<%= if @expanded_path == path and source do %> <.source_viewer
|
||||
content={source}
|
||||
<%= if @expanded_path == path and @source_content do %> <.source_viewer
|
||||
source_content={@source_content}
|
||||
highlighted_line={@highlighted_line}
|
||||
language="elixir"
|
||||
file_path={path}
|
||||
@ -111,11 +115,14 @@ defmodule FirehoseWeb.MicroprintsLive do
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
{:noreply,
|
||||
socket =
|
||||
socket
|
||||
|> assign(:highlighted_path, path)
|
||||
|> assign(:highlighted_line, highlighted)
|
||||
|> assign(:expanded_path, expanded)}
|
||||
|> assign(:expanded_path, expanded)
|
||||
|> assign(:source_content, nil)
|
||||
|
||||
{:noreply, push_patch(socket, to: build_params(socket))}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@ -126,11 +133,75 @@ defmodule FirehoseWeb.MicroprintsLive do
|
||||
_ -> path
|
||||
end
|
||||
|
||||
{:noreply, assign(socket, :expanded_path, expanded)}
|
||||
source_content =
|
||||
case expanded do
|
||||
nil -> nil
|
||||
^path ->
|
||||
item = Enum.find(socket.assigns.microprints, &(&1.path == path))
|
||||
if item && item.source_dir do
|
||||
abs_path = resolve_source_path(item.source_dir, path)
|
||||
read_source(abs_path)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
socket = socket |> assign(:expanded_path, expanded) |> assign(:source_content, source_content)
|
||||
|
||||
{:noreply, push_patch(socket, to: build_params(socket))}
|
||||
end
|
||||
|
||||
# Private helpers
|
||||
|
||||
defp restore_state_from_params(socket, params) do
|
||||
expanded_path = params["expanded"]
|
||||
highlighted_path = params["highlighted"]
|
||||
highlighted_line = if params["line"], do: String.to_integer(params["line"]), else: nil
|
||||
|
||||
source_content =
|
||||
if expanded_path do
|
||||
item = Enum.find(socket.assigns.microprints, &(&1.path == expanded_path))
|
||||
|
||||
if item && item.source_dir do
|
||||
abs_path = resolve_source_path(item.source_dir, expanded_path)
|
||||
read_source(abs_path)
|
||||
end
|
||||
end
|
||||
|
||||
socket
|
||||
|> assign(:expanded_path, expanded_path)
|
||||
|> assign(:source_content, source_content)
|
||||
|> assign(:highlighted_path, highlighted_path)
|
||||
|> assign(:highlighted_line, highlighted_line)
|
||||
end
|
||||
|
||||
defp build_params(socket) do
|
||||
params = %{}
|
||||
|
||||
params =
|
||||
if socket.assigns.expanded_path do
|
||||
Map.put(params, "expanded", socket.assigns.expanded_path)
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
params =
|
||||
if socket.assigns.highlighted_path do
|
||||
Map.put(params, "highlighted", socket.assigns.highlighted_path)
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
params =
|
||||
if socket.assigns.highlighted_line do
|
||||
params |> Map.put("line", socket.assigns.highlighted_line)
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
"/microprints" <> if params != %{}, do: "?" <> URI.encode_query(params), else: ""
|
||||
end
|
||||
|
||||
defp sort_by_mtime(files) do
|
||||
app_root = Mix.Project.project_file() |> Path.dirname()
|
||||
|
||||
@ -143,16 +214,39 @@ defmodule FirehoseWeb.MicroprintsLive do
|
||||
|> Enum.map(fn {file, _mtime} -> file end)
|
||||
end
|
||||
|
||||
defp sort_by_mtime_with_dir(tuples) do
|
||||
app_root = Mix.Project.project_file() |> Path.dirname()
|
||||
|
||||
tuples
|
||||
|> Enum.map(fn {path, dir} ->
|
||||
abs_path = Path.join(app_root, path)
|
||||
{{path, dir}, File.stat!(abs_path).mtime}
|
||||
end)
|
||||
|> Enum.sort_by(fn {{_path, _dir}, mtime} -> mtime end, :desc)
|
||||
|> Enum.map(fn {{path, dir}, _mtime} -> {path, dir} end)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def scan_source_files do
|
||||
@source_dirs
|
||||
|> Enum.flat_map(&collect_elixir_files/1)
|
||||
|> Enum.uniq()
|
||||
|> Enum.uniq_by(&elem(&1, 0))
|
||||
|> Enum.map(&elem(&1, 0))
|
||||
|> Enum.sort()
|
||||
|> sort_by_mtime()
|
||||
|> Enum.take(2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def scan_source_files_with_dir do
|
||||
@source_dirs
|
||||
|> Enum.flat_map(&collect_elixir_files/1)
|
||||
|> Enum.uniq_by(&elem(&1, 0))
|
||||
|> Enum.sort_by(&elem(&1, 0))
|
||||
|> sort_by_mtime_with_dir()
|
||||
|> Enum.take(2)
|
||||
end
|
||||
|
||||
defp resolve_absolute_paths(files) do
|
||||
app_root = Mix.Project.project_file() |> Path.dirname()
|
||||
|
||||
@ -181,26 +275,26 @@ defmodule FirehoseWeb.MicroprintsLive do
|
||||
|> Path.wildcard()
|
||||
|> Enum.filter(&File.regular?(&1))
|
||||
|> Enum.reject(&excluded_path?/1)
|
||||
|> Enum.map(&format_path(&1, monorepo_root, dir))
|
||||
|> Enum.map(&format_path_and_dir(&1, monorepo_root, dir))
|
||||
|
||||
false ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp format_path(path, monorepo_root, dir) do
|
||||
defp format_path_and_dir(path, monorepo_root, dir) do
|
||||
relative = Path.relative_to(path, monorepo_root)
|
||||
|
||||
formatted =
|
||||
case dir do
|
||||
"app" ->
|
||||
String.replace_prefix(relative, "app/", "")
|
||||
|
||||
_ ->
|
||||
"../" <> relative
|
||||
end
|
||||
"app" -> String.replace_prefix(relative, "app/", "")
|
||||
_ -> "../" <> relative
|
||||
end
|
||||
|
||||
defp process_file(rel_path) do
|
||||
{formatted, dir}
|
||||
end
|
||||
|
||||
defp process_file({rel_path, source_dir}) do
|
||||
abs_path = resolve_absolute_paths([rel_path]) |> List.first()
|
||||
|
||||
case MicroprintCache.get_microprint(abs_path) do
|
||||
@ -211,13 +305,10 @@ defmodule FirehoseWeb.MicroprintsLive do
|
||||
|> Enum.with_index(1)
|
||||
|> Enum.map(fn {line, num} -> Map.put(line, :line_number, num) end)
|
||||
|
||||
# Read source code for expand/contract
|
||||
source = read_source(abs_path)
|
||||
|
||||
%{
|
||||
path: rel_path,
|
||||
microprint: Map.put(microprint, :lines, lines_with_numbers),
|
||||
source: source
|
||||
source_dir: source_dir,
|
||||
microprint: Map.put(microprint, :lines, lines_with_numbers)
|
||||
}
|
||||
|
||||
{:error, reason} ->
|
||||
@ -225,6 +316,13 @@ defmodule FirehoseWeb.MicroprintsLive do
|
||||
end
|
||||
end
|
||||
|
||||
defp resolve_source_path(source_dir, rel_path) do
|
||||
app_root = Mix.Project.project_file() |> Path.dirname()
|
||||
monorepo_root = app_root |> Path.dirname()
|
||||
base = Path.join(monorepo_root, source_dir)
|
||||
Path.join(base, rel_path)
|
||||
end
|
||||
|
||||
defp read_source(abs_path) do
|
||||
case File.read(abs_path) do
|
||||
{:ok, content} ->
|
||||
@ -255,7 +353,7 @@ defmodule FirehoseWeb.MicroprintsLive do
|
||||
data-language={@language}
|
||||
style="background: var(--sv-bg); color: var(--sv-text);"
|
||||
>
|
||||
<pre id={@viewer_id <> "-pre"} phx-update="ignore" class="m-0 p-2"><code class={"language-#{@language || "plaintext"}"}><%= @content %></code></pre>
|
||||
<pre id={@viewer_id <> "-pre"} phx-update="ignore" class="m-0 p-2"><code class={"language-#{@language || "plaintext"}"}><%= @source_content %></code></pre>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@ -139,4 +139,42 @@ defmodule FirehoseWeb.MicroprintsLiveTest do
|
||||
"file B should show Collapse button when expanded"
|
||||
end
|
||||
end
|
||||
|
||||
describe "URL state persistence" do
|
||||
test "expanded path is restored from query param on mount", %{conn: conn} do
|
||||
files = MicroprintsLive.scan_source_files()
|
||||
file_a = List.first(files)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/microprints?expanded=#{file_a}")
|
||||
|
||||
html = render(view)
|
||||
assert html =~ "Collapse",
|
||||
"Expanded file should show Collapse button when restored from URL param"
|
||||
assert html =~ ~s(phx-value-path="#{file_a}")
|
||||
end
|
||||
|
||||
test "unknown expanded path in URL is ignored gracefully", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/microprints?expanded=nonexistent.ex")
|
||||
|
||||
html = render(view)
|
||||
refute html =~ "Collapse",
|
||||
"Nonexistent expanded path should not show Collapse"
|
||||
end
|
||||
|
||||
test "expand updates URL with expanded param", %{conn: conn} do
|
||||
files = MicroprintsLive.scan_source_files()
|
||||
file_a = List.first(files)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/microprints")
|
||||
|
||||
view
|
||||
|> element("button[phx-value-path=\"#{file_a}\"]", "Expand")
|
||||
|> render_click()
|
||||
|
||||
# URL should contain expanded param after clicking Expand
|
||||
html = render(view)
|
||||
assert html =~ "Collapse",
|
||||
"File should remain expanded after push_patch updates URL"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user