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"""
<%= 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_markdown_content_placeholder(empty: @cell_view.empty?) %>
""" end def render_cell_content(%{cell_view: %{type: :elixir}} = assigns) do ~L"""
<%= if @cell_view.evaluation_status == :ready do %> <% else %> <% end %>
<%= 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) %>
<% end %>
""" end defp render_editor(assigns) do ~L"""
<%= render_editor_content_placeholder(empty: @cell_view.empty?) %>
<%= if @cell_view.type == :elixir do %>
<%= render_cell_status(@cell_view.validity_status, @cell_view.evaluation_status, @cell_view.changed?) %>
<% 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(empty: true) do assigns = %{} ~L"""
""" end defp render_markdown_content_placeholder(empty: false) do assigns = %{} ~L"""
""" end defp render_editor_content_placeholder(empty: true) do assigns = %{} ~L"""
""" end defp render_editor_content_placeholder(empty: false) do assigns = %{} ~L"""
""" end defp render_outputs(assigns) do ~L"""
<%= for {output, index} <- @cell_view.outputs |> Enum.reverse() |> Enum.with_index(), output != :ignored do %>
<%= render_output(output, "#{@cell_view.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(validity_status, evaluation_status, changed) 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(:aborted, _, _) do render_status_indicator("Aborted", "bg-red-400", nil, false) 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 %> ">*
<%= if @animated_circle_class do %> <% end %>
""" end end