Reflect input change when clicking the reevaluate button (#1269)

This commit is contained in:
Jonatan Kłosko 2022-07-11 13:35:05 +01:00 committed by GitHub
parent 1757e21bca
commit 75e47aa593
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 49 additions and 63 deletions

View file

@ -166,11 +166,14 @@ defmodule Livebook.Utils do
iex> Livebook.Utils.valid_url?("http://localhost") iex> Livebook.Utils.valid_url?("http://localhost")
true true
iex> Livebook.Utils.valid_url?("http://")
false
""" """
@spec valid_url?(String.t()) :: boolean() @spec valid_url?(String.t()) :: boolean()
def valid_url?(url) do def valid_url?(url) do
uri = URI.parse(url) uri = URI.parse(url)
uri.scheme != nil and uri.host != nil uri.scheme != nil and uri.host not in [nil, ""]
end end
@doc """ @doc """

View file

@ -3,7 +3,7 @@ defmodule LivebookWeb.Output.InputComponent do
@impl true @impl true
def mount(socket) do def mount(socket) do
{:ok, assign(socket, error: nil, local: false)} {:ok, assign(socket, local: false, counter: 0)}
end end
@impl true @impl true
@ -13,7 +13,7 @@ defmodule LivebookWeb.Output.InputComponent do
socket = socket =
socket socket
|> assign(assigns) |> assign(assigns)
|> assign(value: value, initial_value: value) |> assign(value: value)
{:ok, socket} {:ok, socket}
end end
@ -21,7 +21,7 @@ defmodule LivebookWeb.Output.InputComponent do
@impl true @impl true
def render(assigns) do def render(assigns) do
~H""" ~H"""
<form phx-change="change" phx-submit="submit" phx-target={@myself}> <form id={"#{@id}-form-#{@counter}"} phx-change="change" phx-submit="submit" phx-target={@myself}>
<div class="input-label"> <div class="input-label">
<%= @attrs.label %> <%= @attrs.label %>
</div> </div>
@ -30,14 +30,7 @@ defmodule LivebookWeb.Output.InputComponent do
id={"#{@id}-input"} id={"#{@id}-input"}
attrs={@attrs} attrs={@attrs}
value={@value} value={@value}
error={@error}
myself={@myself} /> myself={@myself} />
<%= if @error do %>
<div class="input-error">
<%= @error %>
</div>
<% end %>
</form> </form>
""" """
end end
@ -77,8 +70,7 @@ defmodule LivebookWeb.Output.InputComponent do
class="input-range" class="input-range"
name="value" name="value"
value={@value} value={@value}
phx-debounce="300" phx-debounce="blur"
phx-blur="blur"
phx-target={@myself} phx-target={@myself}
spellcheck="false" spellcheck="false"
autocomplete="off" autocomplete="off"
@ -96,8 +88,7 @@ defmodule LivebookWeb.Output.InputComponent do
data-el-input data-el-input
class="input min-h-[200px] tiny-scrollbar" class="input min-h-[200px] tiny-scrollbar"
name="value" name="value"
phx-debounce="300" phx-debounce="blur"
phx-blur="blur"
phx-target={@myself} phx-target={@myself}
spellcheck="false"><%= [?\n, @value] %></textarea> spellcheck="false"><%= [?\n, @value] %></textarea>
""" """
@ -111,8 +102,7 @@ defmodule LivebookWeb.Output.InputComponent do
class="input w-auto bg-gray-50" class="input w-auto bg-gray-50"
name="value" name="value"
value={@value} value={@value}
phx-debounce="300" phx-debounce="blur"
phx-blur="blur"
phx-target={@myself} phx-target={@myself}
spellcheck="false" spellcheck="false"
autocomplete="off" /> autocomplete="off" />
@ -124,11 +114,10 @@ defmodule LivebookWeb.Output.InputComponent do
~H""" ~H"""
<input type={html_input_type(@attrs.type)} <input type={html_input_type(@attrs.type)}
data-el-input data-el-input
class={"input w-auto #{if(@error, do: "input--error")}"} class="input w-auto invalid:input--error"
name="value" name="value"
value={to_string(@value)} value={to_string(@value)}
phx-debounce="300" phx-debounce="blur"
phx-blur="blur"
phx-target={@myself} phx-target={@myself}
spellcheck="false" spellcheck="false"
autocomplete="off" /> autocomplete="off" />
@ -145,62 +134,56 @@ defmodule LivebookWeb.Output.InputComponent do
defp html_input_type(:number), do: "number" defp html_input_type(:number), do: "number"
defp html_input_type(:color), do: "color" defp html_input_type(:color), do: "color"
defp html_input_type(:url), do: "text" defp html_input_type(:url), do: "url"
defp html_input_type(:text), do: "text" defp html_input_type(:text), do: "text"
@impl true @impl true
def handle_event("change", %{"value" => html_value}, socket) do def handle_event("change", %{"value" => html_value}, socket) do
socket = handle_html_value(socket, html_value) case handle_change(socket, html_value) do
{:ok, socket} ->
{:noreply, socket}
socket = :error ->
if report_immediately?(socket.assigns.attrs.type) do # Force the current value
maybe_report_change(socket) {:noreply, update(socket, :counter, &(&1 + 1))}
else
socket
end
{:noreply, socket}
end
def handle_event("blur", %{"value" => html_value}, socket) do
socket = socket |> handle_html_value(html_value) |> maybe_report_change()
if socket.assigns.error do
{:noreply, assign(socket, value: socket.assigns.initial_value, error: nil)}
else
{:noreply, socket}
end end
end end
def handle_event("submit", %{"value" => html_value}, socket) do def handle_event("submit", %{"value" => html_value}, socket) do
socket = socket |> handle_html_value(html_value) |> maybe_report_change() case handle_change(socket, html_value) do
send(self(), {:queue_bound_cells_evaluation, socket.assigns.attrs.id}) {:ok, socket} ->
{:noreply, socket} send(self(), {:queue_bound_cells_evaluation, socket.assigns.attrs.id})
end {:noreply, socket}
defp handle_html_value(socket, html_value) do :error ->
case parse(html_value, socket.assigns.attrs) do {:noreply, socket}
{:ok, value} ->
assign(socket, value: value, error: nil)
{:error, error, value} ->
assign(socket, value: value, error: error)
end end
end end
defp report_immediately?(type), do: type in [:select, :checkbox] defp handle_change(socket, html_value) do
case parse(html_value, socket.assigns.attrs) do
{:ok, value} ->
prev_value = socket.assigns.value
defp maybe_report_change(socket) when socket.assigns.error != nil, do: socket socket = assign(socket, value: value)
defp maybe_report_change(%{assigns: %{value: value, initial_value: value}} = socket), do: socket
defp maybe_report_change(%{assigns: assigns} = socket) do if value != prev_value do
report_change(socket)
end
{:ok, socket}
{:error, _error} ->
:error
end
end
defp report_change(%{assigns: assigns} = socket) do
send(self(), {:set_input_values, [{assigns.attrs.id, assigns.value}], assigns.local}) send(self(), {:set_input_values, [{assigns.attrs.id, assigns.value}], assigns.local})
unless assigns.local do unless assigns.local do
report_event(socket, assigns.value) report_event(socket, assigns.value)
end end
socket
end end
defp parse(html_value, %{type: :text}) do defp parse(html_value, %{type: :text}) do
@ -236,7 +219,7 @@ defmodule LivebookWeb.Output.InputComponent do
cond do cond do
html_value == "" -> {:ok, nil} html_value == "" -> {:ok, nil}
Livebook.Utils.valid_url?(html_value) -> {:ok, html_value} Livebook.Utils.valid_url?(html_value) -> {:ok, html_value}
true -> {:error, "not a valid URL", html_value} true -> {:error, "not a valid URL"}
end end
end end

View file

@ -245,8 +245,8 @@ defmodule LivebookWeb.SessionLiveTest do
{:ok, view, _} = live(conn, "/sessions/#{session.id}") {:ok, view, _} = live(conn, "/sessions/#{session.id}")
view view
|> element(~s/[data-el-outputs-container] input/) |> element(~s/[data-el-outputs-container] form/)
|> render_blur(%{"value" => "10"}) |> render_change(%{"value" => "10"})
assert %{input_values: %{"input1" => 10}} = Session.get_data(session.pid) assert %{input_values: %{"input1" => 10}} = Session.get_data(session.pid)
@ -274,8 +274,8 @@ defmodule LivebookWeb.SessionLiveTest do
{:ok, view, _} = live(conn, "/sessions/#{session.id}") {:ok, view, _} = live(conn, "/sessions/#{session.id}")
view view
|> element(~s/[data-el-outputs-container] textarea/) |> element(~s/[data-el-outputs-container] form/)
|> render_blur(%{"value" => "line\r\nline"}) |> render_change(%{"value" => "line\r\nline"})
assert %{input_values: %{"input1" => "line\nline"}} = Session.get_data(session.pid) assert %{input_values: %{"input1" => "line\nline"}} = Session.get_data(session.pid)
end end
@ -312,8 +312,8 @@ defmodule LivebookWeb.SessionLiveTest do
{:ok, view, _} = live(conn, "/sessions/#{session.id}") {:ok, view, _} = live(conn, "/sessions/#{session.id}")
view view
|> element(~s/[data-el-outputs-container] input/) |> element(~s/[data-el-outputs-container] form/)
|> render_blur(%{"value" => "sherlock"}) |> render_change(%{"value" => "sherlock"})
# The new value is on the page # The new value is on the page
assert render(view) =~ "sherlock" assert render(view) =~ "sherlock"