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) # Only show text cells, as they have an actual content bin_entries = Enum.filter(bin_entries, fn entry -> Cell.type(entry.cell) in [:markdown, :elixir] end) {:ok, socket |> assign(:bin_entries, bin_entries) |> assign(assigns) |> assign_matching_entries()} end @impl true def render(assigns) do ~H"""

Bin

Here you can find all the deleted cells from this notebook session.

<%= if @bin_entries == [] do %>
<.remix_icon icon="windy-line" class="text-gray-400 text-xl" />
There are currently no cells in the bin.
<% else %>
<%= for {%{cell: cell} = entry, index} <- Enum.take(@matching_entries, @limit) |> Enum.with_index(1) do %>

<%= index %>. <%= 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.type(cell)} source={cell.source} />
<% end %> <%= if length(@matching_entries) > @limit do %>
<% end %>
<% end %>
""" end 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