defmodule LivebookWeb.SessionLive.InputCellSettingsComponent do
use LivebookWeb, :live_component
alias Livebook.Session
alias Livebook.Notebook.Cell
@impl true
def update(assigns, socket) do
cell = assigns.cell
socket =
socket
|> assign(assigns)
|> assign(:current_type, cell.type)
|> assign_new(:attrs, fn ->
Map.take(cell, [:name, :type, :props])
end)
|> assign_new(:valid, fn -> true end)
{:ok, socket}
end
@impl true
def render(assigns) do
~H"""
"""
end
defp extra_fields(%{type: :range} = assigns) do
~H"""
"""
end
defp extra_fields(%{type: :select} = assigns) do
~H"""
Options
<%= for {option, idx} <- Enum.with_index(@props.options) do %>
<% end %>
"""
end
defp extra_fields(assigns), do: ~H""
@impl true
def handle_event("validate", params, socket) do
{valid?, attrs} = validate_attrs(params["attrs"], socket.assigns.attrs)
{:noreply, socket |> assign(attrs: attrs) |> assign(:valid, valid?)}
end
def handle_event("save", params, socket) do
{true, attrs} = validate_attrs(params["attrs"], socket.assigns.attrs)
attrs =
if attrs.type != socket.assigns.current_type do
Map.put(attrs, :value, default_value(attrs.type, attrs.props))
else
attrs
end
Session.set_cell_attributes(socket.assigns.session.pid, socket.assigns.cell.id, attrs)
{:noreply, push_patch(socket, to: socket.assigns.return_to)}
end
def handle_event("select_options_action", params, socket) do
{action, params} = Map.pop!(params, "action")
attrs = socket.assigns.attrs
options = select_options_action(action, params, attrs.props.options)
attrs = put_in(attrs.props.options, options)
valid? = valid_options?(options)
{:noreply, socket |> assign(attrs: attrs) |> assign(valid: valid?)}
end
defp select_options_action("add", _params, options) do
options ++ [""]
end
defp select_options_action("delete", %{"index" => index}, options) do
index = String.to_integer(index)
List.delete_at(options, index)
end
defp validate_attrs(data, prev_attrs) do
name = data["name"]
type = data["type"] |> String.to_existing_atom()
{props_valid?, props} =
if type == prev_attrs.type do
data |> Map.get("props", %{}) |> validate_props(type)
else
{true, Cell.Input.default_props(type)}
end
valid? = name != "" and props_valid?
{valid?, %{name: name, type: type, props: props}}
end
defp validate_props(data, :range) do
min = parse_number(data["min"])
max = parse_number(data["max"])
step = parse_number(data["step"])
valid? = min != nil and max != nil and step != nil and min < max and step > 0
data = %{min: min, max: max, step: step}
{valid?, data}
end
defp validate_props(data, :select) do
options = data["options"] || []
valid? = valid_options?(options)
{valid?, %{options: options}}
end
defp validate_props(_data, _type) do
{true, %{}}
end
defp valid_options?(options) do
options != [] and options == Enum.uniq(options)
end
defp parse_number(string) do
case Float.parse(string) do
{number, _} ->
integer = round(number)
if integer == number, do: integer, else: number
:error ->
nil
end
end
defp default_value(:checkbox, _props), do: "false"
defp default_value(:color, _props), do: "#3E64FF"
defp default_value(:range, %{min: min}), do: to_string(min)
defp default_value(:select, %{options: [option | _]}), do: option
defp default_value(_type, _props), do: ""
defp input_types do
[
checkbox: "Checkbox",
color: "Color",
number: "Number",
password: "Password",
text: "Text",
textarea: "Textarea",
url: "URL",
range: "Range",
select: "Select"
]
end
end