Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for frame_dynamic output type #688

Merged
merged 1 commit into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/livebook/notebook/cell/elixir.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ defmodule Livebook.Notebook.Cell.Elixir do
| {:vega_lite_dynamic, widget_process :: pid()}
# Interactive data table
| {:table_dynamic, widget_process :: pid()}
# Dynamic wrapper for static output
| {:frame_dynamic, widget_process :: pid()}
# Internal output format for errors
| {:error, message :: binary(), type :: :other | :runtime_restart_required}

Expand Down
104 changes: 104 additions & 0 deletions lib/livebook_web/live/output.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
defmodule LivebookWeb.Output do
use Phoenix.Component

@doc """
Renders the given cell output.
"""
@spec render_output(Livebook.Cell.Elixir.output(), %{
id: String.t(),
socket: Phoenix.LiveView.Socket.t(),
runtime: Livebook.Runtime.t()
}) :: Phoenix.LiveView.Rendered.t()
def render_output(output, context)

def 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

def render_output({:text, text}, %{id: id}) do
live_component(LivebookWeb.Output.TextComponent, id: id, content: text, follow: false)
end

def render_output({:markdown, markdown}, %{id: id}) do
live_component(LivebookWeb.Output.MarkdownComponent, id: id, content: markdown)
end

def render_output({:image, content, mime_type}, %{id: id}) do
live_component(LivebookWeb.Output.ImageComponent,
id: id,
content: content,
mime_type: mime_type
)
end

def render_output({:vega_lite_static, spec}, %{id: id}) do
live_component(LivebookWeb.Output.VegaLiteStaticComponent, id: id, spec: spec)
end

def 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

def render_output({:table_dynamic, pid}, %{id: id, socket: socket}) do
live_render(socket, LivebookWeb.Output.TableDynamicLive,
id: id,
session: %{"id" => id, "pid" => pid}
)
end

def render_output({:frame_dynamic, pid}, %{id: id, socket: socket}) do
live_render(socket, LivebookWeb.Output.FrameDynamicLive,
id: id,
session: %{"id" => id, "pid" => pid}
)
end

def render_output({:error, formatted, :runtime_restart_required}, %{runtime: runtime})
when runtime != nil 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 button-gray" phx-click="restart_runtime">
Restart 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

def render_output({:error, formatted, _type}, %{}) do
render_error_message_output(formatted)
end

def 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
38 changes: 38 additions & 0 deletions lib/livebook_web/live/output/frame_dynamic.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
defmodule LivebookWeb.Output.FrameDynamicLive do
use LivebookWeb, :live_view

@impl true
def mount(_params, %{"pid" => pid, "id" => id}, socket) do
send(pid, {:connect, self()})

{:ok, assign(socket, id: id, output: nil)}
end

@impl true
def render(assigns) do
~H"""
<div>
<%= if @output do %>
<%= LivebookWeb.Output.render_output(@output, %{
id: "#{@id}-frame",
socket: @socket,
runtime: nil
}) %>
<% else %>
<div class="text-gray-300">
Empty output frame
</div>
<% end %>
</div>
"""
end

@impl true
def handle_info({:connect_reply, %{output: output}}, socket) do
{:noreply, assign(socket, output: output)}
end

def handle_info({:render, %{output: output}}, socket) do
{:noreply, assign(socket, output: output)}
end
end
88 changes: 1 addition & 87 deletions lib/livebook_web/live/session_live/cell_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -446,14 +446,12 @@ defmodule LivebookWeb.SessionLive.CellComponent do

defp evaluated_label(_time_ms), do: nil

# Outputs

defp outputs(assigns) do
~H"""
<div class="flex flex-col rounded-lg border border-gray-200 divide-y divide-gray-200">
<%= for {output, index} <- @cell_view.outputs |> Enum.reverse() |> Enum.with_index(), output != :ignored do %>
<div class="p-4 max-w-full overflow-y-auto tiny-scrollbar">
<%= render_output(output, %{
<%= LivebookWeb.Output.render_output(output, %{
id: "cell-#{@cell_view.id}-evaluation#{evaluation_number(@cell_view.evaluation_status, @cell_view.number_of_evaluations)}-output#{index}",
socket: @socket,
runtime: @runtime
Expand All @@ -466,88 +464,4 @@ defmodule LivebookWeb.SessionLive.CellComponent do

defp evaluation_number(:evaluating, number_of_evaluations), do: number_of_evaluations + 1
defp evaluation_number(_evaluation_status, number_of_evaluations), do: number_of_evaluations

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({:error, formatted, :runtime_restart_required}, %{runtime: runtime})
when runtime != nil 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 button-gray" phx-click="restart_runtime">
Restart 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