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_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 %>
<%= 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 %>
" 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_editor_content_placeholder(empty: @cell_view.empty?) %>
<%= if @cell_view.type == :elixir do %>
<%= render_cell_status(@cell_view.validity_status, @cell_view.evaluation_status) %>
<% 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_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, 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, {: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(validity_status, evaluation_status) 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, _) do render_status_indicator("Evaluated", "bg-green-400", change_indicator: true) 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) } ~L"""
<%= @text %> <%= if @change_indicator do %> * <% end %>
<%= if @animated_circle_class do %> <% end %>
""" end end