Add an action converting smart cell to elixir cell (#1030)

* Add an action converting smart cell to elixir cell

* Simplify tests setup

* Add confirmation

* Update lib/livebook_web/live/session_live/cell_component.ex

Co-authored-by: José Valim <jose.valim@dashbit.co>

Co-authored-by: José Valim <jose.valim@dashbit.co>
This commit is contained in:
Jonatan Kłosko 2022-03-02 12:13:44 +01:00 committed by GitHub
parent 29f0f54bbd
commit aaeac6bb95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 119 additions and 31 deletions

View file

@ -301,6 +301,14 @@ defmodule Livebook.Session do
GenServer.cast(pid, {:move_section, self(), section_id, offset})
end
@doc """
Sends cell convertion request to the server.
"""
@spec convert_smart_cell(pid(), Cell.id()) :: :ok
def convert_smart_cell(pid, cell_id) do
GenServer.cast(pid, {:convert_smart_cell, self(), cell_id})
end
@doc """
Sends cell evaluation request to the server.
"""
@ -688,6 +696,26 @@ defmodule Livebook.Session do
{:noreply, handle_operation(state, operation)}
end
def handle_cast({:convert_smart_cell, client_pid, cell_id}, state) do
state =
with {:ok, %Cell.Smart{} = cell, section} <-
Notebook.fetch_cell_and_section(state.data.notebook, cell_id) do
index = Enum.find_index(section.cells, &(&1 == cell))
attrs = Map.take(cell, [:source, :outputs])
state
|> handle_operation({:delete_cell, client_pid, cell.id})
|> handle_operation(
{:insert_cell, client_pid, section.id, index, :elixir, Utils.random_id(), attrs}
)
else
_ -> state
end
{:noreply, state}
end
def handle_cast({:queue_cell_evaluation, client_pid, cell_id}, state) do
operation = {:queue_cells_evaluation, client_pid, [cell_id]}
{:noreply, handle_operation(state, operation)}

View file

@ -706,6 +706,12 @@ defmodule LivebookWeb.SessionLive do
{:noreply, socket}
end
def handle_event("convert_smart_cell", %{"cell_id" => cell_id}, socket) do
Session.convert_smart_cell(socket.assigns.session.pid, cell_id)
{:noreply, socket}
end
def handle_event("queue_cell_evaluation", %{"cell_id" => cell_id}, socket) do
Session.queue_cell_evaluation(socket.assigns.session.pid, cell_id)

View file

@ -44,19 +44,8 @@ defmodule LivebookWeb.SessionLive.CellComponent do
~H"""
<.cell_actions>
<:secondary>
<span class="tooltip top" data-tooltip="Edit content" data-element="enable-insert-mode-button">
<button class="icon-button" aria-label="edit content">
<.remix_icon icon="pencil-line" class="text-xl" />
</button>
</span>
<span class="tooltip top" data-tooltip="Insert image" data-element="insert-image-button">
<%= live_patch to: Routes.session_path(@socket, :cell_upload, @session_id, @cell_view.id),
class: "icon-button",
aria_label: "insert image",
role: "button" do %>
<.remix_icon icon="image-add-line" class="text-xl" />
<% end %>
</span>
<.enable_insert_mode_button />
<.insert_image_button cell_id={@cell_view.id} session_id={@session_id} socket={@socket} />
<.cell_link_button cell_id={@cell_view.id} />
<.move_cell_up_button cell_id={@cell_view.id} />
<.move_cell_down_button cell_id={@cell_view.id} />
@ -127,12 +116,8 @@ defmodule LivebookWeb.SessionLive.CellComponent do
reevaluate_automatically={false} />
</:primary>
<:secondary>
<span class="tooltip top" data-tooltip="Toggle source" data-element="toggle-source-button">
<button class="icon-button" aria-label="toggle source">
<.remix_icon icon="code-line" class="text-xl" data-element="show-code-icon" />
<.remix_icon icon="pencil-line" class="text-xl" data-element="show-ui-icon" />
</button>
</span>
<.toggle_source_button />
<.convert_smart_cell_button cell_id={@cell_view.id} />
<.cell_link_button cell_id={@cell_view.id} />
<.move_cell_up_button cell_id={@cell_view.id} />
<.move_cell_down_button cell_id={@cell_view.id} />
@ -247,6 +232,61 @@ defmodule LivebookWeb.SessionLive.CellComponent do
"""
end
defp enable_insert_mode_button(assigns) do
~H"""
<span class="tooltip top" data-tooltip="Edit content" data-element="enable-insert-mode-button">
<button class="icon-button" aria-label="edit content">
<.remix_icon icon="pencil-line" class="text-xl" />
</button>
</span>
"""
end
defp insert_image_button(assigns) do
~H"""
<span class="tooltip top" data-tooltip="Insert image" data-element="insert-image-button">
<%= live_patch to: Routes.session_path(@socket, :cell_upload, @session_id, @cell_id),
class: "icon-button",
aria_label: "insert image",
role: "button" do %>
<.remix_icon icon="image-add-line" class="text-xl" />
<% end %>
</span>
"""
end
defp toggle_source_button(assigns) do
~H"""
<span class="tooltip top" data-tooltip="Toggle source" data-element="toggle-source-button">
<button class="icon-button" aria-label="toggle source">
<.remix_icon icon="code-line" class="text-xl" data-element="show-code-icon" />
<.remix_icon icon="pencil-line" class="text-xl" data-element="show-ui-icon" />
</button>
</span>
"""
end
defp convert_smart_cell_button(assigns) do
~H"""
<span class="tooltip top" data-tooltip="Convert to Elixir cell">
<button class="icon-button"
aria-label="toggle source"
phx-click={
with_confirm(
JS.push("convert_smart_cell", value: %{cell_id: @cell_id}),
title: "Convert cell",
description: "Once you convert this Smart cell to a Code cell, the Smart cell will be moved to the bin.",
confirm_text: "Convert",
confirm_icon: "arrow-up-down-line",
opt_out_id: "convert-smart-cell"
)
}>
<.remix_icon icon="arrow-up-down-line" class="text-xl" />
</button>
</span>
"""
end
defp cell_link_button(assigns) do
~H"""
<span class="tooltip top" data-tooltip="Link">

View file

@ -99,6 +99,30 @@ defmodule Livebook.SessionTest do
end
end
describe "convert_smart_cell/2" do
test "sends a delete and insert opreations to subscribers" do
smart_cell = %{Notebook.Cell.new(:smart) | kind: "text", source: "content"}
section = %{Notebook.Section.new() | cells: [smart_cell]}
notebook = %{Notebook.new() | sections: [section]}
session = start_session(notebook: notebook)
Phoenix.PubSub.subscribe(Livebook.PubSub, "sessions:#{session.id}")
pid = self()
Session.convert_smart_cell(session.pid, smart_cell.id)
cell_id = smart_cell.id
section_id = section.id
assert_receive {:operation, {:delete_cell, ^pid, ^cell_id}}
assert_receive {:operation,
{:insert_cell, ^pid, ^section_id, 0, :elixir, _id,
%{source: "content", outputs: []}}}
end
end
describe "queue_cell_evaluation/2" do
test "triggers evaluation and sends update operation once it finishes",
%{session: session} do

View file

@ -3,7 +3,7 @@ defmodule LivebookWeb.SessionLiveTest do
import Phoenix.LiveViewTest
alias Livebook.{Sessions, Session, Delta, Runtime, Users, FileSystem}
alias Livebook.{Sessions, Session, Runtime, Users, FileSystem}
alias Livebook.Notebook.Cell
alias Livebook.Users.User
@ -892,18 +892,8 @@ defmodule LivebookWeb.SessionLiveTest do
end
defp insert_text_cell(session_pid, section_id, type, content \\ "") do
Session.insert_cell(session_pid, section_id, 0, type)
Session.insert_cell(session_pid, section_id, 0, type, %{source: content})
%{notebook: %{sections: [%{cells: [cell]}]}} = Session.get_data(session_pid)
# We need to register ourselves as a client to start submitting cell deltas
user = Livebook.Users.User.new()
Session.register_client(session_pid, self(), user)
delta = Delta.new(insert: content)
Session.apply_cell_delta(session_pid, cell.id, delta, 1)
wait_for_session_update(session_pid)
cell.id
end