mirror of
https://github.com/livebook-dev/livebook.git
synced 2024-12-31 04:02:10 +08:00
Optimise rendering and diff by stripping data into view-specific struct (#119)
* Optimise rendering and diff by stripping data into view-specific struct * Move data to socket.private
This commit is contained in:
parent
de83020d05
commit
143cd5d80f
4 changed files with 127 additions and 99 deletions
|
@ -5,16 +5,16 @@ defmodule LivebookWeb.CellComponent do
|
||||||
~L"""
|
~L"""
|
||||||
<div class="flex flex-col relative"
|
<div class="flex flex-col relative"
|
||||||
data-element="cell"
|
data-element="cell"
|
||||||
id="cell-<%= @cell.id %>"
|
id="cell-<%= @cell_view.id %>"
|
||||||
phx-hook="Cell"
|
phx-hook="Cell"
|
||||||
data-cell-id="<%= @cell.id %>"
|
data-cell-id="<%= @cell_view.id %>"
|
||||||
data-type="<%= @cell.type %>">
|
data-type="<%= @cell_view.type %>">
|
||||||
<%= render_cell_content(assigns) %>
|
<%= render_cell_content(assigns) %>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_cell_content(%{cell: %{type: :markdown}} = assigns) do
|
def render_cell_content(%{cell_view: %{type: :markdown}} = assigns) do
|
||||||
~L"""
|
~L"""
|
||||||
<div class="mb-1 flex items-center justify-end">
|
<div class="mb-1 flex items-center justify-end">
|
||||||
<div class="relative z-10 flex items-center justify-end space-x-2" data-element="actions">
|
<div class="relative z-10 flex items-center justify-end space-x-2" data-element="actions">
|
||||||
|
@ -26,7 +26,7 @@ defmodule LivebookWeb.CellComponent do
|
||||||
<span class="tooltip top" aria-label="Move up">
|
<span class="tooltip top" aria-label="Move up">
|
||||||
<button class="icon-button"
|
<button class="icon-button"
|
||||||
phx-click="move_cell"
|
phx-click="move_cell"
|
||||||
phx-value-cell_id="<%= @cell.id %>"
|
phx-value-cell_id="<%= @cell_view.id %>"
|
||||||
phx-value-offset="-1">
|
phx-value-offset="-1">
|
||||||
<%= remix_icon("arrow-up-s-line", class: "text-xl") %>
|
<%= remix_icon("arrow-up-s-line", class: "text-xl") %>
|
||||||
</button>
|
</button>
|
||||||
|
@ -34,7 +34,7 @@ defmodule LivebookWeb.CellComponent do
|
||||||
<span class="tooltip top" aria-label="Move down">
|
<span class="tooltip top" aria-label="Move down">
|
||||||
<button class="icon-button"
|
<button class="icon-button"
|
||||||
phx-click="move_cell"
|
phx-click="move_cell"
|
||||||
phx-value-cell_id="<%= @cell.id %>"
|
phx-value-cell_id="<%= @cell_view.id %>"
|
||||||
phx-value-offset="1">
|
phx-value-offset="1">
|
||||||
<%= remix_icon("arrow-down-s-line", class: "text-xl") %>
|
<%= remix_icon("arrow-down-s-line", class: "text-xl") %>
|
||||||
</button>
|
</button>
|
||||||
|
@ -42,7 +42,7 @@ defmodule LivebookWeb.CellComponent do
|
||||||
<span class="tooltip top" aria-label="Delete">
|
<span class="tooltip top" aria-label="Delete">
|
||||||
<button class="icon-button"
|
<button class="icon-button"
|
||||||
phx-click="delete_cell"
|
phx-click="delete_cell"
|
||||||
phx-value-cell_id="<%= @cell.id %>">
|
phx-value-cell_id="<%= @cell_view.id %>">
|
||||||
<%= remix_icon("delete-bin-6-line", class: "text-xl") %>
|
<%= remix_icon("delete-bin-6-line", class: "text-xl") %>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
@ -57,31 +57,31 @@ defmodule LivebookWeb.CellComponent do
|
||||||
<%= render_editor(assigns) %>
|
<%= render_editor(assigns) %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="markdown" data-element="markdown-container" id="markdown-container-<%= @cell.id %>" phx-update="ignore">
|
<div class="markdown" data-element="markdown-container" id="markdown-container-<%= @cell_view.id %>" phx-update="ignore">
|
||||||
<%= render_markdown_content_placeholder(@cell.source) %>
|
<%= render_markdown_content_placeholder(empty: @cell_view.empty?) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_cell_content(%{cell: %{type: :elixir}} = assigns) do
|
def render_cell_content(%{cell_view: %{type: :elixir}} = assigns) do
|
||||||
~L"""
|
~L"""
|
||||||
<div class="mb-1 flex justify-between">
|
<div class="mb-1 flex justify-between">
|
||||||
<div class="relative z-10 flex items-center justify-end space-x-2" data-element="actions" data-primary>
|
<div class="relative z-10 flex items-center justify-end space-x-2" data-element="actions" data-primary>
|
||||||
<%= if @cell_info.evaluation_status == :ready do %>
|
<%= if @cell_view.evaluation_status == :ready do %>
|
||||||
<button class="text-gray-600 hover:text-gray-800 focus:text-gray-800 flex space-x-1 items-center"
|
<button class="text-gray-600 hover:text-gray-800 focus:text-gray-800 flex space-x-1 items-center"
|
||||||
phx-click="queue_cell_evaluation"
|
phx-click="queue_cell_evaluation"
|
||||||
phx-value-cell_id="<%= @cell.id %>">
|
phx-value-cell_id="<%= @cell_view.id %>">
|
||||||
<%= remix_icon("play-circle-fill", class: "text-xl") %>
|
<%= remix_icon("play-circle-fill", class: "text-xl") %>
|
||||||
<span class="text-sm font-medium">
|
<span class="text-sm font-medium">
|
||||||
<%= if(@cell_info.validity_status == :evaluated, do: "Reevaluate", else: "Evaluate") %>
|
<%= if(@cell_view.validity_status == :evaluated, do: "Reevaluate", else: "Evaluate") %>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<% else %>
|
<% else %>
|
||||||
<button class="text-gray-600 hover:text-gray-800 focus:text-gray-800 flex space-x-1 items-center"
|
<button class="text-gray-600 hover:text-gray-800 focus:text-gray-800 flex space-x-1 items-center"
|
||||||
phx-click="cancel_cell_evaluation"
|
phx-click="cancel_cell_evaluation"
|
||||||
phx-value-cell_id="<%= @cell.id %>">
|
phx-value-cell_id="<%= @cell_view.id %>">
|
||||||
<%= remix_icon("stop-circle-fill", class: "text-xl") %>
|
<%= remix_icon("stop-circle-fill", class: "text-xl") %>
|
||||||
<span class="text-sm font-medium">
|
<span class="text-sm font-medium">
|
||||||
Stop
|
Stop
|
||||||
|
@ -91,14 +91,14 @@ defmodule LivebookWeb.CellComponent do
|
||||||
</div>
|
</div>
|
||||||
<div class="relative z-10 flex items-center justify-end space-x-2" data-element="actions">
|
<div class="relative z-10 flex items-center justify-end space-x-2" data-element="actions">
|
||||||
<span class="tooltip top" aria-label="Cell settings">
|
<span class="tooltip top" aria-label="Cell settings">
|
||||||
<%= live_patch to: Routes.session_path(@socket, :cell_settings, @session_id, @cell.id), class: "icon-button" do %>
|
<%= live_patch to: Routes.session_path(@socket, :cell_settings, @session_id, @cell_view.id), class: "icon-button" do %>
|
||||||
<%= remix_icon("list-settings-line", class: "text-xl") %>
|
<%= remix_icon("list-settings-line", class: "text-xl") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
<span class="tooltip top" aria-label="Move up">
|
<span class="tooltip top" aria-label="Move up">
|
||||||
<button class="icon-button"
|
<button class="icon-button"
|
||||||
phx-click="move_cell"
|
phx-click="move_cell"
|
||||||
phx-value-cell_id="<%= @cell.id %>"
|
phx-value-cell_id="<%= @cell_view.id %>"
|
||||||
phx-value-offset="-1">
|
phx-value-offset="-1">
|
||||||
<%= remix_icon("arrow-up-s-line", class: "text-xl") %>
|
<%= remix_icon("arrow-up-s-line", class: "text-xl") %>
|
||||||
</button>
|
</button>
|
||||||
|
@ -106,7 +106,7 @@ defmodule LivebookWeb.CellComponent do
|
||||||
<span class="tooltip top" aria-label="Move down">
|
<span class="tooltip top" aria-label="Move down">
|
||||||
<button class="icon-button"
|
<button class="icon-button"
|
||||||
phx-click="move_cell"
|
phx-click="move_cell"
|
||||||
phx-value-cell_id="<%= @cell.id %>"
|
phx-value-cell_id="<%= @cell_view.id %>"
|
||||||
phx-value-offset="1">
|
phx-value-offset="1">
|
||||||
<%= remix_icon("arrow-down-s-line", class: "text-xl") %>
|
<%= remix_icon("arrow-down-s-line", class: "text-xl") %>
|
||||||
</button>
|
</button>
|
||||||
|
@ -114,7 +114,7 @@ defmodule LivebookWeb.CellComponent do
|
||||||
<span class="tooltip top" aria-label="Delete">
|
<span class="tooltip top" aria-label="Delete">
|
||||||
<button class="icon-button"
|
<button class="icon-button"
|
||||||
phx-click="delete_cell"
|
phx-click="delete_cell"
|
||||||
phx-value-cell_id="<%= @cell.id %>">
|
phx-value-cell_id="<%= @cell_view.id %>">
|
||||||
<%= remix_icon("delete-bin-6-line", class: "text-xl") %>
|
<%= remix_icon("delete-bin-6-line", class: "text-xl") %>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
@ -127,7 +127,7 @@ defmodule LivebookWeb.CellComponent do
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<%= render_editor(assigns) %>
|
<%= render_editor(assigns) %>
|
||||||
|
|
||||||
<%= if @cell.outputs != [] do %>
|
<%= if @cell_view.outputs != [] do %>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<%= render_outputs(assigns) %>
|
<%= render_outputs(assigns) %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -141,19 +141,15 @@ defmodule LivebookWeb.CellComponent do
|
||||||
~L"""
|
~L"""
|
||||||
<div class="py-3 rounded-lg overflow-hidden bg-editor relative">
|
<div class="py-3 rounded-lg overflow-hidden bg-editor relative">
|
||||||
<div
|
<div
|
||||||
id="editor-container-<%= @cell.id %>"
|
id="editor-container-<%= @cell_view.id %>"
|
||||||
data-element="editor-container"
|
data-element="editor-container"
|
||||||
phx-update="ignore">
|
phx-update="ignore">
|
||||||
<%= render_editor_content_placeholder(@cell.source) %>
|
<%= render_editor_content_placeholder(empty: @cell_view.empty?) %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= if @cell.type == :elixir do %>
|
<%= if @cell_view.type == :elixir do %>
|
||||||
<div class="absolute bottom-2 right-2">
|
<div class="absolute bottom-2 right-2">
|
||||||
<%= render_cell_status(
|
<%= render_cell_status(@cell_view.validity_status, @cell_view.evaluation_status, @cell_view.changed?) %>
|
||||||
@cell_info.validity_status,
|
|
||||||
@cell_info.evaluation_status,
|
|
||||||
@cell_info.digest != @cell_info.evaluation_digest
|
|
||||||
) %>
|
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -164,7 +160,7 @@ defmodule LivebookWeb.CellComponent do
|
||||||
# There may be a tiny delay before the markdown is rendered
|
# There may be a tiny delay before the markdown is rendered
|
||||||
# or and editors are mounted, so show neat placeholders immediately.
|
# or and editors are mounted, so show neat placeholders immediately.
|
||||||
|
|
||||||
defp render_markdown_content_placeholder("" = _content) do
|
defp render_markdown_content_placeholder(empty: true) do
|
||||||
assigns = %{}
|
assigns = %{}
|
||||||
|
|
||||||
~L"""
|
~L"""
|
||||||
|
@ -172,7 +168,7 @@ defmodule LivebookWeb.CellComponent do
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
defp render_markdown_content_placeholder(_content) do
|
defp render_markdown_content_placeholder(empty: false) do
|
||||||
assigns = %{}
|
assigns = %{}
|
||||||
|
|
||||||
~L"""
|
~L"""
|
||||||
|
@ -186,7 +182,7 @@ defmodule LivebookWeb.CellComponent do
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
defp render_editor_content_placeholder("" = _content) do
|
defp render_editor_content_placeholder(empty: true) do
|
||||||
assigns = %{}
|
assigns = %{}
|
||||||
|
|
||||||
~L"""
|
~L"""
|
||||||
|
@ -194,7 +190,7 @@ defmodule LivebookWeb.CellComponent do
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
defp render_editor_content_placeholder(_content) do
|
defp render_editor_content_placeholder(empty: false) do
|
||||||
assigns = %{}
|
assigns = %{}
|
||||||
|
|
||||||
~L"""
|
~L"""
|
||||||
|
@ -211,9 +207,9 @@ defmodule LivebookWeb.CellComponent do
|
||||||
defp render_outputs(assigns) do
|
defp render_outputs(assigns) do
|
||||||
~L"""
|
~L"""
|
||||||
<div class="flex flex-col rounded-lg border border-gray-200 divide-y divide-gray-200 font-editor">
|
<div class="flex flex-col rounded-lg border border-gray-200 divide-y divide-gray-200 font-editor">
|
||||||
<%= for {output, index} <- @cell.outputs |> Enum.reverse() |> Enum.with_index(), output != :ignored do %>
|
<%= for {output, index} <- @cell_view.outputs |> Enum.reverse() |> Enum.with_index(), output != :ignored do %>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<%= render_output(output, "#{@cell.id}-output#{index}") %>
|
<%= render_output(output, "#{@cell_view.id}-output#{index}") %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -274,12 +270,7 @@ defmodule LivebookWeb.CellComponent do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp render_cell_status(_, :evaluating, changed) do
|
defp render_cell_status(_, :evaluating, changed) do
|
||||||
render_status_indicator(
|
render_status_indicator("Evaluating", "bg-blue-500", "bg-blue-400", changed)
|
||||||
"Evaluating",
|
|
||||||
"bg-blue-500",
|
|
||||||
"bg-blue-400",
|
|
||||||
changed
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp render_cell_status(_, :queued, _) do
|
defp render_cell_status(_, :queued, _) do
|
||||||
|
@ -287,12 +278,7 @@ defmodule LivebookWeb.CellComponent do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp render_cell_status(:evaluated, _, changed) do
|
defp render_cell_status(:evaluated, _, changed) do
|
||||||
render_status_indicator(
|
render_status_indicator("Evaluated", "bg-green-400", nil, changed)
|
||||||
"Evaluated",
|
|
||||||
"bg-green-400",
|
|
||||||
nil,
|
|
||||||
changed
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp render_cell_status(:stale, _, changed) do
|
defp render_cell_status(:stale, _, changed) do
|
||||||
|
|
|
@ -3,22 +3,22 @@ defmodule LivebookWeb.SectionComponent do
|
||||||
|
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~L"""
|
~L"""
|
||||||
<div data-element="section" data-section-id="<%= @section.id %>">
|
<div data-element="section" data-section-id="<%= @section_view.id %>">
|
||||||
<div class="flex space-x-4 items-center" data-element="section-headline">
|
<div class="flex space-x-4 items-center" data-element="section-headline">
|
||||||
<h2 class="flex-grow text-gray-800 font-semibold text-2xl px-1 -ml-1 rounded-lg border border-transparent hover:border-blue-200 focus:border-blue-300"
|
<h2 class="flex-grow text-gray-800 font-semibold text-2xl px-1 -ml-1 rounded-lg border border-transparent hover:border-blue-200 focus:border-blue-300"
|
||||||
data-element="section-name"
|
data-element="section-name"
|
||||||
id="section-<%= @section.id %>-name"
|
id="section-<%= @section_view.id %>-name"
|
||||||
contenteditable
|
contenteditable
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
phx-blur="set_section_name"
|
phx-blur="set_section_name"
|
||||||
phx-value-section_id="<%= @section.id %>"
|
phx-value-section_id="<%= @section_view.id %>"
|
||||||
phx-hook="ContentEditable"
|
phx-hook="ContentEditable"
|
||||||
data-update-attribute="phx-value-name"><%= @section.name %></h2>
|
data-update-attribute="phx-value-name"><%= @section_view.name %></h2>
|
||||||
<%# ^ Note it's important there's no space between <h2> and </h2>
|
<%# ^ Note it's important there's no space between <h2> and </h2>
|
||||||
because we want the content to exactly match @section.name. %>
|
because we want the content to exactly match section name. %>
|
||||||
<div class="flex space-x-2 items-center" data-element="section-actions">
|
<div class="flex space-x-2 items-center" data-element="section-actions">
|
||||||
<span class="tooltip top" aria-label="Delete">
|
<span class="tooltip top" aria-label="Delete">
|
||||||
<button class="icon-button" phx-click="delete_section" phx-value-section_id="<%= @section.id %>" tabindex="-1">
|
<button class="icon-button" phx-click="delete_section" phx-value-section_id="<%= @section_view.id %>" tabindex="-1">
|
||||||
<%= remix_icon("delete-bin-6-line", class: "text-xl") %>
|
<%= remix_icon("delete-bin-6-line", class: "text-xl") %>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
@ -26,24 +26,23 @@ defmodule LivebookWeb.SectionComponent do
|
||||||
</div>
|
</div>
|
||||||
<div class="container py-2">
|
<div class="container py-2">
|
||||||
<div class="flex flex-col space-y-1">
|
<div class="flex flex-col space-y-1">
|
||||||
<%= for {cell, index} <- Enum.with_index(@section.cells) do %>
|
<%= for {cell_view, index} <- Enum.with_index(@section_view.cell_views) do %>
|
||||||
<%= live_component @socket, LivebookWeb.InsertButtonsComponent,
|
<%= live_component @socket, LivebookWeb.InsertButtonsComponent,
|
||||||
id: "#{@section.id}:#{index}",
|
id: "#{@section_view.id}:#{index}",
|
||||||
persistent: false,
|
persistent: false,
|
||||||
section_id: @section.id,
|
section_id: @section_view.id,
|
||||||
insert_cell_index: index,
|
insert_cell_index: index,
|
||||||
insert_section_index: nil %>
|
insert_section_index: nil %>
|
||||||
<%= live_component @socket, LivebookWeb.CellComponent,
|
<%= live_component @socket, LivebookWeb.CellComponent,
|
||||||
id: cell.id,
|
id: cell_view.id,
|
||||||
session_id: @session_id,
|
session_id: @session_id,
|
||||||
cell: cell,
|
cell_view: cell_view %>
|
||||||
cell_info: @cell_infos[cell.id] %>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= live_component @socket, LivebookWeb.InsertButtonsComponent,
|
<%= live_component @socket, LivebookWeb.InsertButtonsComponent,
|
||||||
id: "#{@section.id}:last",
|
id: "#{@section_view.id}:last",
|
||||||
persistent: @section.cells == [],
|
persistent: @section_view.cell_views == [],
|
||||||
section_id: @section.id,
|
section_id: @section_view.id,
|
||||||
insert_cell_index: length(@section.cells),
|
insert_cell_index: length(@section_view.cell_views),
|
||||||
insert_section_index: @index + 1 %>
|
insert_section_index: @index + 1 %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,12 +18,23 @@ defmodule LivebookWeb.SessionLive do
|
||||||
|
|
||||||
platform = platform_from_socket(socket)
|
platform = platform_from_socket(socket)
|
||||||
|
|
||||||
{:ok, assign(socket, initial_assigns(session_id, data, platform))}
|
{:ok,
|
||||||
|
socket
|
||||||
|
|> assign(platform: platform, session_id: session_id, data_view: data_to_view(data))
|
||||||
|
|> assign_private(data: data)}
|
||||||
else
|
else
|
||||||
{:ok, redirect(socket, to: Routes.home_path(socket, :page))}
|
{:ok, redirect(socket, to: Routes.home_path(socket, :page))}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Puts the given assigns in `socket.private`,
|
||||||
|
# to ensure they are not used for rendering.
|
||||||
|
defp assign_private(socket, assigns) do
|
||||||
|
Enum.reduce(assigns, socket, fn {key, value}, socket ->
|
||||||
|
put_in(socket.private[key], value)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
defp platform_from_socket(socket) do
|
defp platform_from_socket(socket) do
|
||||||
with connect_info when connect_info != nil <- get_connect_info(socket),
|
with connect_info when connect_info != nil <- get_connect_info(socket),
|
||||||
{:ok, user_agent} <- Map.fetch(connect_info, :user_agent) do
|
{:ok, user_agent} <- Map.fetch(connect_info, :user_agent) do
|
||||||
|
@ -33,14 +44,6 @@ defmodule LivebookWeb.SessionLive do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp initial_assigns(session_id, data, platform) do
|
|
||||||
%{
|
|
||||||
platform: platform,
|
|
||||||
session_id: session_id,
|
|
||||||
data: data
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~L"""
|
~L"""
|
||||||
|
@ -50,7 +53,7 @@ defmodule LivebookWeb.SessionLive do
|
||||||
return_to: Routes.session_path(@socket, :page, @session_id),
|
return_to: Routes.session_path(@socket, :page, @session_id),
|
||||||
tab: @tab,
|
tab: @tab,
|
||||||
session_id: @session_id,
|
session_id: @session_id,
|
||||||
data: @data %>
|
data_view: @data_view %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if @live_action == :shortcuts do %>
|
<%= if @live_action == :shortcuts do %>
|
||||||
|
@ -101,11 +104,11 @@ defmodule LivebookWeb.SessionLive do
|
||||||
Sections
|
Sections
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-4 flex flex-col space-y-4" data-element="section-list">
|
<div class="mt-4 flex flex-col space-y-4" data-element="section-list">
|
||||||
<%= for section <- @data.notebook.sections do %>
|
<%= for section_item <- @data_view.sections_items do %>
|
||||||
<button class="text-left hover:text-gray-900 text-gray-500"
|
<button class="text-left hover:text-gray-900 text-gray-500"
|
||||||
data-element="section-list-item"
|
data-element="section-list-item"
|
||||||
data-section-id="<%= section.id %>">
|
data-section-id="<%= section_item.id %>">
|
||||||
<%= section.name %>
|
<%= section_item.name %>
|
||||||
</button>
|
</button>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -125,10 +128,10 @@ defmodule LivebookWeb.SessionLive do
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
phx-blur="set_notebook_name"
|
phx-blur="set_notebook_name"
|
||||||
phx-hook="ContentEditable"
|
phx-hook="ContentEditable"
|
||||||
data-update-attribute="phx-value-name"><%= @data.notebook.name %></h1>
|
data-update-attribute="phx-value-name"><%= @data_view.notebook_name %></h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col w-full space-y-16">
|
<div class="flex flex-col w-full space-y-16">
|
||||||
<%= if @data.notebook.sections == [] do %>
|
<%= if @data_view.section_views == [] do %>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<button class="button button-small"
|
<button class="button button-small"
|
||||||
phx-click="insert_section"
|
phx-click="insert_section"
|
||||||
|
@ -136,13 +139,12 @@ defmodule LivebookWeb.SessionLive do
|
||||||
>+ Section</button>
|
>+ Section</button>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= for {section, index} <- Enum.with_index(@data.notebook.sections) do %>
|
<%= for {section_view, index} <- Enum.with_index(@data_view.section_views) do %>
|
||||||
<%= live_component @socket, LivebookWeb.SectionComponent,
|
<%= live_component @socket, LivebookWeb.SectionComponent,
|
||||||
id: section.id,
|
id: section_view.id,
|
||||||
index: index,
|
index: index,
|
||||||
session_id: @session_id,
|
session_id: @session_id,
|
||||||
section: section,
|
section_view: section_view %>
|
||||||
cell_infos: @data.cell_infos %>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
<div style="height: 80vh"></div>
|
<div style="height: 80vh"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -158,7 +160,7 @@ defmodule LivebookWeb.SessionLive do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_params(%{"cell_id" => cell_id}, _url, socket) do
|
def handle_params(%{"cell_id" => cell_id}, _url, socket) do
|
||||||
{:ok, cell, _} = Notebook.fetch_cell_and_section(socket.assigns.data.notebook, cell_id)
|
{:ok, cell, _} = Notebook.fetch_cell_and_section(socket.private.data.notebook, cell_id)
|
||||||
{:noreply, assign(socket, cell: cell)}
|
{:noreply, assign(socket, cell: cell)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -172,7 +174,7 @@ defmodule LivebookWeb.SessionLive do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("cell_init", %{"cell_id" => cell_id}, socket) do
|
def handle_event("cell_init", %{"cell_id" => cell_id}, socket) do
|
||||||
data = socket.assigns.data
|
data = socket.private.data
|
||||||
|
|
||||||
case Notebook.fetch_cell_and_section(data.notebook, cell_id) do
|
case Notebook.fetch_cell_and_section(data.notebook, cell_id) do
|
||||||
{:ok, cell, _section} ->
|
{:ok, cell, _section} ->
|
||||||
|
@ -189,7 +191,7 @@ defmodule LivebookWeb.SessionLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("add_section", _params, socket) do
|
def handle_event("add_section", _params, socket) do
|
||||||
end_index = length(socket.assigns.data.notebook.sections)
|
end_index = length(socket.private.data.notebook.sections)
|
||||||
Session.insert_section(socket.assigns.session_id, end_index)
|
Session.insert_section(socket.assigns.session_id, end_index)
|
||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
|
@ -222,14 +224,14 @@ defmodule LivebookWeb.SessionLive do
|
||||||
|
|
||||||
def handle_event("insert_cell_below", %{"cell_id" => cell_id, "type" => type}, socket) do
|
def handle_event("insert_cell_below", %{"cell_id" => cell_id, "type" => type}, socket) do
|
||||||
type = String.to_atom(type)
|
type = String.to_atom(type)
|
||||||
insert_cell_next_to(socket.assigns, cell_id, type, idx_offset: 1)
|
insert_cell_next_to(socket, cell_id, type, idx_offset: 1)
|
||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("insert_cell_above", %{"cell_id" => cell_id, "type" => type}, socket) do
|
def handle_event("insert_cell_above", %{"cell_id" => cell_id, "type" => type}, socket) do
|
||||||
type = String.to_atom(type)
|
type = String.to_atom(type)
|
||||||
insert_cell_next_to(socket.assigns, cell_id, type, idx_offset: 0)
|
insert_cell_next_to(socket, cell_id, type, idx_offset: 0)
|
||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
@ -288,7 +290,7 @@ defmodule LivebookWeb.SessionLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("queue_section_cells_evaluation", %{"section_id" => section_id}, socket) do
|
def handle_event("queue_section_cells_evaluation", %{"section_id" => section_id}, socket) do
|
||||||
with {:ok, section} <- Notebook.fetch_section(socket.assigns.data.notebook, section_id) do
|
with {:ok, section} <- Notebook.fetch_section(socket.private.data.notebook, section_id) do
|
||||||
for cell <- section.cells, cell.type == :elixir do
|
for cell <- section.cells, cell.type == :elixir do
|
||||||
Session.queue_cell_evaluation(socket.assigns.session_id, cell.id)
|
Session.queue_cell_evaluation(socket.assigns.session_id, cell.id)
|
||||||
end
|
end
|
||||||
|
@ -298,7 +300,7 @@ defmodule LivebookWeb.SessionLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("queue_all_cells_evaluation", _params, socket) do
|
def handle_event("queue_all_cells_evaluation", _params, socket) do
|
||||||
data = socket.assigns.data
|
data = socket.private.data
|
||||||
|
|
||||||
for {cell, _} <- Notebook.elixir_cells_with_section(data.notebook),
|
for {cell, _} <- Notebook.elixir_cells_with_section(data.notebook),
|
||||||
data.cell_infos[cell.id].validity_status != :evaluated do
|
data.cell_infos[cell.id].validity_status != :evaluated do
|
||||||
|
@ -310,8 +312,8 @@ defmodule LivebookWeb.SessionLive do
|
||||||
|
|
||||||
def handle_event("queue_child_cells_evaluation", %{"cell_id" => cell_id}, socket) do
|
def handle_event("queue_child_cells_evaluation", %{"cell_id" => cell_id}, socket) do
|
||||||
with {:ok, cell, _section} <-
|
with {:ok, cell, _section} <-
|
||||||
Notebook.fetch_cell_and_section(socket.assigns.data.notebook, cell_id) do
|
Notebook.fetch_cell_and_section(socket.private.data.notebook, cell_id) do
|
||||||
for {cell, _} <- Notebook.child_cells_with_section(socket.assigns.data.notebook, cell.id) do
|
for {cell, _} <- Notebook.child_cells_with_section(socket.private.data.notebook, cell.id) do
|
||||||
Session.queue_cell_evaluation(socket.assigns.session_id, cell.id)
|
Session.queue_cell_evaluation(socket.assigns.session_id, cell.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -339,11 +341,12 @@ defmodule LivebookWeb.SessionLive do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info({:operation, operation}, socket) do
|
def handle_info({:operation, operation}, socket) do
|
||||||
case Session.Data.apply_operation(socket.assigns.data, operation) do
|
case Session.Data.apply_operation(socket.private.data, operation) do
|
||||||
{:ok, data, actions} ->
|
{:ok, data, actions} ->
|
||||||
new_socket =
|
new_socket =
|
||||||
socket
|
socket
|
||||||
|> assign(data: data)
|
|> assign_private(data: data)
|
||||||
|
|> assign(data_view: data_to_view(data))
|
||||||
|> after_operation(socket, operation)
|
|> after_operation(socket, operation)
|
||||||
|> handle_actions(actions)
|
|> handle_actions(actions)
|
||||||
|
|
||||||
|
@ -398,12 +401,12 @@ defmodule LivebookWeb.SessionLive do
|
||||||
defp after_operation(socket, prev_socket, {:delete_cell, _client_pid, cell_id}) do
|
defp after_operation(socket, prev_socket, {:delete_cell, _client_pid, cell_id}) do
|
||||||
# Find a sibling cell that the client would focus if the deleted cell has focus.
|
# Find a sibling cell that the client would focus if the deleted cell has focus.
|
||||||
sibling_cell_id =
|
sibling_cell_id =
|
||||||
case Notebook.fetch_cell_sibling(prev_socket.assigns.data.notebook, cell_id, 1) do
|
case Notebook.fetch_cell_sibling(prev_socket.private.data.notebook, cell_id, 1) do
|
||||||
{:ok, next_cell} ->
|
{:ok, next_cell} ->
|
||||||
next_cell.id
|
next_cell.id
|
||||||
|
|
||||||
:error ->
|
:error ->
|
||||||
case Notebook.fetch_cell_sibling(prev_socket.assigns.data.notebook, cell_id, -1) do
|
case Notebook.fetch_cell_sibling(prev_socket.private.data.notebook, cell_id, -1) do
|
||||||
{:ok, previous_cell} -> previous_cell.id
|
{:ok, previous_cell} -> previous_cell.id
|
||||||
:error -> nil
|
:error -> nil
|
||||||
end
|
end
|
||||||
|
@ -446,12 +449,52 @@ defmodule LivebookWeb.SessionLive do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp insert_cell_next_to(assigns, cell_id, type, idx_offset: idx_offset) do
|
defp insert_cell_next_to(socket, cell_id, type, idx_offset: idx_offset) do
|
||||||
{:ok, cell, section} = Notebook.fetch_cell_and_section(assigns.data.notebook, cell_id)
|
{:ok, cell, section} = Notebook.fetch_cell_and_section(socket.private.data.notebook, cell_id)
|
||||||
index = Enum.find_index(section.cells, &(&1 == cell))
|
index = Enum.find_index(section.cells, &(&1 == cell))
|
||||||
Session.insert_cell(assigns.session_id, section.id, index + idx_offset, type)
|
Session.insert_cell(socket.assigns.session_id, section.id, index + idx_offset, type)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp ensure_integer(n) when is_integer(n), do: n
|
defp ensure_integer(n) when is_integer(n), do: n
|
||||||
defp ensure_integer(n) when is_binary(n), do: String.to_integer(n)
|
defp ensure_integer(n) when is_binary(n), do: String.to_integer(n)
|
||||||
|
|
||||||
|
# Builds view-specific structure of data by cherry-picking
|
||||||
|
# only the relevant attributes.
|
||||||
|
# We then use `@data_view` in the templates and consequently
|
||||||
|
# irrelevant changes to data don't change `@data_view`, so LV doesn't
|
||||||
|
# have to traverse the whole template tree and no diff is sent to the client.
|
||||||
|
defp data_to_view(data) do
|
||||||
|
%{
|
||||||
|
path: data.path,
|
||||||
|
runtime: data.runtime,
|
||||||
|
notebook_name: data.notebook.name,
|
||||||
|
sections_items:
|
||||||
|
for section <- data.notebook.sections do
|
||||||
|
%{id: section.id, name: section.name}
|
||||||
|
end,
|
||||||
|
section_views: Enum.map(data.notebook.sections, §ion_to_view(&1, data))
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp section_to_view(section, data) do
|
||||||
|
%{
|
||||||
|
id: section.id,
|
||||||
|
name: section.name,
|
||||||
|
cell_views: Enum.map(section.cells, &cell_to_view(&1, data))
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp cell_to_view(cell, data) do
|
||||||
|
info = data.cell_infos[cell.id]
|
||||||
|
|
||||||
|
%{
|
||||||
|
id: cell.id,
|
||||||
|
type: cell.type,
|
||||||
|
empty?: cell.source == "",
|
||||||
|
outputs: cell.outputs,
|
||||||
|
validity_status: info.validity_status,
|
||||||
|
evaluation_status: info.evaluation_status,
|
||||||
|
changed?: info.evaluation_digest != nil and info.digest != info.evaluation_digest
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,14 +31,14 @@ defmodule LivebookWeb.SessionLive.SettingsComponent do
|
||||||
<%= live_component @socket, LivebookWeb.SessionLive.PersistenceComponent,
|
<%= live_component @socket, LivebookWeb.SessionLive.PersistenceComponent,
|
||||||
id: :persistence,
|
id: :persistence,
|
||||||
session_id: @session_id,
|
session_id: @session_id,
|
||||||
current_path: @data.path,
|
current_path: @data_view.path,
|
||||||
path: @data.path %>
|
path: @data_view.path %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @tab == "runtime" do %>
|
<%= if @tab == "runtime" do %>
|
||||||
<%= live_component @socket, LivebookWeb.SessionLive.RuntimeComponent,
|
<%= live_component @socket, LivebookWeb.SessionLive.RuntimeComponent,
|
||||||
id: :runtime,
|
id: :runtime,
|
||||||
session_id: @session_id,
|
session_id: @session_id,
|
||||||
runtime: @data.runtime %>
|
runtime: @data_view.runtime %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue