defmodule LivebookWeb.SessionLive.CellComponent do use LivebookWeb, :live_component def render(assigns) do ~L"""
<%= render_cell_content(assigns) %>
""" end def render_cell_content(%{cell_view: %{type: :markdown}} = assigns) do ~L"""
<%= render_cell_anchor_link(assigns) %> <%= live_patch to: Routes.session_path(@socket, :cell_upload, @session_id, @cell_view.id), class: "icon-button" do %> <%= remix_icon("image-add-line", class: "text-xl") %> <% end %>
<%= render_editor(assigns) %>
<%= render_content_placeholder("bg-gray-200", @cell_view.empty?) %>
""" end def render_cell_content(%{cell_view: %{type: :elixir}} = assigns) do ~L"""
<%= if @cell_view.evaluation_status == :ready do %> <% else %> <% end %>
<%= render_cell_anchor_link(assigns) %> <%= 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") %> <% end %>
<%= render_editor(assigns) %> <%= if @cell_view.outputs != [] do %>
<%= render_outputs(assigns, @socket) %>
<% end %>
""" end def render_cell_content(%{cell_view: %{type: :input}} = assigns) do ~L"""
<%= render_cell_anchor_link(assigns) %> <%= 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") %> <% end %>
<%= @cell_view.name %>
" data-element="input" class="input <%= if(@cell_view.error, do: "input--error") %>" name="value" value="<%= @cell_view.value %>" spellcheck="false" autocomplete="off" tabindex="-1" /> <%= if @cell_view.error do %>
<%= String.capitalize(@cell_view.error) %>
<% end %>
""" end defp render_editor(assigns) do ~L"""
<%= render_content_placeholder("bg-gray-500", @cell_view.empty?) %>
<%= if @cell_view.type == :elixir do %>
<%= render_cell_status(@cell_view.validity_status, @cell_view.evaluation_status, @cell_view.evaluation_time_ms) %>
<% end %>
""" end defp render_cell_anchor_link(assigns) do ~L""" <%= remix_icon("link", class: "text-xl") %> """ end # The whole page has to load and then hooks are mounded. # There may be a tiny delay before the markdown is rendered # or editors are mounted, so show neat placeholders immediately. defp render_content_placeholder(_bg_class, true = _empty) do assigns = %{} ~L"""
""" end defp render_content_placeholder(bg_class, false = _empty) do assigns = %{bg_class: bg_class} ~L"""
""" end defp render_outputs(assigns, socket) do ~L"""
<%= for {output, index} <- @cell_view.outputs |> Enum.reverse() |> Enum.with_index(), output != :ignored do %>
<%= render_output(socket, output, "cell-#{@cell_view.id}-output#{index}") %>
<% end %>
""" end defp render_output(_socket, text, id) when is_binary(text) do text # Captured output usually has a trailing newline that we can ignore, # because each line is itself an HTML block anyway. |> String.replace_suffix("\n", "") |> render_virtualized_output(id, follow: true) end defp render_output(_socket, {:text, text}, id) do render_virtualized_output(text, id) end defp render_output(_socket, {:vega_lite_static, spec}, id) do live_component(LivebookWeb.Output.VegaLiteStaticComponent, id: id, spec: spec) end defp render_output(socket, {:vega_lite_dynamic, pid}, id) do live_render(socket, LivebookWeb.Output.VegaLiteDynamicLive, id: id, session: %{"id" => id, "pid" => pid} ) end defp render_output(socket, {:table_dynamic, pid}, id) do live_render(socket, LivebookWeb.Output.TableDynamicLive, id: id, session: %{"id" => id, "pid" => pid} ) end defp render_output(_socket, {:error, formatted}, _id) do render_error_message_output(formatted) end defp render_output(_socket, output, _id) do render_error_message_output(""" Unknown output format: #{inspect(output)}. If you're using Kino, you may want to update Kino and Livebook to the latest version. """) end defp render_virtualized_output(text, id, opts \\ []) do follow = Keyword.get(opts, :follow, false) lines = ansi_to_html_lines(text) assigns = %{lines: lines, id: id, follow: follow} ~L"""
""" end defp render_error_message_output(message) do assigns = %{message: message} ~L"""
<%= @message %>
""" end defp render_cell_status(cell_view, evaluation_status, evaluation_time_ms) defp render_cell_status(_, :evaluating, _) do render_status_indicator("Evaluating", "bg-blue-500", animated_circle_class: "bg-blue-400", change_indicator: true ) end defp render_cell_status(_, :queued, _) do render_status_indicator("Queued", "bg-gray-500", animated_circle_class: "bg-gray-400") end defp render_cell_status(:evaluated, _, evaluation_time_ms) do render_status_indicator("Evaluated", "bg-green-400", change_indicator: true, tooltip: evaluated_label(evaluation_time_ms) ) end defp render_cell_status(:stale, _, _) do render_status_indicator("Stale", "bg-yellow-200", change_indicator: true) end defp render_cell_status(:aborted, _, _) do render_status_indicator("Aborted", "bg-red-400") end defp render_cell_status(_, _, _), do: nil defp render_status_indicator(text, circle_class, opts \\ []) do assigns = %{ text: text, circle_class: circle_class, animated_circle_class: Keyword.get(opts, :animated_circle_class), change_indicator: Keyword.get(opts, :change_indicator, false), tooltip: Keyword.get(opts, :tooltip) } ~L"""
bottom distant-medium" aria-label="<%= @tooltip %>">
<%= @text %> <%= 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