defmodule LivebookWeb.SessionLive.BinComponent do
use LivebookWeb, :live_component
alias Livebook.Notebook.Cell
@initial_limit 10
@limit_step 10
@impl true
def mount(socket) do
{:ok, assign(socket, search: "", limit: @initial_limit)}
end
@impl true
def update(assigns, socket) do
{bin_entries, assigns} = Map.pop(assigns, :bin_entries)
{:ok,
socket
|> assign(:bin_entries, bin_entries)
|> assign(assigns)
|> assign_matching_entries()}
end
@impl true
def render(assigns) do
~H"""
Bin
Find all your deleted cells from this notebook session
<%= if @bin_entries != [] do %>
<% end %>
<%= cond do %>
<% @bin_entries == [] -> %>
<.remix_icon
icon="delete-bin-6-line"
class="text-gray-700 text-xl bg-gray-100 p-3 rounded-full"
/>
You haven't deleted any cells or sections yet. Once you do, they'll appear here.
<% @matching_entries == [] -> %>
<.remix_icon
icon="file-search-line"
class="text-gray-700 text-xl bg-gray-100 p-3 rounded-full"
/>
We couldn't find any results for your query.
<% true -> %>
<%= for %{cell: cell} = entry <- Enum.take(@matching_entries, @limit) do %>
<.cell_icon cell_type={Cell.type(cell)} />
<%= Cell.type(cell) |> Atom.to_string() |> String.capitalize() %>
cell
deleted from “<%= entry.section_name %>”
section
<%= format_date_relatively(entry.deleted_at) %>
<.code_preview
source_id={"bin-cell-#{cell.id}-source"}
language={cell_language(cell)}
source={cell.source}
/>
<% end %>
<%= if length(@matching_entries) > @limit do %>
<% end %>
<% end %>
"""
end
defp cell_icon(%{cell_type: :code} = assigns) do
~H"""
"""
end
defp cell_icon(%{cell_type: :markdown} = assigns) do
~H"""
"""
end
defp cell_icon(%{cell_type: :smart} = assigns) do
~H"""
<.remix_icon icon="flashlight-line text-red-900" />
"""
end
defp cell_language(%Cell.Markdown{}), do: "markdown"
defp cell_language(%Cell.Code{}), do: "elixir"
defp cell_language(%Cell.Smart{}), do: "elixir"
defp format_date_relatively(date) do
time_words = date |> DateTime.to_naive() |> Livebook.Utils.Time.time_ago_in_words()
time_words <> " ago"
end
@impl true
def handle_event("search", %{"search" => search}, socket) do
{:noreply, assign(socket, search: search, limit: @initial_limit) |> assign_matching_entries()}
end
def handle_event("more", %{}, socket) do
{:noreply, assign(socket, limit: socket.assigns.limit + @limit_step)}
end
def handle_event("restore", %{"cell_id" => cell_id}, socket) do
Livebook.Session.restore_cell(socket.assigns.session.pid, cell_id)
{:noreply, push_patch(socket, to: socket.assigns.return_to)}
end
defp assign_matching_entries(socket) do
matching_entries = filter_matching(socket.assigns.bin_entries, socket.assigns.search)
assign(socket, matching_entries: matching_entries)
end
defp filter_matching(entries, search) do
parts =
search
|> String.split()
|> Enum.map(fn part -> ~r/#{Regex.escape(part)}/i end)
Enum.filter(entries, fn entry ->
Enum.all?(parts, fn part ->
entry.cell.source =~ part
end)
end)
end
end