From 05418db0c6c30e1a64643fb36b10ecc8791d9647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Tue, 26 Sep 2023 10:38:15 +0700 Subject: [PATCH] Add :debounce to input outputs when missing (#2228) --- lib/livebook/runtime.ex | 17 +++++-- lib/livebook/session.ex | 50 +++++++++++++++++-- .../live/output/input_component.ex | 8 +-- test/livebook/session/data_test.exs | 6 +-- test/livebook/session_test.exs | 4 +- test/livebook_web/live/session_live_test.exs | 12 +++-- 6 files changed, 75 insertions(+), 22 deletions(-) diff --git a/lib/livebook/runtime.ex b/lib/livebook/runtime.ex index 96c7c461a..f27f6c5f3 100644 --- a/lib/livebook/runtime.ex +++ b/lib/livebook/runtime.ex @@ -298,28 +298,33 @@ defprotocol Livebook.Runtime do %{ type: :text, default: String.t(), - label: String.t() + label: String.t(), + debounce: :blur | non_neg_integer() } | %{ type: :textarea, default: String.t(), label: String.t(), + debounce: :blur | non_neg_integer(), monospace: boolean() } | %{ type: :password, default: String.t(), - label: String.t() + label: String.t(), + debounce: :blur | non_neg_integer() } | %{ type: :number, default: number() | nil, - label: String.t() + label: String.t(), + debounce: :blur | non_neg_integer() } | %{ type: :url, default: String.t() | nil, - label: String.t() + label: String.t(), + debounce: :blur | non_neg_integer() } | %{ type: :select, @@ -336,6 +341,7 @@ defprotocol Livebook.Runtime do type: :range, default: number(), label: String.t(), + debounce: :blur | non_neg_integer(), min: number(), max: number(), step: number() @@ -364,7 +370,8 @@ defprotocol Livebook.Runtime do | %{ type: :color, default: String.t(), - label: String.t() + label: String.t(), + debounce: :blur | non_neg_integer() } | %{ type: :image, diff --git a/lib/livebook/session.ex b/lib/livebook/session.ex index 80806987a..b1a3ed6c9 100644 --- a/lib/livebook/session.ex +++ b/lib/livebook/session.ex @@ -2886,39 +2886,70 @@ defmodule Livebook.Session do end end - # Maps legacy outputs and adds missing attributes + # Normalizes output to match the most recent specification. + # + # Rewrites legacy output formats and adds defaults for newly introduced + # attributes that are missing. + defp normalize_runtime_output(output) + + defp normalize_runtime_output(%{type: :input, attrs: attrs} = output) + when attrs.type in [:text, :textarea, :password, :number, :url, :range, :color] and + not is_map_key(attrs, :debounce) do + put_in(output.attrs[:debounce], :blur) + |> normalize_runtime_output() + end + + # Traverse composite outputs + + defp normalize_runtime_output(output) when output.type in [:frame, :tabs, :grid] do + outputs = Enum.map(output.outputs, &normalize_runtime_output/1) + %{output | outputs: outputs} + end + + defp normalize_runtime_output(%{type: :frame_update} = output) do + {update_type, new_outputs} = output.update + new_outputs = Enum.map(new_outputs, &normalize_runtime_output/1) + %{output | update: {update_type, new_outputs}} + end + defp normalize_runtime_output(output) when is_map(output), do: output + # Rewrite tuples to maps for backward compatibility with Kino <= 0.10.0 + defp normalize_runtime_output(:ignored) do %{type: :ignored} + |> normalize_runtime_output() end - # Rewrite tuples to maps for backward compatibility with Kino <= 0.10.0 defp normalize_runtime_output({:text, text}) do %{type: :terminal_text, text: text, chunk: false} + |> normalize_runtime_output() end defp normalize_runtime_output({:plain_text, text}) do %{type: :plain_text, text: text, chunk: false} + |> normalize_runtime_output() end defp normalize_runtime_output({:markdown, text}) do %{type: :markdown, text: text, chunk: false} + |> normalize_runtime_output() end defp normalize_runtime_output({:image, content, mime_type}) do %{type: :image, content: content, mime_type: mime_type} + |> normalize_runtime_output() end # Rewrite older output format for backward compatibility with Kino <= 0.5.2 defp normalize_runtime_output({:js, %{ref: ref, pid: pid, assets: assets, export: export}}) do - normalize_runtime_output( - {:js, %{js_view: %{ref: ref, pid: pid, assets: assets}, export: export}} - ) + {:js, %{js_view: %{ref: ref, pid: pid, assets: assets}, export: export}} + |> normalize_runtime_output() end defp normalize_runtime_output({:js, info}) do %{type: :js, js_view: info.js_view, export: info.export} + |> normalize_runtime_output() end defp normalize_runtime_output({:frame, outputs, %{ref: ref, type: :default} = info}) do @@ -2928,6 +2959,7 @@ defmodule Livebook.Session do outputs: Enum.map(outputs, &normalize_runtime_output/1), placeholder: Map.get(info, :placeholder, true) } + |> normalize_runtime_output() end defp normalize_runtime_output({:frame, outputs, %{ref: ref, type: :replace}}) do @@ -2936,6 +2968,7 @@ defmodule Livebook.Session do ref: ref, update: {:replace, Enum.map(outputs, &normalize_runtime_output/1)} } + |> normalize_runtime_output() end defp normalize_runtime_output({:frame, outputs, %{ref: ref, type: :append}}) do @@ -2944,10 +2977,12 @@ defmodule Livebook.Session do ref: ref, update: {:append, Enum.map(outputs, &normalize_runtime_output/1)} } + |> normalize_runtime_output() end defp normalize_runtime_output({:tabs, outputs, %{labels: labels}}) do %{type: :tabs, outputs: Enum.map(outputs, &normalize_runtime_output/1), labels: labels} + |> normalize_runtime_output() end defp normalize_runtime_output({:grid, outputs, info}) do @@ -2958,6 +2993,7 @@ defmodule Livebook.Session do gap: Map.get(info, :gap, 8), boxed: Map.get(info, :boxed, false) } + |> normalize_runtime_output() end defp normalize_runtime_output({:input, attrs}) do @@ -2970,6 +3006,7 @@ defmodule Livebook.Session do end Map.merge(fields, %{type: :input, attrs: attrs}) + |> normalize_runtime_output() end defp normalize_runtime_output({:control, attrs}) do @@ -2992,6 +3029,7 @@ defmodule Livebook.Session do end Map.merge(fields, %{type: :control, attrs: attrs}) + |> normalize_runtime_output() end defp normalize_runtime_output({:error, message, type}) do @@ -3002,9 +3040,11 @@ defmodule Livebook.Session do end %{type: :error, message: message, context: context} + |> normalize_runtime_output() end defp normalize_runtime_output(other) do %{type: :unknown, output: other} + |> normalize_runtime_output() end end diff --git a/lib/livebook_web/live/output/input_component.ex b/lib/livebook_web/live/output/input_component.ex index a0b372a50..f0763ca00 100644 --- a/lib/livebook_web/live/output/input_component.ex +++ b/lib/livebook_web/live/output/input_component.ex @@ -169,7 +169,7 @@ defmodule LivebookWeb.Output.InputComponent do class="input-range" name="html_value" value={@value} - phx-debounce={@attrs[:debounce] || "blur"} + phx-debounce={@attrs.debounce} phx-target={@myself} spellcheck="false" autocomplete="off" @@ -190,7 +190,7 @@ defmodule LivebookWeb.Output.InputComponent do class={["input min-h-[38px] max-h-[300px] tiny-scrollbar", @attrs.monospace && "font-mono"]} name="html_value" phx-hook="TextareaAutosize" - phx-debounce={@attrs[:debounce] || "blur"} + phx-debounce={@attrs.debounce} phx-target={@myself} spellcheck="false" ><%= [?\n, @value] %> @@ -206,7 +206,7 @@ defmodule LivebookWeb.Output.InputComponent do class="input w-auto bg-gray-50" name="html_value" value={@value} - phx-debounce={@attrs[:debounce] || "blur"} + phx-debounce={@attrs.debounce} phx-target={@myself} spellcheck="false" autocomplete="off" @@ -241,7 +241,7 @@ defmodule LivebookWeb.Output.InputComponent do class="input w-auto invalid:input--error" name="html_value" value={to_string(@value)} - phx-debounce={@attrs[:debounce] || "blur"} + phx-debounce={@attrs.debounce} phx-target={@myself} spellcheck="false" autocomplete="off" diff --git a/test/livebook/session/data_test.exs b/test/livebook/session/data_test.exs index 717a5ea11..93abbb899 100644 --- a/test/livebook/session/data_test.exs +++ b/test/livebook/session/data_test.exs @@ -18,7 +18,7 @@ defmodule Livebook.Session.DataTest do ref: "ref1", id: "i1", destination: nil, - attrs: %{type: :text, default: "hey", label: "Text"} + attrs: %{type: :text, default: "hey", label: "Text", debounce: :blur} } defp eval_meta(opts \\ []) do @@ -4510,7 +4510,7 @@ defmodule Livebook.Session.DataTest do ref: "ref1", id: "i1", destination: nil, - attrs: %{type: :text, default: "hey", label: "Text"} + attrs: %{type: :text, default: "hey", label: "Text", debounce: :blur} } input2 = %{ @@ -4518,7 +4518,7 @@ defmodule Livebook.Session.DataTest do ref: "ref2", id: "i2", destination: nil, - attrs: %{type: :text, default: "hey", label: "Text"} + attrs: %{type: :text, default: "hey", label: "Text", debounce: :blur} } data = diff --git a/test/livebook/session_test.exs b/test/livebook/session_test.exs index cb057b6b8..be2aedbda 100644 --- a/test/livebook/session_test.exs +++ b/test/livebook/session_test.exs @@ -219,7 +219,7 @@ defmodule Livebook.SessionTest do ref: "ref", id: "input1", destination: :noop, - attrs: %{type: :text, default: "hey", label: "Name"} + attrs: %{type: :text, default: "hey", label: "Name", debounce: :blur} } smart_cell = %{ @@ -1899,7 +1899,7 @@ defmodule Livebook.SessionTest do ref: "ref", id: "input1", destination: :noop, - attrs: %{type: :text, default: "hey", label: "Name"} + attrs: %{type: :text, default: "hey", label: "Name", debounce: :blur} } send(session.pid, {:runtime_evaluation_output, cell_id, legacy_output}) diff --git a/test/livebook_web/live/session_live_test.exs b/test/livebook_web/live/session_live_test.exs index 554e6d63e..d18435941 100644 --- a/test/livebook_web/live/session_live_test.exs +++ b/test/livebook_web/live/session_live_test.exs @@ -518,7 +518,13 @@ defmodule LivebookWeb.SessionLiveTest do ref: "ref1", id: "input1", destination: test, - attrs: %{type: :textarea, default: "hey", label: "Name", monospace: false} + attrs: %{ + type: :textarea, + default: "hey", + label: "Name", + debounce: :blur, + monospace: false + } } Session.subscribe(session.id) @@ -552,7 +558,7 @@ defmodule LivebookWeb.SessionLiveTest do ref: "input_ref1", id: "input1", destination: test, - attrs: %{type: :text, default: "initial", label: "Name"} + attrs: %{type: :text, default: "initial", label: "Name", debounce: :blur} } ], submit: "Send", @@ -781,7 +787,7 @@ defmodule LivebookWeb.SessionLiveTest do ref: "ref1", id: "input1", destination: test, - attrs: %{type: :number, default: 1, label: "Input inside frame"} + attrs: %{type: :number, default: 1, label: "Input inside frame", debounce: :blur} } frame_update = %{