mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-12-18 22:21:32 +08:00
Fix frame updates containing inputs (#2219)
This commit is contained in:
parent
64dad900d7
commit
2a71e49d76
6 changed files with 77 additions and 18 deletions
|
|
@ -797,6 +797,12 @@ defmodule Livebook.Notebook do
|
||||||
{{counter, %{output | outputs: outputs}}, counter + 1}
|
{{counter, %{output | outputs: outputs}}, counter + 1}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp index_output(%{type: :frame_update} = output, counter) do
|
||||||
|
{update_type, new_outputs} = output.update
|
||||||
|
{new_outputs, counter} = index_outputs(new_outputs, counter)
|
||||||
|
{{counter, %{output | update: {update_type, new_outputs}}}, counter + 1}
|
||||||
|
end
|
||||||
|
|
||||||
defp index_output(output, counter) do
|
defp index_output(output, counter) do
|
||||||
{{counter, output}, counter + 1}
|
{{counter, output}, counter + 1}
|
||||||
end
|
end
|
||||||
|
|
@ -804,13 +810,13 @@ defmodule Livebook.Notebook do
|
||||||
@doc """
|
@doc """
|
||||||
Finds frame outputs matching the given ref.
|
Finds frame outputs matching the given ref.
|
||||||
"""
|
"""
|
||||||
@spec find_frame_outputs(t(), String.t()) :: list(Cell.indexed_output())
|
@spec find_frame_outputs(t(), String.t()) :: list({Cell.indexed_output(), Cell.t()})
|
||||||
def find_frame_outputs(notebook, frame_ref) do
|
def find_frame_outputs(notebook, frame_ref) do
|
||||||
for section <- all_sections(notebook),
|
for section <- all_sections(notebook),
|
||||||
%{outputs: outputs} <- section.cells,
|
%{outputs: outputs} = cell <- section.cells,
|
||||||
output <- outputs,
|
output <- outputs,
|
||||||
frame_output <- do_find_frame_outputs(output, frame_ref),
|
frame_output <- do_find_frame_outputs(output, frame_ref),
|
||||||
do: frame_output
|
do: {frame_output, cell}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_find_frame_outputs({_idx, %{type: :frame, ref: ref}} = output, ref) do
|
defp do_find_frame_outputs({_idx, %{type: :frame, ref: ref}} = output, ref) do
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,10 @@ defmodule Livebook.Notebook.Cell do
|
||||||
Enum.flat_map(output.outputs, &find_inputs_in_output/1)
|
Enum.flat_map(output.outputs, &find_inputs_in_output/1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def find_inputs_in_output({_idx, %{type: :frame_update, update: {_update_type, new_outputs}}}) do
|
||||||
|
Enum.flat_map(new_outputs, &find_inputs_in_output/1)
|
||||||
|
end
|
||||||
|
|
||||||
def find_inputs_in_output(_output), do: []
|
def find_inputs_in_output(_output), do: []
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
@ -78,6 +82,10 @@ defmodule Livebook.Notebook.Cell do
|
||||||
Enum.flat_map(output.outputs, &find_assets_in_output/1)
|
Enum.flat_map(output.outputs, &find_assets_in_output/1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def find_assets_in_output(%{type: :frame_update, update: {_update_type, new_outputs}}) do
|
||||||
|
Enum.flat_map(new_outputs, &find_assets_in_output/1)
|
||||||
|
end
|
||||||
|
|
||||||
def find_assets_in_output(_output), do: []
|
def find_assets_in_output(_output), do: []
|
||||||
|
|
||||||
@setup_cell_id "setup"
|
@setup_cell_id "setup"
|
||||||
|
|
|
||||||
|
|
@ -387,11 +387,14 @@ defmodule LivebookWeb.AppSessionLive do
|
||||||
{:add_cell_evaluation_output, _client_id, _cell_id, %{type: :frame_update} = output} ->
|
{:add_cell_evaluation_output, _client_id, _cell_id, %{type: :frame_update} = output} ->
|
||||||
%{ref: ref, update: {update_type, _}} = output
|
%{ref: ref, update: {update_type, _}} = output
|
||||||
|
|
||||||
for {idx, frame} <- Notebook.find_frame_outputs(data.notebook, ref) do
|
changed_input_ids = Session.Data.changed_input_ids(data)
|
||||||
|
|
||||||
|
for {{idx, frame} = output, _cell} <- Notebook.find_frame_outputs(data.notebook, ref) do
|
||||||
send_update(LivebookWeb.Output.FrameComponent,
|
send_update(LivebookWeb.Output.FrameComponent,
|
||||||
id: "output-#{idx}",
|
id: "output-#{idx}",
|
||||||
outputs: frame.outputs,
|
outputs: frame.outputs,
|
||||||
update_type: update_type
|
update_type: update_type,
|
||||||
|
input_views: input_views_for_output(output, data, changed_input_ids)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2784,13 +2784,19 @@ defmodule LivebookWeb.SessionLive do
|
||||||
# to the corresponding component, so the DOM patch is isolated and fast.
|
# to the corresponding component, so the DOM patch is isolated and fast.
|
||||||
# This is important for intensive output updates
|
# This is important for intensive output updates
|
||||||
{:add_cell_evaluation_output, _client_id, _cell_id, %{type: :frame_update} = output} ->
|
{:add_cell_evaluation_output, _client_id, _cell_id, %{type: :frame_update} = output} ->
|
||||||
%{ref: ref, update: {update_type, _}} = output
|
%{ref: ref, update: {update_type, _new_outputs}} = output
|
||||||
|
|
||||||
for {idx, frame} <- Notebook.find_frame_outputs(data.notebook, ref) do
|
changed_input_ids = Session.Data.changed_input_ids(data)
|
||||||
|
|
||||||
|
for {{idx, frame}, cell} <- Notebook.find_frame_outputs(data.notebook, ref) do
|
||||||
send_update(LivebookWeb.Output.FrameComponent,
|
send_update(LivebookWeb.Output.FrameComponent,
|
||||||
id: "output-#{idx}",
|
id: "output-#{idx}",
|
||||||
outputs: frame.outputs,
|
outputs: frame.outputs,
|
||||||
update_type: update_type
|
update_type: update_type,
|
||||||
|
# Note that we are not updating data_view to avoid re-render,
|
||||||
|
# but any change that causes frame to re-render will update
|
||||||
|
# data_view first
|
||||||
|
input_views: input_views_for_cell(cell, data, changed_input_ids)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -533,24 +533,20 @@ defmodule Livebook.NotebookTest do
|
||||||
test "returns frame outputs with matching ref" do
|
test "returns frame outputs with matching ref" do
|
||||||
frame_output = {0, %{type: :frame, ref: "1", outputs: [], placeholder: true}}
|
frame_output = {0, %{type: :frame, ref: "1", outputs: [], placeholder: true}}
|
||||||
|
|
||||||
notebook = %{
|
cell = %{Cell.new(:code) | outputs: [frame_output]}
|
||||||
Notebook.new()
|
notebook = %{Notebook.new() | sections: [%{Section.new() | cells: [cell]}]}
|
||||||
| sections: [%{Section.new() | cells: [%{Cell.new(:code) | outputs: [frame_output]}]}]
|
|
||||||
}
|
|
||||||
|
|
||||||
assert [^frame_output] = Notebook.find_frame_outputs(notebook, "1")
|
assert [{^frame_output, ^cell}] = Notebook.find_frame_outputs(notebook, "1")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "finds a nested frame" do
|
test "finds a nested frame" do
|
||||||
nested_frame_output = {0, %{type: :frame, ref: "2", outputs: [], placeholder: true}}
|
nested_frame_output = {0, %{type: :frame, ref: "2", outputs: [], placeholder: true}}
|
||||||
frame_output = {0, %{type: :frame, ref: "1", outputs: [nested_frame_output]}}
|
frame_output = {0, %{type: :frame, ref: "1", outputs: [nested_frame_output]}}
|
||||||
|
|
||||||
notebook = %{
|
cell = %{Cell.new(:code) | outputs: [frame_output]}
|
||||||
Notebook.new()
|
notebook = %{Notebook.new() | sections: [%{Section.new() | cells: [cell]}]}
|
||||||
| sections: [%{Section.new() | cells: [%{Cell.new(:code) | outputs: [frame_output]}]}]
|
|
||||||
}
|
|
||||||
|
|
||||||
assert [^nested_frame_output] = Notebook.find_frame_outputs(notebook, "2")
|
assert [{^nested_frame_output, ^cell}] = Notebook.find_frame_outputs(notebook, "2")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -761,6 +761,46 @@ defmodule LivebookWeb.SessionLiveTest do
|
||||||
|
|
||||||
assert render(view) =~ "This input has changed."
|
assert render(view) =~ "This input has changed."
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "frame output update with input", %{conn: conn, session: session, test: test} do
|
||||||
|
Session.subscribe(session.id)
|
||||||
|
evaluate_setup(session.pid)
|
||||||
|
|
||||||
|
section_id = insert_section(session.pid)
|
||||||
|
cell_id = insert_text_cell(session.pid, section_id, :code)
|
||||||
|
|
||||||
|
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||||
|
|
||||||
|
frame = %{type: :frame, ref: "1", outputs: [], placeholder: true}
|
||||||
|
send(session.pid, {:runtime_evaluation_output, cell_id, frame})
|
||||||
|
|
||||||
|
{:ok, view, _} = live(conn, ~p"/sessions/#{session.id}")
|
||||||
|
|
||||||
|
input = %{
|
||||||
|
type: :input,
|
||||||
|
ref: "ref1",
|
||||||
|
id: "input1",
|
||||||
|
destination: test,
|
||||||
|
attrs: %{type: :number, default: 1, label: "Input inside frame"}
|
||||||
|
}
|
||||||
|
|
||||||
|
frame_update = %{
|
||||||
|
type: :frame_update,
|
||||||
|
ref: "1",
|
||||||
|
update: {:replace, [input]}
|
||||||
|
}
|
||||||
|
|
||||||
|
send(session.pid, {:runtime_evaluation_output, cell_id, frame_update})
|
||||||
|
|
||||||
|
wait_for_session_update(session.pid)
|
||||||
|
|
||||||
|
# Render once, so that frame send_update is processed
|
||||||
|
_ = render(view)
|
||||||
|
|
||||||
|
content = render(view)
|
||||||
|
assert content =~ "Input inside frame"
|
||||||
|
assert has_element?(view, ~s/input[value="1"]/)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "smart cells" do
|
describe "smart cells" do
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue