Packages
livebook
0.4.1
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook_web/live/output.ex
defmodule LivebookWeb.Output do
use Phoenix.Component
@doc """
Renders a list of cell outputs.
"""
def outputs(assigns) do
~H"""
<div class="flex flex-col space-y-2">
<%= for {{outputs, standalone?}, group_idx} <- @outputs |> group_outputs() |> Enum.with_index() do %>
<div class={"flex flex-col #{if not standalone?, do: "rounded-lg border border-gray-200 divide-y divide-gray-200"}"}>
<%= for {output, idx} <- Enum.with_index(outputs) do %>
<div class={"max-w-full #{if not standalone?, do: "px-4"} #{if not composite?(output), do: "py-4"}"}>
<%= render_output(output, %{
id: "#{@id}-output#{group_idx}_#{idx}",
socket: @socket,
runtime: @runtime,
cell_validity_status: @cell_validity_status,
input_values: @input_values
}) %>
</div>
<% end %>
</div>
<% end %>
</div>
"""
end
defp group_outputs(outputs) do
outputs = Enum.filter(outputs, &(&1 != :ignored))
group_outputs(outputs, [])
end
defp group_outputs([], groups), do: groups
defp group_outputs([output | outputs], []) do
group_outputs(outputs, [{[output], standalone?(output)}])
end
defp group_outputs([output | outputs], [{group_outputs, group_standalone?} | groups]) do
case standalone?(output) do
^group_standalone? ->
group_outputs(outputs, [{[output | group_outputs], group_standalone?} | groups])
standalone? ->
group_outputs(
outputs,
[{[output], standalone?}, {group_outputs, group_standalone?} | groups]
)
end
end
defp standalone?({:table_dynamic, _}), do: true
defp standalone?({:frame_dynamic, _}), do: true
defp standalone?({:input, _}), do: true
defp standalone?(_output), do: false
defp composite?({:frame_dynamic, _}), do: true
defp composite?(_output), do: false
defp render_output(text, %{id: id}) when is_binary(text) do
# Captured output usually has a trailing newline that we can ignore,
# because each line is itself an HTML block anyway.
text = String.replace_suffix(text, "\n", "")
live_component(LivebookWeb.Output.TextComponent, id: id, content: text, follow: true)
end
defp render_output({:text, text}, %{id: id}) do
live_component(LivebookWeb.Output.TextComponent, id: id, content: text, follow: false)
end
defp render_output({:markdown, markdown}, %{id: id}) do
live_component(LivebookWeb.Output.MarkdownComponent, id: id, content: markdown)
end
defp render_output({:image, content, mime_type}, %{id: id}) do
live_component(LivebookWeb.Output.ImageComponent,
id: id,
content: content,
mime_type: mime_type
)
end
defp render_output({:vega_lite_static, spec}, %{id: id}) do
live_component(LivebookWeb.Output.VegaLiteStaticComponent, id: id, spec: spec)
end
defp render_output({:vega_lite_dynamic, pid}, %{id: id, socket: socket}) do
live_render(socket, LivebookWeb.Output.VegaLiteDynamicLive,
id: id,
session: %{"id" => id, "pid" => pid}
)
end
defp render_output({:table_dynamic, pid}, %{id: id, socket: socket}) do
live_render(socket, LivebookWeb.Output.TableDynamicLive,
id: id,
session: %{"id" => id, "pid" => pid}
)
end
defp render_output({:frame_dynamic, pid}, %{
id: id,
socket: socket,
input_values: input_values,
cell_validity_status: cell_validity_status
}) do
live_render(socket, LivebookWeb.Output.FrameDynamicLive,
id: id,
session: %{
"id" => id,
"pid" => pid,
"input_values" => input_values,
"cell_validity_status" => cell_validity_status
}
)
end
defp render_output({:input, attrs}, %{id: id, input_values: input_values}) do
live_component(LivebookWeb.Output.InputComponent,
id: id,
attrs: attrs,
input_values: input_values
)
end
defp render_output({:control, attrs}, %{id: id}) do
live_component(LivebookWeb.Output.ControlComponent, id: id, attrs: attrs)
end
defp render_output({:error, formatted, :runtime_restart_required}, %{
runtime: runtime,
cell_validity_status: cell_validity_status
})
when runtime != nil and cell_validity_status == :evaluated do
assigns = %{formatted: formatted, is_standalone: Livebook.Runtime.standalone?(runtime)}
~H"""
<div class="flex flex-col space-y-4">
<%= render_error_message_output(@formatted) %>
<%= if @is_standalone do %>
<div>
<button class="button-base button-gray" phx-click="restart_runtime">
Reconnect runtime
</button>
</div>
<% else %>
<div class="text-red-600">
<span class="font-semibold">Note:</span>
This operation requires restarting the runtime, but we cannot
do it automatically for the current runtime
</div>
<% end %>
</div>
"""
end
defp render_output({:error, formatted, _type}, %{}) do
render_error_message_output(formatted)
end
defp render_output(output, %{}) 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_error_message_output(message) do
assigns = %{message: message}
~H"""
<div class="overflow-auto whitespace-pre text-red-600 tiny-scrollbar"><%= @message %></div>
"""
end
end