defmodule LivebookWeb.CellComponent do use LivebookWeb, :live_component def render(assigns) do ~L"""
<%= render_cell_content(assigns) %>
""" end def render_cell_content(%{cell: %{type: :markdown}} = assigns) do ~L"""
<%= render_editor(assigns) %>
<%= render_markdown_content_placeholder(@cell.source) %>
""" end def render_cell_content(%{cell: %{type: :elixir}} = assigns) do ~L"""
<%= if @cell_info.evaluation_status == :ready do %> <% else %> <% end %>
<%= live_patch to: Routes.session_path(@socket, :cell_settings, @session_id, @cell.id), class: "icon-button" do %> <%= remix_icon("list-settings-line", class: "text-xl") %> <% end %>
<%= render_editor(assigns) %> <%= if @cell.outputs != [] do %>
<%= render_outputs(assigns) %>
<% end %>
""" end defp render_editor(assigns) do ~L"""
<%= render_editor_content_placeholder(@cell.source) %>
<%= if @cell.type == :elixir do %>
<%= render_cell_status( @cell_info.validity_status, @cell_info.evaluation_status, @cell_info.digest != @cell_info.evaluation_digest ) %>
<% end %>
""" end # The whole page has to load and then hooks are mounded. # There may be a tiny delay before the markdown is rendered # or and editors are mounted, so show neat placeholders immediately. defp render_markdown_content_placeholder("" = _content) do assigns = %{} ~L"""
""" end defp render_markdown_content_placeholder(_content) do assigns = %{} ~L"""
""" end defp render_editor_content_placeholder("" = _content) do assigns = %{} ~L"""
""" end defp render_editor_content_placeholder(_content) do assigns = %{} ~L"""
""" end defp render_outputs(assigns) do ~L"""
<%= for {output, index} <- @cell.outputs |> Enum.reverse() |> Enum.with_index(), output != :ignored do %>
<%= render_output(output, "#{@cell.id}-output#{index}") %>
<% end %>
""" end defp render_output(output, id) when is_binary(output) do # Captured output usually has a trailing newline that we can ignore, # because each line is itself a block anyway. output = String.replace_suffix(output, "\n", "") lines = ansi_to_html_lines(output) assigns = %{lines: lines, id: id} ~L"""
""" end defp render_output({:inspect, inspected}, id) do lines = ansi_to_html_lines(inspected) assigns = %{lines: lines, id: id} ~L"""
""" end defp render_output({:error, formatted}, _id) do assigns = %{formatted: formatted} ~L"""
<%= @formatted %>
""" end defp ansi_to_html_lines(string) do string |> ansi_string_to_html( # Make sure every line is styled separately, # so tht later we can safely split the whole HTML # into valid HTML lines. renderer: fn style, content -> content |> IO.iodata_to_binary() |> String.split("\n") |> Enum.map(&[~s{}, &1, ~s{}]) |> Enum.intersperse("\n") end ) |> Phoenix.HTML.safe_to_string() |> String.split("\n") end defp render_cell_status(_, :evaluating, changed) do render_status_indicator( "Evaluating", "bg-blue-500", "bg-blue-400", changed ) end defp render_cell_status(_, :queued, _) do render_status_indicator("Queued", "bg-gray-500", "bg-gray-400", false) end defp render_cell_status(:evaluated, _, changed) do render_status_indicator( "Evaluated", "bg-green-400", nil, changed ) end defp render_cell_status(:stale, _, changed) do render_status_indicator("Stale", "bg-yellow-200", nil, changed) end defp render_cell_status(_, _, _), do: nil defp render_status_indicator(text, circle_class, animated_circle_class, show_changed) do assigns = %{ text: text, circle_class: circle_class, animated_circle_class: animated_circle_class, show_changed: show_changed } ~L"""
<%= @text %> ">*
""" end end