Beautify match file selector results

- Makes the file selector results to have a "box"/"container" of its own

- Seperates the filtred files from rest of the files with a border

- Optimize path selector to only traverse the filesystem if the directory
  changes
This commit is contained in:
Benjamin Philip 2021-04-21 12:15:11 +02:00 committed by José Valim
parent 193050cae5
commit d41f3a73fe

View file

@ -17,7 +17,23 @@ defmodule LivebookWeb.PathSelectComponent do
@impl true @impl true
def mount(socket) do def mount(socket) do
inner_block = Map.get(socket.assigns, :inner_block, nil) inner_block = Map.get(socket.assigns, :inner_block, nil)
{:ok, assign(socket, inner_block: inner_block)} {:ok, assign(socket, inner_block: inner_block, current_dir: nil)}
end
@impl true
def update(assigns, socket) do
%{assigns: assigns} = socket = assign(socket, assigns)
{dir, basename} = split_path(assigns.path)
dir = Path.expand(dir)
files =
if assigns.current_dir != dir do
list_files(dir, assigns.extnames, assigns.running_paths)
else
assigns.files
end
{:ok, assign(socket, files: annotate_matching(files, basename), current_dir: dir)}
end end
@impl true @impl true
@ -50,8 +66,16 @@ defmodule LivebookWeb.PathSelectComponent do
<% end %> <% end %>
</div> </div>
<div class="flex-grow -m-1 p-1 overflow-y-auto tiny-scrollbar"> <div class="flex-grow -m-1 p-1 overflow-y-auto tiny-scrollbar">
<%= if highlighting?(@files) do %>
<div class="grid grid-cols-4 gap-2 border-b border-dashed border-grey-200 mb-2 pb-2">
<%= for file <- @files, file.highlighted != "" do %>
<%= render_file(file, @phx_target) %>
<% end %>
</div>
<% end %>
<div class="grid grid-cols-4 gap-2"> <div class="grid grid-cols-4 gap-2">
<%= for file <- list_matching_files(@path, @extnames, @running_paths) do %> <%= for file <- @files, file.highlighted == "" do %>
<%= render_file(file, @phx_target) %> <%= render_file(file, @phx_target) %>
<% end %> <% end %>
</div> </div>
@ -60,6 +84,10 @@ defmodule LivebookWeb.PathSelectComponent do
""" """
end end
defp highlighting?(files) do
Enum.any?(files, & &1.highlighted != "")
end
defp render_file(file, phx_target) do defp render_file(file, phx_target) do
icon = icon =
case file do case file do
@ -92,18 +120,17 @@ defmodule LivebookWeb.PathSelectComponent do
""" """
end end
defp list_matching_files(path, extnames, running_paths) do defp annotate_matching(files, prefix) do
# Note: to provide an intuitive behavior when typing the path for %{name: name} = file <- files do
# we enter a new directory when it has a trailing slash, if String.starts_with?(name, prefix) do
# so given "/foo/bar" we list files in "foo" and given "/foo/bar/ %{file | highlighted: prefix, unhighlighted: String.replace_prefix(name, prefix, "")}
# we list files in "bar". else
# %{file | highlighted: "", unhighlighted: name}
# The basename is kinda like search within the current directory, end
# so we highlight files starting with that string. end
end
{dir, basename} = split_path(path)
dir = Path.expand(dir)
defp list_files(dir, extnames, running_paths) do
if File.exists?(dir) do if File.exists?(dir) do
file_names = file_names =
case File.ls(dir) do case File.ls(dir) do
@ -114,36 +141,34 @@ defmodule LivebookWeb.PathSelectComponent do
file_infos = file_infos =
file_names file_names
|> Enum.map(fn name -> |> Enum.map(fn name ->
file_info(dir, name, basename, running_paths) file_info(name, Path.join(dir, name), running_paths)
end) end)
|> Enum.filter(fn file -> |> Enum.filter(fn file ->
not hidden?(file.name) and (file.is_dir or valid_extension?(file.name, extnames)) not hidden?(file.name) and (file.is_dir or valid_extension?(file.name, extnames))
end) end)
parent = Path.dirname(dir)
file_infos = file_infos =
if Path.dirname(dir) == dir do if parent == dir do
file_infos file_infos
else else
parent_dir = file_info(dir, "..", basename, running_paths) [file_info("..", parent, running_paths) | file_infos]
[parent_dir | file_infos]
end end
Enum.sort_by(file_infos, fn file -> Enum.sort_by(file_infos, fn file -> {!file.is_dir, file.name} end)
{-String.length(file.highlighted), !file.is_dir, file.name}
end)
else else
[] []
end end
end end
defp file_info(dir, name, filter, running_paths) do defp file_info(name, path, running_paths) do
path = Path.join(dir, name) |> Path.expand()
is_dir = File.dir?(path) is_dir = File.dir?(path)
%{ %{
name: name, name: name,
highlighted: if(String.starts_with?(name, filter), do: filter, else: ""), highlighted: "",
unhighlighted: String.replace_prefix(name, filter, ""), unhighlighted: name,
path: if(is_dir, do: ensure_trailing_slash(path), else: path), path: if(is_dir, do: ensure_trailing_slash(path), else: path),
is_dir: is_dir, is_dir: is_dir,
is_running: path in running_paths is_running: path in running_paths
@ -158,6 +183,13 @@ defmodule LivebookWeb.PathSelectComponent do
Path.extname(filename) in extnames Path.extname(filename) in extnames
end end
# Note: to provide an intuitive behavior when typing the path
# we enter a new directory when it has a trailing slash,
# so given "/foo/bar" we list files in "foo" and given "/foo/bar/
# we list files in "bar".
#
# The basename is kinda like search within the current directory,
# so we highlight files starting with that string.
defp split_path(path) do defp split_path(path) do
if String.ends_with?(path, "/") do if String.ends_with?(path, "/") do
{path, ""} {path, ""}