defmodule LivebookWeb.SessionLive.CellComponent do use LivebookWeb, :live_component @impl true def mount(socket) do {:ok, assign(socket, initialized: false)} end @impl true def update(assigns, socket) do socket = assign(socket, assigns) socket = if not connected?(socket) or socket.assigns.initialized do socket else %{id: id, source_info: info} = socket.assigns.cell_view socket |> push_event("cell_init:#{id}", info) |> assign(initialized: true) end {:ok, socket} end @impl true def render(assigns) do ~H"""
<%= render_cell(assigns) %>
""" end defp render_cell(%{cell_view: %{type: :markdown}} = assigns) do ~H"""
<.cell_body>
<.editor cell_view={@cell_view} />
<.content_placeholder bg_class="bg-gray-200" empty={empty?(@cell_view.source_info)} />
""" end defp render_cell(%{cell_view: %{type: :elixir}} = assigns) do ~H"""
<%= if @cell_view.evaluation_status == :ready do %> <%= if @cell_view.validity_status != :fresh and @cell_view.reevaluate_automatically do %> <%= live_patch to: Routes.session_path(@socket, :cell_settings, @session_id, @cell_view.id), class: "text-gray-600 hover:text-gray-800 focus:text-gray-800 flex space-x-1 items-center" do %> <.remix_icon icon="check-line" class="text-xl" /> Reevaluates automatically <% end %> <% else %> <% end %> <% else %> <% end %>
<.cell_body> <.editor cell_view={@cell_view} />
""" end defp cell_body(assigns) do ~H"""
<%= render_slot(@inner_block) %>
""" end defp cell_link_button(assigns) do ~H""" <.remix_icon icon="link" class="text-xl" /> """ end defp cell_settings_button(assigns) do ~H""" <%= live_patch to: Routes.session_path(@socket, :cell_settings, @session_id, @cell_id), class: "icon-button", aria_label: "cell settings", role: "button" do %> <.remix_icon icon="settings-3-line" class="text-xl" /> <% end %> """ end defp move_cell_up_button(assigns) do ~H""" """ end defp move_cell_down_button(assigns) do ~H""" """ end defp delete_cell_button(assigns) do ~H""" """ end defp editor(assigns) do ~H"""
<.content_placeholder bg_class="bg-gray-500" empty={empty?(@cell_view.source_info)} />
<%= if @cell_view.type == :elixir do %>
<.cell_status cell_view={@cell_view} />
<% end %>
""" end # The whole page has to load and then hooks are mounted. # There may be a tiny delay before the markdown is rendered # or editors are mounted, so show neat placeholders immediately. defp content_placeholder(assigns) do ~H""" <%= if @empty do %>
<% else %>
<% end %> """ end defp empty?(%{source: ""} = _source_info), do: true defp empty?(_source_info), do: false defp cell_status(%{cell_view: %{evaluation_status: :evaluating}} = assigns) do ~H""" <.status_indicator circle_class="bg-blue-500" animated_circle_class="bg-blue-400" change_indicator={true}> """ end defp cell_status(%{cell_view: %{evaluation_status: :queued}} = assigns) do ~H""" <.status_indicator circle_class="bg-gray-400" animated_circle_class="bg-gray-300"> Queued """ end defp cell_status(%{cell_view: %{validity_status: :evaluated}} = assigns) do ~H""" <.status_indicator circle_class="bg-green-bright-400" change_indicator={true} tooltip={evaluated_label(@cell_view.evaluation_time_ms)}> Evaluated """ end defp cell_status(%{cell_view: %{validity_status: :stale}} = assigns) do ~H""" <.status_indicator circle_class="bg-yellow-bright-200" change_indicator={true}> Stale """ end defp cell_status(%{cell_view: %{validity_status: :aborted}} = assigns) do ~H""" <.status_indicator circle_class="bg-gray-500"> Aborted """ end defp cell_status(assigns), do: ~H"" defp status_indicator(assigns) do assigns = assigns |> assign_new(:animated_circle_class, fn -> nil end) |> assign_new(:change_indicator, fn -> false end) |> assign_new(:tooltip, fn -> nil end) ~H"""
<%= render_slot(@inner_block) %> <%= if @change_indicator do %> * <% end %>
<%= if @animated_circle_class do %> <% end %>
""" end defp evaluated_label(time_ms) when is_integer(time_ms) do evaluation_time = if time_ms > 100 do seconds = time_ms |> Kernel./(1000) |> Float.floor(1) "#{seconds}s" else "#{time_ms}ms" end "Took " <> evaluation_time end defp evaluated_label(_time_ms), do: nil end