Skip to content
Open
69 changes: 55 additions & 14 deletions lib/livebook_web/live/file_select_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ defmodule LivebookWeb.FileSelectComponent do
send_event(socket.assigns.target, {:mount_file_system, file_system})
send_event(socket.assigns.target, {:set_file, file, %{exists: true}})

{:noreply, assign(socket, loading: true)}
{:noreply, socket}
end

def handle_event("set_path", %{"path" => path}, socket) do
Expand All @@ -544,7 +544,7 @@ defmodule LivebookWeb.FileSelectComponent do
end

send_event(socket.assigns.target, {:set_file, file, info})
{:noreply, assign(socket, loading: socket.assigns.file.path != path)}
{:noreply, socket}
end

def handle_event("clear_error", %{}, socket) do
Expand Down Expand Up @@ -640,19 +640,31 @@ defmodule LivebookWeb.FileSelectComponent do
current_file_infos = assigns[:file_infos] || []
{dir, prefix} = dir_and_prefix(assigns.file)

{file_infos, socket} =
if dir != assigns.current_dir or force_reload? do
case get_file_infos(dir, assigns.extnames, assigns.running_files) do
{:ok, file_infos} ->
{file_infos, assign(socket, :current_dir, dir)}
dir_changed? = dir != assigns.current_dir

{:error, error} ->
{current_file_infos, put_error(socket, error)}
end
else
{current_file_infos, socket}
if dir_changed? or force_reload? do
start_async_file_listing(socket, dir, prefix, current_file_infos)
else
# Just re-annotate with the current prefix (search filter changed)
update_file_display(socket, current_file_infos, prefix)
end
end

defp start_async_file_listing(socket, dir, prefix, current_file_infos) do
extnames = socket.assigns.extnames
running_files = socket.assigns.running_files

socket
|> assign(loading: true)
|> start_async(:list_files, fn ->
case get_file_infos(dir, extnames, running_files) do
{:ok, file_infos} -> {:ok, file_infos, dir, prefix}
{:error, error} -> {:error, error, current_file_infos, prefix}
end
end)
end

defp update_file_display(socket, file_infos, prefix) do
file_infos = annotate_matching(file_infos, prefix)

{unhighlighted_file_infos, highlighted_file_infos} =
Expand All @@ -665,11 +677,40 @@ defmodule LivebookWeb.FileSelectComponent do
assign(socket,
file_infos: file_infos,
unhighlighted_file_infos: unhighlighted_file_infos,
highlighted_file_infos: highlighted_file_infos,
loading: false
highlighted_file_infos: highlighted_file_infos
)
end

@impl true
def handle_async(:list_files, {:ok, {:ok, file_infos, dir, prefix}}, socket) do
socket =
socket
|> assign(current_dir: dir)
|> update_file_display(file_infos, prefix)
|> assign(loading: false)

{:noreply, socket}
end

def handle_async(:list_files, {:ok, {:error, error, current_file_infos, prefix}}, socket) do
socket =
socket
|> update_file_display(current_file_infos, prefix)
|> put_error(error)
|> assign(loading: false)

{:noreply, socket}
end

def handle_async(:list_files, {:exit, _reason}, socket) do
socket =
socket
|> put_error("Listing files failed")
|> assign(loading: false)

{:noreply, socket}
end

defp annotate_matching(file_infos, prefix) do
for %{name: name} = info <- file_infos do
case String.split(name, prefix, parts: 2) do
Expand Down
68 changes: 50 additions & 18 deletions test/livebook_web/live/file_select_component_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,69 @@ defmodule LivebookWeb.FileSelectComponentTest do
import Livebook.TestHelpers

alias Livebook.FileSystem
alias LivebookWeb.FileSelectComponent

test "when the path has a trailing slash, lists that directory" do
test "when the path has a trailing slash, lists that directory", %{conn: conn} do
file = FileSystem.File.local(notebooks_path() <> "/")
assert render_component(FileSelectComponent, attrs(file: file)) =~ "basic.livemd"
assert render_component(FileSelectComponent, attrs(file: file)) =~ ".."
html = render_file_select(conn, file)

assert html =~ "basic.livemd"
assert html =~ ".."
end

test "when the path has no trailing slash, lists the parent directory" do
test "when the path has no trailing slash, lists the parent directory", %{conn: conn} do
file = FileSystem.File.local(notebooks_path())
assert render_component(FileSelectComponent, attrs(file: file)) =~ "notebooks"

assert render_file_select(conn, file) =~ "notebooks"
end

test "does not show parent directory when in root" do
test "does not show parent directory when in root", %{conn: conn} do
file = FileSystem.File.local(p("/"))
refute render_component(FileSelectComponent, attrs(file: file)) =~ ".."

refute render_file_select(conn, file) =~ ".."
end

defp attrs(attrs) do
Keyword.merge(
[
id: "1",
file: FileSystem.File.local(p("/")),
extnames: [".livemd"],
running_files: []
],
attrs
)
defp render_file_select(conn, file) do
{:ok, view, _html} =
live_isolated(conn, LivebookWeb.FileSelectComponentTest.Live,
session: %{"path" => file.path}
)

render_async(view)
end

defp notebooks_path() do
Path.expand("../../support/notebooks", __DIR__)
end

defmodule Live do
use Phoenix.LiveView, layout: false

alias Livebook.FileSystem
alias LivebookWeb.FileSelectComponent

@impl true
def mount(_params, %{"path" => path}, socket) do
socket =
assign(socket,
file: FileSystem.File.local(path),
extnames: [".livemd"],
running_files: []
)

{:ok, socket}
end

@impl true
def render(assigns) do
~H"""
<.live_component
module={FileSelectComponent}
id="1"
file={@file}
extnames={@extnames}
running_files={@running_files}
/>
"""
end
end
end