2021-06-30 23:48:27 +08:00
|
|
|
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
|
2021-07-07 20:32:49 +08:00
|
|
|
~H"""
|
2021-06-30 23:48:27 +08:00
|
|
|
<div class="p-6 max-w-4xl flex flex-col space-y-3">
|
|
|
|
<h3 class="text-2xl font-semibold text-gray-800">
|
|
|
|
Bin
|
|
|
|
</h3>
|
|
|
|
<div class="w-full flex-col space-y-5">
|
|
|
|
<p class="text-gray-700">
|
2021-07-01 02:06:33 +08:00
|
|
|
Here you can find all the deleted cells from this notebook session.
|
2021-06-30 23:48:27 +08:00
|
|
|
</p>
|
|
|
|
<%= if @bin_entries == [] do %>
|
|
|
|
<div class="p-5 flex space-x-4 items-center border border-gray-200 rounded-lg">
|
|
|
|
<div>
|
2021-07-07 20:32:49 +08:00
|
|
|
<.remix_icon icon="windy-line" class="text-gray-400 text-xl" />
|
2021-06-30 23:48:27 +08:00
|
|
|
</div>
|
|
|
|
<div class="text-gray-600">
|
|
|
|
There are currently no cells in the bin.
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<% else %>
|
2021-07-07 20:32:49 +08:00
|
|
|
<form phx-change="search" onsubmit="return false" phx-target={@myself}>
|
2021-06-30 23:48:27 +08:00
|
|
|
<input class="input"
|
|
|
|
type="text"
|
|
|
|
name="search"
|
2021-07-07 20:32:49 +08:00
|
|
|
value={@search}
|
2021-06-30 23:48:27 +08:00
|
|
|
placeholder="Search"
|
|
|
|
autocomplete="off"
|
|
|
|
spellcheck="false"
|
|
|
|
autofocus />
|
|
|
|
</form>
|
2021-07-01 04:07:57 +08:00
|
|
|
<div class="flex flex-col space-y-8 min-h-[30rem] pt-3">
|
2021-07-01 02:20:08 +08:00
|
|
|
<%= for {%{cell: cell} = entry, index} <- Enum.take(@matching_entries, @limit) |> Enum.with_index(1) do %>
|
2021-07-01 04:07:57 +08:00
|
|
|
<div class="flex flex-col space-y-1">
|
2021-06-30 23:48:27 +08:00
|
|
|
<div class="flex justify-between items-center">
|
|
|
|
<p class="text-sm text-gray-700">
|
2021-07-01 04:07:57 +08:00
|
|
|
<%= index %>.
|
|
|
|
<span class="font-semibold"><%= Cell.type(cell) |> Atom.to_string() |> String.capitalize() %></span> cell
|
2021-06-30 23:48:27 +08:00
|
|
|
deleted from <span class="font-semibold">“<%= entry.section_name %>”</span> section
|
2021-07-07 20:32:49 +08:00
|
|
|
<span class="font-semibold"><%= format_date_relatively(entry.deleted_at) %></span>
|
2021-06-30 23:48:27 +08:00
|
|
|
</p>
|
|
|
|
<div class="flex justify-end space-x-2">
|
|
|
|
<span class="tooltip left" aria-label="Copy source">
|
|
|
|
<button class="icon-button"
|
2021-07-07 20:32:49 +08:00
|
|
|
id={"bin-cell-#{cell.id}-clipcopy"}
|
2021-06-30 23:48:27 +08:00
|
|
|
phx-hook="ClipCopy"
|
2021-07-07 20:32:49 +08:00
|
|
|
data-target-id={"bin-cell-#{cell.id}-source"}>
|
|
|
|
<.remix_icon icon="clipboard-line" class="text-lg" />
|
2021-06-30 23:48:27 +08:00
|
|
|
</button>
|
|
|
|
</span>
|
|
|
|
<span class="tooltip left" aria-label="Restore">
|
|
|
|
<button class="icon-button"
|
|
|
|
phx-click="restore"
|
2021-07-07 20:32:49 +08:00
|
|
|
phx-value-cell_id={entry.cell.id}
|
|
|
|
phx-target={@myself}>
|
|
|
|
<.remix_icon icon="arrow-go-back-line" class="text-lg" />
|
2021-06-30 23:48:27 +08:00
|
|
|
</button>
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
2021-07-29 01:35:36 +08:00
|
|
|
<.code_preview
|
|
|
|
source_id={"bin-cell-#{cell.id}-source"}
|
|
|
|
language={Cell.type(cell)}
|
|
|
|
source={cell.source} />
|
2021-06-30 23:48:27 +08:00
|
|
|
</div>
|
|
|
|
<% end %>
|
|
|
|
<%= if length(@matching_entries) > @limit do %>
|
|
|
|
<div class="flex justify-center">
|
2021-07-07 20:32:49 +08:00
|
|
|
<button class="button button-outlined-gray" phx-click="more" phx-target={@myself}>
|
2021-06-30 23:48:27 +08:00
|
|
|
Older
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<% end %>
|
|
|
|
</div>
|
|
|
|
<% end %>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
"""
|
|
|
|
end
|
|
|
|
|
2021-07-07 20:32:49 +08:00
|
|
|
defp format_date_relatively(date) do
|
|
|
|
time_words = date |> DateTime.to_naive() |> Livebook.Utils.Time.time_ago_in_words()
|
|
|
|
time_words <> " ago"
|
|
|
|
end
|
|
|
|
|
2021-06-30 23:48:27 +08:00
|
|
|
@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
|
2021-09-05 01:16:01 +08:00
|
|
|
Livebook.Session.restore_cell(socket.assigns.session.pid, cell_id)
|
2021-06-30 23:48:27 +08:00
|
|
|
{: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
|